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>
164 lines
9.8 KiB
TypeScript
164 lines
9.8 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 = ''
|
|
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'
|
|
}
|
|
function status(ok: boolean) { return ok ? 'proven' : 'blocked' }
|
|
|
|
const generatedAt = new Date().toISOString()
|
|
const handoff = readJson(join(outRoot, 'handoff_manifest.json'), {})
|
|
const readiness = readJson(join(outRoot, 'content_population_readiness.json'), {})
|
|
const verification = readJson(join(outRoot, 'handoff_verification_run.json'), {})
|
|
const delivery = readJson(join(outRoot, 'delivery_index.json'), {})
|
|
const dataforseo = readJson(join(outRoot, 'dataforseo_live_connection.json'), {})
|
|
const docs = readJson(join(outRoot, 'dataforseo_reviews_docs_status.json'), {})
|
|
const support = readJson(join(outRoot, 'dataforseo_support_response_gate.json'), {})
|
|
const taskPreflight = readJson(join(outRoot, 'dataforseo_task_preflight.json'), {})
|
|
const spendGate = readJson(join(outRoot, 'source_acquisition_spend_gate.json'), {})
|
|
const approvalGate = readJson(join(outRoot, 'product_approval_decision_gate.json'), {})
|
|
const clinicalGate = readJson(join(outRoot, 'clinical_blank_decision_template.json'), {})
|
|
const staging = readJson(join(outRoot, 'staging_evidence_validation.json'), {})
|
|
const burndown = readJson(join(outRoot, 'blocker_burndown.json'), {})
|
|
|
|
const verificationBlocked = (verification.checks || []).filter((row: any) => row.Status !== 'pass')
|
|
const readinessBlocked = (readiness.gates || []).filter((row: any) => row.ready !== true)
|
|
|
|
const requirements = [
|
|
{
|
|
Requirement: 'Full catalogue content workspace exists',
|
|
Status: status((readiness.summary?.productCount || 0) === 190 && (readiness.summary?.workflowCount || 0) === 190),
|
|
EvidenceSource: 'content_population_exports/content_population_readiness.json',
|
|
Proof: `${readiness.summary?.productCount || 0} product records / ${readiness.summary?.workflowCount || 0} workflow records`,
|
|
RemainingWork: (readiness.summary?.productCount || 0) === 190 ? '' : 'Seed missing catalogue/workflow records.'
|
|
},
|
|
{
|
|
Requirement: 'Matrixify/export packet exists',
|
|
Status: status(delivery.ready === true),
|
|
EvidenceSource: 'content_population_exports/delivery_index.json',
|
|
Proof: `${delivery.counts?.present || 0}/${delivery.counts?.files || 0} packet files present`,
|
|
RemainingWork: delivery.ready === true ? '' : 'Regenerate missing packet files.'
|
|
},
|
|
{
|
|
Requirement: 'Asset references/files are present',
|
|
Status: status((readiness.summary?.missingAssetUploads || 0) === 0),
|
|
EvidenceSource: 'content_population_exports/content_population_readiness.json / asset_manifest_validation.*',
|
|
Proof: `${readiness.summary?.missingAssetUploads || 0} missing asset uploads`,
|
|
RemainingWork: (readiness.summary?.missingAssetUploads || 0) === 0 ? 'Human image approval/final creative still separate from file presence.' : 'Generate/upload missing asset files.'
|
|
},
|
|
{
|
|
Requirement: 'Source drops and claims library validate',
|
|
Status: status((readiness.summary?.sourceDropIssues || 0) === 0 && (readiness.summary?.claimsIssues || 0) === 0),
|
|
EvidenceSource: 'source_drop_validation.* / claims_library_validation.*',
|
|
Proof: `source issues ${readiness.summary?.sourceDropIssues || 0}; claims issues ${readiness.summary?.claimsIssues || 0}`,
|
|
RemainingWork: 'Full quote-level/human-approved evidence remains separate for non-pilot drafted queues.'
|
|
},
|
|
{
|
|
Requirement: 'Compliance clinical claim/blank decisions complete',
|
|
Status: status(clinicalGate.ready === true),
|
|
EvidenceSource: 'content_population_exports/clinical_blank_decision_template.json',
|
|
Proof: `${clinicalGate.counts?.validBlankApprovals || 0}/${clinicalGate.counts?.decisions || 0} approved blank decisions`,
|
|
RemainingWork: 'Compliance must fill approved_blank rows or provide source-backed claim refs; no generated clinical claims.'
|
|
},
|
|
{
|
|
Requirement: 'Product approvals complete',
|
|
Status: status(approvalGate.ready === true),
|
|
EvidenceSource: 'content_population_exports/product_approval_decision_gate.json',
|
|
Proof: `${approvalGate.counts?.validApprovals || 0}/${approvalGate.counts?.products || 0} valid approvals`,
|
|
RemainingWork: 'Umar/JV reviewer must approve all products with evidence and acknowledgements.'
|
|
},
|
|
{
|
|
Requirement: 'DataForSEO credentials/account verified without guessing',
|
|
Status: status(dataforseo.accountConnection?.status_code === 20000 && dataforseo.credentialPrinted === false),
|
|
EvidenceSource: 'content_population_exports/dataforseo_live_connection.json',
|
|
Proof: `account ${dataforseo.accountConnection?.status_code || 'n/a'} ${dataforseo.accountConnection?.status || dataforseo.accountConnection?.status_message || ''}; credentialPrinted=${dataforseo.credentialPrinted === true}`,
|
|
RemainingWork: 'Keep using pi-fresh-foundation .env; do not print or duplicate secrets.'
|
|
},
|
|
{
|
|
Requirement: 'DataForSEO Reviews pilot safe to run',
|
|
Status: status(taskPreflight.ready === true && spendGate.spendAllowed?.dataforseoStage1 === true && support.ready === true && docs.endpointTemporarilyUnavailable !== true),
|
|
EvidenceSource: 'dataforseo_task_preflight.json / source_acquisition_spend_gate.json / dataforseo_reviews_docs_status.json / dataforseo_support_response_gate.json',
|
|
Proof: `preflight ${taskPreflight.counts?.passing || 0}/${taskPreflight.counts?.checks || 0}; spend DataForSEO=${spendGate.spendAllowed?.dataforseoStage1 === true}; docs unavailable=${docs.endpointTemporarilyUnavailable === true}; support ready=${support.ready === true}`,
|
|
RemainingWork: 'Do not run paid Reviews task_post until provider availability/support response and spend approval gates pass.'
|
|
},
|
|
{
|
|
Requirement: 'Lewis/Euan staging import evidence complete',
|
|
Status: status(staging.ready === true),
|
|
EvidenceSource: 'content_population_exports/staging_evidence_validation.json',
|
|
Proof: `${staging.counts?.passing || 0}/${staging.counts?.gates || 0} staging gates passing; import logs ${staging.counts?.importLogs || 0}; PDP checks ${staging.counts?.pdpSpotChecks || 0}`,
|
|
RemainingWork: 'Record clean Matrixify import logs, at least 5 passing PDP checks, and cutover approval.'
|
|
},
|
|
{
|
|
Requirement: 'Master handoff verifier passes',
|
|
Status: status(verification.ready === true),
|
|
EvidenceSource: 'content_population_exports/handoff_verification_run.json',
|
|
Proof: `${verification.counts?.passing || 0}/${verification.counts?.checks || 0} checks passing; ${verification.counts?.blocked || 0} blocked`,
|
|
RemainingWork: verificationBlocked.map((row: any) => row.Name).join(' | ')
|
|
}
|
|
]
|
|
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: requirements.every(row => row.Status === 'proven'),
|
|
objective: 'jv-dashboard/CONTENT_POPULATION_PLAN.md',
|
|
headline: `Completion unproven: ${requirements.filter(row => row.Status !== 'proven').length}/${requirements.length} requirement areas still blocked; ${burndown.counts?.totalRemaining || 0} evidence items remain.`,
|
|
counts: {
|
|
requirements: requirements.length,
|
|
proven: requirements.filter(row => row.Status === 'proven').length,
|
|
blocked: requirements.filter(row => row.Status !== 'proven').length,
|
|
exportBlockers: handoff.counts?.blockers ?? readiness.summary?.exportBlockers ?? 0,
|
|
burndownRemaining: burndown.counts?.totalRemaining ?? 0,
|
|
verificationPassing: verification.counts?.passing ?? 0,
|
|
verificationChecks: verification.counts?.checks ?? 0
|
|
},
|
|
blockerSources: {
|
|
verificationBlocked: verificationBlocked.map((row: any) => ({ name: row.Name, command: row.Command, stdout: row.Stdout })),
|
|
readinessBlocked: readinessBlocked.map((row: any) => ({ name: row.name, blocker: row.blocker, evidence: row.evidence }))
|
|
},
|
|
requirements
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'handoff_evidence_dossier.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'handoff_evidence_dossier.csv'), csv(requirements, ['Requirement', 'Status', 'EvidenceSource', 'Proof', 'RemainingWork']), 'utf8')
|
|
writeFileSync(join(outRoot, 'handoff_evidence_dossier.md'), [
|
|
'# JV handoff evidence dossier',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
manifest.headline,
|
|
'',
|
|
'This dossier ties the master plan requirements to the current evidence files. It is intentionally strict: uncertain or human-pending work stays blocked rather than being inferred complete.',
|
|
'',
|
|
'## Requirement evidence',
|
|
'| Requirement | Status | Proof | Remaining work |',
|
|
'|---|---|---|---|',
|
|
...requirements.map(row => `| ${row.Requirement} | ${row.Status} | ${String(row.Proof).replace(/\|/g, '/')} | ${String(row.RemainingWork).replace(/\|/g, '/')} |`),
|
|
'',
|
|
'## Blocked verifier checks',
|
|
...(verificationBlocked.length ? verificationBlocked.map((row: any) => `- ${row.Name}: ${String(row.Stdout || '').split('\n')[0] || row.Command}`) : ['- none']),
|
|
'',
|
|
'## Blocked readiness gates',
|
|
...(readinessBlocked.length ? readinessBlocked.map((row: any) => `- ${row.name}: ${row.blocker}`) : ['- none']),
|
|
'',
|
|
'## Completion rule',
|
|
'Do not mark the goal complete until every row above is `proven`, `bun run verify:handoff` passes, and the staging/cutover evidence exists with clean results.',
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Handoff evidence dossier ready: ${manifest.ready}`)
|
|
console.log(manifest.headline)
|