-
Notifications
You must be signed in to change notification settings - Fork 19
/
push_priority_appeals_to_judges_job.rb
150 lines (125 loc) · 6.58 KB
/
push_priority_appeals_to_judges_job.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# frozen_string_literal: true
# Job that pushes priority cases to a judge rather than waiting for them to request cases. This will distribute cases
# to all judges whose teams that have `accepts_priority_pushed_cases` enabled. The first step distributes all priority
# cases tied to a judge without limit. The second step distributes remaining general population cases (cases not tied to
# an active judge) while attempting to even out the number of priority cases all judges have received over one month.
class PushPriorityAppealsToJudgesJob < CaseflowJob
queue_with_priority :low_priority
application_attr :queue
include AutomaticCaseDistribution
def perform
@tied_distributions = distribute_non_genpop_priority_appeals
@genpop_distributions = distribute_genpop_priority_appeals
send_job_report
end
def send_job_report
slack_service.send_notification(slack_report.join("\n"), self.class.name, "#appeals-job-alerts")
datadog_report_runtime(metric_group_name: "priority_appeal_push_job")
end
def slack_report
report = []
report << "*Number of cases tied to judges distributed*: " \
"#{@tied_distributions.map { |distribution| distribution.statistics['batch_size'] }.sum}"
report << "*Number of general population cases distributed*: " \
"#{@genpop_distributions.map { |distribution| distribution.statistics['batch_size'] }.sum}"
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 << "*Number of appeals _not_ distributed*: #{appeals_not_distributed.values.flatten.count}"
report << ""
report << "*Debugging information*"
report << "Priority Target: #{priority_target}"
report << "Previous monthly distributions: #{priority_distributions_this_month_for_eligible_judges}"
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 << "Legacy appeals not distributed: `LegacyAppeal.where(vacols_id: #{appeals[:legacy]})`"
report << "AMA appeals not distributed: `Appeal.where(uuid: #{appeals.values.drop(1).flatten})`"
report << COPY::PRIORITY_PUSH_WARNING_MESSAGE
end
# Distribute all priority cases tied to a judge without limit
def distribute_non_genpop_priority_appeals
eligible_judges.map do |judge|
Distribution.create!(judge: User.find(judge.id), priority_push: true).tap(&:distribute!)
end
end
# Distribute remaining general population cases while attempting to even out the number of priority cases all judges
# have received over one month
def distribute_genpop_priority_appeals
eligible_judge_target_distributions_with_leftovers.map do |judge_id, target|
Distribution.create!(
judge: User.find(judge_id),
priority_push: true
).tap { |distribution| distribution.distribute!(target) }
end
end
# Give any leftover cases to judges with the lowest distribution targets. Remove judges with 0 cases to be distributed
# as these are the final counts to distribute remaining ready priority cases
def eligible_judge_target_distributions_with_leftovers
leftover_cases = leftover_cases_count
target_distributions_for_eligible_judges.sort_by(&:last).map do |judge_id, target|
if leftover_cases > 0
leftover_cases -= 1
target += 1
end
(target > 0) ? [judge_id, target] : nil
end.compact.to_h
end
# Because we cannot distribute fractional cases, there can be cases leftover after taking the priority target
# into account. This number will always be less than the number of judges that need distribution because division
def leftover_cases_count
ready_priority_appeals_count - target_distributions_for_eligible_judges.values.sum
end
# Calculate the number of cases a judge should receive based on the priority target. Don't toss out judges with 0 as
# they could receive some of the leftover cases (if any)
def target_distributions_for_eligible_judges
priority_distributions_this_month_for_eligible_judges.map do |judge_id, distributions_this_month|
target = priority_target - distributions_this_month
(target >= 0) ? [judge_id, target] : nil
end.compact.to_h
end
# Calculates a target that will distribute all ready appeals so the remaining counts for each judge will produce
# even case counts over a full month (or as close as we can get to it)
def priority_target
@priority_target ||= begin
distribution_counts = priority_distributions_this_month_for_eligible_judges.values
target = (distribution_counts.sum + ready_priority_appeals_count) / distribution_counts.count
# If there are any judges that have previous distributions that are MORE than the currently calculated priority
# target, no target will be large enough to get all other judges up to their number of cases. Remove them from
# consideration and recalculate the target for all other judges.
while distribution_counts.any? { |distribution_count| distribution_count > target }
distribution_counts = distribution_counts.reject { |distribution_count| distribution_count > target }
target = (distribution_counts.sum + ready_priority_appeals_count) / distribution_counts.count
end
target
end
end
def docket_coordinator
@docket_coordinator ||= DocketCoordinator.new
end
def ready_priority_appeals_count
@ready_priority_appeals_count ||= docket_coordinator.priority_count
end
# Number of priority distributions every eligible judge has received in the last month
def priority_distributions_this_month_for_eligible_judges
eligible_judges.map { |judge| [judge.id, priority_distributions_this_month_for_all_judges[judge.id] || 0] }.to_h
end
def eligible_judges
@eligible_judges ||= JudgeTeam.pushed_priority_cases_allowed.map(&:judge)
end
# Produces a hash of judge_id and the number of cases distributed to them in the last month
def priority_distributions_this_month_for_all_judges
@priority_distributions_this_month_for_all_judges ||= priority_distributions_this_month
.pluck(:judge_id, :statistics)
.group_by(&:first)
.map { |judge_id, arr| [judge_id, arr.flat_map(&:last).map { |stats| stats["batch_size"] }.sum] }.to_h
end
def priority_distributions_this_month
Distribution.priority_pushed.completed.where(completed_at: 30.days.ago..Time.zone.now)
end
end