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>
139 lines
5.9 KiB
TypeScript
139 lines
5.9 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { existsSync, readdirSync } from 'fs'
|
|
import { join } from 'path'
|
|
import { ROOT, readJson, writeJson } from './lib/intel-utils'
|
|
|
|
const contentRoot = join(ROOT, 'data', 'content')
|
|
const productsRoot = join(contentRoot, 'products')
|
|
const workflowRoot = join(contentRoot, 'workflow')
|
|
const outRoot = join(ROOT, 'content_population_exports')
|
|
const metaPath = join(contentRoot, 'metaobjects.json')
|
|
const libraryPath = join(ROOT, 'data', 'sources', 'claims', 'claims-library.json')
|
|
const now = new Date().toISOString()
|
|
|
|
function claimRef(handle: string) {
|
|
return `clinically_shown_to.${handle}`
|
|
}
|
|
|
|
function asMetaobject(claim: any) {
|
|
return {
|
|
handle: claim.handle,
|
|
name: claim.heading || claim.name_internal || claim.handle,
|
|
heading: claim.heading || '',
|
|
description: claim.description || '',
|
|
stat_1_value: claim.stat_1_value || '',
|
|
stat_1_name: claim.stat_1_name || '',
|
|
stat_2_value: claim.stat_2_value || '',
|
|
stat_2_name: claim.stat_2_name || '',
|
|
stat_3_value: claim.stat_3_value || '',
|
|
stat_3_name: claim.stat_3_name || '',
|
|
disclaimer: claim.disclaimer || '',
|
|
citation_title: claim.citation?.source_title || '',
|
|
citation_url: claim.citation?.source_url || '',
|
|
evidence_type: claim.citation?.evidence_type || '',
|
|
approval_owner: claim.approval_owner || '',
|
|
approval_date: claim.approval_date || '',
|
|
status: 'needs_human_compliance_approval',
|
|
source: 'data/sources/claims/claims-library.json'
|
|
}
|
|
}
|
|
|
|
const library = readJson(libraryPath, { claims: [] })
|
|
const meta = readJson(metaPath, { definitions: {} })
|
|
meta.definitions = meta.definitions || {}
|
|
meta.definitions.clinically_shown_to = (library.claims || []).map(asMetaobject)
|
|
meta.generatedAt = now
|
|
writeJson(metaPath, meta)
|
|
|
|
const rows: any[] = []
|
|
let mapped = 0
|
|
let unmapped = 0
|
|
for (const file of readdirSync(productsRoot).filter(file => file.endsWith('.json')).sort()) {
|
|
const path = join(productsRoot, file)
|
|
const product = readJson(path, {})
|
|
const sku = product.sku || file.replace(/\.json$/, '')
|
|
const evidence = readJson(join(ROOT, 'data', 'intelligence', sku, 'claims-evidence.json'), { claims: [] })
|
|
const claims = evidence.claims || []
|
|
product.metafields = product.metafields || {}
|
|
product.approval = product.approval || {}
|
|
const previous = product.metafields.pdpClinicallyShownTo || ''
|
|
if (claims.length > 0) {
|
|
const refs = claims.map((claim: any) => claimRef(claim.handle))
|
|
product.metafields.pdpClinicallyShownTo = refs[0]
|
|
product.approval.compliance = product.approval.compliance === 'approved'
|
|
? 'approved'
|
|
: 'needs_human_compliance_approval'
|
|
product.notes = Array.isArray(product.notes) ? product.notes : []
|
|
const note = 'Mapped source-backed draft clinically_shown_to reference from claims library; final human compliance approval still required.'
|
|
if (!product.notes.includes(note)) product.notes.push(note)
|
|
mapped += 1
|
|
rows.push({
|
|
sku,
|
|
status: 'mapped_source_backed_draft',
|
|
previous,
|
|
mappedRef: refs[0],
|
|
allCandidateRefs: refs.join(' | '),
|
|
complianceGate: evidence.complianceGate || ''
|
|
})
|
|
} else {
|
|
// Deliberately leave blank rather than adding a fake or generic clinical claim.
|
|
product.metafields.pdpClinicallyShownTo = ''
|
|
unmapped += 1
|
|
rows.push({
|
|
sku,
|
|
status: 'unmapped_no_source_backed_claim',
|
|
previous,
|
|
mappedRef: '',
|
|
allCandidateRefs: '',
|
|
complianceGate: evidence.complianceGate || 'missing_claims_evidence'
|
|
})
|
|
}
|
|
product.updatedAt = now
|
|
writeJson(path, product)
|
|
|
|
const workflowPath = join(workflowRoot, `${sku}.json`)
|
|
if (existsSync(workflowPath)) {
|
|
const workflow = readJson(workflowPath, {})
|
|
workflow.fields = workflow.fields || {}
|
|
workflow.fields.pdpClinicallyShownTo = workflow.fields.pdpClinicallyShownTo || {}
|
|
workflow.fields.pdpClinicallyShownTo.value = product.metafields.pdpClinicallyShownTo
|
|
workflow.fields.pdpClinicallyShownTo.status = product.metafields.pdpClinicallyShownTo
|
|
? 'needs_human_compliance_approval'
|
|
: 'blocked_no_source_backed_claim'
|
|
workflow.fields.pdpClinicallyShownTo.citations = claims.map((claim: any) => claim.citation?.source_url).filter(Boolean)
|
|
workflow.approval = { ...(workflow.approval || {}), compliance: product.approval.compliance || 'blocked' }
|
|
workflow.events = Array.isArray(workflow.events) ? workflow.events : []
|
|
workflow.events.unshift({
|
|
at: now,
|
|
type: 'clinical_claim_mapping_refresh',
|
|
actor: 'sync-clinically-shown-claims.ts',
|
|
field: 'pdpClinicallyShownTo',
|
|
from: previous,
|
|
to: product.metafields.pdpClinicallyShownTo,
|
|
summary: product.metafields.pdpClinicallyShownTo
|
|
? 'Mapped source-backed draft claim reference; human compliance approval still required.'
|
|
: 'No source-backed claim matched; left clinically_shown_to blank.'
|
|
})
|
|
workflow.updatedAt = now
|
|
writeJson(workflowPath, workflow)
|
|
}
|
|
}
|
|
|
|
const headers = ['sku', 'status', 'previous', 'mappedRef', 'allCandidateRefs', 'complianceGate']
|
|
const esc = (value: any) => /[",\n\r]/.test(String(value ?? '')) ? `"${String(value ?? '').replace(/"/g, '""')}"` : String(value ?? '')
|
|
await Bun.write(join(outRoot, 'clinical_claim_mapping_report.csv'), [headers.join(','), ...rows.map(row => headers.map(header => esc(row[header])).join(','))].join('\n') + '\n')
|
|
writeJson(join(outRoot, 'clinical_claim_mapping_report.json'), {
|
|
generatedAt: now,
|
|
sourceLibrary: 'data/sources/claims/claims-library.json',
|
|
totalProducts: rows.length,
|
|
sourceBackedClaimMetaobjects: meta.definitions.clinically_shown_to.length,
|
|
mappedProducts: mapped,
|
|
unmappedProducts: unmapped,
|
|
caveat: 'Only SKUs with source-backed claim evidence were mapped. Unmatched SKUs remain blank; no generic/fake clinical claim was created.',
|
|
rows
|
|
})
|
|
|
|
console.log(`Source-backed claim metaobjects: ${meta.definitions.clinically_shown_to.length}`)
|
|
console.log(`Mapped products: ${mapped}`)
|
|
console.log(`Unmapped products: ${unmapped}`)
|