Skip to content

Commit 2678fbb

Browse files
authored
Merge pull request #4291 from nhsuk/simplify-pds-update-during-import
Simplify PDS update during import
2 parents 2118429 + f0d9baf commit 2678fbb

File tree

6 files changed

+175
-82
lines changed

6 files changed

+175
-82
lines changed

app/jobs/process_patient_changesets_job.rb

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,9 @@ class ProcessPatientChangesetsJob < ApplicationJob
88
def perform(patient_changeset)
99
attrs = patient_changeset.child_attributes
1010

11-
pds_patient =
12-
if attrs["nhs_number"].present?
13-
patient_found = find_patient(attrs["nhs_number"])
14-
15-
if patient_found == :invalid
16-
search_for_patient(attrs).then do |newly_found_patient|
17-
# If we found a patient, update the changeset with the new NHS
18-
# number. If we couldn't determine who this patient should really
19-
# be, we won't have an NHS number to replace the invalid one, so
20-
# just mark it as invalid.
21-
patient_changeset.invalidate! if newly_found_patient.nil?
22-
newly_found_patient
23-
end
24-
elsif patient_found == :not_found
25-
patient_changeset.update!(nhs_number: nil)
26-
search_for_patient(attrs)
27-
else
28-
patient_found
29-
end
30-
else
31-
search_for_patient(attrs)
32-
end
33-
34-
if pds_patient.present?
35-
patient_changeset.pending_changes.tap do |changes|
36-
changes["pds"] = {
37-
nhs_number: pds_patient.nhs_number,
38-
restricted: pds_patient.restricted,
39-
gp_ods_code: pds_patient.gp_ods_code,
40-
date_of_death: pds_patient.date_of_death
41-
}
42-
end
11+
if attrs["nhs_number"].blank? &&
12+
(pds_patient = search_for_patient(attrs)).present?
13+
attrs["nhs_number"] = pds_patient.nhs_number
4314
end
4415

4516
patient_changeset.processed!
@@ -57,14 +28,6 @@ def perform(patient_changeset)
5728

5829
private
5930

60-
def find_patient(nhs_number)
61-
PDS::Patient.find(nhs_number)
62-
rescue NHS::PDS::InvalidatedResource, NHS::PDS::InvalidNHSNumber
63-
:invalid
64-
rescue NHS::PDS::PatientNotFound
65-
:not_found
66-
end
67-
6831
def search_for_patient(attrs)
6932
PDS::Patient.search(
7033
family_name: attrs["family_name"],

app/models/concerns/csv_importable.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ def process!
126126
parse_rows! if rows.nil?
127127
return if invalid?
128128

129+
if is_a?(PatientImport) && Flipper.enabled?(:pds_lookup_during_import)
130+
changesets =
131+
rows.each_with_index.map do |row, row_number|
132+
PatientChangeset.from_import_row(row:, import: self, row_number:)
133+
end
134+
135+
changesets.each do
136+
if slow?
137+
ProcessPatientChangesetsJob.perform_later(it)
138+
else
139+
ProcessPatientChangesetsJob.perform_now(it)
140+
end
141+
end
142+
return
143+
end
144+
129145
counts = COUNT_COLUMNS.index_with(0)
130146

131147
ActiveRecord::Base.transaction do

app/models/patient_changeset.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def auto_accept_child_attributes(existing_patient)
213213
existing_patient,
214214
%w[male female not_specified]
215215
)
216-
set_child_attribute_if_valid(:invalidated_at, existing_patient)
216+
set_child_attribute_if_valid(:nhs_number, existing_patient)
217217
end
218218

219219
def set_child_attribute_if_valid(
Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,41 @@
11
# frozen_string_literal: true
22

33
describe "Import child records" do
4-
scenario "PDS lookup extravaganza" do
5-
skip "Feature is completely disabled temporarily"
4+
let(:today) { Time.zone.local(2025, 9, 1, 12, 0, 0) }
65

6+
around { |example| travel_to(today) { example.run } }
7+
8+
scenario "PDS lookup extravaganza with multiple patient scenarios" do
79
given_i_am_signed_in
810
and_an_hpv_programme_is_underway
911
and_an_existing_patient_record_exists
1012
and_pds_lookup_during_import_is_enabled
1113

1214
when_i_visit_the_import_page
13-
and_i_upload_the_pds_extravaganza_import_file
15+
and_i_upload_import_file("pds_extravaganza.csv")
1416
then_i_should_see_the_import_page
15-
and_i_should_see_the_existing_patient_as_imported
16-
17-
when_i_click_on_the_invalidated_patient
18-
then_i_should_see_invalidated_patient_record
17+
and_i_should_see_one_new_patient_created
18+
19+
# Case 1: Patient with existing NHS number (Albert) - nothing should happen
20+
and_i_see_the_patient_uploaded_with_nhs_number
21+
22+
# Case 2: Existing patient without NHS number (Betty) - should not show duplicate review
23+
and_i_do_not_see_an_import_review_for_the_first_patient_uploaded_without_nhs_number
24+
when_i_click_on_the_patient_without_review
25+
then_i_see_the_new_patient_has_an_nhs_number
26+
27+
# Case 3: Existing patient with NHS number (Catherine) - should show duplicate review
28+
when_i_go_back_to_the_import_page
29+
then_i_see_an_import_review_for_the_second_patient_uploaded_without_nhs_number
30+
when_i_click_review
31+
then_i_see_both_records_have_an_nhs_number
32+
when_i_use_duplicate_record_during_merge
33+
then_the_existing_patient_has_an_nhs_number_in_mavis
34+
35+
# Case 4: New patient without NHS number (Charlie) - should be created with NHS number from PDS
36+
when_i_go_back_to_the_import_page
37+
when_i_click_on_new_patient_uploaded_without_an_nhs_number
38+
then_i_see_the_new_patient_now_has_an_nhs_number
1939
end
2040

2141
def given_i_am_signed_in
@@ -38,7 +58,7 @@ def and_an_hpv_programme_is_underway
3858
end
3959

4060
def and_an_existing_patient_record_exists
41-
@existing_patient =
61+
@existing_patient_uploaded_with_nhs_number =
4262
create(
4363
:patient,
4464
given_name: "Albert",
@@ -50,40 +70,72 @@ def and_an_existing_patient_record_exists
5070
address_line_2: "",
5171
address_town: "London",
5272
address_postcode: "SW11 1EH",
53-
school: nil, # Unknown school, should be silently updated
73+
school: nil,
5474
session: @session
5575
)
56-
@invalidated_patient =
76+
77+
# Betty - will match exactly except NHS number (no review needed)
78+
@existing_patient_uploaded_without_nhs_number =
5779
create(
5880
:patient,
5981
given_name: "Betty",
6082
family_name: "Samson",
61-
nhs_number: "9993524689",
83+
nhs_number: nil,
6284
date_of_birth: Date.new(2010, 1, 1),
6385
gender_code: :female,
64-
address_line_1: "10 Downing Street",
86+
address_line_1: "123 High Street",
6587
address_line_2: "",
6688
address_town: "London",
67-
address_postcode: "SW11 1AA",
68-
school: nil, # Unknown school, should be silently updated
89+
address_postcode: "SW1A 1AA",
90+
school: nil,
91+
session: @session
92+
)
93+
94+
# Catherine - will have different address, causing review
95+
@existing_patient_duplicate_review =
96+
create(
97+
:patient,
98+
given_name: "Catherine",
99+
family_name: "Williams",
100+
nhs_number: "9876543210",
101+
date_of_birth: Date.new(2010, 5, 15),
102+
gender_code: :female,
103+
address_line_1: "999 Old Street", # Different from CSV
104+
address_line_2: "",
105+
address_town: "Birmingham", # Different from CSV
106+
address_postcode: "B1 1AA", # Different from CSV
107+
school: nil,
69108
session: @session
70109
)
110+
111+
expect(Patient.count).to eq(3)
71112
end
72113

73114
def and_pds_lookup_during_import_is_enabled
74115
Flipper.enable(:pds_lookup_during_import)
75116

76-
stub_pds_get_nhs_number_to_return_a_patient(@existing_patient.nhs_number)
117+
stub_pds_search_to_return_a_patient(
118+
"9449306168",
119+
"family" => "Samson",
120+
"given" => "Betty",
121+
"birthdate" => "eq2010-01-01",
122+
"address-postalcode" => "SW1A 1AA"
123+
)
77124

78-
stub_pds_search_to_return_no_patients(
79-
"family" => @invalidated_patient.family_name,
80-
"given" => @invalidated_patient.given_name,
81-
"birthdate" => "eq#{@invalidated_patient.date_of_birth}",
82-
"address-postalcode" => @invalidated_patient.address_postcode
125+
stub_pds_search_to_return_a_patient(
126+
"9876543210",
127+
"family" => "Williams",
128+
"given" => "Catherine",
129+
"birthdate" => "eq2009-05-15",
130+
"address-postalcode" => "SW2 2BB"
83131
)
84132

85-
stub_pds_get_nhs_number_to_return_an_invalidated_patient(
86-
@invalidated_patient.nhs_number
133+
stub_pds_search_to_return_a_patient(
134+
"4491459835",
135+
"family" => "Brown",
136+
"given" => "Charlie",
137+
"birthdate" => "eq2011-03-15",
138+
"address-postalcode" => "SW2 2BB"
87139
)
88140
end
89141

@@ -92,35 +144,82 @@ def when_i_visit_the_import_page
92144
click_link "Import", match: :first
93145
end
94146

95-
def and_i_upload_the_pds_extravaganza_import_file
147+
def when_i_go_back_to_the_import_page
148+
visit "/imports"
149+
click_link "1 September 2025 at 12:00pm"
150+
end
151+
152+
def when_i_click_review
153+
click_link "Review"
154+
end
155+
156+
def and_i_upload_import_file(filename)
96157
click_button "Import records"
97158
choose "Child records"
98159
click_button "Continue"
99-
attach_file(
100-
"cohort_import[csv]",
101-
"spec/fixtures/cohort_import/pds_extravaganza.csv"
102-
)
160+
attach_file("cohort_import[csv]", "spec/fixtures/cohort_import/#{filename}")
103161
click_on "Continue"
104162
end
105163

164+
def and_i_do_not_see_an_import_review_for_the_first_patient_uploaded_without_nhs_number
165+
expect(page).not_to have_content("Actions Review SAMSON, Betty")
166+
end
167+
168+
def when_i_click_on_the_patient_without_review
169+
click_link "SAMSON, Betty"
170+
end
171+
172+
def then_i_see_an_import_review_for_the_second_patient_uploaded_without_nhs_number
173+
expect(page).to have_content("Actions Review WILLIAMS, Catherine")
174+
end
175+
176+
def when_i_click_on_new_patient_uploaded_without_an_nhs_number
177+
click_link "BROWN, Charlie"
178+
end
179+
180+
def when_i_use_duplicate_record_during_merge
181+
choose "Use duplicate record"
182+
click_on "Resolve duplicate"
183+
end
184+
106185
def then_i_should_see_the_import_page
107186
expect(page).to have_content("Imported on")
108187
expect(page).to have_content("Imported byUSER, Test")
109188
end
110189

111-
def and_i_should_see_the_existing_patient_as_imported
112-
expect(page).not_to have_content("Actions Review TWEEDLE, Albert")
190+
def and_i_should_see_one_new_patient_created
191+
perform_enqueued_jobs
192+
expect(Patient.count).to eq(4)
113193
end
114194

115-
def when_i_click_on_the_invalidated_patient
116-
click_link "SAMSON, Betty"
195+
def and_i_see_the_patient_uploaded_with_nhs_number
196+
expect(page).to have_content(
197+
"Name and NHS number TWEEDLE, Albert 999 907 5320"
198+
)
117199
end
118200

119-
def then_i_should_see_invalidated_patient_record
120-
expect(page).to have_content("Record flagged as invalid")
201+
def then_i_see_the_new_patient_has_an_nhs_number
202+
expect(page).to have_content("944 930 6168")
121203
expect(page).to have_content("SAMSON, Betty")
122-
expect(page).to have_content("NHS number999 352 4689")
123-
expect(page).to have_content("Date of birth1 January 2010 (aged 15)")
124-
expect(page).to have_content("SW11 1AA")
204+
expect(page).to have_content("1 January 2010 (aged 15)")
205+
end
206+
207+
def then_i_see_both_records_have_an_nhs_number
208+
expect(page).to have_text("987 654 3210", count: 2)
209+
end
210+
211+
def then_i_see_the_new_patient_now_has_an_nhs_number
212+
expect(page).to have_content("NHS number449 145 9835")
213+
expect(page).to have_content("Full nameBROWN, Charlie")
214+
expect(page).to have_content("Date of birth15 March 2011 (aged 14)")
215+
end
216+
217+
def then_the_existing_patient_has_an_nhs_number_in_mavis
218+
expect(Patient.count).to eq(4)
219+
patient = Patient.where(given_name: "Catherine").first
220+
expect(patient.nhs_number).to eq("9876543210")
221+
expect(patient.address_line_1).to eq("456 New Street")
222+
expect(patient.address_town).to eq("London")
223+
expect(patient.address_postcode).to eq("SW2 2BB")
125224
end
126225
end
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
CHILD_SCHOOL_URN,PARENT_1_NAME,PARENT_1_RELATIONSHIP,PARENT_1_EMAIL,PARENT_1_PHONE,PARENT_2_NAME,PARENT_2_RELATIONSHIP,PARENT_2_EMAIL,PARENT_2_PHONE,CHILD_FIRST_NAME,CHILD_LAST_NAME,CHILD_PREFERRED_GIVEN_NAME,CHILD_DATE_OF_BIRTH,CHILD_YEAR_GROUP,CHILD_ADDRESS_LINE_1,CHILD_ADDRESS_LINE_2,CHILD_TOWN,CHILD_POSTCODE,CHILD_NHS_NUMBER
2-
123456,,,,,,,,,Albert,Tweedle,,2009-12-29,9,38A Battersaa Rise,,London,SW11 1EH,9999075320
3-
123456,,,,,,,,,Betty,Samson,Beth,2010-01-01,9,10 Downing Street,,London,SW11 1AA,9993524689
2+
123456,,,,,,,,,Albert,Tweedle,,2009-12-29,8,38A Battersea Rise,,London,SW11 1EH,9999075320
3+
123456,,,,,,,,,Betty,Samson,Beth,2010-01-01,8,123 High Street,,London,SW1A 1AA,
4+
123456,,,,,,,,,Catherine,Williams,,2009-05-15,8,456 New Street,,London,SW2 2BB,
5+
123456,,,,,,,,,Charlie,Brown,,2011-03-15,9,42 Wallaby Way,,London,SW2 2BB,

spec/support/pds_helper.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,25 @@ def stub_pds_search_to_return_no_patients(**query)
1010
).with(query: hash_including(query)).to_return_json(body: { total: 0 })
1111
end
1212

13-
def stub_pds_search_to_return_a_patient
13+
def stub_pds_search_to_return_a_patient(nhs_number = "9449306168", **query)
14+
query["_history"] ||= "true"
15+
16+
response_data =
17+
JSON.parse(file_fixture("pds/search-patients-response.json").read)
18+
19+
patient_resource = response_data["entry"][0]["resource"]
20+
21+
patient_resource["id"] = nhs_number
22+
patient_resource["identifier"][0]["value"] = nhs_number
23+
response_data["entry"][0][
24+
"fullUrl"
25+
] = "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/#{nhs_number}"
26+
1427
stub_request(
1528
:get,
1629
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient"
17-
).with(query: hash_including({})).to_return(
18-
body: file_fixture("pds/search-patients-response.json"),
30+
).with(query: hash_including(query)).to_return(
31+
body: response_data.to_json,
1932
headers: {
2033
"Content-Type" => "application/fhir+json"
2134
}

0 commit comments

Comments
 (0)