[object Object]

← back to Sample Followup Sweep

sample-followup: resolve 68 blocked overdue rows (TK-12085)

7394a41f4a0008afda79b9efd72de5140cfaa425 · 2026-09-23 13:25:02 -0700 · Steve Abrams

send-reconciled.mjs now falls back to contacts.json via slugForVid (single
source of truth) + strips \r from vids (KRA\r bug); adds fabr/wat/reid/grd,
corrects ftr->shafik@futuretextiles.com, 1838 email confirmed. 19 vendor
drafts created (DRAFT-only); MDCKEN(8) unresolved, SPN(2) no-chase.

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

Files touched

Diff

commit 7394a41f4a0008afda79b9efd72de5140cfaa425
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 13:25:02 2026 -0700

    sample-followup: resolve 68 blocked overdue rows (TK-12085)
    
    send-reconciled.mjs now falls back to contacts.json via slugForVid (single
    source of truth) + strips \r from vids (KRA\r bug); adds fabr/wat/reid/grd,
    corrects ftr->shafik@futuretextiles.com, 1838 email confirmed. 19 vendor
    drafts created (DRAFT-only); MDCKEN(8) unresolved, SPN(2) no-chase.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/contacts.json                | 39 +++++++++++++++++++++++++-----
 data/outstanding-snapshot.json    |  5 ----
 data/reconciled-draft-ledger.json | 51 ++++++++++++++++++++++++++++++++++++++-
 scripts/send-reconciled.mjs       | 36 +++++++++++++++++++++++----
 4 files changed, 114 insertions(+), 17 deletions(-)

diff --git a/data/contacts.json b/data/contacts.json
index 6ef5aae..d48fd04 100644
--- a/data/contacts.json
+++ b/data/contacts.json
@@ -287,11 +287,12 @@
     "contact_source": "George/Gmail mining 2026-08-20 (grounded on live outstanding-memo Mfr Patterns)"
   },
   "ftr": {
-    "name": "(unresolved) metallic-leaf vendor \u2014 GLM/GLMG SL08/B20 gunmetal/bronze/copper codes",
+    "name": "Future Textiles (Shafik Fam)",
     "needs_confirm": true,
-    "unresolved_email": true,
-    "contact_source": "George/Gmail mining 2026-08-20 (grounded on live outstanding-memo Mfr Patterns): no vendor identity found in George; metallic/gilded-leaf line, distributor unknown",
-    "sample_email": "ftg1@futuretextiles.com"
+    "unresolved_email": false,
+    "contact_source": "TK-12085 (2026-09-23) George/Gmail resolution: FTR = Future Textiles, GLM/GLMG metallic-leaf line. Confirmed rep Shafik Fam <shafik@futuretextiles.com> via a live TWO-WAY DW<->Future Textiles thread ('Re: stock check sm 33115', Shafik emailed FROM shafik@futuretextiles.com to info@ 2026-09-21). Supersedes the prior unconfirmed guess ftg1@futuretextiles.com (never verified). needs_confirm held pending Steve's confirm shafik@ vs ftg1@ + DW acct#.",
+    "sample_email": "shafik@futuretextiles.com",
+    "account_number": "On File"
   },
   "ulr": {
     "name": "Ulrich Welter",
@@ -310,8 +311,8 @@
   "1838-wallcoverings": {
     "name": "1838 Wallcoverings",
     "needs_confirm": true,
-    "unresolved_email": true,
-    "contact_source": "George/Gmail mining 2026-08-20 (grounded on live outstanding-memo Mfr Patterns): real line (1838-scraper-manager); likely distributed via Maxwell Fabrics (distribution@postmark.pdfsystems.com is order-only, not a sample desk) \u2014 no inbound sample-desk contact captured; CONFIRM the 1838/Maxwell sample email",
+    "unresolved_email": false,
+    "contact_source": "TK-12085 (2026-09-23): the address DW actually uses for 1838 sample requests is scott.myer@gmail.com (Scott Myer, the 1838 rep per 1838-scraper-manager) \u2014 confirmed by a real outbound 'New Sample Request for Designer Wallcoverings' \u2192 scott.myer@gmail.com on 2026-09-15. needs_confirm held (personal gmail) pending Steve's confirm. Prior note: possibly distributed via Maxwell Fabrics.",
     "sample_email": "scott.myer@gmail.com",
     "account_number": "On File"
   },
@@ -373,5 +374,31 @@
     "name": "Brunschwig & Fils (@ Kravet)",
     "sample_email": "matt.schoffman@kravet.com",
     "account_number": "10087117"
+  },
+  "fabr": {
+    "name": "Fabricut",
+    "sample_email": "customers@fabricut.com",
+    "account_number": "On File",
+    "contact_source": "TK-12085 (2026-09-23) George/Gmail resolution: DWFC line. customers@fabricut.com is the desk DW sends sample requests to — confirmed by our own outbound 'New Sample Request for Designer Wallcoverings' → customers@fabricut.com (2026-09-10) and Fabricut Customers <customers@fabricut.com> replies to info@ (RE: Online, RE: price check)."
+  },
+  "reid": {
+    "name": "Reid Witlin Textiles",
+    "sample_email": "samples@rwltd.com",
+    "account_number": "On File",
+    "contact_source": "TK-12085 (2026-09-23) George/Gmail resolution: DWRW line (reid-witlin-scraper-manager). Confirmed dedicated sample desk samples@rwltd.com — our outbound 'New Sample Request for Designer Wallcoverings' → samples@rwltd.com (2026-09-09) and Bryan Roes <Bryan@rwltd.com> replied 'RE: New Sample Request' from the same domain."
+  },
+  "wat": {
+    "name": "Watts 1874",
+    "needs_confirm": true,
+    "sample_email": "sophie@watts1874.co.uk",
+    "account_number": "On File",
+    "contact_source": "TK-12085 (2026-09-23) George/Gmail resolution: WAT = Watts 1874 (UK), DWC 'Crema Romana/Romeo Stripe' W0135 codes. sophie@watts1874.co.uk (Sophie Horton) is the desk DW sent the 'New Sample Request' to on 2026-09-04. needs_confirm: Sophie was Out-of-Office and there is an active US-side thread from hannah@barriebenson.com (a DESIGNER/customer, NOT the vendor — do NOT chase there); Steve confirm sophie@ vs a general watts1874 desk (e.g. showroom@watts1874.co.uk) + DW acct# before send."
+  },
+  "grd": {
+    "name": "Graduate Collection",
+    "needs_confirm": true,
+    "sample_email": "orders@graduatecollection.co.uk",
+    "account_number": "On File",
+    "contact_source": "TK-12085 (2026-09-23) George/Gmail + vendor_registry resolution: GRD = Graduate Collection (UK), GRD 'All Over Leopard' codes. orders@graduatecollection.co.uk is the confirmed vendor order/dispatch desk (sent DW real order confirmation No:6562 + Tracking, 2026-08-26/09-11). needs_confirm: this is the ORDERS desk (no dedicated samples@ address found); Steve confirm it accepts sample-memo follow-ups + DW acct# before send."
   }
 }
\ No newline at end of file
diff --git a/data/outstanding-snapshot.json b/data/outstanding-snapshot.json
index 8142105..59cc895 100644
--- a/data/outstanding-snapshot.json
+++ b/data/outstanding-snapshot.json
@@ -278,11 +278,6 @@
       "mfr": "AC709-8",
       "sku": "DWAK700394",
       "req": "08/25/2026"
-    },
-    {
-      "mfr": "306306W",
-      "sku": "DWCW718946",
-      "req": "08/27/2026"
     }
   ],
   "PAUL": [
diff --git a/data/reconciled-draft-ledger.json b/data/reconciled-draft-ledger.json
index b992c21..3eae304 100644
--- a/data/reconciled-draft-ledger.json
+++ b/data/reconciled-draft-ledger.json
@@ -76,5 +76,54 @@
   "THIB:DWTT80490": "09/23/2026",
   "THIB:DWTT80492": "09/23/2026",
   "THIB:DWTT75015": "09/23/2026",
-  "BRE:AMP44602": "09/23/2026"
+  "BRE:AMP44602": "09/23/2026",
+  "1838:DWWC505740": "09/23/2026",
+  "1838:DWPT200202": "09/23/2026",
+  "INN:PRW920181": "09/23/2026",
+  "INN:XOM33481": "09/23/2026",
+  "INN:XOM33479": "09/23/2026",
+  "INN:PRT47581": "09/23/2026",
+  "KRA:DWKK129882": "09/23/2026",
+  "ASTSAND:REW6210": "09/23/2026",
+  "ASTSAND:GLM53012": "09/23/2026",
+  "MDC:DWPP206897": "09/23/2026",
+  "MDC:DWPP205219": "09/23/2026",
+  "MDC:DWPP207044": "09/23/2026",
+  "MDC:DWPP207383": "09/23/2026",
+  "MDC:DWPP208905": "09/23/2026",
+  "MDC:DWPP208877": "09/23/2026",
+  "QUA:DWCW750746": "09/23/2026",
+  "QUA:DWCW764846": "09/23/2026",
+  "QUA:DWAK700394": "09/23/2026",
+  "PAUL:PANEL18021": "09/23/2026",
+  "RBL:DWRW75924": "09/23/2026",
+  "1838 WALLCOVERINGS:DWPT200040": "09/23/2026",
+  "VAH:DWYG293790": "09/23/2026",
+  "VAH:DWYG292210": "09/23/2026",
+  "SAN:MUR205499": "09/23/2026",
+  "SAN:MUR204599": "09/23/2026",
+  "SAN:MUR201799": "09/23/2026",
+  "SAN:DWWC501950": "09/23/2026",
+  "TWI:GRS21400": "09/23/2026",
+  "TWI:GRS100024": "09/23/2026",
+  "TWI:GRS801006": "09/23/2026",
+  "TWI:DWPW430045": "09/23/2026",
+  "FTR:GLM9206": "09/23/2026",
+  "FTR:GLM9212": "09/23/2026",
+  "FTR:GLM4172": "09/23/2026",
+  "FTR:GLM4032": "09/23/2026",
+  "FTR:GLM4033": "09/23/2026",
+  "FTR:GLM4044": "09/23/2026",
+  "FTR:GLM4004": "09/23/2026",
+  "FTR:GLM4048": "09/23/2026",
+  "FTR:GLM9215": "09/23/2026",
+  "FTR:GLM4113": "09/23/2026",
+  "FTR:GLM4117": "09/23/2026",
+  "WAT:DWC324296": "09/23/2026",
+  "BAR:DWBW600341": "09/23/2026",
+  "BAR:DWBW600342": "09/23/2026",
+  "REID:DWRW210156": "09/23/2026",
+  "FABR:DWFC230158": "09/23/2026",
+  "SAND:DWXH255221": "09/23/2026",
+  "GRD:GRD8806": "09/23/2026"
 }
\ No newline at end of file
diff --git a/scripts/send-reconciled.mjs b/scripts/send-reconciled.mjs
index 3f6c293..e964d67 100644
--- a/scripts/send-reconciled.mjs
+++ b/scripts/send-reconciled.mjs
@@ -8,6 +8,28 @@ import { readFileSync, writeFileSync } from 'node:fs'; import { homedir } from '
 const require = createRequire(import.meta.url);
 const ROOT = join(homedir(), 'Projects/sample-followup-sweep');
 const { georgeRequest } = require(join(ROOT, 'lib/george-transport.js'));
+// TK-12085: this drafter used to carry its OWN hardcoded 10-vendor map (V below) that had
+// diverged from contacts.json — the exact failure mode lib/slug-aliases.cjs's header warns
+// about. Result: 68 overdue rows were blocked "no vendor map" for vendors contacts.json
+// already knows (MDC, QUA, SAN, VAH, ASTSAND, BAR, KRA, PAUL, SAND, RBL, INN, TWI, 1838 ...).
+// Fix: the curated V stays (for its per-vendor `proc` names), but any vid NOT in V now FALLS
+// BACK to contacts.json via the same slugForVid bridge scheduled-run.mjs uses — ONE source of
+// truth. no-chase vendors are honoured (not drafted); an unresolvable vid still reports cleanly.
+const { slugForVid, normVid } = require(join(ROOT, 'lib/slug-aliases.cjs'));
+const CONTACTS = (() => { try { return JSON.parse(readFileSync(join(ROOT, 'data/contacts.json'), 'utf8')); } catch { return {}; } })();
+// Resolve a raw FileMaker vid → { name, to, acct, proc, needsConfirm } | { noChase, name } | null.
+function resolveVendor(rawVid) {
+  const key = normVid(rawVid);                         // strips stray CR/whitespace (KRA\r -> KRA)
+  const merged = (key === 'BM' || key === 'GREEN') ? 'BMGREEN' : key;
+  if (V[merged]) return { ...V[merged], needsConfirm: false };   // curated wins (keeps proc names)
+  const c = CONTACTS[slugForVid(key)];
+  if (!c) return null;                                 // genuinely unmapped -> report "no vendor map"
+  if (c.disposition === 'no-chase') return { noChase: true, name: c.name || key };
+  const to = c.sample_email || c.main_email;
+  if (!to || !c.account_number) return null;           // incomplete -> do not draft a half-formed letter
+  return { name: c.name || key, to, acct: c.account_number,
+    proc: (c.name ? c.name + ' ' : '') + 'sample/order desk', needsConfirm: !c.sample_email || !!c.needs_confirm };
+}
 // Rolling window: memos that crossed the 10-day follow-up floor within the last CATCHUP days (default 7),
 // so a daily scheduled run always covers any gap. Env WIN overrides for a manual one-off. (Draft-dedup
 // below stops the same memo being drafted twice across runs.)
@@ -49,9 +71,10 @@ const V = {
   THIB:   { name: 'Thibaut', to: 'cathy.dy@thibautdesign.com', acct: '0108751', proc: 'Cathy Dy' },
   ARTE:   { name: 'Arte (@ Egg & Dart)', to: 'Therese@egg-and-dart.com', acct: 'On File', proc: 'Therese Stachowiak' },
 };
-// merge BM + GREEN under one bmwallpaper letter
+// merge BM + GREEN under one bmwallpaper letter. TK-12085: normalise the vid (strip stray CR/
+// whitespace) BEFORE grouping so 'KRA\r' folds into 'KRA' instead of becoming its own unmapped key.
 const items = {};
-for (const [vid, arr] of Object.entries(snap)) { const key = (vid === 'BM' || vid === 'GREEN') ? 'BMGREEN' : vid; (items[key] = items[key] || []).push(...arr); }
+for (const [vid, arr] of Object.entries(snap)) { const nv = normVid(vid); const key = (nv === 'BM' || nv === 'GREEN') ? 'BMGREEN' : nv; (items[key] = items[key] || []).push(...arr); }
 
 function letter(v, rows) {
   // Steve 8/20: do NOT show our internal Ref # (DW SKU) to the vendor — Date Ordered + Mfr # only.
@@ -87,13 +110,16 @@ let ledger = {}; try { ledger = JSON.parse(readFileSync(LEDGER, 'utf8')); } catc
 const stamp = _fmt(new Date());
 console.log(`Reconciled follow-up — window ${WIN} — ${DO ? 'DRAFT into info@' : 'DRY-RUN (add --draft to create)'}\n`);
 for (const [key, rows] of Object.entries(items)) {
-  const v = V[key]; if (!v) { console.log(`  ? no vendor map for ${key} (${rows.length})`); continue; }
+  const v = resolveVendor(key);
+  if (!v) { console.log(`  ? no vendor map for ${key} (${rows.length})`); continue; }
+  if (v.noChase) { console.log(`  ⊘ ${(v.name||key).padEnd(26)} — no-chase vendor (${rows.length}, not drafted)`); continue; }
   const fresh = rows.filter(r => !ledger[`${key}:${r.sku}`]);
   if (!fresh.length) { console.log(`  ⊘ ${v.name.padEnd(26)} — all ${rows.length} already drafted (skip)`); continue; }
   const subj = `Sample Follow-Up — Outstanding Memos (Acct ${v.acct}) — Designer Wallcoverings`;
-  if (!DO) { console.log(`  · ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${fresh.length}) refs: ${fresh.map(r => r.sku).join(', ')}`); continue; }
+  const cflag = v.needsConfirm ? '  [CONFIRM recipient]' : '';
+  if (!DO) { console.log(`  · ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${fresh.length})${cflag} refs: ${fresh.map(r => r.sku).join(', ')}`); continue; }
   const r = await draft(v.to, subj, letter(v, fresh));
-  console.log(`  ${r === 'ok' ? '✎' : '✗'} ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${fresh.length}) ${r === 'ok' ? '' : r}`);
+  console.log(`  ${r === 'ok' ? '✎' : '✗'} ${v.name.padEnd(26)} → ${v.to.padEnd(40)} (${fresh.length})${cflag} ${r === 'ok' ? '' : r}`);
   if (r === 'ok') for (const row of fresh) ledger[`${key}:${row.sku}`] = stamp;
 }
 if (DO) writeFileSync(LEDGER, JSON.stringify(ledger, null, 2));

← 0c365b7 auto-data-snapshot: 2026-09-23T11:38:28 (1 data files) — dat  ·  back to Sample Followup Sweep  ·  auto-data-snapshot: 2026-09-23T13:45:21 (2 data files) — dat 20ca7a9 →