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>
119 lines
4.5 KiB
TypeScript
119 lines
4.5 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 docsUrl = 'https://docs.dataforseo.com/v3/merchant-amazon-reviews-task_post/'
|
|
|
|
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'
|
|
}
|
|
|
|
function compact(text: string) {
|
|
return text.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
.replace(/<[^>]+>/g, ' ')
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
}
|
|
|
|
const generatedAt = new Date().toISOString()
|
|
let httpStatus = 0
|
|
let fetchOk = false
|
|
let endpointTemporarilyUnavailable = false
|
|
let endpointMentioned = false
|
|
let evidence = ''
|
|
let error = ''
|
|
|
|
try {
|
|
const res = await fetch(docsUrl, { headers: { 'User-Agent': 'jv-dashboard-source-gate/1.0' } })
|
|
httpStatus = res.status
|
|
const html = await res.text()
|
|
const text = compact(html)
|
|
fetchOk = res.ok
|
|
endpointMentioned = text.includes('merchant/amazon/reviews/task_post')
|
|
endpointTemporarilyUnavailable = /endpoint is temporarily unavailable|function temporarily unavailable/i.test(text)
|
|
const unavailableIndex = text.toLowerCase().indexOf('temporarily unavailable')
|
|
if (unavailableIndex >= 0) evidence = text.slice(Math.max(0, unavailableIndex - 80), unavailableIndex + 160)
|
|
} catch (err: any) {
|
|
error = err.message || String(err)
|
|
}
|
|
|
|
const checks = [
|
|
{
|
|
Check: 'Official docs reachable',
|
|
Status: fetchOk ? 'pass' : 'blocked',
|
|
Evidence: fetchOk ? `Fetched ${docsUrl} with HTTP ${httpStatus}` : `HTTP ${httpStatus || 'n/a'} ${error}`
|
|
},
|
|
{
|
|
Check: 'Amazon Reviews task_post endpoint documented',
|
|
Status: endpointMentioned ? 'pass' : 'blocked',
|
|
Evidence: endpointMentioned ? 'Docs page mentions merchant/amazon/reviews/task_post.' : 'Endpoint string not found in fetched docs.'
|
|
},
|
|
{
|
|
Check: 'Official unavailable note detected',
|
|
Status: endpointTemporarilyUnavailable ? 'blocked' : 'pass',
|
|
Evidence: endpointTemporarilyUnavailable
|
|
? 'Official DataForSEO docs currently state the Amazon Reviews endpoint is temporarily unavailable.'
|
|
: 'No temporary-unavailable note detected in fetched docs.'
|
|
}
|
|
]
|
|
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: fetchOk && endpointMentioned && !endpointTemporarilyUnavailable,
|
|
provider: 'DataForSEO',
|
|
endpoint: 'POST https://api.dataforseo.com/v3/merchant/amazon/reviews/task_post',
|
|
docsUrl,
|
|
httpStatus,
|
|
credentialPrinted: false,
|
|
endpointTemporarilyUnavailable,
|
|
recommendation: endpointTemporarilyUnavailable
|
|
? 'Do not retry paid Amazon Reviews task_post while the official docs page marks the endpoint temporarily unavailable. Use support response or manual capture fallback.'
|
|
: 'Official docs no longer show the endpoint unavailable; still require local preflight, support/approval/spend gates before any capped retry.',
|
|
evidenceSnippet: evidence,
|
|
counts: {
|
|
checks: checks.length,
|
|
passing: checks.filter(row => row.Status === 'pass').length,
|
|
blocked: checks.filter(row => row.Status !== 'pass').length
|
|
},
|
|
checks
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'dataforseo_reviews_docs_status.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'dataforseo_reviews_docs_status.csv'), csv(checks, ['Check', 'Status', 'Evidence']), 'utf8')
|
|
writeFileSync(join(outRoot, 'dataforseo_reviews_docs_status.md'), [
|
|
'# DataForSEO Amazon Reviews docs status',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Ready for retry by docs status: ${manifest.ready ? 'YES' : 'NO'}`,
|
|
'',
|
|
`Official docs: ${docsUrl}`,
|
|
'',
|
|
manifest.recommendation,
|
|
'',
|
|
'## Checks',
|
|
...checks.map(row => `- [${row.Status}] ${row.Check}: ${row.Evidence}`),
|
|
'',
|
|
evidence ? '## Evidence snippet' : '',
|
|
evidence ? `> ${evidence}` : '',
|
|
'',
|
|
'## Guardrail',
|
|
'This check fetches official DataForSEO documentation only. It does not load credentials, print secrets, or call any paid endpoint.',
|
|
''
|
|
].filter(Boolean).join('\n'), 'utf8')
|
|
|
|
console.log(`DataForSEO Reviews docs status ready: ${manifest.ready}`)
|
|
console.log(`Official unavailable note: ${manifest.endpointTemporarilyUnavailable}`)
|
|
console.log(`Checks passing: ${manifest.counts.passing}/${manifest.counts.checks}`)
|