remove metrics from assignment v2
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::Reports::AssignmentMetricsController < Api::V1::Accounts::BaseController
|
||||
include AssignmentMetricsHelper
|
||||
include DistributionMetrics
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :validate_date_range
|
||||
|
||||
def index
|
||||
@metrics = metrics_service.compute_assignment_metrics
|
||||
render json: { assignment_metrics: @metrics }
|
||||
end
|
||||
|
||||
def agent_history
|
||||
history_service = Reports::AgentHistoryService.new(Current.account, params)
|
||||
|
||||
if params[:agent_id].present?
|
||||
@agent = Current.account.users.find(params[:agent_id])
|
||||
@assignment_history = history_service.fetch_agent_assignment_history(@agent)
|
||||
|
||||
render json: {
|
||||
agent: serialize_agent(@agent),
|
||||
assignment_history: @assignment_history,
|
||||
meta: pagination_meta
|
||||
}
|
||||
else
|
||||
agents_summary = history_service.compute_all_agents_history
|
||||
render json: { agents_history: agents_summary }
|
||||
end
|
||||
end
|
||||
|
||||
def policy_performance
|
||||
@policies = Current.account.assignment_policies.includes(:inboxes)
|
||||
performance_data = @policies.map do |policy|
|
||||
metrics_service.compute_policy_performance(policy)
|
||||
end
|
||||
|
||||
render json: { policy_performance: performance_data }
|
||||
end
|
||||
|
||||
def agent_utilization
|
||||
agents = Current.account.users.joins(:account_users).where(account_users: { role: %w[agent administrator] })
|
||||
utilization_data = agents.map do |agent|
|
||||
metrics_service.compute_agent_utilization(agent)
|
||||
end
|
||||
|
||||
render json: { agent_utilization: utilization_data }
|
||||
end
|
||||
|
||||
def assignment_distribution
|
||||
distribution_data = {
|
||||
by_inbox: metrics_service.compute_distribution_by_inbox,
|
||||
by_team: metrics_service.compute_distribution_by_team,
|
||||
by_hour: metrics_service.compute_distribution_by_hour,
|
||||
by_day_of_week: compute_distribution_by_day_of_week
|
||||
}
|
||||
|
||||
render json: { assignment_distribution: distribution_data }
|
||||
end
|
||||
|
||||
def export
|
||||
type = params[:type] || 'csv'
|
||||
data = metrics_service.compute_assignment_metrics
|
||||
|
||||
case type
|
||||
when 'csv'
|
||||
export_service = Reports::AssignmentExportService.new(data)
|
||||
send_data export_service.generate_csv, filename: "assignment_metrics_#{Date.current}.csv", type: 'text/csv'
|
||||
when 'json'
|
||||
send_data data.to_json, filename: "assignment_metrics_#{Date.current}.json", type: 'application/json'
|
||||
else
|
||||
render json: { error: 'Unsupported export type' }, status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_date_range
|
||||
return if params[:since].blank? || params[:until].blank?
|
||||
|
||||
start_date = Date.parse(params[:since])
|
||||
end_date = Date.parse(params[:until])
|
||||
|
||||
if start_date > end_date
|
||||
render json: { error: 'Start date must be before end date' }, status: :unprocessable_entity
|
||||
elsif (end_date - start_date).to_i > 365
|
||||
render json: { error: 'Date range cannot exceed 365 days' }, status: :unprocessable_entity
|
||||
end
|
||||
rescue Date::Error
|
||||
render json: { error: 'Invalid date format' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize Current.account, :show_metrics?
|
||||
end
|
||||
|
||||
def metrics_service
|
||||
@metrics_service ||= Reports::AssignmentMetricsService.new(Current.account, params)
|
||||
end
|
||||
end
|
||||
@@ -1,117 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module AssignmentMetricsHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def compute_summary_metrics
|
||||
conversations = filter_conversations_by_date_range
|
||||
|
||||
{
|
||||
total_assignments: conversations.count,
|
||||
average_assignment_time: calculate_average_assignment_time(conversations),
|
||||
assignments_per_agent: calculate_assignments_per_agent(conversations),
|
||||
unassigned_conversations: Current.account.conversations.open.unassigned.count,
|
||||
policies_active: Current.account.assignment_policies.enabled.count
|
||||
}
|
||||
end
|
||||
|
||||
def compute_inbox_metrics
|
||||
inboxes = params[:inbox_id].present? ? Current.account.inboxes.where(id: params[:inbox_id]) : Current.account.inboxes
|
||||
|
||||
inboxes.map do |inbox|
|
||||
conversations = filter_conversations_by_date_range.where(inbox_id: inbox.id)
|
||||
{
|
||||
inbox_id: inbox.id,
|
||||
inbox_name: inbox.name,
|
||||
total_assignments: conversations.count,
|
||||
average_assignment_time: calculate_average_assignment_time(conversations),
|
||||
unique_agents: conversations.where.not(assignee_id: nil).distinct.count(:assignee_id),
|
||||
assignment_policy: inbox.assignment_policy&.name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def compute_agent_metrics
|
||||
agents = Current.account.users.joins(:account_users).where(account_users: { role: %w[agent administrator] })
|
||||
|
||||
agents.map do |agent|
|
||||
conversations = filter_conversations_by_date_range.where(assignee_id: agent.id)
|
||||
{
|
||||
agent_id: agent.id,
|
||||
agent_name: agent.name,
|
||||
agent_email: agent.email,
|
||||
assignment_count: conversations.count,
|
||||
average_resolution_time: calculate_average_resolution_time(conversations),
|
||||
current_load: agent.assigned_conversations.open.count,
|
||||
capacity_utilization: compute_agent_capacity_utilization(agent)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def compute_policy_metrics
|
||||
Current.account.assignment_policies.enabled.map do |policy|
|
||||
compute_policy_performance(policy)
|
||||
end
|
||||
end
|
||||
|
||||
def filter_conversations_by_date_range
|
||||
Current.account.conversations.where(created_at: date_range)
|
||||
end
|
||||
|
||||
def date_range
|
||||
@date_range ||= params[:since]..params[:until]
|
||||
end
|
||||
|
||||
def calculate_average_assignment_time(_conversations)
|
||||
# Implementation
|
||||
0
|
||||
end
|
||||
|
||||
def calculate_assignments_per_agent(conversations)
|
||||
agents_count = conversations.where.not(assignee_id: nil).distinct.count(:assignee_id)
|
||||
return 0 if agents_count.zero?
|
||||
|
||||
(conversations.count.to_f / agents_count).round(2)
|
||||
end
|
||||
|
||||
def calculate_average_resolution_time(_conversations)
|
||||
# Implementation
|
||||
0
|
||||
end
|
||||
|
||||
def compute_agent_capacity_utilization(_agent)
|
||||
# Implementation
|
||||
0.0
|
||||
end
|
||||
|
||||
def calculate_assignment_success_rate(_conversations)
|
||||
# Implementation
|
||||
100.0
|
||||
end
|
||||
|
||||
def serialize_agent(agent)
|
||||
{
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
email: agent.email
|
||||
}
|
||||
end
|
||||
|
||||
def group_by_hour(conversations)
|
||||
conversations.group_by_hour(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_day(conversations)
|
||||
conversations.group_by_day(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_week(conversations)
|
||||
conversations.group_by_week(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_month(conversations)
|
||||
conversations.group_by_month(:created_at).count
|
||||
end
|
||||
end
|
||||
@@ -1,92 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DistributionMetrics
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def compute_distribution_by_day_of_week
|
||||
filter_conversations_by_date_range
|
||||
.group_by_day_of_week(:created_at)
|
||||
.count
|
||||
end
|
||||
|
||||
def compute_policy_performance(policy)
|
||||
inbox_ids = policy.inboxes.pluck(:id)
|
||||
conversations = filter_conversations_by_date_range.where(inbox_id: inbox_ids)
|
||||
|
||||
{
|
||||
policy_id: policy.id,
|
||||
policy_name: policy.name,
|
||||
assignment_order: policy.assignment_order,
|
||||
total_assignments: conversations.count,
|
||||
average_assignment_time: calculate_average_assignment_time(conversations),
|
||||
success_rate: calculate_assignment_success_rate(conversations),
|
||||
inbox_count: inbox_ids.count
|
||||
}
|
||||
end
|
||||
|
||||
def compute_agent_utilization(agent)
|
||||
capacity_policy = agent.account_users.first&.agent_capacity_policy
|
||||
assigned_conversations = agent.assigned_conversations.open.count
|
||||
|
||||
utilization = if capacity_policy
|
||||
policy_limit = capacity_policy.inbox_capacity_limits.sum(:conversation_limit)
|
||||
policy_limit.positive? ? (assigned_conversations.to_f / policy_limit * 100).round(2) : 0
|
||||
else
|
||||
0
|
||||
end
|
||||
|
||||
{
|
||||
agent: serialize_agent(agent),
|
||||
assigned_conversations: assigned_conversations,
|
||||
capacity_policy: capacity_policy&.name,
|
||||
utilization_percentage: utilization,
|
||||
available_capacity: capacity_policy ? capacity_policy.inbox_capacity_limits.sum(:conversation_limit) - assigned_conversations : nil
|
||||
}
|
||||
end
|
||||
|
||||
def compute_distribution_by_inbox
|
||||
filter_conversations_by_date_range
|
||||
.joins(:inbox)
|
||||
.group('inboxes.id', 'inboxes.name')
|
||||
.count
|
||||
.map { |k, v| { inbox_id: k[0], inbox_name: k[1], count: v } }
|
||||
end
|
||||
|
||||
def compute_distribution_by_team
|
||||
filter_conversations_by_date_range
|
||||
.joins(assignee: { team_members: :team })
|
||||
.group('teams.id', 'teams.name')
|
||||
.count
|
||||
.map { |k, v| { team_id: k[0], team_name: k[1], count: v } }
|
||||
end
|
||||
|
||||
def compute_distribution_by_hour
|
||||
filter_conversations_by_date_range
|
||||
.group_by_hour_of_day(:created_at)
|
||||
.count
|
||||
end
|
||||
|
||||
def calculate_average_resolution_time(conversations)
|
||||
resolved = conversations.resolved
|
||||
return 0 if resolved.empty?
|
||||
|
||||
total_time = resolved.sum { |c| (c.last_activity_at - c.created_at) / 1.hour }
|
||||
(total_time / resolved.count).round(2)
|
||||
end
|
||||
|
||||
def calculate_assignment_success_rate(conversations)
|
||||
total = conversations.count
|
||||
return 0.0 if total.zero?
|
||||
|
||||
successful = conversations.where(status: %w[resolved snoozed]).count
|
||||
(successful.to_f / total * 100).round(2)
|
||||
end
|
||||
|
||||
def pagination_meta
|
||||
{
|
||||
current_page: params[:page] || 1,
|
||||
per_page: params[:per_page] || 50,
|
||||
total_count: @assignment_history&.total_count || 0
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,70 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Reports::AgentHistoryService
|
||||
attr_reader :account, :params
|
||||
|
||||
def initialize(account, params = {})
|
||||
@account = account
|
||||
@params = params
|
||||
end
|
||||
|
||||
def fetch_agent_assignment_history(agent)
|
||||
conversations = agent.assigned_conversations
|
||||
.includes(:inbox, :contact)
|
||||
.where(created_at: date_range)
|
||||
.order(created_at: :desc)
|
||||
.page(params[:page])
|
||||
.per(params[:per_page] || 50)
|
||||
|
||||
conversations.map do |conversation|
|
||||
{
|
||||
conversation_id: conversation.id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
inbox_name: conversation.inbox.name,
|
||||
contact_name: conversation.contact.name,
|
||||
assigned_at: conversation.assignee_last_seen_at || conversation.created_at,
|
||||
status: conversation.status,
|
||||
created_at: conversation.created_at
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def compute_all_agents_history
|
||||
agents = account.users.joins(:account_users).where(account_users: { role: %w[agent administrator] })
|
||||
|
||||
agents.map do |agent|
|
||||
conversations = agent.assigned_conversations.where(created_at: date_range)
|
||||
{
|
||||
agent: serialize_agent(agent),
|
||||
total_assignments: conversations.count,
|
||||
resolved_count: conversations.resolved.count,
|
||||
open_count: conversations.open.count,
|
||||
average_resolution_time: calculate_average_resolution_time(conversations.resolved)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def date_range
|
||||
start_date = params[:start_date] ? Date.parse(params[:start_date]).beginning_of_day : 30.days.ago
|
||||
end_date = params[:end_date] ? Date.parse(params[:end_date]).end_of_day : Time.current
|
||||
start_date..end_date
|
||||
end
|
||||
|
||||
def serialize_agent(agent)
|
||||
{
|
||||
id: agent.id,
|
||||
name: agent.name,
|
||||
email: agent.email,
|
||||
avatar_url: agent.avatar_url
|
||||
}
|
||||
end
|
||||
|
||||
def calculate_average_resolution_time(conversations)
|
||||
return 0 if conversations.empty?
|
||||
|
||||
total_time = conversations.sum { |c| (c.last_activity_at - c.created_at) / 1.hour }
|
||||
(total_time / conversations.count).round(2)
|
||||
end
|
||||
end
|
||||
@@ -1,54 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
class Reports::AssignmentExportService
|
||||
attr_reader :data
|
||||
|
||||
def initialize(data)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def generate_csv
|
||||
CSV.generate(headers: true) do |csv|
|
||||
add_header(csv)
|
||||
add_summary_metrics(csv)
|
||||
add_inbox_metrics(csv)
|
||||
add_agent_metrics(csv)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_header(csv)
|
||||
csv << ['Assignment Metrics Report']
|
||||
csv << ['Generated at', Time.current]
|
||||
csv << []
|
||||
end
|
||||
|
||||
def add_summary_metrics(csv)
|
||||
csv << ['Summary Metrics']
|
||||
csv << %w[Metric Value]
|
||||
data[:summary].each do |key, value|
|
||||
csv << [key.to_s.humanize, value]
|
||||
end
|
||||
csv << []
|
||||
end
|
||||
|
||||
def add_inbox_metrics(csv)
|
||||
csv << ['Inbox Metrics']
|
||||
csv << ['Inbox Name', 'Total Assignments', 'Average Assignment Time', 'Unique Agents']
|
||||
data[:by_inbox].each do |inbox|
|
||||
csv << [inbox[:inbox_name], inbox[:total_assignments], inbox[:average_assignment_time], inbox[:unique_agents]]
|
||||
end
|
||||
csv << []
|
||||
end
|
||||
|
||||
def add_agent_metrics(csv)
|
||||
csv << ['Agent Metrics']
|
||||
csv << ['Agent Name', 'Email', 'Assignment Count']
|
||||
data[:by_agent].each do |agent|
|
||||
csv << [agent[:agent_name], agent[:agent_email], agent[:assignment_count]]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,163 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Reports::AssignmentMetricsService
|
||||
attr_reader :account, :params
|
||||
|
||||
def initialize(account, params = {})
|
||||
@account = account
|
||||
@params = params
|
||||
end
|
||||
|
||||
def compute_assignment_metrics
|
||||
{
|
||||
total_assigned: total_assigned_conversations,
|
||||
assignment_rate: calculate_assignment_rate,
|
||||
average_response_time: calculate_average_response_time,
|
||||
average_resolution_time: calculate_average_resolution_time,
|
||||
assignments_by_policy: assignments_by_policy,
|
||||
period_metrics: compute_period_metrics
|
||||
}
|
||||
end
|
||||
|
||||
def compute_policy_performance(policy)
|
||||
conversations = policy.assignment_logs
|
||||
.joins(:conversation)
|
||||
.where(conversations: { created_at: date_range })
|
||||
|
||||
{
|
||||
policy_id: policy.id,
|
||||
policy_name: policy.name,
|
||||
total_assignments: conversations.count,
|
||||
average_assignment_time: calculate_average_time(conversations, :assignment_time),
|
||||
successful_assignments: conversations.where(success: true).count,
|
||||
failed_assignments: conversations.where(success: false).count
|
||||
}
|
||||
end
|
||||
|
||||
def compute_agent_utilization(agent)
|
||||
conversations = agent.assigned_conversations.where(created_at: date_range)
|
||||
capacity_limit = fetch_agent_capacity_limit(agent)
|
||||
|
||||
{
|
||||
agent_id: agent.id,
|
||||
agent_name: agent.name,
|
||||
current_load: agent.assigned_conversations.open.count,
|
||||
capacity_limit: capacity_limit,
|
||||
utilization_percentage: calculate_utilization_percentage(agent, capacity_limit),
|
||||
total_handled: conversations.count,
|
||||
average_handling_time: calculate_average_handling_time(conversations)
|
||||
}
|
||||
end
|
||||
|
||||
def compute_distribution_by_inbox
|
||||
Conversation.joins(:inbox)
|
||||
.where(created_at: date_range, account_id: account.id)
|
||||
.where.not(assignee_id: nil)
|
||||
.group('inboxes.name')
|
||||
.count
|
||||
end
|
||||
|
||||
def compute_distribution_by_team
|
||||
Conversation.joins(assignee: { team_members: :team })
|
||||
.where(created_at: date_range, account_id: account.id)
|
||||
.group('teams.name')
|
||||
.count
|
||||
end
|
||||
|
||||
def compute_distribution_by_hour
|
||||
Conversation.where(created_at: date_range, account_id: account.id)
|
||||
.where.not(assignee_id: nil)
|
||||
.group_by_hour(:created_at, format: '%H')
|
||||
.count
|
||||
end
|
||||
|
||||
def compute_period_metrics
|
||||
group_by = params[:group_by] || 'day'
|
||||
conversations = filter_conversations_by_date_range
|
||||
|
||||
case group_by
|
||||
when 'hour'
|
||||
group_by_hour(conversations)
|
||||
when 'week'
|
||||
group_by_week(conversations)
|
||||
when 'month'
|
||||
group_by_month(conversations)
|
||||
else
|
||||
group_by_day(conversations)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def date_range
|
||||
@date_range ||= params[:since]..params[:until]
|
||||
end
|
||||
|
||||
def filter_conversations_by_date_range
|
||||
Conversation.where(account_id: account.id, created_at: date_range)
|
||||
end
|
||||
|
||||
def total_assigned_conversations
|
||||
filter_conversations_by_date_range.where.not(assignee_id: nil).count
|
||||
end
|
||||
|
||||
def calculate_assignment_rate
|
||||
total = filter_conversations_by_date_range.count
|
||||
return 0.0 if total.zero?
|
||||
|
||||
(total_assigned_conversations.to_f / total * 100).round(2)
|
||||
end
|
||||
|
||||
def calculate_average_response_time
|
||||
# Implementation for average response time
|
||||
0
|
||||
end
|
||||
|
||||
def calculate_average_resolution_time
|
||||
# Implementation for average resolution time
|
||||
0
|
||||
end
|
||||
|
||||
def assignments_by_policy
|
||||
# Implementation for assignments by policy
|
||||
{}
|
||||
end
|
||||
|
||||
def fetch_agent_capacity_limit(_agent)
|
||||
# Implementation to fetch agent capacity limit
|
||||
nil
|
||||
end
|
||||
|
||||
def calculate_utilization_percentage(agent, capacity_limit)
|
||||
return 0.0 unless capacity_limit&.positive?
|
||||
|
||||
current_load = agent.assigned_conversations.open.count
|
||||
(current_load.to_f / capacity_limit * 100).round(2)
|
||||
end
|
||||
|
||||
def calculate_average_handling_time(_conversations)
|
||||
# Implementation for average handling time
|
||||
0
|
||||
end
|
||||
|
||||
def calculate_average_time(_conversations, _field)
|
||||
# Implementation for average time calculation
|
||||
0
|
||||
end
|
||||
|
||||
def group_by_hour(conversations)
|
||||
conversations.group_by_hour(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_day(conversations)
|
||||
conversations.group_by_day(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_week(conversations)
|
||||
conversations.group_by_week(:created_at).count
|
||||
end
|
||||
|
||||
def group_by_month(conversations)
|
||||
conversations.group_by_month(:created_at).count
|
||||
end
|
||||
end
|
||||
@@ -244,19 +244,6 @@ Rails.application.routes.draw do
|
||||
# Agent capacity status
|
||||
get 'agents/:agent_id/capacity', to: 'agent_capacity_policies#agent_capacity'
|
||||
|
||||
# Assignment Metrics
|
||||
namespace :reports do
|
||||
resources :assignment_metrics, only: [:index] do
|
||||
collection do
|
||||
get 'agent_history'
|
||||
get 'policy_performance'
|
||||
get 'agent_utilization'
|
||||
get 'assignment_distribution'
|
||||
get 'export'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :twitter do
|
||||
resource :authorization, only: [:create]
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user