Skip to content

Commit

Permalink
move scopes from dockets to distribution_scopes (#20816)
Browse files Browse the repository at this point in the history
* move scopes from dockets to distribution_scopes

* add comment, add rubocop disable
  • Loading branch information
craigrva authored Feb 23, 2024
1 parent 01fdc51 commit b64b2a7
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 151 deletions.
143 changes: 142 additions & 1 deletion app/models/concerns/distribution_scopes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

module DistributionScopes
# This class is used in the Distribution process to easily create and customize SQL queries generated by Rails
# by extending the Appeal class.
# Note: The words "Tied To" are used in this class to refer to Appeals which fall under the affinity rules as
# determined by the Board. There are currently no AMA appeals which have a legal requirement to go to a certain
# Judge.
module DistributionScopes # rubocop:disable Metrics/ModuleLength
extend ActiveSupport::Concern

def with_assigned_distribution_task_sql
Expand All @@ -13,4 +18,140 @@ def with_assigned_distribution_task_sql
AND distribution_task.status = 'assigned'
SQL
end

# From docket.rb

def priority
include_aod_motions
.where("advance_on_docket_motions.created_at > appeals.established_at")
.where("advance_on_docket_motions.granted = ?", true)
.or(include_aod_motions.where("people.date_of_birth <= ?", 75.years.ago))
.or(include_aod_motions.where("appeals.stream_type = ?", Constants.AMA_STREAM_TYPES.court_remand))
.group("appeals.id")
end

def nonpriority
include_aod_motions
.where("people.date_of_birth > ? or people.date_of_birth is null", 75.years.ago)
.where.not("appeals.stream_type = ?", Constants.AMA_STREAM_TYPES.court_remand)
.group("appeals.id")
.having("count(case when advance_on_docket_motions.granted "\
"\n and advance_on_docket_motions.created_at > appeals.established_at then 1 end) = ?", 0)
end

def include_aod_motions
joins(:claimants)
.joins("LEFT OUTER JOIN people on people.participant_id = claimants.participant_id")
.joins("LEFT OUTER JOIN advance_on_docket_motions on advance_on_docket_motions.person_id = people.id")
end

def ready_for_distribution
joins(:tasks)
.group("appeals.id")
.having("count(case when tasks.type = ? and tasks.status = ? then 1 end) >= ?",
DistributionTask.name, Constants.TASK_STATUSES.assigned, 1)
end

def genpop
joins(with_assigned_distribution_task_sql)
.with_original_appeal_and_judge_task
.where(
"appeals.stream_type != ? OR distribution_task.assigned_at <= ? 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
)
end

def with_original_appeal_and_judge_task
joins("LEFT JOIN cavc_remands ON cavc_remands.remand_appeal_id = appeals.id")
.joins("LEFT JOIN appeals AS original_cavc_appeal ON original_cavc_appeal.id = cavc_remands.source_appeal_id")
.joins(
"LEFT JOIN tasks AS original_judge_task ON original_judge_task.appeal_id = original_cavc_appeal.id
AND original_judge_task.type = 'JudgeDecisionReviewTask'
AND original_judge_task.status = 'completed'"
)
end

# Within the first 21 days, the appeal should be distributed only to the issuing judge.
def non_genpop_for_judge(judge)
joins(with_assigned_distribution_task_sql)
.with_original_appeal_and_judge_task
.where("distribution_task.assigned_at > ?", CaseDistributionLever.cavc_affinity_days.days.ago)
.where(original_judge_task: { assigned_to_id: judge&.id })
end

def ordered_by_distribution_ready_date
joins(:tasks)
.group("appeals.id")
.order(
Arel.sql("max(case when tasks.type = 'DistributionTask' then tasks.assigned_at end)")
)
end

def non_ihp
joins(:tasks)
.group("appeals.id")
.having("count(case when tasks.type = ? then 1 end) = ?",
InformalHearingPresentationTask.name, 0)
end

# From hearing_request_distribution_query.rb

def most_recent_hearings
query = <<-SQL
INNER JOIN
(SELECT h.appeal_id, max(hd.scheduled_for) as latest_scheduled_for
FROM hearings h
JOIN hearing_days hd on h.hearing_day_id = hd.id
GROUP BY
h.appeal_id
) as latest_date_by_appeal
ON appeals.id = latest_date_by_appeal.appeal_id
AND hearing_days.scheduled_for = latest_date_by_appeal.latest_scheduled_for
SQL

joins(query, hearings: :hearing_day)
end

def tied_to_distribution_judge(judge)
joins(with_assigned_distribution_task_sql)
.where(hearings: { disposition: "held", judge_id: judge.id })
.where("distribution_task.assigned_at > ?", CaseDistributionLever.ama_hearing_case_affinity_days.days.ago)
end

def tied_to_ineligible_judge
where(hearings: { disposition: "held", judge_id: HearingRequestDistributionQuery.ineligible_judges_id_cache })
.where("1 = ?", FeatureToggle.enabled?(:acd_cases_tied_to_judges_no_longer_with_board) ? 1 : 0)
end

def tied_to_judges_with_exclude_appeals_from_affinity
where(hearings: { disposition: "held", judge_id: JudgeTeam.judges_with_exclude_appeals_from_affinity })
.where("1 = ?", FeatureToggle.enabled?(:acd_exclude_from_affinity) ? 1 : 0)
end

# If an appeal has exceeded the affinity, it should be returned to genpop.
def exceeding_affinity_threshold
joins(with_assigned_distribution_task_sql)
.where(hearings: { disposition: "held" })
.where("distribution_task.assigned_at <= ?", CaseDistributionLever.ama_hearing_case_affinity_days.days.ago)
end

# Historical note: We formerly had not_tied_to_any_active_judge until CASEFLOW-1928,
# when that distinction became irrelevant because cases become genpop after 30 days anyway.
def not_tied_to_any_judge
where(hearings: { disposition: "held", judge_id: nil })
end

def with_no_hearings
left_joins(:hearings).where(hearings: { id: nil })
end

def with_no_held_hearings
left_joins(:hearings).where.not(hearings: { disposition: "held" })
end

def with_held_hearings
where(hearings: { disposition: "held" })
end
end
88 changes: 5 additions & 83 deletions app/models/docket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
class Docket
include ActiveModel::Model
include DistributionConcern
include DistributionScopes

def docket_type
fail Caseflow::Error::MustImplementInSubclass
end

# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# :reek:LongParameterList
def appeals(priority: nil, genpop: nil, ready: nil, judge: nil)
fail "'ready for distribution' value cannot be false" if ready == false
Expand All @@ -27,7 +28,7 @@ def appeals(priority: nil, genpop: nil, ready: nil, judge: nil)

scope.order("appeals.receipt_date")
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

def count(priority: nil, ready: nil)
# The underlying scopes here all use `group_by` statements, so calling
Expand Down Expand Up @@ -122,7 +123,7 @@ def distribute_appeals(distribution, priority: false, genpop: nil, limit: 1, sty
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Lint/UnusedMethodArgument

def self.nonpriority_decisions_per_year
Appeal.extending(Scopes).nonpriority
Appeal.extending(DistributionScopes).nonpriority
.joins(:decision_documents)
.where("decision_date > ?", 1.year.ago)
.pluck(:id).size
Expand All @@ -148,89 +149,10 @@ def scoped_for_priority(scope)
end

def docket_appeals
Appeal.where(docket_type: docket_type).extending(Scopes)
Appeal.where(docket_type: docket_type).extending(DistributionScopes)
end

def use_by_docket_date?
FeatureToggle.enabled?(:acd_distribute_by_docket_date, user: RequestStore.store[:current_user])
end

module Scopes
include DistributionScopes

def priority
include_aod_motions
.where("advance_on_docket_motions.created_at > appeals.established_at")
.where("advance_on_docket_motions.granted = ?", true)
.or(include_aod_motions.where("people.date_of_birth <= ?", 75.years.ago))
.or(include_aod_motions.where("appeals.stream_type = ?", Constants.AMA_STREAM_TYPES.court_remand))
.group("appeals.id")
end

def nonpriority
include_aod_motions
.where("people.date_of_birth > ? or people.date_of_birth is null", 75.years.ago)
.where.not("appeals.stream_type = ?", Constants.AMA_STREAM_TYPES.court_remand)
.group("appeals.id")
.having("count(case when advance_on_docket_motions.granted "\
"\n and advance_on_docket_motions.created_at > appeals.established_at then 1 end) = ?", 0)
end

def include_aod_motions
joins(:claimants)
.joins("LEFT OUTER JOIN people on people.participant_id = claimants.participant_id")
.joins("LEFT OUTER JOIN advance_on_docket_motions on advance_on_docket_motions.person_id = people.id")
end

def ready_for_distribution
joins(:tasks)
.group("appeals.id")
.having("count(case when tasks.type = ? and tasks.status = ? then 1 end) >= ?",
DistributionTask.name, Constants.TASK_STATUSES.assigned, 1)
end

def genpop
joins(with_assigned_distribution_task_sql)
.with_original_appeal_and_judge_task
.where(
"appeals.stream_type != ? OR distribution_task.assigned_at <= ? 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
)
end

def with_original_appeal_and_judge_task
joins("LEFT JOIN cavc_remands ON cavc_remands.remand_appeal_id = appeals.id")
.joins("LEFT JOIN appeals AS original_cavc_appeal ON original_cavc_appeal.id = cavc_remands.source_appeal_id")
.joins(
"LEFT JOIN tasks AS original_judge_task ON original_judge_task.appeal_id = original_cavc_appeal.id
AND original_judge_task.type = 'JudgeDecisionReviewTask'
AND original_judge_task.status = 'completed'"
)
end

# Within the first 21 days, the appeal should be distributed only to the issuing judge.
def non_genpop_for_judge(judge)
joins(with_assigned_distribution_task_sql)
.with_original_appeal_and_judge_task
.where("distribution_task.assigned_at > ?", CaseDistributionLever.cavc_affinity_days.days.ago)
.where(original_judge_task: { assigned_to_id: judge.id })
end

def ordered_by_distribution_ready_date
joins(:tasks)
.group("appeals.id")
.order(
Arel.sql("max(case when tasks.type = 'DistributionTask' then tasks.assigned_at end)")
)
end

def non_ihp
joins(:tasks)
.group("appeals.id")
.having("count(case when tasks.type = ? then 1 end) = ?",
InformalHearingPresentationTask.name, 0)
end
end
end
65 changes: 3 additions & 62 deletions app/queries/hearing_request_distribution_query.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# frozen_string_literal: true

class HearingRequestDistributionQuery
include DistributionScopes

def initialize(base_relation:, genpop:, judge: nil, use_by_docket_date: false)
@base_relation = base_relation.extending(Scopes)
@base_relation = base_relation.extending(DistributionScopes)
@genpop = genpop
@judge = judge
@use_by_docket_date = use_by_docket_date
Expand Down Expand Up @@ -94,65 +96,4 @@ def most_recent_held_hearings_tied_to_ineligible_judge
def most_recent_held_hearings_tied_to_judges_with_exclude_appeals_from_affinity
base_relation.most_recent_hearings.tied_to_judges_with_exclude_appeals_from_affinity
end

module Scopes
include DistributionScopes
def most_recent_hearings
query = <<-SQL
INNER JOIN
(SELECT h.appeal_id, max(hd.scheduled_for) as latest_scheduled_for
FROM hearings h
JOIN hearing_days hd on h.hearing_day_id = hd.id
GROUP BY
h.appeal_id
) as latest_date_by_appeal
ON appeals.id = latest_date_by_appeal.appeal_id
AND hearing_days.scheduled_for = latest_date_by_appeal.latest_scheduled_for
SQL

joins(query, hearings: :hearing_day)
end

def tied_to_distribution_judge(judge)
joins(with_assigned_distribution_task_sql)
.where(hearings: { disposition: "held", judge_id: judge.id })
.where("distribution_task.assigned_at > ?",
CaseDistributionLever.ama_hearing_case_affinity_days.days.ago)
end

def tied_to_ineligible_judge
where(hearings: { disposition: "held", judge_id: HearingRequestDistributionQuery.ineligible_judges_id_cache })
.where("1 = ?", FeatureToggle.enabled?(:acd_cases_tied_to_judges_no_longer_with_board) ? 1 : 0)
end

def tied_to_judges_with_exclude_appeals_from_affinity
where(hearings: { disposition: "held", judge_id: JudgeTeam.judges_with_exclude_appeals_from_affinity })
.where("1 = ?", FeatureToggle.enabled?(:acd_exclude_from_affinity) ? 1 : 0)
end

# If an appeal has exceeded the affinity, it should be returned to genpop.
def exceeding_affinity_threshold
joins(with_assigned_distribution_task_sql)
.where(hearings: { disposition: "held" })
.where("distribution_task.assigned_at <= ?", CaseDistributionLever.ama_hearing_case_affinity_days.days.ago)
end

# Historical note: We formerly had not_tied_to_any_active_judge until CASEFLOW-1928,
# when that distinction became irrelevant because cases become genpop after 30 days anyway.
def not_tied_to_any_judge
where(hearings: { disposition: "held", judge_id: nil })
end

def with_no_hearings
left_joins(:hearings).where(hearings: { id: nil })
end

def with_no_held_hearings
left_joins(:hearings).where.not(hearings: { disposition: "held" })
end

def with_held_hearings
where(hearings: { disposition: "held" })
end
end
end
10 changes: 5 additions & 5 deletions db/seeds/ama_affinity_cases.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

# This seed creates ~100 appeals which have an affinity to a judge based on levers in DISTRIBUTION.json,
# This seed creates ~100 appeals which have an affinity to a judge based case distribution algorithm levers,
# and ~100 appeals which are similar but fall just outside of the affinity day levers and will be distributed
# to any judge. Used primarily in testing APPEALS-36998 and other ACD feature work
module Seeds
Expand Down Expand Up @@ -88,7 +88,7 @@ def create_case_ready_for_less_than_cavc_affinty_days(judge)
# return system time back to now, then go to desired date where appeal will be ready for distribution
# using [].max with 0 will ensure that if the lever is set to 0 we won't go into the future
Timecop.return
Timecop.travel([(Constants.DISTRIBUTION.cavc_affinity_days - 7), 0].max.days.ago)
Timecop.travel([(CaseDistributionLever.cavc_affinity_days - 7), 0].max.days.ago)

# complete the CAVC task and make the appeal ready to distribute
remand.remand_appeal.tasks.where(type: SendCavcRemandProcessedLetterTask.name).first.completed!
Expand Down Expand Up @@ -122,7 +122,7 @@ def create_case_ready_for_more_than_cavc_affinty_days(judge)

# return system time back to now, then go to desired date where appeal will be ready for distribution
Timecop.return
Timecop.travel((Constants.DISTRIBUTION.cavc_affinity_days + 7).days.ago)
Timecop.travel((CaseDistributionLever.cavc_affinity_days + 7).days.ago)

# complete the CAVC task and make the appeal ready to distribute
remand.remand_appeal.tasks.where(type: SendCavcRemandProcessedLetterTask.name).first.completed!
Expand All @@ -139,7 +139,7 @@ def create_case_ready_for_less_than_hearing_affinity_days(judge)
# add 91 days for the amount of time the post-hearing tasks are open and remove 7 to make the case ready
# for less than the hearing affinity days value
Timecop.return
Timecop.travel((91 + Constants.DISTRIBUTION.hearing_case_affinity_days - 7).days.ago)
Timecop.travel((91 + CaseDistributionLever.ama_hearing_case_affinity_days - 7).days.ago)
create(:hearing, :held, appeal: appeal, judge: judge, adding_user: User.system_user)

# travel to when the tasks will auto-complete and complete them
Expand All @@ -160,7 +160,7 @@ def create_case_ready_for_more_than_hearing_affinity_days(judge)
# add 91 days for the amount of time the post-hearing tasks are open and add 7 more to make the case ready
# for more than the hearing affinity days value
Timecop.return
Timecop.travel((91 + Constants.DISTRIBUTION.hearing_case_affinity_days + 7).days.ago)
Timecop.travel((91 + CaseDistributionLever.ama_hearing_case_affinity_days + 7).days.ago)
create(:hearing, :held, appeal: appeal, judge: judge, adding_user: User.system_user)

# travel to when the tasks will auto-complete and complete them
Expand Down

0 comments on commit b64b2a7

Please sign in to comment.