@peopl-health/nexus 6.4.0-dev.18776 → 6.4.0-dev.18777

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.
@@ -10,7 +10,7 @@ const REQUIRED_SECTION = 'risk';
10
10
 
11
11
  const definition = {
12
12
  name: 'getRouterContextBundle',
13
- description: '**Does:** Batched router-context fetch — always evaluates and returns the mandatory `risk` section, plus the requested `landscape` and/or `history` sections. Read-only.\n\n**Required inputs:** none. Optional `include` selects additional sections (`landscape`, `history`; a supplied `risk` value is accepted but redundant), plus optional `landscape_min_priority`, `history_scope`, `history_filter`. Omitting `include` returns all three sections.\n\n**When to call:** after intake at the start of every routing turn, as the single load of the patient\'s clinical context. Do not call it in parallel with `Skill(clinical_routing)`: first read the skill body, then call this bundle. The patient\'s risk disposition cannot be excluded by `include`. Prefer this over calling `getActiveSymptomLandscape`, `getPatientHistory` or `getPatientRiskAssessment` for the same context.\n\n**`turn_focus.recent_escalations` — what you already sent the team.** Every escalation dispatched for this patient in the last 14 days, newest first, with `escalated_at`, `days_since`, `urgency`, `reason` and the `channels` used. Read it before `reportMedicalEscalation`. It records only what was SENT; where the team is with it is in `team_cases`. A complaint unchanged since an escalation a day ago rarely needs a second dispatch; one still uncontrolled days after its escalation needs one at a HIGHER urgency, and `urgency_justification` should say which of the two this is. Empty means nothing was escalated in the window.\n\n**`turn_focus.team_cases` — where the care team is with each case.** Every case the team still holds, and those it closed in the last 14 days, newest first: `status`, `is_open`, `level`, `opened_at`, `days_since`, `doctor_decision` and `case_ids` (the symptom cases it covers).\n- `Por revisar`, `En evaluación`, `Con el médico`, `Indicación por enviar`, `Requiere valoración`: the team has it and has not answered yet. Tell the patient it is with the team, without promising a time. A new escalation is for a new symptom, a higher grade, a new red flag, or a symptom that persists or worsens while it waits (send it as an update).\n- `Esperando paciente`, `Indicaciones dadas`, `En vigilancia`: the team already answered (an indication on its way to the patient, given, or under watch). This comes first: ask how the indication is going before escalating again.\n- `Cerrado`, `No requiere`: the team closed it. A new complaint is a new decision.\n`doctor_decision` is internal (`approved`, `edited_approved`, `monitor`, `needs_intervention` = the doctor asked for an in-person evaluation, `returned`, `direct_chat`): use it to understand the case, never quote it. `team_cases` never says whether the problem resolved; ask the patient. An escalation in `recent_escalations` with no case here has an unknown team status, not an absent one.\n\n**Use `turn_focus.symptom_history` before grading or escalating.** It carries what no other section does: how this patient compares to their own past. `worst_grade` and `worst_reported_at` are the worst they have ever been recorded, `at_personal_worst` says whether they are back at it, and `days_since_last_report` says how stale `last_grade` is. (The current grade and trajectory are already in `landscape.active_cases` — do not expect them repeated here.) Escalate on the comparison, not on severity alone: a patient at their personal worst is a different decision from one below it, and a grade unchanged for weeks is different from the same grade reported yesterday. When you ask about it, anchor the question in the value and the date so the patient can compare. Empty means no graded case on record. \n\n**A case flagged `stale_no_recent_evidence` has had no new evidence for two weeks or more.** `days_since_evidence` says how long. Ask whether the symptom is still there, and close the case with `recordClinicalImpression` `close` when it is not: an open case keeps consuming the landscape and keeps the symptom reading as active on the record.\n\n**How to use risk:** read `risk.turn_focus` first — it is always present. It deterministically keeps every current symptom and maps it to relevant full-profile risks, `checks_now`, and `escalation_threshold`; use the full `risk.profile` only for supporting detail. A highest-severity fallback is labelled explicitly when no direct symptom match exists. When `turn_focus.selection` is `no_current_concerns` the patient reported no current symptom this turn.\n\n**`risk.profile` is a forecast, not history:** `predicted_aes[].minimum_symptom_set`, `disease_risks[].warning_symptoms` and every other profile list name symptoms to watch for, none of which the patient has reported. Never tell the patient a profile symptom appears in their report, and never attach a CTCAE grade to one. Reported symptoms come only from `landscape`, `history` and `turn_focus.current_concerns`.\n\n**Recovery:** verify `sections_returned` contains `risk`. If it does not, inspect `_diagnostics.risk_error` and call `getPatientRiskAssessment` before `submitRoutingDecision`.\n\n**Returns:** `patient_id`, `sections_requested[]`, `sections_returned[]` (only sections that succeeded), mandatory `risk` (always including `turn_focus`), and requested `landscape` / `history`. A section that errors is omitted and its error reported under `_diagnostics`. The attempted risk read and deterministic turn processing are recorded on the turn trace even when no profile exists.',
13
+ description: '**Does:** Batched router-context fetch — always evaluates and returns the mandatory `risk` section, plus the requested `landscape` and/or `history` sections. Read-only.\n\n**Required inputs:** none. Optional `include` selects additional sections (`landscape`, `history`; a supplied `risk` value is accepted but redundant), plus optional `landscape_min_priority`, `history_scope`, `history_filter`. Omitting `include` returns all three sections.\n\n**When to call:** after intake at the start of every routing turn, as the single load of the patient\'s clinical context. Do not call it in parallel with `Skill(clinical_routing)`: first read the skill body, then call this bundle. The patient\'s risk disposition cannot be excluded by `include`. Prefer this over calling `getActiveSymptomLandscape`, `getPatientHistory` or `getPatientRiskAssessment` for the same context.\n\n**`turn_focus.recent_escalations` — what you already sent the team.** Every escalation dispatched for this patient in the last 14 days, newest first, with `escalated_at`, `days_since`, `urgency`, `reason` and the `channels` used. Read it before `reportMedicalEscalation`. It records only what was SENT; where the team is with it is in `team_cases`. A complaint unchanged since an escalation a day ago rarely needs a second dispatch; one still uncontrolled days after its escalation needs one at a HIGHER urgency, and `urgency_justification` should say which of the two this is. Empty means nothing was escalated in the window.\n\n**`turn_focus.team_cases` — where the care team is with each case.** Every case the team still holds, and those it closed in the last 14 days, newest first: `status`, `is_open`, `level`, `opened_at`, `days_since`, `doctor_decision` and `case_ids` (the symptom cases it covers).\n- `Por revisar`, `En evaluación`, `Con el médico`, `Indicación por enviar`, `Requiere valoración`: the team has it and has not answered yet. Tell the patient it is with the team, without promising a time. A new escalation is for a new symptom, a higher grade, a new red flag, or a symptom that persists or worsens while it waits (send it as an update).\n- `Esperando paciente`, `Indicaciones dadas`, `En vigilancia`: the team already answered (an indication on its way to the patient, given, or under watch). This comes first: ask how the indication is going before escalating again.\n- `Cerrado`, `No requiere`: the team closed it. A new complaint is a new decision.\n`doctor_decision` is internal (`approved`, `edited_approved`, `plan_approved` = the doctor gave a structured plan, `monitor`, `needs_intervention` = the doctor asked for an in-person evaluation, `returned`, `direct_chat`): use it to understand the case, never quote it. `team_cases` never says whether the problem resolved; ask the patient. An escalation in `recent_escalations` with no case here has an unknown team status, not an absent one.\n\n**Use `turn_focus.symptom_history` before grading or escalating.** It carries what no other section does: how this patient compares to their own past. `worst_grade` and `worst_reported_at` are the worst they have ever been recorded, `at_personal_worst` says whether they are back at it, and `days_since_last_report` says how stale `last_grade` is. (The current grade and trajectory are already in `landscape.active_cases` — do not expect them repeated here.) Escalate on the comparison, not on severity alone: a patient at their personal worst is a different decision from one below it, and a grade unchanged for weeks is different from the same grade reported yesterday. When you ask about it, anchor the question in the value and the date so the patient can compare. Empty means no graded case on record. \n\n**A case flagged `stale_no_recent_evidence` has had no new evidence for two weeks or more.** `days_since_evidence` says how long. Ask whether the symptom is still there, and close the case with `recordClinicalImpression` `close` when it is not: an open case keeps consuming the landscape and keeps the symptom reading as active on the record.\n\n**How to use risk:** read `risk.turn_focus` first — it is always present. It deterministically keeps every current symptom and maps it to relevant full-profile risks, `checks_now`, and `escalation_threshold`; use the full `risk.profile` only for supporting detail. A highest-severity fallback is labelled explicitly when no direct symptom match exists. When `turn_focus.selection` is `no_current_concerns` the patient reported no current symptom this turn.\n\n**`risk.profile` is a forecast, not history:** `predicted_aes[].minimum_symptom_set`, `disease_risks[].warning_symptoms` and every other profile list name symptoms to watch for, none of which the patient has reported. Never tell the patient a profile symptom appears in their report, and never attach a CTCAE grade to one. Reported symptoms come only from `landscape`, `history` and `turn_focus.current_concerns`.\n\n**Recovery:** verify `sections_returned` contains `risk`. If it does not, inspect `_diagnostics.risk_error` and call `getPatientRiskAssessment` before `submitRoutingDecision`.\n\n**Returns:** `patient_id`, `sections_requested[]`, `sections_returned[]` (only sections that succeeded), mandatory `risk` (always including `turn_focus`), and requested `landscape` / `history`. A section that errors is omitted and its error reported under `_diagnostics`. The attempted risk read and deterministic turn processing are recorded on the turn trace even when no profile exists.',
14
14
  strict: false,
15
15
  replayScope: 'turn',
16
16
  parameters: {
@@ -2163,7 +2163,8 @@ async function notifyPlanIssues(review, issues) {
2163
2163
  const header = '⚠️ Revisar el envío del plan';
2164
2164
  const lines = issues.map((issue) => `- ${issue}`);
2165
2165
  const action = 'Qué hacer: revisar el chat; corregir con la paciente o volver a mandar la receta si hace falta';
2166
- const recordId = [...currentAlarms(review)].reverse().find((alarm) => alarm.emergencyRecordId)?.emergencyRecordId;
2166
+ // Every alarm, not only those since the decision: the plan is always sent after it, so the case's alarm came before.
2167
+ const recordId = [...(review.alarms ?? [])].reverse().find((alarm) => alarm.emergencyRecordId)?.emergencyRecordId;
2167
2168
  const quoted = await readGroupMessageId(recordId, review.patientCode);
2168
2169
  const sent = await sendGroupNotice([header, `${patient} (${doctor})`, ...lines, action, link].join('\n'), quoted, [header, ...lines, action].join('\n'));
2169
2170
  if (!sent.delivered) logger.error('[EscalationReview] The plan check notice did not reach the group', { escalationReviewId: String(review._id), error: sent.error });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "6.4.0-dev.18776",
3
+ "version": "6.4.0-dev.18777",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",