#!/usr/bin/env bun import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' import { dirname, join } from 'path' const root = process.cwd() 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') } const apifyRaw = readJson(join(root, 'data', 'sources', 'apify', 'raw', 'top3-market-competitor-capped-apify.json'), {}) const apifyItems = Array.isArray(apifyRaw.items) ? apifyRaw.items : (Array.isArray(apifyRaw.items?.value) ? apifyRaw.items.value : []) const bySku = new Map(apifyItems.map((item: any) => [item.sku, item])) function sourceSkuAliases(sku: string) { return sku === 'JV-DEFF1000' ? ['JV-DEFF1000', 'JV-D1000'] : [sku] } const top3 = [ { sku: 'JV-TURMERIC500', name: 'Turmeric with BioPerine (Black Pepper Extract)', competitor: { brand: 'Feel', source: 'Apify capped scrape', url: 'https://wearefeel.com/en-us/products/feel-pro-turmeric', fallbackUsed: false, signals: [ 'Pro Turmeric leads with an outcome-led joint/mobility promise and a strong science wrapper.', 'The offer foregrounds 29 clinical studies, 100x higher bioavailability, 1 patented ingredient and a 31% improvement proof point.', 'The PDP sells subscription flexibility hard: discounted subscription, cancellation in two clicks, gifts/rewards and timeline milestones.', 'The formula uses Longvida Optimised Curcumin plus Boswellia, Bamboo, Quercetin, Ginger, Rosehip, Vitamin C and Vitamin D.' ], whitespace: [ 'Just Vitamins can win on simpler product truth: 95% curcuminoids and guaranteed 475mg curcuminoids per tablet.', 'Avoid copying disease/pain language; use strength, transparency and expectation-setting instead.', 'Add a clear comparison panel: headline mg vs actual curcuminoids vs BioPerine absorption support.' ] }, offer: 'Lead with potency transparency: 95% curcuminoids, 475mg curcuminoids per tablet and BioPerine, then set realistic routine expectations.', pdpDecisions: [ 'Hero should answer “what makes this turmeric strong/trustworthy?” rather than generic joint relief.', 'Comparison asset should show actual curcuminoids per tablet, not only 12,500mg headline turmeric.', 'FAQ should handle time-to-judge, no-effect minority and broken-tablet/replacement reassurance.', 'Do not use competitor-style anti-inflammatory or pain-relief promises without compliance approval.' ] }, { sku: 'JV-DEFF1000', name: 'Vitamin D Effervescent Tablets - 1000iu', competitor: { brand: 'Vitabiotics Immunace Fizz', source: 'Apify capped scrape', url: 'https://www.vitabiotics.com/collections/all-vitabiotics-products/products/immunace-fizz-effervescent-tablets', fallbackUsed: false, signals: [ 'Immunace Fizz leads with convenient, great-tasting effervescent orange drink format for people who dislike swallowing tablets.', 'The PDP positions 1000IU Vitamin D inside a broader 12-nutrient immune/vitality formula with Vitamin C, Zinc and Selenium.', 'The offer uses 3-for-2 and subscribe-and-save mechanics alongside free UK delivery.', 'The brand proof angle is “from the UK’s No.1 vitamin company”.' ], whitespace: [ 'Just Vitamins must make the drink format visually obvious: peach/passion fruit, one tablet in 200ml water.', 'Because the SKU is single-ingredient Vitamin D3, compete on simplicity and daily maintenance rather than broad multinutrient complexity.', 'Exclude swallow-tablet Amazon/category language from this PDP.' ] }, offer: 'Make the format the offer: a simple daily Vitamin D3 drink for low-sun routines, not another capsule/tablet.', pdpDecisions: [ 'Hero image/copy should show the tablet dissolving in water and name the peach/passion fruit flavour.', 'FAQ should explain this is for daily maintenance/support and not a treatment for diagnosed deficiency.', 'Offer module should compare “drink format” against capsules/tablets, not claim superiority over medical dosing.', 'Out-of-stock or availability status needs owner confirmation before final export.' ] }, { sku: 'JV-VISISOFT', name: 'Visisoft Original Tablets - 10mg', competitor: { brand: 'Healthspan OptiVision', source: 'Public web evidence; Apify Healthspan request returned no dataset item under cap', url: 'https://www.healthspan.co.uk/optivision/', fallbackUsed: true, signals: [ 'OptiVision leads with an advanced eye-health complex, lutein and zeaxanthin, omega 3 and “maintains normal vision” wording.', 'The competitor stacks 20mg lutein, 2000mcg zeaxanthin, vegan omega 3 with 250mg DHA and Vitamin A normal-vision support.', 'The offer uses subscribe-and-save, first-order discounting and free UK delivery thresholds.', 'Customer proof language references optometrist/eye-health outcomes, which must be treated carefully as review language, not a treatment claim.' ], whitespace: [ 'Just Vitamins should clarify Original 10mg: Lutein 10mg, Zeaxanthin 400mcg, Bilberry, Grape Seed and Vitamin A.', 'Use authorised Vitamin A normal-vision wording only; do not imply AMD, cataract or glaucoma treatment.', 'A comparison asset should show Original vs richer competitor stacks honestly, then position JV on simplicity/value/routine fit.' ] }, offer: 'Make ingredient-stack clarity the offer: Original 10mg lutein plus zeaxanthin, bilberry, grape seed and authorised Vitamin A normal-vision support.', pdpDecisions: [ 'Hero should be routine/normal-vision support, not medical improvement.', 'Comparison should clearly distinguish Original 10mg from Ultra/advanced competitor stacks.', 'FAQ should say effects are hard to prove and eye-health routines should be discussed with an optician/clinician where relevant.', 'Use optician/consultant language only as audience context, not as proof of efficacy.' ] } ] const summaryRows: any[] = [] for (const item of top3) { const amazon = readJson(join(intelRoot, item.sku, 'amazon-reviews.json'), {}) const reddit = readJson(join(intelRoot, item.sku, 'reddit-pulse.json'), {}) const publicEvidence = readJson(join(intelRoot, item.sku, 'public-internet-evidence.json'), {}) const apifyItem = sourceSkuAliases(item.sku).map(alias => bySku.get(alias)).find(Boolean) as any const competitor = { ...item.competitor, apifyCaptured: Boolean(apifyItem), apifyTitle: apifyItem?.title || null, apifyRunId: apifyRaw.runId || null, apifyDatasetId: apifyRaw.datasetId || null, apifyRequestCap: apifyRaw.requestCap || null } const synthesis = { sku: item.sku, name: item.name, generatedAt, generatedBy: 'synthesize-top3-market-insights.ts', sourceCoverage: { officialPdpFacts: publicEvidence.facts?.length || 0, publicReviewSignals: publicEvidence.reviewSignals?.length || 0, amazonSignals: amazon.themes?.length || 0, redditSignals: reddit.pulses?.length || 0, competitorSignals: competitor.signals.length, apifyCaptured: competitor.apifyCaptured, apifyFallbackUsed: competitor.fallbackUsed }, offer: item.offer, competitor, synthesis: { customerObjections: (reddit.pulses || []).slice(0, 4).map((row: any) => row.topic || row.theme), marketplacePressure: (amazon.themes || []).slice(0, 4).map((row: any) => row.theme), pdpDecisions: item.pdpDecisions, contentGuardrails: [ ...(publicEvidence.contaminationExclusions || []), ...(amazon.themes || []).filter((row: any) => /guardrail|contamination|not confirmed/i.test(`${row.status} ${row.theme}`)).map((row: any) => row.contentUse || row.evidence), 'Final copy still requires human content and compliance approval.' ] } } writeJson(join(intelRoot, item.sku, 'market-insight-synthesis.json'), synthesis) writeJson(join(intelRoot, item.sku, 'competitor-comparison.json'), { sku: item.sku, name: item.name, generatedAt, generatedBy: 'synthesize-top3-market-insights.ts', coverage: competitor.apifyCaptured ? 'apify_capped_competitor_pdp_plus_market_context' : 'public_competitor_context_apify_no_item', sourceStatus: { apify: competitor.apifyCaptured ? 'captured_under_request_cap' : 'attempted_under_request_cap_no_dataset_item', amazon: amazon.coverage || 'missing', reddit: reddit.coverage || 'missing' }, competitors: [ { rank: 1, brand: competitor.brand, domain: new URL(competitor.url).hostname, sourceCoverage: competitor.source, whitespace: competitor.whitespace.join(' '), status: competitor.apifyCaptured ? 'captured' : 'fallback_public_web_context', url: competitor.url, signals: competitor.signals } ], comparison: competitor.signals.map((signal: string, index: number) => ({ rank: index + 1, attribute: `${competitor.brand} signal ${index + 1}`, justVitamins: item.pdpDecisions[index] || item.offer, competitorEvidence: signal, recommendation: competitor.whitespace[index % competitor.whitespace.length] })), jvSignals: item.pdpDecisions.map((decision: string, index: number) => ({ rank: index + 1, signal: decision, source: 'market-insight-synthesis' })) }) summaryRows.push({ sku: item.sku, product: item.name, offer: item.offer, competitor: competitor.brand, apifyCaptured: competitor.apifyCaptured, amazonSignals: amazon.themes?.length || 0, redditSignals: reddit.pulses?.length || 0, output: `data/intelligence/${item.sku}/market-insight-synthesis.json` }) } writeJson(join(outRoot, 'top3_market_insight_synthesis.json'), { generatedAt, ready: true, apifyRun: { rawPath: 'data/sources/apify/raw/top3-market-competitor-capped-apify.json', status: apifyRaw.status, statusMessage: apifyRaw.statusMessage, requestCap: apifyRaw.requestCap, itemCount: apifyRaw.itemCount, computeUnits: apifyRaw.stats?.computeUnits }, counts: { products: top3.length, marketInsightFiles: top3.length, competitorComparisonFiles: top3.length }, products: summaryRows }) console.log(`Top 3 market insight synthesis ready: ${top3.length}`) console.log(`Apify captured items: ${summaryRows.filter(row => row.apifyCaptured).length}/${top3.length}`)