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>
134 lines
7.6 KiB
TypeScript
134 lines
7.6 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
|
|
const root = process.cwd()
|
|
const outRoot = join(root, 'content_population_exports')
|
|
mkdirSync(outRoot, { recursive: true })
|
|
|
|
for (const file of ['handoff_packet_refresh.json', 'handoff_packet_refresh.csv', 'handoff_packet_refresh.md', 'handoff_artifact_freshness.json', 'handoff_artifact_freshness.csv', 'handoff_artifact_freshness.md']) {
|
|
const path = join(outRoot, file)
|
|
if (!existsSync(path)) writeFileSync(path, file.endsWith('.json') ? '{}\n' : '', 'utf8')
|
|
}
|
|
|
|
const steps = [
|
|
{ name: 'export_content', command: ['bun', 'run', 'export:content'], requiredToRefresh: true },
|
|
{ name: 'validate_assets', command: ['bun', 'run', 'validate:assets'], requiredToRefresh: true },
|
|
{ name: 'validate_template', command: ['bun', 'run', 'validate:template'], requiredToRefresh: true },
|
|
{ name: 'source_pack', command: ['bun', 'run', 'source:pack'], requiredToRefresh: true },
|
|
{ name: 'validate_sources', command: ['bun', 'run', 'validate:sources'], requiredToRefresh: false },
|
|
{ name: 'validate_claims', command: ['bun', 'run', 'validate:claims'], requiredToRefresh: false },
|
|
{ name: 'clinical_blank_decisions', command: ['bun', 'run', 'compliance:blank-decisions'], requiredToRefresh: false },
|
|
{ name: 'intelligence_queue', command: ['bun', 'run', 'intelligence:queue'], requiredToRefresh: false },
|
|
{ name: 'product_approval_decisions', command: ['bun', 'run', 'approval:decisions'], requiredToRefresh: false },
|
|
{ name: 'product_approval_review_packet', command: ['bun', 'run', 'approval:review-packet'], requiredToRefresh: false },
|
|
{ name: 'handoff_actions', command: ['bun', 'run', 'handoff:actions'], requiredToRefresh: false },
|
|
{ name: 'executive_decision_brief', command: ['bun', 'run', 'handoff:decision-brief'], requiredToRefresh: false },
|
|
{ name: 'blocker_burndown', command: ['bun', 'run', 'handoff:burndown'], requiredToRefresh: false },
|
|
{ name: 'source_acquisition_control_run', command: ['bun', 'run', 'source:refresh-packet'], requiredToRefresh: true },
|
|
{ name: 'staging_evidence_intake', command: ['bun', 'run', 'staging:intake'], requiredToRefresh: true },
|
|
{ name: 'staging_handoff_packet', command: ['bun', 'run', 'staging:handoff-packet'], requiredToRefresh: false },
|
|
{ name: 'staging_evidence', command: ['bun', 'run', 'staging:evidence'], requiredToRefresh: false },
|
|
{ name: 'staging_checklist', command: ['bun', 'run', 'staging:checklist'], requiredToRefresh: false },
|
|
{ name: 'content_audit', command: ['bun', 'run', 'audit:content'], requiredToRefresh: false },
|
|
{ name: 'reviewer_decision_workbook', command: ['bun', 'run', 'handoff:reviewer-workbook'], requiredToRefresh: true },
|
|
{ name: 'reviewer_input_preflight', command: ['bun', 'run', 'handoff:reviewer-preflight'], requiredToRefresh: false },
|
|
{ name: 'owner_handoff_messages', command: ['bun', 'run', 'handoff:owner-messages'], requiredToRefresh: true },
|
|
{ name: 'owner_response_intake', command: ['bun', 'run', 'handoff:owner-responses'], requiredToRefresh: false },
|
|
{ name: 'post_owner_response_rerun_plan', command: ['bun', 'run', 'handoff:post-response-rerun'], requiredToRefresh: false },
|
|
{ name: 'owner_evidence_acceptance_guide', command: ['bun', 'run', 'handoff:evidence-guide'], requiredToRefresh: true },
|
|
{ name: 'delivery_index_pre_verify', command: ['bun', 'run', 'delivery:index'], requiredToRefresh: true },
|
|
{ name: 'verify_handoff', command: ['bun', 'run', 'verify:handoff'], requiredToRefresh: false },
|
|
{ name: 'handoff_gate_dependency_map', command: ['bun', 'run', 'handoff:gate-map'], requiredToRefresh: false },
|
|
{ name: 'handoff_evidence_dossier', command: ['bun', 'run', 'handoff:evidence-dossier'], requiredToRefresh: true },
|
|
{ name: 'daily_status', command: ['bun', 'run', 'handoff:daily-status'], requiredToRefresh: true },
|
|
{ name: 'delivery_index_before_freshness', command: ['bun', 'run', 'delivery:index'], requiredToRefresh: true },
|
|
{ name: 'handoff_artifact_freshness', command: ['bun', 'run', 'handoff:freshness'], requiredToRefresh: true },
|
|
{ name: 'delivery_index_final', command: ['bun', 'run', 'delivery:index'], requiredToRefresh: true }
|
|
]
|
|
|
|
function csv(rows: any[], headers: string[]) {
|
|
const esc = (value: any) => {
|
|
if (value === undefined || value === null) value = ''
|
|
value = Array.isArray(value) ? value.join(' | ') : 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 results: any[] = []
|
|
|
|
for (const step of steps) {
|
|
const startedAt = new Date().toISOString()
|
|
const proc = Bun.spawnSync({ cmd: step.command, cwd: root, stdout: 'pipe', stderr: 'pipe' })
|
|
const stdout = proc.stdout ? new TextDecoder().decode(proc.stdout).trim() : ''
|
|
const stderr = proc.stderr ? new TextDecoder().decode(proc.stderr).trim() : ''
|
|
const status = proc.exitCode === 0 ? 'pass' : step.requiredToRefresh ? 'refresh_failed' : 'blocked_expected'
|
|
results.push({
|
|
Name: step.name,
|
|
Command: step.command.join(' '),
|
|
ExitCode: proc.exitCode,
|
|
Status: status,
|
|
RequiredToRefresh: step.requiredToRefresh ? 'yes' : 'no',
|
|
StartedAt: startedAt,
|
|
FinishedAt: new Date().toISOString(),
|
|
Stdout: stdout,
|
|
Stderr: stderr
|
|
})
|
|
console.log(`${step.name}: ${status} (${proc.exitCode})`)
|
|
}
|
|
|
|
const refreshReady = results.every(row => row.RequiredToRefresh !== 'yes' || row.ExitCode === 0)
|
|
const handoffReady = results.every(row => row.ExitCode === 0)
|
|
const report = {
|
|
generatedAt,
|
|
refreshReady,
|
|
handoffReady,
|
|
objective: 'Regenerate all JV handoff packet artifacts without hiding blockers',
|
|
counts: {
|
|
steps: results.length,
|
|
passing: results.filter(row => row.ExitCode === 0).length,
|
|
blockedExpected: results.filter(row => row.Status === 'blocked_expected').length,
|
|
refreshFailed: results.filter(row => row.Status === 'refresh_failed').length
|
|
},
|
|
steps: results
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'handoff_packet_refresh.json'), JSON.stringify(report, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'handoff_packet_refresh.csv'), csv(results.map(row => ({
|
|
Name: row.Name,
|
|
Command: row.Command,
|
|
Status: row.Status,
|
|
ExitCode: row.ExitCode,
|
|
RequiredToRefresh: row.RequiredToRefresh,
|
|
Stdout: row.Stdout.split('\n').slice(0, 5).join(' | '),
|
|
Stderr: row.Stderr.split('\n').slice(0, 5).join(' | ')
|
|
})), ['Name', 'Command', 'Status', 'ExitCode', 'RequiredToRefresh', 'Stdout', 'Stderr']), 'utf8')
|
|
|
|
const md = [
|
|
'# JV handoff packet refresh',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Packet refresh ready: **${refreshReady ? 'YES' : 'NO'}**`,
|
|
`Final handoff ready: **${handoffReady ? 'YES' : 'NO'}**`,
|
|
'',
|
|
`Steps passing: ${report.counts.passing}/${report.counts.steps}`,
|
|
`Expected blockers: ${report.counts.blockedExpected}`,
|
|
`Refresh failures: ${report.counts.refreshFailed}`,
|
|
'',
|
|
'## Steps',
|
|
...results.map(row => `- ${row.Status === 'pass' ? '[PASS]' : row.Status === 'blocked_expected' ? '[BLOCKED]' : '[REFRESH FAILED]'} ${row.Name}: \`${row.Command}\` exit=${row.ExitCode}`),
|
|
'',
|
|
'## Interpretation',
|
|
'This command is for regenerating the whole packet. `blocked_expected` means the artifact refreshed and truthfully reports an unresolved handoff blocker. `refresh_failed` means packet generation itself broke and must be fixed.',
|
|
''
|
|
].join('\n')
|
|
writeFileSync(join(outRoot, 'handoff_packet_refresh.md'), md, 'utf8')
|
|
|
|
console.log(`Packet refresh ready: ${refreshReady}`)
|
|
console.log(`Final handoff ready: ${handoffReady}`)
|
|
console.log(`Steps passing: ${report.counts.passing}/${report.counts.steps}`)
|
|
if (!refreshReady) process.exitCode = 1
|