@peopl-health/nexus 5.69.0-dev.12622 → 5.69.0-dev.12624
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.
|
@@ -6,6 +6,7 @@ const { getMessages } = require('../../services/messageService');
|
|
|
6
6
|
const { DEFAULT_TIMEZONE, isWithinHourWindow } = require('../../utils/scheduleUtils');
|
|
7
7
|
const { logger } = require('../../utils/logger');
|
|
8
8
|
const { botIsPaused } = require('../../helpers/pausedThreadHelper');
|
|
9
|
+
const { Thread } = require('../../models/threadModel');
|
|
9
10
|
const { PATIENT_CONTACT_WINDOW } = require('./contingencyDispatchService');
|
|
10
11
|
const { hasPendingReminderForPatient } = require('./clinicalAirtableService');
|
|
11
12
|
const { WriteClaimStore } = require('./writeClaimStore');
|
|
@@ -53,13 +54,6 @@ const AUDIT = {
|
|
|
53
54
|
const WEEKLY_SOURCE = 'weekly';
|
|
54
55
|
const SCOPE_SOURCE = 'patient_scope';
|
|
55
56
|
|
|
56
|
-
const PATIENT_TABLE = 'estado_general';
|
|
57
|
-
const PATIENT_F = {
|
|
58
|
-
whatsapp: 'whatsapp_id',
|
|
59
|
-
firstName: 'first_name',
|
|
60
|
-
disableFollowUp: 'desactivar_seguimiento_48hrs',
|
|
61
|
-
};
|
|
62
|
-
|
|
63
57
|
const sweepClaims = new WriteClaimStore({ namespace: 'outreach_sweep', ttlMs: 8 * 24 * 60 * 60 * 1000 });
|
|
64
58
|
|
|
65
59
|
const UNDELIVERED_STATUSES = ['failed', 'undelivered'];
|
|
@@ -216,32 +210,27 @@ function buildCandidates(rows, { now, cadence = 'daily', mode = 'continuity' })
|
|
|
216
210
|
return { candidates, targetDays, weekday, cadence, mode };
|
|
217
211
|
}
|
|
218
212
|
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (name) patient.names.set(name.toLowerCase().replace(/\s+/g, ' '), name);
|
|
227
|
-
patient.recordId = patient.recordId || row._recordId;
|
|
228
|
-
if (row[PATIENT_F.disableFollowUp] === true) patient.followUpDisabled = true;
|
|
229
|
-
}
|
|
213
|
+
function firstWord(value) {
|
|
214
|
+
return String(value || '').trim().split(/\s+/)[0];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildScopeCandidates({ threads, excludedCodes }) {
|
|
218
|
+
const threadsByCode = new Map();
|
|
219
|
+
for (const thread of threads) threadsByCode.set(thread.code, [...(threadsByCode.get(thread.code) || []), thread]);
|
|
230
220
|
|
|
231
221
|
const candidates = [];
|
|
232
|
-
for (const [code,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
222
|
+
for (const [code, docs] of threadsByCode) {
|
|
223
|
+
const [thread] = docs.sort((a, b) => String(a._id).localeCompare(String(b._id)));
|
|
224
|
+
const name = firstWord(thread.nombre);
|
|
225
|
+
const names = new Set(docs.map((doc) => firstWord(doc.nombre).toLowerCase()));
|
|
226
|
+
if (excludedCodes.has(code) || !name || names.size !== 1 || !thread.patient_id) continue;
|
|
237
227
|
candidates.push({
|
|
238
228
|
source: SCOPE_SOURCE,
|
|
239
|
-
patientId:
|
|
240
|
-
recordId:
|
|
229
|
+
patientId: thread.patient_id,
|
|
230
|
+
recordId: String(thread._id),
|
|
241
231
|
code,
|
|
242
|
-
name
|
|
232
|
+
name,
|
|
243
233
|
reason: 'no_triage_history',
|
|
244
|
-
followUpDisabled: patient.followUpDisabled,
|
|
245
234
|
rows: [],
|
|
246
235
|
brief: {
|
|
247
236
|
reason: 'no hay un triaje contestado que sirva como ancla clínica',
|
|
@@ -256,15 +245,15 @@ function buildScopeCandidates({ patientScope, patientRows, excludedCodes }) {
|
|
|
256
245
|
|
|
257
246
|
async function loadScopeCandidates({ now, viewRows, patientScope }) {
|
|
258
247
|
const weekStart = startOfLocalWeek(now).toISOString();
|
|
259
|
-
const [
|
|
260
|
-
|
|
248
|
+
const [threads, weekRows] = await Promise.all([
|
|
249
|
+
Thread.find({ code: { $in: patientScope } }, { code: 1, nombre: 1, patient_id: 1 }).lean(),
|
|
261
250
|
airtableService.getRecordByFilter(
|
|
262
251
|
Symptoms_ID, TABLE, `NOT(IS_BEFORE({${F.createdAt}}, DATETIME_PARSE("${weekStart}")))`, null, [F.whatsapp],
|
|
263
252
|
),
|
|
264
253
|
]);
|
|
265
254
|
const excludedCodes = new Set([...viewRows, ...(weekRows || [])]
|
|
266
255
|
.map((row) => String(first(row[F.whatsapp]) || '').trim()));
|
|
267
|
-
return buildScopeCandidates({
|
|
256
|
+
return buildScopeCandidates({ threads: threads || [], excludedCodes });
|
|
268
257
|
}
|
|
269
258
|
|
|
270
259
|
function sweepClaimKey(candidate, now) {
|
|
@@ -272,7 +261,6 @@ function sweepClaimKey(candidate, now) {
|
|
|
272
261
|
}
|
|
273
262
|
|
|
274
263
|
function opsOptedOut(candidate, { now }) {
|
|
275
|
-
if (candidate.followUpDisabled) return 'patient_follow_up_disabled';
|
|
276
264
|
const weekStart = startOfLocalWeek(now);
|
|
277
265
|
for (const row of candidate.rows) {
|
|
278
266
|
if (first(row[F.disableFollowUp]) === true) return 'patient_follow_up_disabled';
|
|
@@ -307,6 +295,7 @@ async function rejectionReason(candidate, {
|
|
|
307
295
|
} = {}) {
|
|
308
296
|
const opsReason = opsOptedOut(candidate, { now });
|
|
309
297
|
if (opsReason) return opsReason;
|
|
298
|
+
if (await Thread.exists({ code: candidate.code, outreachOptOut: true })) return 'outreach_opt_out';
|
|
310
299
|
if (await botIsPaused(candidate.code, { label: '[outreachSweep]' })) return 'bot_paused';
|
|
311
300
|
if (!includePreviouslySwept) {
|
|
312
301
|
const sweptReason = candidate.source === SCOPE_SOURCE
|
|
@@ -432,9 +421,8 @@ async function enrichBrief(candidate) {
|
|
|
432
421
|
// contentSid and no idempotency key, so nothing downstream would catch a duplicate.
|
|
433
422
|
async function claimCandidate({ candidate, weekdayLabel, dryRun, now }) {
|
|
434
423
|
if (dryRun) return true;
|
|
435
|
-
if (candidate
|
|
436
|
-
|
|
437
|
-
}
|
|
424
|
+
if ((await sweepClaims.acquire(sweepClaimKey(candidate, now))) !== 'acquired') return false;
|
|
425
|
+
if (candidate.source === SCOPE_SOURCE) return true;
|
|
438
426
|
try {
|
|
439
427
|
const claimed = await airtableService.updateRecordByFilter(
|
|
440
428
|
Symptoms_ID,
|
|
@@ -649,7 +637,6 @@ module.exports = {
|
|
|
649
637
|
mondayTriageStillPending,
|
|
650
638
|
VIEW,
|
|
651
639
|
F,
|
|
652
|
-
PATIENT_F,
|
|
653
640
|
AUDIT,
|
|
654
641
|
sweepClaims,
|
|
655
642
|
};
|
|
@@ -10,6 +10,7 @@ const threadSchema = new mongoose.Schema({
|
|
|
10
10
|
run_id: { type: String, default: null },
|
|
11
11
|
nombre: { type: String, default: null },
|
|
12
12
|
stopped: { type: Boolean, default: false },
|
|
13
|
+
outreachOptOut: { type: Boolean, default: false },
|
|
13
14
|
pausedAt: { type: Date, default: null },
|
|
14
15
|
doNotAlertThrough: { type: Date, default: null },
|
|
15
16
|
review: { type: Boolean, default: false },
|