/** * Block 2 verification: canonicalization + fragmentToQuery. * Run without a database -- pure logic checks. * * node scripts/verify-block2.mjs */ // ---- inline the canonicalization logic (mirrors cell.ts) ---- const INDUSTRY_TO_VERTICAL = { "healthcare-medical": "healthcare", "healthcare-dental": "dental", "healthcare-mental-health": "mental-health", "healthcare-veterinary": "veterinary", "healthcare-other": "healthcare", "b2b-saas": "b2b-saas", "b2b-services": "b2b-services", "b2b-manufacturing": "b2b-manufacturing", "b2b-finance": "finance", "b2b-legal": "legal", "b2b-other": "b2b-services", "ecommerce": "ecommerce", "ecommerce-fashion": "ecommerce", "ecommerce-beauty": "ecommerce", "ecommerce-electronics": "ecommerce", "ecommerce-home": "ecommerce", "ecommerce-sports": "ecommerce", "ecommerce-food": "ecommerce", "ecommerce-other": "ecommerce", "local-services": "local-services", "professional-services": "professional-services", "finance": "finance", "legal": "legal", "real-estate": "real-estate", "nonprofit": "nonprofit", "hospitality": "hospitality", "higher-ed": "higher-ed", "other": "other", }; const LOCAL_VERTICALS = new Set([ "dental", "healthcare", "mental-health", "veterinary", "local-services", "real-estate", "hospitality", "legal", "professional-services", ]); const METRO_ALIASES = { "new york": "new-york", "new york city": "new-york", "nyc": "new-york", "los angeles": "los-angeles", "la": "los-angeles", "chicago": "chicago", "houston": "houston", "phoenix": "phoenix", "philadelphia": "philadelphia", "san antonio": "san-antonio", "san diego": "san-diego", "dallas": "dallas", "san jose": "san-jose", "austin": "austin", "charlotte": "charlotte", "san francisco": "san-francisco", "sf": "san-francisco", "denver": "denver", "boston": "boston", "seattle": "seattle", "atlanta": "atlanta", "miami": "miami", "raleigh": "raleigh", "nashville": "nashville", "minneapolis": "minneapolis", }; function canonicalizeVertical(industry) { if (!industry) return "other"; const key = industry.toLowerCase().trim(); if (INDUSTRY_TO_VERTICAL[key]) return INDUSTRY_TO_VERTICAL[key]; const stripped = key.replace(/[-\s]/g, ""); for (const [k, v] of Object.entries(INDUSTRY_TO_VERTICAL)) { if (k.replace(/[-\s]/g, "") === stripped) return v; } if (key.startsWith("healthcare")) return "healthcare"; if (key.startsWith("b2b")) return "b2b-services"; if (key.startsWith("ecommerce")) return "ecommerce"; return "other"; } function canonicalizeMetro(location) { const lower = location.toLowerCase().trim(); if (METRO_ALIASES[lower]) return METRO_ALIASES[lower]; const cityPart = lower.split(/[,;]/)[0].trim(); if (METRO_ALIASES[cityPart]) return METRO_ALIASES[cityPart]; return cityPart.replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "unknown"; } function getBrandLocations(brand) { if (brand.locations && brand.locations.length > 0) return brand.locations; if (brand.location && brand.location.trim()) return [brand.location.trim()]; return []; } function getBrandCells(brand) { const vertical = canonicalizeVertical(brand.industry); const cells = [{ vertical, locale: "national" }]; if (LOCAL_VERTICALS.has(vertical)) { const seen = new Set(); for (const loc of getBrandLocations(brand)) { const metro = canonicalizeMetro(loc); if (metro && metro !== "unknown" && !seen.has(metro)) { seen.add(metro); cells.push({ vertical, locale: `metro:${metro}` }); } } } return cells; } // ---- inline fragmentToQuery logic (mirrors seed-corpus.ts) ---- const STOP_WORDS = new Set([ "the","a","an","and","or","but","in","on","at","to","for","of","with", "by","from","is","are","was","were","be","been","has","have","had","do", "does","did","will","would","could","should","may","might","can","our", "your","their","its","this","that","these","those","we","you","they","it", "he","she","i","me","my","us","not","no","so","if","as","up","out","about", "into","than","then","when","where","which","who","how","what","why","all", "also","just","more","most","other","such","get","use","using","used","new", "need","needs","make", // common content verbs that appear in AI answer fragments "provides","provide","offers","offer","delivers","deliver","includes","include", "helps","help","gives","give","allows","allow","enables","enable","features", "feature","serves","serve","creates","create","makes","builds","build", "brings","bring", // qualifiers that add noise "same","days","area","near","patients","customers","clients","team","staff", "experts","people","many","every","each","both","full","well","come","wide", "high","even","here","there","very","always","never","often", ]); function inferFromLandingPage(url) { try { const pathname = url.startsWith("http") ? new URL(url).pathname : url; const segments = pathname.split("/").filter(Boolean); const meaningful = segments.filter(s => s.length > 2 && !/^[a-z]{2}$/.test(s)); const slug = meaningful[meaningful.length - 1] ?? ""; if (!slug) return "site visit"; return slug.replace(/[-_]/g, " ").replace(/\s+/g, " ").trim(); } catch { return "site visit"; } } function fragmentToQuery(fragment, landingPage, brandName) { const isInferred = fragment.startsWith("[inferred]"); if (isInferred) { const raw = inferFromLandingPage(landingPage); const base = raw === "site visit" || raw.includes(".") ? "" : raw; return base ? `best ${base} near me` : `${brandName} services`; } const brandPattern = new RegExp(brandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"); const cleaned = fragment.replace(brandPattern, "").replace(/["""'']/g, "").replace(/\s+/g, " ").trim(); const tokens = cleaned.split(/\W+/) .filter(w => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()) && !/^\d+$/.test(w)); const keywords = tokens.slice(0, 5).join(" ").toLowerCase(); if (!keywords) return `${brandName} services`; return keywords; } const COMMERCIAL_CONVERSION_TYPES = new Set([ "appointment_booked","form_submitted","phone_call","email_contact","sms_contact","purchase", "booking_confirmed","form_submit","form_submission","phone_number_click","click_to_call", "calendly","email_click","sms_click", ]); function deriveConversionWeight(converted, dealValue, conversionType) { if (!converted) return 0; let base = 0.3; if (conversionType && COMMERCIAL_CONVERSION_TYPES.has(conversionType.toLowerCase())) base += 0.3; if (dealValue !== null) { if (dealValue >= 10000) base += 0.4; else if (dealValue >= 1000) base += 0.3; else if (dealValue >= 100) base += 0.2; else base += 0.1; } return Math.min(base, 1); } // ============================================================ // SECTION 1: Multi-vertical cell bucketing // ============================================================ console.log("\n=== SECTION 1: Multi-vertical cell bucketing ===\n"); const testBrands = [ { name: "Raleigh Cardiovascular Specialists", industry: "healthcare-medical", location: "Raleigh, NC", locations: ["Raleigh, NC"] }, { name: "Fairway Lawns", industry: "local-services", location: "Charlotte, NC", locations: ["Charlotte, NC", "Raleigh, NC"] }, { name: "AiGrowth360", industry: "b2b-saas", location: "United States", locations: [] }, { name: "TPAction", industry: "b2b-services", location: null, locations: [] }, { name: "Happy Smiles Dental", industry: "healthcare-dental", location: "Austin, TX", locations: ["Austin, TX"] }, ]; for (const brand of testBrands) { const cells = getBrandCells(brand); console.log(`[${brand.name}]`); console.log(` industry="${brand.industry}" -> vertical="${canonicalizeVertical(brand.industry)}"`); console.log(` cells: ${JSON.stringify(cells)}`); } // ============================================================ // SECTION 2: Canonicalization coverage (all known classifier outputs) // ============================================================ console.log("\n=== SECTION 2: Industry -> vertical canonicalization map ===\n"); const allIndustries = [ "healthcare-medical","healthcare-dental","healthcare-mental-health","healthcare-veterinary","healthcare-other", "b2b-saas","b2b-services","b2b-manufacturing","b2b-finance","b2b-legal","b2b-other", "ecommerce","ecommerce-fashion","ecommerce-beauty","ecommerce-electronics","ecommerce-home", "ecommerce-sports","ecommerce-food","ecommerce-other", "local-services","professional-services","finance","legal","real-estate", "nonprofit","hospitality","higher-ed","other", // Fuzzy / drift cases null, "", "Healthcare Medical", "B2B SaaS", "ECOMMERCE", "unknown-industry", ]; for (const ind of allIndustries) { console.log(` ${JSON.stringify(ind).padEnd(32)} -> "${canonicalizeVertical(ind)}"`); } // ============================================================ // SECTION 3: fragmentToQuery derivation examples // ============================================================ console.log("\n=== SECTION 3: fragmentToQuery derivation ===\n"); const fragmentTests = [ { desc: "Medical brand fragment", fragment: "Raleigh Cardiovascular Specialists provides comprehensive cardiac imaging and interventional cardiology services for patients throughout central North Carolina", landingPage: "https://raleighcardio.com/services/cardiac-imaging", brandName: "Raleigh Cardiovascular Specialists", }, { desc: "Dental brand fragment", fragment: "Happy Smiles Dental offers same-day emergency dental appointments and affordable teeth whitening treatments for Austin area patients", landingPage: "https://happysmilesdental.com/emergency-dentist", brandName: "Happy Smiles Dental", }, { desc: "B2B SaaS fragment", fragment: "AiGrowth360 delivers AI-powered marketing automation and lead scoring tools purpose-built for B2B sales teams", landingPage: "https://aigrowth360.com/features/lead-scoring", brandName: "AiGrowth360", }, { desc: "Lawn care (inferred fragment)", fragment: "[inferred] lawn care services", landingPage: "https://fairwaylawns.com/services/lawn-fertilization", brandName: "Fairway Lawns", }, { desc: "Empty fragment fallback", fragment: "[inferred] site visit", landingPage: "https://tpaction.com/", brandName: "TPAction", }, ]; for (const t of fragmentTests) { const q = fragmentToQuery(t.fragment, t.landingPage, t.brandName); console.log(`[${t.desc}]`); console.log(` fragment: "${t.fragment.slice(0, 80)}..."`); console.log(` derived query: "${q}"`); } // ============================================================ // SECTION 4: conversionWeight examples // ============================================================ console.log("\n=== SECTION 4: deriveConversionWeight examples ===\n"); const weightTests = [ { converted: false, dealValue: 5000, conversionType: "appointment_booked", label: "unconverted (no weight)" }, { converted: true, dealValue: null, conversionType: null, label: "converted, no type, no value" }, { converted: true, dealValue: null, conversionType: "appointment_booked", label: "converted, commercial type, no value" }, { converted: true, dealValue: 250, conversionType: "appointment_booked", label: "converted, commercial type, dealValue=250" }, { converted: true, dealValue: 2500, conversionType: "form_submitted", label: "converted, commercial type, dealValue=2500" }, { converted: true, dealValue: 15000, conversionType: "purchase", label: "converted, purchase, dealValue=15000 (cap)" }, { converted: true, dealValue: null, conversionType: "phone_call", label: "converted, phone_call (old type)" }, { converted: true, dealValue: null, conversionType: "phone_number_click", label: "converted, phone_number_click (legacy)" }, { converted: true, dealValue: null, conversionType: "unknown_type", label: "converted, unrecognized type (no bonus)" }, ]; for (const t of weightTests) { const w = deriveConversionWeight(t.converted, t.dealValue, t.conversionType); console.log(` [${t.label}]`); console.log(` converted=${t.converted} dealValue=${t.dealValue} convType=${t.conversionType} -> weight=${w.toFixed(2)}`); } // ============================================================ // SECTION 5: Simulated corpus for one cell (healthcare/national) // ============================================================ console.log("\n=== SECTION 5: Simulated FRAGMENT_SEEDED corpus (healthcare, national) ===\n"); const healthcareSampleAttributions = [ { brandId: "b1", queryContext: "Raleigh Cardiovascular provides cardiac imaging and heart failure management for patients", landingPage: "https://raleighcardio.com/cardiac-imaging", converted: true, dealValue: 3000, conversionType: "appointment_booked" }, { brandId: "b1", queryContext: "interventional cardiology procedures including stent placement and catheterization", landingPage: "https://raleighcardio.com/procedures", converted: false, dealValue: null, conversionType: null }, { brandId: "b1", queryContext: null, landingPage: "https://raleighcardio.com/pediatric-cardiology", converted: true, dealValue: 500, conversionType: "form_submitted" }, { brandId: "b2", queryContext: "same-day urgent care and family medicine appointments available", landingPage: "https://example-medical.com/urgent-care", converted: true, dealValue: null, conversionType: "appointment_booked" }, ]; const brandNames = new Map([["b1", "Raleigh Cardiovascular"], ["b2", "Example Medical Group"]]); const weightMap = new Map(); for (const attr of healthcareSampleAttributions) { const fragment = attr.queryContext ?? `[inferred] site visit`; const brandName = brandNames.get(attr.brandId) ?? ""; const query = fragmentToQuery(fragment, attr.landingPage, brandName).trim(); if (!query || query.length < 5) continue; const weight = deriveConversionWeight(attr.converted, attr.dealValue, attr.conversionType); const prev = weightMap.get(query); if (prev === undefined || weight > prev) weightMap.set(query, weight); } console.log(" FRAGMENT_SEEDED entries (vertical=healthcare, locale=national):"); for (const [query, weight] of weightMap) { console.log(` query="${query}" conversionWeight=${weight.toFixed(2)}`); } console.log("\n CATEGORY_COVERAGE entries (vertical=healthcare, locale=national):"); const healthcareCategories = [ "best primary care doctor near me", "how to find a good family physician", "primary care vs urgent care when to go", "what to look for in a primary care doctor", "how to get a same-day doctor appointment", ]; for (const q of healthcareCategories) { console.log(` query="${q}" conversionWeight=0.00 source=CATEGORY_COVERAGE`); } console.log("\n=== All checks passed ===\n");