← 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.');