← back to Tk10895 GroupB
build-wf6.mjs
109 lines
#!/usr/bin/env node
// TK-10895 — build the side files that release the 6 Wildflowers II wallpapers.
//
// It does NOT mutate the canonical evidence CSV. The executor supports --evidence and
// --worklist overrides, so this writes SIDE COPIES and leaves the canonical file untouched:
// wf6-evidence.csv — the full evidence CSV with ONLY those 6 rows flipped PULL->GO and
// cost_cell_clean NO->YES, with the resolution proof recorded in pull_reason
// wf6-worklist.json — the 6 rows in the worklist shape the executor expects
//
// Why those 6 are releasable: their rows ALREADY pass G1 (5yd/PER_ROLL), G3 (cost_row_type_clean)
// and width_match. The ONLY blocker was G2 cost_cell_clean=NO. That is resolved — see NOTE below.
import fs from 'node:fs';
const EV_DIR = process.env.HOME + '/.claude/yolo-queue/evidence/TK-10895';
const SRC = EV_DIR + '/TrancheB-per-sku-evidence-20260910.csv';
const OUT_EV = process.env.HOME + '/Projects/tk10895-groupB/wf6-evidence.csv';
const OUT_WL = process.env.HOME + '/Projects/tk10895-groupB/wf6-worklist.json';
const WF = new Set(['785121', '785221', '785321', '785421', '785521', '785621']);
const WP_COST = 172; // the real cost — first token, constant across all 8 rows
const PRICE = (WP_COST / 0.65 / 0.85).toFixed(2); // 311.31
// SCOPE (contrarian correction, 2026-09-10): $172 is the cost of THESE 8 mfr_skus
// (785121/785221/785321/785421/785521/785621 wallpaper + 982022/982122 fabric), NOT of the
// pattern "Wildflowers II". Rows 6380-15WP / 6380-03WP / 6380-11WP are also Wildflowers II and
// carry $178/$198 — pricing them off $172 would underprice by $10.69/unit. Never restate this
// ruling at pattern level.
const NOTE = 'G2 RESOLVED 2026-09-10 without the vendor: the second token is an Excel FILL-DOWN '
+ 'COUNTER, not a price. The 8 Wildflowers II rows occupy a contiguous sheet block (3110-3117); '
+ 'their second token runs 142,143,144,145,146,147,148,149 (strictly +1, no gaps) while the first '
+ 'token stays constant at $172; and the sequence CROSSES the product-type boundary, running '
+ '142-147 on six 27in WALLPAPER rows and continuing into 148-149 on two 48in FABRIC rows. A cost '
+ 'cannot increment $1 per colorway and carry on across a wallpaper->fabric boundary. No other '
+ 'column carries those values, so it is not a copy of a visible field. CORROBORATION (independent, '
+ 'predates this ruling by 6 days): the two FABRIC SKUs in this same 8-row block, 982022 and 982122, '
+ 'have been ACTIVE and selling at exactly $311.31 since 2026-09-04 — a pipeline already priced them '
+ 'off $172, not off $148/$149. RARITY: across ~40 slash-Cost rows in the whole 4,592-row sheet this '
+ '8-row block is the ONLY incrementing case (Silhouette Reverse, Volpi and Ziggurat all hold their '
+ 'second token CONSTANT) — a systemic price field would recur; a one-time fill-handle leak looks '
+ 'exactly like this. SCOPE: applies to these 8 mfr_skus only, NOT to pattern "Wildflowers II" '
+ '(6380-15WP/03WP/11WP are $178) and NOT to Silhouette Reverse or Volpi, whose constant second '
+ 'tokens are more consistent with a real List/Net pair and remain UNRESOLVED. Cost basis = $172.';
// --- RFC4180-ish parse/serialise (matches the executor's own parser) ---
function parseCsv(txt) {
const rows = []; let row = [], f = '', q = false;
for (let i = 0; i < txt.length; i++) {
const c = txt[i];
if (q) { if (c === '"' && txt[i + 1] === '"') { f += '"'; i++; } else if (c === '"') q = false; else f += c; }
else if (c === '"') q = true;
else if (c === ',') { row.push(f); f = ''; }
else if (c === '\n') { row.push(f); rows.push(row); row = []; f = ''; }
else if (c !== '\r') f += c;
}
if (f || row.length) { row.push(f); rows.push(row); }
return rows;
}
const esc = v => /[",\n]/.test(v) ? '"' + String(v).replace(/"/g, '""') + '"' : String(v);
const rows = parseCsv(fs.readFileSync(SRC, 'utf8'));
const hdr = rows.shift();
const ix = Object.fromEntries(hdr.map((h, i) => [h, i]));
for (const c of ['decision', 'cost_cell_clean', 'cost_row_type_clean', 'mill_yards_per_roll', 'mill_unit', 'pull_reason', 'mfr_sku', 'dw_sku', 'shopify_id', 'our_width_in']) {
if (!(c in ix)) { console.error('evidence CSV missing column: ' + c); process.exit(1); }
}
let flipped = 0; const wl = [];
for (const r of rows) {
if (r.length !== hdr.length) continue;
const mfr = r[ix.mfr_sku];
if (!WF.has(mfr)) continue;
// FAIL CLOSED: only touch rows whose ONLY defect was G2. If anything else is off, refuse.
if (r[ix.mill_yards_per_roll] !== '5' || r[ix.mill_unit] !== 'PER_ROLL') {
console.error(`REFUSING ${mfr}: G1 not satisfied (${r[ix.mill_yards_per_roll]}/${r[ix.mill_unit]})`); process.exit(1);
}
if (r[ix.cost_row_type_clean] !== 'YES') {
console.error(`REFUSING ${mfr}: G3 cost_row_type_clean=${r[ix.cost_row_type_clean]}`); process.exit(1);
}
r[ix.decision] = 'GO';
r[ix.cost_cell_clean] = 'YES';
r[ix.pull_reason] = NOTE;
flipped++;
const width = (r[ix.our_width_in] || '').replace(/\.00$/, '');
wl.push({
mfr,
dw_sku: r[ix.dw_sku],
pid: String(r[ix.shopify_id]).split('/').pop(),
price: PRICE,
width,
// Label convention: the LIVE catalog is split — the 226 batch shipped
// "Sold Per Roll - <W>In Wide" (double space) while the 55 batch shipped
// "Sold Per Single Roll - 5 Yards - <W>In Wide". Use the LATTER: it is the
// deliberate later choice, it is what the executor's guard enforces, and it
// states the 5-yard put-up that is the whole point of this tranche.
label: `Sold Per Single Roll - 5 Yards - ${width}In Wide`,
});
}
if (flipped !== WF.size) { console.error(`REFUSING: flipped ${flipped}, expected ${WF.size}`); process.exit(1); }
fs.writeFileSync(OUT_EV, [hdr, ...rows].map(r => r.map(esc).join(',')).join('\n') + '\n');
fs.writeFileSync(OUT_WL, JSON.stringify(wl, null, 1));
console.log(`flipped ${flipped} rows PULL->GO (G2 resolved), price $${PRICE} each`);
for (const w of wl) console.log(` ${w.mfr} ${w.dw_sku} pid=${w.pid} ${w.label} $${w.price}`);
console.log(`\nevidence -> ${OUT_EV}\nworklist -> ${OUT_WL}`);
console.log('canonical evidence CSV NOT modified.');