Files
justvitamin/scripts/validate-staging-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

182 lines
6.8 KiB
TypeScript

#!/usr/bin/env bun
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
const root = process.cwd()
const outRoot = join(root, 'content_population_exports')
const stagingRoot = join(root, 'data', 'staging')
const templateRoot = join(stagingRoot, '_templates')
const importLogRoot = join(stagingRoot, 'import-logs')
const pdpCheckRoot = join(stagingRoot, 'pdp-checks')
function readJson(path: string, fallback: any = null) {
if (!existsSync(path)) return fallback
return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
}
function writeJson(path: string, data: any) {
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf8')
}
function csv(rows: any[], headers: string[]) {
const esc = (value: any) => {
if (Array.isArray(value)) value = value.join(' | ')
if (value === undefined || value === null) value = ''
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 listJson(dir: string) {
if (!existsSync(dir)) return []
return readdirSync(dir)
.filter(file => file.endsWith('.json'))
.map(file => ({ file, path: join(dir, file), data: readJson(join(dir, file), {}) }))
}
mkdirSync(outRoot, { recursive: true })
mkdirSync(templateRoot, { recursive: true })
mkdirSync(importLogRoot, { recursive: true })
mkdirSync(pdpCheckRoot, { recursive: true })
const generatedAt = new Date().toISOString()
const handoff = readJson(join(outRoot, 'handoff_manifest.json'), {})
const importLogTemplate = {
importType: 'products|metaobjects|smart_collections',
sourceFile: 'products_content_v5.csv',
environment: 'staging',
importedAt: '2026-05-19T00:00:00.000Z',
importedBy: 'Lewis/Euan',
totalRows: 350,
successfulRows: 350,
failedRows: 0,
warningRows: 0,
matrixifyJobUrl: '',
notes: ''
}
const pdpTemplate = {
sku: 'JV-D1000',
handle: 'vitamin-d3-1000iu',
stagingUrl: 'https://staging-store.myshopify.com/products/example',
checkedAt: '2026-05-19T00:00:00.000Z',
checkedBy: 'Dashboard owner / Lewis / Euan',
checks: {
productMetafields: 'pass',
variantMetafields: 'pass',
metaobjectReferences: 'pass',
fileReferences: 'pass',
faqFields: 'pass',
promoCard: 'pass',
collectionMapping: 'pass',
mobileLayout: 'pass'
},
issues: [],
approvedForProduction: false
}
const cutoverTemplate = {
approved: false,
approvedBy: '',
approvedAt: '',
stagingImportEvidence: 'data/staging/import-logs/',
pdpSpotCheckEvidence: 'data/staging/pdp-checks/',
knownRisksAccepted: [],
notes: ''
}
writeJson(join(templateRoot, 'import-log-template.json'), importLogTemplate)
writeJson(join(templateRoot, 'pdp-spot-check-template.json'), pdpTemplate)
writeJson(join(templateRoot, 'cutover-approval-template.json'), cutoverTemplate)
const importLogs = listJson(importLogRoot)
const pdpChecks = listJson(pdpCheckRoot)
const cutover = readJson(join(stagingRoot, 'cutover-approval.json'), null)
const expectedImports = [
{ type: 'metaobjects', expectedRows: handoff.counts?.metaobjectRows ?? 0 },
{ type: 'smart_collections', expectedRows: handoff.counts?.smartCollectionRows ?? 0 },
{ type: 'products', expectedRows: handoff.counts?.variantRows ?? 0 }
]
const rows: any[] = []
for (const expected of expectedImports) {
const matches = importLogs.filter(log => log.data.importType === expected.type)
const clean = matches.some(log => Number(log.data.failedRows || 0) === 0 && Number(log.data.successfulRows || 0) >= expected.expectedRows)
rows.push({
Gate: `import_log_${expected.type}`,
RequiredEvidence: `Matrixify ${expected.type} staging import log with failedRows=0 and successfulRows>=${expected.expectedRows}.`,
CurrentStatus: clean ? 'pass' : matches.length ? 'blocked' : 'missing',
EvidencePath: matches.map(log => `data/staging/import-logs/${log.file}`).join(' | '),
Blocking: clean ? 'no' : 'yes'
})
}
const passingPdpChecks = pdpChecks.filter(check => {
const values = Object.values(check.data.checks || {})
return values.length > 0 && values.every(value => value === 'pass') && (check.data.issues || []).length === 0
})
rows.push({
Gate: 'pdp_spot_checks',
RequiredEvidence: 'At least 5 PDP spot-check JSON files with all checks=pass and no issues.',
CurrentStatus: passingPdpChecks.length >= 5 ? 'pass' : pdpChecks.length ? 'blocked' : 'missing',
EvidencePath: pdpChecks.map(check => `data/staging/pdp-checks/${check.file}`).join(' | '),
Blocking: passingPdpChecks.length >= 5 ? 'no' : 'yes'
})
rows.push({
Gate: 'cutover_approval',
RequiredEvidence: 'data/staging/cutover-approval.json with approved=true, approver, date, and accepted known risks.',
CurrentStatus: cutover?.approved && cutover?.approvedBy && cutover?.approvedAt ? 'pass' : cutover ? 'blocked' : 'missing',
EvidencePath: cutover ? 'data/staging/cutover-approval.json' : '',
Blocking: cutover?.approved && cutover?.approvedBy && cutover?.approvedAt ? 'no' : 'yes'
})
const ready = rows.every(row => row.Blocking === 'no')
const report = {
generatedAt,
ready,
evidenceRoot: 'data/staging',
templates: [
'data/staging/_templates/import-log-template.json',
'data/staging/_templates/pdp-spot-check-template.json',
'data/staging/_templates/cutover-approval-template.json'
],
counts: {
gates: rows.length,
blocking: rows.filter(row => row.Blocking === 'yes').length,
importLogs: importLogs.length,
pdpChecks: pdpChecks.length,
passingPdpChecks: passingPdpChecks.length,
cutoverApprovalPresent: Boolean(cutover)
},
gates: rows
}
writeJson(join(outRoot, 'staging_evidence_validation.json'), report)
writeFileSync(join(outRoot, 'staging_evidence_validation.csv'), csv(rows, ['Gate', 'RequiredEvidence', 'CurrentStatus', 'EvidencePath', 'Blocking']), 'utf8')
const md = [
'# JV staging evidence validation',
'',
`Generated: ${generatedAt}`,
'',
`Ready: **${ready ? 'YES' : 'NO'}**`,
'',
'## Counts',
`- Import logs: ${report.counts.importLogs}`,
`- PDP checks: ${report.counts.pdpChecks}`,
`- Passing PDP checks: ${report.counts.passingPdpChecks}`,
`- Blocking gates: ${report.counts.blocking}/${report.counts.gates}`,
'',
'## Templates written',
...report.templates.map(path => `- \`${path}\``),
'',
'## Gates',
...rows.map(row => `- ${row.Blocking === 'no' ? '[PASS]' : '[BLOCKED]'} ${row.Gate}: ${row.RequiredEvidence} Current: ${row.CurrentStatus}`),
''
].join('\n')
writeFileSync(join(outRoot, 'staging_evidence_validation.md'), md, 'utf8')
console.log(`Staging evidence ready: ${ready}`)
console.log(`Import logs: ${report.counts.importLogs}`)
console.log(`PDP checks: ${report.counts.pdpChecks}`)
console.log(`Blocking gates: ${report.counts.blocking}`)
if (!ready) process.exitCode = 1