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>
287 lines
14 KiB
TypeScript
287 lines
14 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
import { dirname, join } from 'path'
|
|
|
|
const root = process.cwd()
|
|
const profileRoot = join(root, 'data', 'research-profiles')
|
|
const contentRoot = join(root, 'data', 'content', 'products')
|
|
const intelRoot = join(root, 'data', 'intelligence')
|
|
const outRoot = join(root, 'content_population_exports')
|
|
const generatedAt = new Date().toISOString()
|
|
|
|
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) {
|
|
mkdirSync(dirname(path), { recursive: true })
|
|
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf8')
|
|
}
|
|
function compact<T>(items: T[]): T[] { return items.filter(Boolean) }
|
|
function lines(value: any): string[] { return String(value || '').split('\n').map(v => v.trim()).filter(Boolean) }
|
|
|
|
const skuBriefs: Record<string, any> = {
|
|
'JV-TURMERIC500': {
|
|
archetype: 'Sceptical joint-care researcher comparing turmeric strength and absorption support.',
|
|
primaryIntent: 'Find a turmeric supplement that is strong, transparent, and credible without feeling like exaggerated joint-pain marketing.',
|
|
topSearchQuestions: [
|
|
'Is turmeric/curcumin actually worth taking?',
|
|
'How much curcumin is in the tablet?',
|
|
'Does black pepper or BioPerine matter?',
|
|
'How long before I can judge it?',
|
|
'Is the product vegan and easy to take daily?'
|
|
],
|
|
offerFrame: 'Potency transparency over hype: 95% curcuminoids, guaranteed 475mg curcuminoids per tablet and BioPerine absorption support.',
|
|
heroHeadline: 'High-strength turmeric with the curcuminoid detail clearly shown',
|
|
heroSubheadline: 'A daily vegan turmeric formula with 95% curcuminoids, BioPerine black pepper extract and compliance-gated Vitamin C support.',
|
|
imageBriefs: [
|
|
'Comparison graphic: headline turmeric equivalent vs actual guaranteed curcuminoids per tablet.',
|
|
'Ingredient graphic: turmeric extract + BioPerine + Vitamin C, with compliance-safe wording.',
|
|
'Expectation graphic: daily routine; best judged after consistent use, not instant relief.',
|
|
'Reassurance graphic: product-specific tablet/pack imagery and packaging quality check.'
|
|
],
|
|
claimsToAvoid: ['treats arthritis', 'relieves pain', 'reduces inflammation', 'heals joints', 'guaranteed mobility improvement'],
|
|
sections: ['Hero potency proof', 'Ingredient standardisation', 'Why BioPerine is included', 'Customer expectation FAQ', 'Comparison against generic high-mg turmeric']
|
|
},
|
|
'JV-DEFF1000': {
|
|
archetype: 'UK low-sun customer who wants Vitamin D but dislikes another swallow tablet.',
|
|
primaryIntent: 'Find a simple daily Vitamin D3 format that is easy and pleasant to take during darker months.',
|
|
topSearchQuestions: [
|
|
'Is 1000IU enough for daily maintenance?',
|
|
'Is this for deficiency or everyday support?',
|
|
'Does it taste good?',
|
|
'How much water do I use?',
|
|
'Is it actually effervescent, not a tablet?'
|
|
],
|
|
offerFrame: 'The format is the offer: daily 1000IU Vitamin D3 as a peach and passion-fruit drink.',
|
|
heroHeadline: 'Daily Vitamin D3 in a simple effervescent drink',
|
|
heroSubheadline: 'One 1000IU Vitamin D3 tablet dissolved in 200ml water — a peach and passion-fruit alternative to capsules.',
|
|
imageBriefs: [
|
|
'Hero usage image: tablet dissolving in 200ml water with peach/passion-fruit cues.',
|
|
'Comparison graphic: drink format vs swallow tablets/capsules.',
|
|
'Seasonal education graphic: UK low-sun months and daily maintenance support.',
|
|
'Guardrail FAQ graphic: maintenance support, not deficiency treatment.'
|
|
],
|
|
claimsToAvoid: ['treats deficiency', 'replaces medical dosing', 'boosts immunity beyond authorised wording', 'small tablet/no taste swallow-tablet copy'],
|
|
sections: ['Hero drink format', 'How to take', 'UK low-sun education', 'Maintenance vs deficiency FAQ', 'Taste/convenience proof']
|
|
},
|
|
'JV-VISISOFT': {
|
|
archetype: 'Eye-health routine buyer comparing lutein/zeaxanthin formulas and looking for safe, credible normal-vision support.',
|
|
primaryIntent: 'Find a daily eye-health supplement with recognisable ingredients and normal-vision support without medical treatment claims.',
|
|
topSearchQuestions: [
|
|
'How much lutein and zeaxanthin does it contain?',
|
|
'Is this Original or a stronger eye formula?',
|
|
'Does Vitamin A support normal vision?',
|
|
'Is this suitable for vegans?',
|
|
'Should I ask an optician before taking eye supplements?'
|
|
],
|
|
offerFrame: 'Ingredient-stack clarity: Original 10mg lutein plus zeaxanthin, bilberry, grape seed and authorised Vitamin A normal-vision wording.',
|
|
heroHeadline: 'Original daily eye-health support with lutein, zeaxanthin and Vitamin A',
|
|
heroSubheadline: 'A vegan-friendly eye-health formula with Lutein 10mg, Zeaxanthin 400mcg, Bilberry, Grape Seed and Vitamin A normal-vision support.',
|
|
imageBriefs: [
|
|
'Ingredient stack graphic: Lutein 10mg, Zeaxanthin 400mcg, Bilberry, Grape Seed, Vitamin A.',
|
|
'Comparison graphic: Visisoft Original vs advanced/omega-rich competitor stacks without overstating superiority.',
|
|
'Routine graphic: one to two tablets daily with food.',
|
|
'Compliance graphic: Vitamin A contributes to maintenance of normal vision; not treatment for eye disease.'
|
|
],
|
|
claimsToAvoid: ['treats AMD', 'improves cataracts', 'prevents glaucoma', 'restores eyesight', 'improves night vision as a guaranteed result'],
|
|
sections: ['Hero ingredient clarity', 'Normal vision claim', 'Original vs richer stacks', 'Optician/review context', 'Hard-to-prove expectation FAQ']
|
|
}
|
|
}
|
|
|
|
const top3 = Object.keys(skuBriefs)
|
|
const summary: any[] = []
|
|
|
|
for (const sku of top3) {
|
|
const content = readJson(join(contentRoot, `${sku}.json`), {})
|
|
const publicEvidence = readJson(join(intelRoot, sku, 'public-internet-evidence.json'), {})
|
|
const amazon = readJson(join(intelRoot, sku, 'amazon-reviews.json'), {})
|
|
const reddit = readJson(join(intelRoot, sku, 'reddit-pulse.json'), {})
|
|
const competitor = readJson(join(intelRoot, sku, 'competitor-comparison.json'), {})
|
|
const market = readJson(join(intelRoot, sku, 'market-insight-synthesis.json'), {})
|
|
const claims = readJson(join(intelRoot, sku, 'claims-evidence.json'), {})
|
|
const blockers = readJson(join(intelRoot, sku, 'conversion-blockers.json'), {})
|
|
const improvements = readJson(join(intelRoot, sku, 'improvements.json'), {})
|
|
const brief = skuBriefs[sku]
|
|
const m = content.metafields || {}
|
|
|
|
const productTruth = {
|
|
productName: content.name,
|
|
handle: content.handle,
|
|
formatAndUse: sku === 'JV-DEFF1000' ? 'Effervescent tablet dissolved in 200ml water.' : sku === 'JV-VISISOFT' ? 'Tablet; adults take one to two daily with food.' : 'Tablet; daily turmeric routine.',
|
|
ingredientsAndDose: publicEvidence.facts || [],
|
|
dietaryAndCollection: {
|
|
dietaryTags: m.filterDietaryTag || [],
|
|
healthGoals: m.filterHealthGoals || [],
|
|
keyIngredient: m.pdpKeyIngredients,
|
|
primaryCollection: m.breadcrumbPrimaryCollection,
|
|
strength: m.filterStrength
|
|
},
|
|
nonNegotiableFacts: compact([
|
|
m.pdpKeyIngredients,
|
|
...lines(m.pdpKeyFeatures),
|
|
...(publicEvidence.facts || []).slice(0, 4)
|
|
])
|
|
}
|
|
|
|
const customerIntent = {
|
|
archetype: brief.archetype,
|
|
primaryIntent: brief.primaryIntent,
|
|
topSearchQuestions: brief.topSearchQuestions,
|
|
redditLanguage: (reddit.pulses || []).map((row: any) => ({
|
|
topic: row.topic || row.theme,
|
|
evidence: row.evidence || row.whyItMatters,
|
|
contentUse: row.contentUse,
|
|
sourceUrl: row.sourceUrl || null
|
|
})),
|
|
publicReviewLanguage: (publicEvidence.reviewSignals || []).map((row: any) => ({
|
|
theme: row.theme,
|
|
evidence: row.evidence,
|
|
contentUse: row.use
|
|
}))
|
|
}
|
|
|
|
const objections = [
|
|
...(blockers.blockers || []).map((row: any) => ({ source: 'conversion-blocker', objection: row.blocker, evidence: row.evidence, response: row.fix })),
|
|
...(reddit.pulses || []).filter((row: any) => /skeptic|confusion|concern|trust|hard|dose|doctor|optician/i.test(`${row.topic} ${row.evidence} ${row.status}`)).map((row: any) => ({ source: 'reddit', objection: row.topic || row.theme, evidence: row.evidence, response: row.contentUse })),
|
|
...(amazon.themes || []).filter((row: any) => /guardrail|not confirmed|contamination|pressure|clarity/i.test(`${row.theme} ${row.status}`)).map((row: any) => ({ source: 'amazon', objection: row.theme, evidence: row.evidence, response: row.contentUse }))
|
|
]
|
|
|
|
const competitorMap = {
|
|
coverage: competitor.coverage,
|
|
comparedCompetitors: competitor.competitors || [],
|
|
marketplacePressure: (amazon.themes || []).map((row: any) => ({ theme: row.theme, evidence: row.evidence, use: row.contentUse })),
|
|
positioningWhitespace: market.competitor?.whitespace || (competitor.competitors || []).flatMap((row: any) => row.whitespace ? [row.whitespace] : []),
|
|
competitorSignals: market.competitor?.signals || (competitor.comparison || []).map((row: any) => row.competitorEvidence).filter(Boolean)
|
|
}
|
|
|
|
const claimProfile = {
|
|
allowedClaims: (claims.claims || []).map((claim: any) => ({
|
|
heading: claim.heading,
|
|
handle: claim.handle,
|
|
status: claim.status,
|
|
citation: claim.citation
|
|
})),
|
|
complianceGate: claims.complianceGate,
|
|
claimsToAvoid: brief.claimsToAvoid,
|
|
customerLanguageRestriction: 'Customer/reddit/review language can shape objections and FAQs, but cannot become clinical or disease-treatment claims.'
|
|
}
|
|
|
|
const offerArchitecture = {
|
|
mainOffer: market.offer || brief.offerFrame,
|
|
proofPoints: compact([
|
|
...(publicEvidence.facts || []).slice(0, 3),
|
|
...(amazon.themes || []).slice(0, 2).map((row: any) => row.evidence),
|
|
...(market.competitor?.signals || []).slice(0, 2)
|
|
]),
|
|
recommendedSections: brief.sections,
|
|
imageBriefs: brief.imageBriefs,
|
|
faqStrategy: [
|
|
{ question: m.faqHeadingOne, answer: m.faqAnswerOne },
|
|
{ question: m.faqHeadingTwo, answer: m.faqAnswerTwo },
|
|
{ question: m.faqHeadingThree, answer: m.faqAnswerThree }
|
|
].filter(row => row.question || row.answer),
|
|
subscriptionOrBundleAngle: sku === 'JV-DEFF1000'
|
|
? 'Use replenishment convenience only if stock/variant logic supports it; do not overcomplicate a simple drink-format SKU.'
|
|
: sku === 'JV-TURMERIC500'
|
|
? 'Routine-based multi-pack or best-value angle can support consistency, but avoid promising results by a fixed date.'
|
|
: 'Routine/long-term value angle is more suitable than aggressive clinical proof.'
|
|
}
|
|
|
|
const pdpGenerationBrief = {
|
|
audience: brief.archetype,
|
|
primaryIntent: brief.primaryIntent,
|
|
mainOfferAngle: market.offer || brief.offerFrame,
|
|
heroHeadline: brief.heroHeadline,
|
|
heroSubheadline: brief.heroSubheadline,
|
|
topBenefits: m.pdpProductBenefits || [],
|
|
proofPoints: offerArchitecture.proofPoints,
|
|
comparisonClaims: competitorMap.positioningWhitespace,
|
|
faqQuestions: offerArchitecture.faqStrategy.map(row => row.question),
|
|
imageBriefs: brief.imageBriefs,
|
|
complianceSafeClaims: claimProfile.allowedClaims.map((claim: any) => claim.heading),
|
|
claimsToAvoid: brief.claimsToAvoid,
|
|
competitorWhitespace: competitorMap.positioningWhitespace,
|
|
amazonInsights: (amazon.themes || []).map((row: any) => row.theme),
|
|
redditInsights: (reddit.pulses || []).map((row: any) => row.topic || row.theme),
|
|
recommendedPdpSections: brief.sections
|
|
}
|
|
|
|
const profile = {
|
|
sku,
|
|
generatedAt,
|
|
generatedBy: 'generate-top3-research-profiles.ts',
|
|
profileVersion: 'v1',
|
|
status: 'research_profile_ready_for_pdp_generation',
|
|
sourceCoverage: {
|
|
officialPdpFacts: publicEvidence.facts?.length || 0,
|
|
publicReviewSignals: publicEvidence.reviewSignals?.length || 0,
|
|
amazonSignals: amazon.themes?.length || 0,
|
|
redditSignals: reddit.pulses?.length || 0,
|
|
competitorSignals: competitorMap.competitorSignals.length,
|
|
objections: objections.length,
|
|
allowedClaims: claimProfile.allowedClaims.length
|
|
},
|
|
productTruth,
|
|
customerIntent,
|
|
objectionAndAnxietyMap: objections,
|
|
competitorPositioningMap: competitorMap,
|
|
amazonMarketplaceIntelligence: {
|
|
coverage: amazon.coverage,
|
|
sourceStatus: amazon.sourceStatus,
|
|
aggregate: amazon.aggregate,
|
|
themes: amazon.themes || []
|
|
},
|
|
redditSocialListeningProfile: {
|
|
coverage: reddit.coverage,
|
|
sourceStatus: reddit.sourceStatus,
|
|
pulses: reddit.pulses || []
|
|
},
|
|
reviewVoiceOfCustomerProfile: {
|
|
publicReviewSignals: publicEvidence.reviewSignals || [],
|
|
usableLanguage: (publicEvidence.reviewSignals || []).map((row: any) => row.theme),
|
|
restrictedLanguage: [
|
|
...(publicEvidence.contaminationExclusions || []),
|
|
...brief.claimsToAvoid
|
|
]
|
|
},
|
|
claimAndComplianceProfile: claimProfile,
|
|
offerArchitecture,
|
|
pdpGenerationBrief,
|
|
generationInputs: {
|
|
contentFile: `data/content/products/${sku}.json`,
|
|
publicEvidenceFile: `data/intelligence/${sku}/public-internet-evidence.json`,
|
|
amazonFile: `data/intelligence/${sku}/amazon-reviews.json`,
|
|
redditFile: `data/intelligence/${sku}/reddit-pulse.json`,
|
|
competitorFile: `data/intelligence/${sku}/competitor-comparison.json`,
|
|
marketSynthesisFile: `data/intelligence/${sku}/market-insight-synthesis.json`,
|
|
claimsFile: `data/intelligence/${sku}/claims-evidence.json`
|
|
}
|
|
}
|
|
|
|
writeJson(join(profileRoot, `${sku}.json`), profile)
|
|
writeJson(join(intelRoot, sku, 'research-profile.json'), profile)
|
|
summary.push({
|
|
sku,
|
|
product: content.name,
|
|
status: profile.status,
|
|
sourceCoverage: profile.sourceCoverage,
|
|
mainOfferAngle: pdpGenerationBrief.mainOfferAngle,
|
|
profileFile: `data/research-profiles/${sku}.json`
|
|
})
|
|
}
|
|
|
|
writeJson(join(outRoot, 'top3_research_profile_manifest.json'), {
|
|
generatedAt,
|
|
ready: true,
|
|
purpose: 'Complete top-3 PDP research profiles for content and offer generation. Matrixify/export handoff intentionally out of scope.',
|
|
counts: {
|
|
products: summary.length,
|
|
profiles: summary.length
|
|
},
|
|
products: summary
|
|
})
|
|
|
|
console.log(`Top 3 research profiles generated: ${summary.length}`)
|
|
|