@peopl-health/nexus 6.3.1 → 6.4.0-dev.18675

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.
@@ -0,0 +1,165 @@
1
+ const { httpError } = require('../../utils/httpErrorUtils');
2
+ const { oneLineOf } = require('../../utils/coercionUtils');
3
+ const { PLAN_FOLLOW_UPS } = require('../../models/escalationReviewModel');
4
+
5
+ const DEFAULT_FOLLOW_UP_HOURS = { check_in: 24, vigilancia: 48 };
6
+ const FIRST_LINE = '1L';
7
+ const MAX_ENTRIES = 10;
8
+ const MAX_FIELD_LENGTH = 300;
9
+ const MAX_NOTE_LENGTH = 1000;
10
+ const MAX_INSTRUCTIONS_LENGTH = 4000;
11
+ const MAX_DAYS = 90;
12
+ const MAX_FOLLOW_UP_HOURS = 168;
13
+ // A route is not a dose: the doctor still says how many tablets.
14
+ const ROUTE_ONLY = /^(vo|v[ií]a oral|oral)$/i;
15
+
16
+ const textOf = (value, name, max = MAX_FIELD_LENGTH) => {
17
+ if (value != null && typeof value !== 'string') throw httpError(400, `${name} must be text`);
18
+ const text = oneLineOf(value);
19
+ if (text.length > max) throw httpError(400, `${name} exceeds ${max} characters`);
20
+ return text || null;
21
+ };
22
+
23
+ const neededText = (value, name) => {
24
+ const text = textOf(value, name);
25
+ if (!text) throw httpError(400, `${name} is required`);
26
+ return text;
27
+ };
28
+
29
+ const isPlainObject = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
30
+
31
+ // Only a number or a string of digits: `true`, `[5]` and `'1e1'` are not days.
32
+ const wholeNumber = (value, name, max) => {
33
+ const number = typeof value === 'number' || (typeof value === 'string' && /^\d+$/.test(value.trim())) ? Number(value) : NaN;
34
+ if (!Number.isInteger(number) || number < 1 || number > max) throw httpError(400, `${name} must be a whole number from 1 to ${max}`);
35
+ return number;
36
+ };
37
+
38
+ const listOf = (value, name) => {
39
+ if (value == null) return [];
40
+ if (!Array.isArray(value)) throw httpError(400, `${name} must be a list`);
41
+ if (value.length > MAX_ENTRIES) throw httpError(400, `${name} allows at most ${MAX_ENTRIES} entries`);
42
+ return value;
43
+ };
44
+
45
+ function itemOf(item, index) {
46
+ const name = `plan.items[${index}]`;
47
+ const position = textOf(item?.position, `${name}.position`);
48
+ const reason = textOf(item?.reason, `${name}.reason`);
49
+ // An alternative to the first line has to say why the first line does not fit.
50
+ if (position && position !== FIRST_LINE && !reason) throw httpError(400, `${name}.reason is required for a drug that is not first line`);
51
+ return {
52
+ drug: neededText(item?.drug, `${name}.drug`),
53
+ strength: textOf(item?.strength, `${name}.strength`),
54
+ dose: neededText(item?.dose, `${name}.dose`),
55
+ frequency: neededText(item?.frequency, `${name}.frequency`),
56
+ days: wholeNumber(item?.days, `${name}.days`, MAX_DAYS),
57
+ position,
58
+ reason,
59
+ formularyKey: textOf(item?.formularyKey, `${name}.formularyKey`),
60
+ };
61
+ }
62
+
63
+ function followUpOf(value) {
64
+ if (value != null && !isPlainObject(value)) throw httpError(400, 'plan.followUp must be an object with a kind');
65
+ const kind = value?.kind ?? 'none';
66
+ if (!PLAN_FOLLOW_UPS.includes(kind)) throw httpError(400, `plan.followUp.kind must be one of: ${PLAN_FOLLOW_UPS.join(', ')}`);
67
+ if (kind === 'none') return { kind, hours: null };
68
+ return { kind, hours: wholeNumber(value?.hours ?? DEFAULT_FOLLOW_UP_HOURS[kind], 'plan.followUp.hours', MAX_FOLLOW_UP_HOURS) };
69
+ }
70
+
71
+ // The plan as the doctor approved it, refused whole when any line is incomplete.
72
+ function planOf(input) {
73
+ if (!isPlainObject(input)) throw httpError(400, 'plan is required');
74
+ const plan = {
75
+ items: listOf(input.items, 'plan.items').map((item, index) => {
76
+ if (!isPlainObject(item)) throw httpError(400, `plan.items[${index}] must be an object`);
77
+ return itemOf(item, index);
78
+ }),
79
+ studies: listOf(input.studies, 'plan.studies').map((study, index) => ({
80
+ name: neededText(study?.name, `plan.studies[${index}].name`),
81
+ instructions: textOf(study?.instructions, `plan.studies[${index}].instructions`),
82
+ })),
83
+ homeCare: listOf(input.homeCare, 'plan.homeCare').map((entry, index) => neededText(entry, `plan.homeCare[${index}]`)),
84
+ note: textOf(input.note, 'plan.note', MAX_NOTE_LENGTH),
85
+ followUp: followUpOf(input.followUp),
86
+ };
87
+ if (!plan.items.length && !plan.studies.length && !plan.homeCare.length && !plan.note) {
88
+ throw httpError(400, 'plan needs a drug, a study, a home care measure or a note');
89
+ }
90
+ return plan;
91
+ }
92
+
93
+ const needsPrescriber = (plan) => plan.items.length > 0 || plan.studies.length > 0;
94
+
95
+ const daysText = (days) => (days === 1 ? '1 día' : `${days} días`);
96
+
97
+ const FOLLOW_UP_LINES = {
98
+ check_in: (hours) => `Seguimiento: le escribiremos en unas ${hours} horas para ver cómo va.`,
99
+ vigilancia: (hours) => `Seguimiento: queda en vigilancia las próximas ${hours} horas; le escribiremos para ver cómo va.`,
100
+ };
101
+
102
+ // What the assistant is told to pass on; it writes the patient message itself.
103
+ function planInstructions(plan) {
104
+ const lines = ['Transmítele al paciente lo que indicó el médico, con tus palabras: no cambies dosis, frecuencia ni días, y no agregues otros medicamentos ni estudios.'];
105
+ if (plan.items.length) {
106
+ lines.push('Medicamentos:', ...plan.items.map((item) => `- ${[item.drug, item.strength].filter(Boolean).join(' ')}: ${item.dose} ${item.frequency} por ${daysText(item.days)}.`));
107
+ }
108
+ if (plan.studies.length) {
109
+ lines.push('Estudios:', ...plan.studies.map((study) => `- ${study.name}${study.instructions ? `: ${study.instructions}` : ''}.`));
110
+ }
111
+ if (plan.homeCare.length) lines.push('Cuidados en casa:', ...plan.homeCare.map((entry) => `- ${entry}`));
112
+ if (plan.note) lines.push(`Nota del médico: «${plan.note}».`);
113
+ if (plan.followUp.kind !== 'none') lines.push(FOLLOW_UP_LINES[plan.followUp.kind](plan.followUp.hours));
114
+ return lines.join('\n');
115
+ }
116
+
117
+ // The doctor's edit wins; with none, the instructions follow the plan.
118
+ function approvedInstructions(plan, edited) {
119
+ if (edited != null && typeof edited !== 'string') throw httpError(400, 'instructions must be text');
120
+ if (!edited?.trim()) return { instructions: planInstructions(plan), instructionsEdited: false };
121
+ const instructions = edited.trim();
122
+ if (instructions.length > MAX_INSTRUCTIONS_LENGTH) throw httpError(400, `instructions exceed ${MAX_INSTRUCTIONS_LENGTH} characters`);
123
+ return { instructions, instructionsEdited: instructions !== planInstructions(plan) };
124
+ }
125
+
126
+ // «1 tableta cada 24 horas por 5 días; además, …» → dose, frequency, days and the rest; what does not match is left for the doctor.
127
+ function dosingOf(instructions) {
128
+ const text = oneLineOf(instructions);
129
+ const match = text.match(/^(.*?)\s+(cada [^,;]+?)(?:\s+por\s+(\d+)\s+d[ií]as?)?(?:\s*[,;]\s*(.*))?$/i);
130
+ if (!match) return { dose: text || null, frequency: null, days: null, rest: null };
131
+ const [, dose, frequency, days, rest] = match;
132
+ // A duration said after other directions («…, y tragar, por 7 días») is still the days.
133
+ const lateDays = !days && rest?.match(/(?:^|[,;]\s*|\s)por\s+(\d+)\s+d[ií]as?\s*$/i);
134
+ return {
135
+ dose: ROUTE_ONLY.test(dose) ? null : dose,
136
+ frequency,
137
+ days: Number(days ?? lateDays?.[1]) || null,
138
+ rest: (lateDays ? rest.slice(0, lateDays.index).replace(/[,;\s]+$/, '') : rest) || null,
139
+ };
140
+ }
141
+
142
+ // The doctor's starting point: the drugs the bot proposed and confirmed, once each, and a 24 h check-in.
143
+ // What the formulary adds after the dosing («además, valoración por oftalmología») goes to the note.
144
+ function proposedPlanOf(draft) {
145
+ const seen = new Set();
146
+ const rests = [];
147
+ const items = (draft?.symptoms ?? [])
148
+ .filter((entry) => entry?.proposal?.confirmed)
149
+ .flatMap((entry) => (entry.proposal.items ?? []).map((item) => ({ item, formularyKey: entry.proposal.formularyKey })))
150
+ .filter(({ item }) => !seen.has(oneLineOf(item.drug).toLowerCase()) && seen.add(oneLineOf(item.drug).toLowerCase()))
151
+ .map(({ item, formularyKey }) => {
152
+ const { rest, ...dosing } = dosingOf(item.instructions);
153
+ if (rest) rests.push(`${item.drug}: ${rest}`);
154
+ return { drug: item.drug, strength: item.strength ?? null, ...dosing, position: item.position ?? null, reason: null, formularyKey };
155
+ });
156
+ return { items, studies: [], homeCare: [], note: rests.join('. ') || null, followUp: { kind: 'check_in', hours: DEFAULT_FOLLOW_UP_HOURS.check_in } };
157
+ }
158
+
159
+ module.exports = {
160
+ approvedInstructions,
161
+ needsPrescriber,
162
+ planInstructions,
163
+ planOf,
164
+ proposedPlanOf,
165
+ };
@@ -11,6 +11,7 @@ const {
11
11
  handOffToDoctor,
12
12
  listEscalationReviews,
13
13
  openEscalationReview,
14
+ previewPlanInstructions,
14
15
  reclassifyUrgency,
15
16
  recordDoctorDecision,
16
17
  requestBotDraft,
@@ -53,13 +54,22 @@ const openEscalationReviewController = async (req, res) => {
53
54
 
54
55
  const getEscalationReviewController = async (req, res) => {
55
56
  try {
56
- const { review, isApprover, responseDeadline } = await getEscalationReviewById({ id: req.params.id, requestedBy: req.query.requestedBy });
57
- res.status(200).json({ success: true, review: withCaseStatus(review), isApprover, responseDeadline });
57
+ const { review, isApprover, responseDeadline, proposedPlan } = await getEscalationReviewById({ id: req.params.id, requestedBy: req.query.requestedBy });
58
+ res.status(200).json({ success: true, review: withCaseStatus(review), isApprover, responseDeadline, proposedPlan });
58
59
  } catch (error) {
59
60
  respondWithError(res, error, 'reading escalation review', { reviewId: req.params?.id });
60
61
  }
61
62
  };
62
63
 
64
+ const previewEscalationReviewPlanController = async (req, res) => {
65
+ try {
66
+ const instructions = previewPlanInstructions({ id: req.params.id, plan: req.body?.plan });
67
+ res.status(200).json({ success: true, instructions });
68
+ } catch (error) {
69
+ respondWithError(res, error, 'previewing the plan instructions', { reviewId: req.params?.id });
70
+ }
71
+ };
72
+
63
73
  const getReviewTimelineController = async (req, res) => {
64
74
  try {
65
75
  const timeline = await getReviewTimeline({ id: req.params.id });
@@ -175,6 +185,8 @@ const recordEscalationReviewDecisionController = async (req, res) => {
175
185
  source,
176
186
  decision,
177
187
  finalText = null,
188
+ plan = null,
189
+ instructions = null,
178
190
  comment = null,
179
191
  doctorName = null,
180
192
  doctorReply = null,
@@ -188,6 +200,8 @@ const recordEscalationReviewDecisionController = async (req, res) => {
188
200
  source,
189
201
  decision,
190
202
  finalText,
203
+ plan,
204
+ instructions,
191
205
  comment,
192
206
  doctorName,
193
207
  doctorReply,
@@ -284,6 +298,7 @@ module.exports = {
284
298
  closeEscalationReviewController,
285
299
  getEscalationReviewController,
286
300
  getReviewTimelineController,
301
+ previewEscalationReviewPlanController,
287
302
  requestEscalationReviewDraftController,
288
303
  handOffEscalationReviewController,
289
304
  listEscalationReviewsController,
@@ -12,7 +12,8 @@ const HANDOFF_MODES = ['sent_whatsapp', 'copied'];
12
12
  const INTERRUPTED_STATUSES = ['handing_off', 'sending'];
13
13
  const CANCELLABLE_AFTER_HANDOFF_STATUSES = ['awaiting_doctor', 'approved', 'returned', 'indications_queued'];
14
14
  const CANCELLABLE_STATUSES = ['open', 'documenting', 'draft_ready', ...CANCELLABLE_AFTER_HANDOFF_STATUSES];
15
- const DOCTOR_DECISIONS = ['approved', 'edited_approved', 'needs_intervention', 'returned', 'monitor', 'direct_chat'];
15
+ const DOCTOR_DECISIONS = ['approved', 'edited_approved', 'plan_approved', 'needs_intervention', 'returned', 'monitor', 'direct_chat'];
16
+ const PLAN_FOLLOW_UPS = ['none', 'check_in', 'vigilancia'];
16
17
  const DECISION_SOURCES = ['platform', 'registered_by_navigator', 'chat'];
17
18
  const RETURN_REASONS = ['llamar', 'falta_dato', 'no_requiere'];
18
19
 
@@ -193,11 +194,39 @@ const doctorHandoffSchema = new mongoose.Schema({
193
194
  sentBy: { type: String, required: true },
194
195
  }, { timestamps: { createdAt: true, updatedAt: false } });
195
196
 
197
+ const planItemSchema = new mongoose.Schema({
198
+ drug: { type: String, required: true },
199
+ strength: { type: String, default: null },
200
+ dose: { type: String, required: true },
201
+ frequency: { type: String, required: true },
202
+ days: { type: Number, required: true },
203
+ position: { type: String, default: null },
204
+ reason: { type: String, default: null },
205
+ formularyKey: { type: String, default: null },
206
+ }, { _id: false });
207
+
208
+ const planSchema = new mongoose.Schema({
209
+ items: { type: [planItemSchema], default: [] },
210
+ studies: {
211
+ type: [new mongoose.Schema({ name: { type: String, required: true }, instructions: { type: String, default: null } }, { _id: false })],
212
+ default: [],
213
+ },
214
+ homeCare: { type: [String], default: [] },
215
+ note: { type: String, default: null },
216
+ followUp: {
217
+ type: new mongoose.Schema({ kind: { type: String, enum: PLAN_FOLLOW_UPS, required: true }, hours: { type: Number, default: null } }, { _id: false }),
218
+ required: true,
219
+ },
220
+ instructions: { type: String, required: true },
221
+ instructionsEdited: { type: Boolean, default: false },
222
+ }, { _id: false });
223
+
196
224
  const decisionSchema = new mongoose.Schema({
197
225
  decision: { type: String, enum: DOCTOR_DECISIONS, required: true },
198
226
  source: { type: String, enum: DECISION_SOURCES, required: true },
199
227
  draftNumber: { type: Number, required: true },
200
228
  finalText: { type: String, default: null },
229
+ plan: { type: planSchema, default: undefined },
201
230
  comment: { type: String, default: null },
202
231
  doctorReply: { type: String, default: null },
203
232
  returnReason: { type: String, enum: [...RETURN_REASONS, null], default: null },
@@ -381,6 +410,7 @@ module.exports = {
381
410
  DOCTOR_DECISIONS,
382
411
  HANDOFF_MODES,
383
412
  INTERRUPTED_STATUSES,
413
+ PLAN_FOLLOW_UPS,
384
414
  FOLLOWUP_STATUSES,
385
415
  RETURN_REASONS,
386
416
  URGENCY_LEVELS,
@@ -138,6 +138,7 @@ const escalationReviewRouteDefinitions = {
138
138
  'POST /:id/note-facts': 'confirmEscalationReviewNoteFactsController',
139
139
  'POST /:id/drafts': 'addEscalationReviewDraftController',
140
140
  'POST /:id/doctor-handoffs': 'handOffEscalationReviewController',
141
+ 'POST /:id/plan-instructions': 'previewEscalationReviewPlanController',
141
142
  'POST /:id/decisions': 'recordEscalationReviewDecisionController',
142
143
  'POST /:id/patient-deliveries': 'sendEscalationReviewIndicationsController',
143
144
  'POST /:id/close': 'closeEscalationReviewController',
@@ -314,6 +315,7 @@ const builtInControllers = {
314
315
  listReviewBoardController: escalationReviewController.listReviewBoardController,
315
316
  getEscalationReviewController: escalationReviewController.getEscalationReviewController,
316
317
  getReviewTimelineController: escalationReviewController.getReviewTimelineController,
318
+ previewEscalationReviewPlanController: escalationReviewController.previewEscalationReviewPlanController,
317
319
  requestEscalationReviewDraftController: escalationReviewController.requestEscalationReviewDraftController,
318
320
  setEscalationReviewCasesController: escalationReviewController.setEscalationReviewCasesController,
319
321
  addEscalationReviewCallLogController: escalationReviewController.addEscalationReviewCallLogController,
@@ -33,6 +33,7 @@ const { doctorAlertHeadline, doctorAlertOpener, renderDoctorMessage, templateSum
33
33
  const { generateKeyQuestions } = require('../clinical/services/escalationQuestionsService');
34
34
  const { drugsNamedIn } = require('../clinical/services/treatmentProposalService');
35
35
  const { characterizationSeedOf } = require('../clinical/helpers/reviewUpdateHelper');
36
+ const { approvedInstructions, needsPrescriber, planInstructions, planOf, proposedPlanOf } = require('../clinical/helpers/doctorPlanHelper');
36
37
  const { readNoteFacts, assertionOf } = require('../clinical/services/escalationNoteFactsService');
37
38
  const { humanReplyFilter } = require('../clinical/config/safetyNetConfig');
38
39
  const {
@@ -104,7 +105,8 @@ const RECLASSIFIABLE_STATUSES = [...DOCUMENTING_STATUSES, 'awaiting_doctor'];
104
105
  const SAME_URGENCY = 'The review is already at that urgency';
105
106
  const RECLASSIFY_CHANGED = 'The review changed while you were reclassifying it; read it again before changing the urgency';
106
107
  const DIRECT_CHAT_STATUSES = ['open', 'documenting', 'draft_ready', 'returned', 'awaiting_doctor', 'approved', 'needs_intervention'];
107
- const APPROVALS = ['approved', 'edited_approved'];
108
+ const APPROVALS = ['approved', 'edited_approved', 'plan_approved'];
109
+ const PRESCRIBER_ONLY = 'Only an approving doctor can indicate drugs or studies, or edit the instructions to the assistant';
108
110
  const STALE_DRAFT = 'New information reached the review after this draft; wait for the updated draft before approving';
109
111
  const STALE_HANDOFF = 'New information reached the review after the draft the doctor received; it went back to documenting for a new draft';
110
112
  const DRAFT_CHANGED = 'A newer draft was written after the one you saw; read it before deciding';
@@ -145,7 +147,7 @@ const configured = (key) => {
145
147
  return value;
146
148
  };
147
149
 
148
- const STATUS_AFTER = { edited_approved: 'approved', monitor: 'monitoring' };
150
+ const STATUS_AFTER = { edited_approved: 'approved', plan_approved: 'approved', monitor: 'monitoring' };
149
151
  const statusAfter = (decision) => STATUS_AFTER[decision] ?? decision;
150
152
 
151
153
  const redactedError = (error) => redactDirectIdentifiers(String(error?.message || ''));
@@ -728,6 +730,18 @@ async function isEscalationApprover(operator) {
728
730
  }
729
731
  }
730
732
 
733
+ // Unlike a read, a failed lookup refuses the decision instead of treating the doctor as not approving.
734
+ async function requireApprover(operator) {
735
+ let approver;
736
+ try {
737
+ approver = await getEscalationApprover(operator);
738
+ } catch (error) {
739
+ logger.error('[EscalationReview] Approver lookup failed; the plan was not recorded', { error: redactedError(error) });
740
+ throw httpError(503, 'The approving doctors could not be read; try again');
741
+ }
742
+ if (!approver) throw httpError(403, PRESCRIBER_ONLY);
743
+ }
744
+
731
745
  async function getEscalationReviewById({ id, requestedBy } = {}) {
732
746
  const reviewId = reviewIdOf(id);
733
747
  const operator = requiredText(requestedBy, 'requestedBy');
@@ -748,7 +762,19 @@ async function getEscalationReviewById({ id, requestedBy } = {}) {
748
762
  .updateOne({ _id: review._id }, { $max: { approverViewedAt: readAt } }, { timestamps: false })
749
763
  .catch(warnUnmarked);
750
764
  }
751
- return { review, isApprover, responseDeadline: responseDeadlineOf(review, responseDeadlinePolicy()) };
765
+ const draft = DECIDABLE_STATUSES.includes(review.status) ? review.drafts?.at(-1) : null;
766
+ return {
767
+ review,
768
+ isApprover,
769
+ responseDeadline: responseDeadlineOf(review, responseDeadlinePolicy()),
770
+ proposedPlan: draft ? { draftNumber: review.drafts.length, ...proposedPlanOf(draft) } : null,
771
+ };
772
+ }
773
+
774
+ // The instructions a plan would give the assistant, for the doctor to read before approving.
775
+ function previewPlanInstructions({ id, plan } = {}) {
776
+ reviewIdOf(id);
777
+ return planInstructions(planOf(plan));
752
778
  }
753
779
 
754
780
  async function getReviewTimeline({ id } = {}) {
@@ -1787,6 +1813,8 @@ async function recordDoctorDecision({
1787
1813
  source,
1788
1814
  decision,
1789
1815
  finalText = null,
1816
+ plan: requestedPlan = null,
1817
+ instructions = null,
1790
1818
  comment = null,
1791
1819
  doctorName = null,
1792
1820
  doctorReply = null,
@@ -1815,6 +1843,17 @@ async function recordDoctorDecision({
1815
1843
  if (!RETURN_REASONS.includes(reason)) throw httpError(400, `reason must be one of: ${RETURN_REASONS.join(', ')}`);
1816
1844
  returnReason = reason;
1817
1845
  }
1846
+ let plan = null;
1847
+ if (decision === 'plan_approved') {
1848
+ if (source !== 'platform') throw httpError(400, 'A plan is approved by the doctor on the platform');
1849
+ // The plan names the draft it started from, so a newer draft is refused instead of decided unseen.
1850
+ if (seenDraftNumber === null) throw httpError(400, 'draftNumber is required for a plan');
1851
+ plan = planOf(requestedPlan);
1852
+ plan = { ...plan, ...approvedInstructions(plan, instructions) };
1853
+ // The team may send home care alone; a note, an edit or a named drug goes out on a doctor's word.
1854
+ const namesDrug = drugsNamedIn(plan.homeCare.join(' ')).length > 0;
1855
+ if (needsPrescriber(plan) || plan.note || plan.instructionsEdited || namesDrug) await requireApprover(operator);
1856
+ }
1818
1857
 
1819
1858
  const review = await loadReview(reviewId);
1820
1859
  const isDraftless = DRAFTLESS_DECISIONS.includes(decision);
@@ -1858,6 +1897,7 @@ async function recordDoctorDecision({
1858
1897
  source,
1859
1898
  draftNumber,
1860
1899
  finalText: approvedText,
1900
+ ...(plan && { plan }),
1861
1901
  comment: optionalText(comment),
1862
1902
  doctorReply: reply,
1863
1903
  ...(returnReason && { returnReason }),
@@ -2076,6 +2116,7 @@ const EPISODE_SOURCE = 'doctor';
2076
2116
  const DECISION_WORDS = {
2077
2117
  approved: 'el médico aprobó las indicaciones',
2078
2118
  edited_approved: 'el médico aprobó las indicaciones con cambios',
2119
+ plan_approved: 'el médico dio las indicaciones',
2079
2120
  needs_intervention: 'el médico pidió valoración presencial',
2080
2121
  returned: 'el médico devolvió el caso a navegación',
2081
2122
  };
@@ -2259,6 +2300,7 @@ module.exports = {
2259
2300
  generateBotDraft,
2260
2301
  getEscalationReviewById,
2261
2302
  getReviewTimeline,
2303
+ previewPlanInstructions,
2262
2304
  handOffToDoctor,
2263
2305
  listEscalationReviews,
2264
2306
  openEscalationReview,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "6.3.1",
3
+ "version": "6.4.0-dev.18675",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",