Files
Andres Resiri 70daeb214a
CI / Quality gate (push) Has been cancelled
Initial project commit for Coolify staging
2026-08-03 10:45:41 -04:00

326 lines
15 KiB
JavaScript

/**
* 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");