← back to Sample Followup Sweep
Add 60-day dead-lead ceiling + next vendor (Scalamandre) from live sweep
d8d9ad85f885ed0ba7906fba10a9264237354f17 · 2026-08-14 13:30:29 -0700 · Steve Abrams
Files touched
M bin/run.jsA data/vendors/scalamandre.jsonM lib/compose.jsM lib/sweep.js
Diff
commit d8d9ad85f885ed0ba7906fba10a9264237354f17
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 14 13:30:29 2026 -0700
Add 60-day dead-lead ceiling + next vendor (Scalamandre) from live sweep
---
bin/run.js | 22 +++++++++++++---------
data/vendors/scalamandre.json | 28 ++++++++++++++++++++++++++++
lib/compose.js | 5 +++--
lib/sweep.js | 13 ++++++++-----
4 files changed, 52 insertions(+), 16 deletions(-)
diff --git a/bin/run.js b/bin/run.js
index 2a836c6..2ce56f1 100644
--- a/bin/run.js
+++ b/bin/run.js
@@ -16,20 +16,21 @@ const today = new Date();
// Phase 1 — sweep
const { followUp, escalation, skipped } = sweep(vendor.rows, vendor.sweep_config || {}, today);
-// Phase 2 — resolve recipient
-if (!vendor.sample_email) {
- console.error(`SKIP ${vendor.name}: no "Email address for Samples" on file — not sending to a guessed address.`);
- process.exit(1);
-}
+// Phase 2 — resolve recipient (vendor-record fields). Missing = flag, don't guess.
+const missing = [];
+if (!vendor.sample_email) missing.push('sample_email (Email address for Samples)');
+if (!vendor.account_number) missing.push('account_number');
// Phase 3 — compose
const draft = compose(vendor, followUp);
-// Phase 4 — emit draft payload + preview (this JSON is exactly what gmail_create_draft consumes)
+// Phase 4 — emit preview always; only emit a sendable draft.json when the recipient is known.
const outDir = path.join(root, 'out');
fs.mkdirSync(outDir, { recursive: true });
-const payload = { account: 'info', to: draft.to, subject: draft.subject, body: draft.html };
-fs.writeFileSync(path.join(outDir, `${slug}.draft.json`), JSON.stringify(payload, null, 2));
+if (missing.length === 0) {
+ const payload = { account: 'info', to: draft.to, subject: draft.subject, body: draft.html };
+ fs.writeFileSync(path.join(outDir, `${slug}.draft.json`), JSON.stringify(payload, null, 2));
+}
fs.writeFileSync(path.join(outDir, `${slug}.preview.html`), preview(vendor, draft, followUp, escalation, skipped));
// Console summary
@@ -47,8 +48,11 @@ if (skipped.length) {
console.log(`\nSkipped (${skipped.length}):`);
skipped.forEach(s => console.log(` - ${s.row.mfr}: ${s.reason}`));
}
+if (missing.length) {
+ console.log(`\n⚠ NEEDS from vendor record before it can send: ${missing.join(', ')}`);
+}
console.log(`\nArtifacts:`);
-console.log(` out/${slug}.draft.json → payload for gmail_create_draft (account=info)`);
+if (missing.length === 0) console.log(` out/${slug}.draft.json → payload for gmail_create_draft (account=info)`);
console.log(` out/${slug}.preview.html → open in browser`);
console.log(`\nPhase 5 (send) + Phase 6 (stamp 2nd Request = today) stay gated / manual.\n`);
diff --git a/data/vendors/scalamandre.json b/data/vendors/scalamandre.json
new file mode 100644
index 0000000..b07332a
--- /dev/null
+++ b/data/vendors/scalamandre.json
@@ -0,0 +1,28 @@
+{
+ "name": "Scalamandre",
+ "vid": "SCA",
+ "account_number": "",
+ "sample_email": "",
+ "ship_to": {
+ "name": "Designer Wallcoverings",
+ "line1": "15442 Ventura Blvd. #102",
+ "city_state_zip": "Sherman Oaks, CA 91403",
+ "phone": "1-888-373-4564"
+ },
+ "sweep_config": {
+ "minAgeDays": 10,
+ "maxAgeDays": 60,
+ "excludeDisco": true,
+ "requireNotReceived": true,
+ "secondRequestPolicy": "escalate"
+ },
+ "_source": "live fm_find REPORT ON SAMPLES ORDERED 2026-08-14, vid=SCA, outstanding & not disco",
+ "rows": [
+ { "mfr": "wh000023326", "dw": "DWA88956", "client": "Nikki Sturm", "entered": "2026-06-12" },
+ { "mfr": "sc_0008g1194", "dw": "DWA89306", "client": "Michelle Kungis", "entered": "2026-06-30" },
+ { "mfr": "SC WP88355D 0006", "dw": "DWA82596", "client": "Leah E Ramsay", "entered": "2026-07-11" },
+ { "mfr": "wh000013326", "dw": "DWA88957", "client": "GoldLeaf Interior", "entered": "2026-07-22" },
+ { "mfr": "wtt661518", "dw": "DWA88660", "client": "GoldLeaf Interior", "entered": "2026-07-22" },
+ { "mfr": "CL WP26420 0003", "dw": "DWA89455", "client": "Lisa McIlhenny", "entered": "2026-07-27" }
+ ]
+}
diff --git a/lib/compose.js b/lib/compose.js
index 4bf5973..819ce53 100644
--- a/lib/compose.js
+++ b/lib/compose.js
@@ -7,7 +7,8 @@ function esc(s) {
}
function compose(vendor, rows) {
- const account = vendor.account_number;
+ const account = vendor.account_number || '‹CONFIRM DW ACCOUNT #›';
+ const to = vendor.sample_email || '‹CONFIRM SAMPLE EMAIL›';
const lines = rows
.map(r => (r.gap_before ? '<br>\n' : '') + esc(r.mfr))
.join('<br>\n');
@@ -39,7 +40,7 @@ ${esc(s.phone)}
<p>Best Regards,<br>Showroom Manager</p>
</div>`;
- return { to: vendor.sample_email, subject, html };
+ return { to, subject, html };
}
module.exports = { compose };
diff --git a/lib/sweep.js b/lib/sweep.js
index 75f93e3..3d9c125 100644
--- a/lib/sweep.js
+++ b/lib/sweep.js
@@ -13,6 +13,7 @@ function ageDays(entered, today) {
function sweep(rows, opts = {}, today = new Date()) {
const {
minAgeDays = 10,
+ maxAgeDays = 60, // Steve 2026-08-14: >60 days = dead lead, don't chase
excludeDisco = true,
requireNotReceived = true,
secondRequestPolicy = 'escalate', // 'escalate' (default A) | 'include'
@@ -24,14 +25,16 @@ function sweep(rows, opts = {}, today = new Date()) {
for (const r of rows) {
const age = ageDays(r.entered, today);
- const overdue = r.manual_add === true || (age !== null && age > minAgeDays);
if (excludeDisco && r.disco) { skipped.push({ row: r, reason: 'discontinued' }); continue; }
if (requireNotReceived && r.received) { skipped.push({ row: r, reason: 'already received' }); continue; }
- if (!overdue) {
- skipped.push({ row: r, reason: age === null ? 'no entered date' : `only ${age}d old (<=${minAgeDays})` });
- continue;
- }
+
+ // Manual adds bypass the age window entirely (explicit human override).
+ if (r.manual_add === true) { followUp.push({ ...r, age }); continue; }
+
+ if (age === null) { skipped.push({ row: r, reason: 'no request date' }); continue; }
+ if (age <= minAgeDays) { skipped.push({ row: r, reason: `only ${age}d old (<=${minAgeDays})` }); continue; }
+ if (age > maxAgeDays) { skipped.push({ row: r, reason: `dead lead (${age}d > ${maxAgeDays})` }); continue; }
if (r.second_request) {
if (secondRequestPolicy === 'include') followUp.push({ ...r, age });
← a51c7d2 Sample Follow-Up Sweep — phases 1-4 engine + canary vendor (
·
back to Sample Followup Sweep
·
Fleet sweep (37 vendors/106 items) + selection console (serv d5b7022 →