← back to Tk 10867 Gb Safe Plan
build-plan.mjs
88 lines
#!/usr/bin/env node
// TK-10867: offline, read-only Graham & Brown cohort reconciliation.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const activePath = '/Users/macstudio3/Projects/_overnight/shopify-products-live.jsonl';
const samplePath = '/Users/macstudio3/Projects/designerwallcoverings/today-viewer/data/sample-only-skus.json';
const csvPath = '/Users/macstudio3/Projects/Designer-Wallcoverings/vendor-scrapers/gb.csv';
const rollLogPath = '/Users/macstudio3/Projects/Designer-Wallcoverings/vendor-scrapers/gb-roll-complete.log';
const active = fs.readFileSync(activePath, 'utf8').trim().split(/\n/).map(JSON.parse)
.filter(product => product.vendor === 'Graham & Brown' && String(product.status).toUpperCase() === 'ACTIVE');
const sampleJson = JSON.parse(fs.readFileSync(samplePath, 'utf8'));
const sampleOnly = (Array.isArray(sampleJson) ? sampleJson : sampleJson.products || sampleJson.items || [])
.filter(product => product.vendor === 'Graham & Brown');
const sampleIds = new Set(sampleOnly.map(product => String(product.id)));
const needsVariantVerification = active.filter(product => !sampleIds.has(String(product.id).split('/').pop()));
const csv = fs.readFileSync(csvPath, 'utf8').trim().split(/\r?\n/).slice(1).map(line => {
const [sku, title, oldCost, oldRetail, newCost, newRetail] = line.split(',').map(value => value.trim());
return { sku, title, old_cost: Number(oldCost), old_retail: Number(oldRetail), new_cost: Number(newCost), csv_new_retail: Number(newRetail) };
});
const formulaRetail = cost => Math.round((cost / 0.65 / 0.85) * 100) / 100;
const rollLog = fs.readFileSync(rollLogPath, 'utf8');
const appliedMatches = [...rollLog.matchAll(/APPLIED:\s*(\d+),\s*skipped:\s*(\d+)/g)].map(match => ({ applied: Number(match[1]), skipped: Number(match[2]) }));
const plan = {
ticket: 'TK-10867', generated_at: new Date().toISOString(), mode: 'OFFLINE_DRY_RUN_HOLD',
sources: [
{ path: activePath, modified: '2026-05-30T09:53:46-07:00', role: 'active product identity only; no variants' },
{ path: samplePath, modified: '2026-06-22T12:36:41-07:00', role: 'sample-only product identity' },
{ path: csvPath, modified: '2026-06-09T17:57:39-07:00', role: '93-SKU Laura Ashley pricing sheet' },
{ path: rollLogPath, modified: '2026-06-10T17:11:42-07:00', role: 'historical roll-add result' }
],
cohort: {
active_snapshot: active.length,
sample_only_snapshot: sampleOnly.length,
not_in_sample_only_needs_variant_verification: needsVariantVerification.length,
candidates: needsVariantVerification.map(product => ({
product_id: String(product.id).split('/').pop(), handle: product.handle, title: product.title,
verdict: 'HOLD_LIVE_VARIANT_VERIFY',
reason: 'active snapshot has no variant data; absence from sample-only snapshot does not prove a missing Sample variant'
}))
},
provenance: {
unit: {
value: 'Sold Per Roll — 20.5 in x 33 ft',
scope: 'historical Laura Ashley gb.csv cohort only',
source: 'gb-add-roll.js ROLL_TITLE and gb.csv titles',
verdict: 'HOLD_FOR_NON_LAURA_ASHLEY'
},
cost: {
rows: csv.length, uniform_new_cost: [...new Set(csv.map(row => row.new_cost))],
scope: 'exact gb.csv SKU matches only; candidate snapshot lacks SKU, so no exact join is possible offline',
verdict: 'HOLD_NO_EXACT_SKU_JOIN'
},
retail: {
csv_uniform_retail: [...new Set(csv.map(row => row.csv_new_retail))],
required_formula_at_66: formulaRetail(66),
formula: 'cost / 0.65 / 0.85',
verdict: formulaRetail(66) === 110 ? 'CONSISTENT' : 'CONFLICT_HOLD'
},
historical_roll_result: appliedMatches.at(-1) || null
},
dry_run: {
executable_mutations: 0,
required_before_any_future_sample_add: [
'current live product ACTIVE', 'no existing Sample variant', 'base SKU is unique and nonempty',
'capture product options and every variant id/title/sku/price as restore-map',
'unit provenance applies to exact SKU', 'cost provenance applies to exact SKU',
'formula retail independently recomputed', 'bounded canary approved'
],
proposed_sample_shape_after_gates: { title: 'Sample', sku: '<BASE-SKU>-Sample', price: '4.25' },
rollback_contract: 'delete only the newly-created variant GID; restore any changed option/SKU fields from captured before-map'
},
blockers: [
'Current local mirror read remained blocked for 498.5 seconds and returned no cohort.',
'Available active snapshot has no variant nodes, so missing-Sample status cannot be inferred.',
'Historical $66->$110 CSV pricing conflicts with required $119.46 formula price.',
'Unit/cost evidence is Laura Ashley/CSV scoped and cannot be generalized to all 41 rows.'
],
side_effects: { shopify_writes: 0, database_writes: 0, activation: 0, deploy: 0, sends: 0, spend: 0, schedule_changes: 0 }
};
fs.writeFileSync(path.join(here, 'dry-run-plan.json'), JSON.stringify(plan, null, 2) + '\n');
console.log(JSON.stringify({ cohort: plan.cohort, provenance: plan.provenance, executable_mutations: 0, blockers: plan.blockers }, null, 2));