feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)

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>
This commit is contained in:
Omair Saleh
2026-06-02 18:20:17 +08:00
parent e470e113ee
commit 056c47581f
3302 changed files with 391779 additions and 854 deletions
+139
View File
@@ -0,0 +1,139 @@
#!/usr/bin/env bun
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { dirname, join } from 'path'
const root = process.cwd()
const outJson = join(root, 'data', 'intelligence', 'COLLAGEN-NPD', 'collagen-market-report.json')
const outMd = join(root, 'data', 'handoff', 'collagen-market-reverse-engineering-report-2026-05-21.md')
const summaryPath = join(root, 'data', 'intelligence', 'COLLAGEN-NPD', 'scrape-tranche-summary.json')
const generatedAt = new Date().toISOString()
function readJson(path: string, fallback: any = null) { return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, '')) : fallback }
function write(path: string, text: string) { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, text, 'utf8') }
function writeJson(path: string, data: any) { write(path, JSON.stringify(data, null, 2) + '\n') }
const summary = readJson(summaryPath, {})
const counts = summary.aggregateCounts || {}
const report = {
sku: 'COLLAGEN-NPD',
title: 'UK collagen market reverse-engineering: flavour, mixability and retention',
generatedAt,
sourceBase: {
latestVisibleItems: summary.totalItems || 0,
cumulativeApifySpendUsd: summary.cumulativeSpendUsd || 0,
latestVisibleUsageUsd: summary.latestVisibleUsageUsd || 0,
sources: [
'Amazon UK competitor review scrape from seeded collagen ASINs',
'Trustpilot reviews for Feel and Ancient & Brave',
'Reddit starter search rows, retained as directional/noisy only',
'Existing competitor PDP captures for Feel, Ancient & Brave, Heights, Wild Nutrition, Dirtea, Ethical Nutrition and Puro Labs',
'JV migration truth for JV-COLLAGEN400 kept separate from upcoming NPD decisions'
],
signalCounts: counts
},
executiveDecision: {
recommendation: 'Launch R&D around unflavoured first, with fresh/mixed berry as the strongest flavoured challenger; keep vanilla as a secondary test cell and hold chocolate unless taste-panel/review evidence improves.',
reason: 'The market is not just buying collagen dosage. The friction is daily use: taste, smell, dissolving, perceived value and whether the customer can keep taking it every day. Unflavoured owns the widest routine use cases; berry has appetite appeal but higher flavour-fatigue/sweetness risk; vanilla/chocolate are not yet supported enough by this corpus.',
positioning: 'A collagen ritual people can actually stick with: clean taste, no fishy smell, mixes smoothly into existing daily drinks, clear servings and flexible subscription control.'
},
flavourIntelligence: [
{
flavour: 'Unflavoured',
verdict: 'Lead candidate',
whyItCanWin: [
'Best fit for coffee, smoothies, tea, oats and other existing habits.',
'Avoids the sweetness/artificial-flavour trap.',
'Most flexible for subscription retention because it does not force one taste every day.'
],
risksToEngineerOut: [
'Marine/fishy smell or aftertaste must be actively controlled.',
'Must prove it dissolves cleanly in hot drinks and does not clump in cold drinks.',
'Cannot be positioned as tasteless unless QA and reviews support that exact claim.'
],
productRequirement: 'Neutral odour, low/no aftertaste, hot-drink compatible, smoothie compatible, visible “how to mix” guidance.'
},
{
flavour: 'Fresh berry / mixed berries',
verdict: 'Strongest flavoured challenger',
whyItCanWin: [
'Berry is already visible in high-volume competitor positioning and Amazon review language.',
'Works naturally for water/cold drink use cases.',
'Can mask marine notes better than unflavoured if sweetness is controlled.'
],
risksToEngineerOut: [
'Too sweet, synthetic or sour becomes a daily-use problem.',
'Berry can create flavour fatigue on subscription if it is the only option.',
'Bad aftertaste is more damaging because customers expect a pleasant drink.'
],
productRequirement: 'Adult fresh berry profile, light sweetness, no candy notes, no lingering marine finish.'
},
{
flavour: 'Vanilla',
verdict: 'Secondary test cell',
whyItCanWin: [
'Good fit for coffee, oats and creamy smoothie rituals.',
'Can feel softer/premium if not over-sweetened.'
],
risksToEngineerOut: [
'Evidence volume is currently weak versus unflavoured and berry.',
'Artificial vanilla or protein-powder notes could damage repeat use.'
],
productRequirement: 'Low sweetness, no protein-shake aftertaste, test specifically in coffee and oats.'
},
{
flavour: 'Chocolate / cocoa',
verdict: 'Hold for now',
whyItCanWin: [
'Could work for milk/smoothie usage and dessert-like positioning.'
],
risksToEngineerOut: [
'Very little current evidence in the scraped corpus.',
'Cocoa can expose powdery/chalky texture and requires more sweetness management.'
],
productRequirement: 'Do not prioritise until review scraping or taste panels show demand.'
}
],
mixabilityAndTexture: {
marketRule: 'Mixability is a retention feature, not a technical footnote.',
requiredProofPoints: [
'Dissolves easily in hot drinks.',
'No clumps or gritty/chalky finish in cold drinks when prepared as instructed.',
'Clear instructions for water, coffee, smoothie and oats.',
'Scoop/serving guidance that makes daily use feel simple.'
],
riskLanguageToMonitor: ['clumpy', 'chalky', 'gritty', 'powdery', 'does not dissolve', 'fishy', 'aftertaste', 'too sweet', 'artificial']
},
retentionAndSubscription: {
finding: 'Retention is driven by habit fit plus subscription trust. Reviews repeatedly surface ease of repeat orders, delivery/support, skip/cancel control and perceived value.',
buildIntoOffer: [
'30-day serving clarity: avoid half-empty tub / short-duration disappointment.',
'Subscribe-and-save framed as flexible replenishment, not a trap.',
'Reminder before dispatch and easy skip/pause/cancel language.',
'Bundle route: unflavoured daily base plus flavoured occasional variant once validated.',
'Usage ritual: “one scoop in your morning coffee/smoothie” rather than vague wellness copy.'
]
},
positioningMap: [
{ territory: 'Daily ritual', use: 'Primary recommended territory', angle: 'Clean collagen that fits your existing morning drink.' },
{ territory: 'Beauty from within', use: 'Secondary', angle: 'Can be used if compliance-approved claims/evidence are in place.' },
{ territory: 'Joint/active ageing', use: 'Secondary segment', angle: 'Useful audience, but avoid unsupported therapeutic claims.' },
{ territory: 'Premium wellness', use: 'Packaging/brand layer', angle: 'Use quality and routine credibility, not vague luxury language alone.' }
],
customerPsychology: [
'Customers are sceptical of collagen outcomes, so taste/mixability is the first hurdle and proof/consistency is the second.',
'Bad first experience creates immediate churn: smell, aftertaste, clumps, sweetness and pack-value disappointment all create cancellation risk.',
'People want collagen to attach to something they already do. Coffee and smoothies are stronger habit anchors than “mix with water” alone.',
'Customers tolerate premium pricing when the serving count, pack fill and daily experience feel honest.'
],
recommendedNextActions: [
'Run internal taste/mixability panel with unflavoured, berry and vanilla across water, coffee, smoothie and oats.',
'Ask supplier for odour/aftertaste controls and solubility specs before choosing marine vs bovine route for powder.',
'Do one more targeted Reddit pass using specific Reddit URLs/threads, not broad search.',
'If budget allows, expand Amazon reviews to 500+ rows across the same ASIN set and add Ancient & Brave/Feel exact Amazon products if found.',
'Create PDP once flavour choice is validated: lead with daily ritual, smooth mixing, serving honesty and flexible subscription.'
],
finalAnswerForUmar: 'We should not finalise the flavour from ingredient logic. The market evidence says daily usability is the product: low/no smell, no aftertaste, smooth mixing, honest serving count and subscription flexibility. Recommendation is to prioritise unflavoured as the base R&D candidate, test fresh/mixed berry as the flavoured challenger, keep vanilla as a secondary test, and de-prioritise chocolate until stronger evidence appears.'
}
writeJson(outJson, report)
const md = `# ${report.title}\n\nGenerated: ${generatedAt}\n\n## Evidence base\n- Latest visible scraped items: ${report.sourceBase.latestVisibleItems}\n- Cumulative Apify spend: $${Number(report.sourceBase.cumulativeApifySpendUsd).toFixed(4)}\n- Sources: Amazon UK reviews, Trustpilot, Reddit starter rows, competitor PDP captures, JV current collagen truth kept separate.\n\n## Executive decision\n${report.executiveDecision.recommendation}\n\n**Why:** ${report.executiveDecision.reason}\n\n**Positioning:** ${report.executiveDecision.positioning}\n\n## Flavour recommendation\n\n1. **Unflavoured — lead candidate**\n - Wins because it can attach to coffee, smoothies, tea and oats.\n - Must prove neutral smell, low/no aftertaste and smooth mixing.\n\n2. **Fresh/mixed berry — strongest flavoured challenger**\n - Best for water/cold drink occasions.\n - Must avoid candy sweetness, synthetic notes and flavour fatigue.\n\n3. **Vanilla — secondary test cell**\n - Useful for coffee/oats/creamy rituals.\n - Current evidence is weaker than unflavoured and berry.\n\n4. **Chocolate — hold**\n - Not enough evidence yet; higher risk of powdery/chalky perception.\n\n## Product requirements\n${report.mixabilityAndTexture.requiredProofPoints.map(item => `- ${item}`).join('\n')}\n\n## Retention/subscription requirements\n${report.retentionAndSubscription.buildIntoOffer.map(item => `- ${item}`).join('\n')}\n\n## Customer psychology\n${report.customerPsychology.map(item => `- ${item}`).join('\n')}\n\n## Recommended next actions\n${report.recommendedNextActions.map(item => `- ${item}`).join('\n')}\n\n## Final answer for Umar\n${report.finalAnswerForUmar}\n`
write(outMd, md)
console.log(`Wrote ${outJson}`)
console.log(`Wrote ${outMd}`)