@peopl-health/nexus 5.11.0-dev.1119 → 5.11.0-dev.1122
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/clinical/tools/analyzeSymptomPatternsTool.js +3 -6
- package/lib/clinical/tools/getActiveSymptomLandscapeTool.js +2 -2
- package/lib/clinical/tools/getPatientHistoryTool.js +1 -1
- package/lib/clinical/tools/openClusterHypothesisTool.js +3 -7
- package/lib/clinical/tools/openConditionTool.js +2 -2
- package/lib/clinical/tools/recordClinicalImpressionTool.js +1 -2
- package/lib/clinical/tools/recordInterventionTool.js +3 -5
- package/lib/clinical/tools/setContingencyPlanTool.js +3 -7
- package/lib/clinical/tools/submitSafetyGateTool.js +1 -5
- package/lib/clinical/tools/updateClusterHypothesisTool.js +4 -7
- package/lib/clinical/tools/updateConditionStatusTool.js +1 -2
- package/lib/fhir/constants/extensionSlugs.js +111 -0
- package/lib/fhir/resources/CarePlan.js +8 -7
- package/lib/fhir/resources/ClinicalImpression.js +39 -38
- package/lib/fhir/resources/CommunicationRequest.js +5 -4
- package/lib/fhir/resources/Condition.js +10 -9
- package/lib/fhir/resources/Observation.js +7 -6
- package/lib/fhir/resources/RiskAssessment.js +13 -12
- package/lib/fhir/resources/Task.js +4 -3
- package/lib/fhir/services/clusterService.js +9 -8
- package/lib/fhir/services/contingencyService.js +14 -11
- package/lib/fhir/services/palliativeService.js +23 -22
- package/lib/fhir/services/riskService.js +11 -10
- package/lib/fhir/services/routingService.js +10 -9
- package/lib/fhir/services/symptomCaseService.js +21 -20
- package/lib/shared/dtos/ClusterImpression.js +16 -5
- package/lib/shared/dtos/ContingencySafetyNet.js +3 -1
- package/lib/shared/dtos/Intervention.js +10 -3
- package/lib/shared/dtos/ManagedSymptom.js +15 -5
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ const { getSystems } = require('../config/fhirConfig');
|
|
|
2
2
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
3
3
|
const { identifier, reference, patientReference, codeableConcept, extension, toIso } = require('../helpers/elementHelper');
|
|
4
4
|
const { SYMPTOM_GRADE_SLUG } = require('../constants/projectionSlugs');
|
|
5
|
+
const { GRADE_EXT } = require('../constants/extensionSlugs');
|
|
5
6
|
|
|
6
7
|
const OBSERVED_FIELD_SYSTEM_KEY = 'observedField';
|
|
7
8
|
|
|
@@ -61,7 +62,7 @@ class Observation {
|
|
|
61
62
|
valueCodeableConcept: gradeConcept,
|
|
62
63
|
method,
|
|
63
64
|
extension: [
|
|
64
|
-
extension(
|
|
65
|
+
extension(GRADE_EXT.VERIFICATION, { valueCode: 'provisional' }),
|
|
65
66
|
extension('grade-derivation', { valueCode: 'pro-ctcae-composite' }),
|
|
66
67
|
extension('symptom-source', { valueString: String(sym.source || 'triage') }),
|
|
67
68
|
],
|
|
@@ -77,11 +78,11 @@ class Observation {
|
|
|
77
78
|
const grade = assessment.grade;
|
|
78
79
|
const gradeId = `${assessment.assessmentId}-grade`;
|
|
79
80
|
const extensions = [];
|
|
80
|
-
if (grade.confidence !== null) extensions.push(extension(
|
|
81
|
-
if (grade.possibleRange !== null) extensions.push(extension(
|
|
82
|
-
if (grade.reasoning) extensions.push(extension(
|
|
83
|
-
if (assessment.gradeVerification) extensions.push(extension(
|
|
84
|
-
if (assessment.gradeThresholdClause) extensions.push(extension(
|
|
81
|
+
if (grade.confidence !== null) extensions.push(extension(GRADE_EXT.CONFIDENCE, { valueString: grade.confidence }));
|
|
82
|
+
if (grade.possibleRange !== null) extensions.push(extension(GRADE_EXT.RANGE, { valueString: `${grade.possibleRange[0]}-${grade.possibleRange[1]}` }));
|
|
83
|
+
if (grade.reasoning) extensions.push(extension(GRADE_EXT.REASONING, { valueString: grade.reasoning }));
|
|
84
|
+
if (assessment.gradeVerification) extensions.push(extension(GRADE_EXT.VERIFICATION, { valueString: assessment.gradeVerification }));
|
|
85
|
+
if (assessment.gradeThresholdClause) extensions.push(extension(GRADE_EXT.THRESHOLD_CLAUSE, { valueString: assessment.gradeThresholdClause }));
|
|
85
86
|
const payload = {
|
|
86
87
|
resourceType: 'Observation',
|
|
87
88
|
id: fhirId(gradeId),
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
toIso,
|
|
10
10
|
} = require('../helpers/elementHelper');
|
|
11
11
|
const { PATIENT_RISK_SLUG, SAFETY_GATE_SLUG } = require('../constants/projectionSlugs');
|
|
12
|
+
const { RISK_EXT } = require('../constants/extensionSlugs');
|
|
12
13
|
|
|
13
14
|
const RISK_SEVERITY_CODESYSTEM = 'risk-severity';
|
|
14
15
|
const PREDICTION_KIND_AE = 'ae';
|
|
@@ -39,11 +40,11 @@ function aePrediction(ae) {
|
|
|
39
40
|
code: ae.severityIfConfirmed,
|
|
40
41
|
system: codeSystemUrl(RISK_SEVERITY_CODESYSTEM),
|
|
41
42
|
}),
|
|
42
|
-
extension: [extension(
|
|
43
|
+
extension: [extension(RISK_EXT.PREDICTION_KIND, { valueString: PREDICTION_KIND_AE })],
|
|
43
44
|
};
|
|
44
45
|
if (ae.why) prediction.rationale = ae.why;
|
|
45
46
|
for (const symptom of ae.minimumSymptomSet) {
|
|
46
|
-
prediction.extension.push(extension(
|
|
47
|
+
prediction.extension.push(extension(RISK_EXT.MINIMUM_SYMPTOM_SET, { valueString: symptom }));
|
|
47
48
|
}
|
|
48
49
|
return prediction;
|
|
49
50
|
}
|
|
@@ -51,16 +52,16 @@ function aePrediction(ae) {
|
|
|
51
52
|
function diseasePrediction(risk) {
|
|
52
53
|
const prediction = {
|
|
53
54
|
outcome: codeableConcept(risk.bodySite),
|
|
54
|
-
extension: [extension(
|
|
55
|
+
extension: [extension(RISK_EXT.PREDICTION_KIND, { valueString: PREDICTION_KIND_DISEASE })],
|
|
55
56
|
};
|
|
56
57
|
for (const complication of risk.complications) {
|
|
57
|
-
prediction.extension.push(extension(
|
|
58
|
+
prediction.extension.push(extension(RISK_EXT.DISEASE_COMPLICATION, { valueString: complication }));
|
|
58
59
|
}
|
|
59
60
|
for (const symptom of risk.warningSymptoms) {
|
|
60
|
-
prediction.extension.push(extension(
|
|
61
|
+
prediction.extension.push(extension(RISK_EXT.DISEASE_WARNING_SYMPTOM, { valueString: symptom }));
|
|
61
62
|
}
|
|
62
63
|
for (const comorbidity of risk.amplifierComorbidities) {
|
|
63
|
-
prediction.extension.push(extension(
|
|
64
|
+
prediction.extension.push(extension(RISK_EXT.DISEASE_AMPLIFIER, { valueString: comorbidity }));
|
|
64
65
|
}
|
|
65
66
|
return prediction;
|
|
66
67
|
}
|
|
@@ -69,10 +70,10 @@ function comorbidityPrediction(amplifier) {
|
|
|
69
70
|
const prediction = {
|
|
70
71
|
outcome: codeableConcept(amplifier.comorbidity),
|
|
71
72
|
rationale: amplifier.interaction,
|
|
72
|
-
extension: [extension(
|
|
73
|
+
extension: [extension(RISK_EXT.PREDICTION_KIND, { valueString: PREDICTION_KIND_COMORBIDITY })],
|
|
73
74
|
};
|
|
74
75
|
if (amplifier.clinicalNote) {
|
|
75
|
-
prediction.extension.push(extension(
|
|
76
|
+
prediction.extension.push(extension(RISK_EXT.COMORBIDITY_CLINICAL_NOTE, { valueString: amplifier.clinicalNote }));
|
|
76
77
|
}
|
|
77
78
|
return prediction;
|
|
78
79
|
}
|
|
@@ -99,10 +100,10 @@ class RiskAssessment {
|
|
|
99
100
|
];
|
|
100
101
|
if (prediction.length) payload.prediction = prediction;
|
|
101
102
|
const extensions = [];
|
|
102
|
-
if (assessment.sourceRegimen) extensions.push(extension(
|
|
103
|
-
if (assessment.sourceDiagnosis) extensions.push(extension(
|
|
104
|
-
if (assessment.sourcePhase) extensions.push(extension(
|
|
105
|
-
if (assessment.assessedByModel) extensions.push(extension(
|
|
103
|
+
if (assessment.sourceRegimen) extensions.push(extension(RISK_EXT.SOURCE_REGIMEN, { valueString: assessment.sourceRegimen }));
|
|
104
|
+
if (assessment.sourceDiagnosis) extensions.push(extension(RISK_EXT.SOURCE_DIAGNOSIS, { valueString: assessment.sourceDiagnosis }));
|
|
105
|
+
if (assessment.sourcePhase) extensions.push(extension(RISK_EXT.SOURCE_PHASE, { valueString: assessment.sourcePhase }));
|
|
106
|
+
if (assessment.assessedByModel) extensions.push(extension(RISK_EXT.ASSESSED_BY_MODEL, { valueString: assessment.assessedByModel }));
|
|
106
107
|
if (extensions.length) payload.extension = extensions;
|
|
107
108
|
return new RiskAssessment(payload);
|
|
108
109
|
}
|
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
PALLIATIVE_ESCALATION_SLUG,
|
|
21
21
|
PALLIATIVE_CAREPLAN_REC_SLUG,
|
|
22
22
|
} = require('../constants/projectionSlugs');
|
|
23
|
+
const { PALLIATIVE_EXT } = require('../constants/extensionSlugs');
|
|
23
24
|
const { SEVERITY_TO_PRIORITY } = require('../constants/severityPriority');
|
|
24
25
|
const { interventionExtensions } = require('./interventionExtensions');
|
|
25
26
|
|
|
@@ -133,7 +134,7 @@ class Task {
|
|
|
133
134
|
authoredOn: toIso(committedAt),
|
|
134
135
|
requester: reference('Device', deviceId),
|
|
135
136
|
restriction: { period: { end: toIso(end) } },
|
|
136
|
-
extension: [extension(
|
|
137
|
+
extension: [extension(PALLIATIVE_EXT.ESCALATION_RANK, { valueInteger: finding.rank })],
|
|
137
138
|
};
|
|
138
139
|
if (escalation.escalationRoutes.length) {
|
|
139
140
|
payload.requestedPerformer = escalation.escalationRoutes.map((route) => ({ concept: codeableConcept(route) }));
|
|
@@ -158,10 +159,10 @@ class Task {
|
|
|
158
159
|
for: patientReference(patientId),
|
|
159
160
|
authoredOn: toIso(committedAt),
|
|
160
161
|
requester: reference('Device', deviceId),
|
|
161
|
-
extension: [extension(
|
|
162
|
+
extension: [extension(PALLIATIVE_EXT.CAREPLAN_REC_RANK, { valueInteger: recommendation.rank })],
|
|
162
163
|
};
|
|
163
164
|
if (recommendation.otcOffer && recommendation.otcOffer.label) {
|
|
164
|
-
payload.extension.push(extension(
|
|
165
|
+
payload.extension.push(extension(PALLIATIVE_EXT.CAREPLAN_OTC_OFFER, { valueString: JSON.stringify(recommendation.otcOffer) }));
|
|
165
166
|
}
|
|
166
167
|
if (recommendation.mainRecommendation) payload.description = recommendation.mainRecommendation;
|
|
167
168
|
const noteTexts = [];
|
|
@@ -4,6 +4,7 @@ const { fhirId, identifierSystem } = require('../helpers/fhirHelper');
|
|
|
4
4
|
const { extMap, identifierValue, refToId } = require('../helpers/fhirReadHelper');
|
|
5
5
|
const { CLUSTER_IMPRESSION_SLUG, CLUSTER_LABEL_SLUG } = require('../constants/projectionSlugs');
|
|
6
6
|
const { CLUSTER_LIST_SEPARATOR } = require('../resources/ClinicalImpression');
|
|
7
|
+
const { CLUSTER_EXT } = require('../constants/extensionSlugs');
|
|
7
8
|
const { PROJECTOR_NAME } = require('../projections/clusterProjection');
|
|
8
9
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
9
10
|
const { ClusterImpression } = require('../../shared/dtos/ClusterImpression');
|
|
@@ -54,19 +55,19 @@ function reconstructCluster(resource, patientId) {
|
|
|
54
55
|
clusterId: identifierValue(resource, CLUSTER_IMPRESSION_SLUG),
|
|
55
56
|
patientId,
|
|
56
57
|
label: identifierValue(resource, CLUSTER_LABEL_SLUG) || '',
|
|
57
|
-
hypothesisType: ext.
|
|
58
|
-
status: ext[
|
|
59
|
-
confidence: ext.
|
|
60
|
-
clinicalSignificance: ext.
|
|
58
|
+
hypothesisType: ext[CLUSTER_EXT.HYPOTHESIS_TYPE],
|
|
59
|
+
status: ext[CLUSTER_EXT.STATUS],
|
|
60
|
+
confidence: ext[CLUSTER_EXT.CONFIDENCE],
|
|
61
|
+
clinicalSignificance: ext[CLUSTER_EXT.CLINICAL_SIGNIFICANCE],
|
|
61
62
|
memberCaseIds: (resource.problem || []).map((problemRef) => refToId(problemRef.reference)).filter(Boolean),
|
|
62
|
-
consultationId: ext[
|
|
63
|
+
consultationId: ext[CLUSTER_EXT.CONSULTATION_ID] || null,
|
|
63
64
|
relationshipModel: resource.summary || '',
|
|
64
|
-
strengthensIf: ext.
|
|
65
|
-
weakensIf: ext.
|
|
65
|
+
strengthensIf: ext[CLUSTER_EXT.STRENGTHENS_IF] ? ext[CLUSTER_EXT.STRENGTHENS_IF].split(CLUSTER_LIST_SEPARATOR) : [],
|
|
66
|
+
weakensIf: ext[CLUSTER_EXT.WEAKENS_IF] ? ext[CLUSTER_EXT.WEAKENS_IF].split(CLUSTER_LIST_SEPARATOR) : [],
|
|
66
67
|
openingReasoning: resource.description || '',
|
|
67
68
|
openedAt: resource.effectiveDateTime || period.start,
|
|
68
69
|
dissolvedAt: period.end || null,
|
|
69
|
-
dissolveReason: ext[
|
|
70
|
+
dissolveReason: ext[CLUSTER_EXT.DISSOLVE_REASON] || null,
|
|
70
71
|
dissolveCategory: (resource.statusReason && resource.statusReason.text) || null,
|
|
71
72
|
};
|
|
72
73
|
}
|
|
@@ -6,10 +6,11 @@ const {
|
|
|
6
6
|
CONTINGENCY_PLAN_SLUG,
|
|
7
7
|
CONTINGENCY_STEP_SLUG,
|
|
8
8
|
} = require('../constants/projectionSlugs');
|
|
9
|
+
const { CONTINGENCY_EXT, CONTINGENCY_CATEGORY_PREFIX } = require('../constants/extensionSlugs');
|
|
9
10
|
const { CONTINGENCY_STATUS_TO_FHIR } = require('../resources/CarePlan');
|
|
10
11
|
const { PROJECTOR_NAME } = require('../projections/contingencyProjection');
|
|
11
12
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
12
|
-
const { ContingencySafetyNet } = require('../../shared/dtos/ContingencySafetyNet');
|
|
13
|
+
const { ContingencySafetyNet, RECIPIENTS } = require('../../shared/dtos/ContingencySafetyNet');
|
|
13
14
|
const { logger } = require('../../utils/logger');
|
|
14
15
|
const { project, storeResources } = require('./fhirService');
|
|
15
16
|
|
|
@@ -69,13 +70,13 @@ function reconstructContingency(plan, requests, patientId) {
|
|
|
69
70
|
relatedCaseId: refToId(address && address.reference),
|
|
70
71
|
relatedClusterId: refToId(plan.supportingInfo && plan.supportingInfo[0] && plan.supportingInfo[0].reference),
|
|
71
72
|
concern: plan.description || '',
|
|
72
|
-
awaitingCharacterization: ext[
|
|
73
|
-
whyThisMatters: ext[
|
|
74
|
-
creationReasoning: ext[
|
|
75
|
-
status: ext[
|
|
73
|
+
awaitingCharacterization: ext[CONTINGENCY_EXT.AWAITING_CHARACTERIZATION] || '',
|
|
74
|
+
whyThisMatters: ext[CONTINGENCY_EXT.WHY_THIS_MATTERS] || '',
|
|
75
|
+
creationReasoning: ext[CONTINGENCY_EXT.CREATION_REASONING] || '',
|
|
76
|
+
status: ext[CONTINGENCY_EXT.STATUS] || FHIR_TO_CONTINGENCY_STATUS[plan.status] || 'armed',
|
|
76
77
|
createdAt: plan.created,
|
|
77
78
|
resolvedAt: (plan.period && plan.period.end) || null,
|
|
78
|
-
resolvedReason: ext[
|
|
79
|
+
resolvedReason: ext[CONTINGENCY_EXT.RESOLVED_REASON] || null,
|
|
79
80
|
},
|
|
80
81
|
teamEscalation,
|
|
81
82
|
patientReminder,
|
|
@@ -84,21 +85,23 @@ function reconstructContingency(plan, requests, patientId) {
|
|
|
84
85
|
|
|
85
86
|
function reconstructStep(resource) {
|
|
86
87
|
const ext = extMap(resource);
|
|
87
|
-
const silence = ext[
|
|
88
|
+
const silence = ext[CONTINGENCY_EXT.FIRES_AFTER_SILENCE_MINUTES];
|
|
88
89
|
const firstPayload = resource.payload && resource.payload[0];
|
|
89
90
|
return {
|
|
90
91
|
recipient: recipientOf(resource),
|
|
91
92
|
message: (firstPayload && firstPayload.contentCodeableConcept && firstPayload.contentCodeableConcept.text) || null,
|
|
92
|
-
priority: ext[
|
|
93
|
-
actionRequested: ext[
|
|
93
|
+
priority: ext[CONTINGENCY_EXT.STEP_PRIORITY] || resource.priority || null,
|
|
94
|
+
actionRequested: ext[CONTINGENCY_EXT.ACTION_REQUESTED] || null,
|
|
94
95
|
firesAfterSilenceMinutes: typeof silence === 'number' ? silence : null,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
function recipientOf(resource) {
|
|
99
100
|
const category = resource.category && resource.category[0] && resource.category[0].text;
|
|
100
|
-
if (category
|
|
101
|
-
|
|
101
|
+
if (category && category.startsWith(CONTINGENCY_CATEGORY_PREFIX)) {
|
|
102
|
+
const recipient = category.slice(CONTINGENCY_CATEGORY_PREFIX.length);
|
|
103
|
+
if (RECIPIENTS.includes(recipient)) return recipient;
|
|
104
|
+
}
|
|
102
105
|
const ref = resource.recipient && resource.recipient[0] && resource.recipient[0].reference;
|
|
103
106
|
return ref && ref.startsWith('Organization/') ? 'care_team' : 'patient';
|
|
104
107
|
}
|
|
@@ -3,6 +3,7 @@ const { ZodError } = require('zod');
|
|
|
3
3
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
4
4
|
const { extMap, extValues, identifierValue } = require('../helpers/fhirReadHelper');
|
|
5
5
|
const { PALLIATIVE_ASSESSMENT_SLUG, PALLIATIVE_ESCALATION_SLUG, PALLIATIVE_CAREPLAN_SLUG } = require('../constants/projectionSlugs');
|
|
6
|
+
const { PALLIATIVE_EXT } = require('../constants/extensionSlugs');
|
|
6
7
|
const { PROJECTOR_NAME } = require('../projections/palliativeProjection');
|
|
7
8
|
const { PROJECTOR_NAME: CAREPLAN_PROJECTOR_NAME } = require('../projections/palliativeCarePlanProjection');
|
|
8
9
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
@@ -46,7 +47,7 @@ async function readPalliativeCarePlanByTurn({ patientId, turnId }) {
|
|
|
46
47
|
const patient = `Patient/${fhirId(patientId)}`;
|
|
47
48
|
const plans = (await store.find({ patient, resourceType: 'CarePlan' }))
|
|
48
49
|
.filter((resource) => identifierValue(resource, PALLIATIVE_CAREPLAN_SLUG));
|
|
49
|
-
const plan = plans.find((resource) => extMap(resource)[
|
|
50
|
+
const plan = plans.find((resource) => extMap(resource)[PALLIATIVE_EXT.TURN_ID] === turnId) || null;
|
|
50
51
|
if (!plan) return null;
|
|
51
52
|
const careplanId = identifierValue(plan, PALLIATIVE_CAREPLAN_SLUG);
|
|
52
53
|
const provenance = await store.getByReference(`Provenance/${fhirId(`${careplanId}-prov`)}`);
|
|
@@ -56,7 +57,7 @@ async function readPalliativeCarePlanByTurn({ patientId, turnId }) {
|
|
|
56
57
|
|
|
57
58
|
async function reconstructCarePlan(store, plan, patientId, turnId) {
|
|
58
59
|
const careplanId = identifierValue(plan, PALLIATIVE_CAREPLAN_SLUG);
|
|
59
|
-
const assessmentId = extMap(plan)[
|
|
60
|
+
const assessmentId = extMap(plan)[PALLIATIVE_EXT.ASSESSMENT_ID];
|
|
60
61
|
const taskRefs = (plan.activity || [])
|
|
61
62
|
.map((activity) => activity.plannedActivityReference && activity.plannedActivityReference.reference)
|
|
62
63
|
.filter(Boolean);
|
|
@@ -98,12 +99,12 @@ function reconstructRecommendation(task) {
|
|
|
98
99
|
const ext = extMap(task);
|
|
99
100
|
const ctcaeTerms = ((task.code && task.code.coding) || []).map((coding) => coding.code).filter(Boolean);
|
|
100
101
|
return {
|
|
101
|
-
rank: ext[
|
|
102
|
+
rank: ext[PALLIATIVE_EXT.CAREPLAN_REC_RANK],
|
|
102
103
|
elementNameShort: (task.code && task.code.text) || 'recommendation',
|
|
103
104
|
isPharmacologicalMedNeeded: medsText === undefined ? null : medsText === 'true',
|
|
104
105
|
ctcaeTerms,
|
|
105
106
|
mainRecommendation: task.description || null,
|
|
106
|
-
otcOffer: parseOtcOffer(ext[
|
|
107
|
+
otcOffer: parseOtcOffer(ext[PALLIATIVE_EXT.CAREPLAN_OTC_OFFER]),
|
|
107
108
|
whenToEscalate: noteMap.when_to_escalate || null,
|
|
108
109
|
expectedEffectiveness: noteMap.expected_effectiveness || null,
|
|
109
110
|
followup,
|
|
@@ -124,7 +125,7 @@ async function findImpression(store, patientId, turnId) {
|
|
|
124
125
|
const patient = `Patient/${fhirId(patientId)}`;
|
|
125
126
|
const impressions = (await store.find({ patient, resourceType: 'ClinicalImpression' }))
|
|
126
127
|
.filter((resource) => identifierValue(resource, PALLIATIVE_ASSESSMENT_SLUG));
|
|
127
|
-
return impressions.find((impression) => extMap(impression)[
|
|
128
|
+
return impressions.find((impression) => extMap(impression)[PALLIATIVE_EXT.TURN_ID] === turnId) || null;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
async function reconstruct(store, impression, provenance, patientId, turnId) {
|
|
@@ -158,7 +159,7 @@ async function readEscalations(store, provenance, patientId) {
|
|
|
158
159
|
for (const ref of taskRefs) {
|
|
159
160
|
const task = await store.getByReference(ref);
|
|
160
161
|
if (!task) continue;
|
|
161
|
-
const rank = extMap(task)[
|
|
162
|
+
const rank = extMap(task)[PALLIATIVE_EXT.ESCALATION_RANK];
|
|
162
163
|
if (rank === undefined || rank === null) continue;
|
|
163
164
|
byRank[rank] = reconstructEscalation(task, patientId);
|
|
164
165
|
}
|
|
@@ -168,25 +169,25 @@ async function readEscalations(store, provenance, patientId) {
|
|
|
168
169
|
function reconstructFinding(finding, escalations) {
|
|
169
170
|
const ext = extMap(finding);
|
|
170
171
|
const nameShort = ccText(finding.item && finding.item.concept);
|
|
171
|
-
const rank = ext[
|
|
172
|
+
const rank = ext[PALLIATIVE_EXT.FINDING_RANK];
|
|
172
173
|
return {
|
|
173
174
|
rank,
|
|
174
|
-
elementType: ext[
|
|
175
|
+
elementType: ext[PALLIATIVE_EXT.FINDING_ELEMENT_TYPE],
|
|
175
176
|
nameShort,
|
|
176
|
-
timeWindow: ext[
|
|
177
|
-
symptomTerms: extValues(finding,
|
|
178
|
-
clinicalDescription: ext[
|
|
179
|
-
whyRelevant: ext[
|
|
180
|
-
qolImpact: ext[
|
|
181
|
-
qolCriteria: ext[
|
|
182
|
-
improvementCapacity: ext[
|
|
183
|
-
improvementCriteria: ext[
|
|
184
|
-
areMedsNeeded: ext[
|
|
185
|
-
needsCharacterization: ext[
|
|
186
|
-
characterizationCriteria: ext[
|
|
187
|
-
probeQuestions: extValues(finding,
|
|
188
|
-
auditNotes: ext[
|
|
189
|
-
clusterNote: ext[
|
|
177
|
+
timeWindow: ext[PALLIATIVE_EXT.FINDING_TIME_WINDOW] || '',
|
|
178
|
+
symptomTerms: extValues(finding, PALLIATIVE_EXT.FINDING_SYMPTOM_TERM),
|
|
179
|
+
clinicalDescription: ext[PALLIATIVE_EXT.FINDING_CLINICAL_DESCRIPTION] || '',
|
|
180
|
+
whyRelevant: ext[PALLIATIVE_EXT.FINDING_WHY_RELEVANT] || '',
|
|
181
|
+
qolImpact: ext[PALLIATIVE_EXT.FINDING_QOL_IMPACT] || null,
|
|
182
|
+
qolCriteria: ext[PALLIATIVE_EXT.FINDING_QOL_CRITERIA] || '',
|
|
183
|
+
improvementCapacity: ext[PALLIATIVE_EXT.FINDING_IMPROVEMENT_CAPACITY] || null,
|
|
184
|
+
improvementCriteria: ext[PALLIATIVE_EXT.FINDING_IMPROVEMENT_CRITERIA] || '',
|
|
185
|
+
areMedsNeeded: ext[PALLIATIVE_EXT.FINDING_ARE_MEDS_NEEDED] ?? null,
|
|
186
|
+
needsCharacterization: ext[PALLIATIVE_EXT.FINDING_NEEDS_CHARACTERIZATION] ?? null,
|
|
187
|
+
characterizationCriteria: ext[PALLIATIVE_EXT.FINDING_CHARACTERIZATION_CRITERIA] || '',
|
|
188
|
+
probeQuestions: extValues(finding, PALLIATIVE_EXT.FINDING_PROBE_QUESTION),
|
|
189
|
+
auditNotes: ext[PALLIATIVE_EXT.FINDING_AUDIT_NOTES] || '',
|
|
190
|
+
clusterNote: ext[PALLIATIVE_EXT.FINDING_CLUSTER_NOTE] ?? null,
|
|
190
191
|
escalation: escalations[rank] || null,
|
|
191
192
|
};
|
|
192
193
|
}
|
|
@@ -3,6 +3,7 @@ const { ZodError } = require('zod');
|
|
|
3
3
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
4
4
|
const { identifierValue, extValues } = require('../helpers/fhirReadHelper');
|
|
5
5
|
const { PATIENT_RISK_SLUG } = require('../constants/projectionSlugs');
|
|
6
|
+
const { RISK_EXT } = require('../constants/extensionSlugs');
|
|
6
7
|
const { PROJECTOR_NAME } = require('../projections/riskProjection');
|
|
7
8
|
const { PROJECTOR_NAME: SAFETY_GATE_PROJECTOR_NAME } = require('../projections/safetyGateProjection');
|
|
8
9
|
const {
|
|
@@ -67,14 +68,14 @@ function reconstructRisk(resource, patientId) {
|
|
|
67
68
|
assessmentId: identifierValue(resource, PATIENT_RISK_SLUG),
|
|
68
69
|
patientId,
|
|
69
70
|
status: resource.status,
|
|
70
|
-
sourceRegimen: extValues(resource,
|
|
71
|
-
sourceDiagnosis: extValues(resource,
|
|
72
|
-
sourcePhase: extValues(resource,
|
|
71
|
+
sourceRegimen: extValues(resource, RISK_EXT.SOURCE_REGIMEN)[0] || null,
|
|
72
|
+
sourceDiagnosis: extValues(resource, RISK_EXT.SOURCE_DIAGNOSIS)[0] || null,
|
|
73
|
+
sourcePhase: extValues(resource, RISK_EXT.SOURCE_PHASE)[0] || null,
|
|
73
74
|
predictedAes: predictions.filter((prediction) => predictionKind(prediction) === PREDICTION_KIND_AE).map(reconstructAe),
|
|
74
75
|
diseaseRisks: predictions.filter((prediction) => predictionKind(prediction) === PREDICTION_KIND_DISEASE).map(reconstructDisease),
|
|
75
76
|
comorbidityAmplifiers: predictions.filter((prediction) => predictionKind(prediction) === PREDICTION_KIND_COMORBIDITY).map(reconstructComorbidity),
|
|
76
77
|
assessedAt: resource.occurrenceDateTime,
|
|
77
|
-
assessedByModel: extValues(resource,
|
|
78
|
+
assessedByModel: extValues(resource, RISK_EXT.ASSESSED_BY_MODEL)[0] || null,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -86,7 +87,7 @@ function reconstructAe(prediction) {
|
|
|
86
87
|
&& prediction.qualitativeRisk.coding
|
|
87
88
|
&& prediction.qualitativeRisk.coding[0]
|
|
88
89
|
&& prediction.qualitativeRisk.coding[0].code) || null,
|
|
89
|
-
minimumSymptomSet: extValues(prediction,
|
|
90
|
+
minimumSymptomSet: extValues(prediction, RISK_EXT.MINIMUM_SYMPTOM_SET),
|
|
90
91
|
why: prediction.rationale || null,
|
|
91
92
|
};
|
|
92
93
|
}
|
|
@@ -94,9 +95,9 @@ function reconstructAe(prediction) {
|
|
|
94
95
|
function reconstructDisease(prediction) {
|
|
95
96
|
return {
|
|
96
97
|
bodySite: outcomeText(prediction),
|
|
97
|
-
complications: extValues(prediction,
|
|
98
|
-
warningSymptoms: extValues(prediction,
|
|
99
|
-
amplifierComorbidities: extValues(prediction,
|
|
98
|
+
complications: extValues(prediction, RISK_EXT.DISEASE_COMPLICATION),
|
|
99
|
+
warningSymptoms: extValues(prediction, RISK_EXT.DISEASE_WARNING_SYMPTOM),
|
|
100
|
+
amplifierComorbidities: extValues(prediction, RISK_EXT.DISEASE_AMPLIFIER),
|
|
100
101
|
};
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -104,11 +105,11 @@ function reconstructComorbidity(prediction) {
|
|
|
104
105
|
return {
|
|
105
106
|
comorbidity: outcomeText(prediction),
|
|
106
107
|
interaction: prediction.rationale,
|
|
107
|
-
clinicalNote: extValues(prediction,
|
|
108
|
+
clinicalNote: extValues(prediction, RISK_EXT.COMORBIDITY_CLINICAL_NOTE)[0] || null,
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
const predictionKind = (prediction) => extValues(prediction,
|
|
112
|
+
const predictionKind = (prediction) => extValues(prediction, RISK_EXT.PREDICTION_KIND)[0] || null;
|
|
112
113
|
|
|
113
114
|
const outcomeText = (prediction) => (prediction.outcome && prediction.outcome.text) || null;
|
|
114
115
|
|
|
@@ -3,6 +3,7 @@ const { ZodError } = require('zod');
|
|
|
3
3
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
4
4
|
const { extMap, extValues, identifierValue, refToId } = require('../helpers/fhirReadHelper');
|
|
5
5
|
const { ROUTING_ASSESSMENT_SLUG, ROUTING_DISPOSITION_SLUG } = require('../constants/projectionSlugs');
|
|
6
|
+
const { ROUTING_EXT } = require('../constants/extensionSlugs');
|
|
6
7
|
const { PROJECTOR_NAME } = require('../projections/routingProjection');
|
|
7
8
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
8
9
|
const { RoutingDecision } = require('../../shared/dtos/RoutingDecision');
|
|
@@ -32,7 +33,7 @@ async function readRoutingByTurn({ patientId, turnId }) {
|
|
|
32
33
|
const patient = `Patient/${fhirId(patientId)}`;
|
|
33
34
|
const impressions = (await store.find({ patient, resourceType: 'ClinicalImpression' }))
|
|
34
35
|
.filter((resource) => identifierValue(resource, ROUTING_ASSESSMENT_SLUG));
|
|
35
|
-
const assessment = impressions.find((impression) => extMap(impression)[
|
|
36
|
+
const assessment = impressions.find((impression) => extMap(impression)[ROUTING_EXT.TURN_ID] === turnId);
|
|
36
37
|
if (!assessment) return null;
|
|
37
38
|
const provenance = await store.getByReference(`Provenance/${fhirId(`${assessment.id}-prov`)}`);
|
|
38
39
|
if (!provenance) return null;
|
|
@@ -62,10 +63,10 @@ function reconstructRouting(assessment, disposition, provenance, patientId, turn
|
|
|
62
63
|
const agent = (provenance.agent || [])[0] || {};
|
|
63
64
|
return {
|
|
64
65
|
schemaVersion: '1',
|
|
65
|
-
decisionId: ext[
|
|
66
|
+
decisionId: ext[ROUTING_EXT.DECISION_ID],
|
|
66
67
|
patientId,
|
|
67
68
|
turnId,
|
|
68
|
-
committedAt: ext[
|
|
69
|
+
committedAt: ext[ROUTING_EXT.COMMITTED_AT],
|
|
69
70
|
assessment: reconstructAssessment(assessment, patientId, turnId),
|
|
70
71
|
disposition: reconstructDisposition(disposition, assessment, patientId),
|
|
71
72
|
provenance: {
|
|
@@ -92,9 +93,9 @@ function reconstructAssessment(resource, patientId, turnId) {
|
|
|
92
93
|
function reconstructFinding(finding) {
|
|
93
94
|
const ext = extMap(finding);
|
|
94
95
|
return {
|
|
95
|
-
caseId: ext[
|
|
96
|
-
clinicalReasoning: ext[
|
|
97
|
-
decisionAction: ext[
|
|
96
|
+
caseId: ext[ROUTING_EXT.FINDING_CASE_ID] || refToId(finding.item && finding.item.reference),
|
|
97
|
+
clinicalReasoning: ext[ROUTING_EXT.FINDING_REASONING] || '',
|
|
98
|
+
decisionAction: ext[ROUTING_EXT.FINDING_ACTION] || '',
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -105,7 +106,7 @@ function reconstructDisposition(resource, assessment, patientId) {
|
|
|
105
106
|
kind: DISPOSITION_KIND_BY_RESOURCE[resource.resourceType],
|
|
106
107
|
primaryAction: dispositionAction(resource),
|
|
107
108
|
primaryReason: dispositionReason(resource),
|
|
108
|
-
primaryCaseId: extMap(assessment)[
|
|
109
|
+
primaryCaseId: extMap(assessment)[ROUTING_EXT.PRIMARY_CASE_ID] || null,
|
|
109
110
|
secondaryActions: (resource.action || []).slice(1).map(reconstructSecondary),
|
|
110
111
|
deferredItems: reconstructDeferredItems(assessment),
|
|
111
112
|
authoredAt: resource.sent || resource.authoredOn || resource.created,
|
|
@@ -113,8 +114,8 @@ function reconstructDisposition(resource, assessment, patientId) {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
function reconstructDeferredItems(assessment) {
|
|
116
|
-
const items = extValues(assessment,
|
|
117
|
-
const reasons = extValues(assessment,
|
|
117
|
+
const items = extValues(assessment, ROUTING_EXT.DEFERRED_ITEM);
|
|
118
|
+
const reasons = extValues(assessment, ROUTING_EXT.DEFERRED_REASON);
|
|
118
119
|
return items.map((item, i) => ({ item, deferReason: reasons[i] }));
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -3,6 +3,7 @@ const { ZodError } = require('zod');
|
|
|
3
3
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
4
4
|
const { extMap, extValues, identifierValue } = require('../helpers/fhirReadHelper');
|
|
5
5
|
const { SYMPTOM_CASE_SLUG, SYMPTOM_ASSESSMENT_SLUG } = require('../constants/projectionSlugs');
|
|
6
|
+
const { SYMPTOM_CASE_EXT, ASSESSMENT_EXT, GRADE_EXT } = require('../constants/extensionSlugs');
|
|
6
7
|
const { PROJECTOR_NAME } = require('../projections/symptomCaseProjection');
|
|
7
8
|
const { getFhirStore } = require('../stores/fhirStore');
|
|
8
9
|
const { ManagedSymptom } = require('../../shared/dtos/ManagedSymptom');
|
|
@@ -23,7 +24,7 @@ async function readSymptomCases({ patientId, ctcaeTerm = null, status = null })
|
|
|
23
24
|
const store = getFhirStore();
|
|
24
25
|
const patient = `Patient/${fhirId(patientId)}`;
|
|
25
26
|
const conditions = (await store.find({ patient, resourceType: 'Condition', symptomTerm: ctcaeTerm }))
|
|
26
|
-
.filter((condition) =>
|
|
27
|
+
.filter((condition) => SYMPTOM_CASE_EXT.STATUS in extMap(condition) && identifierValue(condition, SYMPTOM_CASE_SLUG));
|
|
27
28
|
const cases = [];
|
|
28
29
|
for (const condition of conditions) {
|
|
29
30
|
let managed;
|
|
@@ -61,17 +62,17 @@ function reconstructCondition(condition, patientId, conditionId) {
|
|
|
61
62
|
conditionId,
|
|
62
63
|
patientId,
|
|
63
64
|
ctcaeTerm: coding.code || code.text || null,
|
|
64
|
-
clinicalStatus: ext[
|
|
65
|
+
clinicalStatus: ext[SYMPTOM_CASE_EXT.STATUS],
|
|
65
66
|
onsetAt: condition.onsetDateTime,
|
|
66
|
-
abatementAt: condition.abatementDateTime || ext[
|
|
67
|
-
closeReason: ext[
|
|
68
|
-
temporality: ext[
|
|
69
|
-
evidenceEpisodeIds: extValues(condition,
|
|
70
|
-
currentEpisodeId: ext[
|
|
71
|
-
evidenceMentionIds: extValues(condition,
|
|
67
|
+
abatementAt: condition.abatementDateTime || ext[SYMPTOM_CASE_EXT.LEGACY_ABATEMENT] || null,
|
|
68
|
+
closeReason: ext[SYMPTOM_CASE_EXT.CLOSE_REASON] || null,
|
|
69
|
+
temporality: ext[SYMPTOM_CASE_EXT.TEMPORALITY] || null,
|
|
70
|
+
evidenceEpisodeIds: extValues(condition, SYMPTOM_CASE_EXT.EVIDENCE_EPISODE_IDS),
|
|
71
|
+
currentEpisodeId: ext[SYMPTOM_CASE_EXT.CURRENT_EPISODE_ID] || null,
|
|
72
|
+
evidenceMentionIds: extValues(condition, SYMPTOM_CASE_EXT.EVIDENCE_MENTION_IDS),
|
|
72
73
|
lastTransitionReason: (condition.note && condition.note[0] && condition.note[0].text) || null,
|
|
73
|
-
recurrenceCount: ext[
|
|
74
|
-
reactivatedAt: ext[
|
|
74
|
+
recurrenceCount: ext[SYMPTOM_CASE_EXT.RECURRENCE_COUNT] ?? 0,
|
|
75
|
+
reactivatedAt: ext[SYMPTOM_CASE_EXT.REACTIVATED_AT] || null,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -109,13 +110,13 @@ async function reconstructAssessment(impression, { patientId, conditionId, previ
|
|
|
109
110
|
performedAt: impression.effectiveDateTime,
|
|
110
111
|
previousId,
|
|
111
112
|
grade: reconstructGrade(gradeObs),
|
|
112
|
-
trajectory: ext.
|
|
113
|
-
decisionPosture: ext[
|
|
114
|
-
gradeVerification: gradeExt[
|
|
115
|
-
reasoning: ext[
|
|
116
|
-
postureReason: ext[
|
|
113
|
+
trajectory: ext[ASSESSMENT_EXT.TRAJECTORY] || null,
|
|
114
|
+
decisionPosture: ext[ASSESSMENT_EXT.DECISION_POSTURE] || null,
|
|
115
|
+
gradeVerification: gradeExt[GRADE_EXT.VERIFICATION] || 'provisional',
|
|
116
|
+
reasoning: ext[ASSESSMENT_EXT.REASONING] || null,
|
|
117
|
+
postureReason: ext[ASSESSMENT_EXT.POSTURE_REASON] || null,
|
|
117
118
|
problemRepresentation: impression.summary || null,
|
|
118
|
-
gradeThresholdClause: gradeExt[
|
|
119
|
+
gradeThresholdClause: gradeExt[GRADE_EXT.THRESHOLD_CLAUSE] || null,
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -124,12 +125,12 @@ function reconstructGrade(obs) {
|
|
|
124
125
|
if (!obs) return grade;
|
|
125
126
|
const ext = extMap(obs);
|
|
126
127
|
if (typeof obs.valueInteger === 'number') grade.bestEstimate = obs.valueInteger;
|
|
127
|
-
if (ext[
|
|
128
|
-
if (ext[
|
|
129
|
-
const [lo, hi] = ext[
|
|
128
|
+
if (ext[GRADE_EXT.CONFIDENCE]) grade.confidence = ext[GRADE_EXT.CONFIDENCE];
|
|
129
|
+
if (ext[GRADE_EXT.RANGE]) {
|
|
130
|
+
const [lo, hi] = ext[GRADE_EXT.RANGE].split('-').map((n) => Number.parseInt(n, 10));
|
|
130
131
|
if (Number.isInteger(lo) && Number.isInteger(hi)) grade.possibleRange = [lo, hi];
|
|
131
132
|
}
|
|
132
|
-
if (ext[
|
|
133
|
+
if (ext[GRADE_EXT.REASONING]) grade.reasoning = ext[GRADE_EXT.REASONING];
|
|
133
134
|
return grade;
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -4,15 +4,20 @@ const { BaseDto } = require('./BaseDto');
|
|
|
4
4
|
|
|
5
5
|
const dateTime = z.iso.datetime({ offset: true });
|
|
6
6
|
|
|
7
|
+
const HYPOTHESIS_TYPES = ['ae_profile', 'symptom_syndrome', 'disease_complication', 'relational_causal'];
|
|
8
|
+
const CLUSTER_STATUSES = ['active', 'dissolved'];
|
|
9
|
+
const CONFIDENCE_LEVELS = ['speculative', 'moderate', 'strong'];
|
|
10
|
+
const CLINICAL_SIGNIFICANCE = ['benign', 'moderate', 'safety_relevant', 'emergency'];
|
|
11
|
+
|
|
7
12
|
const schema = z.strictObject({
|
|
8
13
|
schemaVersion: z.string().default('1'),
|
|
9
14
|
clusterId: z.string(),
|
|
10
15
|
patientId: z.string(),
|
|
11
16
|
label: z.string(),
|
|
12
|
-
hypothesisType: z.enum(
|
|
13
|
-
status: z.enum(
|
|
14
|
-
confidence: z.enum(
|
|
15
|
-
clinicalSignificance: z.enum(
|
|
17
|
+
hypothesisType: z.enum(HYPOTHESIS_TYPES),
|
|
18
|
+
status: z.enum(CLUSTER_STATUSES).default('active'),
|
|
19
|
+
confidence: z.enum(CONFIDENCE_LEVELS),
|
|
20
|
+
clinicalSignificance: z.enum(CLINICAL_SIGNIFICANCE),
|
|
16
21
|
memberCaseIds: z.array(z.string()).min(1),
|
|
17
22
|
consultationId: z.string().nullable().default(null),
|
|
18
23
|
relationshipModel: z.string(),
|
|
@@ -37,4 +42,10 @@ class ClusterImpression extends BaseDto {}
|
|
|
37
42
|
|
|
38
43
|
ClusterImpression.schema = schema;
|
|
39
44
|
|
|
40
|
-
module.exports = {
|
|
45
|
+
module.exports = {
|
|
46
|
+
ClusterImpression,
|
|
47
|
+
HYPOTHESIS_TYPES,
|
|
48
|
+
CLUSTER_STATUSES,
|
|
49
|
+
CONFIDENCE_LEVELS,
|
|
50
|
+
CLINICAL_SIGNIFICANCE,
|
|
51
|
+
};
|
|
@@ -6,9 +6,10 @@ const dateTime = z.iso.datetime({ offset: true });
|
|
|
6
6
|
|
|
7
7
|
const ESCALATION_PRIORITIES = ['critical', 'high', 'medium', 'low', 'routine', 'urgent', 'asap', 'stat'];
|
|
8
8
|
const ACTIVE_CARE_PLAN_STATUSES = ['armed', 'fired_reminder', 'fired_team'];
|
|
9
|
+
const RECIPIENTS = ['patient', 'care_team'];
|
|
9
10
|
|
|
10
11
|
const escalationStepSchema = z.strictObject({
|
|
11
|
-
recipient: z.enum(
|
|
12
|
+
recipient: z.enum(RECIPIENTS),
|
|
12
13
|
message: z.string(),
|
|
13
14
|
priority: z.enum(ESCALATION_PRIORITIES).nullable().default(null),
|
|
14
15
|
actionRequested: z.string().nullable().default(null),
|
|
@@ -51,4 +52,5 @@ module.exports = {
|
|
|
51
52
|
ContingencySafetyNet,
|
|
52
53
|
ESCALATION_PRIORITIES,
|
|
53
54
|
ACTIVE_CARE_PLAN_STATUSES,
|
|
55
|
+
RECIPIENTS,
|
|
54
56
|
};
|
|
@@ -4,16 +4,19 @@ const { BaseDto } = require('./BaseDto');
|
|
|
4
4
|
|
|
5
5
|
const dateTime = z.iso.datetime({ offset: true });
|
|
6
6
|
|
|
7
|
+
const KINDS = ['otc', 'self_care', 'other', 'education', 'team_escalation', 'follow_up_check'];
|
|
8
|
+
const OUTCOMES = ['pending', 'improved', 'no_relief', 'side_effects', 'worsened', 'unknown'];
|
|
9
|
+
|
|
7
10
|
const schema = z.strictObject({
|
|
8
11
|
schemaVersion: z.string().default('1'),
|
|
9
|
-
kind: z.enum(
|
|
12
|
+
kind: z.enum(KINDS),
|
|
10
13
|
interventionId: z.string(),
|
|
11
14
|
patientId: z.string(),
|
|
12
15
|
caseId: z.string(),
|
|
13
16
|
interventionLabel: z.string(),
|
|
14
17
|
rationale: z.string().nullable().default(null),
|
|
15
18
|
deliveredAt: dateTime,
|
|
16
|
-
outcome: z.enum(
|
|
19
|
+
outcome: z.enum(OUTCOMES).default('pending'),
|
|
17
20
|
outcomeNotes: z.string().nullable().default(null),
|
|
18
21
|
recipientKind: z.enum(['patient', 'related_person']).default('patient'),
|
|
19
22
|
recipientRefId: z.string().nullable().default(null),
|
|
@@ -26,4 +29,8 @@ class Intervention extends BaseDto {}
|
|
|
26
29
|
|
|
27
30
|
Intervention.schema = schema;
|
|
28
31
|
|
|
29
|
-
module.exports = {
|
|
32
|
+
module.exports = {
|
|
33
|
+
Intervention,
|
|
34
|
+
KINDS,
|
|
35
|
+
OUTCOMES,
|
|
36
|
+
};
|