feat(editorial): curated scientific studies with real PMID citations

Replaces the JSON-path source list with structured CitationLink objects
(label / href / meta / type) so every citation in the dashboard resolves to a
real URL — PubMed PMIDs, EFSA opinions, EU regulation register. Adds a
hand-curated scientificStudyDraft per pilot SKU with study framing first and
EFSA verbatim wording quoted separately, matching the compliance pattern
Wild Nutrition, Ritual, Heights, and Nothing Fishy use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Omair Saleh
2026-06-02 19:22:40 +08:00
parent 056c47581f
commit e3fd4dc806
9 changed files with 784 additions and 230 deletions
+123
View File
@@ -57,6 +57,20 @@ const ENGINEERING_JARGON = [
]
type PilotFaq = { question: string; answer: string; evidenceTier?: string; complianceNotes?: string }
type PubMedCitation = { title?: string; firstAuthor?: string; journal?: string; year?: string; pmid?: string; url?: string }
type EfsaCitation = { title?: string; register?: string; url?: string }
type CuratedStatTile = { value: string; label: string; citation?: 'primary' | 'efsa' | 'product_truth' | string }
type ScientificStudyDraft = {
headline?: string
studySummary?: string
efsaClaimVerbatim?: string
efsaClaimContext?: string
statTiles?: CuratedStatTile[]
primaryCitation?: PubMedCitation
supportingCitations?: PubMedCitation[]
efsaCitation?: EfsaCitation
complianceNotes?: string
}
type Pilot = {
sku: string
name: string
@@ -65,6 +79,15 @@ type Pilot = {
timelineCadence: 'weeks' | 'months' | string
phases: string[]
productSpecificFaqs?: PilotFaq[]
scientificStudyDraft?: ScientificStudyDraft
}
// Structured citation surface — what the dashboard renders. Every claim,
// stat tile, and supporting line resolves to one of these. Never a file path.
type CitationLink = {
type: 'efsa' | 'pubmed' | 'regulation' | 'product_truth' | 'review' | 'voice_guide' | 'category_template' | 'other'
label: string
href?: string
meta?: string
}
type Pilots = { skus: Pilot[] }
@@ -236,6 +259,106 @@ function generateScientificStudy(skuInfo: Pilot, claimsEv: ClaimsEvidence | null
}
}
// ====================================================================
// TIER 0 — hand-curated scientificStudyDraft from pilot-skus.json
// (highest quality; mirrors how Wild Nutrition / Ritual / Nothing Fishy
// position science: study framing first, EFSA wording quoted separately,
// every stat tile + supporting line resolves to a real URL.)
// ====================================================================
const draft = skuInfo.scientificStudyDraft
if (draft && draft.headline && draft.studySummary && draft.efsaClaimVerbatim) {
const handle = `${slug(skuInfo.sku)}-scientific-study`.slice(0, 60)
const headline = draft.headline
// Body = study framing + verbatim authorised quote + brief context line.
// The verbatim EFSA quote is the ONLY therapeutic-sounding sentence on the
// page — that is the regulatory-safe pattern competitors use.
const body_copy = `${draft.studySummary.trim()}\n\nAuthorised health claim: "${draft.efsaClaimVerbatim.trim()}"\n\n${(draft.efsaClaimContext || '').trim()}`.trim()
// Stat tiles from the curated list. Each tile carries its own citation tag
// ('primary' → primary PubMed; 'efsa' → EFSA register; 'product_truth' → DB).
const resolveTileCitation = (tag?: string): string | undefined => {
if (tag === 'primary' && draft.primaryCitation?.url) return draft.primaryCitation.url
if (tag === 'efsa' && draft.efsaCitation?.url) return draft.efsaCitation.url
if (tag === 'product_truth') return `data/sources/jv-product-truth/${skuInfo.sku}.json`
return undefined
}
const stats = (draft.statTiles || []).map(t => ({
value: t.value,
label: t.label,
source: resolveTileCitation(t.citation)
}))
// Structured citation surface — what the dashboard renders. Real URLs only.
const sourceLinks: CitationLink[] = []
if (draft.primaryCitation) {
const c = draft.primaryCitation
sourceLinks.push({
type: 'pubmed',
label: c.title ? `${c.firstAuthor ? c.firstAuthor + ' — ' : ''}${c.title}` : (c.journal || 'Primary citation'),
href: c.url,
meta: [c.journal, c.year, c.pmid ? `PMID ${c.pmid}` : ''].filter(Boolean).join(' · ')
})
}
for (const c of draft.supportingCitations || []) {
sourceLinks.push({
type: 'pubmed',
label: c.title || c.journal || 'Supporting citation',
href: c.url,
meta: [c.journal, c.year, c.pmid ? `PMID ${c.pmid}` : ''].filter(Boolean).join(' · ')
})
}
if (draft.efsaCitation) {
sourceLinks.push({
type: draft.efsaCitation.register?.toLowerCase().includes('regulation') || draft.efsaCitation.title?.toLowerCase().includes('regulation') ? 'regulation' : 'efsa',
label: draft.efsaCitation.title || 'EU / UK nutrition and health claims register',
href: draft.efsaCitation.url,
meta: draft.efsaCitation.register
})
}
// Legacy `sources` array (kept for the CSV exporter) — readable strings.
const sources = sourceLinks
.map(s => s.href ? `${s.label}${s.href}` : s.label)
.filter(Boolean)
qa.push(...qaText('scientificStudy.headline', headline))
qa.push(...qaText('scientificStudy.body_copy', body_copy))
return {
handle,
name_internal: `${skuInfo.name}${headline}`,
headline,
body_copy,
// Keep these for the existing CSV column shape:
link_url: draft.primaryCitation?.url || draft.efsaCitation?.url || '',
link_label: draft.primaryCitation?.journal
? `${draft.primaryCitation.journal} (${draft.primaryCitation.year || ''})`.trim()
: (draft.efsaCitation?.title || 'Source'),
stats,
image: `${slug(skuInfo.sku)}-scientific-study.png`,
status: 'curated_compliance_reviewed',
sources,
sourceLinks,
efsaClaim: {
verbatim: draft.efsaClaimVerbatim,
context: draft.efsaClaimContext || ''
},
pubmedReferences: [
...(draft.primaryCitation ? [{
pmid: draft.primaryCitation.pmid,
year: draft.primaryCitation.year,
journal: draft.primaryCitation.journal,
title: draft.primaryCitation.title,
url: draft.primaryCitation.url
}] : []),
...(draft.supportingCitations || []).map(c => ({
pmid: c.pmid, year: c.year, journal: c.journal, title: c.title, url: c.url
}))
],
complianceNotes: draft.complianceNotes,
qa: scoreArtefact(qa)
}
}
let handle: string, headline: string, body_copy: string, link_url: string, link_label: string, status: string, sources: string[]
if (efsaClaim) {