@peopl-health/nexus 5.13.0-dev.1142 → 5.13.0-dev.1146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fhir/projections/interventionProjection.js +224 -22
- package/lib/fhir/projections/registerProjectors.js +0 -2
- package/lib/fhir/resources/Communication.js +3 -39
- package/lib/fhir/resources/CommunicationRequest.js +0 -28
- package/lib/fhir/resources/Procedure.js +2 -34
- package/lib/fhir/resources/Task.js +0 -31
- package/lib/fhir/services/interventionService.js +7 -131
- package/package.json +1 -1
- package/lib/fhir/resources/interventionExtensions.js +0 -28
|
@@ -1,18 +1,36 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
const
|
|
1
|
+
const { ZodError } = require('zod');
|
|
2
|
+
|
|
3
|
+
const { getDeviceId } = require('../config/fhirConfig');
|
|
4
|
+
const { fhirId, codeSystemUrl } = require('../helpers/fhirHelper');
|
|
5
|
+
const { identifier, patientReference, reference, recipientReference, codeableConcept, extension, notes, toIso } = require('../helpers/elementHelper');
|
|
6
|
+
const { baseResource, provenance } = require('../helpers/resourceHelper');
|
|
7
|
+
const { identifierValue, refToId, extValues } = require('../helpers/fhirReadHelper');
|
|
8
|
+
const { INTERVENTION_SLUG, INTERVENTION_KIND_SLUG, INTERVENTION_OUTCOME_SLUG } = require('../constants/projectionSlugs');
|
|
9
|
+
const { Intervention } = require('../../shared/dtos/Intervention');
|
|
10
|
+
const { logger } = require('../../utils/logger');
|
|
11
|
+
|
|
9
12
|
const ACTIVITY_TEXT = 'intervention-record';
|
|
10
13
|
const PROCEDURE_KINDS = new Set(['otc', 'self_care', 'other']);
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
const EXT = {
|
|
16
|
+
RATIONALE: 'intervention-rationale',
|
|
17
|
+
OUTCOME_NOTES: 'intervention-outcome-notes',
|
|
18
|
+
OUTCOME: 'intervention-outcome',
|
|
19
|
+
RECIPIENT_KIND: 'intervention-recipient-kind',
|
|
20
|
+
RECIPIENT_REF: 'intervention-recipient-ref',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RESOURCE_TYPES = ['Procedure', 'Communication', 'Task', 'CommunicationRequest'];
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
const interventionReason = (caseId) => [{ reference: reference('Condition', caseId) }];
|
|
26
|
+
const withNote = (payload, intervention) => {
|
|
27
|
+
const note = notes(intervention.rationale, intervention.outcomeNotes);
|
|
28
|
+
return note ? { ...payload, note } : payload;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function toFhir(intervention) {
|
|
32
|
+
const primary = primaryResource(intervention);
|
|
33
|
+
return [primary, provenance({
|
|
16
34
|
provenanceId: `${intervention.interventionId}-prov`,
|
|
17
35
|
targetRefs: [{ reference: `${primary.resourceType}/${fhirId(intervention.interventionId)}` }],
|
|
18
36
|
recordedAt: intervention.deliveredAt,
|
|
@@ -21,22 +39,206 @@ function projectIntervention(intervention) {
|
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
function primaryResource(intervention) {
|
|
24
|
-
if (PROCEDURE_KINDS.has(intervention.kind))
|
|
25
|
-
|
|
42
|
+
if (PROCEDURE_KINDS.has(intervention.kind)) return procedureResource(intervention);
|
|
43
|
+
if (intervention.kind === 'education') return communicationResource(intervention);
|
|
44
|
+
if (intervention.kind === 'team_escalation') return taskResource(intervention);
|
|
45
|
+
if (intervention.kind === 'follow_up_check') return communicationRequestResource(intervention);
|
|
46
|
+
throw new Error(`unknown intervention variant: ${intervention.kind}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function procedureResource(intervention) {
|
|
50
|
+
return withNote({
|
|
51
|
+
...baseResource('Procedure', intervention.interventionId, INTERVENTION_SLUG, intervention.patientId),
|
|
52
|
+
status: intervention.outcome === 'pending' ? 'in-progress' : 'completed',
|
|
53
|
+
code: codeableConcept(intervention.interventionLabel),
|
|
54
|
+
reason: interventionReason(intervention.caseId),
|
|
55
|
+
occurrenceDateTime: toIso(intervention.deliveredAt),
|
|
56
|
+
outcome: codeableConcept(intervention.outcome, { code: intervention.outcome, system: codeSystemUrl(INTERVENTION_OUTCOME_SLUG) }),
|
|
57
|
+
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
58
|
+
extension: interventionExtensions(intervention, { withRecipient: true }),
|
|
59
|
+
}, intervention);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function communicationResource(intervention) {
|
|
63
|
+
return withNote({
|
|
64
|
+
...baseResource('Communication', intervention.interventionId, INTERVENTION_SLUG, intervention.patientId),
|
|
65
|
+
status: 'completed',
|
|
66
|
+
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
67
|
+
reason: interventionReason(intervention.caseId),
|
|
68
|
+
sent: toIso(intervention.deliveredAt),
|
|
69
|
+
payload: [{ contentCodeableConcept: codeableConcept(intervention.interventionLabel) }],
|
|
70
|
+
recipient: [recipientReference({
|
|
71
|
+
patientId: intervention.patientId,
|
|
72
|
+
recipientKind: intervention.recipientKind,
|
|
73
|
+
recipientRefId: intervention.recipientRefId,
|
|
74
|
+
})],
|
|
75
|
+
extension: interventionExtensions(intervention),
|
|
76
|
+
}, intervention);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function taskResource(intervention) {
|
|
80
|
+
const payload = withNote({
|
|
81
|
+
resourceType: 'Task',
|
|
82
|
+
id: fhirId(intervention.interventionId),
|
|
83
|
+
identifier: [identifier(INTERVENTION_SLUG, intervention.interventionId)],
|
|
84
|
+
status: intervention.outcome === 'pending' ? 'requested' : 'completed',
|
|
85
|
+
intent: 'order',
|
|
86
|
+
code: codeableConcept(intervention.interventionLabel, {
|
|
87
|
+
code: intervention.kind,
|
|
88
|
+
system: codeSystemUrl(INTERVENTION_KIND_SLUG),
|
|
89
|
+
display: intervention.interventionLabel,
|
|
90
|
+
}),
|
|
91
|
+
authoredOn: toIso(intervention.deliveredAt),
|
|
92
|
+
for: patientReference(intervention.patientId),
|
|
93
|
+
requester: reference('Device', getDeviceId()),
|
|
94
|
+
reason: interventionReason(intervention.caseId),
|
|
95
|
+
extension: interventionExtensions(intervention, { withRecipient: true }),
|
|
96
|
+
}, intervention);
|
|
97
|
+
if (intervention.outcome !== 'pending') {
|
|
98
|
+
const outcomeCc = codeableConcept(intervention.outcome, { code: intervention.outcome, system: codeSystemUrl(INTERVENTION_OUTCOME_SLUG) });
|
|
99
|
+
payload.output = [{ type: codeableConcept(INTERVENTION_OUTCOME_SLUG), valueCodeableConcept: outcomeCc }];
|
|
26
100
|
}
|
|
27
|
-
|
|
28
|
-
|
|
101
|
+
return payload;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function communicationRequestResource(intervention) {
|
|
105
|
+
return withNote({
|
|
106
|
+
...baseResource('CommunicationRequest', intervention.interventionId, INTERVENTION_SLUG, intervention.patientId),
|
|
107
|
+
status: intervention.outcome === 'pending' ? 'active' : 'completed',
|
|
108
|
+
intent: 'plan',
|
|
109
|
+
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
110
|
+
reason: interventionReason(intervention.caseId),
|
|
111
|
+
payload: [{ contentCodeableConcept: codeableConcept(intervention.interventionLabel) }],
|
|
112
|
+
recipient: [recipientReference({
|
|
113
|
+
patientId: intervention.patientId,
|
|
114
|
+
recipientKind: intervention.recipientKind,
|
|
115
|
+
recipientRefId: intervention.recipientRefId,
|
|
116
|
+
})],
|
|
117
|
+
occurrenceDateTime: toIso(intervention.deliveredAt),
|
|
118
|
+
authoredOn: toIso(intervention.deliveredAt),
|
|
119
|
+
extension: interventionExtensions(intervention),
|
|
120
|
+
}, intervention);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function interventionExtensions(intervention, { withRecipient = false } = {}) {
|
|
124
|
+
const out = [];
|
|
125
|
+
if (intervention.rationale) out.push(extension(EXT.RATIONALE, { valueString: intervention.rationale }));
|
|
126
|
+
if (intervention.outcomeNotes) out.push(extension(EXT.OUTCOME_NOTES, { valueString: intervention.outcomeNotes }));
|
|
127
|
+
if (intervention.outcome) out.push(extension(EXT.OUTCOME, { valueString: intervention.outcome }));
|
|
128
|
+
if (withRecipient && intervention.recipientKind === 'related_person' && intervention.recipientRefId) {
|
|
129
|
+
out.push(extension(EXT.RECIPIENT_KIND, { valueString: 'related_person' }));
|
|
130
|
+
out.push(extension(EXT.RECIPIENT_REF, { valueString: intervention.recipientRefId }));
|
|
29
131
|
}
|
|
30
|
-
|
|
31
|
-
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function fromFhir(resources, { patientId }) {
|
|
136
|
+
return resources
|
|
137
|
+
.filter((r) => RESOURCE_TYPES.includes(r.resourceType) && identifierValue(r, INTERVENTION_SLUG))
|
|
138
|
+
.map((resource) => reconstruct(resource, patientId))
|
|
139
|
+
.filter(Boolean);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function reconstruct(resource, patientId) {
|
|
143
|
+
const reasonRef = resource.reason && resource.reason[0] && resource.reason[0].reference;
|
|
144
|
+
const base = {
|
|
145
|
+
interventionId: identifierValue(resource, INTERVENTION_SLUG),
|
|
146
|
+
patientId,
|
|
147
|
+
caseId: refToId(reasonRef && reasonRef.reference),
|
|
148
|
+
rationale: extValues(resource, EXT.RATIONALE)[0] ?? null,
|
|
149
|
+
outcomeNotes: extValues(resource, EXT.OUTCOME_NOTES)[0] ?? null,
|
|
150
|
+
outcomeExt: extValues(resource, EXT.OUTCOME)[0] ?? null,
|
|
151
|
+
recipientKind: 'patient',
|
|
152
|
+
recipientRefId: null,
|
|
153
|
+
};
|
|
154
|
+
let reconstructed;
|
|
155
|
+
if (resource.resourceType === 'Procedure') reconstructed = reconstructProcedure(resource, base);
|
|
156
|
+
else if (resource.resourceType === 'Communication') reconstructed = reconstructCommunication(resource, base);
|
|
157
|
+
else if (resource.resourceType === 'Task') reconstructed = reconstructTask(resource, base);
|
|
158
|
+
else reconstructed = reconstructCommunicationRequest(resource, base);
|
|
159
|
+
try {
|
|
160
|
+
return new Intervention(reconstructed);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error instanceof ZodError) {
|
|
163
|
+
logger.warn({ interventionId: reconstructed.interventionId }, 'skipping malformed intervention resource');
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
32
167
|
}
|
|
33
|
-
|
|
34
|
-
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function reconstructProcedure(resource, base) {
|
|
171
|
+
const { outcomeExt, ...dto } = base;
|
|
172
|
+
return {
|
|
173
|
+
...dto,
|
|
174
|
+
kind: categoryCode(resource),
|
|
175
|
+
interventionLabel: (resource.code && resource.code.text) || null,
|
|
176
|
+
deliveredAt: resource.occurrenceDateTime,
|
|
177
|
+
outcome: outcomeExt ?? codeableConceptCode(resource.outcome),
|
|
178
|
+
...recipientFromExtension(resource),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function reconstructCommunication(resource, base) {
|
|
183
|
+
const { outcomeExt, ...dto } = base;
|
|
184
|
+
const content = resource.payload && resource.payload[0] && resource.payload[0].contentCodeableConcept;
|
|
185
|
+
const outcome = outcomeExt ?? undefined;
|
|
186
|
+
return {
|
|
187
|
+
...dto,
|
|
188
|
+
kind: categoryCode(resource),
|
|
189
|
+
interventionLabel: (content && content.text) || null,
|
|
190
|
+
deliveredAt: resource.sent,
|
|
191
|
+
...(outcome ? { outcome } : {}),
|
|
192
|
+
...recipientFromReference(resource),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function reconstructTask(resource, base) {
|
|
197
|
+
const { outcomeExt, ...dto } = base;
|
|
198
|
+
const output = resource.output && resource.output[0];
|
|
199
|
+
return {
|
|
200
|
+
...dto,
|
|
201
|
+
kind: codingCode(resource.code),
|
|
202
|
+
interventionLabel: (resource.code && resource.code.text) || null,
|
|
203
|
+
deliveredAt: resource.authoredOn,
|
|
204
|
+
outcome: outcomeExt ?? (output ? codeableConceptCode(output.valueCodeableConcept) : 'pending'),
|
|
205
|
+
...recipientFromExtension(resource),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function reconstructCommunicationRequest(resource, base) {
|
|
210
|
+
const { outcomeExt, ...dto } = base;
|
|
211
|
+
const content = resource.payload && resource.payload[0] && resource.payload[0].contentCodeableConcept;
|
|
212
|
+
return {
|
|
213
|
+
...dto,
|
|
214
|
+
kind: categoryCode(resource),
|
|
215
|
+
interventionLabel: (content && content.text) || null,
|
|
216
|
+
deliveredAt: resource.occurrenceDateTime,
|
|
217
|
+
outcome: outcomeExt ?? (resource.status === 'active' ? 'pending' : 'unknown'),
|
|
218
|
+
...recipientFromReference(resource),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function recipientFromExtension(resource) {
|
|
223
|
+
const kind = extValues(resource, EXT.RECIPIENT_KIND)[0];
|
|
224
|
+
const refId = extValues(resource, EXT.RECIPIENT_REF)[0];
|
|
225
|
+
if (kind === 'related_person' && refId) return { recipientKind: 'related_person', recipientRefId: refId };
|
|
226
|
+
return { recipientKind: 'patient', recipientRefId: null };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function recipientFromReference(resource) {
|
|
230
|
+
const recipientRef = resource.recipient && resource.recipient[0] && resource.recipient[0].reference;
|
|
231
|
+
if (recipientRef && recipientRef.startsWith('RelatedPerson/')) {
|
|
232
|
+
return { recipientKind: 'related_person', recipientRefId: refToId(recipientRef) };
|
|
35
233
|
}
|
|
36
|
-
|
|
234
|
+
return { recipientKind: 'patient', recipientRefId: null };
|
|
37
235
|
}
|
|
38
236
|
|
|
237
|
+
const codingCode = (concept) => (concept && concept.coding && concept.coding[0] && concept.coding[0].code) || null;
|
|
238
|
+
const categoryCode = (resource) => codingCode(resource.category && resource.category[0]);
|
|
239
|
+
const codeableConceptCode = (concept) => codingCode(concept) || (concept && concept.text) || 'pending';
|
|
240
|
+
|
|
39
241
|
module.exports = {
|
|
40
|
-
|
|
41
|
-
|
|
242
|
+
toFhir,
|
|
243
|
+
fromFhir,
|
|
42
244
|
};
|
|
@@ -6,7 +6,6 @@ const { projectSymptomCase, PROJECTOR_NAME: SYMPTOM_CASE_PROJECTOR_NAME } = requ
|
|
|
6
6
|
const { projectPatientRisk, PROJECTOR_NAME: RISK_PROJECTOR_NAME } = require('./riskProjection');
|
|
7
7
|
const { projectSafetyGate, PROJECTOR_NAME: SAFETY_GATE_PROJECTOR_NAME } = require('./safetyGateProjection');
|
|
8
8
|
const { projectRecommendationPlan, PROJECTOR_NAME: RECOMMENDATION_PLAN_PROJECTOR_NAME } = require('./recommendationPlanProjection');
|
|
9
|
-
const { projectIntervention, PROJECTOR_NAME: INTERVENTION_PROJECTOR_NAME } = require('./interventionProjection');
|
|
10
9
|
const { projectCluster, PROJECTOR_NAME: CLUSTER_PROJECTOR_NAME } = require('./clusterProjection');
|
|
11
10
|
const { projectRoutingDecision, PROJECTOR_NAME: ROUTING_PROJECTOR_NAME } = require('./routingProjection');
|
|
12
11
|
const { projectPalliativeAssessment, PROJECTOR_NAME: PALLIATIVE_PROJECTOR_NAME } = require('./palliativeProjection');
|
|
@@ -20,7 +19,6 @@ const PROJECTORS = [
|
|
|
20
19
|
{ name: RISK_PROJECTOR_NAME, project: projectPatientRisk },
|
|
21
20
|
{ name: SAFETY_GATE_PROJECTOR_NAME, project: projectSafetyGate },
|
|
22
21
|
{ name: RECOMMENDATION_PLAN_PROJECTOR_NAME, project: projectRecommendationPlan },
|
|
23
|
-
{ name: INTERVENTION_PROJECTOR_NAME, project: projectIntervention },
|
|
24
22
|
{ name: CLUSTER_PROJECTOR_NAME, project: projectCluster },
|
|
25
23
|
{ name: ROUTING_PROJECTOR_NAME, project: projectRoutingDecision },
|
|
26
24
|
{ name: PALLIATIVE_PROJECTOR_NAME, project: projectPalliativeAssessment },
|
|
@@ -1,48 +1,12 @@
|
|
|
1
|
-
const { fhirId
|
|
2
|
-
const {
|
|
3
|
-
|
|
4
|
-
reference,
|
|
5
|
-
patientReference,
|
|
6
|
-
recipientReference,
|
|
7
|
-
codeableConcept,
|
|
8
|
-
notes,
|
|
9
|
-
toIso,
|
|
10
|
-
} = require('../helpers/elementHelper');
|
|
11
|
-
const {
|
|
12
|
-
INTERVENTION_SLUG,
|
|
13
|
-
INTERVENTION_KIND_SLUG,
|
|
14
|
-
ROUTING_DISPOSITION_SLUG,
|
|
15
|
-
} = require('../constants/projectionSlugs');
|
|
16
|
-
const { interventionExtensions } = require('./interventionExtensions');
|
|
1
|
+
const { fhirId } = require('../helpers/fhirHelper');
|
|
2
|
+
const { identifier, reference, patientReference, codeableConcept, toIso } = require('../helpers/elementHelper');
|
|
3
|
+
const { ROUTING_DISPOSITION_SLUG } = require('../constants/projectionSlugs');
|
|
17
4
|
|
|
18
5
|
class Communication {
|
|
19
6
|
constructor(payload) {
|
|
20
7
|
Object.assign(this, payload);
|
|
21
8
|
}
|
|
22
9
|
|
|
23
|
-
static fromIntervention({ intervention }) {
|
|
24
|
-
const payload = {
|
|
25
|
-
resourceType: 'Communication',
|
|
26
|
-
id: fhirId(intervention.interventionId),
|
|
27
|
-
identifier: [identifier(INTERVENTION_SLUG, intervention.interventionId)],
|
|
28
|
-
status: 'completed',
|
|
29
|
-
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
30
|
-
subject: patientReference(intervention.patientId),
|
|
31
|
-
reason: [{ reference: reference('Condition', intervention.caseId) }],
|
|
32
|
-
sent: toIso(intervention.deliveredAt),
|
|
33
|
-
payload: [{ contentCodeableConcept: codeableConcept(intervention.interventionLabel) }],
|
|
34
|
-
recipient: [recipientReference({
|
|
35
|
-
patientId: intervention.patientId,
|
|
36
|
-
recipientKind: intervention.recipientKind,
|
|
37
|
-
recipientRefId: intervention.recipientRefId,
|
|
38
|
-
})],
|
|
39
|
-
extension: interventionExtensions(intervention),
|
|
40
|
-
};
|
|
41
|
-
const note = notes(intervention.rationale, intervention.outcomeNotes);
|
|
42
|
-
if (note) payload.note = note;
|
|
43
|
-
return new Communication(payload);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
10
|
static fromRoutingDisposition({ disposition }) {
|
|
47
11
|
const payload = {
|
|
48
12
|
resourceType: 'Communication',
|
|
@@ -15,12 +15,9 @@ const {
|
|
|
15
15
|
REMINDER_SLUG,
|
|
16
16
|
REMINDER_KIND_SLUG,
|
|
17
17
|
COMMUNICATION_CHANNEL_SLUG,
|
|
18
|
-
INTERVENTION_SLUG,
|
|
19
|
-
INTERVENTION_KIND_SLUG,
|
|
20
18
|
ROUTING_DISPOSITION_SLUG,
|
|
21
19
|
} = require('../constants/projectionSlugs');
|
|
22
20
|
const { SEVERITY_TO_PRIORITY } = require('../constants/severityPriority');
|
|
23
|
-
const { interventionExtensions } = require('./interventionExtensions');
|
|
24
21
|
|
|
25
22
|
const NOTIFY_STATUS_TO_CR_STATUS = {
|
|
26
23
|
queued: 'active',
|
|
@@ -91,31 +88,6 @@ class CommunicationRequest {
|
|
|
91
88
|
return new CommunicationRequest(payload);
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
static fromIntervention({ intervention }) {
|
|
95
|
-
const payload = {
|
|
96
|
-
resourceType: 'CommunicationRequest',
|
|
97
|
-
id: fhirId(intervention.interventionId),
|
|
98
|
-
identifier: [identifier(INTERVENTION_SLUG, intervention.interventionId)],
|
|
99
|
-
status: intervention.outcome === 'pending' ? 'active' : 'completed',
|
|
100
|
-
intent: 'plan',
|
|
101
|
-
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
102
|
-
subject: patientReference(intervention.patientId),
|
|
103
|
-
reason: [{ reference: reference('Condition', intervention.caseId) }],
|
|
104
|
-
payload: [{ contentCodeableConcept: codeableConcept(intervention.interventionLabel) }],
|
|
105
|
-
recipient: [recipientReference({
|
|
106
|
-
patientId: intervention.patientId,
|
|
107
|
-
recipientKind: intervention.recipientKind,
|
|
108
|
-
recipientRefId: intervention.recipientRefId,
|
|
109
|
-
})],
|
|
110
|
-
occurrenceDateTime: toIso(intervention.deliveredAt),
|
|
111
|
-
authoredOn: toIso(intervention.deliveredAt),
|
|
112
|
-
extension: interventionExtensions(intervention),
|
|
113
|
-
};
|
|
114
|
-
const note = notes(intervention.rationale, intervention.outcomeNotes);
|
|
115
|
-
if (note) payload.note = note;
|
|
116
|
-
return new CommunicationRequest(payload);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
91
|
static fromRoutingDisposition({ disposition }) {
|
|
120
92
|
const payload = {
|
|
121
93
|
resourceType: 'CommunicationRequest',
|
|
@@ -1,43 +1,11 @@
|
|
|
1
|
-
const { fhirId
|
|
2
|
-
const {
|
|
3
|
-
identifier,
|
|
4
|
-
patientReference,
|
|
5
|
-
reference,
|
|
6
|
-
codeableConcept,
|
|
7
|
-
notes,
|
|
8
|
-
toIso,
|
|
9
|
-
} = require('../helpers/elementHelper');
|
|
10
|
-
const {
|
|
11
|
-
INTERVENTION_SLUG,
|
|
12
|
-
INTERVENTION_KIND_SLUG,
|
|
13
|
-
INTERVENTION_OUTCOME_SLUG,
|
|
14
|
-
} = require('../constants/projectionSlugs');
|
|
15
|
-
const { interventionExtensions } = require('./interventionExtensions');
|
|
1
|
+
const { fhirId } = require('../helpers/fhirHelper');
|
|
2
|
+
const { identifier, patientReference } = require('../helpers/elementHelper');
|
|
16
3
|
|
|
17
4
|
class Procedure {
|
|
18
5
|
constructor(payload) {
|
|
19
6
|
Object.assign(this, payload);
|
|
20
7
|
}
|
|
21
8
|
|
|
22
|
-
static fromIntervention({ intervention }) {
|
|
23
|
-
const payload = {
|
|
24
|
-
resourceType: 'Procedure',
|
|
25
|
-
id: fhirId(intervention.interventionId),
|
|
26
|
-
identifier: [identifier(INTERVENTION_SLUG, intervention.interventionId)],
|
|
27
|
-
status: intervention.outcome === 'pending' ? 'in-progress' : 'completed',
|
|
28
|
-
code: codeableConcept(intervention.interventionLabel),
|
|
29
|
-
subject: patientReference(intervention.patientId),
|
|
30
|
-
reason: [{ reference: reference('Condition', intervention.caseId) }],
|
|
31
|
-
occurrenceDateTime: toIso(intervention.deliveredAt),
|
|
32
|
-
outcome: codeableConcept(intervention.outcome, { code: intervention.outcome, system: codeSystemUrl(INTERVENTION_OUTCOME_SLUG) }),
|
|
33
|
-
category: [codeableConcept(intervention.kind, { code: intervention.kind, system: codeSystemUrl(INTERVENTION_KIND_SLUG) })],
|
|
34
|
-
extension: interventionExtensions(intervention, { withRecipient: true }),
|
|
35
|
-
};
|
|
36
|
-
const note = notes(intervention.rationale, intervention.outcomeNotes);
|
|
37
|
-
if (note) payload.note = note;
|
|
38
|
-
return new Procedure(payload);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
9
|
static fromMentionProjection({ fact, provenance, code, extensions, slug }) {
|
|
42
10
|
const assertion = fact.assertion;
|
|
43
11
|
const notes = provenance.verbatimQuotes && provenance.verbatimQuotes.length
|
|
@@ -12,9 +12,6 @@ const {
|
|
|
12
12
|
const {
|
|
13
13
|
ESCALATION_SLUG,
|
|
14
14
|
ESCALATION_KIND_SLUG,
|
|
15
|
-
INTERVENTION_SLUG,
|
|
16
|
-
INTERVENTION_KIND_SLUG,
|
|
17
|
-
INTERVENTION_OUTCOME_SLUG,
|
|
18
15
|
ROUTING_DISPOSITION_SLUG,
|
|
19
16
|
RECOMMENDATION_REC_SLUG,
|
|
20
17
|
PALLIATIVE_ESCALATION_SLUG,
|
|
@@ -22,7 +19,6 @@ const {
|
|
|
22
19
|
} = require('../constants/projectionSlugs');
|
|
23
20
|
const { PALLIATIVE_EXT } = require('../constants/extensionSlugs');
|
|
24
21
|
const { SEVERITY_TO_PRIORITY } = require('../constants/severityPriority');
|
|
25
|
-
const { interventionExtensions } = require('./interventionExtensions');
|
|
26
22
|
|
|
27
23
|
const TICKET_STATUS_TO_TASK_STATUS = {
|
|
28
24
|
queued: 'requested',
|
|
@@ -60,33 +56,6 @@ class Task {
|
|
|
60
56
|
return new Task(payload);
|
|
61
57
|
}
|
|
62
58
|
|
|
63
|
-
static fromIntervention({ intervention }) {
|
|
64
|
-
const payload = {
|
|
65
|
-
resourceType: 'Task',
|
|
66
|
-
id: fhirId(intervention.interventionId),
|
|
67
|
-
identifier: [identifier(INTERVENTION_SLUG, intervention.interventionId)],
|
|
68
|
-
status: intervention.outcome === 'pending' ? 'requested' : 'completed',
|
|
69
|
-
intent: 'order',
|
|
70
|
-
code: codeableConcept(intervention.interventionLabel, {
|
|
71
|
-
code: intervention.kind,
|
|
72
|
-
system: codeSystemUrl(INTERVENTION_KIND_SLUG),
|
|
73
|
-
display: intervention.interventionLabel,
|
|
74
|
-
}),
|
|
75
|
-
authoredOn: toIso(intervention.deliveredAt),
|
|
76
|
-
for: patientReference(intervention.patientId),
|
|
77
|
-
requester: reference('Device', getDeviceId()),
|
|
78
|
-
reason: [{ reference: reference('Condition', intervention.caseId) }],
|
|
79
|
-
extension: interventionExtensions(intervention, { withRecipient: true }),
|
|
80
|
-
};
|
|
81
|
-
if (intervention.outcome !== 'pending') {
|
|
82
|
-
const outcomeCc = codeableConcept(intervention.outcome, { code: intervention.outcome, system: codeSystemUrl(INTERVENTION_OUTCOME_SLUG) });
|
|
83
|
-
payload.output = [{ type: codeableConcept(INTERVENTION_OUTCOME_SLUG), valueCodeableConcept: outcomeCc }];
|
|
84
|
-
}
|
|
85
|
-
const note = notes(intervention.rationale, intervention.outcomeNotes);
|
|
86
|
-
if (note) payload.note = note;
|
|
87
|
-
return new Task(payload);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
59
|
static fromRecommendationPlan({ plan, element, taskId }) {
|
|
91
60
|
const { recommendation } = element;
|
|
92
61
|
const { patient } = recommendation;
|
|
@@ -1,148 +1,24 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
const { fhirId, identifierSystem } = require('../helpers/fhirHelper');
|
|
4
|
-
const { identifierValue, refToId, extValues } = require('../helpers/fhirReadHelper');
|
|
5
|
-
const { INTERVENTION_SLUG } = require('../constants/projectionSlugs');
|
|
6
|
-
const {
|
|
7
|
-
RATIONALE_EXT,
|
|
8
|
-
OUTCOME_NOTES_EXT,
|
|
9
|
-
OUTCOME_EXT,
|
|
10
|
-
RECIPIENT_KIND_EXT,
|
|
11
|
-
RECIPIENT_REF_EXT,
|
|
12
|
-
} = require('../resources/interventionExtensions');
|
|
13
|
-
const { PROJECTOR_NAME } = require('../projections/interventionProjection');
|
|
1
|
+
const { fhirId } = require('../helpers/fhirHelper');
|
|
14
2
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
15
|
-
const {
|
|
16
|
-
const {
|
|
17
|
-
const { project, storeResources } = require('./fhirService');
|
|
18
|
-
|
|
19
|
-
const VARIANT_TYPES = new Set(['Procedure', 'Communication', 'Task', 'CommunicationRequest']);
|
|
3
|
+
const { toFhir, fromFhir } = require('../projections/interventionProjection');
|
|
4
|
+
const { storeResources } = require('./fhirService');
|
|
20
5
|
|
|
21
6
|
async function storeIntervention({ patientId, intervention }) {
|
|
22
7
|
if (intervention.patientId !== patientId) {
|
|
23
8
|
throw new Error(`storeIntervention patientId mismatch: expected ${patientId}`);
|
|
24
9
|
}
|
|
25
|
-
const
|
|
26
|
-
const resources = bundle.entry.map((entry) => entry.resource);
|
|
27
|
-
const { stored } = await storeResources(resources);
|
|
10
|
+
const { stored } = await storeResources(toFhir(intervention));
|
|
28
11
|
return { count: stored.length, ids: stored };
|
|
29
12
|
}
|
|
30
13
|
|
|
31
14
|
async function readInterventions({ patientId, caseId = null }) {
|
|
32
15
|
const store = getFhirStore();
|
|
33
16
|
const patient = `Patient/${fhirId(patientId)}`;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
.filter((
|
|
37
|
-
&& (resource.identifier || []).some((id) => id.system === interventionSystem));
|
|
38
|
-
const interventions = [];
|
|
39
|
-
for (const resource of resources) {
|
|
40
|
-
if (!identifierValue(resource, INTERVENTION_SLUG)) continue;
|
|
41
|
-
const reconstructed = reconstructIntervention(resource, patientId);
|
|
42
|
-
if (!reconstructed.caseId) continue;
|
|
43
|
-
if (caseId && reconstructed.caseId !== caseId) continue;
|
|
44
|
-
try {
|
|
45
|
-
interventions.push(new Intervention(reconstructed));
|
|
46
|
-
} catch (error) {
|
|
47
|
-
if (error instanceof ZodError) {
|
|
48
|
-
logger.warn({ interventionId: reconstructed.interventionId }, 'skipping malformed intervention resource');
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return interventions;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function reconstructIntervention(resource, patientId) {
|
|
58
|
-
const reasonRef = resource.reason && resource.reason[0] && resource.reason[0].reference;
|
|
59
|
-
const base = {
|
|
60
|
-
interventionId: identifierValue(resource, INTERVENTION_SLUG),
|
|
61
|
-
patientId,
|
|
62
|
-
caseId: refToId(reasonRef && reasonRef.reference),
|
|
63
|
-
rationale: extValues(resource, RATIONALE_EXT)[0] ?? null,
|
|
64
|
-
outcomeNotes: extValues(resource, OUTCOME_NOTES_EXT)[0] ?? null,
|
|
65
|
-
outcomeExt: extValues(resource, OUTCOME_EXT)[0] ?? null,
|
|
66
|
-
recipientKind: 'patient',
|
|
67
|
-
recipientRefId: null,
|
|
68
|
-
};
|
|
69
|
-
if (resource.resourceType === 'Procedure') return reconstructProcedure(resource, base);
|
|
70
|
-
if (resource.resourceType === 'Communication') return reconstructCommunication(resource, base);
|
|
71
|
-
if (resource.resourceType === 'Task') return reconstructTask(resource, base);
|
|
72
|
-
return reconstructCommunicationRequest(resource, base);
|
|
17
|
+
const resources = await store.find({ patient });
|
|
18
|
+
return fromFhir(resources, { patientId })
|
|
19
|
+
.filter((intervention) => !caseId || intervention.caseId === caseId);
|
|
73
20
|
}
|
|
74
21
|
|
|
75
|
-
function reconstructProcedure(resource, base) {
|
|
76
|
-
const { outcomeExt, ...dto } = base;
|
|
77
|
-
return {
|
|
78
|
-
...dto,
|
|
79
|
-
kind: categoryCode(resource),
|
|
80
|
-
interventionLabel: (resource.code && resource.code.text) || null,
|
|
81
|
-
deliveredAt: resource.occurrenceDateTime,
|
|
82
|
-
outcome: outcomeExt ?? codeableConceptCode(resource.outcome),
|
|
83
|
-
...recipientFromExtension(resource),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function reconstructCommunication(resource, base) {
|
|
88
|
-
const { outcomeExt, ...dto } = base;
|
|
89
|
-
const content = resource.payload && resource.payload[0] && resource.payload[0].contentCodeableConcept;
|
|
90
|
-
const outcome = outcomeExt ?? undefined;
|
|
91
|
-
return {
|
|
92
|
-
...dto,
|
|
93
|
-
kind: categoryCode(resource),
|
|
94
|
-
interventionLabel: (content && content.text) || null,
|
|
95
|
-
deliveredAt: resource.sent,
|
|
96
|
-
...(outcome ? { outcome } : {}),
|
|
97
|
-
...recipientFromReference(resource),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function reconstructTask(resource, base) {
|
|
102
|
-
const { outcomeExt, ...dto } = base;
|
|
103
|
-
const output = resource.output && resource.output[0];
|
|
104
|
-
return {
|
|
105
|
-
...dto,
|
|
106
|
-
kind: codingCode(resource.code),
|
|
107
|
-
interventionLabel: (resource.code && resource.code.text) || null,
|
|
108
|
-
deliveredAt: resource.authoredOn,
|
|
109
|
-
outcome: outcomeExt ?? (output ? codeableConceptCode(output.valueCodeableConcept) : 'pending'),
|
|
110
|
-
...recipientFromExtension(resource),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function reconstructCommunicationRequest(resource, base) {
|
|
115
|
-
const { outcomeExt, ...dto } = base;
|
|
116
|
-
const content = resource.payload && resource.payload[0] && resource.payload[0].contentCodeableConcept;
|
|
117
|
-
return {
|
|
118
|
-
...dto,
|
|
119
|
-
kind: categoryCode(resource),
|
|
120
|
-
interventionLabel: (content && content.text) || null,
|
|
121
|
-
deliveredAt: resource.occurrenceDateTime,
|
|
122
|
-
outcome: outcomeExt ?? (resource.status === 'active' ? 'pending' : 'unknown'),
|
|
123
|
-
...recipientFromReference(resource),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function recipientFromExtension(resource) {
|
|
128
|
-
const kind = extValues(resource, RECIPIENT_KIND_EXT)[0];
|
|
129
|
-
const refId = extValues(resource, RECIPIENT_REF_EXT)[0];
|
|
130
|
-
if (kind === 'related_person' && refId) return { recipientKind: 'related_person', recipientRefId: refId };
|
|
131
|
-
return { recipientKind: 'patient', recipientRefId: null };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function recipientFromReference(resource) {
|
|
135
|
-
const recipientRef = resource.recipient && resource.recipient[0] && resource.recipient[0].reference;
|
|
136
|
-
if (recipientRef && recipientRef.startsWith('RelatedPerson/')) {
|
|
137
|
-
return { recipientKind: 'related_person', recipientRefId: refToId(recipientRef) };
|
|
138
|
-
}
|
|
139
|
-
return { recipientKind: 'patient', recipientRefId: null };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const codingCode = (concept) => (concept && concept.coding && concept.coding[0] && concept.coding[0].code) || null;
|
|
143
|
-
const categoryCode = (resource) => codingCode(resource.category && resource.category[0]);
|
|
144
|
-
const codeableConceptCode = (concept) => codingCode(concept) || (concept && concept.text) || 'pending';
|
|
145
|
-
|
|
146
22
|
module.exports = {
|
|
147
23
|
storeIntervention,
|
|
148
24
|
readInterventions,
|
package/package.json
CHANGED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const { extension } = require('../helpers/elementHelper');
|
|
2
|
-
|
|
3
|
-
const RATIONALE_EXT = 'intervention-rationale';
|
|
4
|
-
const OUTCOME_NOTES_EXT = 'intervention-outcome-notes';
|
|
5
|
-
const OUTCOME_EXT = 'intervention-outcome';
|
|
6
|
-
const RECIPIENT_KIND_EXT = 'intervention-recipient-kind';
|
|
7
|
-
const RECIPIENT_REF_EXT = 'intervention-recipient-ref';
|
|
8
|
-
|
|
9
|
-
function interventionExtensions(intervention, { withRecipient = false } = {}) {
|
|
10
|
-
const out = [];
|
|
11
|
-
if (intervention.rationale) out.push(extension(RATIONALE_EXT, { valueString: intervention.rationale }));
|
|
12
|
-
if (intervention.outcomeNotes) out.push(extension(OUTCOME_NOTES_EXT, { valueString: intervention.outcomeNotes }));
|
|
13
|
-
if (intervention.outcome) out.push(extension(OUTCOME_EXT, { valueString: intervention.outcome }));
|
|
14
|
-
if (withRecipient && intervention.recipientKind === 'related_person' && intervention.recipientRefId) {
|
|
15
|
-
out.push(extension(RECIPIENT_KIND_EXT, { valueString: 'related_person' }));
|
|
16
|
-
out.push(extension(RECIPIENT_REF_EXT, { valueString: intervention.recipientRefId }));
|
|
17
|
-
}
|
|
18
|
-
return out;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = {
|
|
22
|
-
interventionExtensions,
|
|
23
|
-
RATIONALE_EXT,
|
|
24
|
-
OUTCOME_NOTES_EXT,
|
|
25
|
-
OUTCOME_EXT,
|
|
26
|
-
RECIPIENT_KIND_EXT,
|
|
27
|
-
RECIPIENT_REF_EXT,
|
|
28
|
-
};
|