056c47581f
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>
130 lines
5.7 KiB
TypeScript
130 lines
5.7 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
|
|
const root = process.cwd()
|
|
const outRoot = join(root, 'content_population_exports')
|
|
mkdirSync(outRoot, { recursive: true })
|
|
|
|
function readJson(path: string, fallback: any = null) {
|
|
if (!existsSync(path)) return fallback
|
|
return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
|
|
}
|
|
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'
|
|
}
|
|
|
|
const generatedAt = new Date().toISOString()
|
|
const ownerResponses = readJson(join(outRoot, 'owner_response_intake.json'), { rows: [] })
|
|
const gateMap = readJson(join(outRoot, 'handoff_gate_dependency_map.json'), { blockedRows: [] })
|
|
const reviewerPreflight = readJson(join(outRoot, 'reviewer_input_preflight.json'), { blockingRows: [] })
|
|
|
|
const laneCommands: Record<string, string[]> = {
|
|
'Compliance blank/source-backed claim decisions': [
|
|
'bun run compliance:blank-decisions',
|
|
'bun run claims:sync-content',
|
|
'bun run export:content',
|
|
'bun run audit:content',
|
|
'bun run verify:handoff'
|
|
],
|
|
'Product approval decisions': [
|
|
'bun run approval:decisions',
|
|
'bun run approval:review-packet',
|
|
'bun run export:content',
|
|
'bun run audit:content',
|
|
'bun run verify:handoff'
|
|
],
|
|
'Lewis/Euan staging import evidence': [
|
|
'bun run staging:evidence',
|
|
'bun run staging:checklist',
|
|
'bun run staging:handoff-packet',
|
|
'bun run verify:handoff'
|
|
],
|
|
'DataForSEO Reviews endpoint/support/spend clearance': [
|
|
'bun run dataforseo:support-response',
|
|
'bun run dataforseo:task-preflight',
|
|
'bun run source:spend-gate',
|
|
'bun run source:refresh-packet'
|
|
]
|
|
}
|
|
|
|
const rows = (ownerResponses.rows || []).map((lane: any) => {
|
|
const ready = lane.Status === 'ready_for_gate_rerun'
|
|
const relatedGates = (gateMap.blockedRows || []).filter((gate: any) => gate.Owner === lane.Owner || gate.Lane === lane.Lane || String(gate.Lane || '').includes(String(lane.Lane || '').split(' ')[0]))
|
|
const relatedPreflight = (reviewerPreflight.blockingRows || []).filter((row: any) => {
|
|
if (lane.Lane.includes('Compliance')) return row.Area.includes('Compliance')
|
|
if (lane.Lane.includes('Product approval')) return row.Area.includes('Product approval')
|
|
if (lane.Lane.includes('staging')) return row.Area.includes('Staging')
|
|
if (lane.Lane.includes('DataForSEO')) return row.Area.includes('DataForSEO')
|
|
return false
|
|
})
|
|
const commands = laneCommands[lane.Lane] || [lane.CommandRunAfterUpdate || 'bun run verify:handoff']
|
|
return {
|
|
Lane: lane.Lane,
|
|
Owner: lane.Owner,
|
|
ReadyForRerun: ready ? 'yes' : 'no',
|
|
OwnerResponseStatus: lane.Status,
|
|
Commands: commands,
|
|
BlockedVerifierGates: relatedGates.map((gate: any) => gate.Gate),
|
|
BlockingPreflightEvidence: relatedPreflight.map((row: any) => `${row.Area}: ${row.Evidence}`),
|
|
NextAction: ready ? `Run: ${commands.join(' && ')}` : lane.NextAction,
|
|
Guardrail: lane.Guardrail
|
|
}
|
|
})
|
|
const readyRows = rows.filter(row => row.ReadyForRerun === 'yes')
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: readyRows.length > 0,
|
|
purpose: 'Safe rerun plan for after owner responses/evidence updates arrive. It lists exact commands but only marks lanes runnable when owner_response_intake says the lane is ready_for_gate_rerun.',
|
|
counts: {
|
|
lanes: rows.length,
|
|
readyForRerun: readyRows.length,
|
|
held: rows.length - readyRows.length,
|
|
commandsReady: readyRows.reduce((sum, row) => sum + row.Commands.length, 0)
|
|
},
|
|
rows,
|
|
files: {
|
|
markdown: 'content_population_exports/post_owner_response_rerun_plan.md',
|
|
json: 'content_population_exports/post_owner_response_rerun_plan.json',
|
|
csv: 'content_population_exports/post_owner_response_rerun_plan.csv'
|
|
}
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'post_owner_response_rerun_plan.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'post_owner_response_rerun_plan.csv'), csv(rows, ['Lane', 'Owner', 'ReadyForRerun', 'OwnerResponseStatus', 'Commands', 'BlockedVerifierGates', 'BlockingPreflightEvidence', 'NextAction', 'Guardrail']), 'utf8')
|
|
writeFileSync(join(outRoot, 'post_owner_response_rerun_plan.md'), [
|
|
'# Post-owner response rerun plan',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Ready lanes: ${manifest.counts.readyForRerun}/${manifest.counts.lanes}`,
|
|
`Held lanes: ${manifest.counts.held}`,
|
|
'',
|
|
'This plan is intentionally held until owner evidence is recorded. It prevents accidental reruns from being mistaken for approval or staging proof.',
|
|
'',
|
|
'## Lane reruns',
|
|
...rows.map(row => [
|
|
`### ${row.Lane}`,
|
|
`- Owner: ${row.Owner}`,
|
|
`- Ready for rerun: ${row.ReadyForRerun}`,
|
|
`- Owner response status: ${row.OwnerResponseStatus}`,
|
|
`- Blocked verifier gates: ${row.BlockedVerifierGates.length ? row.BlockedVerifierGates.join(', ') : 'none mapped'}`,
|
|
`- Next action: ${row.NextAction}`,
|
|
`- Commands: ${row.Commands.map(command => `\`${command}\``).join(' -> ')}`,
|
|
''
|
|
].join('\n')),
|
|
'## Guardrail',
|
|
'Only run a lane sequence after `data/handoff/owner-response-intake.json` records a usable response, evidence files updated, and a post-update command result expectation. Master readiness still requires `bun run verify:handoff` to pass.',
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Post-owner response rerun plan ready: ${manifest.ready}`)
|
|
console.log(`Ready lanes: ${manifest.counts.readyForRerun}/${manifest.counts.lanes}`)
|
|
if (!manifest.ready) process.exitCode = 1
|