← back to Dw Unbuyable Recovery Pilot
classify.mjs
89 lines
#!/usr/bin/env node
// TK-10875 — unbuyable-catalog classification (READ-ONLY).
// Writes data/classification-summary.json. Makes ZERO writes to any DB.
import { writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { queryOne, queryRows } from './lib/db.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const UNBUYABLE = `lower(status)='active' AND (has_product_variant IS DISTINCT FROM true)`;
// "Single priced variant" signature: variant_count=1 with a non-sample SKU priced
// above the $4.25 floor. In the mirror this LOOKS like "already sellable", but a
// live-Shopify check (2026-08-30) proved the opposite for the Wolf Gordon cohort:
// these are the RR-defect (a lone variant labeled option1='Sample' but priced at
// roll retail). The mirror CANNOT distinguish the two — it carries no option1 —
// so this count is "needs live classification", not a recovery verdict.
const SINGLE_PRICED = `variant_count = 1 AND variant_sku IS NOT NULL AND variant_sku NOT ILIKE '%-sample' AND COALESCE(min_variant_price,0) > 4.50`;
const SAMPLE_ONLY = `NOT (${SINGLE_PRICED})`;
// 1) Headline
const headline = queryOne(`
SELECT
count(*) AS unbuyable_total,
count(*) FILTER (WHERE ${SINGLE_PRICED}) AS single_priced_variant_needs_live_check,
count(*) FILTER (WHERE ${SAMPLE_ONLY}) AS sample_only_mirror,
count(*) FILTER (WHERE has_product_variant IS NULL) AS null_flag
FROM shopify_products WHERE ${UNBUYABLE}`);
// 2) Vendor breakdown (top 25)
const vendors = queryRows(`
SELECT vendor, count(*) AS unbuyable,
round(100.0*count(*)/sum(count(*)) over (),1) AS pct
FROM shopify_products WHERE ${UNBUYABLE}
GROUP BY vendor ORDER BY count(*) DESC LIMIT 25`);
// 3) Join-validated staging-cost recoverability for the 3 cohorts the prior
// memo flagged as "quick wins". Only Wolf Gordon survives the real join.
const cohorts = [
{ vendor: 'Wolf Gordon', table: 'wolf_gordon_catalog', cost: 'price_trade' },
{ vendor: 'Coordonné', table: 'coordonne_catalog', cost: 'price_trade' },
{ vendor: 'Malibu Wallpaper',table: 'wallquest_catalog', cost: 'COALESCE(net_cost,price_trade)' },
];
const recoverable = cohorts.map(({ vendor, table, cost }) => {
const row = queryOne(`
WITH ub AS (SELECT id, sku, variant_count, variant_sku, min_variant_price FROM shopify_products
WHERE vendor='${vendor}' AND ${UNBUYABLE})
SELECT (SELECT count(*) FROM ub) AS unbuyable,
count(DISTINCT ub.id) FILTER (WHERE (${cost}) > 0) AS cost_joined
FROM ub LEFT JOIN ${table} c ON c.dw_sku = ub.sku`);
return { vendor, staging_table: table, cost_column: cost,
unbuyable: Number(row.unbuyable),
cost_joined: Number(row.cost_joined),
// Wolf Gordon's cost_joined cohort was live-verified as RR-defect →
// recoverable via variant restructure (GATED). Others unverified.
recovery: vendor === 'Wolf Gordon'
? 'live-verified RR-defect → recoverable via restructure (GATED)'
: (vendor === 'Coordonné' ? 'blocked: DWCO2↔DWDC prefix + empty trade cost'
: 'no staging join (CDA line ≠ wallquest DWQW)') };
});
const summary = {
ticket: 'TK-10875',
generated_at: new Date().toISOString(),
source: 'dw_unified mirror (local /tmp socket) — READ-ONLY',
definition_unbuyable: "status='active' AND has_product_variant IS NOT TRUE",
headline: {
unbuyable_total: Number(headline.unbuyable_total),
single_priced_variant_needs_live_check: Number(headline.single_priced_variant_needs_live_check),
sample_only_mirror: Number(headline.sample_only_mirror),
null_flag: Number(headline.null_flag),
},
top_vendors: vendors.map(v => ({ vendor: v.vendor, unbuyable: Number(v.unbuyable), pct: Number(v.pct) })),
join_validated_recoverable: recoverable,
finding: 'Three-layer result. (1) JOIN-KEY SWAP: working join is shopify.sku = <vendor>_catalog.dw_sku, not dw_sku↔dw_sku (yields 0) — why prior cycles used unreliable table-totals. (2) MIRROR CANNOT CLASSIFY: it has no variant option1 and is price-stale (dead dw-shopify-products-sync-hourly, 13d). (3) LIVE-VERIFIED (2026-08-30): the WG 224 cost-joined cohort is the RR-defect — a lone option1=Sample variant priced at ROLL RETAIL (not $4.25), no real sample, no roll. They ARE recoverable via the proven TK-10875-130 variant-restructure (relabel Sample→Sold Per Roll @ trade/0.65/0.85 + add $4.25 Sample), reversible+ledgered — but it is a GATED customer-facing write. Coordonné/Malibu do not join. Executor MUST read live Shopify + recompute retail from staging (never the stale mirror price).',
};
const out = join(HERE, 'data', 'classification-summary.json');
writeFileSync(out, JSON.stringify(summary, null, 2));
console.log(`[classify] unbuyable=${summary.headline.unbuyable_total} ` +
`single-priced(needs-live-check)=${summary.headline.single_priced_variant_needs_live_check} ` +
`sample-only(mirror)=${summary.headline.sample_only_mirror}`);
console.log(`[classify] cohorts (cost_joined / unbuyable): ` +
recoverable.map(r=>`${r.vendor}:${r.cost_joined}/${r.unbuyable}`).join(' '));
console.log(`[classify] WG 224 = live-verified RR-defect → recoverable via restructure (GATED)`);
console.log(`[classify] wrote ${out}`);