← back to Dw Unbuyable Recovery Pilot
tk10978-rebelwalls/build-plan.mjs
97 lines
#!/usr/bin/env node
// TK-10978 — Rebel Walls "unbuyable" recovery plan builder (READ-ONLY, dry-run).
//
// CORRECTION to the 2026-09-01 progress note: the 494 live unbuyable RW products
// use an mfr_sku "RS####" series (e.g. RS12851 "Tin Plates, Ontario") that is
// ABSENT from rebel_walls_catalog (which is entirely an "R####" series). So the
// "cost already staged, pure compute" premise is FALSE for these 494 — there is
// no identity join and no staged price for the RS-series. Recovery is therefore
// a Scalamandre-class CONTENT-MATCH (pattern -> catalog pattern_name), which
// "fails plausibly" and is PILOT-GATED per the methodology.
//
// SAFE cohort = live pattern (text before first comma) matches EXACTLY ONE
// catalog pattern_name that carries a SINGLE price_retail (n_prices=1) ->
// unambiguous price. Everything else is excluded:
// - multi_price_ambiguous : pattern maps to >1 catalog price (wrong-price risk)
// - no_catalog_match : pattern absent from catalog -> needs re-scrape
//
// SELL-PRICE CONVENTION (per the 09-01 vp-dw-commerce note, PILOT-VERIFY before batch):
// sellable variant = base $191 flat + custom.mural_price_per_sqm metafield,
// where per_sqm = round(catalog price_retail * 1.0909, 2). Keep $4.25 Sample.
//
// NO WRITES. psql read-only. Emits data/plan.csv + data/report.json.
import { writeFileSync, mkdirSync } from 'node:fs';
import { execSync } from 'node:child_process';
const HERE = new URL('.', import.meta.url).pathname;
mkdirSync(`${HERE}data`, { recursive: true });
const BASE_PRICE = 191; // flat sellable-variant base, per 09-01 note
const PER_SQM_MARKUP = 1.0909;
const SQL = `
BEGIN READ ONLY;
SELECT json_agg(t) FROM (
WITH live AS (
SELECT regexp_replace(sp.shopify_id,'.*/','') AS pid, sp.title, sp.variant_sku AS sample_sku,
sp.mfr_sku AS live_mfr,
lower(trim(split_part(replace(sp.title,' | Rebel Walls',''), ',', 1))) AS pat
FROM shopify_products sp
WHERE sp.vendor='Rebel Walls' AND sp.status='ACTIVE'
AND NOT coalesce(sp.has_product_variant,false)
AND sp.variant_sku ILIKE '%-Sample'
),
catp AS (
SELECT lower(trim(pattern_name)) AS pat,
count(DISTINCT price_retail) AS n_prices,
min(price_retail) AS price_retail,
(array_agg(dw_sku ORDER BY dw_sku))[1] AS a_dw_sku
FROM rebel_walls_catalog WHERE price_retail>0 GROUP BY 1
)
SELECT live.pid, live.title, live.sample_sku, live.live_mfr, live.pat,
catp.n_prices, catp.price_retail, catp.a_dw_sku
FROM live LEFT JOIN catp ON catp.pat=live.pat
) t;
ROLLBACK;
`;
const out = execSync('psql -h /tmp -d dw_unified -tA -v ON_ERROR_STOP=1', { input: SQL, encoding: 'utf8', maxBuffer: 64*1024*1024 });
const _s = out.slice(out.indexOf('['), out.lastIndexOf(']')+1);
const rows = _s ? JSON.parse(_s) : [];
const plan = [], multi = [], nomatch = [];
for (const r of rows) {
if (r.n_prices == null) { nomatch.push({ pid:r.pid, title:r.title, pat:r.pat, reason:'no_catalog_match_needs_rescrape' }); continue; }
if (Number(r.n_prices) > 1) { multi.push({ pid:r.pid, title:r.title, pat:r.pat, reason:`multi_price_ambiguous(${r.n_prices})` }); continue; }
const pr = Number(r.price_retail);
if (!(pr > 10 && pr < 10000)) { multi.push({ pid:r.pid, title:r.title, pat:r.pat, reason:`retail_range ${pr}` }); continue; }
const perSqm = Math.round(pr * PER_SQM_MARKUP * 100) / 100;
const muralSku = String(r.sample_sku).replace(/-Sample$/i, '');
plan.push({
pid:r.pid, title:r.title, sampleSku:r.sample_sku, muralSku,
live_mfr:r.live_mfr, matched_pattern:r.pat, catalog_dw_sku:r.a_dw_sku,
catalog_price_retail:pr, base_price:BASE_PRICE, per_sqm:perSqm,
matchType:'content-match(pattern,unique-price)', pilotRequired:true,
});
}
const report = {
ticket:'TK-10978', vendor:'Rebel Walls', child:'RW-494 corrected', generated_at:new Date().toISOString(),
mode:'READ-ONLY dry-run — NO writes fired',
premise_correction:'RS-series live SKUs absent from rebel_walls_catalog (R-series); NOT an identity join / staged-cost clean win',
sell_convention:`base $${BASE_PRICE} + custom.mural_price_per_sqm = price_retail*${PER_SQM_MARKUP} (09-01 note — PILOT-VERIFY)`,
raw_unbuyable: rows.length,
content_match_safe: plan.length,
multi_price_ambiguous_excluded: multi.length,
no_catalog_match_needs_rescrape: nomatch.length,
per_sqm_min: plan.length?Math.min(...plan.map(p=>p.per_sqm)):null,
per_sqm_max: plan.length?Math.max(...plan.map(p=>p.per_sqm)):null,
gate:'customer-facing Shopify write -> HARD-GATED; pilot-first; needs Steve on-ticket go',
};
const cols = ['pid','title','sampleSku','muralSku','live_mfr','matched_pattern','catalog_dw_sku','catalog_price_retail','base_price','per_sqm','matchType','pilotRequired'];
const esc = v => `"${String(v??'').replace(/"/g,'""')}"`;
const csv = [cols.join(',')].concat(plan.map(p=>cols.map(c=>esc(p[c])).join(','))).join('\n');
writeFileSync(`${HERE}data/plan.csv`, csv);
writeFileSync(`${HERE}data/report.json`, JSON.stringify({ report, excluded_multi:multi.slice(0,20), excluded_nomatch:nomatch.slice(0,20) }, null, 2));
console.log(JSON.stringify(report, null, 2));