@peopl-health/nexus 5.71.9 → 5.71.10
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.
|
@@ -78,11 +78,18 @@ async function extractClinicalMentions({ text, turnId, code, provider, detectedT
|
|
|
78
78
|
const { datosClinicos, contextGaps } = await loadPatientContext(code);
|
|
79
79
|
const vagueWords = await getVagueEntityWords();
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
let timed;
|
|
82
|
+
try {
|
|
83
|
+
timed = await withTiming(() => provider.runStructured({
|
|
84
|
+
presetId,
|
|
85
|
+
input: [{ role: 'user', content: buildExtractorInput(text, detectedTags, curatedRows, template, { priorTurns, patientContext: datosClinicos }) }],
|
|
86
|
+
metadata: { extractor: 'clinical-mention', turnId },
|
|
87
|
+
}));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error('[clinicalExtraction] extractor provider unavailable; returning deterministic safety flags only', { turnId, error: err?.message });
|
|
90
|
+
return { completed: false, unavailable: Boolean(patterns?.length), clinicalMentions: [], safetyFlags: [...flags], safetyFlagSources: sources() };
|
|
91
|
+
}
|
|
92
|
+
const { result, durationMs } = timed;
|
|
86
93
|
trace?.addSubCall({ response: result, durationMs, presetId });
|
|
87
94
|
|
|
88
95
|
if (result?.status && result.status !== 'completed') throw new Error(`clinical extraction incomplete: status=${result.status}`);
|
|
@@ -38,6 +38,8 @@ function nonClinicalRequestSchema() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
const MAX_CONTEXT_TURNS = 6;
|
|
41
|
+
const EXTRACTOR_UNAVAILABLE = 'extractor_unavailable';
|
|
42
|
+
const ESCALATION_WHILE_UNAVAILABLE = 'described in the patient\'s words and graded on the symptom itself; never mention the extractor or a technical failure, to the patient or the team.';
|
|
41
43
|
|
|
42
44
|
const definition = {
|
|
43
45
|
name: 'extractClinicalInfo',
|
|
@@ -239,13 +241,19 @@ async function handler(args, context = {}) {
|
|
|
239
241
|
});
|
|
240
242
|
}
|
|
241
243
|
|
|
244
|
+
if (!intakeCompleted && extraction.unavailable && !safetyFlags.length && !detectedTags.includes('symptom')) {
|
|
245
|
+
return degradedIntake(trace);
|
|
246
|
+
}
|
|
247
|
+
|
|
242
248
|
if (!intakeCompleted) {
|
|
243
249
|
return JSON.stringify({
|
|
244
250
|
success: false,
|
|
245
251
|
error: 'clinical_intake_incomplete',
|
|
246
252
|
data: {
|
|
247
253
|
safety_flags_received: safetyFlags,
|
|
248
|
-
recovery:
|
|
254
|
+
recovery: extraction.unavailable
|
|
255
|
+
? `The clinical extractor is unavailable and a retry will not succeed. Send what the message reports to the team with reportMedicalEscalation (reportCrisis for a crisis), ${ESCALATION_WHILE_UNAVAILABLE}`
|
|
256
|
+
: 'Retry extractClinicalInfo with the complete patient message and all applicable supported tags.',
|
|
249
257
|
},
|
|
250
258
|
});
|
|
251
259
|
}
|
|
@@ -312,6 +320,10 @@ async function declareNonClinical({ declaredRequest, rawMessage, intakeSource, p
|
|
|
312
320
|
});
|
|
313
321
|
}
|
|
314
322
|
|
|
323
|
+
if (check?.unavailable && !safetyFlags.length) {
|
|
324
|
+
return degradedIntake(trace, { non_clinical_request: declaredRequest });
|
|
325
|
+
}
|
|
326
|
+
|
|
315
327
|
trace?.setSignals?.({ clinicalIntakeCompleted: false, clinicalMentions: [], safetyFlags });
|
|
316
328
|
const supported = [...SUPPORTED_TAGS].sort();
|
|
317
329
|
const reason = found
|
|
@@ -325,6 +337,20 @@ async function declareNonClinical({ declaredRequest, rawMessage, intakeSource, p
|
|
|
325
337
|
});
|
|
326
338
|
}
|
|
327
339
|
|
|
340
|
+
function degradedIntake(trace, data = {}) {
|
|
341
|
+
trace?.setSignals?.({ clinicalIntakeCompleted: true, clinicalIntakeDegraded: EXTRACTOR_UNAVAILABLE, clinicalMentions: [], safetyFlags: [] });
|
|
342
|
+
return JSON.stringify({
|
|
343
|
+
success: true,
|
|
344
|
+
data: {
|
|
345
|
+
...data,
|
|
346
|
+
degraded: true,
|
|
347
|
+
degraded_reasons: [EXTRACTOR_UNAVAILABLE],
|
|
348
|
+
clinical_facts: [],
|
|
349
|
+
agent_hint: `The clinical extractor is unavailable, so nothing from this message was recorded as clinical information. Do not call extractClinicalInfo again this turn. A new or worsening symptom goes to the team with reportMedicalEscalation, ${ESCALATION_WHILE_UNAVAILABLE} Anything else gets a reply only.`,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
328
354
|
async function scanSafetyFlags(text) {
|
|
329
355
|
try {
|
|
330
356
|
const patterns = await getSafetyFlagPatterns();
|
package/lib/utils/retryUtils.js
CHANGED
|
@@ -53,6 +53,7 @@ function isAbortError(error) {
|
|
|
53
53
|
function isRetryableError(error) {
|
|
54
54
|
if (!error) return false;
|
|
55
55
|
if (isAbortError(error)) return false;
|
|
56
|
+
if (error.code === 'insufficient_quota') return false;
|
|
56
57
|
|
|
57
58
|
const status = error?.status;
|
|
58
59
|
const code = error?.code;
|