[object Object]

← back to Sample Followup Sweep

sample-followup: make Tuesday window weekday-aware (cover Fri/Sat/Sun/Mon -10)

c1ce8fa6d7a888650fd3e32f26146ffded152593 · 2026-09-01 10:33:33 -0700 · Steve

windowRange() had no day-of-week awareness despite comments claiming Tuesday
catch-up; it applied a flat CATCHUP_DAYS=7 band [today-16..today-10] every run.
Now each run covers the 10-day cohorts for [prevScheduledRun..yesterday]: Tuesday
covers Fri/Sat/Sun/Mon each -10 (=[today-14..today-11]); Wed/Thu/Fri cover the
single prior day (today-11). Today's cohort is deferred to the next run.
CATCHUP env preserved as an explicit manual wide-recovery override.

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

Files touched

Diff

commit c1ce8fa6d7a888650fd3e32f26146ffded152593
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Sep 1 10:33:33 2026 -0700

    sample-followup: make Tuesday window weekday-aware (cover Fri/Sat/Sun/Mon -10)
    
    windowRange() had no day-of-week awareness despite comments claiming Tuesday
    catch-up; it applied a flat CATCHUP_DAYS=7 band [today-16..today-10] every run.
    Now each run covers the 10-day cohorts for [prevScheduledRun..yesterday]: Tuesday
    covers Fri/Sat/Sun/Mon each -10 (=[today-14..today-11]); Wed/Thu/Fri cover the
    single prior day (today-11). Today's cohort is deferred to the next run.
    CATCHUP env preserved as an explicit manual wide-recovery override.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/scheduled-run.mjs | 55 +++++++++++++++++++++++++++++++----------------
 1 file changed, 37 insertions(+), 18 deletions(-)

diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index 412c573..0c02de6 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -39,19 +39,22 @@ const FIELD_SENT = 'Date Email Sent to Vendor after 10 Days';
 // Steve 8/25: on a real send, ALSO stamp the 2nd-request date (chosen target — no dedicated
 // "2nd request date" field is exposed to the Data API; this real date field is on the same layout).
 const FIELD_2ND_DATE = 'Date Sample Request Letter Sent';
-// Steve 8/20 (REVISED): chase ONLY memos that JUST crossed the 10-day mark — a rolling 3-day
-// catch-up band, NOT the whole back-catalog. "10 days old exactly, for the past 3 days" → on 8/20
-// that is entered dates 8/8, 8/9, 8/10. The open-ended `...hi` was pulling every old memo; this
-// bounds it to [today-10-(CATCHUP-1) .. today-10]. Each daily run picks up the one new day; the
-// 3-day width is a safety net so a skipped run still catches the memos it would have missed.
 const MIN_AGE_DAYS = 10;
-// Steve 8/20 (FINAL): chase memos EXACTLY 10 days old — EXCEPT Tuesday, which also covers the
-// Sat/Sun/Mon the job didn't run (schedule = Tue–Fri). Tuesday → 4-day band [today-13..today-10]
-// (memos that turned 10 on Sat/Sun/Mon/Tue); every other run day → 1 day (entered today-10).
-// Env CATCHUP overrides for a manual catch-up.
-// Rolling 7-day catch-up band by default so a missed/unloaded morning is always caught up next run
-// (the draft-ledger dedup + Sent-based suppression prevent any duplicate drafts). Env CATCHUP overrides.
-const CATCHUP_DAYS = Number(process.env.CATCHUP) || 7;
+// Steve 9/01 (FINAL RULE — weekday-aware catch-up): the schedule is Tue–Fri (Mon/Sat/Sun never run).
+// Each run chases the memos that turned exactly 10 days old on the days SINCE the last run — i.e. the
+// cohorts for [previous-scheduled-run-day .. yesterday]. Today's own cohort (entered today-10) is
+// ALWAYS deferred to the next run, so the newest day is never chased prematurely.
+//   • Tuesday: last run was Friday, and Sat/Sun/Mon didn't run → cover Fri, Sat, Sun, Mon → 10 days
+//     before EACH → entered band [Mon-10 .. Fri-10] = [today-11 .. today-14]. (Steve, verbatim:
+//     "on tuesday we go back to the last day, friday and add friday, saturday, sunday, monday
+//      then 10 days before each.")
+//   • Wed/Thu/Fri: last run was yesterday → cover just yesterday → single day entered today-11.
+// Coverage is gapless and non-overlapping week-to-week (verified). The old code IGNORED the weekday
+// and applied a flat CATCHUP_DAYS band every run — that's the bug this replaces.
+// Env CATCHUP (manual override): if set, revert to the old fixed rolling band [today-10-(N-1)..today-10]
+// as an explicit wide recovery net (e.g. after a silently-missed run). Dedup makes the overlap safe.
+const RUN_WEEKDAYS = new Set([2, 3, 4, 5]);   // Tue(2) Wed(3) Thu(4) Fri(5)
+const CATCHUP_DAYS = Number(process.env.CATCHUP) || 0;   // 0 = use the weekday-aware band (default)
 const SHIP_TO = { name: 'Designer Wallcoverings', line1: '15442 Ventura Blvd. #102', city_state_zip: 'Sherman Oaks, CA 91403', phone: '1-888-373-4564' };
 
 // --- FileMaker client (reuse the connector; creds from ~/.claude.json) ---
@@ -66,12 +69,27 @@ const fm = await import('/Users/macstudio3/Projects/filemaker-mcp/src/fm-client.
 
 const pad = (n) => String(n).padStart(2, '0');
 const fmtDate = (d) => `${pad(d.getMonth() + 1)}/${pad(d.getDate())}/${d.getFullYear()}`;
+// Most recent scheduled run day strictly before `today` (walks back over weekend / off days).
+function prevScheduledRun(today) {
+  const d = new Date(today);
+  do { d.setDate(d.getDate() - 1); } while (!RUN_WEEKDAYS.has(d.getDay()));
+  return d;
+}
 function windowRange(today = new Date()) {
-  // hi = the memo that hits 10 days old TODAY (entered today-10).
-  // lo = the memo that hit 10 days old CATCHUP_DAYS-1 days ago (entered today-10-(CATCHUP-1)).
-  // On 8/20 with CATCHUP=3 → hi=8/10, lo=8/8 → FileMaker range "08/08/2026...08/10/2026".
-  const hi = new Date(today); hi.setDate(hi.getDate() - MIN_AGE_DAYS);
-  const lo = new Date(today); lo.setDate(lo.getDate() - MIN_AGE_DAYS - (CATCHUP_DAYS - 1));
+  if (CATCHUP_DAYS > 0) {
+    // Manual override — old fixed rolling band [today-10-(N-1) .. today-10].
+    const hi = new Date(today); hi.setDate(hi.getDate() - MIN_AGE_DAYS);
+    const lo = new Date(today); lo.setDate(lo.getDate() - MIN_AGE_DAYS - (CATCHUP_DAYS - 1));
+    return `${fmtDate(lo)}...${fmtDate(hi)}`;
+  }
+  // Default weekday-aware band: cover the 10-day cohorts for [prevScheduledRun .. yesterday].
+  //   hi = yesterday - 10           (= today - 11; today's own cohort is deferred to next run)
+  //   lo = prevScheduledRun - 10
+  // Tue → prevRun=Fri → [today-14 .. today-11] (Fri/Sat/Sun/Mon each -10).
+  // Wed/Thu/Fri → prevRun=yesterday → single day today-11.
+  const prev = prevScheduledRun(today);
+  const hi = new Date(today); hi.setDate(hi.getDate() - (MIN_AGE_DAYS + 1));
+  const lo = new Date(prev);  lo.setDate(lo.getDate() - MIN_AGE_DAYS);
   return `${fmtDate(lo)}...${fmtDate(hi)}`;
 }
 const norm = (s) => String(s || '').toLowerCase().trim();
@@ -212,7 +230,8 @@ const run = async () => {
   mkdirSync(join(ROOT, 'data', 'runs'), { recursive: true });
   writeFileSync(join(ROOT, 'data', 'runs', `scheduled-${today.replace(/\//g, '-')}.json`), JSON.stringify(report, null, 2));
 
-  console.log(`Scheduled follow-up — ${mode} · window ${win} (>=${MIN_AGE_DAYS}d, ${CATCHUP_DAYS}-day catch-up band)\n`);
+  const bandDesc = CATCHUP_DAYS > 0 ? `${CATCHUP_DAYS}-day manual catch-up band` : 'weekday-aware band (Tue covers Fri–Mon)';
+  console.log(`Scheduled follow-up — ${mode} · window ${win} (>=${MIN_AGE_DAYS}d, ${bandDesc})\n`);
   if (DRAFT) {
     console.log(`Created ${drafted.length} draft(s) in info@ Drafts for review:`);
     drafted.forEach((s) => console.log(`  ✎ ${s.name.padEnd(30)} [${s.vid}] → ${s.to}  (${s.n} SKU${s.n > 1 ? 's' : ''})${s.confirm ? '  [CONFIRM recipient]' : ''}`));

← c024fa0 auto-data-snapshot: 2026-09-01T08:28:16 (1 data files) — dat  ·  back to Sample Followup Sweep  ·  sample-followup: chase today's 10-day cohort today; Tue fold f546361 →