Skip to content

Commit 2093d1b

Browse files
Merge pull request #5897 from nhsuk/alistair/imms-api-mmrv-fhir-mapper
Use `Programme::Variant`s in `FHIRMapper`
2 parents ec9ba2d + 4173782 commit 2093d1b

5 files changed

Lines changed: 309 additions & 20 deletions

File tree

app/lib/fhir_mapper/programme.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,46 @@ def fhir_target_disease_coding
2525
)
2626
end
2727
end
28+
29+
def self.from_fhir_record(fhir_record)
30+
target_diseases = fhir_record.protocolApplied.sole.targetDisease
31+
target_disease_codes =
32+
target_diseases.map do |disease|
33+
disease
34+
.coding
35+
.find { |coding| coding.system == "http://snomed.info/sct" }
36+
.code
37+
end
38+
39+
if (
40+
variant_type =
41+
::Programme::Variant::SNOMED_TARGET_DISEASE_CODES.key(
42+
target_disease_codes.to_set
43+
)
44+
)
45+
# If there is a matching `Programme::Variant`
46+
# TODO: Make `Programme::Variant` type more generic, so that it can handle any programme type;
47+
# remove MMR hardcoding here
48+
if %w[mmr mmrv].include?(variant_type)
49+
::Programme.find(
50+
"mmr",
51+
disease_types:
52+
::Programme::Variant::SNOMED_TARGET_DISEASE_TERMS.fetch(
53+
variant_type
54+
)
55+
)
56+
else
57+
raise Programme::InvalidType,
58+
"Programme::Variant type not mapped to a Programme; #{variant_type}"
59+
end
60+
else
61+
# Otherwise it must be a `Programme`
62+
::Programme.find(
63+
::Programme::SNOMED_TARGET_DISEASE_CODES.key(
64+
target_disease_codes.to_set
65+
)
66+
)
67+
end
68+
end
2869
end
2970
end

app/lib/fhir_mapper/vaccination_record.rb

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def self.from_fhir_record(fhir_record, patient:)
7575
.value
7676
attrs[:nhs_immunisations_api_primary_source] = fhir_record.primarySource
7777

78-
attrs[:programme] = programme_from_fhir(fhir_record)
78+
attrs[:programme] = Programme.from_fhir_record(fhir_record)
7979

8080
attrs[:performed_at] = Time.zone.parse(fhir_record.occurrenceDateTime)
8181
attrs[:outcome] = outcome_from_fhir(fhir_record)
@@ -331,23 +331,6 @@ def fhir_protocol_applied
331331
fhir_record.protocolApplied.sole.doseNumberPositiveInt
332332
end
333333

334-
private_class_method def self.programme_from_fhir(fhir_record)
335-
target_diseases = fhir_record.protocolApplied.sole.targetDisease
336-
target_disease_codes =
337-
target_diseases.map do |disease|
338-
disease
339-
.coding
340-
.find { |coding| coding.system == "http://snomed.info/sct" }
341-
.code
342-
end
343-
344-
::Programme.find(
345-
::Programme::SNOMED_TARGET_DISEASE_CODES.key(
346-
target_disease_codes.to_set
347-
)
348-
)
349-
end
350-
351334
private_class_method def self.batch_from_fhir(fhir_record, vaccine:)
352335
return if fhir_record.lotNumber&.to_s&.presence.nil?
353336

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
{
2+
"id": "11112222-3333-4444-5555-666677779999",
3+
"meta": {
4+
"versionId": "1"
5+
},
6+
"extension": [
7+
{
8+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
9+
"valueCodeableConcept": {
10+
"coding": [
11+
{
12+
"system": "http://snomed.info/sct",
13+
"code": "432636005",
14+
"display": "Administration of vaccine product containing only Human alphaherpesvirus 3 and Measles morbillivirus and Mumps orthorubulavirus and Rubella virus antigens"
15+
}
16+
]
17+
}
18+
}
19+
],
20+
"identifier": [
21+
{
22+
"use": "official",
23+
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
24+
"value": "aaaabbbb-0000-1111-3333-ffff77773333"
25+
}
26+
],
27+
"status": "completed",
28+
"vaccineCode": {
29+
"coding": [
30+
{
31+
"system": "http://snomed.info/sct",
32+
"code": "45525711000001102",
33+
"display": "Priorix Tetra vaccine powder and solvent for solution for injection 0.5ml pre-filled syringes (GlaxoSmithKline UK Ltd) (product)"
34+
}
35+
]
36+
},
37+
"patient": {
38+
"reference": "urn:uuid:b98d36c9-acab-444e-b2e8-f92c59f2ee26",
39+
"type": "Patient",
40+
"identifier": {
41+
"system": "https://fhir.nhs.uk/Id/nhs-number",
42+
"value": "9990001855"
43+
}
44+
},
45+
"occurrenceDateTime": "2025-01-27T08:49:44+00:00",
46+
"recorded": "2025-01-27T08:50:53.257000+00:00",
47+
"primarySource": true,
48+
"location": {
49+
"identifier": {
50+
"system": "https://fhir.hl7.org.uk/Id/urn-school-number",
51+
"value": "100006"
52+
}
53+
},
54+
"manufacturer": {
55+
"display": "GlaxoSmithKline"
56+
},
57+
"lotNumber": "XKCD1234",
58+
"expirationDate": "2026-11-10",
59+
"site": {
60+
"coding": [
61+
{
62+
"system": "http://snomed.info/sct",
63+
"code": "368208006",
64+
"display": "Left upper arm structure"
65+
}
66+
]
67+
},
68+
"route": {
69+
"coding": [
70+
{
71+
"system": "http://snomed.info/sct",
72+
"code": "78421000",
73+
"display": "Intramuscular"
74+
}
75+
]
76+
},
77+
"doseQuantity": {
78+
"value": 0.5,
79+
"unit": "ml",
80+
"system": "http://snomed.info/sct",
81+
"code": "258773002"
82+
},
83+
"performer": [
84+
{
85+
"actor": {
86+
"type": "Organization",
87+
"identifier": {
88+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
89+
"value": "R1L"
90+
}
91+
}
92+
}
93+
],
94+
"reasonCode": [
95+
{
96+
"coding": [
97+
{
98+
"system": "http://snomed.info/sct",
99+
"code": "723620004"
100+
}
101+
]
102+
}
103+
],
104+
"protocolApplied": [
105+
{
106+
"targetDisease": [
107+
{
108+
"coding": [
109+
{
110+
"system": "http://snomed.info/sct",
111+
"code": "14189004",
112+
"display": "Measles"
113+
}
114+
]
115+
},
116+
{
117+
"coding": [
118+
{
119+
"system": "http://snomed.info/sct",
120+
"code": "36989005",
121+
"display": "Mumps"
122+
}
123+
]
124+
},
125+
{
126+
"coding": [
127+
{
128+
"system": "http://snomed.info/sct",
129+
"code": "36653000",
130+
"display": "Rubella"
131+
}
132+
]
133+
},
134+
{
135+
"coding": [
136+
{
137+
"system": "http://snomed.info/sct",
138+
"code": "38907003",
139+
"display": "Varicella"
140+
}
141+
]
142+
}
143+
],
144+
"doseNumberString": "Unknown"
145+
}
146+
],
147+
"resourceType": "Immunization"
148+
}

spec/lib/fhir_mapper/programme_spec.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,76 @@
3131
end
3232
end
3333
end
34+
35+
describe "#from_fhir_record" do
36+
subject(:programme) { described_class.from_fhir_record(fhir_record) }
37+
38+
context "for a flu record" do
39+
let(:fhir_record) do
40+
FHIR.from_contents(file_fixture("fhir/flu/fhir_record_full.json").read)
41+
end
42+
43+
it { should be Programme.flu }
44+
end
45+
46+
context "for an hpv record" do
47+
let(:fhir_record) do
48+
FHIR.from_contents(
49+
file_fixture("fhir/hpv/fhir_record_from_mavis.json").read
50+
)
51+
end
52+
53+
it { should be Programme.hpv }
54+
end
55+
56+
context "for a menacwy record" do
57+
let(:fhir_record) do
58+
FHIR.from_contents(
59+
file_fixture("fhir/menacwy/fhir_record_from_mavis.json").read
60+
)
61+
end
62+
63+
it { should be Programme.menacwy }
64+
end
65+
66+
context "for a td_ipv record" do
67+
let(:fhir_record) do
68+
FHIR.from_contents(
69+
file_fixture("fhir/td_ipv/fhir_record_from_mavis.json").read
70+
)
71+
end
72+
73+
it { should be Programme.td_ipv }
74+
end
75+
76+
context "for a mmr record" do
77+
let(:fhir_record) do
78+
FHIR.from_contents(
79+
file_fixture("fhir/mmr/fhir_record_from_mavis.json").read
80+
)
81+
end
82+
83+
it do
84+
expect(programme).to eq Programme.mmr.variant_for(
85+
disease_types: Programme::Variant::DISEASE_TYPES.fetch("mmr")
86+
)
87+
end
88+
end
89+
90+
context "for a mmrv record" do
91+
before { Flipper.enable(:mmrv) }
92+
93+
let(:fhir_record) do
94+
FHIR.from_contents(
95+
file_fixture("fhir/mmrv/fhir_record_from_mavis.json").read
96+
)
97+
end
98+
99+
it do
100+
expect(programme).to eq Programme.mmr.variant_for(
101+
disease_types: Programme::Variant::DISEASE_TYPES.fetch("mmrv")
102+
)
103+
end
104+
end
105+
end
34106
end

spec/lib/fhir_mapper/vaccination_record_spec.rb

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -994,8 +994,7 @@
994994

995995
include_examples "a mapped vaccination record (common fields)"
996996

997-
its(:programme) { should be_a Programme::Variant }
998-
it { expect(vaccination_record.programme.variant_type).to eq "mmr" }
997+
its(:programme) { should eq programme }
999998

1000999
its(:nhs_immunisations_api_identifier_system) do
10011000
should eq "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records"
@@ -1026,5 +1025,51 @@
10261025
its(:notes) { should be_nil }
10271026
end
10281027
end
1028+
1029+
context "for mmrv" do
1030+
before { Flipper.enable(:mmrv) }
1031+
1032+
let(:programme) do
1033+
Programme.mmr.variant_for(
1034+
disease_types: Programme::Variant::DISEASE_TYPES.fetch("mmrv")
1035+
)
1036+
end
1037+
1038+
context "with a fhir record from Mavis" do
1039+
let(:fixture_file_name) { "fhir/mmrv/fhir_record_from_mavis.json" }
1040+
1041+
include_examples "a mapped vaccination record (common fields)"
1042+
1043+
its(:programme) { should eq programme }
1044+
1045+
its(:nhs_immunisations_api_identifier_system) do
1046+
should eq "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records"
1047+
end
1048+
1049+
its(:nhs_immunisations_api_identifier_value) do
1050+
should eq "aaaabbbb-0000-1111-3333-ffff77773333"
1051+
end
1052+
1053+
its(:performed_by_given_name) { should be_nil }
1054+
its(:performed_by_family_name) { should be_nil }
1055+
its(:batch) { should have_attributes(name: "XKCD1234") }
1056+
1057+
its(:vaccine) do
1058+
should have_attributes(snomed_product_code: "45525711000001102")
1059+
end
1060+
1061+
its(:performed_at) { should eq Time.parse("2025-01-27T08:49:44+0000") }
1062+
its(:delivery_method) { should eq "intramuscular" }
1063+
its(:delivery_site) { should eq "left_arm_upper_position" }
1064+
its(:full_dose) { should be true }
1065+
its(:outcome) { should eq "administered" }
1066+
its(:location) { should have_attributes(urn: "100006") }
1067+
its(:location_name) { should be_nil }
1068+
its(:performed_ods_code) { should eq "R1L" }
1069+
its(:nhs_immunisations_api_primary_source) { should be true }
1070+
1071+
its(:notes) { should be_nil }
1072+
end
1073+
end
10291074
end
10301075
end

0 commit comments

Comments
 (0)