← 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
M data/draft-ledger.jsonA data/recently-contacted.jsonM data/runs/scheduled-08-20-2026.jsonM scripts/scheduled-run.mjs
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 →