GM Dealer Source
The quantum_dealers source fetches the US dealer roster for the four GM brands
(Chevrolet, Buick, GMC, Cadillac) and feeds the dealers entity. It lives in the
us market of projects/ai_gm (src/ai_gm/sources/quantum_dealers.py).
Inventory references each dealer by BAC (dealer.bac). In transformed inventory this
is named dealer_code, and the consolidated tier resolves the dealer FK with the
asymmetric join on: ["dealer_code>bac", "brand"]. Dealer coverage therefore bounds
how many inventory rows resolve a dealer FK.
Current endpoint — quantum-dealer-locator
GM's brand sites expose a native dealer service that resolves dealer details directly by BAC — no geography, no 50-row cap:
GET https://www.<brand>.com/bypass/pcf/quantum-dealer-locator/v1/dealerId/<BAC>
Header: clientapplicationid: quantum (plus standard Accept / locale headers)
- Records path:
payload.dealers[], withaddress.postalCode,geolocation.{latitude,longitude},dealerName,dealerUrl, hours,makeCodes. - Granularity: one BAC returns one object per brand franchise at that location, each
with its own 5-digit
dealerCodeunder the shared 6-digitbac/id. - Row identity: one row per brand franchise (
bac + dealer_code + brand). Each brand's raw asset enumerates BACs from its brand inventory raw asset, fetches dealer details by BAC, discovers the brandmakeCodefromquantum_dealer_locator.jswith a static fallback, and extracts the matching franchise row. - Fetch tolerance: this is a high-cardinality per-BAC fan-out; the raw asset
allows up to 1% terminal BAC fetch failures before blocking downstream tiers.
Failed BACs still surface in
fetch_attemptsmetadata for follow-up. - Current FK: consolidated inventory joins
inventory.dealer_codetodealers.bacand also joins onbrand, so inventory does not need to carry the 5-digit franchisedealerCode. - A latlong+radius variant also exists (the current GMC site's "locate a dealer").
In the verification, this endpoint resolved 1,539 of 1,540 missing BACs (1 dead BAC), every one with real coordinates.
Retired endpoint — Tekion AEC-CP integrations API
POST https://<brand>.aecloud.io/api/aec-cp-integrations-api/p/v1/integrations/locate-dealers
Body: {"zipCode": "<zip>", "radius": 100}
- Records path:
data.locatorResult[](each element wrapsvendor). - Identity:
vendor.id— the 6-digit BAC, matching inventorydealer.bac.vendor.vendorCodeis a separate 5-digit GM-internal code — do not use it. - Auth: none; this is the one GM endpoint that needs no
clientheader. - Hard limits (verified live): requires a
zipCode; caps at 50 vendors per call; no national/unfiltered form.
This is the same Tekion AEC-CP platform that serves inventory and configurator.
Fan-out — hardcoded national zip sweep
fetch_dealers sweeps ai_core.geo.US_NATIONAL_ZIP_SWEEP (58 zips, one call
each at 100-mile radius, DEFAULT_RADIUS), then dedups vendors by vendor.id.
The zip list is a hand-picked set of metros and per-state cities — not derived from
where GM dealers actually are.
Coverage verification — 2026-07-22
A hands-on review measured what this sweep actually collects versus the full dealer
population implied by inventory. Method: materialized dealer_locator raw +
transformed locally (reproducing production exactly), took the distinct dealer BACs
that appear in that day's inventory, resolved the ones the sweep missed via the
quantum-dealer-locator endpoint, and
classified each miss against the sweep geometry.
The gap
| Segment | Dealers | Share |
|---|---|---|
| Inventory distinct dealer BACs (full population) | 3,836 | 100% |
| Captured by the current sweep | 2,296 | 59.8% |
| Missing — geographic hole (>100mi from every sweep anchor) | 1,371 | 35.7% |
| Missing — in-radius (50-cap truncation / other) | 168 | 4.4% |
~40% of dealers-with-inventory are never collected, and the shortfall is ~89% geographic holes, ~11% the 50-cap. The dealer locator has never had national coverage — the gap has existed since onboarding.
Root cause 1 — geographic holes (dominant)
The 58 zips at 100mi cover only 62.4% of the 40,567 populated continental-US ZIP centroids. Whole regions sit >100mi from any anchor (e.g. no Wyoming anchor; Las Vegas, and much of the Intermountain West / Great Plains uncovered). A deterministic greedy cover shows +89 zips reach 99% and +151 reach 100% CONUS coverage at 100mi.
Root cause 2 — hallucinated sweep zips (never vetted)
Three entries in US_NATIONAL_ZIP_SWEEP are not valid ZIP codes; the API
silently returns zero results, so those metros have had no coverage since
onboarding:
| Invalid (in sweep) | Intended metro | Valid replacement |
|---|---|---|
94101 | San Francisco, CA | 94102 |
23201 | Richmond, VA | 23221 |
14601 | Rochester, NY | 14602 |
Root cause 3 — the 50-per-call cap (secondary)
27 of 232 sweep calls (12%) returned exactly 50 (saturated), entirely Chevrolet (20 zips) and GMC (7) — Buick and Cadillac never have enough dealers within 100mi to saturate. Saturated calls are dense metros (NYC, Boston, Philadelphia, Chicago, Detroit, …). Overlap between neighboring metro circles recaptures much of the truncated set, so net cap loss is small (~168 dealers).
Decision
Dealers resolve through quantum-dealer-locator rather than the AEC-CP zip-sweep tier: dealer BACs
are enumerated from the inventory feed and each one resolved via the BAC endpoint — deterministic
~100% coverage, richer data, no holes/cap/invalid zips. The dealers asset is downstream of
inventory. The
old raw + transformed dealer assets are deleted outright (no freeze, no end_date, no
dual-source).
Row identity
- BAC is the inventory FK target. Dealers store the 6-digit BAC in
bacbecause inventory'sdealer.bacreferences that value. The 5-digit GM franchisedealerCodeis retained separately asdealer_code. - One row per brand franchise. Quantum returns one object per brand franchise at
a BAC (e.g.
330505→ 4dealerCodes, identical address/coords). The implemented extractor filters to the requesting brand'smakeCodeand emits one row keyed bybac + dealer_code + brand. Consolidated inventory joins bydealer_code>bacplusbrand, resolving each vehicle to the brand franchise for that BAC.
History
Old sweep records are discarded (too incomplete). Because dealer rosters are slow-moving, history is
synthesized: materialize today's real quantum partition, then carry it back over historical
partitions with the central fill_partition_job (ai_core/jobs/fill_partition.py, fill-partition
skill), which records filled_from provenance for visibility. Trusted-history-since = the cutover
date; earlier partitions are explicitly carried-forward, not independently observed.