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>
47 lines
4.1 KiB
TypeScript
47 lines
4.1 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { intelPath, listProducts, readJson, sourcePath, sourceStatus, writeJson, now, loadCompetitorConfig } from './lib/intel-utils'
|
|
|
|
const flavourKeywords = ['chocolate', 'vanilla', 'berry', 'strawberry', 'unflavoured', 'unflavored', 'citrus', 'coffee', 'cocoa', 'sweet', 'chalky', 'aftertaste', 'mix']
|
|
const competitorDocs = loadCompetitorConfig().map((c: any) => readJson(sourcePath('competitor-pdps', `${String(c.brand).toLowerCase().replace(/&/g, 'and').replace(/[^a-z0-9]+/g, '-')}.json`), null)).filter(Boolean)
|
|
const snippets = competitorDocs.flatMap((doc: any) => (doc.pages || []).map((p: any) => p.text || '')).join(' ').toLowerCase()
|
|
const found = flavourKeywords.filter(k => snippets.includes(k))
|
|
const sourceStatuses = { amazon: sourceStatus('amazon'), trustpilot: sourceStatus('trustpilot'), reddit: sourceStatus('reddit'), competitorPdp: sourceStatus('competitor-pdps') }
|
|
const flavorSignals = [
|
|
{ rank: 1, flavor: 'Chocolate / cocoa', chalkiness: found.includes('chalky') ? 'language detected; quantify with review ingestion' : 'pending review evidence', sweetness: found.includes('sweet') ? 'language detected; quantify with review ingestion' : 'pending', aftertaste: found.includes('aftertaste') ? 'language detected; quantify with review ingestion' : 'pending', mixability: found.includes('mix') ? 'language detected; quantify with review ingestion' : 'pending', recommendation: 'Candidate to validate against Amazon/Trustpilot/Reddit review language before final recommendation.', status: 'needs_review_ingestion' },
|
|
{ rank: 2, flavor: 'Vanilla', chalkiness: 'pending', sweetness: 'pending', aftertaste: 'pending', mixability: 'pending', recommendation: 'Baseline flavour candidate; validate sweetness and aftertaste complaints.', status: 'needs_review_ingestion' },
|
|
{ rank: 3, flavor: 'Berry / red fruit', chalkiness: 'pending', sweetness: 'pending', aftertaste: 'pending', mixability: 'pending', recommendation: 'Differentiation candidate only if external reviews show demand for lighter fruit flavour.', status: 'needs_review_ingestion' },
|
|
{ rank: 4, flavor: 'Unflavoured', chalkiness: 'pending', sweetness: 'not applicable', aftertaste: 'pending', mixability: 'pending', recommendation: 'Must be benchmarked for coffee/smoothie use and mixability complaints.', status: 'needs_review_ingestion' }
|
|
]
|
|
|
|
writeJson(intelPath('COLLAGEN-NPD', 'flavor-intelligence.json'), {
|
|
sku: 'COLLAGEN-NPD',
|
|
name: 'Collagen NPD market intelligence brief',
|
|
generatedAt: now(),
|
|
generatedBy: 'extract-flavor-signal.ts',
|
|
n: competitorDocs.length,
|
|
sourceStatus: sourceStatuses,
|
|
detectedLanguage: found,
|
|
competitorScope: loadCompetitorConfig().map((c: any) => ({ brand: c.brand, domain: c.domain })),
|
|
flavorSignals,
|
|
evidenceGaps: ['Amazon competitor collagen reviews', 'Trustpilot competitor collagen reviews', 'Reddit collagen flavour threads', 'Specific competitor collagen PDP URLs beyond homepage probes']
|
|
})
|
|
|
|
for (const product of listProducts()) {
|
|
const sku = product.sku
|
|
const relevant = /collagen|protein|powder|effervescent|chew|gumm|cider|coffee|tea/i.test(`${product.name || ''} ${product.metafields?.pdpKeyIngredients || ''}`)
|
|
writeJson(intelPath(sku, 'flavor-intelligence.json'), {
|
|
sku,
|
|
name: product.name || sku,
|
|
generatedAt: now(),
|
|
generatedBy: 'extract-flavor-signal.ts',
|
|
n: competitorDocs.length,
|
|
sourceStatus: sourceStatuses,
|
|
coverage: relevant ? 'catalogue_flavour_queue_relevant_product' : 'catalogue_flavour_queue_low_relevance',
|
|
detectedLanguage: found,
|
|
flavorSignals: relevant ? flavorSignals : [],
|
|
relevanceReason: relevant ? 'Product name/key ingredient suggests flavour, format, aftertaste, or mixability may matter.' : 'Tablets/capsules/softgels usually have low flavour-intelligence relevance; keep as empty queue unless source reviews show taste/mixability concerns.',
|
|
evidenceGaps: ['Review-level flavour/taste/mixability extraction is still required before final copy.']
|
|
})
|
|
}
|
|
console.log(`Catalogue flavour signal updated for ${listProducts().length} products from ${competitorDocs.length} competitor docs; detected: ${found.join(', ') || 'none'}`)
|