Files
justvitamin/scripts/generate-reviewer-decision-workbook.ts
T
Omair Saleh 056c47581f feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)
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>
2026-06-02 18:50:09 +08:00

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}`)