[object Object]

← back to Sample Followup Sweep

sample-followup: single-day rolling window (Entered==today-10), revert >10d backlog (TK-12087)

e3a7a73641d9776fd3465b316fbf08be6120b9be · 2026-09-23 14:17:05 -0700 · Steve Abrams

scheduled-run.mjs windowRange, send-reconciled.mjs WIN, and lib/sweep.js now
chase ONLY the cohort entered EXACTLY minAgeDays(10) calendar days ago instead
of the [10..60] cumulative backlog introduced by TK-12013/12014. WIDEN_DAYS/
CATCHUP/WIN remain as explicit manual recovery nets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e3a7a73641d9776fd3465b316fbf08be6120b9be
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 14:17:05 2026 -0700

    sample-followup: single-day rolling window (Entered==today-10), revert >10d backlog (TK-12087)
    
    scheduled-run.mjs windowRange, send-reconciled.mjs WIN, and lib/sweep.js now
    chase ONLY the cohort entered EXACTLY minAgeDays(10) calendar days ago instead
    of the [10..60] cumulative backlog introduced by TK-12013/12014. WIDEN_DAYS/
    CATCHUP/WIN remain as explicit manual recovery nets.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 lib/sweep.js                |  9 +++++++++
 scripts/scheduled-run.mjs   | 15 +++++++--------
 scripts/send-reconciled.mjs | 11 ++++++++---
 3 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/lib/sweep.js b/lib/sweep.js
index 1e10781..70ec5cb 100644
--- a/lib/sweep.js
+++ b/lib/sweep.js
@@ -13,6 +13,11 @@ function ageDays(entered, today) {
 function sweep(rows, opts = {}, today = new Date()) {
   const {
     minAgeDays = 10,
+    // TK-12087 (Steve 2026-09-23): SINGLE-DAY rolling window. Chase ONLY samples entered EXACTLY
+    // minAgeDays calendar days ago — NOT the >10-day cumulative backlog. widenDays>0 extends the
+    // window's OLD end as an explicit recovery net (dedup makes overlap safe). maxAgeDays kept only
+    // as the absolute dead-lead ceiling for any widened/manual window.
+    widenDays = 0,
     maxAgeDays = 60, // Steve 2026-08-14: >60 days = dead lead, don't chase
     excludeDisco = true,
     requireNotReceived = true,
@@ -33,7 +38,11 @@ function sweep(rows, opts = {}, today = new Date()) {
     if (r.manual_add === true) { followUp.push({ ...r, age }); continue; }
 
     if (age === null) { skipped.push({ row: r, reason: 'no request date' }); continue; }
+    // Single-day window: chase ONLY the [minAgeDays .. minAgeDays+widenDays] cohort (default = exactly
+    // minAgeDays). Anything younger is not yet due; anything older than the window is NOT part of today's
+    // batch (it was that earlier day's batch) — do not fold it back into a cumulative backlog.
     if (age < minAgeDays) { skipped.push({ row: r, reason: `only ${age}d old (<${minAgeDays})` }); continue; }
+    if (age > minAgeDays + widenDays) { skipped.push({ row: r, reason: `outside single-day window (${age}d, want exactly ${minAgeDays}${widenDays ? `..${minAgeDays + widenDays}` : ''}d)` }); continue; }
     if (age > maxAgeDays) { skipped.push({ row: r, reason: `dead lead (${age}d > ${maxAgeDays})` }); continue; }
 
     if (r.second_request) {
diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index 2fe336f..93df18e 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -131,14 +131,13 @@ function windowRange(today = new Date()) {
     const lo = new Date(today); lo.setDate(lo.getDate() - MIN_AGE_DAYS - (CATCHUP_DAYS - 1));
     return `${fmtDate(lo)}...${fmtDate(hi)}`;
   }
-  // Active follow-up window = requests aged [MIN_AGE_DAYS .. MAX_AGE_DAYS], INCLUSIVE.
-  //   hi = today - MIN_AGE_DAYS  → includes the exactly-10-day cohort (FileMaker `lo...hi` range is inclusive).
-  //   lo = today - MAX_AGE_DAYS  → the 60-day dead-lead floor.
-  // Full cohort every run; the Sent-stamp set-difference is the dedup (see MAX_AGE_DAYS note above),
-  // so this is robust to missed run days — nothing in the active window is ever silently dropped.
-  const hi = new Date(today); hi.setDate(hi.getDate() - MIN_AGE_DAYS);
-  const lo = new Date(today); lo.setDate(lo.getDate() - MAX_AGE_DAYS);
-  return `${fmtDate(lo)}...${fmtDate(hi)}`;
+  // TK-12087 (Steve 2026-09-23): SINGLE-DAY rolling window — chase ONLY the cohort entered EXACTLY
+  // today-MIN_AGE_DAYS calendar days ago, NOT the >10-day cumulative backlog [10..60] that the
+  // TK-12013/12014 change introduced (reverted here). Each daily run drafts only that day's
+  // newly-10-day-old batch. The FileMaker Sent-stamp set-difference is still the dedup, and CATCHUP /
+  // WIN remain as explicit manual recovery nets if a run day is ever missed.
+  const d = new Date(today); d.setDate(d.getDate() - MIN_AGE_DAYS);   // today-10, single calendar day
+  return `${fmtDate(d)}...${fmtDate(d)}`;
 }
 const norm = (s) => String(s || '').toLowerCase().trim();
 const readJSON = (f, d) => { try { return JSON.parse(readFileSync(join(ROOT, f), 'utf8')); } catch { return d; } };
diff --git a/scripts/send-reconciled.mjs b/scripts/send-reconciled.mjs
index e964d67..f5a36f0 100644
--- a/scripts/send-reconciled.mjs
+++ b/scripts/send-reconciled.mjs
@@ -38,9 +38,14 @@ const _fmt = (d) => `${_pad(d.getMonth() + 1)}/${_pad(d.getDate())}/${d.getFullY
 // Steve 2026-09-22 (TK-12013/TK-12014): full active window [today-MAX_AGE .. today-MIN_AGE] inclusive,
 // NOT a narrow rolling catch-up band — so no older still-outstanding memo is ever dropped. Draft-dedup
 // (reconciled-draft-ledger keyed by vendor+combo-sku) makes re-covering already-drafted memos a no-op.
-const _MIN_AGE = 10, _MAX_AGE = Number(process.env.MAX_AGE) || 60;
-const _hi = new Date(); _hi.setDate(_hi.getDate() - _MIN_AGE);   // includes exactly-10d cohort (inclusive range)
-const _lo = new Date(); _lo.setDate(_lo.getDate() - _MAX_AGE);   // 60-day dead-lead floor
+// TK-12087 (Steve 2026-09-23): SINGLE-DAY rolling window. Chase ONLY samples entered EXACTLY
+// today-10 CALENDAR days ago — NOT the >10-day cumulative backlog (the TK-12013/12014 change,
+// reverted here). Each daily run drafts only that day's newly-10-day-old batch. WIN overrides for
+// a manual one-off; WIDEN_DAYS>0 widens the low end as an explicit recovery net (dedup makes it safe).
+const _MIN_AGE = 10;
+const _WIDEN = Number(process.env.WIDEN_DAYS) || 0;   // 0 = strict single day (today-10 only)
+const _hi = new Date(); _hi.setDate(_hi.getDate() - _MIN_AGE);            // today-10
+const _lo = new Date(); _lo.setDate(_lo.getDate() - _MIN_AGE - _WIDEN);   // = today-10 when WIDEN=0
 const WIN = process.env.WIN || `${_fmt(_lo)}...${_fmt(_hi)}`;
 
 // --- FileMaker: pull outstanding (Date WP Sample Sent empty) in WIN, group by vid ---

← 04e0986 auto-data-snapshot: 2026-09-23T14:17:00 (2 data files) — dat  ·  back to Sample Followup Sweep  ·  sample-followup: day-of-week-aware window (Tue 4-day catch-u a11a644 →