BMW c2b-services dealer locator source
The c2b source fetches global dealer data from the BMW c2b-services localsearch API
(BMWSTAGE2_DLO). The API returns all brands (BMW, MINI, BMW Motorrad, Rolls-Royce)
without a brand filter — one POI record per brand per physical outlet. Because the
transformed tier keys dealers on dealer_code (the agDealerCode attribute), a
single dealer that carries multiple brands produces multiple candidate rows that collapse
into one entity. Brand-specific fields (address, phone, name, homepage) vary across
those rows.
BmwDealerEntity field mapping
Status (2026-07-03):
us_dealer_idis added to the entity; the other 6 BQ columns under "New fields to add" are not yet on the entity and have no extractor. Existing C2B mappings are live.
Sources: C2B (c2b-services locator API) | BQ source of truth: bmw-prod-309117.inventory.dealer_locator_data
Legend: ✓ = direct mapping · — = not available / not applicable · ✅ N = BQ check passed, N matching rows · ❌ fmt = concept maps but value format differs
Manual check procedure: For each row, sample values were pulled from the C2B fixture and BQ and compared by eye. The check validates that the mapped path yields values of the correct concept and format. A ✓ in the manual check column means the mapping was confirmed by a human reviewer. Code notes are for format differences only — alias fixes are visible in the git diff.
C2B sample (DE fixture): 00131_2 Autohaus Karl + Co. GmbH & Co. KG, Wiesbaden · 00120_8 Würzburg · 00116_1 Auto-Leebmann GmbH, Passau
BQ sample (US, 349 total): 34400 Herb Chambers BMW, Boston MA · 56357 BMW of Westlake, OH · 16815 Nashua NH
Existing entity fields
| BQ column | Entity field | C2B path | BQ backfill | ║ | C2B sample | BQ ✓ | manual check | code note |
|---|---|---|---|---|---|---|---|---|
| ag_code | dealer_code | attributes.agDealerCode | ✓ | ║ | 03910 · 02766 · 00116 | 96360 · 56357 · 16815 | ✓ | C2B agDealerCode = BMW AG global dealer code = BQ ag_code. C2B uses leading zeros (03910); BQ has none (96360). EU codes won't appear in US BQ — different markets. |
| dealer_name | name | name | ✓ | ║ | Autohaus Karl + Co. GmbH & Co. KG · Auto-Leebmann GmbH | Herb Chambers BMW · BMW of Westlake | ✓ | |
| address_line_one_primary | street | street | ✓ | ║ | Carl-Bosch-Str. 6 | 1168 Commonwealth Ave | ✓ | |
| city_primary | city | city | ✓ | ║ | Wiesbaden · Würzburg · Passau | Boston · Westlake · Nashua | ✓ | |
| state_primary | state | state | ✓ | ║ | null (DE has no state) | MA · OH · NH | ✓ | DE dealers have null state; BQ is US-only so always 2-letter code |
| zipcode_short_primary | postal_code | postalCode | ✓ | ║ | 65203 · 97076 · 94036 | 02134 · 44145 · 03060 | ✓ | |
| lat_primary | latitude | lat | ✓ | ║ | 50.042728 | 42.350171 · 41.475385 | ✓ | C2B has higher decimal precision; concept and float format match |
| long_primary | longitude | lng | ✓ | ║ | 8.221340 | -71.128163 · -81.894526 | ✓ |
Entity fields with no BQ equivalent (C2B-only)
| Entity field | C2B path | C2B sample | notes |
|---|---|---|---|
| outlet_key | key | 00131_2 | composite key: {distributionPartnerId}_{outletId} |
| country_code | countryCode | DE | not in BQ dealer table |
| phone | attributes.phone | +49-611-278090 | not in BQ dealer table |
| homepage_url | attributes.homepage | https://www.autohaus-krah-enders.de/ | not in BQ dealer table |
| portfolio_code | attributes.portfolioCode | MINI | not in BQ dealer table. Transformed output column: portfolio_codes: List[Utf8] — see note below. |
| brand_category | category | MINI | not in BQ dealer table. Transformed output column: brand_categories: List[Utf8] — see note below. |
Note on brand_categories / portfolio_codes
The c2b-services API returns dealers for all BMW Group brands (BMW, MINI, BMW Motorrad, Rolls-Royce) without brand filtering — brand-level filtering is intentionally deferred to the transformed tier. A single dealer may appear under multiple brand entries with the same dealer_code but different brand_category/portfolio_code values (e.g. dealer 25916 appears as both BMW and MINI). Because these represent genuinely distinct brand affiliations for a multi-brand dealer rather than a data conflict, the transformed tier aggregates them into sorted distinct lists: brand_categories and portfolio_codes are List[Utf8] columns in the Iceberg output. When a dealer sells only BMW, the list contains a single element ["BMW"]. When it sells both BMW and MINI the list is ["BMW", "MINI"] (sorted alphabetically — BMW sorts before MINI).
Known data quality issues
city — city name varies across brand-outlet rows for the same dealer code
Observed: 2026-07-08, dealer_code=01113 (ZA), dealer_code=00827 (BE), dealer_code=04121 (CH)
The city field is the dealer's physical city, sourced from the top-level "city" key of
each POI record. Because the same dealer_code can appear as separate POI records for BMW
and MINI outlets, the city value may differ across those rows in several patterns: administrative-level naming
inconsistency ("Roodepoort" for BMW vs "Johannesburg" for MINI at dealer_code=01113 ZA —
Roodepoort is a suburb within the City of Johannesburg Metropolitan Municipality, so both names
refer to the same metro area at different granularities); multilingual variation ("Bruxelles"
vs "Brussels" at dealer_code=00827 BE); or genuinely different municipalities ("Zürich"
vs "Opfikon" at dealer_code=04121 CH — Opfikon is an independent municipality in the
district of Bülach, not a suburb of Zürich city).
The root cause is that key_fields=["dealer_code"] collapses multiple distinct brand-outlet
POIs — each with its own outlet key — into one entity, while the address fields belong to
individual outlets. The correct fix is to widen the entity key to include the outlet key, or
to explicitly accept per-code address variation; this is a structural grain decision affecting
all 32 BMW markets and requires human sign-off before any change to sources/registry.py.
state — state/region varies across brand-outlet rows for the same dealer code
Observed: 2026-07-08, dealer_code=01113 (ZA), dealer_code=26015 (PT), dealer_code=04121 (CH)
The state field is the state, province, or region component of a dealer's physical address,
sourced from the top-level "state" key in the c2b-services POI response. The conflict pattern mirrors city, with an additional wrinkle: the API sometimes populates
state with values at inconsistent administrative levels. At dealer_code=01113 ZA, one
brand record carries "Gauteng" (the province) while the other carries "Johannesburg" (a
city within that province) — Johannesburg is not a province name. At dealer_code=26015 PT,
one record carries "Lisboa" (a district name) while the other carries "Carnaxide" (a civil
parish in Oeiras municipality, which is not part of Lisbon municipality at all). At
dealer_code=04121 CH, "Zürich" is the canton name while "Opfikon" is a municipality
name. In each case the API is mixing administrative levels across brand records for the same
dealer code.
The same root cause as city applies — multi-brand POI collision on a dealer_code-only key.
A grain decision (per-dealer-code vs per-brand-outlet) is needed before this can be resolved.
street — street address varies across brand-outlet rows for the same dealer code
Observed: 2026-07-08, dealer_code=01440 (DE, 19 distinct values across 20 entities), dealer_code=01113 (ZA), dealer_code=00827 (BE)
The street field is the physical street address of a dealer location, sourced from the
top-level "street" key in the POI JSON (e.g. "Carl-Bosch-Str. 6"). Two conflict patterns
appear: (1) formatting variation — the same address appears with abbreviation differences
("Wasserburger Landstr. 18" vs "Wasserburger Landstrasse 18" in DE), umlaut vs ASCII
("Schönbrunner Straße 218" vs "Schönbrunner Strasse 218" in AT), or punctuation
differences ("Via Pinerolo 72/A" vs "Via Pinerolo, 72/A" in IT); (2) genuinely distinct
addresses — BMW and MINI outlets at the same dealer_code are physically located on different
streets ("Ontdekkers Road" vs "Boundary Road & Hendrik Potgieter Road" at
dealer_code=01113 ZA). The DE market shows the highest conflict rate (19 distinct
street-value combinations across 20 entities for one dealer code).
The root cause is the same multi-brand POI grain collision as city and state. Resolving
systematically requires either widening key_fields or explicitly acknowledging address
variation at the per-dealer-code grain.
postal_code — postal code varies across brand-outlet rows for the same dealer code
Observed: 2026-07-08, dealer_code=01113 (ZA), dealer_code=26028 (PT), dealer_code=04121 (CH)
The postal_code field is the postal/ZIP code for a dealer's physical address, sourced from
the top-level "postalCode" key in the c2b-services POI response. Conflicts arise from the
same multi-brand POI collision as the other address fields: in ZA the difference reflects suburb-level granularity within the same metro
("1724" for BMW vs "2158" for MINI at dealer_code=01113 — both are Johannesburg metro
postcodes, 1724 for the Roodepoort suburb and 2158 for a neighboring Johannesburg suburb), in PT the codes are adjacent suburbs ("4470-157" vs "4471-909" in the Maia area); in CH
the codes cross a municipal boundary ("8050" — Zürich-Oerlikon, within Zürich city — vs
"8152" — Opfikon, a separate municipality).
The root cause is identical to city, state, and street: key_fields=["dealer_code"]
conflates multiple brand-outlet POIs into one entity. A grain decision is required before
this can be addressed structurally.
phone — phone number varies across brand-outlet rows for the same dealer code
Observed: 2026-07-08, dealer_code=01440 (DE, 322 distinct combinations across 343 entities), dealer_code=01113 (ZA), dealer_code=23267 (LU)
The phone field is the dealer's main contact telephone number, sourced from the nested path
attributes.phone in the c2b-services POI response. Two conflict patterns appear: (1)
brand-specific phone lines — BMW and MINI outlets sharing a dealer_code each carry a
dedicated number ("011 954 7200" for BMW vs "011 662 4700" for MINI at dealer_code=01113
ZA); (2) formatting variation within the same brand — the same number appears with different
separators, spacing, or country-code prefix ("26 44 20 21" vs "+352 26 44 20 21" at LU;
"01 59 907-0" vs "01 59907-0" at AT). The DE market shows the highest conflict count of
any field across all BMW markets (322 distinct phone-value combinations for a single
dealer code).
The root cause is the same multi-brand POI grain collision as the address fields. Until the
grain question is resolved, the values across brand-outlet rows for a given dealer_code
cannot be deterministically ranked.