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:
@@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user