@peopl-health/nexus 5.9.2 → 5.10.0-dev.1020

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 (32) hide show
  1. package/lib/clinical/config/subAgentsConfig.js +1 -7
  2. package/lib/clinical/memory/DefaultMemoryManager.js +1 -1
  3. package/lib/clinical/providers/AnthropicProvider.js +0 -10
  4. package/lib/clinical/tools/{getPatientRiskAssessmentTool.js → getPatientRiskProfileTool.js} +4 -4
  5. package/lib/clinical/tools/getRouterContextBundleTool.js +1 -1
  6. package/lib/clinical/tools/registerClinicalTools.js +1 -1
  7. package/lib/fhir/constants/projectionSlugs.js +0 -12
  8. package/lib/fhir/index.js +1 -9
  9. package/lib/fhir/projections/registerProjectors.js +0 -6
  10. package/lib/fhir/resources/CarePlan.js +1 -23
  11. package/lib/fhir/resources/ClinicalImpression.js +1 -64
  12. package/lib/fhir/resources/Provenance.js +0 -20
  13. package/lib/fhir/resources/RiskAssessment.js +1 -40
  14. package/lib/fhir/resources/Task.js +0 -57
  15. package/lib/fhir/services/riskService.js +0 -19
  16. package/package.json +1 -1
  17. package/lib/clinical/helpers/palliativeAssessmentHelper.js +0 -62
  18. package/lib/clinical/models/patternConsultModel.js +0 -37
  19. package/lib/clinical/services/patternConsultService.js +0 -115
  20. package/lib/clinical/tools/requestPatternConsultTool.js +0 -70
  21. package/lib/clinical/tools/submitPalliativeAssessmentTool.js +0 -75
  22. package/lib/clinical/tools/submitRecommendationPlanTool.js +0 -242
  23. package/lib/clinical/tools/submitSafetyGateTool.js +0 -207
  24. package/lib/clinical/tools/updatePalliativeAssessmentTool.js +0 -74
  25. package/lib/fhir/projections/palliativeProjection.js +0 -37
  26. package/lib/fhir/projections/recommendationPlanProjection.js +0 -25
  27. package/lib/fhir/projections/safetyGateProjection.js +0 -16
  28. package/lib/fhir/services/palliativeService.js +0 -121
  29. package/lib/fhir/services/recommendationService.js +0 -27
  30. package/lib/shared/dtos/PalliativeAssessment.js +0 -62
  31. package/lib/shared/dtos/RecommendationPlan.js +0 -109
  32. package/lib/shared/dtos/RecommendationSafetyGate.js +0 -64
@@ -10,7 +10,6 @@ const CACHE_TTL = 5 * 60 * 1000;
10
10
  const CACHE_KEY = 'subAgentsConfig';
11
11
  const EXTRACTOR_DEFAULTS = { presetId: '' };
12
12
  const COMPOSER_DEFAULTS = { presetId: '' };
13
- const PATTERN_CONSULT_DEFAULTS = { presetId: '' };
14
13
 
15
14
  const cache = new MapCache({ maxSize: 1, ttl: CACHE_TTL });
16
15
 
@@ -30,11 +29,7 @@ async function load() {
30
29
  if (byKey.COMPOSER && !isPlainObject(parsedComposer)) throw new Error('subAgents config COMPOSER is not a JSON object');
31
30
  const composer = { ...COMPOSER_DEFAULTS, ...(parsedComposer || {}) };
32
31
 
33
- const parsedPatternConsult = safeParse(byKey.PATTERN_CONSULT, null);
34
- if (byKey.PATTERN_CONSULT && !isPlainObject(parsedPatternConsult)) throw new Error('subAgents config PATTERN_CONSULT is not a JSON object');
35
- const patternConsult = { ...PATTERN_CONSULT_DEFAULTS, ...(parsedPatternConsult || {}) };
36
-
37
- const config = { EXTRACTOR: extractor, COMPOSER: composer, PATTERN_CONSULT: patternConsult };
32
+ const config = { EXTRACTOR: extractor, COMPOSER: composer };
38
33
  cache.set(CACHE_KEY, config);
39
34
  return config;
40
35
  }
@@ -46,5 +41,4 @@ async function get(key) {
46
41
  module.exports = {
47
42
  getExtractor: () => get('EXTRACTOR'),
48
43
  getComposer: () => get('COMPOSER'),
49
- getPatternConsult: () => get('PATTERN_CONSULT'),
50
44
  };
@@ -207,4 +207,4 @@ class DefaultMemoryManager extends MemoryManager {
207
207
  }
208
208
  }
209
209
 
210
- module.exports = { DefaultMemoryManager, OPERATOR_TURN_PREFIX };
210
+ module.exports = { DefaultMemoryManager };
@@ -1,6 +1,5 @@
1
1
  const { Anthropic } = require('@anthropic-ai/sdk');
2
2
 
3
- const { OPERATOR_TURN_PREFIX } = require('../memory/DefaultMemoryManager');
4
3
  const { appendCall } = require('../services/llmAuditService');
5
4
  const { composePrompt } = require('../services/promptComposerService');
6
5
  const { retryWithBackoff } = require('../../utils/retryUtils');
@@ -385,15 +384,6 @@ class AnthropicProvider extends BaseLLMProvider {
385
384
  return typeof output === 'string' ? output : JSON.stringify(output ?? '');
386
385
  }
387
386
 
388
- // Split placement: the inline copy carries recency on single-round turns, the hoisted developer
389
- // turn persists once tool results are appended after it on multi-round turns.
390
- _buildInput({ additionalInstructions, ...rest }) {
391
- const input = super._buildInput({ additionalInstructions, ...rest });
392
- if (!additionalInstructions) return input;
393
- const { content } = this._operatorInstructionTurn(additionalInstructions);
394
- return [...input, { role: 'user', content: `${OPERATOR_TURN_PREFIX} ${content}`, type: 'message' }];
395
- }
396
-
397
387
  _mapModelConfig(modelConfig) {
398
388
  const mapped = { ...(modelConfig || {}) };
399
389
  if (mapped.max_tokens == null && mapped.max_output_tokens != null) mapped.max_tokens = mapped.max_output_tokens;
@@ -1,8 +1,8 @@
1
1
  const { readPatientRisk } = require('../../fhir');
2
2
 
3
3
  const definition = {
4
- name: 'getPatientRiskAssessment',
5
- description: '**Does:** Returns the patient\'s persistent risk assessment — predicted adverse events, disease risks, and comorbidity amplifiers derived from their regimen. Read-only.\n\n**Required inputs:** none.\n\n**When to call:** before routing, to anticipate which adverse events or complications the current regimen makes plausible.\n\n**Returns:** `status` (`final` when an assessment exists, `not_generated` otherwise) and `profile` (`assessment_id`, `source_regimen`, `source_diagnosis`, `source_phase`, `predicted_aes[]`, `disease_risks[]`, `comorbidity_amplifiers[]`, `assessed_at`, `assessed_by_model`) — `null` when not yet generated.',
4
+ name: 'getPatientRiskProfile',
5
+ description: '**Does:** Returns the patient\'s persistent risk profile — predicted adverse events, disease risks, and comorbidity amplifiers derived from their regimen. Read-only.\n\n**Required inputs:** none.\n\n**When to call:** before routing, to anticipate which adverse events or complications the current regimen makes plausible.\n\n**Returns:** `status` (`final` when a profile exists, `not_generated` otherwise) and `profile` (`assessment_id`, `source_regimen`, `source_diagnosis`, `source_phase`, `predicted_aes[]`, `disease_risks[]`, `comorbidity_amplifiers[]`, `assessed_at`, `assessed_by_model`) — `null` when not yet generated.',
6
6
  strict: false,
7
7
  parameters: {
8
8
  type: 'object',
@@ -55,12 +55,12 @@ async function handler(_args = {}, context = {}) {
55
55
  try {
56
56
  const runtime = context?.toolRuntimeContext || null;
57
57
  if (!runtime?.turnId || !runtime?.patientCode) {
58
- return JSON.stringify({ success: false, error: 'getPatientRiskAssessment requires an active turn context (turnId, patientCode).', data: {} });
58
+ return JSON.stringify({ success: false, error: 'getPatientRiskProfile requires an active turn context (turnId, patientCode).', data: {} });
59
59
  }
60
60
  const data = await assembleRiskProfile(runtime.patientCode);
61
61
  return JSON.stringify({ success: true, data });
62
62
  } catch (err) {
63
- return JSON.stringify({ success: false, error: err?.message || 'getPatientRiskAssessment failed', data: {} });
63
+ return JSON.stringify({ success: false, error: err?.message || 'getPatientRiskProfile failed', data: {} });
64
64
  }
65
65
  }
66
66
 
@@ -1,6 +1,6 @@
1
1
  const { assembleLandscape } = require('./getActiveSymptomLandscapeTool');
2
2
  const { assembleHistory, SUPPORTED_SCOPES, TIMEFRAME_DAYS } = require('./getPatientHistoryTool');
3
- const { assembleRiskProfile } = require('./getPatientRiskAssessmentTool');
3
+ const { assembleRiskProfile } = require('./getPatientRiskProfileTool');
4
4
 
5
5
  const ALLOWED_SECTIONS = ['landscape', 'history', 'risk'];
6
6
 
@@ -17,7 +17,7 @@ const TOOLS = [
17
17
  require('./getActiveSymptomLandscapeTool'),
18
18
  require('./recordInterventionTool'),
19
19
  require('./setContingencyPlanTool'),
20
- require('./getPatientRiskAssessmentTool'),
20
+ require('./getPatientRiskProfileTool'),
21
21
  require('./getPatientHistoryTool'),
22
22
  require('./getRouterContextBundleTool'),
23
23
  require('./deliverPatientMessageTool'),
@@ -9,10 +9,6 @@ const SYMPTOM_CASE_SLUG = 'symptom-condition';
9
9
  const SYMPTOM_ASSESSMENT_SLUG = 'symptom-assessment';
10
10
  const SYMPTOM_GRADE_SLUG = 'symptom-grade';
11
11
  const PATIENT_RISK_SLUG = 'patient-risk-assessment';
12
- const SAFETY_GATE_SLUG = 'recommendation-safety';
13
- const RECOMMENDATION_PLAN_SLUG = 'recommendation-careplan';
14
- const RECOMMENDATION_ELEMENTS_SLUG = 'recommendation-palliative';
15
- const RECOMMENDATION_REC_SLUG = 'recommendation-rec';
16
12
  const CONTINGENCY_PLAN_SLUG = 'contingency-plan';
17
13
  const CONTINGENCY_STEP_SLUG = 'contingency-step';
18
14
  const INTERVENTION_SLUG = 'intervention';
@@ -22,8 +18,6 @@ const CLUSTER_IMPRESSION_SLUG = 'cluster-impression';
22
18
  const CLUSTER_LABEL_SLUG = 'cluster-label';
23
19
  const ROUTING_ASSESSMENT_SLUG = 'routing-assessment';
24
20
  const ROUTING_DISPOSITION_SLUG = 'routing-disposition';
25
- const PALLIATIVE_ASSESSMENT_SLUG = 'palliative-assessment';
26
- const PALLIATIVE_ESCALATION_SLUG = 'palliative-escalation';
27
21
 
28
22
  module.exports = {
29
23
  MENTION_SLUG,
@@ -37,10 +31,6 @@ module.exports = {
37
31
  SYMPTOM_ASSESSMENT_SLUG,
38
32
  SYMPTOM_GRADE_SLUG,
39
33
  PATIENT_RISK_SLUG,
40
- SAFETY_GATE_SLUG,
41
- RECOMMENDATION_PLAN_SLUG,
42
- RECOMMENDATION_ELEMENTS_SLUG,
43
- RECOMMENDATION_REC_SLUG,
44
34
  CONTINGENCY_PLAN_SLUG,
45
35
  CONTINGENCY_STEP_SLUG,
46
36
  INTERVENTION_SLUG,
@@ -50,6 +40,4 @@ module.exports = {
50
40
  CLUSTER_LABEL_SLUG,
51
41
  ROUTING_ASSESSMENT_SLUG,
52
42
  ROUTING_DISPOSITION_SLUG,
53
- PALLIATIVE_ASSESSMENT_SLUG,
54
- PALLIATIVE_ESCALATION_SLUG,
55
43
  };
package/lib/fhir/index.js CHANGED
@@ -2,13 +2,11 @@ const { registerProjector, project, storeResources } = require('./services/fhirS
2
2
  const { storeClinicalMentions } = require('./services/clinicalMentionService');
3
3
  const { storeResultObservations } = require('./services/resultObservationService');
4
4
  const { storeSymptomCase, readSymptomCases } = require('./services/symptomCaseService');
5
- const { storePatientRisk, readPatientRisk, storeSafetyGate, safetyGateExists } = require('./services/riskService');
6
- const { storeRecommendationPlan, recommendationPlanExists } = require('./services/recommendationService');
5
+ const { storePatientRisk, readPatientRisk } = require('./services/riskService');
7
6
  const { storeContingency, readContingencies } = require('./services/contingencyService');
8
7
  const { storeIntervention, readInterventions } = require('./services/interventionService');
9
8
  const { storeCluster, readClusters } = require('./services/clusterService');
10
9
  const { storeRoutingDecision, readRoutingByTurn } = require('./services/routingService');
11
- const { storePalliativeAssessment, readPalliativeByTurn } = require('./services/palliativeService');
12
10
  const { storeTriage } = require('./services/triageService');
13
11
  const { patientSnapshot } = require('./services/snapshotService');
14
12
  const { registerAllProjectors } = require('./projections/registerProjectors');
@@ -32,10 +30,6 @@ module.exports = {
32
30
  readSymptomCases,
33
31
  storePatientRisk,
34
32
  readPatientRisk,
35
- storeSafetyGate,
36
- safetyGateExists,
37
- storeRecommendationPlan,
38
- recommendationPlanExists,
39
33
  storeContingency,
40
34
  readContingencies,
41
35
  storeIntervention,
@@ -44,8 +38,6 @@ module.exports = {
44
38
  readClusters,
45
39
  storeRoutingDecision,
46
40
  readRoutingByTurn,
47
- storePalliativeAssessment,
48
- readPalliativeByTurn,
49
41
  storeTriage,
50
42
  patientSnapshot,
51
43
  getFhirStore,
@@ -4,13 +4,10 @@ const { projectDispatchedEscalation, PROJECTOR_NAME: ESCALATION_PROJECTOR_NAME }
4
4
  const { projectReminder, PROJECTOR_NAME: REMINDER_PROJECTOR_NAME } = require('./reminderProjection');
5
5
  const { projectSymptomCase, PROJECTOR_NAME: SYMPTOM_CASE_PROJECTOR_NAME } = require('./symptomCaseProjection');
6
6
  const { projectPatientRisk, PROJECTOR_NAME: RISK_PROJECTOR_NAME } = require('./riskProjection');
7
- const { projectSafetyGate, PROJECTOR_NAME: SAFETY_GATE_PROJECTOR_NAME } = require('./safetyGateProjection');
8
- const { projectRecommendationPlan, PROJECTOR_NAME: RECOMMENDATION_PLAN_PROJECTOR_NAME } = require('./recommendationPlanProjection');
9
7
  const { projectContingency, PROJECTOR_NAME: CONTINGENCY_PROJECTOR_NAME } = require('./contingencyProjection');
10
8
  const { projectIntervention, PROJECTOR_NAME: INTERVENTION_PROJECTOR_NAME } = require('./interventionProjection');
11
9
  const { projectCluster, PROJECTOR_NAME: CLUSTER_PROJECTOR_NAME } = require('./clusterProjection');
12
10
  const { projectRoutingDecision, PROJECTOR_NAME: ROUTING_PROJECTOR_NAME } = require('./routingProjection');
13
- const { projectPalliativeAssessment, PROJECTOR_NAME: PALLIATIVE_PROJECTOR_NAME } = require('./palliativeProjection');
14
11
 
15
12
  const PROJECTORS = [
16
13
  { name: MENTION_PROJECTOR_NAME, project: projectClinicalMention },
@@ -18,13 +15,10 @@ const PROJECTORS = [
18
15
  { name: REMINDER_PROJECTOR_NAME, project: projectReminder },
19
16
  { name: SYMPTOM_CASE_PROJECTOR_NAME, project: projectSymptomCase },
20
17
  { name: RISK_PROJECTOR_NAME, project: projectPatientRisk },
21
- { name: SAFETY_GATE_PROJECTOR_NAME, project: projectSafetyGate },
22
- { name: RECOMMENDATION_PLAN_PROJECTOR_NAME, project: projectRecommendationPlan },
23
18
  { name: CONTINGENCY_PROJECTOR_NAME, project: projectContingency },
24
19
  { name: INTERVENTION_PROJECTOR_NAME, project: projectIntervention },
25
20
  { name: CLUSTER_PROJECTOR_NAME, project: projectCluster },
26
21
  { name: ROUTING_PROJECTOR_NAME, project: projectRoutingDecision },
27
- { name: PALLIATIVE_PROJECTOR_NAME, project: projectPalliativeAssessment },
28
22
  ];
29
23
 
30
24
  function registerAllProjectors() {
@@ -7,7 +7,7 @@ const {
7
7
  extension,
8
8
  toIso,
9
9
  } = require('../helpers/elementHelper');
10
- const { CONTINGENCY_PLAN_SLUG, ROUTING_DISPOSITION_SLUG, RECOMMENDATION_PLAN_SLUG } = require('../constants/projectionSlugs');
10
+ const { CONTINGENCY_PLAN_SLUG, ROUTING_DISPOSITION_SLUG } = require('../constants/projectionSlugs');
11
11
 
12
12
  const CONTINGENCY_STATUS_TO_FHIR = {
13
13
  armed: 'active',
@@ -45,28 +45,6 @@ class CarePlan {
45
45
  return new CarePlan(payload);
46
46
  }
47
47
 
48
- static fromRecommendationPlan({ plan, taskIds, hasSafetyGate }) {
49
- const payload = {
50
- resourceType: 'CarePlan',
51
- id: fhirId(`${plan.runId}-careplan`),
52
- identifier: [identifier(RECOMMENDATION_PLAN_SLUG, `${plan.runId}-careplan`)],
53
- status: 'active',
54
- intent: 'plan',
55
- category: [codeableConcept('symptom_management')],
56
- subject: patientReference(plan.patientId),
57
- created: toIso(plan.createdAt),
58
- activity: taskIds.map((taskId) => ({
59
- plannedActivityReference: reference('Task', taskId),
60
- })),
61
- };
62
- if (hasSafetyGate) {
63
- payload.supportingInfo = [reference('RiskAssessment', `${plan.runId}-safety`)];
64
- }
65
- if (plan.takeaways.summaryInternal) payload.description = plan.takeaways.summaryInternal;
66
- if (plan.takeaways.summaryPatientFacing) payload.note = [{ text: plan.takeaways.summaryPatientFacing }];
67
- return new CarePlan(payload);
68
- }
69
-
70
48
  static fromRoutingDisposition({ disposition }) {
71
49
  const payload = {
72
50
  resourceType: 'CarePlan',
@@ -1,6 +1,6 @@
1
1
  const { fhirId } = require('../helpers/fhirHelper');
2
2
  const { identifier, reference, patientReference, codeableConcept, extension, toIso } = require('../helpers/elementHelper');
3
- const { SYMPTOM_ASSESSMENT_SLUG, CLUSTER_IMPRESSION_SLUG, CLUSTER_LABEL_SLUG, ROUTING_ASSESSMENT_SLUG, RECOMMENDATION_ELEMENTS_SLUG, PALLIATIVE_ASSESSMENT_SLUG } = require('../constants/projectionSlugs');
3
+ const { SYMPTOM_ASSESSMENT_SLUG, CLUSTER_IMPRESSION_SLUG, CLUSTER_LABEL_SLUG, ROUTING_ASSESSMENT_SLUG } = require('../constants/projectionSlugs');
4
4
 
5
5
  const CLUSTER_STATUS_TO_FHIR = { active: 'in-progress', dissolved: 'completed' };
6
6
  const CLUSTER_LIST_SEPARATOR = '; ';
@@ -76,30 +76,6 @@ class ClinicalImpression {
76
76
  return new ClinicalImpression(payload);
77
77
  }
78
78
 
79
- static fromRecommendationPlan({ plan }) {
80
- const finding = [...plan.elements]
81
- .sort((a, b) => a.rank - b.rank)
82
- .map((element) => {
83
- const basis = [`rank: ${element.rank}`, `tipo: ${element.type}`];
84
- if (element.qolImpact) basis.push(`QoL: ${element.qolImpact}`);
85
- if (element.whyRelevant) basis.push(`relevancia: ${element.whyRelevant}`);
86
- const grounding = element.groundedIn.filter(Boolean).join('; ');
87
- if (grounding) basis.push(`sustento: ${grounding}`);
88
- return { item: { concept: codeableConcept(element.nameShort) }, basis: basis.join('; ') };
89
- });
90
- const payload = {
91
- resourceType: 'ClinicalImpression',
92
- id: fhirId(`${plan.runId}-palliative`),
93
- identifier: [identifier(RECOMMENDATION_ELEMENTS_SLUG, `${plan.runId}-palliative`)],
94
- status: 'completed',
95
- subject: patientReference(plan.patientId),
96
- effectiveDateTime: toIso(plan.createdAt),
97
- finding,
98
- };
99
- if (plan.takeaways.summaryInternal) payload.summary = plan.takeaways.summaryInternal;
100
- return new ClinicalImpression(payload);
101
- }
102
-
103
79
  static fromRoutingAssessment({ assessment, decisionId, committedAt, deferredItems = [], primaryCaseId = null }) {
104
80
  const extensions = [
105
81
  extension('routing-turn-id', { valueString: assessment.turnId }),
@@ -133,45 +109,6 @@ class ClinicalImpression {
133
109
  if (assessment.notes.length) payload.note = assessment.notes.map((text) => ({ text }));
134
110
  return new ClinicalImpression(payload);
135
111
  }
136
-
137
- static fromPalliativeAssessment({ assessment }) {
138
- const payload = {
139
- resourceType: 'ClinicalImpression',
140
- id: fhirId(assessment.assessmentId),
141
- identifier: [identifier(PALLIATIVE_ASSESSMENT_SLUG, assessment.assessmentId)],
142
- status: 'completed',
143
- subject: patientReference(assessment.patientId),
144
- effectiveDateTime: toIso(assessment.committedAt),
145
- finding: assessment.findings.map(palliativeFinding),
146
- extension: [extension('palliative-turn-id', { valueString: assessment.turnId })],
147
- };
148
- if (assessment.evaluationSummary) payload.summary = assessment.evaluationSummary;
149
- if (assessment.rankingRationale) payload.description = assessment.rankingRationale;
150
- if (assessment.escalationReason) payload.note = [{ text: assessment.escalationReason }];
151
- return new ClinicalImpression(payload);
152
- }
153
- }
154
-
155
- function palliativeFinding(finding) {
156
- const extensions = [
157
- extension('palliative-finding-rank', { valueInteger: finding.rank }),
158
- extension('palliative-finding-element-type', { valueString: finding.elementType }),
159
- extension('palliative-finding-time-window', { valueString: finding.timeWindow }),
160
- extension('palliative-finding-clinical-description', { valueString: finding.clinicalDescription }),
161
- extension('palliative-finding-why-relevant', { valueString: finding.whyRelevant }),
162
- extension('palliative-finding-qol-criteria', { valueString: finding.qolCriteria }),
163
- extension('palliative-finding-improvement-criteria', { valueString: finding.improvementCriteria }),
164
- extension('palliative-finding-characterization-criteria', { valueString: finding.characterizationCriteria }),
165
- extension('palliative-finding-audit-notes', { valueString: finding.auditNotes }),
166
- ];
167
- for (const term of finding.symptomTerms) extensions.push(extension('palliative-finding-symptom-term', { valueString: term }));
168
- for (const question of finding.probeQuestions) extensions.push(extension('palliative-finding-probe-question', { valueString: question }));
169
- if (finding.qolImpact !== null) extensions.push(extension('palliative-finding-qol-impact', { valueString: finding.qolImpact }));
170
- if (finding.improvementCapacity !== null) extensions.push(extension('palliative-finding-improvement-capacity', { valueString: finding.improvementCapacity }));
171
- if (finding.areMedsNeeded !== null) extensions.push(extension('palliative-finding-are-meds-needed', { valueBoolean: finding.areMedsNeeded }));
172
- if (finding.needsCharacterization !== null) extensions.push(extension('palliative-finding-needs-characterization', { valueBoolean: finding.needsCharacterization }));
173
- if (finding.clusterNote !== null) extensions.push(extension('palliative-finding-cluster-note', { valueString: finding.clusterNote }));
174
- return { item: { concept: codeableConcept(finding.nameShort) }, extension: extensions };
175
112
  }
176
113
 
177
114
  module.exports = {
@@ -89,26 +89,6 @@ class Provenance {
89
89
  });
90
90
  }
91
91
 
92
- static fromSafetyGate({ gate }) {
93
- return Provenance.fromSymptomCase({
94
- provenanceId: `${gate.runId}-safety-prov`,
95
- targetRefs: [{ reference: `RiskAssessment/${fhirId(`${gate.runId}-safety`)}` }],
96
- recordedAt: gate.createdAt,
97
- activityText: 'recommendation-safety-gate',
98
- });
99
- }
100
-
101
- static fromRecommendationPlan({ plan, targetRefs }) {
102
- return new Provenance({
103
- resourceType: 'Provenance',
104
- id: fhirId(`${plan.runId}-careplan-prov`),
105
- target: targetRefs,
106
- recorded: toIso(plan.createdAt),
107
- agent: [{ who: reference('Device', getDeviceId()), onBehalfOf: reference('Organization', getOrganizationId()) }],
108
- activity: codeableConcept('recommendations-process'),
109
- });
110
- }
111
-
112
92
  static fromContingency({ planId, recipients, recordedAt, activityText }) {
113
93
  const targets = [{ reference: `CarePlan/${fhirId(planId)}` }];
114
94
  for (const recipient of recipients) {
@@ -8,30 +8,13 @@ const {
8
8
  extension,
9
9
  toIso,
10
10
  } = require('../helpers/elementHelper');
11
- const { PATIENT_RISK_SLUG, SAFETY_GATE_SLUG } = require('../constants/projectionSlugs');
11
+ const { PATIENT_RISK_SLUG } = require('../constants/projectionSlugs');
12
12
 
13
13
  const RISK_SEVERITY_CODESYSTEM = 'risk-severity';
14
14
  const PREDICTION_KIND_AE = 'ae';
15
15
  const PREDICTION_KIND_DISEASE = 'disease';
16
16
  const PREDICTION_KIND_COMORBIDITY = 'comorbidity';
17
17
 
18
- const PROBABILITY_TO_RISK = { high: 'high', medium: 'moderate', low: 'low' };
19
- const PREDICTION_BASIS_STATUSES = ['confirmed', 'partially_confirmed'];
20
-
21
- function safetyGatePrediction(ae) {
22
- const risk = PROBABILITY_TO_RISK[ae.probabilityGivenPatient] || ae.probabilityGivenPatient;
23
- const prediction = {
24
- outcome: codeableConcept(ae.ctcaeTerm),
25
- qualitativeRisk: codeableConcept(risk, { code: risk, system: codeSystemUrl(RISK_SEVERITY_CODESYSTEM) }),
26
- };
27
- const rationaleBits = [];
28
- if (ae.whyIsRelevant) rationaleBits.push(ae.whyIsRelevant);
29
- const grounding = ae.groundedIn.filter(Boolean).join('; ');
30
- if (grounding) rationaleBits.push(`sustento: ${grounding}`);
31
- if (rationaleBits.length) prediction.rationale = rationaleBits.join(' | ');
32
- return prediction;
33
- }
34
-
35
18
  function aePrediction(ae) {
36
19
  const prediction = {
37
20
  outcome: codeableConcept(ae.aeName),
@@ -106,28 +89,6 @@ class RiskAssessment {
106
89
  if (extensions.length) payload.extension = extensions;
107
90
  return new RiskAssessment(payload);
108
91
  }
109
-
110
- static fromSafetyGate({ gate }) {
111
- const payload = {
112
- resourceType: 'RiskAssessment',
113
- id: fhirId(`${gate.runId}-safety`),
114
- identifier: [identifier(SAFETY_GATE_SLUG, `${gate.runId}-safety`)],
115
- status: 'final',
116
- subject: patientReference(gate.patientId),
117
- performer: reference('Device', getDeviceId()),
118
- occurrenceDateTime: toIso(gate.createdAt),
119
- };
120
- const prediction = gate.redFlags
121
- .filter((redFlag) => PREDICTION_BASIS_STATUSES.includes(redFlag.verificationStatus))
122
- .flatMap((redFlag) => redFlag.relatedAes.map(safetyGatePrediction));
123
- if (prediction.length) payload.prediction = prediction;
124
- if (gate.summary) payload.note = [{ text: gate.summary }];
125
- if (gate.urgentUnder24h && gate.escalationRoute) {
126
- payload.mitigation = `ruta de escalamiento: ${gate.escalationRoute}`
127
- + (gate.maxActionTimeHours ? `; actuar en ≤${gate.maxActionTimeHours}h` : '');
128
- }
129
- return new RiskAssessment(payload);
130
- }
131
92
  }
132
93
 
133
94
  module.exports = {
@@ -16,8 +16,6 @@ const {
16
16
  INTERVENTION_KIND_SLUG,
17
17
  INTERVENTION_OUTCOME_SLUG,
18
18
  ROUTING_DISPOSITION_SLUG,
19
- RECOMMENDATION_REC_SLUG,
20
- PALLIATIVE_ESCALATION_SLUG,
21
19
  } = require('../constants/projectionSlugs');
22
20
  const { SEVERITY_TO_PRIORITY } = require('../constants/severityPriority');
23
21
  const { interventionExtensions } = require('./interventionExtensions');
@@ -85,61 +83,6 @@ class Task {
85
83
  return new Task(payload);
86
84
  }
87
85
 
88
- static fromRecommendationPlan({ plan, element, taskId }) {
89
- const { recommendation } = element;
90
- const { patient } = recommendation;
91
- const code = {
92
- text: element.nameShort,
93
- coding: element.targetedCtcaeTerms.map((term) => ({
94
- system: codeSystemUrl('pro-ctcae'), code: term, display: term,
95
- })),
96
- };
97
- const noteTexts = [];
98
- if (patient.whenToEscalate) noteTexts.push(`cuándo escalar: ${patient.whenToEscalate}`);
99
- if (patient.expectedEffectiveness) noteTexts.push(`efectividad esperada: ${patient.expectedEffectiveness}`);
100
- if (patient.contraindications) noteTexts.push(`contraindicaciones: ${patient.contraindications}`);
101
- noteTexts.push(`requiere coordinación farmacológica: ${recommendation.isPharmacologicalMedNeeded}`);
102
- const payload = {
103
- resourceType: 'Task',
104
- id: fhirId(taskId),
105
- identifier: [identifier(RECOMMENDATION_REC_SLUG, taskId)],
106
- status: 'requested',
107
- intent: 'plan',
108
- code,
109
- description: patient.mainRecommendation,
110
- for: patientReference(plan.patientId),
111
- authoredOn: toIso(plan.createdAt),
112
- requester: reference('Device', getDeviceId()),
113
- note: noteTexts.map((text) => ({ text })),
114
- };
115
- return new Task(payload);
116
- }
117
-
118
- static fromPalliativeEscalation({ assessmentId, committedAt, finding, deviceId }) {
119
- const { escalation } = finding;
120
- const start = new Date(committedAt);
121
- const end = new Date(start.getTime() + escalation.maxActionTimeHours * 3600000);
122
- const payload = {
123
- resourceType: 'Task',
124
- id: fhirId(`${assessmentId}-${finding.rank}`),
125
- identifier: [identifier(PALLIATIVE_ESCALATION_SLUG, `${assessmentId}-${finding.rank}`)],
126
- status: 'requested',
127
- intent: 'order',
128
- priority: 'urgent',
129
- code: codeableConcept(finding.nameShort),
130
- description: escalation.escalationReason,
131
- for: patientReference(escalation.patientId),
132
- authoredOn: toIso(committedAt),
133
- requester: reference('Device', deviceId),
134
- restriction: { period: { end: toIso(end) } },
135
- extension: [extension('palliative-escalation-rank', { valueInteger: finding.rank })],
136
- };
137
- if (escalation.escalationRoutes.length) {
138
- payload.requestedPerformer = escalation.escalationRoutes.map((route) => ({ concept: codeableConcept(route) }));
139
- }
140
- return new Task(payload);
141
- }
142
-
143
86
  static fromRoutingDisposition({ disposition }) {
144
87
  const payload = {
145
88
  resourceType: 'Task',
@@ -4,7 +4,6 @@ const { fhirId } = require('../helpers/fhirHelper');
4
4
  const { identifierValue, extValues } = require('../helpers/fhirReadHelper');
5
5
  const { PATIENT_RISK_SLUG } = require('../constants/projectionSlugs');
6
6
  const { PROJECTOR_NAME } = require('../projections/riskProjection');
7
- const { PROJECTOR_NAME: SAFETY_GATE_PROJECTOR_NAME } = require('../projections/safetyGateProjection');
8
7
  const {
9
8
  PREDICTION_KIND_AE,
10
9
  PREDICTION_KIND_DISEASE,
@@ -44,22 +43,6 @@ async function readPatientRisk({ patientId }) {
44
43
  return null;
45
44
  }
46
45
 
47
- async function storeSafetyGate({ patientId, safetyGate }) {
48
- if (safetyGate.patientId !== patientId) {
49
- throw new Error(`storeSafetyGate patientId mismatch: expected ${patientId}`);
50
- }
51
- const bundle = project([{ name: SAFETY_GATE_PROJECTOR_NAME, aggregate: safetyGate }]);
52
- const resources = bundle.entry.map((entry) => entry.resource);
53
- const { stored } = await storeResources(resources);
54
- return { count: stored.length, ids: stored };
55
- }
56
-
57
- async function safetyGateExists({ runId }) {
58
- const store = getFhirStore();
59
- const resource = await store.getByKey(`Provenance|${fhirId(`${runId}-safety-prov`)}`);
60
- return Boolean(resource);
61
- }
62
-
63
46
  function reconstructRisk(resource, patientId) {
64
47
  const predictions = resource.prediction || [];
65
48
  return {
@@ -115,6 +98,4 @@ const outcomeText = (prediction) => (prediction.outcome && prediction.outcome.te
115
98
  module.exports = {
116
99
  storePatientRisk,
117
100
  readPatientRisk,
118
- storeSafetyGate,
119
- safetyGateExists,
120
101
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "5.9.2",
3
+ "version": "5.10.0-dev.1020",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -1,62 +0,0 @@
1
- const crypto = require('node:crypto');
2
-
3
- const { PalliativeAssessment } = require('../../shared/dtos/PalliativeAssessment');
4
-
5
- const uid = (prefix) => `${prefix}_${crypto.randomUUID().replace(/-/g, '').slice(0, 12)}`;
6
-
7
- function assessmentUid() {
8
- return uid('pa');
9
- }
10
-
11
- function mapEscalation(raw, patientId) {
12
- if (!raw || typeof raw !== 'object') return null;
13
- return {
14
- patientId,
15
- maxActionTimeHours: raw.max_action_time_hours,
16
- escalationRoutes: raw.escalation_routes || [],
17
- escalationReason: raw.escalation_reason || '',
18
- };
19
- }
20
-
21
- function mapFinding(raw, patientId) {
22
- if (!raw || typeof raw !== 'object') return {};
23
- return {
24
- rank: raw.rank,
25
- elementType: raw.element_type,
26
- nameShort: raw.name_short,
27
- timeWindow: raw.time_window || '',
28
- symptomTerms: raw.symptom_terms || [],
29
- clinicalDescription: raw.clinical_description || '',
30
- whyRelevant: raw.why_relevant || '',
31
- qolImpact: raw.qol_impact ?? null,
32
- qolCriteria: raw.qol_criteria || '',
33
- improvementCapacity: raw.improvement_capacity ?? null,
34
- improvementCriteria: raw.improvement_criteria || '',
35
- areMedsNeeded: raw.are_meds_needed ?? null,
36
- needsCharacterization: raw.needs_characterization ?? null,
37
- characterizationCriteria: raw.characterization_criteria || '',
38
- probeQuestions: raw.probe_questions || [],
39
- auditNotes: raw.audit_notes || '',
40
- clusterNote: raw.cluster_note ?? null,
41
- escalation: mapEscalation(raw.escalation, patientId),
42
- };
43
- }
44
-
45
- function buildAssessment({ raw, assessmentId, patientId, turnId, committedAt }) {
46
- const findings = Array.isArray(raw.findings) ? raw.findings : [];
47
- return new PalliativeAssessment({
48
- assessmentId,
49
- patientId,
50
- turnId,
51
- committedAt,
52
- evaluationSummary: raw.evaluation_summary || '',
53
- rankingRationale: raw.ranking_rationale || '',
54
- escalationReason: raw.escalation_reason ?? null,
55
- findings: findings.map((finding) => mapFinding(finding, patientId)),
56
- });
57
- }
58
-
59
- module.exports = {
60
- assessmentUid,
61
- buildAssessment,
62
- };
@@ -1,37 +0,0 @@
1
- const mongoose = require('mongoose');
2
-
3
- const { clinicalConnection } = require('../config/connection');
4
-
5
- const patternConsultSchema = new mongoose.Schema({
6
- consultationId: { type: String, required: true },
7
- patientCode: { type: String, required: true },
8
- turnId: { type: String, default: null },
9
- concern: { type: String, default: null },
10
- impressions: { type: Array, default: [] },
11
- recommendedProbes: { type: [String], default: [] },
12
- watchFor: { type: [String], default: [] },
13
- escalationAdvice: { type: Object, default: null },
14
- degraded: { type: Boolean, default: false },
15
- degradedReasons: { type: [String], default: [] },
16
- }, { timestamps: true });
17
-
18
- patternConsultSchema.index({ consultationId: 1 }, { unique: true });
19
- patternConsultSchema.index({ patientCode: 1, createdAt: -1 });
20
-
21
- function patternConsultStore() {
22
- const conn = clinicalConnection();
23
- const model = conn.models.PatternConsult || conn.model('PatternConsult', patternConsultSchema, 'patternConsult');
24
-
25
- return {
26
- get(consultationId) {
27
- return model.findOne({ consultationId }).lean();
28
- },
29
- put(doc) {
30
- return model.create(doc);
31
- },
32
- };
33
- }
34
-
35
- module.exports = {
36
- patternConsultStore,
37
- };