Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Part 2] Populate aod_based_on_age column #14764

1 change: 1 addition & 0 deletions app/controllers/api/v1/jobs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Api::V1::JobsController < Api::ApplicationController
"prepare_establish_claim" => PrepareEstablishClaimTasksJob,
"reassign_old_tasks" => ReassignOldTasksJob,
"retrieve_documents_for_reader" => RetrieveDocumentsForReaderJob,
"set_appeal_age_aod" => SetAppealAgeAodJob,
"stats_collector" => StatsCollectorJob,
"sync_intake" => SyncIntakeJob,
"sync_reviews" => SyncReviewsJob,
Expand Down
66 changes: 66 additions & 0 deletions app/jobs/set_appeal_age_aod_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

# Early morning job that checks if the claimant meets the Advance-On-Docket age criteria.
# If criteria is satisfied, all active appeals associated with claimant will be marked as AOD.
# This job also handles the scenario where a claimant's DOB is updated such that their appeal(s) are no longer AOD.
class SetAppealAgeAodJob < CaseflowJob
include ActionView::Helpers::DateHelper

def perform
RequestStore.store[:current_user] = User.system_user

aod_appeals_to_unset = appeals_to_unset_age_based_aod
detail_msg = "IDs of appeals to remove age-related AOD: #{aod_appeals_to_unset.pluck(:id)}"
aod_appeals_to_unset.update_all(aod_based_on_age: false, updated_at: Time.now.utc)

# We expect there to be only one claimant on an appeal. Any claimant meeting the age criteria will cause AOD.
appeals_for_aod = appeals_to_set_age_based_aod
detail_msg += "\nIDs of appeals to be updated with age-related AOD: #{appeals_for_aod.pluck(:id)}"
appeals_for_aod.update_all(aod_based_on_age: true, updated_at: Time.now.utc)

log_success(detail_msg)
rescue StandardError => error
log_error(self.class.name, error, detail_msg)
end

protected

def log_success(details)
duration = time_ago_in_words(start_time)
msg = "#{self.class.name} completed after running for #{duration}.\n#{details}"
Rails.logger.info(msg)

slack_service.send_notification("[INFO] #{msg}")
end

def log_error(collector_name, err, details)
duration = time_ago_in_words(start_time)
msg = "#{collector_name} failed after running for #{duration}. Fatal error: #{err.message}.\n#{details}"
Rails.logger.info(msg)
Rails.logger.info(err.backtrace.join("\n"))

Raven.capture_exception(err, extra: { stats_collector_name: collector_name })

slack_service.send_notification("[ERROR] #{msg}")
end

private

def appeals_to_unset_age_based_aod
active_appeals_with_age_based_aod.joins(claimants: :person).where("people.date_of_birth > ?", 75.years.ago)
end

def active_appeals_with_age_based_aod
Appeal.active.where(aod_based_on_age: true)
end

def appeals_to_set_age_based_aod
active_appeals_without_age_based_aod.joins(claimants: :person).where("people.date_of_birth <= ?", 75.years.ago)
end

def active_appeals_without_age_based_aod
# `aod_based_on_age` is initially nil
# `aod_based_on_age` being false means that it was once true (in the case where the claimant's DOB was updated)
Appeal.active.where(aod_based_on_age: [nil, false])
end
end
12 changes: 11 additions & 1 deletion app/models/appeal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Appeal < DecisionReview
"de_novo": "de_novo"
}

after_create :conditionally_set_aod_based_on_age

after_save :set_original_stream_data

with_options on: :intake_review do
Expand Down Expand Up @@ -267,8 +269,16 @@ def regional_office_key
nil
end

def conditionally_set_aod_based_on_age
updated_aod_based_on_age = claimant&.advanced_on_docket_based_on_age?
update(aod_based_on_age: updated_aod_based_on_age) if aod_based_on_age != updated_aod_based_on_age
end

def advanced_on_docket?
claimant&.advanced_on_docket?(receipt_date)
conditionally_set_aod_based_on_age
# One of the AOD motion reasons is 'age'. Keep interrogation of any motions separate from `aod_based_on_age`,
# which reflects `claimant.advanced_on_docket_based_on_age?`.
aod_based_on_age || claimant&.advanced_on_docket_motion_granted?(receipt_date)
end

# Prefer aod? over aod going forward, as this function returns a boolean
Expand Down
12 changes: 12 additions & 0 deletions app/models/attorney_claimant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
class AttorneyClaimant < Claimant
delegate :name, to: :bgs_attorney

def advanced_on_docket?(_appeal_receipt_date)
false
end

def advanced_on_docket_based_on_age?
false
end

def advanced_on_docket_motion_granted?(_appeal_receipt_date)
false
end

private

def find_power_of_attorney
Expand Down
3 changes: 3 additions & 0 deletions app/models/claimant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

##
# The Claimant model associates a claimant to a decision review.
# There are several subclasses, such as VeteranClaimant, DependentClaimant, and AttorneyClaimant.
hschallhorn marked this conversation as resolved.
Show resolved Hide resolved

class Claimant < CaseflowRecord
include HasDecisionReviewUpdatedSince
Expand Down Expand Up @@ -44,6 +45,8 @@ def person

delegate :date_of_birth,
:advanced_on_docket?,
:advanced_on_docket_based_on_age?,
:advanced_on_docket_motion_granted?,
hschallhorn marked this conversation as resolved.
Show resolved Hide resolved
:name,
:first_name,
:last_name,
Expand Down
6 changes: 5 additions & 1 deletion app/models/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def find_or_create_by_ssn(ssn)
end

def advanced_on_docket?(appeal_receipt_date)
advanced_on_docket_based_on_age? || AdvanceOnDocketMotion.granted_for_person?(id, appeal_receipt_date)
advanced_on_docket_based_on_age? || advanced_on_docket_motion_granted?(appeal_receipt_date)
end

def date_of_birth
Expand Down Expand Up @@ -105,6 +105,10 @@ def advanced_on_docket_based_on_age?
date_of_birth && date_of_birth < 75.years.ago
end

def advanced_on_docket_motion_granted?(appeal_receipt_date)
AdvanceOnDocketMotion.granted_for_person?(id, appeal_receipt_date)
end
hschallhorn marked this conversation as resolved.
Show resolved Hide resolved

def found?
return false if not_found?

Expand Down
70 changes: 70 additions & 0 deletions spec/jobs/set_appeal_age_aod_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

describe SetAppealAgeAodJob, :postgres do
include_context "Metrics Reports"

# rubocop:disable Metrics/LineLength
let(:success_msg) do
"[INFO] SetAppealAgeAodJob completed after running for less than a minute."
end
# rubocop:enable Metrics/LineLength

describe "#perform" do
let(:non_aod_appeal) { create(:appeal, :with_schedule_hearing_tasks) }

# appeal with wrong date-of-birth causing AOD; it is fixed in the before block
let(:age_aod_appeal_wrong_dob) { create(:appeal, :with_schedule_hearing_tasks, :advanced_on_docket_due_to_age) }

let(:age_aod_appeal) { create(:appeal, :with_schedule_hearing_tasks, :advanced_on_docket_due_to_age) }
let(:motion_aod_appeal) { create(:appeal, :with_schedule_hearing_tasks, :advanced_on_docket_due_to_motion) }

let(:inactive_age_aod_appeal) { create(:appeal, :advanced_on_docket_due_to_age) }
let(:cancelled_age_aod_appeal) { create(:appeal, :advanced_on_docket_due_to_age, :cancelled) }

before do
allow_any_instance_of(SlackService).to receive(:send_notification) { |_, first_arg| @slack_msg = first_arg }

age_aod_appeal_wrong_dob.update(aod_based_on_age: true)
# simulate date-of-birth being corrected
age_aod_appeal_wrong_dob.claimant.person.update(date_of_birth: 50.years.ago)
end

it "sets aod_based_on_age for only active appeals with a claimant that satisfies the age criteria" do
expect(non_aod_appeal.active?).to eq(true)
expect(age_aod_appeal.active?).to eq(true)
expect(motion_aod_appeal.active?).to eq(true)
expect(inactive_age_aod_appeal.active?).to eq(false)
expect(cancelled_age_aod_appeal.active?).to eq(false)

expect(age_aod_appeal_wrong_dob.aod_based_on_age).to eq(true)

described_class.perform_now
expect(@slack_msg).to include(success_msg)

# `aod_based_on_age` will be nil
# `aod_based_on_age` being false means that it was once true (in the case where the claimant's DOB was updated)
expect(non_aod_appeal.reload.aod_based_on_age).not_to eq(true)
expect(inactive_age_aod_appeal.reload.aod_based_on_age).not_to eq(true)

expect(age_aod_appeal.reload.aod_based_on_age).to eq(true)
expect(motion_aod_appeal.reload.aod_based_on_age).to eq(false)

expect(age_aod_appeal_wrong_dob.reload.aod_based_on_age).to eq(false)
yoomlam marked this conversation as resolved.
Show resolved Hide resolved
end

context "when the entire job fails" do
let(:error_msg) { "Some dummy error" }

it "sends a message to Slack that includes the error" do
slack_msg = ""
allow_any_instance_of(SlackService).to receive(:send_notification) { |_, first_arg| slack_msg = first_arg }

allow_any_instance_of(described_class).to receive(:appeals_to_set_age_based_aod).and_raise(error_msg)
described_class.perform_now

expected_msg = "#{described_class.name} failed after running for .*. Fatal error: #{error_msg}"
expect(slack_msg).to match(/#{expected_msg}/)
end
end
end
end
23 changes: 15 additions & 8 deletions spec/models/appeal_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -455,23 +455,30 @@
end

context "#advanced_on_docket?" do
context "when a claimant is advanced_on_docket?" do
let(:appeal) do
create(:appeal, claimants: [create(:claimant, :advanced_on_docket_due_to_age)])
end
context "when a claimant is advanced_on_docket? due to age" do
let(:appeal) { create(:appeal, claimants: [create(:claimant, :advanced_on_docket_due_to_age)]) }

it "returns true" do
expect(appeal.advanced_on_docket?).to eq(true)
expect(appeal.aod_based_on_age).to eq(true)
end
end

context "when no claimant is advanced_on_docket?" do
let(:appeal) do
create(:appeal)
end
context "when no claimant is advanced_on_docket? due to age" do
let(:appeal) { create(:appeal) }

it "returns false" do
expect(appeal.advanced_on_docket?).to eq(false)
expect(appeal.aod_based_on_age).to eq(false)
end
end

context "when a claimant is advanced_on_docket? due to motion" do
let(:appeal) { create(:appeal, :advanced_on_docket_due_to_motion) }

it "returns true" do
expect(appeal.advanced_on_docket?).to eq(true)
expect(appeal.aod_based_on_age).to eq(false)
end
end
end
Expand Down
64 changes: 59 additions & 5 deletions spec/models/claimant_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,64 @@
end

context "#advanced_on_docket?" do
context "when claimant is over 75 years old" do
context "when claimant satisfies AOD age criteria" do
let(:claimant) { create(:claimant, :advanced_on_docket_due_to_age) }

it "returns true" do
claimant = create(:claimant, :advanced_on_docket_due_to_age)
expect(claimant.advanced_on_docket?(1.year.ago)).to eq(true)
expect(claimant.advanced_on_docket_based_on_age?).to eq(true)
end
end

context "when claimant has motion granted" do
it "returns true" do
claimant = create(:claimant)
let(:claimant) { create(:claimant) }

before do
create(:advance_on_docket_motion, person_id: claimant.person.id, granted: true)
end

it "returns true" do
expect(claimant.advanced_on_docket?(1.year.ago)).to eq(true)
expect(claimant.advanced_on_docket_based_on_age?).to eq(false)
expect(claimant.advanced_on_docket_motion_granted?(1.year.ago)).to eq(true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a quick expect(claimant.advanced_on_docket_based_on_age?).to eq(false) check here?

end
end

context "when claimant is younger than 75 years old and has no motion granted" do
let(:claimant) { create(:claimant) }

it "returns false" do
expect(claimant.advanced_on_docket?(1.year.ago)).to eq(false)
expect(claimant.advanced_on_docket_based_on_age?).to eq(false)
expect(claimant.advanced_on_docket_motion_granted?(1.year.ago)).to eq(false)
end
end

context "when claimant satisfies AOD age criteria and has motion granted" do
let(:claimant) { create(:claimant, :advanced_on_docket_due_to_age) }

before do
create(:advance_on_docket_motion, person_id: claimant.person.id, granted: true)
end

it "returns true" do
expect(claimant.advanced_on_docket?(1.year.ago)).to eq(true)
expect(claimant.advanced_on_docket_based_on_age?).to eq(true)
expect(claimant.advanced_on_docket_motion_granted?(1.year.ago)).to eq(true)
end
end

context "when AttorneyClaimant satisfies AOD age criteria and has motion granted" do
let(:claimant) { create(:claimant, :advanced_on_docket_due_to_age, type: "AttorneyClaimant") }

before do
create(:advance_on_docket_motion, person_id: claimant.person.id, granted: true)
end

it "returns false" do
claimant = create(:claimant)
expect(claimant.advanced_on_docket?(1.year.ago)).to eq(false)
expect(claimant.advanced_on_docket_based_on_age?).to eq(false)
expect(claimant.advanced_on_docket_motion_granted?(1.year.ago)).to eq(false)
end
end
end
Expand Down Expand Up @@ -165,6 +203,22 @@
expect(bgs_service).to have_received(:fetch_poas_by_participant_ids).once
end
end

context "when claimant is AttorneyClaimant" do
let(:claimant) { create(:claimant, :advanced_on_docket_due_to_age, type: "AttorneyClaimant") }

before do
create(:bgs_attorney, participant_id: claimant.participant_id, name: "JOHN SMITH")
end

it "returns name of AttorneyClaimant" do
expect(claimant.name).to eq "JOHN SMITH"
end

it "returns BgsPowerOfAttorney" do
expect(subject).to be_a BgsPowerOfAttorney
end
end
end

context "#valid?" do
Expand Down