← 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));