Files
justvitamin/scripts/validate-dataforseo-pilot-review.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

146 lines
6.0 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')
const reviewRoot = join(root, 'data', 'sources', 'reviews')
mkdirSync(outRoot, { recursive: true })
mkdirSync(reviewRoot, { 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 ingest = readJson(join(outRoot, 'dataforseo_ingest_validation.json'), {})
const spendGate = readJson(join(outRoot, 'source_acquisition_spend_gate.json'), {})
const apifyGapfill = readJson(join(outRoot, 'apify_gapfill_manifest.json'), {})
const reviewPath = join(reviewRoot, 'dataforseo-stage1-review.json')
const reviewTemplate = {
reviewed: false,
reviewedBy: '',
reviewedAt: '',
provider: 'DataForSEO',
stage: 1,
rawOutputInspected: false,
costReviewed: false,
outputHasReviewText: false,
outputHasRating: false,
outputHasDate: false,
outputHasSourceUrl: false,
costAcceptable: false,
decision: 'pending',
allowedDecisions: ['scale_dataforseo', 'run_apify_gapfill', 'stop_adjust_source_map'],
namedGapForApify: '',
notes: ''
}
if (!existsSync(reviewPath)) writeFileSync(reviewPath, JSON.stringify(reviewTemplate, null, 2) + '\n', 'utf8')
const review = readJson(reviewPath, reviewTemplate)
const convertedReviews = Number(ingest.counts?.convertedReviews || 0)
const convertedFiles = Number(ingest.counts?.convertedFiles || 0)
const issues = Number(ingest.counts?.issues || 0)
const apifyNamedGap = String(review.namedGapForApify || '').trim()
const checks = [
{
Check: 'Raw DataForSEO result ingested',
Status: convertedFiles > 0 && convertedReviews > 0 ? 'pass' : 'blocked',
Evidence: `${convertedFiles} converted files / ${convertedReviews} converted reviews`
},
{
Check: 'Ingest has no structural issues',
Status: convertedFiles > 0 && issues === 0 ? 'pass' : 'blocked',
Evidence: `${issues} ingest issues`
},
{
Check: 'Reviewer inspected raw output',
Status: review.reviewed === true && review.rawOutputInspected === true ? 'pass' : 'blocked',
Evidence: review.reviewed ? `${review.reviewedBy || 'unknown'} at ${review.reviewedAt || 'unknown'}` : 'review not recorded'
},
{
Check: 'Required evidence fields present',
Status: review.outputHasReviewText && review.outputHasRating && review.outputHasDate && review.outputHasSourceUrl ? 'pass' : 'blocked',
Evidence: `text=${!!review.outputHasReviewText}, rating=${!!review.outputHasRating}, date=${!!review.outputHasDate}, url=${!!review.outputHasSourceUrl}`
},
{
Check: 'Cost reviewed and acceptable',
Status: review.costReviewed === true && review.costAcceptable === true ? 'pass' : 'blocked',
Evidence: `costReviewed=${!!review.costReviewed}, costAcceptable=${!!review.costAcceptable}`
},
{
Check: 'Decision recorded',
Status: review.reviewed === true && reviewTemplate.allowedDecisions.includes(review.decision) ? 'pass' : 'blocked',
Evidence: `decision=${review.decision || 'pending'}`
},
{
Check: 'Apify gap named if selected',
Status: review.decision === 'run_apify_gapfill' ? (apifyNamedGap ? 'pass' : 'blocked') : 'pass',
Evidence: review.decision === 'run_apify_gapfill' ? (apifyNamedGap || 'missing named gap') : 'not selecting Apify gap-fill'
}
].map(row => ({ ...row, Blocking: row.Status === 'pass' ? 'no' : 'yes' }))
const ready = checks.every(row => row.Status === 'pass')
const decision = ready ? review.decision : 'pending'
const recommendations = ready
? decision === 'scale_dataforseo'
? ['Scale DataForSEO only to the next controlled priority batch; keep Apify off.']
: decision === 'run_apify_gapfill'
? [`Run only the Apify first batch (${apifyGapfill.counts?.firstBatchTargets || 0} targets) for named gap: ${apifyNamedGap}.`]
: ['Stop and adjust the source map before any further provider spend.']
: ['Do not scale DataForSEO or run Apify yet.', 'Complete raw ingest, evidence-field review, cost review, and explicit decision first.']
const manifest = {
generatedAt,
ready,
decision,
reviewFile: 'data/sources/reviews/dataforseo-stage1-review.json',
counts: {
checks: checks.length,
passing: checks.filter(row => row.Status === 'pass').length,
blocked: checks.filter(row => row.Status !== 'pass').length,
convertedFiles,
convertedReviews,
ingestIssues: issues
},
checks,
recommendations,
review
}
writeFileSync(join(outRoot, 'dataforseo_pilot_review.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
writeFileSync(join(outRoot, 'dataforseo_pilot_review.csv'), csv(checks, ['Check', 'Status', 'Blocking', 'Evidence']), 'utf8')
writeFileSync(join(outRoot, 'dataforseo_pilot_review.md'), [
'# DataForSEO Stage 1 pilot review gate',
'',
`Generated: ${generatedAt}`,
'',
`Ready: ${ready ? 'YES' : 'NO'}`,
`Decision: ${decision}`,
'',
'This gate is the stop/review step between paid Stage 1 output and any scale-up or Apify gap-fill spend.',
'',
'## Checks',
...checks.map(row => `- ${row.Status === 'pass' ? '[pass]' : '[blocked]'} ${row.Check}${row.Evidence}`),
'',
'## Recommendations',
...recommendations.map(item => `- ${item}`),
'',
'## Review file',
'- `data/sources/reviews/dataforseo-stage1-review.json`',
''
].join('\n'), 'utf8')
console.log(`DataForSEO pilot review ready: ${ready}`)
console.log(`Decision: ${decision}`)
console.log(`Checks passing: ${manifest.counts.passing}/${manifest.counts.checks}`)