#!/usr/bin/env bun import { existsSync, readdirSync } from 'fs' import { join } from 'path' import { intelPath, readJson, readPilotDoc, sourcePath, writeJson, now } from './lib/intel-utils' const root = process.cwd() const productsRoot = join(root, 'data', 'content', 'products') const library = readJson(sourcePath('claims', 'claims-library.json'), { claims: [] }) const productFiles = existsSync(productsRoot) ? readdirSync(productsRoot).filter(file => file.endsWith('.json')).sort() : [] function claimMatchesProduct(claim: any, product: any) { const sku = product.sku const name = String(product.name || '').toLowerCase() const ingredients = String(product.metafields?.pdpKeyIngredients || '').toLowerCase() const handle = String(claim.handle || '').toLowerCase() if ((claim.skus || []).includes(sku)) return true if (handle.startsWith('vitamin-d') && (name.includes('vitamin d') || ingredients.includes('vitamin-d'))) return true if (handle.startsWith('vitamin-k') && (name.includes(' k2') || ingredients.includes('vitamin-k'))) return true if (handle.startsWith('vitamin-c') && (name.includes('vitamin c') || ingredients.includes('vitamin-c'))) return true return false } function candidateThemes(product: any, sw: any) { const fromSw = (sw.items || []).slice(0, 7).map((item: any) => ({ rank: item.rank, theme: item.insight, source: `strengths-weaknesses rank ${item.rank}`, allowedUse: 'VoC/benefit copy only; do not use as clinically_shown_to without source-backed evidence.', status: 'not_a_clinical_claim' })) if (fromSw.length) return fromSw return (product.metafields?.pdpProductBenefits || []).slice(0, 7).map((theme: string, index: number) => ({ rank: index + 1, theme, source: 'product content benefits seed', allowedUse: 'Product/content seed only; do not use as clinically_shown_to without source-backed evidence and final approval.', status: 'not_a_clinical_claim' })) } let withClaims = 0 for (const file of productFiles) { const product = readJson(join(productsRoot, file), {}) const sku = product.sku || file.replace(/\.json$/, '') let aspects: any = { name: product.name || sku } let sw: any = { items: [] } try { const pilot = readPilotDoc(sku) aspects = pilot.aspects || aspects sw = pilot.sw || sw } catch (_) {} const matched = (library.claims || []).filter((claim: any) => claimMatchesProduct(claim, product)) if (matched.length) withClaims += 1 writeJson(intelPath(sku, 'claims-evidence.json'), { sku, name: aspects.name || product.name || sku, generatedAt: now(), generatedBy: 'extract-claims.ts', n: matched.length, complianceGate: matched.length ? 'source_backed_claims_available_for_review' : 'no_source_backed_claim_matched_for_product', sourceLibrary: 'data/sources/claims/claims-library.json', reviewRequired: true, claims: matched, candidateNonClinicalThemes: candidateThemes(product, sw) }) console.log(`${sku}: ${matched.length} source-backed clinical claims`) } console.log(`Claims evidence files: ${productFiles.length}`) console.log(`Products with matched claims: ${withClaims}`)