Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APPEALS-46239 ACD: Distribution Stats improvements FY24Q3 #21727

Merged
merged 11 commits into from
Jul 19, 2024
Merged
4 changes: 2 additions & 2 deletions app/controllers/test/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Test::UsersController < ApplicationController
name: "Queue",
links: {
your_queue: "/queue",
assignment_queue: "/queue/USER_CSS_ID/assign" # USER_CSS_ID is then updated in TestUsers file
assignment_queue: "/queue/USER_CSS_ID/assign", # USER_CSS_ID is then updated in TestUsers file
case_distribution_dashboard: "/acd-controls/test"
}
},
{
Expand Down Expand Up @@ -65,7 +66,6 @@ class Test::UsersController < ApplicationController
admin: "/admin",
test_veterans: "/test/data",
metrics_dashboard: "/metrics/dashboard",
case_distribution_dashboard: "/acd-controls/test",
swagger: "/api-docs"
}
}
Expand Down
123 changes: 73 additions & 50 deletions app/jobs/push_priority_appeals_to_judges_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def perform

perform_later_or_now(UpdateAppealAffinityDatesJob)

send_job_report
slack_service.send_notification(slack_report.join("\n"), self.class.name)
zurbergram marked this conversation as resolved.
Show resolved Hide resolved
rescue StandardError => error
start_time ||= Time.zone.now # temporary fix to get this job to succeed
duration = time_ago_in_words(start_time)
Expand All @@ -32,55 +32,6 @@ def perform
metrics_service_report_runtime(metric_group_name: "priority_appeal_push_job")
end

def send_job_report
slack_service.send_notification(slack_report.join("\n"), self.class.name)
end

def slack_report # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
report = []
if use_by_docket_date?
total_cases = @genpop_distributions.map(&:distributed_cases_count).sum
report << "*Number of cases distributed*: " \
"#{total_cases}"
else
tied_distributions_sum = @tied_distributions.map(&:distributed_cases_count).sum
genpop_distributions_sum = @genpop_distributions.map(&:distributed_cases_count).sum
report << "*Number of cases tied to judges distributed*: " \
"#{tied_distributions_sum}"
report << "*Number of general population cases distributed*: " \
"#{genpop_distributions_sum}"
end

appeals_not_distributed = docket_coordinator.dockets.map do |docket_type, docket|
report << "*Age of oldest #{docket_type} case*: #{docket.oldest_priority_appeal_days_waiting} days"
[docket_type, docket.ready_priority_appeal_ids]
end.to_h

report << "*Total Number of appeals _not_ distributed*: #{appeals_not_distributed.values.flatten.count}"
docket_coordinator.dockets.each_pair do |sym, docket|
report << "*Number of #{sym} appeals _not_ distributed*: #{docket.count(priority: true, ready: true)}"
end
report << "*Number of Legacy Hearing Non Genpop appeals _not_ distributed*: #{legacy_not_genpop_count}"

report << ""
report << "*Debugging information*"
report << "Priority Target: #{priority_target}"
report << "Previous monthly distributions {judge_id=>count}: #{priority_distributions_this_month_for_eligible_judges}" # rubocop:disable Layout/LineLength

if appeals_not_distributed.values.flatten.any?
add_stuck_appeals_to_report(report, appeals_not_distributed)
end

report
end

def add_stuck_appeals_to_report(report, appeals)
report.unshift("[WARN]")
report << COPY::PRIORITY_PUSH_WARNING_MESSAGE
report << "AMA appeals not distributed: `Appeal.where(uuid: #{appeals.values.drop(1).flatten})`"
report << "Legacy appeals not distributed: `LegacyAppeal.where(vacols_id: #{appeals[:legacy]})`"
end

# Distribute all priority cases tied to a judge without limit
def distribute_non_genpop_priority_appeals
eligible_judges.map do |judge|
Expand Down Expand Up @@ -182,7 +133,79 @@ def use_by_docket_date?
FeatureToggle.enabled?(:acd_distribute_by_docket_date, user: RequestStore.store[:current_user])
end

#
# Reporting methods
#

def slack_report
report = []

num_of_cases_distributed(report)

report << "Priority Target: #{priority_target}"

appeals_not_distributed = age_of_oldest_by_docket(report)
num_of_appeals_not_distributed(report, appeals_not_distributed)
num_of_appeals_not_distributed_by_affinity_date(report)

report << ""
report << "*Debugging information*"

excluded_judges_reporting(report)

report << "Previous monthly distributions {judge_id=>count}: #{priority_distributions_this_month_for_eligible_judges}" # rubocop:disable Layout/LineLength
report
end

def num_of_cases_distributed(report)
if use_by_docket_date?
total_cases = @genpop_distributions.map(&:distributed_cases_count).sum
report << "*Number of cases distributed*: " \
"#{total_cases}"
else
tied_distributions_sum = @tied_distributions.map(&:distributed_cases_count).sum
genpop_distributions_sum = @genpop_distributions.map(&:distributed_cases_count).sum
report << "*Number of cases tied to judges distributed*: " \
"#{tied_distributions_sum}"
report << "*Number of general population cases distributed*: " \
"#{genpop_distributions_sum}"
end
end

def age_of_oldest_by_docket(report)
docket_coordinator.dockets.map do |docket_type, docket|
report << "*Age of oldest #{docket_type} case*: #{docket.oldest_priority_appeal_days_waiting} days"
[docket_type, docket.ready_priority_appeal_ids]
end.to_h
end

def num_of_appeals_not_distributed(report, appeals_not_distributed)
report << ""
report << "*Total Number of appeals _not_ distributed*: #{appeals_not_distributed.values.flatten.count}"

docket_coordinator.dockets.each_pair do |sym, docket|
report << "*Number of #{sym} appeals _not_ distributed*: #{docket.count(priority: true, ready: true)}"
end

report << "*Number of Legacy Hearing Non Genpop appeals _not_ distributed*: #{legacy_not_genpop_count}"
end

def legacy_not_genpop_count
docket_coordinator.dockets[:legacy].not_genpop_priority_count
end

def num_of_appeals_not_distributed_by_affinity_date(report)
report << ""
docket_coordinator.dockets.each_pair do |sym, docket|
report << "*Number of #{sym} appeals in affinity date window*: " \
"#{docket.affinity_date_count(in_window: true, priority: true)}"
report << "*Number of #{sym} appeals out of affinity date window*: " \
"#{docket.affinity_date_count(in_window: false, priority: true)}"
end
end

def excluded_judges_reporting(report)
excluded_judges = JudgeTeam.judges_with_exclude_appeals_from_affinity.pluck(:css_id)
report << "*Excluded Judges*: #{excluded_judges}"
end
end
25 changes: 13 additions & 12 deletions app/models/concerns/automatic_case_distribution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,19 @@ def distribute_limited_priority_appeals_from_all_dockets(limit, style: "push")
def ama_statistics
sct_appeals_counts = @appeals.count { |appeal| appeal.try(:sct_appeal) }
{
batch_size: @appeals.count,
total_batch_size: total_batch_size,
priority_count: priority_count,
direct_review_due_count: direct_review_due_count,
legacy_hearing_backlog_count: VACOLS::CaseDocket.nonpriority_hearing_cases_for_judge_count(judge),
legacy_proportion: docket_proportions[:legacy],
direct_review_proportion: docket_proportions[:direct_review],
evidence_submission_proportion: docket_proportions[:evidence_submission],
hearing_proportion: docket_proportions[:hearing],
nonpriority_iterations: @nonpriority_iterations,
algorithm: "proportions",
sct_appeals: sct_appeals_counts
statistics: {
batch_size: @appeals.count,
total_batch_size: total_batch_size,
priority_count: priority_count,
direct_review_due_count: direct_review_due_count,
legacy_hearing_backlog_count: VACOLS::CaseDocket.nonpriority_hearing_cases_for_judge_count(judge),
legacy_proportion: docket_proportions[:legacy],
direct_review_proportion: docket_proportions[:direct_review],
evidence_submission_proportion: docket_proportions[:evidence_submission],
hearing_proportion: docket_proportions[:hearing],
nonpriority_iterations: @nonpriority_iterations,
sct_appeals: sct_appeals_counts
}
}
end

Expand Down
84 changes: 59 additions & 25 deletions app/models/concerns/by_docket_date_distribution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,45 @@ def distribute_nonpriority_appeals_from_all_dockets_by_age_to_limit(limit, style

# rubocop:disable Metrics/MethodLength
def ama_statistics
priority_counts = { count: priority_count }
nonpriority_counts = { count: nonpriority_count }
docket_counts = {
direct_review_priority_stats: {},
direct_review_stats: {},
evidence_submission_priority_stats: {},
evidence_submission_stats: {},
hearing_priority_stats: {},
hearing_stats: {},
legacy_priority_stats: {},
legacy_stats: {}
}

dockets.each_pair do |sym, docket|
priority_counts[sym] = docket.count(priority: true, ready: true)
nonpriority_counts[sym] = docket.count(priority: false, ready: true)
docket_counts["#{sym}_priority_stats".to_sym] = {
count: docket.count(priority: true, ready: true),
affinity_date: {
in_window: docket.affinity_date_count(in_window: true, priority: true),
out_of_window: docket.affinity_date_count(in_window: false, priority: true)
}
}

docket_counts["#{sym}_stats".to_sym] = {
count: docket.count(priority: false, ready: true),
affinity_date: {
in_window: docket.affinity_date_count(in_window: true, priority: false),
out_of_window: docket.affinity_date_count(in_window: false, priority: false)
}
}
end

priority_counts[:legacy_hearing_tied_to] = legacy_hearing_priority_count(judge)
nonpriority_counts[:legacy_hearing_tied_to] = legacy_hearing_nonpriority_count(judge)

nonpriority_counts[:iterations] = @nonpriority_iterations
docket_counts[:legacy_priority_stats][:legacy_hearing_tied_to] = legacy_hearing_priority_count(judge)
docket_counts[:legacy_stats][:legacy_hearing_tied_to] = legacy_hearing_nonpriority_count(judge)

sct_appeals_counts = @appeals.count { |appeal| appeal.try(:sct_appeal) }

cases_tied_to_ineligible_judges = {
ama: ama_distributed_cases_tied_to_ineligible_judges,
legacy: distributed_cases_tied_to_ineligible_judges
}

settings = {}
feature_toggles = [
:specialty_case_team_distribution
Expand All @@ -82,26 +106,36 @@ def ama_statistics
settings[sym] = FeatureToggle.enabled?(sym, user: RequestStore.store[:current_user])
end

{
batch_size: @appeals.count,
total_batch_size: total_batch_size,
priority_target: @push_priority_target || @request_priority_count,
priority: priority_counts,
nonpriority: nonpriority_counts,
sct_appeals: sct_appeals_counts,
distributed_cases_tied_to_ineligible_judges: {
ama: ama_distributed_cases_tied_to_ineligible_judges,
legacy: distributed_cases_tied_to_ineligible_judges
},
algorithm: "by_docket_date",
settings: settings
}
docket_counts.merge(
{
ineligible_judge_stats: {
distributed_cases_tied_to_ineligible_judges: cases_tied_to_ineligible_judges
},
judge_stats: {
team_size: team_size,
ama_judge_assigned_tasks: judge_tasks.length,
legacy_assigned_tasks: judge_legacy_tasks.length,
settings: settings
},
statistics: {
batch_size: @appeals.count,
total_batch_size: total_batch_size,
priority_target: @push_priority_target || @request_priority_count,
priority_count: priority_count,
nonpriority_count: nonpriority_count,
nonpriority_iterations: @nonpriority_iterations,
sct_appeals: sct_appeals_counts
}
}
)
rescue StandardError => error
# There always needs to be a batch_size value for a completed distribution, else the priority push job will error
{
batch_size: @appeals.count,
message: "Distribution successful, but there was an error generating statistics: \
#{error.class}: #{error.message}, #{error.backtrace.first}"
statistics: {
batch_size: @appeals.count,
message: "Distribution successful, but there was an error generating statistics: \
#{error.class}: #{error.message}, #{error.backtrace.first}"
}
}
end
# rubocop:enable Metrics/MethodLength
Expand Down
21 changes: 19 additions & 2 deletions app/models/concerns/distribution_scopes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ def genpop
OR original_judge_task.assigned_to_id in (?)",
Constants.AMA_STREAM_TYPES.court_remand,
CaseDistributionLever.cavc_affinity_days.days.ago,
JudgeTeam.judges_with_exclude_appeals_from_affinity
JudgeTeam.judge_ids_with_exclude_appeals_from_affinity
)
end

def genpop_by_affinity_start_date
with_appeal_affinities
.with_original_appeal_and_judge_task
.where(
"appeal_affinities.affinity_start_date <= ?",
CaseDistributionLever.cavc_affinity_days.days.ago
)
end

Expand Down Expand Up @@ -89,6 +98,14 @@ def non_genpop_for_judge(judge)
.where(original_judge_task: { assigned_to_id: judge&.id })
end

def non_genpop_by_affinity_start_date
with_appeal_affinities
.with_original_appeal_and_judge_task
.where("appeal_affinities.affinity_start_date > ? or appeal_affinities.affinity_start_date is null",
CaseDistributionLever.cavc_affinity_days.days.ago)
.where.not(original_judge_task: { assigned_to_id: nil })
end

def ordered_by_distribution_ready_date
joins(:tasks)
.group("appeals.id")
Expand Down Expand Up @@ -134,7 +151,7 @@ def tied_to_ineligible_judge

def tied_to_judges_with_exclude_appeals_from_affinity
with_appeal_affinities
.where(hearings: { disposition: "held", judge_id: JudgeTeam.judges_with_exclude_appeals_from_affinity })
.where(hearings: { disposition: "held", judge_id: JudgeTeam.judge_ids_with_exclude_appeals_from_affinity })
end

# If an appeal has exceeded the affinity, it should be returned to genpop.
Expand Down
21 changes: 11 additions & 10 deletions app/models/distribution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,21 @@ def assigned_tasks
end
end

def batch_size
team_batch_size = JudgeTeam.for_judge(judge)&.attorneys&.size
def team_size
@team_size ||= JudgeTeam.for_judge(judge)&.attorneys&.size
end

return CaseDistributionLever.alternative_batch_size if team_batch_size.nil? || team_batch_size == 0
def batch_size
return CaseDistributionLever.alternative_batch_size if team_size.nil? || team_size == 0

team_batch_size * CaseDistributionLever.batch_size_per_attorney
team_size * CaseDistributionLever.batch_size_per_attorney
end

def error_statistics(error)
{
error: error&.full_message
statistics: {
error: error&.full_message
}
}
end

Expand All @@ -150,15 +154,12 @@ def process_error(error)
# need to store batch_size in the statistics column for use within the PushPriorityAppealsToJudgesJob
def completed_statistics(stats)
{
batch_size: stats[:batch_size],
batch_size: stats[:statistics][:batch_size],
info: "See related row in distribution_stats for additional stats"
}
end

def record_distribution_stats(stats)
create_distribution_stats!(
statistics: stats,
levers: CaseDistributionLever.snapshot
)
create_distribution_stats!(stats.merge(levers: CaseDistributionLever.snapshot))
end
end
Loading
Loading