Skip to content

Commit cf2ceee

Browse files
committed
Fix NSC
1 parent b4064b6 commit cf2ceee

20 files changed

Lines changed: 388 additions & 253 deletions

app/app/controllers/launcher_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ def render_launcher_error(message)
377377
"lynette" => { first_name: "Lynette", last_name: "Oyola", date_of_birth: "1988-10-24" },
378378
"rick" => { first_name: "Rick", last_name: "Banas", date_of_birth: "1979-08-18" },
379379
"dominique" => { first_name: "Dominique", last_name: "Ricardo", date_of_birth: "1978-01-12" },
380+
"scott" => { first_name: "Scott", last_name: "Tobin", date_of_birth: "1998-02-03" },
380381
"linda" => { first_name: "Linda", last_name: "Cooper", date_of_birth: "1999-01-01" }
381382
}.freeze
382383

app/app/jobs/application_job.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
class ApplicationJob < ActiveJob::Base
22
class SilencedError < StandardError; end
33

4+
NON_RETRYABLE_ERRORS = [
5+
NameError,
6+
TypeError,
7+
ArgumentError,
8+
ActiveRecord::RecordInvalid
9+
].freeze
10+
411
around_perform :with_error_reporting
512

613
retry_on Exception, wait: :polynomially_longer, attempts: 5
14+
retry_on(*NON_RETRYABLE_ERRORS, attempts: 1)
715

816
class_attribute :max_attempts, default: 5
917

app/app/services/launcher/nsc_forward_dating_service.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
class Launcher::NscForwardDatingService
2-
LAUNCHER_SCENARIO_KEYS = %w[lynette rick dominique linda].freeze
2+
LAUNCHER_SCENARIO_KEYS = %w[lynette rick dominique scott linda].freeze
33
# These dates match each persona's latest term end so NSC reports them as currently enrolled before forward-dating.
44
LAUNCHER_AS_OF_DATES = {
55
"lynette" => Date.new(2024, 11, 19),
6-
"rick" => Date.new(2024, 11, 29)
6+
"rick" => Date.new(2024, 11, 29),
7+
"dominique" => Date.new(2024, 5, 9)
78
}.freeze
89

910
def self.applicable?(education_activity)
@@ -37,13 +38,10 @@ def launcher_as_of_date
3738
end
3839

3940
def forward_dated_response(response)
40-
latest_term_end = Array(response["enrollmentDetails"])
41-
.flat_map { |detail| Array(detail["enrollmentData"]) }
42-
.filter_map { |term| term["termEndDate"].presence && Date.parse(term["termEndDate"]) }
43-
.max
44-
return response unless latest_term_end
41+
anchor_term_end = anchor_term_end_for(response)
42+
return response unless anchor_term_end
4543

46-
delta_days = (@education_activity.activity_flow.reporting_window_range.max - latest_term_end).to_i
44+
delta_days = (@education_activity.activity_flow.reporting_window_range.max - anchor_term_end).to_i
4745
transformed_response = response.deep_dup
4846

4947
Array(transformed_response["enrollmentDetails"]).each do |detail|
@@ -56,6 +54,18 @@ def forward_dated_response(response)
5654
transformed_response
5755
end
5856

57+
def anchor_term_end_for(response)
58+
term_ends = Array(response["enrollmentDetails"])
59+
.flat_map { |detail| Array(detail["enrollmentData"]) }
60+
.filter_map { |term| term["termEndDate"].presence && Date.parse(term["termEndDate"]) }
61+
return if term_ends.empty?
62+
63+
as_of_date = launcher_as_of_date
64+
return term_ends.max if as_of_date.blank?
65+
66+
term_ends.select { |term_end| term_end <= as_of_date }.max || term_ends.max
67+
end
68+
5969
def shift_date_string(date_str, delta_days)
6070
return date_str if date_str.blank?
6171

app/app/services/nsc_data_fetcher_service.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,23 @@ def update_education_activity(education_activity, response_data)
6666
def save_enrollment_terms(enrollment_details)
6767
activity_flow = @education_activity.activity_flow
6868

69+
identity = activity_flow.identity
70+
6971
enrollment_details.each do |enrollment_detail|
7072
next unless enrollment_detail["currentEnrollmentStatus"] == CURRENTLY_ENROLLED
7173

74+
name_on_school_record = enrollment_detail["nameOnSchoolRecord"] || {}
75+
7276
enrollment_detail["enrollmentData"].each do |enrollment_data|
7377
term_begin = Date.parse(enrollment_data["termBeginDate"])
7478
term_end = Date.parse(enrollment_data["termEndDate"])
7579
next unless activity_flow.within_reporting_window?(term_begin, term_end)
7680

7781
@education_activity.nsc_enrollment_terms.create!(
7882
school_name: enrollment_detail["officialSchoolName"],
79-
first_name: enrollment_detail["nameOnSchoolRecord"]["firstName"],
80-
middle_name: enrollment_detail["nameOnSchoolRecord"]["middleName"],
81-
last_name: enrollment_detail["nameOnSchoolRecord"]["lastName"],
83+
first_name: name_on_school_record["firstName"] || identity&.first_name,
84+
middle_name: name_on_school_record["middleName"],
85+
last_name: name_on_school_record["lastName"] || identity&.last_name,
8286
enrollment_status: enrollment_status(enrollment_data),
8387
term_begin: term_begin,
8488
term_end: term_end,

app/app/views/launcher/advanced.html.erb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,16 @@
105105
data-reporting-window-months="2">
106106
<label class="usa-radio__label" for="test_scenario_dominique">
107107
<strong>Dominique Ricardo</strong>
108-
<span class="advanced-launcher__scenario-desc">Not currently enrolled (enrollments outside window)</span>
108+
<span class="advanced-launcher__scenario-desc">Enrolled full time (1 school)</span>
109+
</label>
110+
</div>
111+
<div class="usa-radio">
112+
<input class="usa-radio__input usa-radio__input--tile" type="radio"
113+
name="test_scenario" id="test_scenario_scott" value="scott"
114+
data-action="advanced-launcher#selectScenario">
115+
<label class="usa-radio__label" for="test_scenario_scott">
116+
<strong>Scott Tobin</strong>
117+
<span class="advanced-launcher__scenario-desc">Not currently enrolled (enrollment older than 18 months)</span>
109118
</label>
110119
</div>
111120
<div class="usa-radio">

app/app/views/launcher/launcher.html.erb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
data-launcher-flow-value="activity"
55
data-launcher-window-value="application"
66
data-launcher-months-value="2"
7-
data-launcher-status-value="lynette">
7+
data-launcher-status-value="dominique">
88
<%= form_with(url: "/launcher", method: :post, html: { class: "launcher__main", autocomplete: "off" }, data: { turbo: false, launcher_target: "form" }) do |f| %>
99
<div class="launcher__title">
1010
<h1>Emmy launcher</h1>
@@ -17,7 +17,7 @@
1717
<%= hidden_field_tag :flow_type, "activity", data: { launcher_target: "flowInput" } %>
1818
<%= hidden_field_tag :reporting_window, "application", data: { launcher_target: "windowInput" } %>
1919
<%= hidden_field_tag :reporting_window_months, "2", data: { launcher_target: "monthsInput" } %>
20-
<%= hidden_field_tag :test_scenario, "lynette", data: { launcher_target: "statusInput" } %>
20+
<%= hidden_field_tag :test_scenario, "dominique", data: { launcher_target: "statusInput" } %>
2121

2222
<div class="launcher__step">
2323
<div class="launcher__step-header">
@@ -162,15 +162,16 @@
162162
</div>
163163
<div class="launcher__radio-col">
164164
<% [
165-
[ "lynette", "Enrolled full time", "Full-time enrollment verified automatically via the National Student Clearinghouse." ],
166-
[ "renewal_half_time_last_4_of_6_avery", "Enrolled half-time", "Half-time enrollment covering 4 of 6 months (good for renewal required-month testing)." ],
167-
[ "partial_enrollment_maya", "Enrolled less-than-half-time", "Less-than-half-time enrollment (sends user through partially self-attested flow)." ],
168-
[ "linda", "No NSC enrollment found", "Enrollment unable to be verified via NSC (sends user through self-attestation flow)." ]
165+
[ "dominique", "Enrolled full time", "Full-time enrollment verified automatically via the National Student Clearinghouse." ],
166+
[ "renewal_half_time_last_4_of_6_avery", "Enrolled half-time", "Half-time enrollment covering 4 of 6 months (good for renewal required-month testing)." ],
167+
[ "partial_enrollment_maya", "Enrolled less-than-half-time", "Less-than-half-time enrollment (sends user through partially self-attested flow)." ],
168+
[ "lynette", "Enrolled, intensity not reported", "NSC confirms enrollment but reports no intensity (asks the user for credit hours)." ],
169+
[ "linda", "No NSC enrollment found", "Enrollment unable to be verified via NSC (sends user through self-attestation flow)." ]
169170
].each do |value, title, desc| %>
170-
<div class="launcher__rcard <%= "launcher__rcard--selected" if value == "lynette" %>"
171+
<div class="launcher__rcard <%= "launcher__rcard--selected" if value == "dominique" %>"
171172
role="radio"
172173
tabindex="0"
173-
aria-checked="<%= value == "lynette" %>"
174+
aria-checked="<%= value == "dominique" %>"
174175
data-launcher-target="statusCard"
175176
data-value="<%= value %>"
176177
data-action="click->launcher#selectStatus keydown.enter->launcher#selectStatus keydown.space->launcher#selectStatus">

app/spec/controllers/launcher_controller_advanced_spec.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
expect(rendered).to include('Rick Banas')
4646
expect(rendered).to match(/Enrolled half-time.*2 schools/)
4747
expect(rendered).to include('Dominique Ricardo')
48+
expect(rendered).to match(/Enrolled full time.*1 school/)
49+
expect(rendered).to include('Scott Tobin')
4850
expect(rendered).to include('Not currently enrolled')
4951
expect(rendered).to include('Linda Cooper')
5052
expect(rendered).to include('No NSC record found')
@@ -649,6 +651,7 @@
649651
it_behaves_like "creates CbvApplicant with correct data", "lynette", "Lynette", "Oyola", "1988-10-24"
650652
it_behaves_like "creates CbvApplicant with correct data", "rick", "Rick", "Banas", "1979-08-18"
651653
it_behaves_like "creates CbvApplicant with correct data", "dominique", "Dominique", "Ricardo", "1978-01-12"
654+
it_behaves_like "creates CbvApplicant with correct data", "scott", "Scott", "Tobin", "1998-02-03"
652655
it_behaves_like "creates CbvApplicant with correct data", "linda", "Linda", "Cooper", "1999-01-01"
653656

654657
it "creates an ActivityFlowInvitation and redirects to its URL" do

app/spec/controllers/launcher_controller_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,23 @@
6363
expect(body).to include("Enrolled full time")
6464
expect(body).to include("Enrolled half-time")
6565
expect(body).to include("Enrolled less-than-half-time")
66+
expect(body).to include("Enrolled, intensity not reported")
6667
expect(body).to include("No NSC enrollment found")
67-
expect(body).to include('value="lynette"')
68+
expect(body).to include('value="dominique"')
6869
expect(body).to include('value="renewal_half_time_last_4_of_6_avery"')
6970
expect(body).to include('value="partial_enrollment_maya"')
71+
expect(body).to include('value="lynette"')
7072
expect(body).to include('value="linda"')
7173
end
7274

75+
it "preselects the full-time scenario as the default student status" do
76+
get :launcher
77+
body = response.body
78+
expect(body).to include('data-launcher-status-value="dominique"')
79+
expect(body).to match(/launcher__rcard--selected[^>]*data-value="dominique"/)
80+
expect(body).to match(/name="test_scenario"[^>]*value="dominique"/)
81+
end
82+
7383
it "renders the launch buttons posting to /launcher" do
7484
get :launcher
7585
body = response.body

app/spec/factories/identity.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,11 @@
3434
last_name { "Ricardo" }
3535
date_of_birth { "1978-01-12" }
3636
end
37+
38+
trait :nsc_scott do
39+
first_name { "Scott" }
40+
last_name { "Tobin" }
41+
date_of_birth { "1998-02-03" }
42+
end
3743
end
3844
end

app/spec/jobs/application_job_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,50 @@ def perform
7070
end
7171
end
7272

73+
describe "non-retryable errors" do
74+
class ProgrammingErrorTestJob < ApplicationJob
75+
cattr_accessor :discarded_errors, default: []
76+
77+
after_discard { |_job, error| discarded_errors << error }
78+
79+
def perform
80+
raise NoMethodError, "undefined method '[]' for nil"
81+
end
82+
end
83+
84+
before { ProgrammingErrorTestJob.discarded_errors = [] }
85+
86+
it "re-raises on the first attempt instead of enqueuing a retry" do
87+
expect do
88+
expect { ProgrammingErrorTestJob.perform_now }.to raise_error(NoMethodError)
89+
end.not_to have_enqueued_job(ProgrammingErrorTestJob)
90+
end
91+
92+
it "runs after_discard callbacks on the first attempt" do
93+
expect { ProgrammingErrorTestJob.perform_now }.to raise_error(NoMethodError)
94+
95+
expect(ProgrammingErrorTestJob.discarded_errors.map(&:class)).to eq([ NoMethodError ])
96+
end
97+
98+
class ValidationErrorTestJob < ApplicationJob
99+
def perform
100+
raise ActiveRecord::RecordInvalid
101+
end
102+
end
103+
104+
it "does not retry validation errors" do
105+
expect do
106+
expect { ValidationErrorTestJob.perform_now }.to raise_error(ActiveRecord::RecordInvalid)
107+
end.not_to have_enqueued_job(ValidationErrorTestJob)
108+
end
109+
110+
it "still retries errors that are not programming errors" do
111+
expect do
112+
TestJob.perform_now
113+
end.to have_enqueued_job(TestJob)
114+
end
115+
end
116+
73117
describe "#with_flow_tags" do
74118
let(:cbv_applicant) { create(:cbv_applicant, client_agency_id: "sandbox") }
75119
let(:cbv_flow) { create(:cbv_flow, cbv_applicant: cbv_applicant) }

0 commit comments

Comments
 (0)