@peopl-health/nexus 5.11.0-dev.1120 → 5.11.0-dev.1123

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.
Files changed (30) hide show
  1. package/lib/clinical/tools/analyzeSymptomPatternsTool.js +3 -6
  2. package/lib/clinical/tools/getActiveSymptomLandscapeTool.js +2 -2
  3. package/lib/clinical/tools/getPatientHistoryTool.js +1 -1
  4. package/lib/clinical/tools/openClusterHypothesisTool.js +3 -7
  5. package/lib/clinical/tools/openConditionTool.js +2 -2
  6. package/lib/clinical/tools/recordClinicalImpressionTool.js +1 -2
  7. package/lib/clinical/tools/recordInterventionTool.js +3 -5
  8. package/lib/clinical/tools/setContingencyPlanTool.js +3 -7
  9. package/lib/clinical/tools/submitSafetyGateTool.js +1 -5
  10. package/lib/clinical/tools/updateClusterHypothesisTool.js +4 -7
  11. package/lib/clinical/tools/updateConditionStatusTool.js +1 -2
  12. package/lib/fhir/constants/extensionSlugs.js +111 -0
  13. package/lib/fhir/resources/CarePlan.js +8 -7
  14. package/lib/fhir/resources/ClinicalImpression.js +39 -38
  15. package/lib/fhir/resources/CommunicationRequest.js +5 -4
  16. package/lib/fhir/resources/Condition.js +10 -9
  17. package/lib/fhir/resources/Observation.js +7 -6
  18. package/lib/fhir/resources/RiskAssessment.js +13 -12
  19. package/lib/fhir/resources/Task.js +4 -3
  20. package/lib/fhir/services/clusterService.js +9 -8
  21. package/lib/fhir/services/contingencyService.js +14 -11
  22. package/lib/fhir/services/palliativeService.js +23 -22
  23. package/lib/fhir/services/riskService.js +11 -10
  24. package/lib/fhir/services/routingService.js +10 -9
  25. package/lib/fhir/services/symptomCaseService.js +21 -20
  26. package/lib/shared/dtos/ClusterImpression.js +16 -5
  27. package/lib/shared/dtos/ContingencySafetyNet.js +3 -1
  28. package/lib/shared/dtos/Intervention.js +10 -3
  29. package/lib/shared/dtos/ManagedSymptom.js +15 -5
  30. 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) => _OPEN_STATUSES.includes(symptomCase.condition.clinicalStatus));
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) => _TERMINAL_STATUSES.includes(symptomCase.condition.clinicalStatus)
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 low | medium | high.`);
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 (!OPEN_CASE_STATUSES.includes(managedSymptom.condition.clinicalStatus)) return fail('member_case_not_open', { case_id: caseId, status: managedSymptom.condition.clinicalStatus });
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) => ['characterizing', 'monitoring'].includes(symptomCase.condition.clinicalStatus));
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 (!OPEN_CASE_STATUSES.includes(targetCase.condition.clinicalStatus)) {
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
- ContingencySafetyNet,
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 (!OPEN_CASE_STATUSES.includes(targetCase.condition.clinicalStatus)) {
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 && !STATUS_LEVELS.includes(statusNew)) return fail(`invalid status_new. Must be one of ${STATUS_LEVELS.join(' | ')}.`, { status_new: statusNew });
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 (!OPEN_CASE_STATUSES.includes(managedSymptom.condition.clinicalStatus)) return fail('add_member_not_open', { case_id: caseId, status: managedSymptom.condition.clinicalStatus });
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('palliative-turn-id', { valueString: carePlan.turnId })],
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('palliative-assessment-id', { valueString: carePlan.assessmentId }));
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('contingency-status', { valueString: carePlan.status })];
120
+ const out = [extension(CONTINGENCY_EXT.STATUS, { valueString: carePlan.status })];
120
121
  if (carePlan.awaitingCharacterization) {
121
- out.push(extension('contingency-awaiting-characterization', { valueString: carePlan.awaitingCharacterization }));
122
+ out.push(extension(CONTINGENCY_EXT.AWAITING_CHARACTERIZATION, { valueString: carePlan.awaitingCharacterization }));
122
123
  }
123
124
  if (carePlan.whyThisMatters) {
124
- out.push(extension('contingency-why-this-matters', { valueString: carePlan.whyThisMatters }));
125
+ out.push(extension(CONTINGENCY_EXT.WHY_THIS_MATTERS, { valueString: carePlan.whyThisMatters }));
125
126
  }
126
127
  if (carePlan.creationReasoning) {
127
- out.push(extension('contingency-creation-reasoning', { valueString: carePlan.creationReasoning }));
128
+ out.push(extension(CONTINGENCY_EXT.CREATION_REASONING, { valueString: carePlan.creationReasoning }));
128
129
  }
129
130
  if (carePlan.resolvedReason) {
130
- out.push(extension('contingency-resolved-reason', { valueString: carePlan.resolvedReason }));
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('clusterHypothesisType', { valueString: cluster.hypothesisType }),
16
- extension('clusterHypothesisConfidence', { valueString: cluster.confidence }),
17
- extension('clusterClinicalSignificance', { valueString: cluster.clinicalSignificance }),
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('clusterStrengthensIf', { valueString: cluster.strengthensIf.join(CLUSTER_LIST_SEPARATOR) }));
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('clusterWeakensIf', { valueString: cluster.weakensIf.join(CLUSTER_LIST_SEPARATOR) }));
24
+ epistemicExts.push(extension(CLUSTER_EXT.WEAKENS_IF, { valueString: cluster.weakensIf.join(CLUSTER_LIST_SEPARATOR) }));
24
25
  }
25
- const lifecycleExts = [extension('cluster-status', { valueString: cluster.status })];
26
- if (cluster.consultationId) lifecycleExts.push(extension('cluster-consultation-id', { valueString: cluster.consultationId }));
27
- if (cluster.dissolveReason) lifecycleExts.push(extension('cluster-dissolve-reason', { valueString: cluster.dissolveReason }));
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('trajectory', { valueString: assessment.trajectory }));
56
- if (assessment.decisionPosture) extensions.push(extension('decision-posture', { valueString: assessment.decisionPosture }));
57
- if (assessment.postureReason) extensions.push(extension('posture-reason', { valueString: assessment.postureReason }));
58
- if (assessment.reasoning) extensions.push(extension('assessment-reasoning', { valueString: assessment.reasoning }));
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('routing-turn-id', { valueString: assessment.turnId }),
106
- extension('routing-decision-id', { valueString: decisionId }),
107
- extension('routing-committed-at', { valueDateTime: toIso(committedAt) }),
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('routing-primary-case-id', { valueString: primaryCaseId }));
110
+ if (primaryCaseId) extensions.push(extension(ROUTING_EXT.PRIMARY_CASE_ID, { valueString: primaryCaseId }));
110
111
  for (const deferred of deferredItems) {
111
- extensions.push(extension('routing-deferred-item', { valueString: deferred.item }));
112
- extensions.push(extension('routing-deferred-reason', { valueString: deferred.deferReason }));
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('routing-finding-case-id', { valueString: finding.caseId }));
128
+ findingExts.push(extension(ROUTING_EXT.FINDING_CASE_ID, { valueString: finding.caseId }));
128
129
  }
129
130
  findingExts.push(
130
- extension('routing-finding-reasoning', { valueString: finding.clinicalReasoning }),
131
- extension('routing-finding-action', { valueString: finding.decisionAction }),
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('palliative-turn-id', { valueString: assessment.turnId })],
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('palliative-finding-rank', { valueInteger: finding.rank }),
165
- extension('palliative-finding-element-type', { valueString: finding.elementType }),
166
- extension('palliative-finding-time-window', { valueString: finding.timeWindow }),
167
- extension('palliative-finding-clinical-description', { valueString: finding.clinicalDescription }),
168
- extension('palliative-finding-why-relevant', { valueString: finding.whyRelevant }),
169
- extension('palliative-finding-qol-criteria', { valueString: finding.qolCriteria }),
170
- extension('palliative-finding-improvement-criteria', { valueString: finding.improvementCriteria }),
171
- extension('palliative-finding-characterization-criteria', { valueString: finding.characterizationCriteria }),
172
- extension('palliative-finding-audit-notes', { valueString: finding.auditNotes }),
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('palliative-finding-symptom-term', { valueString: term }));
175
- for (const question of finding.probeQuestions) extensions.push(extension('palliative-finding-probe-question', { valueString: question }));
176
- if (finding.qolImpact !== null) extensions.push(extension('palliative-finding-qol-impact', { valueString: finding.qolImpact }));
177
- if (finding.improvementCapacity !== null) extensions.push(extension('palliative-finding-improvement-capacity', { valueString: finding.improvementCapacity }));
178
- if (finding.areMedsNeeded !== null) extensions.push(extension('palliative-finding-are-meds-needed', { valueBoolean: finding.areMedsNeeded }));
179
- if (finding.needsCharacterization !== null) extensions.push(extension('palliative-finding-needs-characterization', { valueBoolean: finding.needsCharacterization }));
180
- if (finding.clusterNote !== null) extensions.push(extension('palliative-finding-cluster-note', { valueString: finding.clusterNote }));
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(`contingency-${step.recipient}`)],
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('contingency-step-priority', { valueString: step.priority }));
129
- if (step.actionRequested) ext.push(extension('contingency-action-requested', { valueString: step.actionRequested }));
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('fires-after-silence-minutes', { valueInteger: step.firesAfterSilenceMinutes }));
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('symptom-case-status', { valueString: condition.clinicalStatus })];
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('legacy-abatement', { valueDateTime: toIso(condition.abatementAt) }));
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('symptom-close-reason', { valueString: condition.closeReason }));
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('symptom-temporality', { valueString: condition.temporality }));
76
- if (condition.currentEpisodeId) extensions.push(extension('symptom-current-episode-id', { valueString: condition.currentEpisodeId }));
77
- for (const id of condition.evidenceEpisodeIds) extensions.push(extension('symptom-evidence-episode-ids', { valueString: id }));
78
- for (const id of condition.evidenceMentionIds) extensions.push(extension('symptom-evidence-mention-ids', { valueString: id }));
79
- extensions.push(extension('symptom-recurrence-count', { valueInteger: condition.recurrenceCount }));
80
- if (condition.reactivatedAt) extensions.push(extension('symptom-reactivated-at', { valueDateTime: toIso(condition.reactivatedAt) }));
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) })),