Files
justvitamin/scripts/validate-owner-response-intake.ts
T
Omair Saleh 056c47581f feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)
Ships the second dashboard surface — a Pattern Library + Preview Theatre — that
presents the 4-section PDP pilot batch back to Umar, compliance, and the board
in an editorial format. Adds the full data layer that drives it: 5 source-backed
per-SKU drafts at QA 100/100, 15 competitor PDP semantic extracts, PubMed
evidence packs, EFSA claims library extension, JV brand voice guide, hand-curated
product FAQs, and the Matrixify-ready CSV exports for Lewis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 18:50:09 +08:00

140 lines
6.6 KiB
TypeScript

#!/usr/bin/env bun
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { dirname, join } from 'path'
const root = process.cwd()
const outRoot = join(root, 'content_population_exports')
const intakePath = join(root, 'data', 'handoff', 'owner-response-intake.json')
mkdirSync(outRoot, { recursive: true })
mkdirSync(dirname(intakePath), { recursive: true })
function readJson(path: string, fallback: any = null) {
if (!existsSync(path)) return fallback
return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
}
function writeJson(path: string, data: any) {
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf8')
}
function csv(rows: any[], headers: string[]) {
const esc = (value: any) => {
if (value === undefined || value === null) value = ''
if (Array.isArray(value)) value = value.join(' | ')
value = String(value)
return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
}
return [headers.join(','), ...rows.map(row => headers.map(header => esc(row[header])).join(','))].join('\n') + '\n'
}
function nonempty(value: any) { return String(value || '').trim().length > 0 }
function isoish(value: any) { const parsed = Date.parse(String(value || '').trim()); return Number.isFinite(parsed) }
const generatedAt = new Date().toISOString()
const ownerMessages = readJson(join(outRoot, 'owner_handoff_messages.json'), {})
const templateRows = (ownerMessages.messages || []).map((message: any) => ({
lane: message.Lane,
owner: message.Audience,
messageSubject: message.Subject,
messageSent: false,
sentAt: '',
sentVia: '',
responseReceived: false,
responseReceivedAt: '',
responderName: '',
responseSummary: '',
evidenceFilesUpdated: [],
commandRunAfterUpdate: '',
gateResultAfterUpdate: 'not_run',
notes: ''
}))
if (!existsSync(intakePath)) {
writeJson(intakePath, {
purpose: 'Editable intake tracker for owner replies to the JV handoff messages. This records communication/evidence receipt only; it does not approve content or bypass any gate.',
guardrails: [
'Only set responseReceived=true after the owner has replied with usable evidence or a concrete decision.',
'Keep evidenceFilesUpdated limited to actual edited files or saved evidence artifacts.',
'Run the lane command after updating evidence and record the result; do not mark gates passed manually.',
'Do not store credentials or secrets in this tracker.'
],
responses: templateRows
})
}
const intake = readJson(intakePath, { responses: [] })
const rows = (ownerMessages.messages || []).map((message: any) => {
const response = (intake.responses || []).find((row: any) => row.lane === message.Lane) || {}
const sentOk = response.messageSent === true && isoish(response.sentAt) && nonempty(response.sentVia)
const responseOk = response.responseReceived === true && isoish(response.responseReceivedAt) && nonempty(response.responderName) && nonempty(response.responseSummary)
const evidenceOk = Array.isArray(response.evidenceFilesUpdated) && response.evidenceFilesUpdated.length > 0
const commandOk = nonempty(response.commandRunAfterUpdate) && ['pass', 'blocked_with_new_evidence'].includes(String(response.gateResultAfterUpdate || ''))
const status = responseOk && evidenceOk && commandOk ? 'ready_for_gate_rerun' : sentOk ? 'awaiting_response_or_evidence' : 'not_sent'
const blockers = []
if (!sentOk) blockers.push('message not recorded as sent')
if (!responseOk) blockers.push('owner response not recorded')
if (!evidenceOk) blockers.push('evidence files not updated')
if (!commandOk) blockers.push('post-update command result not recorded')
return {
Lane: message.Lane,
Owner: message.Audience,
Status: status,
MessageSent: sentOk ? 'yes' : 'no',
ResponseReceived: responseOk ? 'yes' : 'no',
EvidenceUpdated: evidenceOk ? 'yes' : 'no',
CommandResultRecorded: commandOk ? 'yes' : 'no',
EvidenceFilesUpdated: response.evidenceFilesUpdated || [],
NextAction: blockers.length ? blockers.join('; ') : 'Run the lane regeneration command and master verifier.',
Guardrail: message.Guardrail
}
})
const readyRows = rows.filter(row => row.Status === 'ready_for_gate_rerun')
const manifest = {
generatedAt,
ready: rows.length > 0 && readyRows.length === rows.length,
purpose: 'Validates whether owner handoff messages have been sent and whether usable owner responses/evidence updates have been recorded before rerunning blocker gates.',
intakeFile: 'data/handoff/owner-response-intake.json',
counts: {
lanes: rows.length,
readyForGateRerun: readyRows.length,
awaitingResponseOrEvidence: rows.filter(row => row.Status === 'awaiting_response_or_evidence').length,
notSent: rows.filter(row => row.Status === 'not_sent').length
},
rows,
files: {
markdown: 'content_population_exports/owner_response_intake.md',
json: 'content_population_exports/owner_response_intake.json',
csv: 'content_population_exports/owner_response_intake.csv',
editableIntake: 'data/handoff/owner-response-intake.json'
}
}
writeJson(join(outRoot, 'owner_response_intake.json'), manifest)
writeFileSync(join(outRoot, 'owner_response_intake.csv'), csv(rows, ['Lane', 'Owner', 'Status', 'MessageSent', 'ResponseReceived', 'EvidenceUpdated', 'CommandResultRecorded', 'EvidenceFilesUpdated', 'NextAction', 'Guardrail']), 'utf8')
writeFileSync(join(outRoot, 'owner_response_intake.md'), [
'# Owner response intake',
'',
`Generated: ${generatedAt}`,
'',
`Ready for gate reruns: **${manifest.ready ? 'YES' : 'NO'}**`,
`Lanes ready: ${manifest.counts.readyForGateRerun}/${manifest.counts.lanes}`,
`Awaiting response/evidence: ${manifest.counts.awaitingResponseOrEvidence}`,
`Not sent: ${manifest.counts.notSent}`,
'',
`Editable tracker: \`${manifest.intakeFile}\``,
'',
'This tracker records owner communications and received evidence. It does not approve products, approve blank clinical claims, create staging evidence, or unlock paid provider calls by itself.',
'',
'## Rows',
'| Lane | Owner | Status | Next action |',
'|---|---|---|---|',
...rows.map(row => `| ${row.Lane} | ${row.Owner} | ${row.Status} | ${row.NextAction.replace(/\|/g, '/')} |`),
'',
'## Guardrail',
'After an owner response is recorded, rerun the lane-specific validation command. The authoritative gate remains the generated validator output, not this communication tracker.',
''
].join('\n'), 'utf8')
console.log(`Owner response intake ready: ${manifest.ready}`)
console.log(`Lanes ready: ${manifest.counts.readyForGateRerun}/${manifest.counts.lanes}`)
console.log(`Not sent: ${manifest.counts.notSent}`)
if (!manifest.ready) process.exitCode = 1