Skip to main content

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_id is 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 columnEntity fieldC2B pathBQ backfillC2B sampleBQ ✓manual checkcode note
ag_codedealer_codeattributes.agDealerCode03910 · 02766 · 0011696360 · 56357 · 16815C2B 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_namenamenameAutohaus Karl + Co. GmbH & Co. KG · Auto-Leebmann GmbHHerb Chambers BMW · BMW of Westlake
address_line_one_primarystreetstreetCarl-Bosch-Str. 61168 Commonwealth Ave
city_primarycitycityWiesbaden · Würzburg · PassauBoston · Westlake · Nashua
state_primarystatestatenull (DE has no state)MA · OH · NHDE dealers have null state; BQ is US-only so always 2-letter code
zipcode_short_primarypostal_codepostalCode65203 · 97076 · 9403602134 · 44145 · 03060
lat_primarylatitudelat50.04272842.350171 · 41.475385C2B has higher decimal precision; concept and float format match
long_primarylongitudelng8.221340-71.128163 · -81.894526

Entity fields with no BQ equivalent (C2B-only)

Entity fieldC2B pathC2B samplenotes
outlet_keykey00131_2composite key: {distributionPartnerId}_{outletId}
country_codecountryCodeDEnot in BQ dealer table
phoneattributes.phone+49-611-278090not in BQ dealer table
homepage_urlattributes.homepagehttps://www.autohaus-krah-enders.de/not in BQ dealer table
portfolio_codeattributes.portfolioCodeMINInot in BQ dealer table. Transformed output column: portfolio_codes: List[Utf8] — see note below.
brand_categorycategoryMINInot 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.