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>
150 lines
7.7 KiB
TypeScript
150 lines
7.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 = ''
|
|
value = Array.isArray(value) ? value.join(' | ') : 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 reviewsPilot = readJson(join(outRoot, 'dataforseo_reviews_live_pilot.json'), {})
|
|
const reviewsDocsStatus = readJson(join(outRoot, 'dataforseo_reviews_docs_status.json'), {})
|
|
const liveConnection = readJson(join(outRoot, 'dataforseo_live_connection.json'), {})
|
|
const taskPreflight = readJson(join(outRoot, 'dataforseo_task_preflight.json'), {})
|
|
const payload = readJson(join(outRoot, 'dataforseo_amazon_reviews_task_payload.json'), [])
|
|
const latestFailure = reviewsPilot.latestArchivedReviewPost || liveConnection.reviewEndpointStatus || null
|
|
const endpoint = reviewsPilot.endpoint || 'POST https://api.dataforseo.com/v3/merchant/amazon/reviews/task_post'
|
|
|
|
const issueRows = [
|
|
{
|
|
Item: 'Account connection',
|
|
Evidence: `${liveConnection.accountConnection?.status_code || 'n/a'} ${liveConnection.accountConnection?.status || liveConnection.accountConnection?.status_message || ''}`.trim(),
|
|
ActionNeeded: 'Credentials/account work; issue appears endpoint-specific.'
|
|
},
|
|
{
|
|
Item: 'Amazon Products endpoint',
|
|
Evidence: `${liveConnection.counts?.exactAsinsConfirmed || 0}/${liveConnection.counts?.targets || 0} exact ASINs confirmed from live product-result evidence`,
|
|
ActionNeeded: 'Products endpoint is usable; reviews endpoint is the blocker.'
|
|
},
|
|
{
|
|
Item: 'Amazon Reviews endpoint',
|
|
Evidence: latestFailure ? `${latestFailure.status_code}: ${latestFailure.status_message}` : 'No archived provider failure found',
|
|
ActionNeeded: 'Ask whether Merchant Amazon Reviews task_post is disabled for this account/API version/region and what replacement/restoration path exists.'
|
|
},
|
|
{
|
|
Item: 'Pilot payload',
|
|
Evidence: `${Array.isArray(payload) ? payload.length : 0} ASIN rows at depth 10, United Kingdom / English (United Kingdom)`,
|
|
ActionNeeded: 'Payload is intentionally capped; support can reproduce with one row if needed.'
|
|
},
|
|
{
|
|
Item: 'Official DataForSEO docs status',
|
|
Evidence: reviewsDocsStatus.docsUrl
|
|
? `endpointTemporarilyUnavailable=${reviewsDocsStatus.endpointTemporarilyUnavailable === true}; ${reviewsDocsStatus.counts?.passing || 0}/${reviewsDocsStatus.counts?.checks || 0} checks passing; docs=${reviewsDocsStatus.docsUrl}`
|
|
: 'No official docs status artifact found',
|
|
ActionNeeded: 'Ask support whether the official temporary-unavailable note applies globally, to this account, or to the Amazon UK Reviews path specifically.'
|
|
},
|
|
{
|
|
Item: 'Local spend guard',
|
|
Evidence: `${taskPreflight.counts?.passing || 0}/${taskPreflight.counts?.checks || 0} checks passing; paid posting blocked until approval gates are intentionally opened`,
|
|
ActionNeeded: 'No broad scraping or repeated retries until provider confirms endpoint availability.'
|
|
}
|
|
]
|
|
|
|
const supportMessage = [
|
|
'Hello DataForSEO support,',
|
|
'',
|
|
'We can authenticate successfully and use the Merchant Amazon Products endpoint, but Merchant Amazon Reviews task creation is returning a provider-level unavailable response before any task IDs are created.',
|
|
reviewsDocsStatus.endpointTemporarilyUnavailable === true
|
|
? 'Your public API documentation also currently marks the Amazon Reviews Task POST endpoint as temporarily unavailable.'
|
|
: '',
|
|
'',
|
|
`Endpoint: ${endpoint}`,
|
|
latestFailure ? `Response: ${latestFailure.status_code} ${latestFailure.status_message}` : 'Response: no archived response available',
|
|
latestFailure?.cost !== undefined ? `Cost reported: ${latestFailure.cost}` : '',
|
|
latestFailure?.tasks_count !== undefined ? `Tasks count: ${latestFailure.tasks_count}` : '',
|
|
'',
|
|
'The intended pilot is small: 6 Amazon UK ASINs, depth 10, location_name=United Kingdom, language_name=English (United Kingdom). The Products endpoint already returned usable ASIN evidence for those rows.',
|
|
'',
|
|
'Could you confirm whether /v3/merchant/amazon/reviews/task_post is temporarily unavailable globally, unavailable for this account, or requires a different endpoint/parameter set? If it is disabled, please advise the expected restoration path or replacement endpoint.',
|
|
'',
|
|
'No credentials are included in this packet.'
|
|
].filter(Boolean).join('\n')
|
|
|
|
const manifest = {
|
|
generatedAt,
|
|
ready: Boolean(latestFailure && Number(latestFailure.status_code) === 50304),
|
|
provider: 'DataForSEO',
|
|
endpoint,
|
|
issue: latestFailure ? `${latestFailure.status_code}: ${latestFailure.status_message}` : 'No provider failure archived yet',
|
|
credentialPrinted: false,
|
|
counts: {
|
|
payloadRows: Array.isArray(payload) ? payload.length : 0,
|
|
liveConfirmedAsins: liveConnection.counts?.exactAsinsConfirmed || 0,
|
|
missingAsins: liveConnection.counts?.missingAsins || 0,
|
|
preflightPassing: taskPreflight.counts?.passing || 0,
|
|
preflightChecks: taskPreflight.counts?.checks || 0
|
|
,
|
|
docsStatusPassing: reviewsDocsStatus.counts?.passing || 0,
|
|
docsStatusChecks: reviewsDocsStatus.counts?.checks || 0
|
|
},
|
|
files: {
|
|
supportMd: 'content_population_exports/dataforseo_support_escalation.md',
|
|
supportJson: 'content_population_exports/dataforseo_support_escalation.json',
|
|
supportCsv: 'content_population_exports/dataforseo_support_escalation.csv',
|
|
reviewsPilot: 'content_population_exports/dataforseo_reviews_live_pilot.json',
|
|
liveConnection: 'content_population_exports/dataforseo_live_connection.json',
|
|
reviewsDocsStatus: 'content_population_exports/dataforseo_reviews_docs_status.json',
|
|
payload: 'content_population_exports/dataforseo_amazon_reviews_task_payload.json'
|
|
},
|
|
issueRows,
|
|
supportMessage,
|
|
payloadPreview: Array.isArray(payload) ? payload.slice(0, 6).map((row: any) => ({ asin: row.asin, depth: row.depth, location_name: row.location_name, language_name: row.language_name, tag: row.tag })) : []
|
|
}
|
|
|
|
writeFileSync(join(outRoot, 'dataforseo_support_escalation.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
writeFileSync(join(outRoot, 'dataforseo_support_escalation.csv'), csv(issueRows, ['Item', 'Evidence', 'ActionNeeded']), 'utf8')
|
|
writeFileSync(join(outRoot, 'dataforseo_support_escalation.md'), [
|
|
'# DataForSEO support escalation packet',
|
|
'',
|
|
`Generated: ${generatedAt}`,
|
|
'',
|
|
`Ready to send: ${manifest.ready ? 'YES' : 'NO'}`,
|
|
'',
|
|
'## Summary',
|
|
`- Provider: ${manifest.provider}`,
|
|
`- Endpoint: ${endpoint}`,
|
|
`- Issue: ${manifest.issue}`,
|
|
`- Credentials included: no`,
|
|
`- Pilot payload rows: ${manifest.counts.payloadRows}`,
|
|
`- Live-confirmed ASINs: ${manifest.counts.liveConfirmedAsins}`,
|
|
'',
|
|
'## Evidence checklist',
|
|
...issueRows.map(row => `- **${row.Item}:** ${row.Evidence} — ${row.ActionNeeded}`),
|
|
'',
|
|
'## Message to send',
|
|
'```text',
|
|
supportMessage,
|
|
'```',
|
|
'',
|
|
'## Payload preview',
|
|
...manifest.payloadPreview.map((row: any) => `- ${row.tag}: ${row.asin}, depth ${row.depth}, ${row.location_name} / ${row.language_name}`),
|
|
''
|
|
].join('\n'), 'utf8')
|
|
|
|
console.log(`DataForSEO support escalation ready: ${manifest.ready}`)
|
|
console.log(`Issue: ${manifest.issue}`)
|
|
console.log(`Payload rows: ${manifest.counts.payloadRows}`)
|