Files
justvitamin/scripts/extract-flavor-signal.ts
Omair Saleh 056c47581f 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>
2026-06-02 18:50:09 +08:00

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'}`)