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>
153 lines
8.7 KiB
TypeScript
153 lines
8.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 })
|
|
const generatedAt = new Date().toISOString()
|
|
|
|
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 (Array.isArray(value)) value = value.join(' | ')
|
|
if (value === undefined || value === null) value = ''
|
|
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 handoff = readJson(join(outRoot, 'handoff_manifest.json'), {})
|
|
const readiness = readJson(join(outRoot, 'content_population_readiness.json'), {})
|
|
const actionPlan = readJson(join(outRoot, 'handoff_action_plan.json'), {})
|
|
const claimsMapping = readJson(join(outRoot, 'clinical_claim_mapping_report.json'), {})
|
|
const blankDecision = readJson(join(outRoot, 'clinical_blank_decision_template.json'), {})
|
|
const approvalDecision = readJson(join(outRoot, 'product_approval_decision_gate.json'), {})
|
|
const stagingPacket = readJson(join(outRoot, 'staging_handoff_packet.json'), {})
|
|
const stagingEvidence = readJson(join(outRoot, 'staging_evidence_validation.json'), {})
|
|
const delivery = readJson(join(outRoot, 'delivery_index.json'), {})
|
|
const dataforseoDocs = readJson(join(outRoot, 'dataforseo_reviews_docs_status.json'), {})
|
|
const dataforseoPilot = readJson(join(outRoot, 'dataforseo_pilot_manifest.json'), {})
|
|
const sourceNext = readJson(join(outRoot, 'source_acquisition_next_actions.json'), {})
|
|
|
|
const decisions = [
|
|
{
|
|
Priority: 'D1',
|
|
Owner: 'Compliance owner / Umar',
|
|
Decision: 'For each SKU with blank clinically_shown_to, either add a source-backed authorised claim or approve the module being blank.',
|
|
CurrentState: `${claimsMapping.mappedProducts ?? 0} mapped; ${blankDecision.counts?.pending ?? claimsMapping.unmappedProducts ?? 0} blank decisions pending; ${blankDecision.counts?.validBlankApprovals ?? 0} valid blank approvals.`,
|
|
EvidenceFile: 'data/content/compliance/clinical-blank-decisions.json',
|
|
Blocks: '185 compliance/export blockers and final handoff readiness.',
|
|
SafeDefault: 'Leave clinically_shown_to blank and blocked; do not invent generic clinical claims.'
|
|
},
|
|
{
|
|
Priority: 'D2',
|
|
Owner: 'Umar / JV reviewer',
|
|
Decision: 'Approve or reject product content, compliance, images, and export readiness per product.',
|
|
CurrentState: `${approvalDecision.counts?.validApprovals ?? 0} valid approvals; ${approvalDecision.counts?.pending ?? 190} pending; ${approvalDecision.counts?.applied ?? 0} applied.`,
|
|
EvidenceFile: 'data/content/approvals/product-approval-decisions.json',
|
|
Blocks: '190 approval blockers and Lewis/Euan import confidence.',
|
|
SafeDefault: 'Keep products blocked until explicit approval evidence is recorded.'
|
|
},
|
|
{
|
|
Priority: 'D3',
|
|
Owner: 'Lewis/Euan / Kubix',
|
|
Decision: 'Only run Matrixify staging import after dashboard handoff blockers are cleared, then return import logs.',
|
|
CurrentState: `${stagingPacket.counts?.presentImportFiles ?? 0}/${stagingPacket.counts?.importFiles ?? 0} import/reference files present; readyForLewis=${Boolean(handoff.readyForLewis)}; export blockers=${handoff.counts?.blockers ?? 'unknown'}.`,
|
|
EvidenceFile: 'content_population_exports/staging_handoff_packet.md',
|
|
Blocks: 'Staging import evidence, PDP checks, and production cutover.',
|
|
SafeDefault: 'Do not import as final while export_blockers.csv has rows.'
|
|
},
|
|
{
|
|
Priority: 'D4',
|
|
Owner: 'Dashboard owner + Lewis/Euan',
|
|
Decision: 'Complete real staging PDP spot checks and cutover approval after import.',
|
|
CurrentState: `${stagingEvidence.counts?.passingPdpChecks ?? 0} passing PDP checks; ${stagingEvidence.counts?.importLogs ?? 0} import logs; cutover approval present=${Boolean(stagingEvidence.counts?.cutoverApprovalPresent)}.`,
|
|
EvidenceFile: 'data/staging/import-logs/, data/staging/pdp-checks/, data/staging/cutover-approval.json',
|
|
Blocks: 'Production cutover.',
|
|
SafeDefault: 'Queued PDP templates are not evidence; real staging URLs must be inspected.'
|
|
},
|
|
{
|
|
Priority: 'D5',
|
|
Owner: 'Research / project owner',
|
|
Decision: 'Use DataForSEO only for the capped review pilot after endpoint/support/spend gates clear; otherwise use the manual capture fallback.',
|
|
CurrentState: `${dataforseoPilot.counts?.readyTasks ?? 0} DataForSEO payload rows ready; ${dataforseoPilot.counts?.missingAsins ?? 0} ASINs missing; reviews docs unavailable=${Boolean(dataforseoDocs.endpointTemporarilyUnavailable)}; current instruction: ${sourceNext.currentInstruction || 'not generated'}`,
|
|
EvidenceFile: 'content_population_exports/dataforseo_support_escalation.md and source_acquisition_next_actions.md',
|
|
Blocks: 'Amazon review-source depth; not current Matrixify handoff if manual/limited source caveat is accepted.',
|
|
SafeDefault: 'Do not retry paid Reviews endpoint while official docs/support gate says unavailable.'
|
|
}
|
|
]
|
|
|
|
const readinessRows = [
|
|
{ Metric: 'Dashboard products', Value: handoff.counts?.productRecords ?? 190 },
|
|
{ Metric: 'Matrixify product rows', Value: handoff.counts?.variantRows ?? 350 },
|
|
{ Metric: 'Export blockers', Value: handoff.counts?.blockers ?? 0 },
|
|
{ Metric: 'Blocked SKUs', Value: handoff.counts?.blockedSkus ?? 0 },
|
|
{ Metric: 'Readiness gates', Value: `${readiness.summary?.passing ?? 0}/${readiness.summary?.totalGates ?? 0}` },
|
|
{ Metric: 'Delivery packet files', Value: `${delivery.counts?.present ?? 0}/${delivery.counts?.files ?? 0}` },
|
|
{ Metric: 'Action plan blocked actions', Value: actionPlan.counts?.blockedActions ?? 0 },
|
|
{ Metric: 'Staging import files present', Value: `${stagingPacket.counts?.presentImportFiles ?? 0}/${stagingPacket.counts?.importFiles ?? 0}` }
|
|
]
|
|
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: Boolean(handoff.readyForLewis) && Boolean(readiness.ready) && Boolean(stagingEvidence.ready),
|
|
purpose: 'Executive decision brief for moving JV content population from generated packet to approved Lewis/Euan staging handoff.',
|
|
statusLine: Boolean(handoff.readyForLewis)
|
|
? 'Handoff packet is clean; proceed to staging evidence capture.'
|
|
: `Not handoff-ready: ${handoff.counts?.blockers ?? 'unknown'} export blockers remain, concentrated in compliance decisions and product approvals.`,
|
|
counts: {
|
|
decisions: decisions.length,
|
|
exportBlockers: handoff.counts?.blockers ?? null,
|
|
complianceBlockers: handoff.blockerSummary?.compliance ?? null,
|
|
approvalBlockers: handoff.blockerSummary?.approval ?? null,
|
|
deliveryFiles: delivery.counts?.files ?? null,
|
|
deliveryPresent: delivery.counts?.present ?? null
|
|
},
|
|
decisions,
|
|
readinessRows
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'executive_decision_brief.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'executive_decision_brief_decisions.csv'), csv(decisions, ['Priority', 'Owner', 'Decision', 'CurrentState', 'EvidenceFile', 'Blocks', 'SafeDefault']), 'utf8')
|
|
writeFileSync(join(outRoot, 'executive_decision_brief_readiness.csv'), csv(readinessRows, ['Metric', 'Value']), 'utf8')
|
|
writeFileSync(join(outRoot, 'executive_decision_brief.md'), [
|
|
'# JV executive decision brief',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Status: **${manifest.statusLine}**`,
|
|
'',
|
|
'## Snapshot',
|
|
...readinessRows.map(row => `- ${row.Metric}: ${row.Value}`),
|
|
'',
|
|
'## Decisions required',
|
|
...decisions.map(row => [
|
|
`### ${row.Priority} — ${row.Owner}`,
|
|
`- Decision: ${row.Decision}`,
|
|
`- Current state: ${row.CurrentState}`,
|
|
`- Evidence file: \`${row.EvidenceFile}\``,
|
|
`- Blocks: ${row.Blocks}`,
|
|
`- Safe default: ${row.SafeDefault}`,
|
|
''
|
|
].join('\n')),
|
|
'## Recommended meeting close',
|
|
'1. Confirm who owns the 185 clinical blank/source-backed claim decisions.',
|
|
'2. Confirm who can enter the 190 product approval decisions.',
|
|
'3. Confirm Lewis/Euan should wait for zero export blockers before Matrixify staging import.',
|
|
'4. If Amazon Reviews remains blocked at DataForSEO, choose manual capture for the six confirmed ASINs or accept the limited source caveat.',
|
|
'',
|
|
'## Non-negotiable guardrail',
|
|
'Do not mark handoff ready, approve products, or claim staging completion without the explicit evidence files named above.',
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Executive decision brief ready: ${manifest.ready}`)
|
|
console.log(`Decisions: ${manifest.counts.decisions}`)
|
|
console.log(`Export blockers: ${manifest.counts.exportBlockers}`)
|
|
if (!manifest.ready) process.exitCode = 1
|