Files
justvitamin/scripts/validate-dataforseo-asin-web-evidence.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

123 lines
6.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')
const dataforseoRoot = join(root, 'data', 'sources', 'dataforseo')
const evidencePath = join(dataforseoRoot, 'asin-web-evidence.csv')
mkdirSync(outRoot, { recursive: true })
mkdirSync(dataforseoRoot, { recursive: 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'
}
function parseCsv(text: string) {
const rows: string[][] = []
let row: string[] = []
let cell = ''
let quoted = false
for (let index = 0; index < text.length; index += 1) {
const char = text[index]
const next = text[index + 1]
if (quoted) {
if (char === '"' && next === '"') { cell += '"'; index += 1 }
else if (char === '"') quoted = false
else cell += char
} else if (char === '"') quoted = true
else if (char === ',') { row.push(cell); cell = '' }
else if (char === '\n') { row.push(cell); rows.push(row); row = []; cell = '' }
else if (char !== '\r') cell += char
}
if (cell || row.length) { row.push(cell); rows.push(row) }
const headers = rows.shift()?.map(header => header.trim()) || []
return rows.filter(values => values.some(value => value.trim())).map(values => Object.fromEntries(headers.map((header, index) => [header, (values[index] || '').trim()])))
}
if (!existsSync(evidencePath)) {
writeFileSync(evidencePath, csv([], ['SKU', 'Product', 'EvidenceSource', 'EvidenceUrl', 'EvidenceTitle', 'EvidenceType', 'CandidateASIN', 'AmazonConfirmedUrl', 'BrandMatchesJustVitamins', 'ProductMatchesTarget', 'PackSizeMatchesTarget', 'MatchStatus', 'Confidence', 'Notes']), 'utf8')
}
const generatedAt = new Date().toISOString()
const evidenceRows = parseCsv(readFileSync(evidencePath, 'utf8'))
const rows = evidenceRows.map((row: any) => {
const asinValid = /^[A-Z0-9]{10}$/.test(row.CandidateASIN || '')
const amazonUrlValid = /^https:\/\/www\.amazon\.co\.uk\/dp\/[A-Z0-9]{10}/.test(row.AmazonConfirmedUrl || '')
const amazonExact = row.MatchStatus === 'amazon_confirmed_exact_match' && row.Confidence === 'high' && row.BrandMatchesJustVitamins === 'yes' && row.ProductMatchesTarget === 'yes' && row.PackSizeMatchesTarget === 'yes' && asinValid && amazonUrlValid
const usefulLookupAid = asinValid && row.MatchStatus === 'third_party_needs_amazon_confirmation'
const blockers = []
if (!asinValid && row.MatchStatus !== 'rejected_not_target') blockers.push('no valid ASIN')
if (!amazonUrlValid && row.MatchStatus !== 'rejected_not_target') blockers.push('no canonical Amazon dp URL')
if (row.MatchStatus !== 'amazon_confirmed_exact_match') blockers.push(row.MatchStatus === 'rejected_not_target' ? 'rejected as not target' : 'Amazon exact match not confirmed')
if (row.Confidence !== 'high') blockers.push(`confidence=${row.Confidence || 'unknown'}`)
if (row.BrandMatchesJustVitamins !== 'yes') blockers.push('brand not confirmed yes')
if (row.ProductMatchesTarget !== 'yes') blockers.push('target product not confirmed yes')
if (row.PackSizeMatchesTarget !== 'yes') blockers.push('pack size not confirmed yes')
return {
...row,
CandidateASINValid: asinValid ? 'yes' : 'no',
AmazonUrlValid: amazonUrlValid ? 'yes' : 'no',
UsableForPromotion: amazonExact ? 'yes' : 'no',
LookupAid: usefulLookupAid ? 'yes' : 'no',
Blockers: blockers.join(' | ')
}
})
const manifest = {
generatedAt,
ready: rows.some((row: any) => row.UsableForPromotion === 'yes'),
counts: {
evidenceRows: rows.length,
amazonConfirmedExact: rows.filter((row: any) => row.UsableForPromotion === 'yes').length,
thirdPartyLookupAids: rows.filter((row: any) => row.LookupAid === 'yes').length,
rejectedNotTarget: rows.filter((row: any) => row.MatchStatus === 'rejected_not_target').length,
validAsinShape: rows.filter((row: any) => row.CandidateASINValid === 'yes').length
},
files: {
evidenceCsv: 'data/sources/dataforseo/asin-web-evidence.csv',
reportCsv: 'content_population_exports/dataforseo_asin_web_evidence.csv',
reportJson: 'content_population_exports/dataforseo_asin_web_evidence.json',
reportMd: 'content_population_exports/dataforseo_asin_web_evidence.md'
},
guardrails: [
'Public web evidence is no-spend lookup support only.',
'Third-party pages can suggest ASINs but cannot unlock paid DataForSEO payload rows.',
'Only Amazon product-page exact matches with high confidence can be promoted into asin-map.csv.',
'Rejected/not-target search results are recorded to avoid re-checking weak leads.'
],
evidence: rows
}
writeFileSync(join(outRoot, 'dataforseo_asin_web_evidence.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
writeFileSync(join(outRoot, 'dataforseo_asin_web_evidence.csv'), csv(rows, ['SKU', 'Product', 'EvidenceSource', 'EvidenceUrl', 'EvidenceTitle', 'EvidenceType', 'CandidateASIN', 'AmazonConfirmedUrl', 'BrandMatchesJustVitamins', 'ProductMatchesTarget', 'PackSizeMatchesTarget', 'MatchStatus', 'Confidence', 'CandidateASINValid', 'AmazonUrlValid', 'LookupAid', 'UsableForPromotion', 'Blockers', 'Notes']), 'utf8')
writeFileSync(join(outRoot, 'dataforseo_asin_web_evidence.md'), [
'# DataForSEO ASIN web evidence report',
'',
`Generated: ${generatedAt}`,
'',
'This report records no-spend public web evidence gathered before any provider subscription/API usage. It intentionally keeps third-party ASIN hints separate from Amazon-confirmed ASINs.',
'',
`- Evidence rows: ${manifest.counts.evidenceRows}`,
`- Amazon-confirmed exact matches: ${manifest.counts.amazonConfirmedExact}`,
`- Third-party lookup aids: ${manifest.counts.thirdPartyLookupAids}`,
`- Rejected not-target leads: ${manifest.counts.rejectedNotTarget}`,
'',
'## Guardrails',
...manifest.guardrails.map(item => `- ${item}`),
'',
'## Evidence',
...rows.map((row: any) => `- [${row.UsableForPromotion === 'yes' ? 'promotable' : row.LookupAid === 'yes' ? 'lookup-aid' : 'blocked'}] ${row.SKU}${row.CandidateASIN || 'no ASIN'}${row.EvidenceSource}; blockers: ${row.Blockers || 'none'}`),
''
].join('\n'), 'utf8')
console.log(`DataForSEO ASIN web evidence ready: ${manifest.ready}`)
console.log(`Evidence rows: ${manifest.counts.evidenceRows}`)
console.log(`Amazon-confirmed exact matches: ${manifest.counts.amazonConfirmedExact}`)
console.log(`Third-party lookup aids: ${manifest.counts.thirdPartyLookupAids}`)