[object Object]

← back to Sample Followup Sweep

sample-followup: recently-contacted suppression — skip vendors the team already emailed since 8/15 (harvested from info@ Sent); redo drafts non-duplicative (26->3 vendors)

aacc67a6275099b57f75932f6c6b51ed7f717c61 · 2026-08-20 10:58:41 -0700 · Steve Abrams

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

Files touched

Diff

commit aacc67a6275099b57f75932f6c6b51ed7f717c61
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 20 10:58:41 2026 -0700

    sample-followup: recently-contacted suppression — skip vendors the team already emailed since 8/15 (harvested from info@ Sent); redo drafts non-duplicative (26->3 vendors)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/draft-ledger.json              |  12 +-
 data/recently-contacted.json        |   1 +
 data/runs/scheduled-08-20-2026.json | 283 +++++++-----------------------------
 scripts/scheduled-run.mjs           |  19 ++-
 4 files changed, 77 insertions(+), 238 deletions(-)

diff --git a/data/draft-ledger.json b/data/draft-ledger.json
index 0967ef4..570b0a3 100644
--- a/data/draft-ledger.json
+++ b/data/draft-ledger.json
@@ -1 +1,11 @@
-{}
+{
+  "538831": "08/20/2026",
+  "538893": "08/20/2026",
+  "538924": "08/20/2026",
+  "538966": "08/20/2026",
+  "538985": "08/20/2026",
+  "538986": "08/20/2026",
+  "538996": "08/20/2026",
+  "539003": "08/20/2026",
+  "539014": "08/20/2026"
+}
\ No newline at end of file
diff --git a/data/recently-contacted.json b/data/recently-contacted.json
new file mode 100644
index 0000000..0085f41
--- /dev/null
+++ b/data/recently-contacted.json
@@ -0,0 +1 @@
+["riverah@yorkwall.com","orders@yorkwall.com","orders@wallquest.com","matt.schoffman@kravet.com","samples@koroseal.com","sshortino@koroseal.com","jballard@phillipjeffries.com","ddedic@momtex.com","karinkleintextiles@gmail.com","customerservice@wolfgordon.com","ari@quintushome.com","jill@lbiboyd.com","jcarol@lbiboyd.com","hbarragan@kneedlerfauchere.com","polancol@oalusa.com","sales@sancarwallcovering.com","fawn.bowen@romo.com","sample@mdcwall.com","bfoss@maharam.com","aaramburu@maharam.com","annalisa@elitis.us","therese@egg-and-dart.com","cathy.dy@thibautdesign.com","federico.contigiani@lincrusta.com","hmartinez@innovationsusa.com","amy.d@astek.com"]
diff --git a/data/runs/scheduled-08-20-2026.json b/data/runs/scheduled-08-20-2026.json
index 88c6d06..0071274 100644
--- a/data/runs/scheduled-08-20-2026.json
+++ b/data/runs/scheduled-08-20-2026.json
@@ -1,284 +1,99 @@
 {
-  "ranAt": "2026-08-20T17:33:38.106Z",
+  "ranAt": "2026-08-20T17:58:41.672Z",
   "mode": "DRAFT",
-  "window": "07/07/2025...08/10/2026",
+  "window": "08/05/2026...08/10/2026",
   "stampDate": "08/20/2026",
   "vendorsSent": 0,
-  "vendorsDrafted": 26,
-  "vendorsSkipped": 12,
+  "vendorsDrafted": 4,
+  "vendorsSkipped": 0,
   "vendorsNeedsConfirm": 0,
+  "vendorsSuppressed": 7,
   "sent": [],
   "drafted": [
     {
-      "vid": "SIR",
-      "name": "Sir Edward",
-      "to": "sales@sir-edward.com",
-      "n": 6,
-      "draftId": "r-5970601167556171131",
-      "confirm": true
-    },
-    {
-      "vid": "YOR",
-      "name": "York",
-      "to": "RiveraH@yorkwall.com, orders@yorkwall.com",
-      "n": 14,
-      "draftId": "r-8636546062965061604",
+      "vid": "BRE",
+      "name": "Brewster",
+      "to": "brewsterorders@brewp.com",
+      "n": 3,
+      "draftId": "r-2835675445447194018",
       "confirm": false
     },
     {
-      "vid": "COW",
-      "name": "Cowtan & Tout / Colefax & Fowler / Jane Churchill",
-      "to": "la_sample@cowtan.com",
-      "n": 5,
-      "draftId": "r-4679126342639093455",
-      "confirm": true
-    },
-    {
-      "vid": "THIB",
-      "name": "Thibaut",
-      "to": "cathy.dy@thibautdesign.com",
-      "n": 1,
-      "draftId": "r-2884334284696654031",
+      "vid": "GREEN",
+      "name": "Greenland (bmwallpaper)",
+      "to": "sales@bmwallpaper.com",
+      "n": 2,
+      "draftId": "r-7589980844368179437",
       "confirm": false
     },
     {
-      "vid": "RBL",
-      "name": "Rebel Walls (via Gimmersta US)",
-      "to": "support@gimmersta.com",
-      "n": 4,
-      "draftId": "r-9049491309783430956",
+      "vid": "BM",
+      "name": "Greenland (bmwallpaper)",
+      "to": "sales@bmwallpaper.com",
+      "n": 3,
+      "draftId": "r-3509206222191540443",
       "confirm": false
     },
-    {
-      "vid": "EGG",
-      "name": "Egg & Dart (Arte distributor)",
-      "to": "therese@egg-and-dart.com",
-      "n": 1,
-      "draftId": "r1596340670826350654",
-      "confirm": true
-    },
-    {
-      "vid": "AMER",
-      "name": "American Silk Mills (SolidSensuede)",
-      "to": "crystal.guard@americansilk.com",
-      "n": 4,
-      "draftId": "r-2362448450327166742",
-      "confirm": true
-    },
-    {
-      "vid": "COOR",
-      "name": "Coordonné (via NewWall)",
-      "to": "tricia@newwall.com",
-      "n": 1,
-      "draftId": "r4258423404635705361",
-      "confirm": true
-    },
     {
       "vid": "SANDB",
       "name": "Sandberg (via Gimmersta US)",
       "to": "support@gimmersta.com",
-      "n": 2,
-      "draftId": "r-2536473800027103667",
-      "confirm": false
-    },
-    {
-      "vid": "HYG",
-      "name": "Hygge & West",
-      "to": "support@hyggeandwest.com",
-      "n": 1,
-      "draftId": "r-4779139302854925052",
-      "confirm": true
-    },
-    {
-      "vid": "KRA",
-      "name": "Kravet",
-      "to": "matt.schoffman@kravet.com",
-      "n": 2,
-      "draftId": "r-776532427590871620",
-      "confirm": false
-    },
-    {
-      "vid": "TWI",
-      "name": "twill (Karin Klein)",
-      "to": "karinkleintextiles@gmail.com",
-      "n": 3,
-      "draftId": "r1392345451707179907",
-      "confirm": true
-    },
-    {
-      "vid": "CHRISTIAN LACROIX EUROPE",
-      "name": "Christian Lacroix (via Osborne & Little / Designers Guild)",
-      "to": "Polancol@oalusa.com",
-      "n": 2,
-      "draftId": "r4746630731070883599",
-      "confirm": true
-    },
-    {
-      "vid": "AS CREATION",
-      "name": "Sancar (AS Création) — inferred",
-      "to": "sales@sancarwallcovering.com",
-      "n": 1,
-      "draftId": "r-113682690996652223",
-      "confirm": false
-    },
-    {
-      "vid": "ARTE INTERNATIONAL",
-      "name": "Arte (@ Egg & Dart)",
-      "to": "Therese@egg-and-dart.com",
-      "n": 1,
-      "draftId": "r888237295958038762",
-      "confirm": false
-    },
-    {
-      "vid": "ANNA FRENCH",
-      "name": "Thibaut",
-      "to": "cathy.dy@thibautdesign.com",
-      "n": 3,
-      "draftId": "r5691186153559205464",
-      "confirm": false
-    },
-    {
-      "vid": "VER",
-      "name": "Versace (via Sancar)",
-      "to": "sales@sancarwallcovering.com",
-      "n": 1,
-      "draftId": "r4385134615292993539",
-      "confirm": true
-    },
-    {
-      "vid": "MOM",
-      "name": "Momentum (→Hollywood, INTERNAL chase only)",
-      "to": "customerservice@momtex.com",
-      "n": 2,
-      "draftId": "r7198432144051088029",
-      "confirm": true
-    },
-    {
-      "vid": "WOL",
-      "name": "Wolf Gordon",
-      "to": "samples@wolfgordon.com",
-      "n": 2,
-      "draftId": "r-1849234445079891812",
-      "confirm": true
-    },
-    {
-      "vid": "OSB",
-      "name": "Osborne & Little",
-      "to": "Polancol@oalusa.com",
-      "n": 3,
-      "draftId": "r397354059614624942",
-      "confirm": false
-    },
-    {
-      "vid": "ASTSAND",
-      "name": "Astek",
-      "to": "amy.d@astek.com",
       "n": 1,
-      "draftId": "r5896643304860099411",
-      "confirm": false
-    },
-    {
-      "vid": "CHIN",
-      "name": "China Seas by Quadrille — inferred",
-      "to": "Rhaiza@quadrilleinc.com",
-      "n": 2,
-      "draftId": "r7602192929320846844",
+      "draftId": "r-8609275683066690838",
       "confirm": false
-    },
+    }
+  ],
+  "needsConfirm": [],
+  "skipped": [],
+  "suppressed": [
     {
       "vid": "ARTE",
       "name": "Arte (@ Egg & Dart)",
       "to": "Therese@egg-and-dart.com",
       "n": 1,
-      "draftId": "r877641064680640389",
-      "confirm": false
-    },
-    {
-      "vid": "BRE",
-      "name": "Brewster",
-      "to": "brewsterorders@brewp.com",
-      "n": 2,
-      "draftId": "r-2728495066647380270",
-      "confirm": false
-    },
-    {
-      "vid": "GREEN",
-      "name": "Greenland (bmwallpaper)",
-      "to": "sales@bmwallpaper.com",
-      "n": 1,
-      "draftId": "r3023553735302895450",
-      "confirm": false
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
       "vid": "ROMO",
       "name": "Romo",
       "to": "fawn.bowen@romo.com",
       "n": 1,
-      "draftId": "r-8615122704258863622",
-      "confirm": true
-    }
-  ],
-  "needsConfirm": [],
-  "skipped": [
-    {
-      "vid": "FTR",
-      "n": 4,
-      "reason": "no sample email/account on file"
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
-      "vid": "TBD",
-      "n": 6,
-      "reason": "no sample email/account on file"
-    },
-    {
-      "vid": "SCH",
+      "vid": "OSB",
+      "name": "Osborne & Little",
+      "to": "Polancol@oalusa.com",
       "n": 2,
-      "reason": "no-chase vendor"
-    },
-    {
-      "vid": "ULR",
-      "n": 1,
-      "reason": "no sample email/account on file"
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
-      "vid": "\r",
-      "n": 1,
-      "reason": "no sample email/account on file"
+      "vid": "YOR",
+      "name": "York",
+      "to": "RiveraH@yorkwall.com, orders@yorkwall.com",
+      "n": 6,
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
-      "vid": "FOL",
+      "vid": "KRA",
+      "name": "Kravet",
+      "to": "matt.schoffman@kravet.com",
       "n": 2,
-      "reason": "no-chase vendor"
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
-      "vid": "1838 WALLCOVERINGS",
+      "vid": "WQ",
+      "name": "WallQuest / Malibu",
+      "to": "orders@wallquest.com",
       "n": 1,
-      "reason": "no sample email/account on file"
-    },
-    {
-      "vid": "PHILLIPE ROMANO",
-      "n": 1,
-      "reason": "no-chase vendor"
-    },
-    {
-      "vid": "SCHUMACHER",
-      "n": 18,
-      "reason": "no sample email/account on file"
+      "reason": "already emailed since 8/15 (manual team send)"
     },
     {
-      "vid": "CARL",
-      "n": 1,
-      "reason": "no-chase vendor"
-    },
-    {
-      "vid": "SPN",
-      "n": 11,
-      "reason": "no-chase vendor"
-    },
-    {
-      "vid": "POI",
+      "vid": "THIB",
+      "name": "Thibaut",
+      "to": "cathy.dy@thibautdesign.com",
       "n": 1,
-      "reason": "no sample email/account on file"
+      "reason": "already emailed since 8/15 (manual team send)"
     }
   ]
 }
\ No newline at end of file
diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index 044bc15..4c4590b 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -40,7 +40,13 @@ const FIELD_SENT = 'Date Email Sent to Vendor after 10 Days';
 // 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, CATCHUP_DAYS = Number(process.env.CATCHUP || 3);
+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.
+const catchupDays = (d) => (d.getDay() === 2 ? 4 : 1); // getDay: 0=Sun … 2=Tue
+const CATCHUP_DAYS = Number(process.env.CATCHUP) || catchupDays(new Date());
 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) ---
@@ -151,10 +157,16 @@ const run = async () => {
   // recordIds already have a draft locally so the daily run never piles up duplicate drafts.
   const LEDGER = 'data/draft-ledger.json';
   const draftLedger = DRAFT ? readJSON(LEDGER, {}) : {};
-  const sent = [], skipped = [], needsConfirm = [], drafted = [];
+  // Recently-contacted suppression: vendors the human desk already emailed a "New Sample Request"
+  // for (harvested from info@ Sent into data/recently-contacted.json). Stops the sweep from
+  // duplicating what the team already sent manually — the whole point of the 2026-08-20 fix.
+  const RECENT = new Set(readJSON('data/recently-contacted.json', []).map((e) => String(e).toLowerCase().trim()).filter(Boolean));
+  const sent = [], skipped = [], needsConfirm = [], drafted = [], suppressed = [];
   for (const [vid, rows] of Object.entries(byVid)) {
     const c = cmap[vid];
     if (!c || c.noChase) { skipped.push({ vid, n: rows.length, reason: c?.noChase ? 'no-chase vendor' : 'no sample email/account on file' }); continue; }
+    const recips = String((c.sample_email || c.main_email) || '').toLowerCase().split(/[,;]\s*/).map((s) => s.trim()).filter(Boolean);
+    if (recips.length && recips.some((e) => RECENT.has(e))) { suppressed.push({ vid, name: c.name, to: (c.sample_email || c.main_email), n: rows.length, reason: 'already emailed since 8/15 (manual team send)' }); continue; }
     // In SEND mode, an unconfirmed (resolved/fallback) recipient is HELD — never fires. In DRAFT
     // mode the draft itself IS the review, so everything is drafted for Steve to check + send.
     if (c.needs_confirm && !DRAFT) { needsConfirm.push({ vid, name: c.name, to: (c.sample_email || c.main_email), n: rows.length, skus: rows.map((r) => r.sku), reason: 'main-email fallback — CONFIRM person vs recent emails before send' }); continue; }
@@ -183,7 +195,7 @@ const run = async () => {
 
   const mode = DRAFT ? 'DRAFT' : SEND ? 'SEND' : 'DRY-RUN';
   const report = { ranAt: new Date().toISOString(), mode, window: win, stampDate: today,
-    vendorsSent: sent.length, vendorsDrafted: drafted.length, vendorsSkipped: skipped.length, vendorsNeedsConfirm: needsConfirm.length, sent, drafted, needsConfirm, skipped };
+    vendorsSent: sent.length, vendorsDrafted: drafted.length, vendorsSkipped: skipped.length, vendorsNeedsConfirm: needsConfirm.length, vendorsSuppressed: suppressed.length, sent, drafted, needsConfirm, skipped, suppressed };
   mkdirSync(join(ROOT, 'data', 'runs'), { recursive: true });
   writeFileSync(join(ROOT, 'data', 'runs', `scheduled-${today.replace(/\//g, '-')}.json`), JSON.stringify(report, null, 2));
 
@@ -196,6 +208,7 @@ const run = async () => {
     sent.forEach((s) => console.log(`  ${SEND ? '✓' : '·'} ${s.name.padEnd(30)} [${s.vid}] → ${s.to}  (${s.n} SKU${s.n > 1 ? 's' : ''})`));
     if (needsConfirm.length) { console.log(`\n⚠ ${needsConfirm.length} main-email fallback(s) — HELD for person-confirmation (never auto-sent):`); needsConfirm.forEach((s) => console.log(`  ? ${s.name.padEnd(30)} [${s.vid}] → ${s.to}  (${s.n} SKU${s.n > 1 ? 's' : ''}) — ${s.reason}`)); }
   }
+  if (suppressed.length) { console.log(`\n⊘ Suppressed ${suppressed.length} (team already emailed since 8/15 — not duplicated):`); suppressed.forEach((s) => console.log(`  ⊘ ${(s.name||'').padEnd(30)} [${s.vid}] → ${s.to}`)); }
   if (skipped.length) { console.log(`\nSkipped ${skipped.length} (no contact / no-chase / error):`); skipped.forEach((s) => console.log(`  - [${s.vid}] ${s.n} SKU(s): ${s.reason}`)); }
   console.log(`\n→ data/runs/scheduled-${today.replace(/\//g, '-')}.json`);
 };

← 4faf95e auto-data-snapshot: 2026-08-20T10:47:55 (2 data files) — dat  ·  back to Sample Followup Sweep  ·  reconciled drafter: window 8/6-8/10 (fill-in since last send 5df1ebe →