← back to Sanderson Onboard
tk10873/reconcile_from_feed.mjs
98 lines
#!/usr/bin/env node
// reconcile_from_feed.mjs — TK-10873 cycle 4. Steve ruled the S1 feed canonical
// (2026-08-30), empirically confirmed in price_source_verification.md (feed ssp == real
// Zoffany US retail; zoffany_catalog = GBP RRP stored as USD, ~2.5-2.8x too low).
//
// This produces a FEED-RECONCILED draft (v2) where each REPRICE row's proposed price
// comes from the feed retail_usd when the feed has it. It NEVER writes Shopify/dw_unified
// — it writes local artifacts only. The live reprice stays Steve-gated.
//
// READ-ONLY inputs: zoffany_optionA_draft.json + pilot/zoffany_feed_harvest.jsonl.
// OUTPUTS (local, reversible — original draft preserved untouched):
// zoffany_optionA_draft_feedreconciled.json (v2 plan with corrected prices + provenance)
// reconcile_restore_map.json (old->new per mfr_sku, so v2 is revertible to v1)
import fs from 'node:fs';
const DIR = new URL('.', import.meta.url).pathname;
const DRAFT_IN = `${DIR}zoffany_optionA_draft.json`;
const FEED_IN = '/Users/macstudio3/Projects/sanderson-onboard/pilot/zoffany_feed_harvest.jsonl';
const V2_OUT = `${DIR}zoffany_optionA_draft_feedreconciled.json`;
const RESTORE_OUT = `${DIR}reconcile_restore_map.json`;
const SUMMARY_OUT = `${DIR}reconcile_summary.json`;
function loadFeed() {
const m = new Map();
for (const l of fs.readFileSync(FEED_IN, 'utf8').trim().split('\n')) {
const r = JSON.parse(l);
if (r.mfr_sku && Number(r.retail_usd) > 0) m.set(r.mfr_sku, r);
}
return m;
}
export function reconcilePrice(row, feedRow) {
// Returns { price, source, changed } — feed retail_usd is canonical when present.
const old = Number(row.proposed_sellable_price);
if (feedRow && Number(feedRow.retail_usd) > 0) {
const p = Number(Number(feedRow.retail_usd).toFixed(2));
return { price: p, source: 'S1_feed_retail_usd', changed: Math.abs(p - old) > 0.01 };
}
return { price: old, source: 'zoffany_catalog_UNVERIFIED', changed: false };
}
function main() {
const draft = JSON.parse(fs.readFileSync(DRAFT_IN, 'utf8'));
const feed = loadFeed();
const v2 = [];
const restore = [];
let repriced_from_feed = 0, unchanged_confirmed = 0, held_unverified = 0;
for (const row of draft) {
if (row.action !== 'REPRICE') { v2.push(row); continue; }
const f = feed.get(row.mfr_sku);
const { price, source, changed } = reconcilePrice(row, f);
const isVerified = source.startsWith('S1_feed');
if (isVerified) {
if (changed) repriced_from_feed++; else unchanged_confirmed++;
} else {
held_unverified++;
}
if (changed) restore.push({ mfr_sku: row.mfr_sku, old_price: row.proposed_sellable_price, new_price: price });
// Cody cycle-4 fix: rows with NO feed retail keep a likely-GBP-as-USD (~2.5x low) value —
// they must NOT sit in the reprice-now set. Reclassify them OUT of REPRICE so only
// feed-verified prices are reprice-eligible. Sellable price field = feed retail_usd
// (= trade/0.65/0.85, the Option-A contract), NOT ssp_usd (which is the MSRP).
v2.push({
...row,
action: isVerified ? 'REPRICE' : 'HELD_UNVERIFIED',
proposed_sellable_price: price,
price_source: source,
price_field: isVerified ? 'feed.retail_usd (trade/0.65/0.85 — Option-A contract; NOT ssp_usd/MSRP)' : 'zoffany_catalog (UNVERIFIED, likely GBP-as-USD low)',
price_prev_zoffany_catalog: row.proposed_sellable_price,
price_changed: changed,
});
}
const summary = {
ticket: 'TK-10873', cycle: 4, generated_at: new Date().toISOString(), read_only: true,
ruling: 'S1 feed canonical (Steve 2026-08-30, empirically confirmed price_source_verification.md)',
reprice_total: draft.filter(r => r.action === 'REPRICE').length,
reprice_eligible_now: repriced_from_feed + unchanged_confirmed, // 239 feed-VERIFIED only
repriced_from_feed, // the ~209 corrected up off the feed
unchanged_confirmed, // the ~30 already matching the feed
held_unverified_excluded_from_reprice: held_unverified, // the ~89 — reclassified OUT of REPRICE (Cody cycle-4)
sellable_price_field: 'feed.retail_usd (= trade/0.65/0.85, the Option-A contract) — NOT ssp_usd (MSRP)',
restore_map_rows: restore.length,
note: 'v2 corrects the ~2.5x underprice on 209 feed-backed rows; the 89 with no feed retail are RECLASSIFIED OUT of REPRICE to HELD_UNVERIFIED (not shipped into the reprice-now set). Reprice-now set = 239 feed-verified. LIVE reprice stays Steve-gated.',
};
fs.writeFileSync(V2_OUT, JSON.stringify(v2, null, 2));
fs.writeFileSync(RESTORE_OUT, JSON.stringify(restore, null, 2));
fs.writeFileSync(SUMMARY_OUT, JSON.stringify(summary, null, 2));
console.log('=== feed-reconciled draft v2 (READ-ONLY, no live writes) ===');
console.log(`repriced_from_feed=${repriced_from_feed} unchanged_confirmed=${unchanged_confirmed} held_unverified=${held_unverified}`);
console.log(`restore-map rows (revert v2->v1): ${restore.length}`);
console.log(`artifacts:\n ${V2_OUT}\n ${RESTORE_OUT}\n ${SUMMARY_OUT}`);
}
if (import.meta.url === `file://${process.argv[1]}`) main();