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