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>
120 lines
7.2 KiB
TypeScript
120 lines
7.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { mkdirSync, writeFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
|
|
const root = process.cwd()
|
|
const outRoot = join(root, 'content_population_exports')
|
|
mkdirSync(outRoot, { recursive: true })
|
|
|
|
const steps = [
|
|
{ name: 'source_acquisition_plan', command: ['bun', 'run', 'source:acquisition'], requiredForPacket: true },
|
|
{ name: 'dataforseo_pilot_intake', command: ['bun', 'run', 'dataforseo:pilot'], requiredForPacket: true },
|
|
{ name: 'dataforseo_products_live_guard', command: ['bun', 'run', 'dataforseo:products-live'], requiredForPacket: true },
|
|
{ name: 'dataforseo_live_connection_evidence', command: ['bun', 'run', 'dataforseo:live-asin-evidence'], requiredForPacket: true },
|
|
{ name: 'dataforseo_asin_candidates', command: ['bun', 'run', 'dataforseo:asin-candidates'], requiredForPacket: true },
|
|
{ name: 'dataforseo_asin_web_evidence', command: ['bun', 'run', 'dataforseo:asin-web-evidence'], requiredForPacket: true },
|
|
{ name: 'dataforseo_unresolved_asin_evidence', command: ['bun', 'run', 'dataforseo:unresolved-asins'], requiredForPacket: true },
|
|
{ name: 'dataforseo_asin_confirmations', command: ['bun', 'run', 'dataforseo:asin-confirmations'], requiredForPacket: true },
|
|
{ name: 'dataforseo_asin_promotion', command: ['bun', 'run', 'dataforseo:promote-asins'], requiredForPacket: true },
|
|
{ name: 'dataforseo_pilot_packet', command: ['bun', 'run', 'dataforseo:pilot'], requiredForPacket: true },
|
|
{ name: 'dataforseo_asin_lookup', command: ['bun', 'run', 'dataforseo:asin-lookup'], requiredForPacket: true },
|
|
{ name: 'dataforseo_reviews_docs_status', command: ['bun', 'run', 'dataforseo:reviews-docs-status'], requiredForPacket: true },
|
|
{ name: 'dataforseo_task_preflight', command: ['bun', 'run', 'dataforseo:task-preflight'], requiredForPacket: true },
|
|
{ name: 'dataforseo_reviews_live_guard', command: ['bun', 'run', 'dataforseo:reviews-live'], requiredForPacket: true },
|
|
{ name: 'dataforseo_support_escalation', command: ['bun', 'run', 'dataforseo:support-escalation'], requiredForPacket: true },
|
|
{ name: 'dataforseo_support_response_gate', command: ['bun', 'run', 'dataforseo:support-response'], requiredForPacket: true },
|
|
{ name: 'dataforseo_approval_request', command: ['bun', 'run', 'dataforseo:approval-request'], requiredForPacket: true },
|
|
{ name: 'manual_amazon_review_capture', command: ['bun', 'run', 'amazon:manual-capture'], requiredForPacket: true },
|
|
{ name: 'manual_amazon_review_capture_validation', command: ['bun', 'run', 'amazon:manual-capture-validate'], requiredForPacket: true },
|
|
{ name: 'dataforseo_ingest_waiting_room', command: ['bun', 'run', 'dataforseo:ingest'], requiredForPacket: true },
|
|
{ name: 'dataforseo_pilot_review_gate', command: ['bun', 'run', 'dataforseo:review'], requiredForPacket: true },
|
|
{ name: 'apify_gapfill_packet', command: ['bun', 'run', 'apify:gapfill'], requiredForPacket: true },
|
|
{ name: 'apify_task_preflight', command: ['bun', 'run', 'apify:task-preflight'], requiredForPacket: true },
|
|
{ name: 'source_pricing_references', command: ['bun', 'run', 'source:pricing-refs'], requiredForPacket: true },
|
|
{ name: 'source_cost_scenarios', command: ['bun', 'run', 'source:cost-scenarios'], requiredForPacket: true },
|
|
{ name: 'source_budget_gate', command: ['bun', 'run', 'source:budget'], requiredForPacket: true },
|
|
{ name: 'source_cost_plan', command: ['bun', 'run', 'source:cost-plan'], requiredForPacket: true },
|
|
{ name: 'source_approval_gate', command: ['bun', 'run', 'source:approvals'], requiredForPacket: true },
|
|
{ name: 'source_spend_gate', command: ['bun', 'run', 'source:spend-gate'], requiredForPacket: true },
|
|
{ name: 'dataforseo_task_preflight_final', command: ['bun', 'run', 'dataforseo:task-preflight'], requiredForPacket: true },
|
|
{ name: 'source_subscription_decision', command: ['bun', 'run', 'source:subscription-decision'], requiredForPacket: true },
|
|
{ name: 'source_next_actions', command: ['bun', 'run', 'source:next-actions'], requiredForPacket: true }
|
|
]
|
|
|
|
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 results: any[] = []
|
|
for (const step of steps) {
|
|
const startedAt = new Date().toISOString()
|
|
const proc = Bun.spawnSync({ cmd: step.command, cwd: root, stdout: 'pipe', stderr: 'pipe' })
|
|
const stdout = proc.stdout ? new TextDecoder().decode(proc.stdout).trim() : ''
|
|
const stderr = proc.stderr ? new TextDecoder().decode(proc.stderr).trim() : ''
|
|
const status = proc.exitCode === 0 ? 'pass' : 'blocked'
|
|
results.push({
|
|
Name: step.name,
|
|
Command: step.command.join(' '),
|
|
Status: status,
|
|
ExitCode: proc.exitCode,
|
|
RequiredForPacket: step.requiredForPacket ? 'yes' : 'no',
|
|
StartedAt: startedAt,
|
|
FinishedAt: new Date().toISOString(),
|
|
Stdout: stdout,
|
|
Stderr: stderr
|
|
})
|
|
console.log(`${step.name}: ${status} (${proc.exitCode})`)
|
|
}
|
|
|
|
const packetReady = results.filter(row => row.RequiredForPacket === 'yes').every(row => row.ExitCode === 0)
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: packetReady,
|
|
note: 'This proves the staged source-acquisition packet can be regenerated. Run delivery:index after this control run to refresh the overall handoff packet index. It does not mean provider spend or final handoff is allowed.',
|
|
counts: {
|
|
steps: results.length,
|
|
passing: results.filter(row => row.ExitCode === 0).length,
|
|
blocked: results.filter(row => row.ExitCode !== 0).length,
|
|
required: results.filter(row => row.RequiredForPacket === 'yes').length,
|
|
requiredPassing: results.filter(row => row.RequiredForPacket === 'yes' && row.ExitCode === 0).length
|
|
},
|
|
steps: results,
|
|
blockedSteps: results.filter(row => row.ExitCode !== 0).map(row => row.Name)
|
|
}
|
|
writeFileSync(join(outRoot, 'source_acquisition_control_run.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'source_acquisition_control_run.csv'), csv(results.map(row => ({
|
|
Name: row.Name,
|
|
Command: row.Command,
|
|
Status: row.Status,
|
|
ExitCode: row.ExitCode,
|
|
RequiredForPacket: row.RequiredForPacket,
|
|
Stdout: row.Stdout.split('\n').slice(0, 4).join(' | '),
|
|
Stderr: row.Stderr.split('\n').slice(0, 4).join(' | ')
|
|
})), ['Name', 'Command', 'Status', 'ExitCode', 'RequiredForPacket', 'Stdout', 'Stderr']), 'utf8')
|
|
writeFileSync(join(outRoot, 'source_acquisition_control_run.md'), [
|
|
'# Source acquisition control run',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Packet regenerated: ${packetReady ? 'YES' : 'NO'}`,
|
|
'',
|
|
manifest.note,
|
|
'',
|
|
`Steps passing: ${manifest.counts.passing}/${manifest.counts.steps}`,
|
|
`Required packet steps passing: ${manifest.counts.requiredPassing}/${manifest.counts.required}`,
|
|
'',
|
|
'## Steps',
|
|
...results.map(row => `- ${row.Status === 'pass' ? '[pass]' : '[blocked]'} ${row.Name}: \`${row.Command}\` exit=${row.ExitCode}`),
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`Source acquisition control ready: ${manifest.ready}`)
|
|
console.log(`Steps passing: ${manifest.counts.passing}/${manifest.counts.steps}`)
|