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>
166 lines
10 KiB
TypeScript
166 lines
10 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 preflight = readJson(join(outRoot, 'reviewer_input_preflight.json'), { blockingRows: [] })
|
|
|
|
const guideRows = [
|
|
{
|
|
Lane: 'Compliance clinical blanks',
|
|
Owner: 'Compliance owner / Umar',
|
|
EditableFile: 'data/content/compliance/clinical-blank-decisions.csv',
|
|
RequiredEvidence: 'For each SKU: decision=approved_blank OR source-backed claim refs must be provided and synced.',
|
|
RequiredFields: 'decision=approved_blank; approvedBy non-empty; approvedAt ISO/date; rationale non-empty; constraintsAcknowledged=yes',
|
|
ValidExample: 'decision=approved_blank; approvedBy=<real compliance owner>; approvedAt=<actual review date>; rationale=<why blank is safe>; constraintsAcknowledged=yes',
|
|
InvalidExamples: 'pending rows; blank approver/date/rationale; constraintsAcknowledged=no; generic clinical claims without source refs',
|
|
ValidatorCommand: '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',
|
|
EditableFile: 'data/content/approvals/product-approval-decisions.csv',
|
|
RequiredEvidence: 'For each product: human review of content, compliance, images, export, and supporting evidence.',
|
|
RequiredFields: 'decision=approve_all; approvedBy non-empty; approvedAt ISO/date; reviewEvidence non-empty; contentReviewed=yes; complianceReviewed=yes; imagesReviewed=yes; exportReviewed=yes; noFakeEvidenceAcknowledged=yes',
|
|
ValidExample: 'decision=approve_all; approvedBy=<real reviewer>; approvedAt=<actual review date>; reviewEvidence=<packet/link/notes>; all review acknowledgements=yes',
|
|
InvalidExamples: 'self-approval; blank reviewEvidence; any acknowledgement=no; approving generated draft assets without human review',
|
|
ValidatorCommand: 'bun run approval:decisions && bun run approval:review-packet && bun run export:content',
|
|
Guardrail: 'Do not self-approve products or treat generated drafts as human approval.'
|
|
},
|
|
{
|
|
Lane: 'DataForSEO provider response',
|
|
Owner: 'Research / project owner',
|
|
EditableFile: 'data/sources/provider-responses/dataforseo-reviews-endpoint-response.json',
|
|
RequiredEvidence: 'DataForSEO support/docs confirm Amazon Reviews endpoint or replacement endpoint is available and retry-safe.',
|
|
RequiredFields: 'responseReceived=true; responseReceivedAt date; endpointAvailable=true OR replacementEndpoint non-empty; providerSaysRetrySafe=true; credentialIncludedInSupportThread=false; responderName/rawResponseSummary filled',
|
|
ValidExample: 'responseReceived=true; endpointAvailable=true; providerSaysRetrySafe=true; credentialIncludedInSupportThread=false; rawResponseSummary=<support answer summary>',
|
|
InvalidExamples: 'support not received; endpoint still unavailable; retrySafe=false; credentials pasted into support response or tracker',
|
|
ValidatorCommand: 'bun run dataforseo:support-response && bun run dataforseo:task-preflight',
|
|
Guardrail: 'Use pi-fresh-foundation .env credentials only through shell env; never print or paste secrets.'
|
|
},
|
|
{
|
|
Lane: 'DataForSEO spend approval',
|
|
Owner: 'Research / project owner',
|
|
EditableFile: 'data/sources/approvals/dataforseo-stage1-approval.json',
|
|
RequiredEvidence: 'Explicit owner approval for exactly one capped Stage 1 run after endpoint clearance.',
|
|
RequiredFields: 'approved=true; approvedBy non-empty; approvedAt date; maxSpendApproved within expected cap; targetCapApproved=9; runLimitApproved=1; depthOrRequestCapApproved=10; hardStopAcknowledged=true; noCredentialStorageAcknowledged=true; postRunReviewRequired=true',
|
|
ValidExample: 'approved=true only after endpoint is available; maxSpendApproved<=expectedMaxSpend; exact target/run/depth caps recorded',
|
|
InvalidExamples: 'approval before endpoint clearance; maxSpendApproved=0; caps do not match; hard stop/no-secret acknowledgements false',
|
|
ValidatorCommand: 'bun run source:spend-gate && bun run source:refresh-packet',
|
|
Guardrail: 'Approval alone must not override provider endpoint/support/docs blockers.'
|
|
},
|
|
{
|
|
Lane: 'Staging import evidence',
|
|
Owner: 'Lewis/Euan + dashboard owner',
|
|
EditableFile: 'data/staging/import-logs/*.json',
|
|
RequiredEvidence: 'Real Matrixify staging import logs for metaobjects, smart collections, and products.',
|
|
RequiredFields: 'importType matching expected type; environment=staging; importedAt date; importedBy real person; successfulRows>=expected; failedRows=0; sourceFile/matrixifyJobUrl or notes filled',
|
|
ValidExample: 'One JSON per import type from actual Matrixify staging run with failedRows=0 and enough successfulRows',
|
|
InvalidExamples: 'template JSON copied as evidence; failedRows>0; production environment; missing importedBy/importedAt',
|
|
ValidatorCommand: 'bun run staging:evidence && bun run staging:checklist',
|
|
Guardrail: 'Queued templates are not import evidence.'
|
|
},
|
|
{
|
|
Lane: 'Staging PDP spot checks',
|
|
Owner: 'Lewis/Euan + dashboard owner',
|
|
EditableFile: 'data/staging/pdp-checks/*.json',
|
|
RequiredEvidence: 'At least 5 real staging PDP URL checks with all checks passing and no issues.',
|
|
RequiredFields: 'sku; handle; stagingUrl; checkedAt; checkedBy; checks object values all pass; issues=[]',
|
|
ValidExample: 'Five or more real staging product URLs checked by a named reviewer with every required PDP check=pass',
|
|
InvalidExamples: 'queue files used as evidence; fewer than 5 checks; any issue recorded; any check not pass; fake/non-staging URL',
|
|
ValidatorCommand: 'bun run staging:evidence && bun run staging:checklist',
|
|
Guardrail: 'Queue files under pdp-check-queue are assignments only, not completed checks.'
|
|
},
|
|
{
|
|
Lane: 'Staging cutover approval',
|
|
Owner: 'Lewis/Euan + project owner',
|
|
EditableFile: 'data/staging/cutover-approval.json',
|
|
RequiredEvidence: 'Cutover approval signed only after clean import logs and PDP spot checks pass.',
|
|
RequiredFields: 'approved=true; approvedBy non-empty; approvedAt date; stagingImportEvidence path; pdpSpotCheckEvidence path; knownRisksAccepted array; notes optional',
|
|
ValidExample: 'approved=true with real approver/date after staging:evidence passes',
|
|
InvalidExamples: 'approved=false; blank approver/date; approval before staging evidence; treating tracker creation as signoff',
|
|
ValidatorCommand: 'bun run staging:evidence && bun run staging:checklist && bun run verify:handoff',
|
|
Guardrail: 'Do not treat handoff packet generation as cutover approval.'
|
|
}
|
|
]
|
|
|
|
const blockingByArea = new Map((preflight.blockingRows || []).map((row: any) => [row.Area, row]))
|
|
const rows = guideRows.map(row => {
|
|
const blocker = blockingByArea.get(row.Lane)
|
|
return {
|
|
...row,
|
|
CurrentStatus: blocker ? blocker.Status : 'unknown',
|
|
CurrentEvidence: blocker ? blocker.Evidence : 'No matching preflight row found.',
|
|
CurrentNextAction: blocker ? blocker.NextAction : row.ValidatorCommand
|
|
}
|
|
})
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: false,
|
|
purpose: 'Acceptance guide for every owner-editable evidence file. It explains exactly what counts as valid evidence and what remains invalid without filling or approving anything.',
|
|
counts: {
|
|
lanes: rows.length,
|
|
currentlyBlocked: rows.filter(row => row.CurrentStatus !== 'pass').length
|
|
},
|
|
rows,
|
|
files: {
|
|
markdown: 'content_population_exports/owner_evidence_acceptance_guide.md',
|
|
json: 'content_population_exports/owner_evidence_acceptance_guide.json',
|
|
csv: 'content_population_exports/owner_evidence_acceptance_guide.csv'
|
|
}
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'owner_evidence_acceptance_guide.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'owner_evidence_acceptance_guide.csv'), csv(rows, ['Lane', 'Owner', 'EditableFile', 'RequiredEvidence', 'RequiredFields', 'ValidExample', 'InvalidExamples', 'ValidatorCommand', 'Guardrail', 'CurrentStatus', 'CurrentEvidence', 'CurrentNextAction']), 'utf8')
|
|
writeFileSync(join(outRoot, 'owner_evidence_acceptance_guide.md'), [
|
|
'# Owner evidence acceptance guide',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
'This guide defines what evidence is acceptable for each owner-editable blocker file. It does not approve any row, create staging proof, or authorize provider spend.',
|
|
'',
|
|
`Lanes: ${manifest.counts.lanes}`,
|
|
`Currently blocked: ${manifest.counts.currentlyBlocked}`,
|
|
'',
|
|
'## Acceptance criteria by lane',
|
|
...rows.flatMap(row => [
|
|
`### ${row.Lane}`,
|
|
`- Owner: ${row.Owner}`,
|
|
`- Editable file: \`${row.EditableFile}\``,
|
|
`- Required evidence: ${row.RequiredEvidence}`,
|
|
`- Required fields: ${row.RequiredFields}`,
|
|
`- Valid example shape: ${row.ValidExample}`,
|
|
`- Invalid examples: ${row.InvalidExamples}`,
|
|
`- Current evidence: ${row.CurrentEvidence}`,
|
|
`- Validator: \`${row.ValidatorCommand}\``,
|
|
`- Guardrail: ${row.Guardrail}`,
|
|
''
|
|
]),
|
|
'## Final rule',
|
|
'Evidence is only accepted when the relevant validator passes. This guide is a field guide, not an override.',
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Owner evidence acceptance guide ready: ${manifest.ready}`)
|
|
console.log(`Lanes: ${manifest.counts.lanes}`)
|
|
console.log(`Currently blocked: ${manifest.counts.currentlyBlocked}`)
|