Skip to content

Commit af34a9c

Browse files
authored
NIAD-3155: Send over Observation (Narrative).meta.security NOPAT field to incumbent (#1185)
* observation with NOPAT get translated into narrative with confidentialityCode * changelog update * addressing PR comments
1 parent 9ba32c5 commit af34a9c

File tree

8 files changed

+288
-4
lines changed

8 files changed

+288
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
* When the ReferralRequest.meta.security field contains NOPAT, the GP2GP Adaptor will now populate the RequestStatement / confidentialityCode field accordingly.
1111
* The GP2GP Adaptor now populates the CompoundStatement / confidentialityCode field when Observation.meta.security field contains NOPAT
12+
* The GP2GP Adaptor now populates the NarrativeStatement / confidentialityCode field when Observation.meta.security contains NOPAT
1213

1314
## [2.3.0] - 2025-03-24
1415

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/ObservationToNarrativeStatementMapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.github.mustachejava.Mustache;
1313

1414
import lombok.RequiredArgsConstructor;
15+
import uk.nhs.adaptors.gp2gp.common.service.ConfidentialityService;
1516
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrMapperException;
1617
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.NarrativeStatementTemplateParameters;
1718
import uk.nhs.adaptors.gp2gp.ehr.utils.DateFormatUtil;
@@ -24,12 +25,17 @@ public class ObservationToNarrativeStatementMapper {
2425

2526
private final MessageContext messageContext;
2627
private final ParticipantMapper participantMapper;
28+
private final ConfidentialityService confidentialityService;
2729

2830
public String mapObservationToNarrativeStatement(Observation observation, boolean isNested) {
31+
32+
var confidentialityCode = confidentialityService.generateConfidentialityCode(observation);
33+
2934
final IdMapper idMapper = messageContext.getIdMapper();
3035
var narrativeStatementTemplateParameters = NarrativeStatementTemplateParameters.builder()
3136
.narrativeStatementId(idMapper.getOrNew(ResourceType.Observation, observation.getIdElement()))
3237
.availabilityTime(getAvailabilityTime(observation))
38+
.confidentialityCode(confidentialityCode.orElse(null))
3339
.comment(observation.getComment())
3440
.isNested(isNested);
3541

service/src/main/resources/templates/ehr_narrative_statement_template.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<text>{{comment}}</text>
55
<statusCode code="COMPLETE" />
66
<availabilityTime value="{{availabilityTime}}" />
7+
{{#confidentialityCode}}
8+
{{{confidentialityCode}}}
9+
{{/confidentialityCode}}
710
{{#participant}}
811
{{{.}}}
912
{{/participant}}

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ codeableConceptCdMapper, new ParticipantMapper(), confidentialityService),
179179
randomIdGeneratorService,
180180
confidentialityService
181181
),
182-
new ObservationToNarrativeStatementMapper(messageContext, participantMapper),
182+
new ObservationToNarrativeStatementMapper(messageContext, participantMapper, confidentialityService),
183183
new ObservationStatementMapper(
184184
messageContext,
185185
new StructuredObservationValueMapper(),

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EncounterComponentsMapperTest.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.hl7.fhir.dstu3.model.IdType;
88
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
99
import org.hl7.fhir.dstu3.model.Reference;
10+
import org.hl7.fhir.dstu3.model.Resource;
1011
import org.hl7.fhir.dstu3.model.ResourceType;
1112
import org.jetbrains.annotations.NotNull;
1213
import org.junit.jupiter.api.AfterEach;
@@ -83,6 +84,15 @@ public class EncounterComponentsMapperTest {
8384
+ "expected-components-17-topic-no-categories.xml";
8485
private static final String CONTAINED_TEST_DIRECTORY = TEST_DIRECTORY + "contained-resources/";
8586

87+
private static final String AGENT_DIRECTORY_FOLDER = "/ehr/mapper/observation/";
88+
private static final String INPUT_AGENT_DIRECTORY = AGENT_DIRECTORY_FOLDER + "observation-of-narrative-type-with-nopat.json";
89+
private static final String CONFIDENTIALITY_CODE = """
90+
<confidentialityCode
91+
code="NOPAT"
92+
codeSystem="2.16.840.1.113883.4.642.3.47"
93+
displayName="no disclosure to patient, family or caregivers without attending provider's authorization"
94+
/>""";
95+
8696
@Mock
8797
private RandomIdGeneratorService randomIdGeneratorService;
8898
@Mock
@@ -96,6 +106,7 @@ public class EncounterComponentsMapperTest {
96106

97107
private EncounterComponentsMapper encounterComponentsMapper;
98108
private MessageContext messageContext;
109+
private static final FhirParseService FHIR_PARSE_SERVICE = new FhirParseService();
99110

100111
@BeforeEach
101112
public void setUp() {
@@ -165,7 +176,7 @@ public void setUp() {
165176
confidentialityService
166177
);
167178
ObservationToNarrativeStatementMapper observationToNarrativeStatementMapper =
168-
new ObservationToNarrativeStatementMapper(messageContext, participantMapper);
179+
new ObservationToNarrativeStatementMapper(messageContext, participantMapper, confidentialityService);
169180
SpecimenMapper specimenMapper = getSpecimenMapper(structuredObservationValueMapper, participantMapper);
170181

171182
ObservationStatementMapper observationStatementMapper = new ObservationStatementMapper(
@@ -218,6 +229,19 @@ public void tearDown() {
218229
messageContext.resetMessageContext();
219230
}
220231

232+
@Test
233+
void testObservationWithNOPATGetsTranslatedIntoNarrativeWithConfidentialityCode() {
234+
var jsonInput = ResourceTestFileUtils.getFileContent(INPUT_AGENT_DIRECTORY);
235+
Bundle bundle = FHIR_PARSE_SERVICE.parseResource(jsonInput, Bundle.class);
236+
Resource observation = bundle.getEntry().get(1).getResource();
237+
238+
when(confidentialityService.generateConfidentialityCode(observation)).thenReturn(Optional.of(CONFIDENTIALITY_CODE));
239+
240+
var result = encounterComponentsMapper.mapResourceToComponent(observation);
241+
242+
assertThat(result.get()).contains(CONFIDENTIALITY_CODE);
243+
}
244+
221245
@Test
222246
public void When_MappingEncounterComponents_Expect_ResourceMapped() {
223247
String expectedXml = ResourceTestFileUtils.getFileContent(EXPECTED_COMPONENTS_MAPPED_WITH_ALL_MAPPERS_USED);

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/ObservationToNarrativeStatementMapperTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.mockito.junit.jupiter.MockitoSettings;
2929
import org.mockito.quality.Strictness;
3030

31+
import uk.nhs.adaptors.gp2gp.common.service.ConfidentialityService;
3132
import uk.nhs.adaptors.gp2gp.common.service.FhirParseService;
3233
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
3334
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrMapperException;
@@ -60,6 +61,9 @@ public class ObservationToNarrativeStatementMapperTest {
6061
@Mock
6162
private RandomIdGeneratorService randomIdGeneratorService;
6263

64+
@Mock
65+
private ConfidentialityService confidentialityService;
66+
6367
private CharSequence expectedOutputMessage;
6468
private ObservationToNarrativeStatementMapper observationToNarrativeStatementMapper;
6569
private MessageContext messageContext;
@@ -71,7 +75,9 @@ public void setUp() {
7175

7276
messageContext = new MessageContext(randomIdGeneratorService);
7377
messageContext.initialize(new Bundle());
74-
observationToNarrativeStatementMapper = new ObservationToNarrativeStatementMapper(messageContext, new ParticipantMapper());
78+
observationToNarrativeStatementMapper = new ObservationToNarrativeStatementMapper(messageContext,
79+
new ParticipantMapper(),
80+
confidentialityService);
7581
}
7682

7783
@AfterEach

service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ codeableConceptCdMapper, new ParticipantMapper(), confidentialityService),
199199
randomIdGeneratorService,
200200
confidentialityService
201201
),
202-
new ObservationToNarrativeStatementMapper(messageContext, participantMapper),
202+
new ObservationToNarrativeStatementMapper(messageContext, participantMapper, confidentialityService),
203203
new ObservationStatementMapper(
204204
messageContext,
205205
new StructuredObservationValueMapper(),
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
{
2+
"resourceType": "Bundle",
3+
"meta": {
4+
"profile": [
5+
"https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-StructuredRecord-Bundle-1"
6+
]
7+
},
8+
"type": "collection",
9+
"entry": [
10+
{
11+
"resource": {
12+
"resourceType": "Patient",
13+
"id": "88F14BF6-CADE-47D6-90E2-B10519BF956F",
14+
"meta": {
15+
"versionId": "5852021019724084706",
16+
"profile": [
17+
"https://fhir.nhs.uk/STU3/StructureDefinition/CareConnect-GPC-Patient-1"
18+
]
19+
},
20+
"identifier": [
21+
{
22+
"system": "https://fhir.nhs.uk/Id/nhs-number",
23+
"value": "9465701459"
24+
}
25+
],
26+
"name": [
27+
{
28+
"use": "official",
29+
"family": "Nel",
30+
"given": [
31+
"Morris",
32+
"Chad"
33+
],
34+
"prefix": [
35+
"Mr"
36+
]
37+
}
38+
],
39+
"gender": "male",
40+
"birthDate": "1999-02-25",
41+
"generalPractitioner": [
42+
{
43+
"reference": "Practitioner/6D340A1B-BC15-4D4E-93CF-BBCB5B74DF73"
44+
}
45+
],
46+
"managingOrganization": {
47+
"reference": "Organization/5E496953-065B-41F2-9577-BE8F2FBD0757"
48+
}
49+
}
50+
},
51+
{
52+
"resource": {
53+
"resourceType": "Observation",
54+
"id": "9BEFB146-B729-4EB8-908C-539B279FFEB5",
55+
"meta": {
56+
"profile": [
57+
"https://fhir.nhs.uk/STU3/StructureDefinition/CareConnect-GPC-Observation-1"
58+
]
59+
},
60+
"identifier": [
61+
{
62+
"system": "https://EMISWeb/A82038",
63+
"value": "9BEFB146-B729-4EB8-908C-539B279FFEB5"
64+
}
65+
],
66+
"status": "final",
67+
"code": {
68+
"coding": [
69+
{
70+
"system": "http://read.info/readv2",
71+
"code": "246..00",
72+
"display": "O/E - blood pressure reading",
73+
"userSelected": true
74+
},
75+
{
76+
"extension": [
77+
{
78+
"url": "https://fhir.nhs.uk/STU3/StructureDefinition/Extension-coding-sctdescid",
79+
"extension": [
80+
{
81+
"url": "descriptionId",
82+
"valueId": "254063019"
83+
},
84+
{
85+
"url": "descriptionDisplay",
86+
"valueString": "O/E - blood pressure reading"
87+
}
88+
]
89+
}
90+
],
91+
"system": "http://snomed.info/sct",
92+
"code": "37331000000100",
93+
"display": "O/E - blood pressure reading"
94+
}
95+
]
96+
},
97+
"subject": {
98+
"reference": "Patient/88F14BF6-CADE-47D6-90E2-B10519BF956F"
99+
},
100+
"context": {
101+
"reference": "Encounter/4088F3A1-CE58-4374-AABA-763C31738281"
102+
},
103+
"effectiveDateTime": "2010-07-14T17:55:00+01:00",
104+
"issued": "2010-07-14T16:28:13.577+01:00",
105+
"comment": "This is the BP reading in a consultation with a note",
106+
"component": [
107+
{
108+
"code": {
109+
"coding": [
110+
{
111+
"system": "https://fhir.hl7.org.uk/Id/egton-codes",
112+
"code": "2469",
113+
"display": "Systolic blood pressure",
114+
"userSelected": true
115+
},
116+
{
117+
"extension": [
118+
{
119+
"url": "https://fhir.nhs.uk/STU3/StructureDefinition/Extension-coding-sctdescid",
120+
"extension": [
121+
{
122+
"url": "descriptionId",
123+
"valueId": "120159016"
124+
},
125+
{
126+
"url": "descriptionDisplay",
127+
"valueString": "Systolic arterial pressure"
128+
}
129+
]
130+
}
131+
],
132+
"system": "http://snomed.info/sct",
133+
"code": "72313002",
134+
"display": "Systolic arterial pressure"
135+
}
136+
]
137+
},
138+
"valueQuantity": {
139+
"value": 120,
140+
"unit": "mmHg"
141+
}
142+
},
143+
{
144+
"code": {
145+
"coding": [
146+
{
147+
"system": "https://fhir.hl7.org.uk/Id/egton-codes",
148+
"code": "246A",
149+
"display": "Diastolic blood pressure",
150+
"userSelected": true
151+
},
152+
{
153+
"extension": [
154+
{
155+
"url": "https://fhir.nhs.uk/STU3/StructureDefinition/Extension-coding-sctdescid",
156+
"extension": [
157+
{
158+
"url": "descriptionId",
159+
"valueId": "2734671000000117"
160+
},
161+
{
162+
"url": "descriptionDisplay",
163+
"valueString": "Diastolic arterial pressure"
164+
}
165+
]
166+
}
167+
],
168+
"system": "http://snomed.info/sct",
169+
"code": "1091811000000102",
170+
"display": "Diastolic arterial pressure"
171+
}
172+
]
173+
},
174+
"valueQuantity": {
175+
"value": 75,
176+
"unit": "mmHg"
177+
}
178+
}
179+
]
180+
}
181+
},
182+
{
183+
"resource": {
184+
"resourceType": "Organization",
185+
"id": "5E496953-065B-41F2-9577-BE8F2FBD0757",
186+
"meta": {
187+
"versionId": "1112974926854455048",
188+
"profile": [
189+
"https://fhir.nhs.uk/STU3/StructureDefinition/CareConnect-GPC-Organization-1"
190+
]
191+
},
192+
"extension": [
193+
{
194+
"url": "https://fhir.nhs.uk/STU3/StructureDefinition/Extension-CareConnect-GPC-MainLocation-1",
195+
"valueReference": {
196+
"reference": "Location/EB3994A6-5A87-4B53-A414-913137072F57"
197+
}
198+
}
199+
],
200+
"identifier": [
201+
{
202+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
203+
"value": "A82038"
204+
}
205+
],
206+
"type": [
207+
{
208+
"coding": [
209+
{
210+
"system": "https://fhir.nhs.uk/STU3/CodeSystem/GPConnect-OrganisationType-1",
211+
"code": "gp-practice"
212+
}
213+
],
214+
"text": "GP Practice"
215+
}
216+
],
217+
"name": "TEMPLE SOWERBY MEDICAL PRACTICE",
218+
"telecom": [
219+
{
220+
"system": "phone",
221+
"value": "01133800000",
222+
"use": "work",
223+
"rank": 1
224+
}
225+
],
226+
"address": [
227+
{
228+
"use": "work",
229+
"type": "physical",
230+
"line": [
231+
"Fulford Grange",
232+
"Micklefield Lane",
233+
"Rawdon",
234+
"Rawdon"
235+
],
236+
"city": "Leeds",
237+
"district": "Yorkshire",
238+
"postalCode": "LS19 6BA"
239+
}
240+
]
241+
}
242+
}
243+
]
244+
}

0 commit comments

Comments
 (0)