feat(editorial): catchy customer-hook science copy + generic-category comparison tables

Scientific Studies now lead with a customer-relevant outcome (matching how Heights,
Ritual, Nothing Fishy hook attention) before weaving in the PMID citation —
not the academic 'one of the most-studied supplements...' framing. Comparison
tables drop named brands ('Vitabiotics Immunace Fizz', 'Feel') and use generic
category labels ('Standard multivitamins', 'Bovine collagen brands') the way
Grüns, Elavate, Hunter & Gather, and Nothing Fishy actually structure theirs.
Per-SKU comparisonDraft curated in pilot-skus.json with rows derived from JV
product DB + 15-brand competitor section extracts + EFSA register.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Omair Saleh
2026-06-02 19:43:25 +08:00
parent e3fd4dc806
commit 2fbf6f35b5
8 changed files with 640 additions and 483 deletions
+82
View File
@@ -71,6 +71,15 @@ type ScientificStudyDraft = {
efsaCitation?: EfsaCitation
complianceNotes?: string
}
type CuratedCompareCol = { name: string; is_us?: boolean; accent_colour?: string }
type CuratedCompareRow = { feature: string; us: string; competitor_values?: string[] }
type ComparisonDraft = {
section_title?: string
subtitle?: string
columns?: CuratedCompareCol[]
rows?: CuratedCompareRow[]
complianceNotes?: string
}
type Pilot = {
sku: string
name: string
@@ -80,6 +89,7 @@ type Pilot = {
phases: string[]
productSpecificFaqs?: PilotFaq[]
scientificStudyDraft?: ScientificStudyDraft
comparisonDraft?: ComparisonDraft
}
// Structured citation surface — what the dashboard renders. Every claim,
// stat tile, and supporting line resolves to one of these. Never a file path.
@@ -542,6 +552,78 @@ function generateTimelineBlock(skuInfo: Pilot, claimsEv: ClaimsEvidence | null,
// ============================================================================
function generateComparisonTable(skuInfo: Pilot, cc: CompetitorComparison | null, productTruth: ProductTruth | null) {
const truth = productTruth || {}
// ====================================================================
// TIER 0 — hand-curated comparisonDraft from pilot-skus.json
// Column names are GENERIC CATEGORIES — never specific brand names —
// matching how elite competitor brands actually structure their
// comparison tables (Grüns, Nothing Fishy, Elavate, Hunter & Gather).
// ====================================================================
const cdraft = skuInfo.comparisonDraft
if (cdraft && Array.isArray(cdraft.columns) && cdraft.columns.length > 0 && Array.isArray(cdraft.rows) && cdraft.rows.length > 0) {
const columns = cdraft.columns.map((c, i) => ({
handle: i === 0 ? 'column-jv' : `column-${slug(c.name)}`,
name: c.name,
is_us: Boolean(c.is_us),
accent_colour: c.accent_colour || (c.is_us ? '#5C6B4A' : '#9aa0a6'),
image: ''
}))
const rows = cdraft.rows.map((r, i) => {
const values: Record<string, { mark: string; value: string; note: string }> = {
'0': { mark: 'text', value: r.us || '', note: '' }
}
const competitorVals = r.competitor_values || []
for (let j = 0; j < columns.length - 1; j++) {
values[String(j + 1)] = { mark: 'text', value: competitorVals[j] || '', note: '' }
}
return {
handle: `${slug(skuInfo.sku)}-cmp-row-${i + 1}`,
feature_label: r.feature,
values,
status: 'curated_generic_categories',
sources: [`data/pipeline/pilot-skus.json#${skuInfo.sku}.comparisonDraft.rows[${i}]`]
}
})
// Structured citations — what the dashboard renders. Generic-category
// comparisons cite the product DB (for JV col), the EFSA register where
// health-claim language appears, and the competitor section extracts.
const sourceLinks: CitationLink[] = [
{ type: 'product_truth', label: 'Just Vitamins product database', meta: 'sourced from JV Migration export' },
{ type: 'efsa', label: 'EFSA / UK NHC authorised health claims register', href: 'https://www.gov.uk/government/publications/great-britain-nutrition-and-health-claims-nhc-register' },
{ type: 'other', label: 'Competitor section extracts (15 brands)', meta: 'aggregated from observed positioning' }
]
// Run QA over every cell of the curated table — catches forbidden phrases,
// engineering jargon, medical claims, and stray brand names that slipped in.
const tableQa: QaCheck[] = []
if (cdraft.section_title) tableQa.push(...qaText('comparison.section_title', cdraft.section_title))
if (cdraft.subtitle) tableQa.push(...qaText('comparison.subtitle', cdraft.subtitle))
for (const r of rows) {
const jv = (r.values as any)['0']?.value || ''
tableQa.push(...qaText(`comparison.row[${r.feature_label}].jv`, jv))
// also check competitor columns
for (let i = 1; i < columns.length; i++) {
const v = (r.values as any)[String(i)]?.value || ''
if (v) tableQa.push(...qaText(`comparison.row[${r.feature_label}].col${i}`, v))
}
}
return {
handle: `${slug(skuInfo.sku)}-comparison`,
name_internal: `${skuInfo.name}${cdraft.section_title || 'How we compare'}`,
section_title: cdraft.section_title || 'How we compare',
subtitle: cdraft.subtitle || '',
columns,
rows,
status: 'curated_compliance_reviewed',
sources: [`data/pipeline/pilot-skus.json#${skuInfo.sku}.comparisonDraft`],
sourceLinks,
complianceNotes: cdraft.complianceNotes,
qa: scoreArtefact(tableQa)
}
}
if (!cc || ((!cc.comparison || cc.comparison.length === 0) && (!cc.competitors || cc.competitors.length === 0))) {
return {
handle: `${slug(skuInfo.sku)}-comparison-placeholder`,