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