Skip to content

Commit 281e326

Browse files
Create job to make search request, and handle response
The `SearchVaccinationRecordsInNHSJob` job is in charge of making sure that the vaccination records in Mavis match those in the Imms API.
1 parent 09fc8e7 commit 281e326

13 files changed

+758
-19
lines changed

app/components/app_vaccination_record_api_sync_status_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def additional_information_text
5353
case sync_status
5454
when :not_synced
5555
is_not_a_synced_programme =
56-
!vaccination_record.programme.can_write_to_immunisations_api?
56+
!vaccination_record.programme.can_sync_to_immunisations_api?
5757
if is_not_a_synced_programme
5858
"Records are currently not synced for this programme"
5959
elsif notify_parents == false
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
class SearchVaccinationRecordsInNHSJob < ApplicationJob
4+
include ImmunisationsAPIThrottlingConcern
5+
6+
queue_as :immunisation_api
7+
8+
def perform(patient)
9+
tx_id = SecureRandom.urlsafe_base64(16)
10+
SemanticLogger.tagged(tx_id:, job_id: provider_job_id || job_id) do
11+
Sentry.set_tags(tx_id:, job_id: provider_job_id || job_id)
12+
13+
return unless Flipper.enabled?(:imms_api_search_job)
14+
15+
programmes = Programme.can_search_in_immunisations_api
16+
17+
if patient.nhs_number.nil?
18+
incoming_vaccination_records = []
19+
else
20+
fhir_bundle =
21+
NHS::ImmunisationsAPI.search_immunisations(patient, programmes:)
22+
23+
incoming_vaccination_records =
24+
extract_vaccination_records(fhir_bundle).map do |fhir_record|
25+
if FHIRMapper::VaccinationRecord::MAVIS_SYSTEM_NAME.in?(
26+
fhir_record.identifier.map(&:system)
27+
)
28+
next
29+
end
30+
31+
FHIRMapper::VaccinationRecord.from_fhir_record(
32+
fhir_record,
33+
patient:
34+
)
35+
end
36+
incoming_vaccination_records = incoming_vaccination_records.compact
37+
end
38+
39+
existing_vaccination_records =
40+
patient
41+
.vaccination_records
42+
.includes(:identity_check)
43+
.where(programme: programmes, source: :nhs_immunisations_api)
44+
45+
existing_vaccination_records.find_each do |vaccination_record|
46+
incoming_vaccination_record =
47+
incoming_vaccination_records.find do
48+
it.nhs_immunisations_api_id ==
49+
vaccination_record.nhs_immunisations_api_id
50+
end
51+
52+
if incoming_vaccination_record
53+
vaccination_record.update!(
54+
incoming_vaccination_record
55+
.attributes
56+
.except("id", "uuid", "created_at")
57+
.merge(updated_at: Time.current)
58+
)
59+
60+
incoming_vaccination_records.delete(incoming_vaccination_record)
61+
else
62+
vaccination_record.destroy!
63+
end
64+
end
65+
66+
# Remaining incoming_vaccination_records are new
67+
incoming_vaccination_records.each(&:save!)
68+
end
69+
end
70+
71+
def extract_vaccination_records(fhir_bundle)
72+
fhir_bundle
73+
.entry
74+
.map { it.resource if it.resource.resourceType == "Immunization" }
75+
.compact
76+
end
77+
end

app/lib/fhir_mapper/vaccination_record.rb

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ class UnknownVaccine < StandardError
77

88
delegate_missing_to :@vaccination_record
99

10+
MAVIS_SYSTEM_NAME =
11+
"http://manage-vaccinations-in-schools.nhs.uk/vaccination_records"
12+
1013
def initialize(vaccination_record)
1114
@vaccination_record = vaccination_record
1215
end
@@ -119,11 +122,7 @@ def self.from_fhir_record(fhir_record, patient:)
119122
private
120123

121124
def fhir_identifier
122-
FHIR::Identifier.new(
123-
system:
124-
"http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
125-
value: uuid
126-
)
125+
FHIR::Identifier.new(system: MAVIS_SYSTEM_NAME, value: uuid)
127126
end
128127

129128
def fhir_vaccination_procedure_extension
@@ -151,7 +150,6 @@ def fhir_status
151150
when "completed"
152151
"administered"
153152
when "not-done"
154-
# "refused"
155153
# TODO: handle this more gracefully
156154
raise "Cannot import not-done vaccination records"
157155
else

app/lib/nhs/immunisations_api.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def should_be_in_immunisations_api?(
209209
)
210210
vaccination_record.kept? && vaccination_record.recorded_in_service? &&
211211
vaccination_record.administered? &&
212-
vaccination_record.programme.can_write_to_immunisations_api? &&
212+
vaccination_record.programme.can_sync_to_immunisations_api? &&
213213
(ignore_nhs_number || vaccination_record.patient.nhs_number.present?) &&
214214
vaccination_record.notify_parents &&
215215
vaccination_record.patient.not_invalidated?
@@ -226,7 +226,7 @@ def search_immunisations(patient, programmes:, date_from: nil, date_to: nil)
226226

227227
if programmes.empty?
228228
raise "Cannot search for vaccination records in the immunisations API; no programmes provided."
229-
elsif !programmes.all?(&:can_read_from_immunisations_api?)
229+
elsif !programmes.all?(&:can_search_in_immunisations_api?)
230230
raise "Cannot search for vaccination records in the immunisations API; one or more programmes is not supported."
231231
end
232232

app/models/programme.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class Programme < ApplicationRecord
3636
has_many :teams, through: :team_programmes
3737

3838
scope :supports_delegation, -> { flu }
39+
scope :can_sync_to_immunisations_api, -> { flu.or(hpv) }
40+
scope :can_search_in_immunisations_api, -> { flu }
3941

4042
enum :type,
4143
{ flu: "flu", hpv: "hpv", menacwy: "menacwy", td_ipv: "td_ipv" },
@@ -147,9 +149,9 @@ def snomed_target_disease_name
147149
SNOMED_TARGET_DISEASE_NAMES.fetch(type)
148150
end
149151

150-
def can_write_to_immunisations_api? = flu? || hpv?
152+
def can_sync_to_immunisations_api? = hpv? || flu?
151153

152-
def can_read_from_immunisations_api? = flu?
154+
def can_search_in_immunisations_api? = flu?
153155

154156
private
155157

spec/fixtures/files/fhir/search_response_0_results.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"link": [
55
{
66
"relation": "self",
7-
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?-date.from=2025-08-01\u0026-date.to=2025-10-01\u0026immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
7+
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
88
}
99
],
1010
"entry": [],

spec/fixtures/files/fhir/search_response_1_result.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"link": [
55
{
66
"relation": "self",
7-
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?-date.from=2025-08-01\u0026-date.to=2025-10-01\u0026immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
7+
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
88
}
99
],
1010
"entry": [
@@ -30,7 +30,7 @@
3030
"identifier": [
3131
{
3232
"use": "official",
33-
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
33+
"system": "https://supplierABC/identifiers/vacc",
3434
"value": "71f538d8-1171-4204-aee4-17ff0b0ba0b0"
3535
}
3636
],
@@ -52,7 +52,7 @@
5252
"value": "9449308357"
5353
}
5454
},
55-
"occurrenceDateTime": "2025-08-22T14:16:03+01:00",
55+
"occurrenceDateTime": "2025-08-23T14:16:03+01:00",
5656
"recorded": "2025-08-22T14:16:05.246000+01:00",
5757
"primarySource": true,
5858
"location": {
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
{
2+
"resourceType": "Bundle",
3+
"type": "searchset",
4+
"link": [
5+
{
6+
"relation": "self",
7+
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
8+
}
9+
],
10+
"entry": [
11+
{
12+
"fullUrl": "https://api.service.nhs.uk/immunisation-fhir-api/Immunization/4e7f3c91-a14d-4139-bbcf-859e998d2028",
13+
"resource": {
14+
"resourceType": "Immunization",
15+
"id": "4e7f3c91-a14d-4139-bbcf-859e998d2028",
16+
"extension": [
17+
{
18+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
19+
"valueCodeableConcept": {
20+
"coding": [
21+
{
22+
"system": "http://snomed.info/sct",
23+
"code": "884861000000100",
24+
"display": "Seasonal influenza vaccination (procedure)"
25+
}
26+
]
27+
}
28+
}
29+
],
30+
"identifier": [
31+
{
32+
"use": "official",
33+
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
34+
"value": "71f538d8-1171-4204-aee4-17ff0b0ba0b0"
35+
}
36+
],
37+
"status": "completed",
38+
"vaccineCode": {
39+
"coding": [
40+
{
41+
"system": "http://snomed.info/sct",
42+
"code": "43208811000001106",
43+
"display": "Fluenz (trivalent) vaccine nasal suspension 0.2ml unit dose (AstraZeneca UK Ltd) (product)"
44+
}
45+
]
46+
},
47+
"patient": {
48+
"reference": "urn:uuid:e06dbb8d-ef9b-454f-9c5f-fde8460a0b6a",
49+
"type": "Patient",
50+
"identifier": {
51+
"system": "https://fhir.nhs.uk/Id/nhs-number",
52+
"value": "9449308357"
53+
}
54+
},
55+
"occurrenceDateTime": "2025-08-22T14:16:03+01:00",
56+
"recorded": "2025-08-22T14:16:05.246000+01:00",
57+
"primarySource": true,
58+
"location": {
59+
"identifier": {
60+
"system": "https://fhir.hl7.org.uk/Id/urn-school-number",
61+
"value": "100001"
62+
}
63+
},
64+
"manufacturer": {
65+
"display": "AstraZeneca"
66+
},
67+
"lotNumber": "BU5086",
68+
"expirationDate": "2025-09-30",
69+
"site": {
70+
"coding": [
71+
{
72+
"system": "http://snomed.info/sct",
73+
"code": "279549004",
74+
"display": "Nasal cavity structure"
75+
}
76+
]
77+
},
78+
"route": {
79+
"coding": [
80+
{
81+
"system": "http://snomed.info/sct",
82+
"code": "46713006",
83+
"display": "Nasal"
84+
}
85+
]
86+
},
87+
"doseQuantity": {
88+
"value": 0.2,
89+
"unit": "ml",
90+
"system": "http://snomed.info/sct",
91+
"code": "258773002"
92+
},
93+
"performer": [
94+
{
95+
"actor": {
96+
"type": "Organization",
97+
"identifier": {
98+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
99+
"value": "R1L"
100+
}
101+
}
102+
}
103+
],
104+
"reasonCode": [
105+
{
106+
"coding": [
107+
{
108+
"system": "http://snomed.info/sct",
109+
"code": "723620004"
110+
}
111+
]
112+
}
113+
],
114+
"protocolApplied": [
115+
{
116+
"targetDisease": [
117+
{
118+
"coding": [
119+
{
120+
"system": "http://snomed.info/sct",
121+
"code": "6142004",
122+
"display": "Influenza"
123+
}
124+
]
125+
}
126+
],
127+
"doseNumberPositiveInt": 1
128+
}
129+
]
130+
},
131+
"search": {
132+
"mode": "match"
133+
}
134+
},
135+
{
136+
"fullUrl": "urn:uuid:e06dbb8d-ef9b-454f-9c5f-fde8460a0b6a",
137+
"resource": {
138+
"resourceType": "Patient",
139+
"id": "9449308357",
140+
"identifier": [
141+
{
142+
"system": "https://fhir.nhs.uk/Id/nhs-number",
143+
"value": "9449308357"
144+
}
145+
]
146+
},
147+
"search": {
148+
"mode": "include"
149+
}
150+
}
151+
],
152+
"total": 1
153+
}

spec/fixtures/files/fhir/search_response_2_results.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"link": [
55
{
66
"relation": "self",
7-
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?-date.from=2025-08-01\u0026-date.to=2025-10-01\u0026immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
7+
"url": "https://int.api.service.nhs.uk/immunisation-fhir-api/Immunization?immunization.target=FLU\u0026patient.identifier=https%3A%2F%2Ffhir.nhs.uk%2FId%2Fnhs-number%7C9449308357"
88
}
99
],
1010
"entry": [
@@ -30,7 +30,7 @@
3030
"identifier": [
3131
{
3232
"use": "official",
33-
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
33+
"system": "https://supplierABC/identifiers/vacc",
3434
"value": "71f538d8-1171-4204-aee4-17ff0b0ba0b0"
3535
}
3636
],
@@ -154,7 +154,7 @@
154154
"identifier": [
155155
{
156156
"use": "official",
157-
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
157+
"system": "https://supplierABC/identifiers/vacc",
158158
"value": "18441e7b-b652-4d8c-980c-b60009f95942"
159159
}
160160
],

0 commit comments

Comments
 (0)