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>
148 lines
8.7 KiB
TypeScript
148 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 })
|
|
|
|
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 clinical = readJson(join(outRoot, 'clinical_blank_decision_template.json'), {})
|
|
const approvals = readJson(join(outRoot, 'product_approval_decision_gate.json'), {})
|
|
const staging = readJson(join(outRoot, 'staging_evidence_validation.json'), {})
|
|
const support = readJson(join(outRoot, 'dataforseo_support_response_gate.json'), {})
|
|
const spend = readJson(join(outRoot, 'source_acquisition_spend_gate.json'), {})
|
|
const dossier = readJson(join(outRoot, 'handoff_evidence_dossier.json'), {})
|
|
const daily = readJson(join(outRoot, `daily_status_${new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Kuala_Lumpur', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date())}.json`), {})
|
|
|
|
const ownerRows = [
|
|
{
|
|
Lane: 'Compliance blank/source-backed claim decisions',
|
|
Owner: 'Compliance owner / Umar',
|
|
RemainingCount: clinical.counts?.pending ?? clinical.counts?.decisions ?? 185,
|
|
EditableFile: 'data/content/compliance/clinical-blank-decisions.csv',
|
|
ReviewPacket: 'content_population_exports/clinical_blank_decision_template.md',
|
|
RequiredFields: 'decision, approvedBy, approvedAt, rationale, constraintsAcknowledged',
|
|
AcceptedCompletion: 'All blank SKUs have decision=approved_blank with required fields, or source-backed claim refs are provided and synced.',
|
|
RegenerateCommand: 'bun run compliance:blank-decisions && bun run claims:sync-content && bun run export:content',
|
|
Guardrail: 'Do not invent clinical claims. Blank clinical module needs explicit compliance approval.'
|
|
},
|
|
{
|
|
Lane: 'Product approval decisions',
|
|
Owner: 'Umar / JV reviewer',
|
|
RemainingCount: approvals.counts?.pending ?? approvals.counts?.products ?? 190,
|
|
EditableFile: 'data/content/approvals/product-approval-decisions.csv',
|
|
ReviewPacket: 'content_population_exports/product_approval_review_packet.md',
|
|
RequiredFields: 'decision, approvedBy, approvedAt, reviewEvidence, contentReviewed, complianceReviewed, imagesReviewed, exportReviewed, noFakeEvidenceAcknowledged',
|
|
AcceptedCompletion: 'All products have valid decision=approve_all rows with evidence and all acknowledgements.',
|
|
RegenerateCommand: 'bun run approval:decisions && bun run approval:review-packet && bun run export:content',
|
|
Guardrail: 'Do not self-approve; reviewer evidence must be human-entered.'
|
|
},
|
|
{
|
|
Lane: 'Lewis/Euan staging import evidence',
|
|
Owner: 'Lewis/Euan + dashboard owner',
|
|
RemainingCount: (staging.counts?.blocking || 5),
|
|
EditableFile: 'data/staging/import-logs/, data/staging/pdp-checks/, data/staging/cutover-approval.json',
|
|
ReviewPacket: 'content_population_exports/staging_handoff_packet.md',
|
|
RequiredFields: 'clean import logs, at least 5 PDP checks, signed cutover approval',
|
|
AcceptedCompletion: 'staging:evidence passes with failedRows=0, 5 PDP spot checks passing, and cutover approval signed.',
|
|
RegenerateCommand: 'bun run staging:evidence && bun run staging:checklist && bun run verify:handoff',
|
|
Guardrail: 'Queued PDP checks are not evidence; real staging URLs/results are required.'
|
|
},
|
|
{
|
|
Lane: 'DataForSEO Reviews endpoint/support/spend clearance',
|
|
Owner: 'Research / project owner',
|
|
RemainingCount: (support.counts?.blocked || 0) + ((spend.spendAllowed?.dataforseoStage1 === true) ? 0 : 1),
|
|
EditableFile: 'data/sources/provider-responses/dataforseo-reviews-endpoint-response.json; data/sources/approvals/dataforseo-stage1-approval.json',
|
|
ReviewPacket: 'content_population_exports/dataforseo_support_escalation.md; content_population_exports/dataforseo_stage1_approval_request.md',
|
|
RequiredFields: 'provider response, endpoint/replacement path, retry-safe confirmation, valid spend approval',
|
|
AcceptedCompletion: 'support-response, task-preflight, and source spend gate pass before any paid Reviews retry.',
|
|
RegenerateCommand: 'bun run dataforseo:support-response && bun run dataforseo:task-preflight && bun run source:spend-gate',
|
|
Guardrail: 'Use pi-fresh-foundation .env credentials only; do not print secrets or retry while docs/support block.'
|
|
}
|
|
]
|
|
|
|
const completionRows = ownerRows.map((row, index) => ({
|
|
Sequence: index + 1,
|
|
Owner: row.Owner,
|
|
Lane: row.Lane,
|
|
RemainingCount: row.RemainingCount,
|
|
EditableFile: row.EditableFile,
|
|
ReviewPacket: row.ReviewPacket,
|
|
RegenerateCommand: row.RegenerateCommand,
|
|
AcceptedCompletion: row.AcceptedCompletion
|
|
}))
|
|
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: ownerRows.every(row => Number(row.RemainingCount) === 0),
|
|
purpose: 'Owner-facing reviewer workbook index for the remaining human/provider/staging decisions. It packages existing editable files and exact regeneration commands without approving anything.',
|
|
counts: {
|
|
lanes: ownerRows.length,
|
|
openLanes: ownerRows.filter(row => Number(row.RemainingCount) > 0).length,
|
|
totalRemaining: ownerRows.reduce((sum, row) => sum + Number(row.RemainingCount || 0), 0),
|
|
dossierProven: dossier.counts?.proven ?? 0,
|
|
dossierRequirements: dossier.counts?.requirements ?? 0
|
|
},
|
|
files: {
|
|
ownerWorkbookMd: 'content_population_exports/reviewer_decision_workbook.md',
|
|
ownerWorkbookJson: 'content_population_exports/reviewer_decision_workbook.json',
|
|
ownerWorkbookCsv: 'content_population_exports/reviewer_decision_workbook.csv',
|
|
ownerCompletionCsv: 'content_population_exports/reviewer_completion_commands.csv'
|
|
},
|
|
ownerRows,
|
|
completionRows,
|
|
currentStatus: daily.headline || dossier.headline || ''
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'reviewer_decision_workbook.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'reviewer_decision_workbook.csv'), csv(ownerRows, ['Lane', 'Owner', 'RemainingCount', 'EditableFile', 'ReviewPacket', 'RequiredFields', 'AcceptedCompletion', 'RegenerateCommand', 'Guardrail']), 'utf8')
|
|
writeFileSync(join(outRoot, 'reviewer_completion_commands.csv'), csv(completionRows, ['Sequence', 'Owner', 'Lane', 'RemainingCount', 'EditableFile', 'ReviewPacket', 'RegenerateCommand', 'AcceptedCompletion']), 'utf8')
|
|
writeFileSync(join(outRoot, 'reviewer_decision_workbook.md'), [
|
|
'# Reviewer decision workbook',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
manifest.currentStatus,
|
|
'',
|
|
'This workbook is the handoff map for the people who must clear the remaining blockers. It does not approve content, alter decision files, or bypass provider/staging gates.',
|
|
'',
|
|
`Open lanes: ${manifest.counts.openLanes}/${manifest.counts.lanes}`,
|
|
`Tracked remaining items: ${manifest.counts.totalRemaining}`,
|
|
`Evidence dossier: ${manifest.counts.dossierProven}/${manifest.counts.dossierRequirements} requirement areas proven`,
|
|
'',
|
|
'## Owner lanes',
|
|
'| Owner | Lane | Remaining | Editable file | Regenerate command | Guardrail |',
|
|
'|---|---|---:|---|---|---|',
|
|
...ownerRows.map(row => `| ${row.Owner} | ${row.Lane} | ${row.RemainingCount} | ${row.EditableFile.replace(/\|/g, '/')} | ${row.RegenerateCommand.replace(/\|/g, '/')} | ${row.Guardrail.replace(/\|/g, '/')} |`),
|
|
'',
|
|
'## Exact sequence after reviewers fill files',
|
|
'1. `bun run compliance:blank-decisions && bun run claims:sync-content && bun run export:content`',
|
|
'2. `bun run approval:decisions && bun run approval:review-packet && bun run export:content`',
|
|
'3. `bun run dataforseo:support-response && bun run dataforseo:task-preflight && bun run source:spend-gate`',
|
|
'4. `bun run staging:evidence && bun run staging:checklist`',
|
|
'5. `bun run verify:handoff && bun run handoff:evidence-dossier && bun run delivery:index && bun run handoff:daily-status`',
|
|
'',
|
|
'## Guardrail',
|
|
'If any command above remains blocked, keep the goal active and report the exact failing gate. Do not infer approvals, clinical claims, staging evidence, provider availability, or paid-spend clearance.',
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Reviewer decision workbook ready: ${manifest.ready}`)
|
|
console.log(`Open lanes: ${manifest.counts.openLanes}/${manifest.counts.lanes}`)
|
|
console.log(`Tracked remaining items: ${manifest.counts.totalRemaining}`)
|