/** * Post-extraction validator for retail store cohorts. * * Answers: "Are we exhaustive? Should we suspect a gap?" * * Three independent checks per brand: * 1. Internal consistency — does the source's own count match ours? * 2. Authoritative cross-check — does a manually-curated expected * count agree with ours? (Wikipedia / press / annual report) * 3. Coverage heuristics — coords within country, no duplicate IDs, * reasonable city/state distribution, no city with suspiciously few * stores given metro size. * * Used by `scrapers/validate-stores.ts`. */ import type { RetailStore } from "../retail.js"; export interface BrandExpectation { readonly brand: string; /** Source's own claim if available (e.g. Westside API returns store_count). */ readonly sourceClaimedCount?: number; /** * Manually-curated count from authoritative source (Wikipedia / annual report). * Include the citation in `notes`. */ readonly authoritativeCount?: number; /** Acceptable deviation from authoritative (real-world: stores open/close monthly). */ readonly tolerancePct?: number; /** Citation for the authoritative count. */ readonly notes?: string; /** Cities the brand definitely has at least one store in (per public press). */ readonly expectedCities?: readonly string[]; /** * Major metros for the metro-coverage heuristic. Defaults to India's six * metros — set this when validating a brand in another region. */ readonly expectedMetros?: readonly string[]; /** * Bounding box for the coordinate-plausibility check. Defaults to India — * set this when validating a brand in another region. */ readonly geoBounds?: { readonly minLat: number; readonly maxLat: number; readonly minLng: number; readonly maxLng: number; }; } export interface BrandValidationReport { readonly brand: string; readonly extracted: number; readonly sourceClaimedCount: number | null; readonly authoritativeCount: number | null; readonly internalConsistency: "match" | "mismatch" | "no-claim"; readonly authoritativeConsistency: "match" | "low" | "high" | "no-reference"; readonly checks: { readonly uniqueIds: { ok: boolean; total: number; unique: number; duplicates: readonly string[]; }; readonly coordsInBounds: { ok: boolean; outOfBounds: number; total: number; sample: readonly string[]; }; readonly cityDistribution: { ok: boolean; topCities: readonly { city: string; count: number; }[]; suspiciousCities: readonly string[]; }; readonly expectedCitiesPresent: { ok: boolean; missing: readonly string[]; present: readonly string[]; }; }; readonly confidence: "high" | "medium" | "low"; readonly recommendations: readonly string[]; } export declare function validateBrand(stores: readonly RetailStore[], expectation: BrandExpectation): BrandValidationReport; /** Format a report as a human-readable markdown string. */ export declare function formatReport(report: BrandValidationReport): string;