← back to Sanderson Onboard

tk10873/build_v4_promotions.mjs

79 lines

#!/usr/bin/env node
// build_v4_promotions.mjs — TK-10873 follow-up. Promote the harvested held rows to a
// REPRICE-shaped draft (v4) the proven reprice_live_runbook.mjs can drive unchanged.
// READ-ONLY: reads held_price_proposals.json + v3; writes zoffany_optionA_promotions_v4.json.
//
// Promotion set (45):
//   • 43 standard 11yd-roll harvested rows (PROPOSE 35 + PROPOSE_LOW_CONF 8) → price = harvested us_retail.
//   • 2 ZFOW rows (ZFOW312941/943) → price = our_price $309.50 (= cost/0.65/0.85, the Option-A
//     contract on a known cost; decision-resolved, not a guess).
// EXCLUDED (by design, handled as decisions elsewhere):
//   • 2 PROPOSE_UNIT_REVIEW + the per-metre/yard murals (Pangolins/Verdure/Phaedra/Pompadour) → HOLD (unit-mislabel risk).
//   • Highclere (roll, no price found) → HOLD for targeted price harvest.
//   • ZAQF322696 (already product_type=Fabric) → verify-then-price decision, not auto-reprice.
//   • 313114 → HOLD pending the ZOW0146-01 mapping confirm.
import fs from 'node:fs';
const DIR = new URL('.', import.meta.url).pathname;
const props = JSON.parse(fs.readFileSync(`${DIR}held_price_proposals.json`, 'utf8')).proposals;
const v3 = JSON.parse(fs.readFileSync(`${DIR}zoffany_optionA_draft_v3.json`, 'utf8'));

// Web-verification pass (2 web-researcher agents, $0) confirmed current US LIST price per pattern.
// HOLD the patterns whose harvested price came back BELOW current US list (our scrape was too low —
// shipping it would underprice): Darnley ($390 vs ~$450), Ebru II ($230 vs $342), Mitford Damask
// ($230 vs $342+). DROP Domino Paper (double-roll unit + EUR source, math didn't close). The other
// 15 patterns CONFIRM at their harvested US list (incl. the sibling-inferred Kauri/Ormonde Stripe/
// Wallis, independently re-confirmed at $342; Cody's Columns/Medallion $494 "maybe-sale" alarm
// reconciled — $494 is DecoratorsBest's real MAP list, not a sale).
// DTD (2026-08-31, 7B/1A + Cody's coherence catch): all 3 held rows are ZDAR (Darnley collection).
// Ebru II is directly price-pinned on its own US product page ($342, a marbling PRINT) → SHIP.
// Darnley + Mitford Damask are unpinned within the collection (Darnley colorway spread $416–460;
// Mitford is a DAMASK at $342–449 with one unreliable low source) → HOLD together as a
// Darnley-collection per-colorway price-confirm follow-up. Do NOT launder Ebru II's evidence onto Mitford.
const HOLD_PATTERNS = /^(Darnley|Mitford Damask)$/i;
const DROP_SKUS = new Set(['ZEND313098']); // Domino Paper
// Ebru II's harvested us_retail is the STALE below-list scrape ($230). It ships at the web-VERIFIED
// current US list ($342, direct product-page pin) — NOT the wrong scraped number. Any promoted row
// whose scrape was below-list MUST use its verified price here, never the stale us_retail.
const VERIFIED_PRICE = { ZDAR312865: 342.00 }; // Ebru II (Snow), decoratorsbest.com

const rows = [];
// verified 11yd-roll harvested rows (PROPOSE + PROPOSE_LOW_CONF, exactly 11yd single roll, priced)
for (const r of props) {
  if (!['PROPOSE', 'PROPOSE_LOW_CONF'].includes(r.disposition)) continue; // excludes UNIT_REVIEW murals
  if (r.unit !== '11yd-roll' || r.us_retail == null) continue;            // exactly single 11yd roll (drops double-roll)
  if (DROP_SKUS.has(r.mfr_sku)) continue;                                  // Domino Paper
  if (HOLD_PATTERNS.test(r.pattern || '')) continue;                       // below-list patterns → held
  const price = VERIFIED_PRICE[r.mfr_sku] ?? Number(r.us_retail);          // verified override wins over stale scrape
  const verified = r.mfr_sku in VERIFIED_PRICE;
  rows.push({
    mfr_sku: r.mfr_sku, action: 'REPRICE',
    proposed_sellable_price: Number(Number(price).toFixed(2)),
    price_trade: null, // harvested market retail — no DW trade cost, so no cost floor
    price_source: verified
      ? `web_verified_us_list:${VERIFIED_PRICE[r.mfr_sku]} (corrected from stale scrape $${r.us_retail})`
      : `harvest_us_retail:${r.source} (${r.confidence}, US-list-verified)`,
    unit: r.unit, note: `${r.pattern} — ${verified ? 'web-verified current US list (scrape was below-list)' : 'harvested US retail, web-confirmed current list'}`,
  });
}
const harvested = rows.length;
// 2 ZFOW at our_price (cost/0.65/0.85 = Option-A contract)
for (const r of v3) {
  if (r.action !== 'HELD_NEEDS_PRICE') continue;
  if (!['ZFOW312941', 'ZFOW312943'].includes(r.mfr_sku)) continue;
  rows.push({
    mfr_sku: r.mfr_sku, action: 'REPRICE',
    proposed_sellable_price: 309.50,
    price_trade: 171, // our_price 309.50 = 171/0.65/0.85; floor = the cost
    price_source: 'our_price_derived (cost/0.65/0.85 — Option-A contract)',
    unit: '11yd-roll', note: `${r.mfr_sku} — DW-derived retail (resolves HELD_NEEDS_PRICE)`,
  });
}
fs.writeFileSync(`${DIR}zoffany_optionA_promotions_v4.json`, JSON.stringify(rows, null, 2));
const high = rows.filter(r => /\(high[,)]/.test(r.price_source)).length;
const med = harvested - high;
const prices = rows.map(r => r.proposed_sellable_price);
console.log(`v4 promotions: ${rows.length} REPRICE rows (${harvested} harvested rolls + ${rows.length - harvested} ZFOW our_price)`);
console.log(`  harvested by confidence: ${high} high · ${med} med   |   ZFOW our_price: ${rows.length - harvested}`);
console.log(`  price range: $${Math.min(...prices)}–$${Math.max(...prices)}`);
console.log(`  → zoffany_optionA_promotions_v4.json`);