diff --git a/spec/jobs/ama_notification_efolder_sync_job_spec.rb b/spec/jobs/ama_notification_efolder_sync_job_spec.rb index c146f1a2ede..5c46a817f6a 100644 --- a/spec/jobs/ama_notification_efolder_sync_job_spec.rb +++ b/spec/jobs/ama_notification_efolder_sync_job_spec.rb @@ -180,7 +180,7 @@ def find_appeal_ids_from_first_document_sync end def clean_up_after_threads - DatabaseCleaner.clean_with(:truncation, except: %w[notification_events]) + DatabaseCleaner.clean_with(:truncation, except: %w[notification_events vftypes issref]) end end end diff --git a/spec/jobs/legacy_notification_efolder_sync_job_spec.rb b/spec/jobs/legacy_notification_efolder_sync_job_spec.rb index a3a1e4a3aca..73e8f771ab0 100644 --- a/spec/jobs/legacy_notification_efolder_sync_job_spec.rb +++ b/spec/jobs/legacy_notification_efolder_sync_job_spec.rb @@ -196,7 +196,7 @@ def find_appeal_ids_from_first_document_sync end def clean_up_after_threads - DatabaseCleaner.clean_with(:truncation, except: %w[notification_events]) + DatabaseCleaner.clean_with(:truncation, except: %w[notification_events vftypes issref]) end def ensure_notification_events_exist diff --git a/spec/jobs/poll_docketed_legacy_appeals_job_spec.rb b/spec/jobs/poll_docketed_legacy_appeals_job_spec.rb index 976e6b0d761..bb6ffc006a4 100644 --- a/spec/jobs/poll_docketed_legacy_appeals_job_spec.rb +++ b/spec/jobs/poll_docketed_legacy_appeals_job_spec.rb @@ -6,7 +6,7 @@ describe "polling for docketed appeals" do before do - Seeds::NotificationEvents.new.seed! + Seeds::NotificationEvents.new.seed! unless NotificationEvent.count > 0 end let!(:today) { Time.now.utc.iso8601 } diff --git a/spec/jobs/push_priority_appeals_to_judges_job_spec.rb b/spec/jobs/push_priority_appeals_to_judges_job_spec.rb index 2cbacfb1b7f..d46b5c21be7 100644 --- a/spec/jobs/push_priority_appeals_to_judges_job_spec.rb +++ b/spec/jobs/push_priority_appeals_to_judges_job_spec.rb @@ -5,32 +5,35 @@ def to_judge_hash(arr) arr.each_with_index.map { |count, i| [i, count] }.to_h end - context "Test which Distribution is being included" do - before { FeatureToggle.enable!(:acd_distribute_by_docket_date) } + context ".perform" do + before do + expect_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:distribute_genpop_priority_appeals).and_return([]) + expect_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:send_job_report).and_return([]) + end + after { FeatureToggle.disable!(:acd_distribute_by_docket_date) } - subject { described_class.ancestors } + subject { described_class.perform_now } + + it "using Automatic Case Distribution module" do + expect_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:distribute_non_genpop_priority_appeals).and_return([]) - xit do - is_expected.to include ByDocketDateDistribution - is_expected.to_not include AutomaticCaseDistribution + subject end - end - context ".distribute_non_genpop_priority_appeals" do - before do - FeatureToggle.disable!(:acd_distribute_by_docket_date) - allow_any_instance_of(DirectReviewDocket) - .to receive(:nonpriority_receipts_per_year) - .and_return(100) + it "using By Docket Date Distribution module" do + FeatureToggle.enable!(:acd_distribute_by_docket_date) + expect_any_instance_of(PushPriorityAppealsToJudgesJob) + .to_not receive(:distribute_non_genpop_priority_appeals).and_return([]) - allow(Docket) - .to receive(:nonpriority_decisions_per_year) - .and_return(1000) - allow_any_instance_of(PushPriorityAppealsToJudgesJob).to receive(:eligible_judges).and_return(eligible_judges) + subject end - after { FeatureToggle.enable!(:acd_distribute_by_docket_date) } + end + context ".distribute_non_genpop_priority_appeals" do let(:ready_priority_bfkey) { "12345" } let(:ready_priority_bfkey2) { "12346" } let(:ready_priority_uuid) { "bece6907-3b6f-4c49-a580-6d5f2e1ca65c" } @@ -176,28 +179,7 @@ def to_judge_hash(arr) ] end - subject { PushPriorityAppealsToJudgesJob.new.distribute_non_genpop_priority_appeals } - - it "should only distribute the ready priority cases tied to a judge" do - expect(subject.count).to eq eligible_judges.count - expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [1, 1, 0, 0] - - # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge - distributed_cases = DistributedCase.where(distribution: subject) - expect(distributed_cases.count).to eq 2 - expected_array = [ready_priority_bfkey, ready_priority_bfkey2] - expect(distributed_cases.map(&:case_id)).to match_array expected_array - # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases - expected_array2 = %w[legacy legacy] - expect(distributed_cases.map(&:docket)).to match_array expected_array2 - expect(distributed_cases.map(&:priority).uniq).to match_array [true] - expect(distributed_cases.map(&:genpop).uniq).to match_array [false] - end - end - - context ".distribute_non_genpop_priority_appeals - All Case Distribution" do before do - FeatureToggle.enable!(:acd_distribute_by_docket_date) allow_any_instance_of(DirectReviewDocket) .to receive(:nonpriority_receipts_per_year) .and_return(100) @@ -205,170 +187,56 @@ def to_judge_hash(arr) allow(Docket) .to receive(:nonpriority_decisions_per_year) .and_return(1000) - allow_any_instance_of(PushPriorityAppealsToJudgesJob).to receive(:eligible_judges).and_return(eligible_judges) end - let(:ready_priority_bfkey) { "12345" } - let(:ready_priority_bfkey2) { "12346" } - let(:ready_priority_uuid) { "bece6907-3b6f-4c49-a580-6d5f2e1ca65c" } - let(:ready_priority_uuid2) { "bece6907-3b6f-4c49-a580-6d5f2e1ca65d" } - let!(:judge_with_ready_priority_cases) do - create(:user, :judge, :with_vacols_judge_record).tap do |judge| - vacols_case = create( - :case, - :aod, - bfkey: ready_priority_bfkey, - bfd19: 1.year.ago, - bfac: "3", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: 3.days.ago, - bfbox: nil, - folder: build(:folder, tinum: "1801003", titrnum: "123456789S") - ) - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date, - board_member: judge.vacols_attorney_id - ) + subject { PushPriorityAppealsToJudgesJob.new.distribute_non_genpop_priority_appeals } - appeal = create( - :appeal, - :ready_for_distribution, - :advanced_on_docket_due_to_age, - uuid: ready_priority_uuid, - docket_type: Constants.AMA_DOCKETS.hearing - ) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update!(judge: judge) + context "using Automatic Case Distribution module" do + before do + allow_any_instance_of(PushPriorityAppealsToJudgesJob).to receive(:eligible_judges).and_return(eligible_judges) end - end - let!(:judge_with_ready_nonpriority_cases) do - create(:user, :judge, :with_vacols_judge_record).tap do |judge| - vacols_case = create( - :case, - bfd19: 1.year.ago, - bfac: "3", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: 3.days.ago, - bfbox: nil, - folder: build(:folder, tinum: "1801002", titrnum: "123456782S") - ) - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date, - board_member: judge.vacols_attorney_id - ) + it "should only distribute the ready priority cases tied to a judge" do + expect(subject.count).to eq eligible_judges.count + expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [1, 1, 0, 0] - appeal = create( - :appeal, - :ready_for_distribution, - docket_type: Constants.AMA_DOCKETS.hearing - ) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update!(judge: judge) + # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq 2 + expected_array = [ready_priority_bfkey, ready_priority_bfkey2] + expect(distributed_cases.map(&:case_id)).to match_array expected_array + # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases + expected_array2 = %w[legacy legacy] + expect(distributed_cases.map(&:docket)).to match_array expected_array2 + expect(distributed_cases.map(&:priority).uniq).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq).to match_array [false] end end - let!(:judge_with_nonready_priority_cases) do - create(:user, :judge).tap do |judge| - create(:staff, :judge_role, user: judge) - vacols_case = create( - :case, - :aod, - bfd19: 1.year.ago, - bfac: "3", - bfmpro: "ACT", - bfcurloc: "not ready", - bfdloout: 3.days.ago, - bfbox: nil, - folder: build(:folder, tinum: "1801003", titrnum: "123456783S") - ) - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date, - board_member: judge.vacols_attorney_id - ) + context "using By Docket Date Distribution module" do + before do + FeatureToggle.enable!(:acd_distribute_by_docket_date) - appeal = create( - :appeal, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.hearing - ) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update!(judge: judge) + allow_any_instance_of(PushPriorityAppealsToJudgesJob).to receive(:eligible_judges).and_return(eligible_judges) end - end - let!(:ama_only_judge_with_ready_priority_cases) do - create(:user, :ama_only_judge, :with_vacols_judge_record).tap do |judge| - vacols_case = create( - :case, - :aod, - bfkey: ready_priority_bfkey2, - bfd19: 1.year.ago, - bfac: "3", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: 3.days.ago, - bfbox: nil, - folder: build(:folder, tinum: "1801005", titrnum: "923456790S") - ) - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date, - board_member: judge.vacols_attorney_id - ) - appeal = create( - :appeal, - :ready_for_distribution, - :advanced_on_docket_due_to_age, - uuid: "bece6907-3b6f-4c49-a580-6d5f2e1ca65d", - docket_type: Constants.AMA_DOCKETS.hearing - ) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update!(judge: judge) - end - end - let(:eligible_judges) do - [ - judge_with_ready_priority_cases, - judge_with_ready_nonpriority_cases, - judge_with_nonready_priority_cases, - ama_only_judge_with_ready_priority_cases - ] - end + after { FeatureToggle.disable!(:acd_distribute_by_docket_date) } - subject { PushPriorityAppealsToJudgesJob.new.distribute_non_genpop_priority_appeals } + it "should only distribute the ready priority cases tied to a judge" do + expect(subject.count).to eq eligible_judges.count + expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [3, 1, 0, 0] - it "should only distribute the ready priority cases tied to a judge" do - expect(subject.count).to eq eligible_judges.count - expect(subject.map { |dist| dist.statistics["batch_size"] }).to match_array [3, 1, 0, 0] - - # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge - distributed_cases = DistributedCase.where(distribution: subject) - expect(distributed_cases.count).to eq 4 - expected_array = [ready_priority_bfkey, ready_priority_bfkey2, ready_priority_uuid, ready_priority_uuid2] - expect(distributed_cases.map(&:case_id)).to match_array expected_array - # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases - expected_array2 = %w[hearing hearing legacy legacy] - expect(distributed_cases.map(&:docket)).to match_array expected_array2 - expect(distributed_cases.map(&:priority).uniq).to match_array [true] - expect(distributed_cases.map(&:genpop).uniq).to match_array [false, true] + # Ensure we only distributed the 2 ready legacy and hearing priority cases that are tied to a judge + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq 4 + expected_array = [ready_priority_bfkey, ready_priority_bfkey2, ready_priority_uuid, ready_priority_uuid2] + expect(distributed_cases.map(&:case_id)).to match_array expected_array + # Ensure all docket types cases are distributed, including the 5 cavc evidence submission cases + expected_array2 = %w[hearing hearing legacy legacy] + expect(distributed_cases.map(&:docket)).to match_array expected_array2 + expect(distributed_cases.map(&:priority).uniq).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq).to match_array [false, true] + end end end @@ -452,150 +320,75 @@ def to_judge_hash(arr) let(:priority_count) { Appeal.count { |a| a.aod? || a.cavc? } + legacy_priority_cases.count } let(:priority_target) { (priority_count + judge_distributions_this_month.sum) / judges.count } - it "should distribute ready priority appeals to the judges" do - expect(subject.count).to eq judges.count - - # Ensure we distributed all available ready cases from any docket that are not tied to a judge - distributed_cases = DistributedCase.where(distribution: subject) - expect(distributed_cases.count).to eq priority_count - expect(distributed_cases.map(&:priority).uniq.compact).to match_array [true] - expect(distributed_cases.map(&:genpop).uniq.compact).to match_array [true] - expect(distributed_cases.pluck(:docket).uniq).to match_array(Constants::AMA_DOCKETS.keys.unshift("legacy")) - expect(distributed_cases.group(:docket).count.values.uniq).to match_array [5] - end - - it "distributes cases to each judge based on their priority target" do - judges.each_with_index do |judge, i| - target_distributions = priority_target - judge_distributions_this_month[i] - distribution = subject.detect { |dist| dist.judge_id == judge.id } - expect(distribution.statistics["batch_size"]).to eq target_distributions - distributed_cases = DistributedCase.where(distribution: distribution) - expect(distributed_cases.count).to eq target_distributions - end - end + context "using Automatic Case Distribution module" do + it "should distribute ready priority appeals to the judges" do + expect(subject.count).to eq judges.count - context "where there is an ama-only judge" do - let!(:ama_only_judge) { create(:user, :judge, :ama_only_judge, :with_vacols_judge_record) } - let(:judges) { [ama_only_judge] } - it "only distributes ama cases to ama-only judge" do + # Ensure we distributed all available ready cases from any docket that are not tied to a judge distributed_cases = DistributedCase.where(distribution: subject) - distributed_cases.each do |distributed_case| - expect(distributed_case.docket).not_to eq("legacy") + expect(distributed_cases.count).to eq priority_count + expect(distributed_cases.map(&:priority).uniq.compact).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq.compact).to match_array [true] + expect(distributed_cases.pluck(:docket).uniq).to match_array(Constants::AMA_DOCKETS.keys.unshift("legacy")) + expect(distributed_cases.group(:docket).count.values.uniq).to match_array [5] + end + + it "distributes cases to each judge based on their priority target" do + judges.each_with_index do |judge, i| + target_distributions = priority_target - judge_distributions_this_month[i] + distribution = subject.detect { |dist| dist.judge_id == judge.id } + expect(distribution.statistics["batch_size"]).to eq target_distributions + distributed_cases = DistributedCase.where(distribution: distribution) + expect(distributed_cases.count).to eq target_distributions end end - end - end - - context ".distribute_genpop_priority_appeals - All Case Distribution" do - before do - FeatureToggle.enable!(:acd_distribute_by_docket_date) - allow_any_instance_of(DirectReviewDocket) - .to receive(:nonpriority_receipts_per_year) - .and_return(100) - - allow(Docket) - .to receive(:nonpriority_decisions_per_year) - .and_return(1000) - - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges).and_return( - judges.each_with_index.map { |judge, i| [judge.id, judge_distributions_this_month[i]] }.to_h - ) - end - - subject { PushPriorityAppealsToJudgesJob.new.distribute_genpop_priority_appeals } - let(:judges) { create_list(:user, 5, :judge, :with_vacols_judge_record) } - let(:judge_distributions_this_month) { (0..4).to_a } - let!(:legacy_priority_cases) do - (1..5).map do |i| - vacols_case = create( - :case, - :aod, - bfd19: 1.year.ago, - bfac: "1", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: i.months.ago, - folder: build( - :folder, - tinum: "1801#{format('%03d', index: i)}", - titrnum: "123456789S" - ) - ) - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date - ) - vacols_case - end - end - let!(:ready_priority_hearing_cases) do - (1..5).map do |i| - appeal = create(:appeal, - :advanced_on_docket_due_to_age, - :ready_for_distribution, - docket_type: Constants.AMA_DOCKETS.hearing) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.months.ago) - appeal.reload - end - end - let!(:ready_priority_evidence_cases) do - (1..5).map do |i| - appeal = create(:appeal, - :type_cavc_remand, - :cavc_ready_for_distribution, - docket_type: Constants.AMA_DOCKETS.evidence_submission) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.month.ago) - appeal - end - end - let!(:ready_priority_direct_cases) do - (1..5).map do |i| - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 1.month.ago) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.month.ago) - appeal + context "where there is an ama-only judge" do + let!(:ama_only_judge) { create(:user, :judge, :ama_only_judge, :with_vacols_judge_record) } + let(:judges) { [ama_only_judge] } + it "only distributes ama cases to ama-only judge" do + distributed_cases = DistributedCase.where(distribution: subject) + distributed_cases.each do |distributed_case| + expect(distributed_case.docket).not_to eq("legacy") + end + end end end - let(:priority_count) { Appeal.count { |a| a.aod? || a.cavc? } + legacy_priority_cases.count } - let(:priority_target) { (priority_count + judge_distributions_this_month.sum) / judges.count } - - it "should distribute ready priority appeals to the judges" do - expect(subject.count).to eq judges.count + context "using By Docket Date Distribution module" do + before { FeatureToggle.enable!(:acd_distribute_by_docket_date) } + after { FeatureToggle.disable!(:acd_distribute_by_docket_date) } - # Ensure we distributed all available ready cases from any docket that are not tied to a judge - distributed_cases = DistributedCase.where(distribution: subject) - expect(distributed_cases.count).to eq priority_count - expect(distributed_cases.map(&:priority).uniq.compact).to match_array [true] - expect(distributed_cases.map(&:genpop).uniq.compact).to match_array [true] - expect(distributed_cases.pluck(:docket).uniq).to match_array(Constants::AMA_DOCKETS.keys.unshift("legacy")) - expect(distributed_cases.group(:docket).count.values.uniq).to match_array [5] - end + it "should distribute ready priority appeals to the judges" do + expect(subject.count).to eq judges.count - it "distributes cases to each judge based on their priority target" do - judges.each_with_index do |judge, i| - target_distributions = priority_target - judge_distributions_this_month[i] - distribution = subject.detect { |dist| dist.judge_id == judge.id } - expect(distribution.statistics["batch_size"]).to eq target_distributions - distributed_cases = DistributedCase.where(distribution: distribution) - expect(distributed_cases.count).to eq target_distributions + # Ensure we distributed all available ready cases from any docket that are not tied to a judge + distributed_cases = DistributedCase.where(distribution: subject) + expect(distributed_cases.count).to eq priority_count + expect(distributed_cases.map(&:priority).uniq.compact).to match_array [true] + expect(distributed_cases.map(&:genpop).uniq.compact).to match_array [true] + expect(distributed_cases.pluck(:docket).uniq).to match_array(Constants::AMA_DOCKETS.keys.unshift("legacy")) + expect(distributed_cases.group(:docket).count.values.uniq).to match_array [5] + end + + it "distributes cases to each judge based on their priority target" do + judges.each_with_index do |judge, i| + target_distributions = priority_target - judge_distributions_this_month[i] + distribution = subject.detect { |dist| dist.judge_id == judge.id } + expect(distribution.statistics["batch_size"]).to eq target_distributions + distributed_cases = DistributedCase.where(distribution: distribution) + expect(distributed_cases.count).to eq target_distributions + end end - end - context "where there is an ama-only judge" do - let!(:ama_only_judge) { create(:user, :judge, :ama_only_judge, :with_vacols_judge_record) } - let(:judges) { [ama_only_judge] } - it "only distributes ama cases to ama-only judge" do - distributed_cases = DistributedCase.where(distribution: subject) - distributed_cases.each do |distributed_case| - expect(distributed_case.docket).not_to eq("legacy") + context "where there is an ama-only judge" do + let!(:ama_only_judge) { create(:user, :judge, :ama_only_judge, :with_vacols_judge_record) } + let(:judges) { [ama_only_judge] } + it "only distributes ama cases to ama-only judge" do + distributed_cases = DistributedCase.where(distribution: subject) + distributed_cases.each do |distributed_case| + expect(distributed_case.docket).not_to eq("legacy") + end end end end @@ -678,7 +471,9 @@ def to_judge_hash(arr) allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(20) end - it "returns ids and age of ready priority appeals not distributed" do + after { FeatureToggle.disable!(:acd_distribute_by_docket_date) } + + it "using Automatic Case Distribution module" do expect(subject.second).to eq "*Number of cases tied to judges distributed*: 10" expect(subject.third).to eq "*Number of general population cases distributed*: 10" @@ -708,7 +503,7 @@ def to_judge_hash(arr) expect(subject[19].include?(legacy_priority_case.bfkey)).to be true end - it "returns the Slack Message associated with By Docket Date Distribution" do + it "using By Docket Date Distribution module" do FeatureToggle.enable!(:acd_distribute_by_docket_date) expect(subject.second).to eq "*Number of cases distributed*: 10" @@ -737,118 +532,6 @@ def to_judge_hash(arr) expect(subject[17].include?(ready_priority_direct_case.uuid)).to be true expect(subject[18].include?(legacy_priority_case.bfkey)).to be true end - - after do - FeatureToggle.disable!(:acd_distribute_by_docket_date) - end - end - - context ".slack_report - All Case Distribution" do - FeatureToggle.enable!(:acd_distribute_by_docket_date) - let!(:job) { PushPriorityAppealsToJudgesJob.new } - let(:previous_distributions) { to_judge_hash([4, 3, 2, 1, 0]) } - let!(:legacy_priority_case) do - judge = create(:user, :judge, :with_vacols_judge_record) - create( - :case, - :aod, - bfd19: 1.year.ago, - bfac: "1", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: 1.month.ago, - folder: build( - :folder, - tinum: "1801000", - titrnum: "123456789S" - ) - ).tap do |vacols_case| - create( - :case_hearing, - :disposition_held, - folder_nr: vacols_case.bfkey, - hearing_date: 5.days.ago.to_date, - board_member: judge.vacols_attorney_id - ) - end - end - let!(:ready_priority_hearing_case) do - appeal = FactoryBot.create(:appeal, - :advanced_on_docket_due_to_age, - :ready_for_distribution, - docket_type: Constants.AMA_DOCKETS.hearing) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: 2.months.ago) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update!(judge: create(:user, :judge, :with_vacols_judge_record)) - appeal.reload - end - let!(:ready_priority_evidence_case) do - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.evidence_submission) - appeal.tasks.find_by(type: EvidenceSubmissionWindowTask.name).completed! - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: 3.months.ago) - appeal - end - let!(:ready_priority_direct_case) do - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 1.month.ago) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: 4.months.ago) - appeal - end - - let(:distributed_cases) do - (0...5).map do |count| - distribution = build(:distribution, statistics: { "batch_size" => count }) - distribution.save(validate: false) - distribution - end - end - - subject { job.slack_report } - - before do - job.instance_variable_set(:@tied_distributions, distributed_cases) - job.instance_variable_set(:@genpop_distributions, distributed_cases) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges).and_return(previous_distributions) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(20) - end - - it "returns ids and age of ready priority appeals not distributed" do - expect(subject.second).to eq "*Number of cases tied to judges distributed*: 10" - expect(subject.third).to eq "*Number of general population cases distributed*: 10" - - today = Time.zone.now.to_date - legacy_days_waiting = (today - legacy_priority_case.bfdloout.to_date).to_i - expect(subject[3]).to eq "*Age of oldest legacy case*: #{legacy_days_waiting} days" - direct_review_days_waiting = (today - ready_priority_direct_case.ready_for_distribution_at.to_date).to_i - expect(subject[4]).to eq "*Age of oldest direct_review case*: #{direct_review_days_waiting} days" - evidence_submission_days_waiting = (today - ready_priority_evidence_case.ready_for_distribution_at.to_date).to_i - expect(subject[5]).to eq "*Age of oldest evidence_submission case*: #{evidence_submission_days_waiting} days" - hearing_days_waiting = (today - ready_priority_hearing_case.ready_for_distribution_at.to_date).to_i - expect(subject[6]).to eq "*Age of oldest hearing case*: #{hearing_days_waiting} days" - - expect(subject[7]).to eq "*Total Number of appeals _not_ distributed*: 4" - expect(subject[8]).to eq "*Number of legacy appeals _not_ distributed*: 1" - expect(subject[9]).to eq "*Number of direct_review appeals _not_ distributed*: 1" - expect(subject[10]).to eq "*Number of evidence_submission appeals _not_ distributed*: 1" - expect(subject[11]).to eq "*Number of hearing appeals _not_ distributed*: 1" - expect(subject[12]).to eq "*Number of Legacy Hearing Non Genpop appeals _not_ distributed*: 1" - - expect(subject[15]).to eq "Priority Target: 6" - expect(subject[16]).to eq "Previous monthly distributions {judge_id=>count}: #{previous_distributions}" - expect(subject[17]).to eq COPY::PRIORITY_PUSH_WARNING_MESSAGE - expect(subject[18].include?(ready_priority_hearing_case.uuid)).to be true - expect(subject[18].include?(ready_priority_evidence_case.uuid)).to be true - expect(subject[18].include?(ready_priority_direct_case.uuid)).to be true - expect(subject[19].include?(legacy_priority_case.bfkey)).to be true - end end context ".eligible_judge_target_distributions_with_leftovers" do @@ -863,549 +546,52 @@ def to_judge_hash(arr) subject { PushPriorityAppealsToJudgesJob.new.eligible_judge_target_distributions_with_leftovers } it "returns hash of how many cases should be distributed to each judge that is below the priority target " \ - "including any leftover cases" do - expect(subject.values).to match_array expected_priority_targets_with_leftovers - end - end - - context "when the distributions this month have been even" do - shared_examples "even distributions this month" do - context "when there are more eligible judges than cases to distribute" do - let(:eligible_judge_count) { 10 } - let(:priority_count) { 5 } - # Priority target will be 0, 5 cases are allotted to 5 judges - let(:expected_priority_targets_with_leftovers) { Array.new(priority_count, 1) } - - it_behaves_like "correct target distributions with leftovers" - end - - context "when there are more cases to distribute than eligible judges" do - let(:eligible_judge_count) { 5 } - let(:priority_count) { 10 } - # Priority target will be 2, evenly distributed amongst judges - let(:expected_priority_targets_with_leftovers) { Array.new(eligible_judge_count, 2) } - - it_behaves_like "correct target distributions with leftovers" - - context "when the cases cannot be evenly distributed" do - let(:priority_count) { 9 } - # Priority target rounds down, leftover 4 cases are allotted to judges with the fewest cases to distribute - let(:expected_priority_targets_with_leftovers) { [1, 2, 2, 2, 2] } - - it_behaves_like "correct target distributions with leftovers" - end - end - end - - let(:distribution_counts) { Array.new(eligible_judge_count, number_of_distributions) } - - context "when there have been no distributions this month" do - let(:number_of_distributions) { 0 } - - it_behaves_like "even distributions this month" - end - - context "when there have been some distributions this month" do - let(:number_of_distributions) { eligible_judge_count * 2 } - - it_behaves_like "even distributions this month" - end - end - - context "when previous distributions counts are not greater than the priority target" do - let(:distribution_counts) { [10, 15, 20, 25, 30] } - let(:priority_count) { 75 } - # 75 should be able to be divided up to get all judges up to 35 - let(:expected_priority_targets_with_leftovers) { [25, 20, 15, 10, 5] } - - it_behaves_like "correct target distributions with leftovers" - - context "when cases are not evenly distributable" do - let(:priority_count) { 79 } - # Priority target still is 35, the leftover 4 cases are allotted to judges with the fewest cases to distribute - let(:expected_priority_targets_with_leftovers) { [25, 21, 16, 11, 6] } - - it_behaves_like "correct target distributions with leftovers" - end - end - - context "when previous distributions counts are greater than the priority target" do - let(:distribution_counts) { [0, 0, 22, 28, 50] } - let(:priority_count) { 50 } - # The two judges with 0 cases should receive 24 cases, the one judge with 22 cases should recieve 2, leaving us - # with [24, 24, 24, 28, 50] to hopefully even out more in the next distribution. - let(:expected_priority_targets_with_leftovers) { [24, 24, 2] } - - it_behaves_like "correct target distributions with leftovers" - - context "when the distribution counts are even more disparate" do - let(:distribution_counts) { [0, 0, 5, 9, 26, 27, 55, 56, 89, 100] } - let(:priority_count) { 50 } - # Ending counts should be [16, 16, 16, 16, 26, 27, 55, 56, 89, 100], perfectly using up all 50 cases - let(:expected_priority_targets_with_leftovers) { [16, 16, 11, 7] } - - it_behaves_like "correct target distributions with leftovers" - - context "when there are leftover cases" do - let(:priority_count) { 53 } - # Ending counts should be [16, 17, 17, 17, 26, 27, 55, 56, 89, 100] - let(:expected_priority_targets_with_leftovers) { [16, 17, 12, 8] } - - it_behaves_like "correct target distributions with leftovers" - end - end - end - - context "tracking distributions over time" do - let(:number_judges) { rand(5..10) } - let(:priority_count) { rand(10..30) } - # Github Issue 15984, this stops this test from flaking, by making sure the - # expects below are achievable for all values rand() will produce. - let(:max_preexisting_cases) { (priority_count / (number_judges - 1)).floor } - - before do - # Mock cases already distributed this month - @distribution_counts = to_judge_hash(Array.new(number_judges).map { rand(max_preexisting_cases) }) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges).and_return(@distribution_counts) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:ready_genpop_priority_appeals_count).and_return(priority_count) - end - - it "evens out over multiple calls" do - 4.times do - # Mock distributing cases each week - target_distributions = PushPriorityAppealsToJudgesJob.new.eligible_judge_target_distributions_with_leftovers - @distribution_counts.merge!(target_distributions) { |_, prev_dists, target_dist| prev_dists + target_dist } - end - final_counts = @distribution_counts.values.uniq - # Expect no more than two distinct counts, no more than 1 apart - expect(final_counts.max - final_counts.min).to be <= 1 - expect(final_counts.count).to be <= 2 - end - end - end - - context ".eligible_judge_target_distributions_with_leftovers - All Case Distribution" do - shared_examples "correct target distributions with leftovers" do - before do - FeatureToggle.enable!(:acd_distribute_by_docket_date) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges) - .and_return(to_judge_hash(distribution_counts)) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) - end - - subject { PushPriorityAppealsToJudgesJob.new.eligible_judge_target_distributions_with_leftovers } - - it "returns hash of how many cases should be distributed to each judge that is below the priority target " \ - "including any leftover cases" do - expect(subject.values).to match_array expected_priority_targets_with_leftovers - end - end - - context "when the distributions this month have been even" do - shared_examples "even distributions this month" do - context "when there are more eligible judges than cases to distribute" do - let(:eligible_judge_count) { 10 } - let(:priority_count) { 5 } - # Priority target will be 0, 5 cases are allotted to 5 judges - let(:expected_priority_targets_with_leftovers) { Array.new(priority_count, 1) } - - it_behaves_like "correct target distributions with leftovers" - end - - context "when there are more cases to distribute than eligible judges" do - let(:eligible_judge_count) { 5 } - let(:priority_count) { 10 } - # Priority target will be 2, evenly distributed amongst judges - let(:expected_priority_targets_with_leftovers) { Array.new(eligible_judge_count, 2) } - - it_behaves_like "correct target distributions with leftovers" - - context "when the cases cannot be evenly distributed" do - let(:priority_count) { 9 } - # Priority target rounds down, leftover 4 cases are allotted to judges with the fewest cases to distribute - let(:expected_priority_targets_with_leftovers) { [1, 2, 2, 2, 2] } - - it_behaves_like "correct target distributions with leftovers" - end - end - end - - let(:distribution_counts) { Array.new(eligible_judge_count, number_of_distributions) } - - context "when there have been no distributions this month" do - let(:number_of_distributions) { 0 } - - it_behaves_like "even distributions this month" - end - - context "when there have been some distributions this month" do - let(:number_of_distributions) { eligible_judge_count * 2 } - - it_behaves_like "even distributions this month" - end - end - - context "when previous distributions counts are not greater than the priority target" do - let(:distribution_counts) { [10, 15, 20, 25, 30] } - let(:priority_count) { 75 } - # 75 should be able to be divided up to get all judges up to 35 - let(:expected_priority_targets_with_leftovers) { [25, 20, 15, 10, 5] } - - it_behaves_like "correct target distributions with leftovers" - - context "when cases are not evenly distributable" do - let(:priority_count) { 79 } - # Priority target still is 35, the leftover 4 cases are allotted to judges with the fewest cases to distribute - let(:expected_priority_targets_with_leftovers) { [25, 21, 16, 11, 6] } - - it_behaves_like "correct target distributions with leftovers" - end - end - - context "when previous distributions counts are greater than the priority target" do - let(:distribution_counts) { [0, 0, 22, 28, 50] } - let(:priority_count) { 50 } - # The two judges with 0 cases should receive 24 cases, the one judge with 22 cases should recieve 2, leaving us - # with [24, 24, 24, 28, 50] to hopefully even out more in the next distribution. - let(:expected_priority_targets_with_leftovers) { [24, 24, 2] } - - it_behaves_like "correct target distributions with leftovers" - - context "when the distribution counts are even more disparate" do - let(:distribution_counts) { [0, 0, 5, 9, 26, 27, 55, 56, 89, 100] } - let(:priority_count) { 50 } - # Ending counts should be [16, 16, 16, 16, 26, 27, 55, 56, 89, 100], perfectly using up all 50 cases - let(:expected_priority_targets_with_leftovers) { [16, 16, 11, 7] } - - it_behaves_like "correct target distributions with leftovers" - - context "when there are leftover cases" do - let(:priority_count) { 53 } - # Ending counts should be [16, 17, 17, 17, 26, 27, 55, 56, 89, 100] - let(:expected_priority_targets_with_leftovers) { [16, 17, 12, 8] } - - it_behaves_like "correct target distributions with leftovers" - end - end - end - - context "tracking distributions over time" do - let(:number_judges) { rand(5..10) } - let(:priority_count) { rand(10..30) } - # Github Issue 15984, this stops this test from flaking, by making sure the - # expects below are achievable for all values rand() will produce. - let(:max_preexisting_cases) { (priority_count / (number_judges - 1)).floor } - - before do - # Mock cases already distributed this month - @distribution_counts = to_judge_hash(Array.new(number_judges).map { rand(max_preexisting_cases) }) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges).and_return(@distribution_counts) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:ready_genpop_priority_appeals_count).and_return(priority_count) - end - - it "evens out over multiple calls" do - 4.times do - # Mock distributing cases each week - target_distributions = PushPriorityAppealsToJudgesJob.new.eligible_judge_target_distributions_with_leftovers - @distribution_counts.merge!(target_distributions) { |_, prev_dists, target_dist| prev_dists + target_dist } - end - final_counts = @distribution_counts.values.uniq - # Expect no more than two distinct counts, no more than 1 apart - expect(final_counts.max - final_counts.min).to be <= 1 - expect(final_counts.count).to be <= 2 - end - end - end - - context ".leftover_cases_count" do - before do - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:target_distributions_for_eligible_judges).and_return(target_distributions) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) - end - - subject { PushPriorityAppealsToJudgesJob.new.leftover_cases_count } - - context "when the number of cases to distribute is evenly divisible by the number of judges that need cases" do - let(:eligible_judge_count) { 4 } - let(:priority_count) { 100 } - let(:target_distributions) do - Array.new(eligible_judge_count, priority_count / eligible_judge_count) - .each_with_index - .map { |count, i| [i, count] }.to_h - end - - it "returns no leftover cases" do - expect(subject).to eq 0 - end - end - - context "when the number of cases can be distributed to all judges evenly" do - let(:priority_count) { target_distributions.values.sum } - let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } - - it "returns no leftover cases" do - expect(subject).to eq 0 - end - end - - context "when the number of cases are not evenly distributable bewteen all judges" do - let(:leftover_cases_count) { target_distributions.count - 1 } - let(:priority_count) { target_distributions.values.sum + leftover_cases_count } - let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } - - it "returns the correct number of leftover cases" do - expect(subject).to eq leftover_cases_count - end - end - end - - context ".leftover_cases_count - All Case Distribution" do - before do - FeatureToggle.enable!(:acd_distribute_by_docket_date) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:target_distributions_for_eligible_judges).and_return(target_distributions) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) - end - - subject { PushPriorityAppealsToJudgesJob.new.leftover_cases_count } - - context "when the number of cases to distribute is evenly divisible by the number of judges that need cases" do - let(:eligible_judge_count) { 4 } - let(:priority_count) { 100 } - let(:target_distributions) do - Array.new(eligible_judge_count, priority_count / eligible_judge_count) - .each_with_index - .map { |count, i| [i, count] }.to_h - end - - it "returns no leftover cases" do - expect(subject).to eq 0 - end - end - - context "when the number of cases can be distributed to all judges evenly" do - let(:priority_count) { target_distributions.values.sum } - let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } - - it "returns no leftover cases" do - expect(subject).to eq 0 - end - end - - context "when the number of cases are not evenly distributable bewteen all judges" do - let(:leftover_cases_count) { target_distributions.count - 1 } - let(:priority_count) { target_distributions.values.sum + leftover_cases_count } - let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } - - it "returns the correct number of leftover cases" do - expect(subject).to eq leftover_cases_count - end - end - end - - context ".target_distributions_for_eligible_judges" do - shared_examples "correct target distributions" do - before do - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges) - .and_return(to_judge_hash(distribution_counts)) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) - end - - subject { PushPriorityAppealsToJudgesJob.new.target_distributions_for_eligible_judges } - - it "returns hash of how many cases should be distributed to each judge that is below the priority target " \ - "excluding any leftover cases" do - expect(subject).to eq to_judge_hash(expected_priority_targets) - end - end - - context "when the distributions this month have been even" do - let(:distribution_counts) { Array.new(eligible_judge_count, number_of_distributions) } - - context "when there have been no distributions this month" do - let(:number_of_distributions) { 0 } - - context "when there are more eligible judges than cases to distribute" do - let(:eligible_judge_count) { 10 } - let(:priority_count) { 5 } - # Priority target will be 0, cases will be allotted later from the leftover cases - let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } - - it_behaves_like "correct target distributions" - end - - context "when there are more cases to distribute than eligible judges" do - let(:eligible_judge_count) { 5 } - let(:priority_count) { 10 } - # Priority target will be 2, evenly distributed amongst judges - let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } - - it_behaves_like "correct target distributions" - - context "when the cases cannot be evenly distributed" do - let(:priority_count) { 9 } - # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } - - it_behaves_like "correct target distributions" - end - end - end - - context "when there have been some distributions this month" do - let(:number_of_distributions) { eligible_judge_count * 2 } - - context "when there are more eligible judges than cases to distribute" do - let(:eligible_judge_count) { 10 } - let(:priority_count) { 5 } - # Priority target is equal to how many cases have already been distributed. Cases will be allotted from the - # leftover cases. 5 judges will each be distributed 1 case - let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } - - it_behaves_like "correct target distributions" - end - - context "when there are more cases to distribute than eligible judges" do - let(:eligible_judge_count) { 5 } - let(:priority_count) { 10 } - # Priority target will be evenly distributed amongst judges - let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } - - it_behaves_like "correct target distributions" - - context "when the cases cannot be evenly distributed" do - let(:priority_count) { 9 } - # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } - - it_behaves_like "correct target distributions" - end - end - end - end - - context "when previous distributions counts are not greater than the priority target" do - let(:distribution_counts) { [10, 15, 20, 25, 30] } - let(:priority_count) { 75 } - # 75 should be able to be divided up to get all judges up to 35 - let(:expected_priority_targets) { [25, 20, 15, 10, 5] } - - it_behaves_like "correct target distributions" - - context "when cases are not evenly distributable" do - let(:priority_count) { 79 } - # Priority target still is 35, the leftover 4 cases will be allotted later in the algorithm - - it_behaves_like "correct target distributions" - end - end - - context "when previous distributions counts are greater than the priority target" do - let(:distribution_counts) { [0, 0, 22, 28, 50] } - let(:priority_count) { 50 } - # The two judges with 0 cases should receive 24 cases, the one judge with 22 cases should recieve 2, leaving us - # with [24, 24, 24, 28, 50] to hopefully even out more in the next distribution. - let(:expected_priority_targets) { [24, 24, 2] } - - it_behaves_like "correct target distributions" - - context "when the dirstibution counts are even more disparate" do - let(:distribution_counts) { [0, 0, 5, 9, 26, 27, 55, 56, 89, 100] } - let(:priority_count) { 50 } - # Ending counts should be [16, 16, 16, 16, 26, 27, 55, 56, 89, 100], perfectly using up all 50 cases - let(:expected_priority_targets) { [16, 16, 11, 7] } - - it_behaves_like "correct target distributions" - end - end - end - - context ".target_distributions_for_eligible_judges - All Case Distribution" do - shared_examples "correct target distributions" do - before do - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_eligible_judges) - .and_return(to_judge_hash(distribution_counts)) - allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) - end - - subject { PushPriorityAppealsToJudgesJob.new.target_distributions_for_eligible_judges } - - it "returns hash of how many cases should be distributed to each judge that is below the priority target " \ - "excluding any leftover cases" do - expect(subject).to eq to_judge_hash(expected_priority_targets) + "including any leftover cases" do + expect(subject.values).to match_array expected_priority_targets_with_leftovers end end context "when the distributions this month have been even" do - let(:distribution_counts) { Array.new(eligible_judge_count, number_of_distributions) } - - context "when there have been no distributions this month" do - let(:number_of_distributions) { 0 } - + shared_examples "even distributions this month" do context "when there are more eligible judges than cases to distribute" do let(:eligible_judge_count) { 10 } let(:priority_count) { 5 } - # Priority target will be 0, cases will be allotted later from the leftover cases - let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } + # Priority target will be 0, 5 cases are allotted to 5 judges + let(:expected_priority_targets_with_leftovers) { Array.new(priority_count, 1) } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" end context "when there are more cases to distribute than eligible judges" do let(:eligible_judge_count) { 5 } let(:priority_count) { 10 } # Priority target will be 2, evenly distributed amongst judges - let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } + let(:expected_priority_targets_with_leftovers) { Array.new(eligible_judge_count, 2) } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" context "when the cases cannot be evenly distributed" do let(:priority_count) { 9 } - # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } + # Priority target rounds down, leftover 4 cases are allotted to judges with the fewest cases to distribute + let(:expected_priority_targets_with_leftovers) { [1, 2, 2, 2, 2] } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" end end end - context "when there have been some distributions this month" do - let(:number_of_distributions) { eligible_judge_count * 2 } - - context "when there are more eligible judges than cases to distribute" do - let(:eligible_judge_count) { 10 } - let(:priority_count) { 5 } - # Priority target is equal to how many cases have already been distributed. Cases will be allotted from the - # leftover cases. 5 judges will each be distributed 1 case - let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } - - it_behaves_like "correct target distributions" - end + let(:distribution_counts) { Array.new(eligible_judge_count, number_of_distributions) } - context "when there are more cases to distribute than eligible judges" do - let(:eligible_judge_count) { 5 } - let(:priority_count) { 10 } - # Priority target will be evenly distributed amongst judges - let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } + context "when there have been no distributions this month" do + let(:number_of_distributions) { 0 } - it_behaves_like "correct target distributions" + it_behaves_like "even distributions this month" + end - context "when the cases cannot be evenly distributed" do - let(:priority_count) { 9 } - # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } + context "when there have been some distributions this month" do + let(:number_of_distributions) { eligible_judge_count * 2 } - it_behaves_like "correct target distributions" - end - end + it_behaves_like "even distributions this month" end end @@ -1413,15 +599,16 @@ def to_judge_hash(arr) let(:distribution_counts) { [10, 15, 20, 25, 30] } let(:priority_count) { 75 } # 75 should be able to be divided up to get all judges up to 35 - let(:expected_priority_targets) { [25, 20, 15, 10, 5] } + let(:expected_priority_targets_with_leftovers) { [25, 20, 15, 10, 5] } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" context "when cases are not evenly distributable" do let(:priority_count) { 79 } - # Priority target still is 35, the leftover 4 cases will be allotted later in the algorithm + # Priority target still is 35, the leftover 4 cases are allotted to judges with the fewest cases to distribute + let(:expected_priority_targets_with_leftovers) { [25, 21, 16, 11, 6] } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" end end @@ -1430,23 +617,103 @@ def to_judge_hash(arr) let(:priority_count) { 50 } # The two judges with 0 cases should receive 24 cases, the one judge with 22 cases should recieve 2, leaving us # with [24, 24, 24, 28, 50] to hopefully even out more in the next distribution. - let(:expected_priority_targets) { [24, 24, 2] } + let(:expected_priority_targets_with_leftovers) { [24, 24, 2] } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" - context "when the dirstibution counts are even more disparate" do + context "when the distribution counts are even more disparate" do let(:distribution_counts) { [0, 0, 5, 9, 26, 27, 55, 56, 89, 100] } let(:priority_count) { 50 } # Ending counts should be [16, 16, 16, 16, 26, 27, 55, 56, 89, 100], perfectly using up all 50 cases - let(:expected_priority_targets) { [16, 16, 11, 7] } + let(:expected_priority_targets_with_leftovers) { [16, 16, 11, 7] } - it_behaves_like "correct target distributions" + it_behaves_like "correct target distributions with leftovers" + + context "when there are leftover cases" do + let(:priority_count) { 53 } + # Ending counts should be [16, 17, 17, 17, 26, 27, 55, 56, 89, 100] + let(:expected_priority_targets_with_leftovers) { [16, 17, 12, 8] } + + it_behaves_like "correct target distributions with leftovers" + end + end + end + + context "tracking distributions over time" do + let(:number_judges) { rand(5..10) } + let(:priority_count) { rand(10..30) } + # Github Issue 15984, this stops this test from flaking, by making sure the + # expects below are achievable for all values rand() will produce. + let(:max_preexisting_cases) { (priority_count / (number_judges - 1)).floor } + + before do + # Mock cases already distributed this month + @distribution_counts = to_judge_hash(Array.new(number_judges).map { rand(max_preexisting_cases) }) + allow_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:priority_distributions_this_month_for_eligible_judges).and_return(@distribution_counts) + allow_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:ready_genpop_priority_appeals_count).and_return(priority_count) + end + + it "evens out over multiple calls" do + 4.times do + # Mock distributing cases each week + target_distributions = PushPriorityAppealsToJudgesJob.new.eligible_judge_target_distributions_with_leftovers + @distribution_counts.merge!(target_distributions) { |_, prev_dists, target_dist| prev_dists + target_dist } + end + final_counts = @distribution_counts.values.uniq + # Expect no more than two distinct counts, no more than 1 apart + expect(final_counts.max - final_counts.min).to be <= 1 + expect(final_counts.count).to be <= 2 end end end - context ".priority_target" do - shared_examples "correct target" do + context ".leftover_cases_count" do + before do + allow_any_instance_of(PushPriorityAppealsToJudgesJob) + .to receive(:target_distributions_for_eligible_judges).and_return(target_distributions) + allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) + end + + subject { PushPriorityAppealsToJudgesJob.new.leftover_cases_count } + + context "when the number of cases to distribute is evenly divisible by the number of judges that need cases" do + let(:eligible_judge_count) { 4 } + let(:priority_count) { 100 } + let(:target_distributions) do + Array.new(eligible_judge_count, priority_count / eligible_judge_count) + .each_with_index + .map { |count, i| [i, count] }.to_h + end + + it "returns no leftover cases" do + expect(subject).to eq 0 + end + end + + context "when the number of cases can be distributed to all judges evenly" do + let(:priority_count) { target_distributions.values.sum } + let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } + + it "returns no leftover cases" do + expect(subject).to eq 0 + end + end + + context "when the number of cases are not evenly distributable bewteen all judges" do + let(:leftover_cases_count) { target_distributions.count - 1 } + let(:priority_count) { target_distributions.values.sum + leftover_cases_count } + let(:target_distributions) { to_judge_hash([5, 10, 15, 20, 25]) } + + it "returns the correct number of leftover cases" do + expect(subject).to eq leftover_cases_count + end + end + end + + context ".target_distributions_for_eligible_judges" do + shared_examples "correct target distributions" do before do allow_any_instance_of(PushPriorityAppealsToJudgesJob) .to receive(:priority_distributions_this_month_for_eligible_judges) @@ -1454,10 +721,11 @@ def to_judge_hash(arr) allow_any_instance_of(DocketCoordinator).to receive(:genpop_priority_count).and_return(priority_count) end - subject { PushPriorityAppealsToJudgesJob.new.priority_target } + subject { PushPriorityAppealsToJudgesJob.new.target_distributions_for_eligible_judges } - it "calculates a target that distributes cases evenly over one month" do - expect(subject).to eq expected_priority_target + it "returns hash of how many cases should be distributed to each judge that is below the priority target " \ + "excluding any leftover cases" do + expect(subject).to eq to_judge_hash(expected_priority_targets) end end @@ -1470,57 +738,57 @@ def to_judge_hash(arr) context "when there are more eligible judges than cases to distribute" do let(:eligible_judge_count) { 10 } let(:priority_count) { 5 } - # Priority target will be 0, cases will be allotted from the leftover cases. judges will be distributed 1 case - let(:expected_priority_target) { 0 } + # Priority target will be 0, cases will be allotted later from the leftover cases + let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end context "when there are more cases to distribute than eligible judges" do let(:eligible_judge_count) { 5 } let(:priority_count) { 10 } - # Priority target will be evenly distributed amongst judges - let(:expected_priority_target) { 2 } + # Priority target will be 2, evenly distributed amongst judges + let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" context "when the cases cannot be evenly distributed" do let(:priority_count) { 9 } # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_target) { 1 } + let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end end end context "when there have been some distributions this month" do - let(:number_of_distributions) { 10 } + let(:number_of_distributions) { eligible_judge_count * 2 } context "when there are more eligible judges than cases to distribute" do let(:eligible_judge_count) { 10 } let(:priority_count) { 5 } # Priority target is equal to how many cases have already been distributed. Cases will be allotted from the - # leftover cases. judges will each be distributed 1 case - let(:expected_priority_target) { 10 } + # leftover cases. 5 judges will each be distributed 1 case + let(:expected_priority_targets) { Array.new(eligible_judge_count, 0) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end context "when there are more cases to distribute than eligible judges" do let(:eligible_judge_count) { 5 } let(:priority_count) { 10 } # Priority target will be evenly distributed amongst judges - let(:expected_priority_target) { 12 } + let(:expected_priority_targets) { Array.new(eligible_judge_count, 2) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" context "when the cases cannot be evenly distributed" do let(:priority_count) { 9 } # Priority target rounds down, the leftover 4 cases will be allotted later in the algorithm - let(:expected_priority_target) { 11 } + let(:expected_priority_targets) { Array.new(eligible_judge_count, 1) } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end end end @@ -1530,15 +798,15 @@ def to_judge_hash(arr) let(:distribution_counts) { [10, 15, 20, 25, 30] } let(:priority_count) { 75 } # 75 should be able to be divided up to get all judges up to 35 - let(:expected_priority_target) { 35 } + let(:expected_priority_targets) { [25, 20, 15, 10, 5] } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" context "when cases are not evenly distributable" do let(:priority_count) { 79 } # Priority target still is 35, the leftover 4 cases will be allotted later in the algorithm - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end end @@ -1547,22 +815,22 @@ def to_judge_hash(arr) let(:priority_count) { 50 } # The two judges with 0 cases should receive 24 cases, the one judge with 22 cases should recieve 2, leaving us # with [24, 24, 24, 28, 50] to hopefully even out more in the next distribution. - let(:expected_priority_target) { 24 } + let(:expected_priority_targets) { [24, 24, 2] } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" context "when the dirstibution counts are even more disparate" do let(:distribution_counts) { [0, 0, 5, 9, 26, 27, 55, 56, 89, 100] } let(:priority_count) { 50 } # Ending counts should be [16, 16, 16, 16, 26, 27, 55, 56, 89, 100], perfectly using up all 50 cases - let(:expected_priority_target) { 16 } + let(:expected_priority_targets) { [16, 16, 11, 7] } - it_behaves_like "correct target" + it_behaves_like "correct target distributions" end end end - context ".priority_target - All Case Distribution" do + context ".priority_target" do shared_examples "correct target" do before do allow_any_instance_of(PushPriorityAppealsToJudgesJob) @@ -1719,47 +987,6 @@ def to_judge_hash(arr) end end - context ".priority_distributions_this_month_for_eligible_judges - All Case Distribution" do - let!(:judge_without_team) { create(:user) } - let!(:judge_without_active_team) { create(:user).tap { |judge| JudgeTeam.create_for_judge(judge).inactive! } } - let!(:judge_without_priority_push_team) do - create(:user).tap { |judge| JudgeTeam.create_for_judge(judge).update(accepts_priority_pushed_cases: false) } - end - let!(:judge_with_org) { create(:user).tap { |judge| create(:organization).add_user(judge) } } - let!(:judge_with_team_and_distributions) { create(:user, :judge) } - let!(:judge_with_team_without_distributions) { create(:user, :judge) } - - let!(:distributions_for_valid_judge) { 6 } - - subject { PushPriorityAppealsToJudgesJob.new.priority_distributions_this_month_for_eligible_judges } - - before do - FeatureToggle.enable!(:acd_distribute_by_docket_date) - allow_any_instance_of(PushPriorityAppealsToJudgesJob) - .to receive(:priority_distributions_this_month_for_all_judges) - .and_return( - judge_without_team.id => 5, - judge_without_active_team.id => 5, - judge_without_priority_push_team.id => 5, - judge_with_org.id => 5, - judge_with_team_and_distributions.id => distributions_for_valid_judge - ) - end - - it "only returns hash containing the distribution counts of judges that can be pushed priority appeals" do - [ - judge_without_team, - judge_without_active_team, - judge_without_priority_push_team, - judge_with_org - ].each do |ineligible_judge| - expect(subject[ineligible_judge]).to be nil - end - expect(subject[judge_with_team_and_distributions.id]).to eq distributions_for_valid_judge - expect(subject[judge_with_team_without_distributions.id]).to eq 0 - end - end - context ".eligible_judges" do let!(:judge_without_team) { create(:user) } let!(:judge_without_active_team) { create(:user).tap { |judge| JudgeTeam.create_for_judge(judge).inactive! } } @@ -1893,8 +1120,6 @@ def to_judge_hash(arr) end context "when the entire job fails" do - before { FeatureToggle.disable!(:acd_distribute_by_docket_date) } - after { FeatureToggle.enable!(:acd_distribute_by_docket_date) } let(:error_msg) { "Some dummy error" } it "sends a message to Slack that includes the error" do diff --git a/spec/models/concerns/by_docket_date_distribution_spec.rb b/spec/models/concerns/by_docket_date_distribution_spec.rb index 7b35b4452d0..1eeb987c012 100644 --- a/spec/models/concerns/by_docket_date_distribution_spec.rb +++ b/spec/models/concerns/by_docket_date_distribution_spec.rb @@ -66,8 +66,8 @@ def add_dates_to_date_array(num) .with(@new_acd, priority: true, style: "push", limit: priority_count_hash[:hearing]) .and_return(add_object_to_return_array(priority_count_hash[:hearing])) - # requested_distribution is private so .send is used to directly call it - return_array = @new_acd.send :priority_push_distribution, 12 + # priority_push_distribution is private so .send is used to directly call it + @new_acd.send :priority_push_distribution, 12 end end @@ -110,6 +110,7 @@ def add_dates_to_date_array(num) context "#num_oldest_priority_appeals_for_judge_by_docket" do it "returns an empty hash if provided num is zero" do return_value = @new_acd.send :num_oldest_priority_appeals_for_judge_by_docket, @new_acd, 0 + expect(return_value).to eq({}) end it "calls each docket and sorts the return values if num > 0" do diff --git a/spec/models/distribution_spec.rb b/spec/models/distribution_spec.rb index 81b0d6be165..88cd1290275 100644 --- a/spec/models/distribution_spec.rb +++ b/spec/models/distribution_spec.rb @@ -10,8 +10,111 @@ before do Timecop.freeze(Time.zone.now) end - before { FeatureToggle.enable!(:acd_distribute_by_docket_date) } - after { FeatureToggle.disable!(:acd_distribute_by_docket_date) } + + context "validations" do + let(:distribution) { described_class.new(params) } + + it "#validate_user_is_judge" do + non_judge_user = create(:user) + params[:judge] = non_judge_user + + expect(distribution.valid?).to be false + expect(distribution.errors.details).to eq(judge: [{ error: :not_judge }]) + end + + context "#validate_number_of_unassigned_cases" do + it "when distribution is not a priority push" do + create_list(:ama_judge_assign_task, 10, assigned_to: judge) + + expect(distribution.valid?).to be false + expect(distribution.errors.details[:judge].include?(error: :too_many_unassigned_cases)).to be true + end + + it "when distribution is a priority push" do + create_list(:ama_judge_assign_task, 10, assigned_to: judge) + params[:priority_push] = true + + expect(distribution.valid?).to be true + end + end + + context "#validate_days_waiting_on_unassigned_cases" do + it "when distribution is not a priority push" do + create(:ama_judge_assign_task, assigned_at: 35.days.ago, assigned_to: judge) + + expect(distribution.valid?).to be false + expect(distribution.errors.details[:judge].include?(error: :unassigned_cases_waiting_too_long)).to be true + end + + it "when distribution is a priority push" do + create(:ama_judge_assign_task, assigned_at: 35.days.ago, assigned_to: judge) + params[:priority_push] = true + + expect(distribution.valid?).to be true + end + end + + context "#validate_judge_has_no_pending_distributions" do + let(:second_distribution) { described_class.new(params) } + + before { new_distribution } + + context "when judge has a pending priority push distribution" do + let(:priority_push) { true } + + it "judge cannot start another priority push distribution" do + expect(second_distribution.valid?).to be false + expect(second_distribution.errors.details[:judge].include?(error: :pending_distribution)).to be true + end + + it "judge can start a new nonpriority distribution" do + params[:priority_push] = false + expect(second_distribution.valid?).to be true + end + end + + context "when judge has a pending nonpriority distribution" do + it "judge cannot start another nonpriority distribution" do + expect(second_distribution.valid?).to be false + expect(second_distribution.errors.details[:judge].include?(error: :pending_distribution)).to be true + end + + it "judge can start a new priority push distribution" do + params[:priority_push] = true + expect(second_distribution.valid?).to be true + end + end + + context "when distribution with status 'started' exists" do + it "judge cannot start another distribution" do + expect(second_distribution.valid?).to be false + expect(second_distribution.errors.details[:judge].include?(error: :pending_distribution)).to be true + end + end + end + + it "all validations pass" do + create_list(:ama_judge_assign_task, 2, assigned_at: 5.days.ago, assigned_to: judge) + + expect(new_distribution.valid?).to be true + end + end + + context "#batch_size" do + it "is set to alternative batch size if judge has no attorneys" do + expect(new_distribution.send(:batch_size)).to eq(Constants.DISTRIBUTION.alternative_batch_size) + end + + it "is set based on number of attorneys on team" do + judge_team = JudgeTeam.create_for_judge(judge) + 3.times do + team_member = create(:user) + judge_team.add_user(team_member) + end + + expect(new_distribution.send(:batch_size)).to eq(3 * Constants.DISTRIBUTION.batch_size_per_attorney) + end + end context "#distributed_cases_count" do subject { new_distribution } @@ -54,6 +157,14 @@ new_distribution.distribute! end + it "updates status to error if an error is thrown" do + allow_any_instance_of(LegacyDocket).to receive(:distribute_appeals).and_raise(StandardError) + + expect { new_distribution.distribute! }.to raise_error(StandardError) + + expect(new_distribution.status).to eq("error") + end + context "when status is an invalid value" do let(:status) { "invalid!" } @@ -85,6 +196,8 @@ end end + # The following are specifically testing the priority push code in the AutomaticCaseDistribution module + # ByDocketDateDistribution tests are in their own file, by_docket_date_distribution_spec.rb context "priority push distributions" do let(:priority_push) { true } diff --git a/spec/models/distribution_spec_deprecated.rb b/spec/models/distribution_spec_deprecated.rb deleted file mode 100644 index f2f76a8c899..00000000000 --- a/spec/models/distribution_spec_deprecated.rb +++ /dev/null @@ -1,811 +0,0 @@ -# frozen_string_literal: true - -# This is a slow, hard-to-maintain test for Distribution. We are rewriting it in distribution_spec.rb. -# Parts of this test will be removed as the new test achieves parity, until nothing is left here. -# We will lean on this as an end-to-end test until we can eventually rewrite that as well. - -describe Distribution, :all_dbs do - let(:judge) { create(:user, :judge) } - let(:judge_team) { JudgeTeam.for_judge(judge) } - let(:member_count) { 5 } - let(:attorneys) { create_list(:user, member_count) } - let!(:vacols_judge) { create(:staff, :judge_role, sdomainid: judge.css_id) } - let(:today) { Time.utc(2019, 1, 1, 12, 0, 0) } - let(:original_distributed_case_id) { "#{case_id}-redistributed-#{today.strftime('%F')}" } - let(:min_legacy_proportion) { Constants.DISTRIBUTION.minimum_legacy_proportion } - let(:max_direct_review_proportion) { Constants.DISTRIBUTION.maximum_direct_review_proportion } - - before do - FeatureToggle.enable!(:test_facols) - Timecop.freeze(today) - attorneys.each do |u| - judge_team.add_user(u) - end - - # set up a couple of extra judge teams - 2.times do - team = JudgeTeam.create_for_judge(create(:user)) - create_list(:user, 5).each do |attorney| - team.add_user(attorney) - end - end - end - - after do - FeatureToggle.disable!(:test_facols) - end - - context "#distribute!" do - before do - allow_any_instance_of(DirectReviewDocket) - .to receive(:nonpriority_receipts_per_year) - .and_return(100) - - allow(Docket) - .to receive(:nonpriority_decisions_per_year) - .and_return(1000) - end - - def create_legacy_case(index, traits = nil) - create( - :case, - *traits, - bfd19: 1.year.ago, - bfac: "1", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: index.days.ago, - folder: build( - :folder, - tinum: "1801#{format('%03d', index: index)}", - titrnum: "123456789S" - ) - ) - end - - def create_legacy_case_hearing_for(appeal, board_member: judge.vacols_attorney_id) - create(:case_hearing, - :disposition_held, - folder_nr: appeal.bfkey, - hearing_date: 1.month.ago, - board_member: board_member) - end - - context "priority_push is false" do - before do - allow_any_instance_of(HearingRequestDocket) - .to receive(:age_of_n_oldest_genpop_priority_appeals) - .and_return([]) - - allow_any_instance_of(HearingRequestDocket) - .to receive(:distribute_appeals) - .and_return([]) - end - - subject { Distribution.create!(judge: judge) } - - let(:legacy_priority_count) { 14 } - - let!(:legacy_priority_cases) do - (1..legacy_priority_count).map { |i| create_legacy_case(i, :aod) } - end - - let!(:legacy_nonpriority_cases) do - (15..100).map { |i| create_legacy_case(i) } - end - - let!(:same_judge_priority_hearings) do - legacy_priority_cases[0..1].map { |appeal| create_legacy_case_hearing_for(appeal) } - end - - let!(:same_judge_nonpriority_hearings) do - legacy_nonpriority_cases[29..33].map { |appeal| create_legacy_case_hearing_for(appeal) } - end - - let!(:other_judge_hearings) do - legacy_nonpriority_cases[2..27].map { |appeal| create_legacy_case_hearing_for(appeal, board_member: "1234") } - end - - let!(:due_direct_review_cases) do - (0...6).map do - create(:appeal, - :with_post_intake_tasks, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 11.months.ago, - target_decision_date: 1.month.from_now) - end - end - - let!(:priority_direct_review_case) do - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 1.month.ago) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: 1.month.ago) - appeal - end - - let!(:other_direct_review_cases) do - (0...20).map do - create(:appeal, - :with_post_intake_tasks, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 61.days.ago, - target_decision_date: 304.days.from_now) - end - end - - let!(:evidence_submission_cases) do - (0...43).map do - create(:appeal, - :with_post_intake_tasks, - docket_type: Constants.AMA_DOCKETS.evidence_submission) - end - end - - let!(:hearing_cases) do - (0...43).map do - create(:appeal, - :with_post_intake_tasks, - docket_type: Constants.AMA_DOCKETS.hearing) - end - end - - context "when min legacy proportion and max direct review proportion are set and used" do - # Proportion for hearings and evidence submission dockets - let(:other_dockets_proportion) { (1 - min_legacy_proportion - max_direct_review_proportion) / 2 } - - it "correctly distributes cases" do - evidence_submission_cases[0...2].each do |appeal| - appeal.tasks - .find_by(type: EvidenceSubmissionWindowTask.name) - .update!(status: :completed) - end - - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.priority_push).to eq(false) - expect(subject.status).to eq("completed") - expect(subject.started_at).to eq(Time.zone.now) # time is frozen so appears zero time elapsed - expect(subject.errored_at).to be_nil - expect(subject.completed_at).to eq(Time.zone.now) - expect(subject.statistics["batch_size"]).to eq(15) - expect(subject.statistics["total_batch_size"]).to eq(45) - expect(subject.statistics["priority_count"]).to eq(legacy_priority_count + 1) - expect(subject.statistics["legacy_proportion"]).to eq(min_legacy_proportion) - expect(subject.statistics["legacy_hearing_backlog_count"]).to be <= 3 - expect(subject.statistics["direct_review_proportion"]).to eq(max_direct_review_proportion) - expect(subject.statistics["evidence_submission_proportion"]).to be_within(0.01).of(other_dockets_proportion) - expect(subject.statistics["hearing_proportion"]).to be_within(0.01).of(other_dockets_proportion) - expect(subject.statistics["nonpriority_iterations"]).to be_between(1, 3) - expect(subject.distributed_cases.count).to eq(15) - expect(subject.distributed_cases.first.docket).to eq("legacy") - expect(subject.distributed_cases.first.ready_at).to eq(2.days.ago.beginning_of_day) - expect(subject.distributed_cases.where(priority: true).count).to eq(5) - expect(subject.distributed_cases.where(genpop: true).count).to be_within(1).of(7) - expect(subject.distributed_cases.where(priority: true, genpop: false).count).to eq(2) - expect(subject.distributed_cases.where(priority: false, genpop_query: "not_genpop").count).to eq(0) - expect(subject.distributed_cases.where(priority: false, - genpop_query: "any").map(&:docket_index).max).to eq(35) - expect(subject.distributed_cases.where(priority: true, - docket: Constants.AMA_DOCKETS.direct_review).count).to eq(1) - expect(subject.distributed_cases.where(docket: "legacy").count).to be >= 8 - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.direct_review).count) - .to be_within(1).of(1) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.evidence_submission).count) - .to be_within(1).of(0) - end - end - - context "with priority_acd on" do - before { FeatureToggle.enable!(:priority_acd) } - after { FeatureToggle.disable!(:priority_acd) } - - BACKLOG_LIMIT = VACOLS::CaseDocket::HEARING_BACKLOG_LIMIT - - let!(:more_same_judge_nonpriority_hearings) do - to_add = total_tied_nonpriority_hearings - same_judge_nonpriority_hearings.count - legacy_nonpriority_cases[34..(34 + to_add - 1)].map { |appeal| create_legacy_case_hearing_for(appeal) } - end - - context "the judge's backlog has more than #{BACKLOG_LIMIT} legacy hearing non priority cases" do - let(:total_tied_nonpriority_hearings) { BACKLOG_LIMIT + 5 } - - it "distributes legacy hearing non priority cases down to #{BACKLOG_LIMIT}" do - expect(VACOLS::CaseDocket.nonpriority_hearing_cases_for_judge_count(judge)) - .to eq total_tied_nonpriority_hearings - subject.distribute! - expect(subject.valid?).to eq(true) - # This may be less than BACKLOG_LIMIT when minimum_legacy_proportion is very large - expect(subject.statistics["legacy_hearing_backlog_count"]).to be <= BACKLOG_LIMIT - dcs_legacy = subject.distributed_cases.where(docket: "legacy") - - # distributions reliant on BACKLOG_LIMIT - judge_tied_leg_distributions = total_tied_nonpriority_hearings - BACKLOG_LIMIT - expect(dcs_legacy.where(priority: false, genpop_query: "not_genpop").count).to eq( - judge_tied_leg_distributions - ) - - # distributions after handling BACKLOG_LIMIT - expect(dcs_legacy.where(priority: true, genpop_query: "not_genpop").count).to eq(2) - expect(dcs_legacy.where(priority: true, genpop_query: "any").count).to eq(2) - expect(dcs_legacy.count).to be >= 8 - end - end - - context "the judge's backlog has less than #{BACKLOG_LIMIT} legacy hearing non priority cases" do - let(:total_tied_nonpriority_hearings) { BACKLOG_LIMIT - 5 } - - it "distributes legacy hearing non priority cases down to #{BACKLOG_LIMIT}" do - expect(VACOLS::CaseDocket.nonpriority_hearing_cases_for_judge_count(judge)) - .to eq total_tied_nonpriority_hearings - subject.distribute! - - expect(subject.valid?).to eq(true) - dcs_legacy = subject.distributed_cases.where(docket: "legacy") - - # distributions reliant on BACKLOG_LIMIT - expect(dcs_legacy.where(priority: false, genpop_query: "not_genpop").count).to eq(0) - - # distributions after handling BACKLOG_LIMIT - remaining_in_backlog = total_tied_nonpriority_hearings - - dcs_legacy.where(priority: false, genpop: false).count - expect(subject.statistics["legacy_hearing_backlog_count"]).to eq remaining_in_backlog - - expect(dcs_legacy.where(priority: true, genpop_query: "not_genpop").count).to eq(2) - expect(dcs_legacy.where(priority: true, genpop_query: "any").count).to eq(2) - expect(dcs_legacy.count).to be >= 8 - end - end - end - - def create_nonpriority_distributed_case(distribution, case_id, ready_at) - distribution.distributed_cases.create( - case_id: case_id, - priority: false, - docket: "legacy", - ready_at: VacolsHelper.normalize_vacols_datetime(ready_at), - docket_index: "123", - genpop: false, - genpop_query: "any" - ) - end - - def cancel_relevant_legacy_appeal_tasks - legacy_appeal.tasks.reject { |t| t.type == "RootTask" }.each do |task| - # update_columns to avoid triggers that will update VACOLS location dates and - # mess up ACD date logic. - task.update_columns(status: Constants.TASK_STATUSES.cancelled, closed_at: Time.zone.now) - end - end - - context "when a nonpriority distribution of an AMA appeal with an existing distributed case is attempted" do - let!(:target_appeal) { due_direct_review_cases.first } - let(:past_distribution) { Distribution.create(judge: judge) } - let!(:past_distributed_case) do - DistributedCase.create!( - distribution: past_distribution, - ready_at: 6.months.ago, - docket: target_appeal.docket_type, - priority: false, - case_id: target_appeal.uuid, - task: target_appeal.tasks.of_type("DistributionTask").first, - genpop: false, - genpop_query: "not_genpop" - ) - end - let(:current_distribution) do - past_distribution.completed! - Distribution.create!(judge: judge) - end - let!(:second_distribution_task) do - first_distribution_task = target_appeal.tasks.find_by(type: "DistributionTask") - first_distribution_task.completed! - create(:distribution_task, appeal: target_appeal, status: Constants.TASK_STATUSES.assigned) - end - - before do - allow_any_instance_of(Distribution) - .to receive(:batch_size) - .and_return(400) - - second_distribution_task.assigned! - end - - subject { current_distribution.distribute! } - - it "allows cases to be distributed" do - subject - expect(current_distribution.distributed_cases.pluck(:case_id)).to include(target_appeal.uuid) - expect(current_distribution.status).to eq("completed") - end - end - - context "when an illegit nonpriority legacy case re-distribution is attempted" do - let(:case_id) { legacy_case.bfkey } - let!(:previous_location) { legacy_case.bfcurloc } - let(:legacy_case) { legacy_nonpriority_cases.second } - - before do - @raven_called = false - distribution = create(:distribution, judge: judge) - # illegit because appeal has open hearing tasks - appeal = create(:legacy_appeal, :with_schedule_hearing_tasks, vacols_case: legacy_case) - appeal.tasks.open.where.not(type: RootTask.name).each(&:completed!) - create_nonpriority_distributed_case(distribution, case_id, legacy_case.bfdloout) - distribution.update!(status: "completed", completed_at: today) - allow(Raven).to receive(:capture_exception) { @raven_called = true } - allow_any_instance_of(RedistributedCase).to receive(:ok_to_redistribute?).and_return(false) - end - - it "does not create a duplicate distributed_case and sends alert" do - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.error?).to eq(false) - expect(@raven_called).to eq(true) - expect(subject.distributed_cases.pluck(:case_id)).to_not include(case_id) - expect(legacy_case.reload.bfcurloc).to eq(previous_location) - end - end - - context "when a legit nonpriority legacy case re-distribution is attempted" do - let(:case_id) { legacy_case.bfkey } - let(:legacy_case) { legacy_nonpriority_cases.first } - let(:legacy_appeal) { create(:legacy_appeal, :with_schedule_hearing_tasks, vacols_case: legacy_case) } - - before do - @raven_called = false - distribution = create(:distribution, judge: judge) - cancel_relevant_legacy_appeal_tasks - create_nonpriority_distributed_case(distribution, case_id, legacy_case.bfdloout) - distribution.update!(status: "completed", completed_at: today) - allow(Raven).to receive(:capture_exception) { @raven_called = true } - end - - it "renames existing case_id and does not create a duplicate distributed_case" do - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.error?).to eq(false) - expect(@raven_called).to eq(false) - expect(subject.distributed_cases.pluck(:case_id)).to include(case_id) - expect(DistributedCase.find_by(case_id: case_id)).to_not be_nil - expect(DistributedCase.find_by(case_id: original_distributed_case_id)).to_not be_nil - expect(legacy_case.reload.bfcurloc).to eq(judge.vacols_uniq_id) - end - end - - def create_priority_distributed_case(distribution, case_id, ready_at) - distribution.distributed_cases.create( - case_id: case_id, - priority: true, - docket: "legacy", - ready_at: VacolsHelper.normalize_vacols_datetime(ready_at), - docket_index: "123", - genpop: false, - genpop_query: "any" - ) - end - - context "when an illegit priority legacy case re-distribution is attempted" do - let(:case_id) { legacy_case.bfkey } - let(:legacy_case) { legacy_priority_cases.last } - - before do - @raven_called = false - distribution = create(:distribution, judge: judge) - # illegit because appeal has open tasks - create(:legacy_appeal, :with_schedule_hearing_tasks, vacols_case: legacy_case) - create_priority_distributed_case(distribution, case_id, legacy_case.bfdloout) - distribution.update!(status: "completed", completed_at: today) - allow(Raven).to receive(:capture_exception) { @raven_called = true } - end - - it "does not create a duplicate distributed_case and sends alert" do - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.error?).to eq(false) - expect(@raven_called).to eq(false) - expect(subject.distributed_cases.pluck(:case_id)).to_not include(case_id) - expect(legacy_case.reload.bfcurloc).to eq(LegacyAppeal::LOCATION_CODES[:caseflow]) - end - end - - context "when a legit priority legacy case re-distribution is attempted" do - let(:case_id) { legacy_case.bfkey } - let(:legacy_case) { legacy_priority_cases.last } - let(:legacy_appeal) { create(:legacy_appeal, :with_schedule_hearing_tasks, vacols_case: legacy_case) } - - before do - @raven_called = false - distribution = create(:distribution, judge: judge) - cancel_relevant_legacy_appeal_tasks - create_priority_distributed_case(distribution, case_id, legacy_case.bfdloout) - distribution.update!(status: "completed", completed_at: today) - allow(Raven).to receive(:capture_exception) { @raven_called = true } - end - - it "renames existing case_id and does not create a duplicate distributed_case" do - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.error?).to eq(false) - expect(@raven_called).to eq(false) - expect(subject.distributed_cases.pluck(:case_id)).to include(case_id) - expect(DistributedCase.find_by(case_id: case_id)).to_not be_nil - expect(DistributedCase.find_by(case_id: original_distributed_case_id)).to_not be_nil - expect(legacy_case.reload.bfcurloc).to eq(judge.vacols_uniq_id) - end - end - - context "when the job errors" do - it "marks the distribution as error" do - allow_any_instance_of(LegacyDocket).to receive(:distribute_nonpriority_appeals).and_raise(StandardError) - expect { subject.distribute! }.to raise_error(StandardError) - expect(subject.status).to eq("error") - expect(subject.distributed_cases.count).to eq(0) - expect(subject.errored_at).to eq(Time.zone.now) - end - end - - context "when the judge has an empty team" do - let(:judge_wo_attorneys) { create(:user, :judge) } - let!(:vacols_judge_wo_attorneys) { create(:staff, :judge_role, sdomainid: judge_wo_attorneys.css_id) } - - subject { Distribution.create(judge: judge_wo_attorneys) } - - it "uses the alternative batch size" do - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.status).to eq("completed") - expect(subject.statistics["batch_size"]).to eq(15) - expect(subject.distributed_cases.count).to eq(15) - end - end - - context "when there are zero legacy cases eligible" do - let!(:legacy_priority_cases) { [] } - let!(:legacy_nonpriority_cases) { [] } - let!(:same_judge_nonpriority_hearings) { [] } - let!(:other_judge_hearings) { [] } - # Proportion for hearings and evidence submission dockets - let(:other_dockets_proportion) { (1 - max_direct_review_proportion) / 2 } - - it "fills the AMA dockets" do - evidence_submission_cases[0...2].each do |appeal| - appeal.tasks - .find_by(type: EvidenceSubmissionWindowTask.name) - .update!(status: :completed) - end - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.status).to eq("completed") - expect(subject.statistics["batch_size"]).to eq(15) - expect(subject.statistics["total_batch_size"]).to eq(45) - expect(subject.statistics["priority_count"]).to eq(1) - expect(subject.statistics["legacy_proportion"]).to eq(0.0) - expect(subject.statistics["direct_review_proportion"]).to eq(max_direct_review_proportion) - expect(subject.statistics["evidence_submission_proportion"]).to be_within(0.01).of(other_dockets_proportion) - expect(subject.statistics["hearing_proportion"]).to be_within(0.01).of(other_dockets_proportion) - expect(subject.statistics["nonpriority_iterations"]).to be_between(2, 3) - expect(subject.distributed_cases.count).to eq(15) - end - end - - describe "JudgeTeam ama_only_request toggle" do - context "when the toggle is not set for a JudgeTeam (default case)" do - it "includes untied legacy cases" do - subject.distribute! - - untied_legacy_docket_cases = subject.distributed_cases.filter do |dc| - dc.docket == "legacy" && dc.genpop_query != "not_genpop" - end - - expect(untied_legacy_docket_cases).to_not be_empty - end - end - - context "when a JudgeTeam is AMA-only for requested distributions" do - before do - judge_team.update!(ama_only_request: true) - subject.distribute! - end - - it "does distribute tied legacy cases" do - tied_legacy_docket_cases = subject.distributed_cases.filter do |dc| - dc.docket == "legacy" && dc.genpop_query == "not_genpop" - end - - expect(tied_legacy_docket_cases.count).to eq(2) - end - - it "does not distribute any untied legacy cases" do - untied_legacy_docket_cases = subject.distributed_cases.filter do |dc| - dc.docket == "legacy" && dc.genpop_query != "not_genpop" - end - - expect(untied_legacy_docket_cases.count).to eq(0) - end - end - end - end - - context "priority_push is true" do - subject { Distribution.create!(judge: judge, priority_push: true) } - - let!(:legacy_priority_cases) do - (1..4).map do |i| - create( - :case, - :aod, - bfd19: 1.year.ago, - bfac: "1", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: i.months.ago, - folder: build( - :folder, - tinum: "1801#{format('%03d', index: i)}", - titrnum: "123456789S" - ) - ) - end - end - - let!(:legacy_nonpriority_cases) do - (5..8).map do |i| - create( - :case, - bfd19: 1.year.ago, - bfac: "1", - bfmpro: "ACT", - bfcurloc: "81", - bfdloout: i.months.ago, - folder: build( - :folder, - tinum: "1801#{format('%03d', index: i)}", - titrnum: "123456789S" - ) - ) - end - end - - let!(:priority_legacy_hearings_not_tied_to_judge) do - legacy_priority_cases[0..1].map do |appeal| - create(:case_hearing, - :disposition_held, - folder_nr: appeal.bfkey, - hearing_date: 1.month.ago) - end - end - - let!(:priority_legacy_hearings_tied_to_judge) do - legacy_priority_cases[2..3].map do |appeal| - create(:case_hearing, - :disposition_held, - folder_nr: appeal.bfkey, - hearing_date: 1.month.ago, - board_member: judge.vacols_attorney_id) - end - end - - let!(:priority_ama_hearings_tied_to_judge) do - (1...5).map do - appeal = create(:appeal, - :ready_for_distribution, - :advanced_on_docket_due_to_motion, - docket_type: Constants.AMA_DOCKETS.hearing) - most_recent = create(:hearing_day, scheduled_for: 1.day.ago) - hearing = create(:hearing, judge: nil, disposition: "held", appeal: appeal, hearing_day: most_recent) - hearing.update(judge: judge) - appeal - end - end - - let!(:priority_ama_hearings_not_tied_to_judge) do - (1...3).map do |i| - appeal = create(:appeal, - :advanced_on_docket_due_to_age, - :ready_for_distribution, - docket_type: Constants.AMA_DOCKETS.hearing) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.months.ago) - appeal.reload - end - end - - let!(:priority_direct_review_cases) do - (1...3).map do |i| - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.direct_review, - receipt_date: 1.month.ago) - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.month.ago) - appeal - end - end - - let!(:evidence_submission_cases) do - (1...3).map do |i| - appeal = create(:appeal, - :with_post_intake_tasks, - :advanced_on_docket_due_to_age, - docket_type: Constants.AMA_DOCKETS.evidence_submission) - appeal.tasks.find_by(type: EvidenceSubmissionWindowTask.name).completed! - appeal.tasks.find_by(type: DistributionTask.name).update(assigned_at: i.month.ago) - appeal - end - end - - context "when there is no limit specified" do - it "distributes all priority cases associated with the judge" do - distributed_case_ids = priority_legacy_hearings_tied_to_judge.map(&:folder_nr) - .concat(priority_ama_hearings_tied_to_judge.map(&:uuid)) - subject.distribute! - expect(subject.valid?).to eq(true) - expect(subject.priority_push).to eq(true) - expect(subject.status).to eq("completed") - expect(subject.started_at).to eq(Time.zone.now) # time is frozen so appears zero time elapsed - expect(subject.errored_at).to be_nil - expect(subject.completed_at).to eq(Time.zone.now) - expect(subject.statistics["batch_size"]).to eq(distributed_case_ids.count) - expect(subject.distributed_cases.count).to eq(distributed_case_ids.count) - expect(subject.distributed_cases.where(priority: true).count).to eq(distributed_case_ids.count) - expect(subject.distributed_cases.where(priority: false).count).to eq(0) - expect(subject.distributed_cases.where(genpop_query: "not_genpop").count).to eq(distributed_case_ids.count) - expect(subject.distributed_cases.where(genpop: true).count).to eq(0) - expect(subject.distributed_cases.where(docket: "legacy").count).to eq( - priority_legacy_hearings_tied_to_judge.count - ) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.hearing).count).to eq( - priority_ama_hearings_tied_to_judge.count - ) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.direct_review).count).to eq 0 - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.evidence_submission).count).to eq 0 - expect(subject.distributed_cases.pluck(:case_id)).to match_array distributed_case_ids - end - end - - context "when a limit is specified" do - let(:limit) { 4 } - - it "distributes that number of priority cases from all dockets, based on docket age" do - oldest_case_from_each_docket = [ - legacy_priority_cases.last.bfkey, - priority_ama_hearings_not_tied_to_judge.last.uuid, - priority_direct_review_cases.last.uuid, - evidence_submission_cases.last.uuid - ] - - subject.distribute!(limit) - expect(subject.valid?).to eq(true) - expect(subject.priority_push).to eq(true) - expect(subject.status).to eq("completed") - expect(subject.started_at).to eq(Time.zone.now) # time is frozen so appears zero time elapsed - expect(subject.errored_at).to be_nil - expect(subject.completed_at).to eq(Time.zone.now) - expect(subject.statistics["batch_size"]).to eq(limit) - expect(subject.distributed_cases.count).to eq(limit) - expect(subject.distributed_cases.where(priority: true).count).to eq(limit) - expect(subject.distributed_cases.where(priority: false).count).to eq(0) - expect(subject.distributed_cases.where(genpop_query: "not_genpop").count).to eq(0) - expect(subject.distributed_cases.where(docket: "legacy").count).to eq(1) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.hearing).count).to eq(1) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.direct_review).count).to eq(1) - expect(subject.distributed_cases.where(docket: Constants.AMA_DOCKETS.evidence_submission).count).to eq(1) - expect(subject.distributed_cases.pluck(:case_id)).to match_array oldest_case_from_each_docket - end - end - end - end - - context "validations" do - shared_examples "passes validations" do - it "is valid" do - expect(subject.valid?).to be true - end - end - - subject { Distribution.create(judge: user, priority_push: priority_push) } - - let(:user) { judge } - let(:priority_push) { false } - - context "existing Distribution record with status pending" do - let!(:existing_distribution) { create(:distribution, judge: judge) } - - it "prevents new Distribution record" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :pending_distribution) - end - - context "when the priority_push is not the same" do - let!(:existing_distribution) { create(:distribution, judge: judge, priority_push: true) } - - it_behaves_like "passes validations" - end - end - - context "existing Distribution record with status started" do - let!(:existing_distribution) { create(:distribution, judge: judge, status: :started) } - - it "prevents new Distribution record" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :pending_distribution) - end - - context "when the priority_push is not the same" do - let!(:existing_distribution) { create(:distribution, judge: judge, status: :started, priority_push: true) } - - it_behaves_like "passes validations" - end - end - - context "when the user is not a judge in VACOLS" do - let(:user) { create(:user) } - - it "is invalid" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :not_judge) - end - end - - context "when the judge has 8 or fewer unassigned appeals" do - before do - 5.times { create(:case, bfcurloc: vacols_judge.slogid, bfdloout: Time.zone.today) } - 3.times { create(:ama_judge_assign_task, assigned_to: judge, assigned_at: Time.zone.today) } - end - - it "is valid" do - expect(subject.errors.details).not_to have_key(:judge) - end - end - - context "when the judge has 8 or more unassigned appeals" do - before do - 5.times { create(:case, bfcurloc: vacols_judge.slogid, bfdloout: Time.zone.today) } - 4.times { create(:ama_judge_assign_task, assigned_to: judge, assigned_at: Time.zone.today) } - end - - it "is invalid" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :too_many_unassigned_cases) - end - - context "when priority_push is true" do - let(:priority_push) { true } - - it_behaves_like "passes validations" - end - end - - context "when the judge has an appeal that has waited more than 30 days" do - let!(:task) { create(:ama_judge_assign_task, assigned_to: judge, assigned_at: 31.days.ago) } - - it "is invalid" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :unassigned_cases_waiting_too_long) - end - - context "when priority_push is true" do - let(:priority_push) { true } - - it_behaves_like "passes validations" - end - end - - context "when the judge has a legacy appeal that has waited more than 30 days" do - let!(:task) { create(:case, bfcurloc: vacols_judge.slogid, bfdloout: 31.days.ago) } - - it "is invalid" do - expect(subject.errors.details).to have_key(:judge) - expect(subject.errors.details[:judge]).to include(error: :unassigned_cases_waiting_too_long) - end - - context "when priority_push is true" do - let(:priority_push) { true } - - it_behaves_like "passes validations" - end - end - end -end