@peopl-health/nexus 5.68.0-dev.12084 → 5.68.0-dev.12085
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.
|
@@ -461,22 +461,33 @@ function isAtOrBefore(when, now) {
|
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
/*
|
|
464
|
-
* Another check-in
|
|
465
|
-
*
|
|
466
|
-
* rows firing together see each other; an unsent
|
|
467
|
-
* is not coming. Patient-requested reminders are
|
|
464
|
+
* Another check-in close to this one: a net's patient check-in or another routine follow-up that
|
|
465
|
+
* was sent in the last `withinMinutes`, or is still pending and due within them. The pending lower
|
|
466
|
+
* bound is the trigger's own 5-minute lead, so two rows firing together see each other; an unsent
|
|
467
|
+
* row older than that, or expired, was missed and is not coming. Patient-requested reminders are
|
|
468
|
+
* left out; they are what she asked for.
|
|
468
469
|
*/
|
|
469
|
-
async function hasCheckInNear({ code, withinMinutes, excludeReminderId }) {
|
|
470
|
+
async function hasCheckInNear({ code, withinMinutes, excludeReminderId, fireAt = null }) {
|
|
470
471
|
const window = Number(withinMinutes);
|
|
471
472
|
if (!code || !Number.isFinite(window) || window <= 0) return false;
|
|
473
|
+
const pending = `AND(${PENDING_ROW_FILTER}, DATETIME_DIFF({reminder_date}, NOW(), 'minutes') >= -5, DATETIME_DIFF({reminder_date}, NOW(), 'minutes') <= ${window}, OR({expires_at} = BLANK(), IS_AFTER({expires_at}, NOW())))`;
|
|
474
|
+
const sent = `AND({is_sent}, DATETIME_DIFF(NOW(), {sent_at}, 'minutes') <= ${window})`;
|
|
472
475
|
const rows = await airtableService.getRecordByFilter(
|
|
473
476
|
Monitoreo_ID,
|
|
474
477
|
'reminder',
|
|
475
|
-
`AND(${fieldEquals('whatsapp_id', code)},
|
|
478
|
+
`AND(${fieldEquals('whatsapp_id', code)}, OR(RIGHT({schedule_id}, 8) = "-patient", ${ROUTINE_OUTREACH_FILTER}), NOT(${fieldEquals('schedule_id', excludeReminderId)}), OR(${pending}, ${sent}))`,
|
|
476
479
|
null,
|
|
477
|
-
['schedule_id'],
|
|
480
|
+
['schedule_id', 'reminder_date', 'is_sent'],
|
|
478
481
|
);
|
|
479
|
-
|
|
482
|
+
// A sent row or a net check-in always wins. Between two pending routine rows the earlier one,
|
|
483
|
+
// then the lower id, wins, so two jobs firing together on separate instances never both stand down.
|
|
484
|
+
const ownAt = new Date(fireAt).getTime() || 0;
|
|
485
|
+
return (rows || []).some((row) => {
|
|
486
|
+
const id = String(row.schedule_id || '');
|
|
487
|
+
if (row.is_sent || id.endsWith('-patient')) return true;
|
|
488
|
+
const at = new Date(row.reminder_date).getTime() || 0;
|
|
489
|
+
return at < ownAt || (at === ownAt && id < excludeReminderId);
|
|
490
|
+
});
|
|
480
491
|
}
|
|
481
492
|
|
|
482
493
|
async function readPendingReminderForPatient({ code, reminderId }) {
|
|
@@ -20,7 +20,7 @@ const { normalizePayload: normalize, registerInteractiveRoute, unregisterInterac
|
|
|
20
20
|
const { getMessages } = require('../../services/messageService');
|
|
21
21
|
|
|
22
22
|
const { WriteClaimStore } = require('./writeClaimStore');
|
|
23
|
-
const { OPS_ALERTS, recordPausedThreadMessage, recordUnresolvedRequest } = require('./clinicalAirtableService');
|
|
23
|
+
const { OPS_ALERTS, cancelPendingFollowUps, recordPausedThreadMessage, recordUnresolvedRequest } = require('./clinicalAirtableService');
|
|
24
24
|
const { anyoneRepliedSince, humanRepliedSince, openPledge, stopSafetyNetSweep } = require('./safetyNetPledgeService');
|
|
25
25
|
|
|
26
26
|
const getMessaging = () => require('../../core/NexusMessaging');
|
|
@@ -219,6 +219,11 @@ async function applyChoice(code, choice) {
|
|
|
219
219
|
const queued = await fileUrgentRequest(code, choice);
|
|
220
220
|
if (!queued) return { queued, pledge: null };
|
|
221
221
|
|
|
222
|
+
// The team now owns this patient's next contact; no turn ran to reassess a pending seguimiento.
|
|
223
|
+
await cancelPendingFollowUps({ code, reason: 'safety_net_request' }).catch((error) =>
|
|
224
|
+
logger.error('[PausedSafetyNet] Pending follow-ups were not stood down', { code, error: error.message })
|
|
225
|
+
);
|
|
226
|
+
|
|
222
227
|
try {
|
|
223
228
|
return { queued, pledge: await openPledge({ code }) };
|
|
224
229
|
} catch (error) {
|
|
@@ -1092,7 +1092,7 @@ class NexusMessaging {
|
|
|
1092
1092
|
// another check-in is about to reach the patient. Same-day activity does not hold an agent
|
|
1093
1093
|
// seguimiento: the agent saw it on every turn since and kept it. The fire trigger does not come
|
|
1094
1094
|
// back for a skipped row, so a skip retires it with its reason instead of leaving it armed.
|
|
1095
|
-
const holdReason = await hasCheckInNear({ code, withinMinutes: FOLLOW_UP_SPACING_MIN, excludeReminderId: reminderId })
|
|
1095
|
+
const holdReason = await hasCheckInNear({ code, withinMinutes: FOLLOW_UP_SPACING_MIN, excludeReminderId: reminderId, fireAt: plan.fireAt })
|
|
1096
1096
|
? 'check_in_near'
|
|
1097
1097
|
: await followUpCollisionReason(code, reminderId, startedAt);
|
|
1098
1098
|
if (holdReason) {
|