← back to Sample Followup Sweep

tk10743-deterministic-triage/triage.mjs

66 lines

#!/usr/bin/env node
// TK-10743 deterministic, prepare-only triage from approved local JSON.
// No network, email, FileMaker, API, or database code exists in this file.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, '..');
const read = name => JSON.parse(fs.readFileSync(path.join(root, 'data', name), 'utf8'));
const fleet = read('fleet.json');
const contacts = read('contacts.json');
const resend = read('resend-queue.json');
const notes = read('notes-queue.json');
const recentlyContacted = new Set(read('recently-contacted.json').map(email => String(email).trim().toLowerCase()));

const aliases = { schumacher: 'sch', brewster: 'bre' };
const contactKey = slug => aliases[slug] || slug;
const queuedVids = new Set(resend.queue.map(row => String(row.vid).toUpperCase()));
const noChaseSlugs = new Set(notes.queue.flatMap(row => row.slug === 'sandb' ? ['sandb', 'sand'] : [row.slug]));

function classify(vendor) {
  const actual = vendor.items?.length || 0;
  const contact = contacts[contactKey(vendor.slug)] || null;
  const base = { vid: vendor.vid, slug: vendor.slug, name: vendor.name, declared_skus: vendor.count, materialized_skus: actual };
  if (vendor.count !== actual) return { ...base, status: 'DATA_INCOMPLETE', reason: `declared ${vendor.count} but materialized ${actual}; cannot construct a SKU-level plan` };
  if (contact?.disposition === 'no-chase') return { ...base, status: 'NO_CHASE', reason: contact.disposition_reason };
  if (noChaseSlugs.has(vendor.slug)) return { ...base, status: 'NO_CHASE', reason: 'approved local note says samples were ordered online; do not chase as memo samples' };
  if (!contact || contact.unresolved_email || !(contact.sample_email || contact.main_email)) {
    return { ...base, status: 'UNRESOLVED_LOCAL_DATA', reason: contact?.contact_source || 'no approved local contact record' };
  }
  const recipient = contact.sample_email || contact.main_email;
  const itemKeys = vendor.items.map(item => [item.dw || item.mfr, item.client, item.req].join('|'));
  if (contact.needs_confirm) return { ...base, status: 'HELD_NEEDS_CONFIRM', recipient, item_keys: itemKeys, evidence: contact.contact_source || 'local contact marked needs_confirm' };
  if (queuedVids.has(vendor.vid) || queuedVids.has(aliases[vendor.slug]?.toUpperCase())) {
    return { ...base, status: 'ALREADY_IN_RESEND_QUEUE', recipient, item_keys: itemKeys, reason: 'existing gated queue entry; do not duplicate' };
  }
  const recipients = recipient.split(',').map(email => email.trim().toLowerCase()).filter(Boolean);
  if (recipients.some(email => recentlyContacted.has(email))) {
    return { ...base, status: 'RECENTLY_CONTACTED_HOLD', recipient, item_keys: itemKeys, reason: 'recipient appears in approved local recently-contacted suppression list' };
  }
  return { ...base, status: 'PREPARED_NOT_SENT', recipient, item_keys: itemKeys, evidence: contact.contact_source || 'approved local contacts.json' };
}

const rows = fleet.vendors.filter(vendor => vendor.count > 0).map(classify);
const byStatus = Object.fromEntries([...new Set(rows.map(row => row.status))].sort().map(status => [status, {
  vendors: rows.filter(row => row.status === status).length,
  declared_skus: rows.filter(row => row.status === status).reduce((sum, row) => sum + row.declared_skus, 0),
  materialized_skus: rows.filter(row => row.status === status).reduce((sum, row) => sum + row.materialized_skus, 0)
}]));
const plan = {
  ticket: 'TK-10743', generated_at: new Date().toISOString(), mode: 'LOCAL_PREPARE_ONLY',
  source_files: ['data/fleet.json', 'data/contacts.json', 'data/resend-queue.json', 'data/notes-queue.json', 'data/recently-contacted.json'],
  invariants: ['NO_SEND', 'NO_FILEMAKER_WRITE', 'NO_NETWORK', 'NO_PAID_API', 'NO_CANONICAL_DB_WRITE'],
  integrity: {
    fleet_declared_total: fleet.totalItems,
    sum_vendor_declared_counts: fleet.vendors.reduce((sum, vendor) => sum + vendor.count, 0),
    sum_materialized_item_rows: fleet.vendors.reduce((sum, vendor) => sum + (vendor.items?.length || 0), 0),
    count_without_items: rows.filter(row => row.status === 'DATA_INCOMPLETE').reduce((sum, row) => sum + row.declared_skus, 0)
  },
  summary: byStatus,
  rows
};
fs.writeFileSync(path.join(here, 'triage-plan.json'), JSON.stringify(plan, null, 2) + '\n');
console.log(JSON.stringify({ integrity: plan.integrity, summary: plan.summary }, null, 2));