@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { readSymptomCases, readPatientRisk } = require('../../fhir');
|
|
2
|
+
const { OPEN_STATUSES, CLOSED_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
2
3
|
|
|
3
4
|
const _CHECKPOINT_KEYWORDS = [
|
|
4
5
|
'pembrolizumab', 'nivolumab', 'atezolizumab', 'durvalumab',
|
|
@@ -101,10 +102,6 @@ const _PATTERN_CATALOG = [
|
|
|
101
102
|
|
|
102
103
|
const _SCOPES = ['all_active_cases', 'specific_cases', 'include_recent_resolved'];
|
|
103
104
|
|
|
104
|
-
const _OPEN_STATUSES = ['characterizing', 'monitoring'];
|
|
105
|
-
|
|
106
|
-
const _TERMINAL_STATUSES = ['closed', 'resolved', 'inactive'];
|
|
107
|
-
|
|
108
105
|
const definition = {
|
|
109
106
|
name: 'analyzeSymptomPatterns',
|
|
110
107
|
description: '**Does:** VALIDATES a clinical pattern you propose against the patient\'s active symptom landscape + treatment context. You pick a `specific_pattern_to_validate` (one of 6 frozen catalog patterns) from your reasoning over the symptoms + regimen + grades; the tool deterministically confirms whether the data matches. Read-only — never writes.\n\n**Required inputs:** `specific_pattern_to_validate` — one of `pattern-pneumonitis`, `pattern-cinv`, `pattern-febrile-syndrome`, `pattern-neuro-constellation`, `pattern-cord-compression`, `pattern-pain-insomnia-fatigue`. Optional `scope` (`all_active_cases` default | `specific_cases` | `include_recent_resolved`); `specific_case_ids` (required only for `scope=specific_cases`).\n\n**Returns:** `pattern_validated` (bool). When validated: `match` (pattern_id, type, hypothesis_label_suggestion, treatment_context, member_case_ids_matching, key_symptoms_present/missing, disconfirming_symptoms, match_strength, confidence). When not: `pattern_id`, `reason`, `key_symptoms_present[]`, `key_symptoms_missing[]`. Requires ≥2 key-symptom overlaps and a compatible treatment context.',
|
|
@@ -194,10 +191,10 @@ async function gatherCases({ patientId, scope, specificCaseIds }) {
|
|
|
194
191
|
return cases.filter((symptomCase) => ids.has(symptomCase.condition.conditionId));
|
|
195
192
|
}
|
|
196
193
|
const cases = await readSymptomCases({ patientId });
|
|
197
|
-
const open = cases.filter((symptomCase) =>
|
|
194
|
+
const open = cases.filter((symptomCase) => OPEN_STATUSES.includes(symptomCase.condition.clinicalStatus));
|
|
198
195
|
if (scope !== 'include_recent_resolved') return open;
|
|
199
196
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
200
|
-
const recentTerminal = cases.filter((symptomCase) =>
|
|
197
|
+
const recentTerminal = cases.filter((symptomCase) => CLOSED_STATUSES.includes(symptomCase.condition.clinicalStatus)
|
|
201
198
|
&& symptomCase.condition.abatementAt && new Date(symptomCase.condition.abatementAt).getTime() >= cutoff);
|
|
202
199
|
return [...open, ...recentTerminal];
|
|
203
200
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { readSymptomCases, readClusters } = require('../../fhir');
|
|
2
|
+
const { OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
2
3
|
|
|
3
4
|
const definition = {
|
|
4
5
|
name: 'getActiveSymptomLandscape',
|
|
@@ -18,7 +19,6 @@ const definition = {
|
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const PRIORITY_LEVEL = { low: 0, medium: 1, high: 2 };
|
|
21
|
-
const OPEN_STATUSES = ['characterizing', 'monitoring'];
|
|
22
22
|
|
|
23
23
|
function priorityAndFlags(managedSymptom, clusterMemberships) {
|
|
24
24
|
const flags = [];
|
|
@@ -69,7 +69,7 @@ function openedEpoch(openedAt) {
|
|
|
69
69
|
async function assembleLandscape(patientId, minimumPriorityArg) {
|
|
70
70
|
const minimumPriority = (typeof minimumPriorityArg === 'string' ? minimumPriorityArg : 'low').toLowerCase();
|
|
71
71
|
if (!(minimumPriority in PRIORITY_LEVEL)) {
|
|
72
|
-
throw new Error(`unknown minimum_priority: '${minimumPriority}'. Must be
|
|
72
|
+
throw new Error(`unknown minimum_priority: '${minimumPriority}'. Must be ${Object.keys(PRIORITY_LEVEL).join(' | ')}.`);
|
|
73
73
|
}
|
|
74
74
|
const minLevel = PRIORITY_LEVEL[minimumPriority];
|
|
75
75
|
|
|
@@ -2,6 +2,7 @@ const { readSymptomCases, readInterventions, patientSnapshot } = require('../../
|
|
|
2
2
|
const { fhirId } = require('../../fhir/helpers/fhirHelper');
|
|
3
3
|
const { identifierValue } = require('../../fhir/helpers/fhirReadHelper');
|
|
4
4
|
const { INTERVENTION_SLUG } = require('../../fhir/constants/projectionSlugs');
|
|
5
|
+
const { CLOSED_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
5
6
|
|
|
6
7
|
const SUPPORTED_SCOPES = new Set([
|
|
7
8
|
'closed_cases_30d',
|
|
@@ -16,7 +17,6 @@ const SUPPORTED_SCOPES = new Set([
|
|
|
16
17
|
|
|
17
18
|
const DEFAULT_SCOPE = ['closed_cases_30d', 'recurrence_patterns'];
|
|
18
19
|
const TIMEFRAME_DAYS = { last_7_days: 7, last_30_days: 30, last_90_days: 90 };
|
|
19
|
-
const CLOSED_STATUSES = ['resolved', 'inactive', 'closed'];
|
|
20
20
|
const POSITION_E_DEFAULT_WINDOW_DAYS = 30;
|
|
21
21
|
const POSITION_E_SCOPES = ['recent_medications', 'appointment_mentions', 'patient_reported_procedures'];
|
|
22
22
|
|
|
@@ -2,14 +2,10 @@ const crypto = require('node:crypto');
|
|
|
2
2
|
|
|
3
3
|
const { patternConsultStore } = require('../models/patternConsultModel');
|
|
4
4
|
const { readSymptomCases, readClusters, storeCluster } = require('../../fhir');
|
|
5
|
-
const { ClusterImpression } = require('../../shared/dtos/ClusterImpression');
|
|
5
|
+
const { ClusterImpression, HYPOTHESIS_TYPES, CONFIDENCE_LEVELS, CLINICAL_SIGNIFICANCE } = require('../../shared/dtos/ClusterImpression');
|
|
6
6
|
const { ClusterHistoryRecord } = require('../../shared/dtos/ClusterHistoryRecord');
|
|
7
7
|
const { patternConsultLive } = require('../flags/patternConsultFlags');
|
|
8
|
-
|
|
9
|
-
const HYPOTHESIS_TYPES = ['ae_profile', 'symptom_syndrome', 'disease_complication', 'relational_causal'];
|
|
10
|
-
const CONFIDENCE_LEVELS = ['speculative', 'moderate', 'strong'];
|
|
11
|
-
const CLINICAL_SIGNIFICANCE = ['benign', 'moderate', 'safety_relevant', 'emergency'];
|
|
12
|
-
const OPEN_CASE_STATUSES = ['characterizing', 'monitoring'];
|
|
8
|
+
const { OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
13
9
|
|
|
14
10
|
const definition = {
|
|
15
11
|
name: 'openClusterHypothesis',
|
|
@@ -134,7 +130,7 @@ async function handler(args = {}, context = {}) {
|
|
|
134
130
|
for (const caseId of memberCaseIds) {
|
|
135
131
|
const managedSymptom = cases.find((c) => c.condition.conditionId === caseId);
|
|
136
132
|
if (!managedSymptom) return fail('member_case_not_found_or_wrong_patient', { member_case_id: caseId });
|
|
137
|
-
if (!
|
|
133
|
+
if (!OPEN_STATUSES.includes(managedSymptom.condition.clinicalStatus)) return fail('member_case_not_open', { case_id: caseId, status: managedSymptom.condition.clinicalStatus });
|
|
138
134
|
}
|
|
139
135
|
|
|
140
136
|
const now = new Date().toISOString();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const crypto = require('node:crypto');
|
|
2
2
|
|
|
3
3
|
const { readSymptomCases, storeSymptomCase } = require('../../fhir');
|
|
4
|
-
const { ManagedSymptom } = require('../../shared/dtos/ManagedSymptom');
|
|
4
|
+
const { ManagedSymptom, OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
5
5
|
const { GRADE_MIN, GRADE_MAX, parseGradeEstimate } = require('../helpers/gradeEstimateHelper');
|
|
6
6
|
|
|
7
7
|
const definition = {
|
|
@@ -62,7 +62,7 @@ async function handler(args = {}, context = {}) {
|
|
|
62
62
|
|
|
63
63
|
const patientId = runtime.patientCode;
|
|
64
64
|
const casesForTerm = await readSymptomCases({ patientId, ctcaeTerm });
|
|
65
|
-
const openCase = casesForTerm.find((symptomCase) =>
|
|
65
|
+
const openCase = casesForTerm.find((symptomCase) => OPEN_STATUSES.includes(symptomCase.condition.clinicalStatus));
|
|
66
66
|
if (openCase) {
|
|
67
67
|
return JSON.stringify({
|
|
68
68
|
success: false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const crypto = require('node:crypto');
|
|
2
2
|
|
|
3
3
|
const { readSymptomCases, storeSymptomCase } = require('../../fhir');
|
|
4
|
-
const { ManagedSymptom } = require('../../shared/dtos/ManagedSymptom');
|
|
4
|
+
const { ManagedSymptom, OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
5
5
|
const { GRADE_MIN, GRADE_MAX, parseGradeEstimate } = require('../helpers/gradeEstimateHelper');
|
|
6
6
|
|
|
7
7
|
const definition = {
|
|
@@ -62,7 +62,6 @@ const definition = {
|
|
|
62
62
|
const EVENTS = definition.parameters.properties.event.enum;
|
|
63
63
|
const TRAJECTORIES = definition.parameters.properties.trajectory_direction.enum;
|
|
64
64
|
const POSTURES = definition.parameters.properties.decision_posture.enum;
|
|
65
|
-
const OPEN_STATUSES = ['characterizing', 'monitoring'];
|
|
66
65
|
|
|
67
66
|
const uid = (prefix) => `${prefix}_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;
|
|
68
67
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
const crypto = require('node:crypto');
|
|
2
2
|
|
|
3
3
|
const { readSymptomCases, readInterventions, storeIntervention } = require('../../fhir');
|
|
4
|
-
const { Intervention } = require('../../shared/dtos/Intervention');
|
|
4
|
+
const { Intervention, KINDS, OUTCOMES } = require('../../shared/dtos/Intervention');
|
|
5
|
+
const { OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
5
6
|
|
|
6
|
-
const OPEN_CASE_STATUSES = ['characterizing', 'monitoring'];
|
|
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
|
const ACTIONS = ['create', 'record_outcome'];
|
|
10
8
|
|
|
11
9
|
const definition = {
|
|
@@ -88,7 +86,7 @@ async function create(patientId, args) {
|
|
|
88
86
|
|
|
89
87
|
const targetCase = (await readSymptomCases({ patientId })).find((c) => c.condition.conditionId === caseId);
|
|
90
88
|
if (!targetCase) return fail('case_not_found_or_wrong_patient', { case_id: caseId });
|
|
91
|
-
if (!
|
|
89
|
+
if (!OPEN_STATUSES.includes(targetCase.condition.clinicalStatus)) {
|
|
92
90
|
return fail('case_is_dormant — interventions attach to open cases; if the symptom recurred, reactivate the case via recordClinicalImpression first.', { case_id: caseId, status: targetCase.condition.clinicalStatus });
|
|
93
91
|
}
|
|
94
92
|
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
const crypto = require('node:crypto');
|
|
2
2
|
|
|
3
3
|
const { readSymptomCases, readClusters, readContingencies, storeContingency } = require('../../fhir');
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
ESCALATION_PRIORITIES,
|
|
7
|
-
ACTIVE_CARE_PLAN_STATUSES,
|
|
8
|
-
} = require('../../shared/dtos/ContingencySafetyNet');
|
|
4
|
+
const { ContingencySafetyNet, ESCALATION_PRIORITIES, ACTIVE_CARE_PLAN_STATUSES } = require('../../shared/dtos/ContingencySafetyNet');
|
|
5
|
+
const { OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
9
6
|
|
|
10
|
-
const OPEN_CASE_STATUSES = ['characterizing', 'monitoring'];
|
|
11
7
|
const STEP_SHAPE = '{recipient, message, priority, action_requested, fires_after_silence_minutes}';
|
|
12
8
|
|
|
13
9
|
const definition = {
|
|
@@ -124,7 +120,7 @@ async function handler(args = {}, context = {}) {
|
|
|
124
120
|
if (relatedCaseId) {
|
|
125
121
|
const targetCase = (await readSymptomCases({ patientId })).find((c) => c.condition.conditionId === relatedCaseId);
|
|
126
122
|
if (!targetCase) return fail('case_not_found_or_wrong_patient', { related_case_id: relatedCaseId });
|
|
127
|
-
if (!
|
|
123
|
+
if (!OPEN_STATUSES.includes(targetCase.condition.clinicalStatus)) {
|
|
128
124
|
return fail('target_not_open — contingency plans require an active case', { related_case_id: relatedCaseId, current_status: targetCase.condition.clinicalStatus });
|
|
129
125
|
}
|
|
130
126
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
const { ZodError } = require('zod');
|
|
2
2
|
|
|
3
3
|
const { storeSafetyGate, safetyGateExists } = require('../../fhir');
|
|
4
|
-
const {
|
|
5
|
-
RecommendationSafetyGate,
|
|
6
|
-
ESCALATION_ROUTES,
|
|
7
|
-
RED_FLAG_STATUSES,
|
|
8
|
-
} = require('../../shared/dtos/RecommendationSafetyGate');
|
|
4
|
+
const { RecommendationSafetyGate, ESCALATION_ROUTES, RED_FLAG_STATUSES } = require('../../shared/dtos/RecommendationSafetyGate');
|
|
9
5
|
const { logger } = require('../../utils/logger');
|
|
10
6
|
|
|
11
7
|
const definition = {
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
const { readSymptomCases, readClusters, storeCluster } = require('../../fhir');
|
|
2
|
-
const { ClusterImpression } = require('../../shared/dtos/ClusterImpression');
|
|
2
|
+
const { ClusterImpression, CLUSTER_STATUSES, CONFIDENCE_LEVELS, CLINICAL_SIGNIFICANCE } = require('../../shared/dtos/ClusterImpression');
|
|
3
3
|
const { ClusterHistoryRecord } = require('../../shared/dtos/ClusterHistoryRecord');
|
|
4
|
+
const { OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
4
5
|
|
|
5
|
-
const CONFIDENCE_LEVELS = ['speculative', 'moderate', 'strong'];
|
|
6
|
-
const STATUS_LEVELS = ['active', 'dissolved'];
|
|
7
|
-
const CLINICAL_SIGNIFICANCE = ['benign', 'moderate', 'safety_relevant', 'emergency'];
|
|
8
|
-
const OPEN_CASE_STATUSES = ['characterizing', 'monitoring'];
|
|
9
6
|
const UPDATE_KEYS = ['add_members', 'remove_members', 'label_new', 'confidence_new', 'status_new', 'clinical_significance_new', 'relationship_model_new', 'strengthens_if_new', 'weakens_if_new'];
|
|
10
7
|
|
|
11
8
|
const definition = {
|
|
@@ -72,7 +69,7 @@ async function handler(args = {}, context = {}) {
|
|
|
72
69
|
const weakensNew = update.weakens_if_new;
|
|
73
70
|
|
|
74
71
|
if (confidenceNew !== null && !CONFIDENCE_LEVELS.includes(confidenceNew)) return fail(`invalid confidence_new. Must be one of ${CONFIDENCE_LEVELS.join(' | ')}.`, { confidence_new: confidenceNew });
|
|
75
|
-
if (statusNew !== null && !
|
|
72
|
+
if (statusNew !== null && !CLUSTER_STATUSES.includes(statusNew)) return fail(`invalid status_new. Must be one of ${CLUSTER_STATUSES.join(' | ')}.`, { status_new: statusNew });
|
|
76
73
|
if (significanceNew !== null && !CLINICAL_SIGNIFICANCE.includes(significanceNew)) return fail(`invalid clinical_significance_new. Must be one of ${CLINICAL_SIGNIFICANCE.join(' | ')}.`, { clinical_significance_new: significanceNew });
|
|
77
74
|
if (relModelNew !== undefined && (typeof relModelNew !== 'string' || !relModelNew.trim())) return fail('relationship_model_new must be a non-empty string');
|
|
78
75
|
|
|
@@ -91,7 +88,7 @@ async function handler(args = {}, context = {}) {
|
|
|
91
88
|
if (currentMembers.includes(caseId)) return fail('add_member_already_in_cluster', { case_id: caseId });
|
|
92
89
|
const managedSymptom = cases.find((c) => c.condition.conditionId === caseId);
|
|
93
90
|
if (!managedSymptom) return fail('add_member_not_found_or_wrong_patient', { case_id: caseId });
|
|
94
|
-
if (!
|
|
91
|
+
if (!OPEN_STATUSES.includes(managedSymptom.condition.clinicalStatus)) return fail('add_member_not_open', { case_id: caseId, status: managedSymptom.condition.clinicalStatus });
|
|
95
92
|
}
|
|
96
93
|
for (const caseId of removeMembers) {
|
|
97
94
|
if (!currentMembers.includes(caseId)) return fail('remove_member_not_in_cluster', { case_id: caseId });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { readSymptomCases, storeSymptomCase, readContingencies, storeContingency, readClusters, storeCluster } = require('../../fhir');
|
|
2
|
-
const { ManagedSymptom } = require('../../shared/dtos/ManagedSymptom');
|
|
2
|
+
const { ManagedSymptom, OPEN_STATUSES } = require('../../shared/dtos/ManagedSymptom');
|
|
3
3
|
const { RESOLUTION_CLOSE_REASONS } = require('../../fhir/resources/Condition');
|
|
4
4
|
const { ContingencySafetyNet, ACTIVE_CARE_PLAN_STATUSES } = require('../../shared/dtos/ContingencySafetyNet');
|
|
5
5
|
const { ClusterImpression } = require('../../shared/dtos/ClusterImpression');
|
|
@@ -7,7 +7,6 @@ const { ClusterHistoryRecord } = require('../../shared/dtos/ClusterHistoryRecord
|
|
|
7
7
|
const { logger } = require('../../utils/logger');
|
|
8
8
|
|
|
9
9
|
const DEACTIVATE_CLOSE_REASONS = ['escalated_to_team', 'patient_transferred', 'stale_no_activity'];
|
|
10
|
-
const OPEN_STATUSES = ['characterizing', 'monitoring'];
|
|
11
10
|
const CLUSTER_MEMBER_FLOOR = 2;
|
|
12
11
|
|
|
13
12
|
const definition = {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const CONTINGENCY_CATEGORY_PREFIX = 'contingency-';
|
|
2
|
+
|
|
3
|
+
const SYMPTOM_CASE_EXT = {
|
|
4
|
+
STATUS: 'symptom-case-status',
|
|
5
|
+
LEGACY_ABATEMENT: 'legacy-abatement',
|
|
6
|
+
CLOSE_REASON: 'symptom-close-reason',
|
|
7
|
+
TEMPORALITY: 'symptom-temporality',
|
|
8
|
+
CURRENT_EPISODE_ID: 'symptom-current-episode-id',
|
|
9
|
+
EVIDENCE_EPISODE_IDS: 'symptom-evidence-episode-ids',
|
|
10
|
+
EVIDENCE_MENTION_IDS: 'symptom-evidence-mention-ids',
|
|
11
|
+
RECURRENCE_COUNT: 'symptom-recurrence-count',
|
|
12
|
+
REACTIVATED_AT: 'symptom-reactivated-at',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ASSESSMENT_EXT = {
|
|
16
|
+
TRAJECTORY: 'trajectory',
|
|
17
|
+
DECISION_POSTURE: 'decision-posture',
|
|
18
|
+
POSTURE_REASON: 'posture-reason',
|
|
19
|
+
REASONING: 'assessment-reasoning',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const GRADE_EXT = {
|
|
23
|
+
CONFIDENCE: 'grade-confidence',
|
|
24
|
+
RANGE: 'grade-range',
|
|
25
|
+
REASONING: 'grade-reasoning',
|
|
26
|
+
VERIFICATION: 'grade-verification',
|
|
27
|
+
THRESHOLD_CLAUSE: 'grade-threshold-clause',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ROUTING_EXT = {
|
|
31
|
+
TURN_ID: 'routing-turn-id',
|
|
32
|
+
DECISION_ID: 'routing-decision-id',
|
|
33
|
+
COMMITTED_AT: 'routing-committed-at',
|
|
34
|
+
PRIMARY_CASE_ID: 'routing-primary-case-id',
|
|
35
|
+
DEFERRED_ITEM: 'routing-deferred-item',
|
|
36
|
+
DEFERRED_REASON: 'routing-deferred-reason',
|
|
37
|
+
FINDING_CASE_ID: 'routing-finding-case-id',
|
|
38
|
+
FINDING_REASONING: 'routing-finding-reasoning',
|
|
39
|
+
FINDING_ACTION: 'routing-finding-action',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const CLUSTER_EXT = {
|
|
43
|
+
HYPOTHESIS_TYPE: 'clusterHypothesisType',
|
|
44
|
+
CONFIDENCE: 'clusterHypothesisConfidence',
|
|
45
|
+
CLINICAL_SIGNIFICANCE: 'clusterClinicalSignificance',
|
|
46
|
+
STRENGTHENS_IF: 'clusterStrengthensIf',
|
|
47
|
+
WEAKENS_IF: 'clusterWeakensIf',
|
|
48
|
+
STATUS: 'cluster-status',
|
|
49
|
+
CONSULTATION_ID: 'cluster-consultation-id',
|
|
50
|
+
DISSOLVE_REASON: 'cluster-dissolve-reason',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const PALLIATIVE_EXT = {
|
|
54
|
+
TURN_ID: 'palliative-turn-id',
|
|
55
|
+
ASSESSMENT_ID: 'palliative-assessment-id',
|
|
56
|
+
ESCALATION_RANK: 'palliative-escalation-rank',
|
|
57
|
+
CAREPLAN_REC_RANK: 'palliative-careplan-rec-rank',
|
|
58
|
+
CAREPLAN_OTC_OFFER: 'palliative-careplan-otc-offer',
|
|
59
|
+
FINDING_RANK: 'palliative-finding-rank',
|
|
60
|
+
FINDING_ELEMENT_TYPE: 'palliative-finding-element-type',
|
|
61
|
+
FINDING_TIME_WINDOW: 'palliative-finding-time-window',
|
|
62
|
+
FINDING_SYMPTOM_TERM: 'palliative-finding-symptom-term',
|
|
63
|
+
FINDING_CLINICAL_DESCRIPTION: 'palliative-finding-clinical-description',
|
|
64
|
+
FINDING_WHY_RELEVANT: 'palliative-finding-why-relevant',
|
|
65
|
+
FINDING_QOL_IMPACT: 'palliative-finding-qol-impact',
|
|
66
|
+
FINDING_QOL_CRITERIA: 'palliative-finding-qol-criteria',
|
|
67
|
+
FINDING_IMPROVEMENT_CAPACITY: 'palliative-finding-improvement-capacity',
|
|
68
|
+
FINDING_IMPROVEMENT_CRITERIA: 'palliative-finding-improvement-criteria',
|
|
69
|
+
FINDING_ARE_MEDS_NEEDED: 'palliative-finding-are-meds-needed',
|
|
70
|
+
FINDING_NEEDS_CHARACTERIZATION: 'palliative-finding-needs-characterization',
|
|
71
|
+
FINDING_CHARACTERIZATION_CRITERIA: 'palliative-finding-characterization-criteria',
|
|
72
|
+
FINDING_PROBE_QUESTION: 'palliative-finding-probe-question',
|
|
73
|
+
FINDING_AUDIT_NOTES: 'palliative-finding-audit-notes',
|
|
74
|
+
FINDING_CLUSTER_NOTE: 'palliative-finding-cluster-note',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const CONTINGENCY_EXT = {
|
|
78
|
+
STATUS: 'contingency-status',
|
|
79
|
+
AWAITING_CHARACTERIZATION: 'contingency-awaiting-characterization',
|
|
80
|
+
WHY_THIS_MATTERS: 'contingency-why-this-matters',
|
|
81
|
+
CREATION_REASONING: 'contingency-creation-reasoning',
|
|
82
|
+
RESOLVED_REASON: 'contingency-resolved-reason',
|
|
83
|
+
STEP_PRIORITY: 'contingency-step-priority',
|
|
84
|
+
ACTION_REQUESTED: 'contingency-action-requested',
|
|
85
|
+
FIRES_AFTER_SILENCE_MINUTES: 'fires-after-silence-minutes',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const RISK_EXT = {
|
|
89
|
+
PREDICTION_KIND: 'risk-prediction-kind',
|
|
90
|
+
MINIMUM_SYMPTOM_SET: 'risk-minimum-symptom-set',
|
|
91
|
+
DISEASE_COMPLICATION: 'risk-disease-complication',
|
|
92
|
+
DISEASE_WARNING_SYMPTOM: 'risk-disease-warning-symptom',
|
|
93
|
+
DISEASE_AMPLIFIER: 'risk-disease-amplifier',
|
|
94
|
+
COMORBIDITY_CLINICAL_NOTE: 'risk-comorbidity-clinical-note',
|
|
95
|
+
SOURCE_REGIMEN: 'assessed-source-regimen',
|
|
96
|
+
SOURCE_DIAGNOSIS: 'assessed-source-diagnosis',
|
|
97
|
+
SOURCE_PHASE: 'assessed-source-phase',
|
|
98
|
+
ASSESSED_BY_MODEL: 'assessed-by-model',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
SYMPTOM_CASE_EXT,
|
|
103
|
+
ASSESSMENT_EXT,
|
|
104
|
+
GRADE_EXT,
|
|
105
|
+
ROUTING_EXT,
|
|
106
|
+
CLUSTER_EXT,
|
|
107
|
+
PALLIATIVE_EXT,
|
|
108
|
+
CONTINGENCY_EXT,
|
|
109
|
+
CONTINGENCY_CATEGORY_PREFIX,
|
|
110
|
+
RISK_EXT,
|
|
111
|
+
};
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
toIso,
|
|
9
9
|
} = require('../helpers/elementHelper');
|
|
10
10
|
const { CONTINGENCY_PLAN_SLUG, ROUTING_DISPOSITION_SLUG, RECOMMENDATION_PLAN_SLUG, PALLIATIVE_CAREPLAN_SLUG } = require('../constants/projectionSlugs');
|
|
11
|
+
const { CONTINGENCY_EXT, PALLIATIVE_EXT } = require('../constants/extensionSlugs');
|
|
11
12
|
|
|
12
13
|
const CONTINGENCY_STATUS_TO_FHIR = {
|
|
13
14
|
armed: 'active',
|
|
@@ -77,11 +78,11 @@ class CarePlan {
|
|
|
77
78
|
category: [codeableConcept(PALLIATIVE_CAREPLAN_SLUG)],
|
|
78
79
|
subject: patientReference(carePlan.patientId),
|
|
79
80
|
created: toIso(carePlan.committedAt),
|
|
80
|
-
extension: [extension(
|
|
81
|
+
extension: [extension(PALLIATIVE_EXT.TURN_ID, { valueString: carePlan.turnId })],
|
|
81
82
|
};
|
|
82
83
|
if (carePlan.summary) payload.description = carePlan.summary;
|
|
83
84
|
if (carePlan.assessmentId) {
|
|
84
|
-
payload.extension.push(extension(
|
|
85
|
+
payload.extension.push(extension(PALLIATIVE_EXT.ASSESSMENT_ID, { valueString: carePlan.assessmentId }));
|
|
85
86
|
payload.supportingInfo = [reference('ClinicalImpression', carePlan.assessmentId)];
|
|
86
87
|
}
|
|
87
88
|
if (taskIds.length) {
|
|
@@ -116,18 +117,18 @@ function contingencyPeriod(carePlan) {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function contingencyExtensions(carePlan) {
|
|
119
|
-
const out = [extension(
|
|
120
|
+
const out = [extension(CONTINGENCY_EXT.STATUS, { valueString: carePlan.status })];
|
|
120
121
|
if (carePlan.awaitingCharacterization) {
|
|
121
|
-
out.push(extension(
|
|
122
|
+
out.push(extension(CONTINGENCY_EXT.AWAITING_CHARACTERIZATION, { valueString: carePlan.awaitingCharacterization }));
|
|
122
123
|
}
|
|
123
124
|
if (carePlan.whyThisMatters) {
|
|
124
|
-
out.push(extension(
|
|
125
|
+
out.push(extension(CONTINGENCY_EXT.WHY_THIS_MATTERS, { valueString: carePlan.whyThisMatters }));
|
|
125
126
|
}
|
|
126
127
|
if (carePlan.creationReasoning) {
|
|
127
|
-
out.push(extension(
|
|
128
|
+
out.push(extension(CONTINGENCY_EXT.CREATION_REASONING, { valueString: carePlan.creationReasoning }));
|
|
128
129
|
}
|
|
129
130
|
if (carePlan.resolvedReason) {
|
|
130
|
-
out.push(extension(
|
|
131
|
+
out.push(extension(CONTINGENCY_EXT.RESOLVED_REASON, { valueString: carePlan.resolvedReason }));
|
|
131
132
|
}
|
|
132
133
|
return out;
|
|
133
134
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { fhirId } = require('../helpers/fhirHelper');
|
|
2
2
|
const { identifier, reference, patientReference, codeableConcept, extension, toIso } = require('../helpers/elementHelper');
|
|
3
3
|
const { SYMPTOM_ASSESSMENT_SLUG, CLUSTER_IMPRESSION_SLUG, CLUSTER_LABEL_SLUG, ROUTING_ASSESSMENT_SLUG, RECOMMENDATION_ELEMENTS_SLUG, PALLIATIVE_ASSESSMENT_SLUG } = require('../constants/projectionSlugs');
|
|
4
|
+
const { ASSESSMENT_EXT, ROUTING_EXT, CLUSTER_EXT, PALLIATIVE_EXT } = require('../constants/extensionSlugs');
|
|
4
5
|
|
|
5
6
|
const CLUSTER_STATUS_TO_FHIR = { active: 'in-progress', dissolved: 'completed' };
|
|
6
7
|
const CLUSTER_LIST_SEPARATOR = '; ';
|
|
@@ -12,19 +13,19 @@ class ClinicalImpression {
|
|
|
12
13
|
|
|
13
14
|
static fromClusterImpression({ cluster }) {
|
|
14
15
|
const epistemicExts = [
|
|
15
|
-
extension(
|
|
16
|
-
extension(
|
|
17
|
-
extension(
|
|
16
|
+
extension(CLUSTER_EXT.HYPOTHESIS_TYPE, { valueString: cluster.hypothesisType }),
|
|
17
|
+
extension(CLUSTER_EXT.CONFIDENCE, { valueString: cluster.confidence }),
|
|
18
|
+
extension(CLUSTER_EXT.CLINICAL_SIGNIFICANCE, { valueString: cluster.clinicalSignificance }),
|
|
18
19
|
];
|
|
19
20
|
if (cluster.strengthensIf.length) {
|
|
20
|
-
epistemicExts.push(extension(
|
|
21
|
+
epistemicExts.push(extension(CLUSTER_EXT.STRENGTHENS_IF, { valueString: cluster.strengthensIf.join(CLUSTER_LIST_SEPARATOR) }));
|
|
21
22
|
}
|
|
22
23
|
if (cluster.weakensIf.length) {
|
|
23
|
-
epistemicExts.push(extension(
|
|
24
|
+
epistemicExts.push(extension(CLUSTER_EXT.WEAKENS_IF, { valueString: cluster.weakensIf.join(CLUSTER_LIST_SEPARATOR) }));
|
|
24
25
|
}
|
|
25
|
-
const lifecycleExts = [extension(
|
|
26
|
-
if (cluster.consultationId) lifecycleExts.push(extension(
|
|
27
|
-
if (cluster.dissolveReason) lifecycleExts.push(extension(
|
|
26
|
+
const lifecycleExts = [extension(CLUSTER_EXT.STATUS, { valueString: cluster.status })];
|
|
27
|
+
if (cluster.consultationId) lifecycleExts.push(extension(CLUSTER_EXT.CONSULTATION_ID, { valueString: cluster.consultationId }));
|
|
28
|
+
if (cluster.dissolveReason) lifecycleExts.push(extension(CLUSTER_EXT.DISSOLVE_REASON, { valueString: cluster.dissolveReason }));
|
|
28
29
|
|
|
29
30
|
const identifiers = [identifier(CLUSTER_IMPRESSION_SLUG, cluster.clusterId)];
|
|
30
31
|
if (cluster.label) identifiers.push(identifier(CLUSTER_LABEL_SLUG, cluster.label));
|
|
@@ -52,10 +53,10 @@ class ClinicalImpression {
|
|
|
52
53
|
|
|
53
54
|
static fromSymptomAssessment({ assessment, dropPreviousTo = null, hasGradeSignal = false }) {
|
|
54
55
|
const extensions = [];
|
|
55
|
-
if (assessment.trajectory) extensions.push(extension(
|
|
56
|
-
if (assessment.decisionPosture) extensions.push(extension(
|
|
57
|
-
if (assessment.postureReason) extensions.push(extension(
|
|
58
|
-
if (assessment.reasoning) extensions.push(extension(
|
|
56
|
+
if (assessment.trajectory) extensions.push(extension(ASSESSMENT_EXT.TRAJECTORY, { valueString: assessment.trajectory }));
|
|
57
|
+
if (assessment.decisionPosture) extensions.push(extension(ASSESSMENT_EXT.DECISION_POSTURE, { valueString: assessment.decisionPosture }));
|
|
58
|
+
if (assessment.postureReason) extensions.push(extension(ASSESSMENT_EXT.POSTURE_REASON, { valueString: assessment.postureReason }));
|
|
59
|
+
if (assessment.reasoning) extensions.push(extension(ASSESSMENT_EXT.REASONING, { valueString: assessment.reasoning }));
|
|
59
60
|
const payload = {
|
|
60
61
|
resourceType: 'ClinicalImpression',
|
|
61
62
|
id: fhirId(assessment.assessmentId),
|
|
@@ -102,14 +103,14 @@ class ClinicalImpression {
|
|
|
102
103
|
|
|
103
104
|
static fromRoutingAssessment({ assessment, decisionId, committedAt, deferredItems = [], primaryCaseId = null }) {
|
|
104
105
|
const extensions = [
|
|
105
|
-
extension(
|
|
106
|
-
extension(
|
|
107
|
-
extension(
|
|
106
|
+
extension(ROUTING_EXT.TURN_ID, { valueString: assessment.turnId }),
|
|
107
|
+
extension(ROUTING_EXT.DECISION_ID, { valueString: decisionId }),
|
|
108
|
+
extension(ROUTING_EXT.COMMITTED_AT, { valueDateTime: toIso(committedAt) }),
|
|
108
109
|
];
|
|
109
|
-
if (primaryCaseId) extensions.push(extension(
|
|
110
|
+
if (primaryCaseId) extensions.push(extension(ROUTING_EXT.PRIMARY_CASE_ID, { valueString: primaryCaseId }));
|
|
110
111
|
for (const deferred of deferredItems) {
|
|
111
|
-
extensions.push(extension(
|
|
112
|
-
extensions.push(extension(
|
|
112
|
+
extensions.push(extension(ROUTING_EXT.DEFERRED_ITEM, { valueString: deferred.item }));
|
|
113
|
+
extensions.push(extension(ROUTING_EXT.DEFERRED_REASON, { valueString: deferred.deferReason }));
|
|
113
114
|
}
|
|
114
115
|
const payload = {
|
|
115
116
|
resourceType: 'ClinicalImpression',
|
|
@@ -124,11 +125,11 @@ class ClinicalImpression {
|
|
|
124
125
|
// A caseless mention (caseId null) has no case to point at: no Condition item and no case-id extension, so absence stays absence in the FHIR.
|
|
125
126
|
if (finding.caseId) {
|
|
126
127
|
projected.item = reference('Condition', finding.caseId);
|
|
127
|
-
findingExts.push(extension(
|
|
128
|
+
findingExts.push(extension(ROUTING_EXT.FINDING_CASE_ID, { valueString: finding.caseId }));
|
|
128
129
|
}
|
|
129
130
|
findingExts.push(
|
|
130
|
-
extension(
|
|
131
|
-
extension(
|
|
131
|
+
extension(ROUTING_EXT.FINDING_REASONING, { valueString: finding.clinicalReasoning }),
|
|
132
|
+
extension(ROUTING_EXT.FINDING_ACTION, { valueString: finding.decisionAction }),
|
|
132
133
|
);
|
|
133
134
|
projected.extension = findingExts;
|
|
134
135
|
return projected;
|
|
@@ -150,7 +151,7 @@ class ClinicalImpression {
|
|
|
150
151
|
subject: patientReference(assessment.patientId),
|
|
151
152
|
effectiveDateTime: toIso(assessment.committedAt),
|
|
152
153
|
finding: assessment.findings.map(palliativeFinding),
|
|
153
|
-
extension: [extension(
|
|
154
|
+
extension: [extension(PALLIATIVE_EXT.TURN_ID, { valueString: assessment.turnId })],
|
|
154
155
|
};
|
|
155
156
|
if (assessment.evaluationSummary) payload.summary = assessment.evaluationSummary;
|
|
156
157
|
if (assessment.rankingRationale) payload.description = assessment.rankingRationale;
|
|
@@ -161,23 +162,23 @@ class ClinicalImpression {
|
|
|
161
162
|
|
|
162
163
|
function palliativeFinding(finding) {
|
|
163
164
|
const extensions = [
|
|
164
|
-
extension(
|
|
165
|
-
extension(
|
|
166
|
-
extension(
|
|
167
|
-
extension(
|
|
168
|
-
extension(
|
|
169
|
-
extension(
|
|
170
|
-
extension(
|
|
171
|
-
extension(
|
|
172
|
-
extension(
|
|
165
|
+
extension(PALLIATIVE_EXT.FINDING_RANK, { valueInteger: finding.rank }),
|
|
166
|
+
extension(PALLIATIVE_EXT.FINDING_ELEMENT_TYPE, { valueString: finding.elementType }),
|
|
167
|
+
extension(PALLIATIVE_EXT.FINDING_TIME_WINDOW, { valueString: finding.timeWindow }),
|
|
168
|
+
extension(PALLIATIVE_EXT.FINDING_CLINICAL_DESCRIPTION, { valueString: finding.clinicalDescription }),
|
|
169
|
+
extension(PALLIATIVE_EXT.FINDING_WHY_RELEVANT, { valueString: finding.whyRelevant }),
|
|
170
|
+
extension(PALLIATIVE_EXT.FINDING_QOL_CRITERIA, { valueString: finding.qolCriteria }),
|
|
171
|
+
extension(PALLIATIVE_EXT.FINDING_IMPROVEMENT_CRITERIA, { valueString: finding.improvementCriteria }),
|
|
172
|
+
extension(PALLIATIVE_EXT.FINDING_CHARACTERIZATION_CRITERIA, { valueString: finding.characterizationCriteria }),
|
|
173
|
+
extension(PALLIATIVE_EXT.FINDING_AUDIT_NOTES, { valueString: finding.auditNotes }),
|
|
173
174
|
];
|
|
174
|
-
for (const term of finding.symptomTerms) extensions.push(extension(
|
|
175
|
-
for (const question of finding.probeQuestions) extensions.push(extension(
|
|
176
|
-
if (finding.qolImpact !== null) extensions.push(extension(
|
|
177
|
-
if (finding.improvementCapacity !== null) extensions.push(extension(
|
|
178
|
-
if (finding.areMedsNeeded !== null) extensions.push(extension(
|
|
179
|
-
if (finding.needsCharacterization !== null) extensions.push(extension(
|
|
180
|
-
if (finding.clusterNote !== null) extensions.push(extension(
|
|
175
|
+
for (const term of finding.symptomTerms) extensions.push(extension(PALLIATIVE_EXT.FINDING_SYMPTOM_TERM, { valueString: term }));
|
|
176
|
+
for (const question of finding.probeQuestions) extensions.push(extension(PALLIATIVE_EXT.FINDING_PROBE_QUESTION, { valueString: question }));
|
|
177
|
+
if (finding.qolImpact !== null) extensions.push(extension(PALLIATIVE_EXT.FINDING_QOL_IMPACT, { valueString: finding.qolImpact }));
|
|
178
|
+
if (finding.improvementCapacity !== null) extensions.push(extension(PALLIATIVE_EXT.FINDING_IMPROVEMENT_CAPACITY, { valueString: finding.improvementCapacity }));
|
|
179
|
+
if (finding.areMedsNeeded !== null) extensions.push(extension(PALLIATIVE_EXT.FINDING_ARE_MEDS_NEEDED, { valueBoolean: finding.areMedsNeeded }));
|
|
180
|
+
if (finding.needsCharacterization !== null) extensions.push(extension(PALLIATIVE_EXT.FINDING_NEEDS_CHARACTERIZATION, { valueBoolean: finding.needsCharacterization }));
|
|
181
|
+
if (finding.clusterNote !== null) extensions.push(extension(PALLIATIVE_EXT.FINDING_CLUSTER_NOTE, { valueString: finding.clusterNote }));
|
|
181
182
|
return { item: { concept: codeableConcept(finding.nameShort) }, extension: extensions };
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
INTERVENTION_KIND_SLUG,
|
|
22
22
|
ROUTING_DISPOSITION_SLUG,
|
|
23
23
|
} = require('../constants/projectionSlugs');
|
|
24
|
+
const { CONTINGENCY_EXT, CONTINGENCY_CATEGORY_PREFIX } = require('../constants/extensionSlugs');
|
|
24
25
|
const { SEVERITY_TO_PRIORITY } = require('../constants/severityPriority');
|
|
25
26
|
const { CONTINGENCY_STATUS_TO_FHIR } = require('./CarePlan');
|
|
26
27
|
|
|
@@ -113,7 +114,7 @@ class CommunicationRequest {
|
|
|
113
114
|
identifier: [identifier(CONTINGENCY_STEP_SLUG, stepId)],
|
|
114
115
|
status: CONTINGENCY_STATUS_TO_FHIR[parentStatus],
|
|
115
116
|
intent: 'plan',
|
|
116
|
-
category: [codeableConcept(
|
|
117
|
+
category: [codeableConcept(`${CONTINGENCY_CATEGORY_PREFIX}${step.recipient}`)],
|
|
117
118
|
subject: patientReference(patientId),
|
|
118
119
|
recipient: [step.recipient === 'patient'
|
|
119
120
|
? patientReference(patientId)
|
|
@@ -125,10 +126,10 @@ class CommunicationRequest {
|
|
|
125
126
|
const priority = step.priority ? CONTINGENCY_PRIORITY_CROSSWALK[step.priority] || 'routine' : null;
|
|
126
127
|
if (priority) payload.priority = priority;
|
|
127
128
|
const ext = [];
|
|
128
|
-
if (step.priority) ext.push(extension(
|
|
129
|
-
if (step.actionRequested) ext.push(extension(
|
|
129
|
+
if (step.priority) ext.push(extension(CONTINGENCY_EXT.STEP_PRIORITY, { valueString: step.priority }));
|
|
130
|
+
if (step.actionRequested) ext.push(extension(CONTINGENCY_EXT.ACTION_REQUESTED, { valueString: step.actionRequested }));
|
|
130
131
|
if (step.firesAfterSilenceMinutes !== null && step.firesAfterSilenceMinutes !== undefined) {
|
|
131
|
-
ext.push(extension(
|
|
132
|
+
ext.push(extension(CONTINGENCY_EXT.FIRES_AFTER_SILENCE_MINUTES, { valueInteger: step.firesAfterSilenceMinutes }));
|
|
132
133
|
}
|
|
133
134
|
if (ext.length) payload.extension = ext;
|
|
134
135
|
return new CommunicationRequest(payload);
|
|
@@ -2,6 +2,7 @@ const { getSystems } = require('../config/fhirConfig');
|
|
|
2
2
|
const { fhirId, codeSystemUrl } = require('../helpers/fhirHelper');
|
|
3
3
|
const { identifier, reference, patientReference, codeableConcept, extension, toIso } = require('../helpers/elementHelper');
|
|
4
4
|
const { SYMPTOM_CASE_SLUG } = require('../constants/projectionSlugs');
|
|
5
|
+
const { SYMPTOM_CASE_EXT } = require('../constants/extensionSlugs');
|
|
5
6
|
|
|
6
7
|
const CLINICAL_STATUS_TO_FHIR = {
|
|
7
8
|
resolved: 'resolved',
|
|
@@ -39,7 +40,7 @@ class Condition {
|
|
|
39
40
|
const code = condition.ctcaeTerm && condition.ctcaeTerm !== 'other'
|
|
40
41
|
? codeableConcept(condition.ctcaeTerm, { code: condition.ctcaeTerm, system: codeSystemUrl('pro-ctcae'), display: condition.ctcaeTerm })
|
|
41
42
|
: codeableConcept(condition.ctcaeTerm || 'other');
|
|
42
|
-
const extensions = [extension(
|
|
43
|
+
const extensions = [extension(SYMPTOM_CASE_EXT.STATUS, { valueString: condition.clinicalStatus })];
|
|
43
44
|
const payload = {
|
|
44
45
|
resourceType: 'Condition',
|
|
45
46
|
id: fhirId(condition.conditionId),
|
|
@@ -63,21 +64,21 @@ class Condition {
|
|
|
63
64
|
if (clinicalStatusCode === 'resolved') {
|
|
64
65
|
payload.abatementDateTime = toIso(condition.abatementAt);
|
|
65
66
|
} else {
|
|
66
|
-
extensions.push(extension(
|
|
67
|
+
extensions.push(extension(SYMPTOM_CASE_EXT.LEGACY_ABATEMENT, { valueDateTime: toIso(condition.abatementAt) }));
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
if (condition.closeReason) {
|
|
70
|
-
extensions.push(extension(
|
|
71
|
+
extensions.push(extension(SYMPTOM_CASE_EXT.CLOSE_REASON, { valueString: condition.closeReason }));
|
|
71
72
|
if (clinicalStatusCode === 'inactive' && condition.closeReason === 'stale_no_activity') {
|
|
72
73
|
extensions.push(extension('presumed-inactive-by-gap', { valueBoolean: true }));
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
if (condition.temporality) extensions.push(extension(
|
|
76
|
-
if (condition.currentEpisodeId) extensions.push(extension(
|
|
77
|
-
for (const id of condition.evidenceEpisodeIds) extensions.push(extension(
|
|
78
|
-
for (const id of condition.evidenceMentionIds) extensions.push(extension(
|
|
79
|
-
extensions.push(extension(
|
|
80
|
-
if (condition.reactivatedAt) extensions.push(extension(
|
|
76
|
+
if (condition.temporality) extensions.push(extension(SYMPTOM_CASE_EXT.TEMPORALITY, { valueString: condition.temporality }));
|
|
77
|
+
if (condition.currentEpisodeId) extensions.push(extension(SYMPTOM_CASE_EXT.CURRENT_EPISODE_ID, { valueString: condition.currentEpisodeId }));
|
|
78
|
+
for (const id of condition.evidenceEpisodeIds) extensions.push(extension(SYMPTOM_CASE_EXT.EVIDENCE_EPISODE_IDS, { valueString: id }));
|
|
79
|
+
for (const id of condition.evidenceMentionIds) extensions.push(extension(SYMPTOM_CASE_EXT.EVIDENCE_MENTION_IDS, { valueString: id }));
|
|
80
|
+
extensions.push(extension(SYMPTOM_CASE_EXT.RECURRENCE_COUNT, { valueInteger: condition.recurrenceCount }));
|
|
81
|
+
if (condition.reactivatedAt) extensions.push(extension(SYMPTOM_CASE_EXT.REACTIVATED_AT, { valueDateTime: toIso(condition.reactivatedAt) }));
|
|
81
82
|
if (condition.lastTransitionReason) payload.note = [{ text: condition.lastTransitionReason }];
|
|
82
83
|
const evidence = [
|
|
83
84
|
...condition.evidenceEpisodeIds.map((id) => ({ reference: reference('Observation', id) })),
|