BMW UCP Source
BmwModelEntity field mapping
Status (2026-07-20): 11 of 14 entity fields are now extracted from the UCP model matrix. Remaining 3 (
model_year,base_price,m_performance_flag) are BQ-only — no UCP path identified.body_type_namehas a UCP code (bodyStyle) mapped tobody_type_codebut no long-form name.
UCP source: prod.ucp.bmw.cloud/model-matrices
Legend: ✓ = direct mapping · — = not available · ❌ fmt = format differs · ⚠️ = uncertain/escalated
Note on UCP extraction: BmwModelEntity records are built directly in extract_models (ucp.py) — no Pydantic alias subclass. Fields set by keyword arg from nested UCP response {series_name: {modelRanges: {range_code: {models: {model_code: {...}}}}}}. Two brand calls (bmwCar, bmwi) per locale, merged. Language for phrases lookup is derived from the request locale.
UCP sample (DE fixture, brand bmwCar): series=1, range=F70, models=61GE, 11GF
BQ samples (all i7, MY2026): 267S = i7 M70, 267R = i7 eDrive50, 267Q = i7 xDrive60
Manual check procedure: Same as inventory mapping — 3 samples per source compared by eye. ✓ = concept and path confirmed by human reviewer.
Extracted fields
| BQ column | Entity field | UCP source | UCP sample (61GE) | UCP sample (11GF) | BQ 1 (267S) | manual check | code note |
|---|---|---|---|---|---|---|---|
| code | model_code | top-level key in models dict | 61GE | 11GF | 267S | ✓ | |
| is_bmwi (BOOL) | brand | request param brand | bmwCar | bmwCar | false | ✓ | UCP: bmwCar/bmwi string; BQ: is_bmwi boolean |
| series | series_name | top-level series key | 1 | 1 | i7 | ✓ | |
| model_range | model_range_code | modelRanges key | F70 | F70 | G70 | ✓ | |
| series_code | series_code | model["series"] | 1 | 1 | i7 | ✓ | |
| name | model_name | model["phrases"][lang]["longDescription"] | BMW 123 xDrive | BMW 118d | i7 M70 | ✓ | locale language from request params; falls back to first available |
| bodystyle_code | body_type_code | model["bodyStyle"] | SH | SH | 02 | UCP uses letter codes (SH); BQ uses numeric (02) — different coding | |
| fuel_type | fuel_type | model["fuelType"] | O | D | E | ✓ | single-letter code |
| drive_train | drive_type | model["driveType"] | AR | VR | xDrive all-wheel drive | UCP uses 2-letter codes (AR, VR); BQ uses long-form strings | |
| horsepower | horsepower | model["technicalData"]["enginePower"] | 150 | 110 | 650 | ✓ | string→float coercion |
| sports_flag (BOOL) | sports_flag | model["sportsFlag"] | N → false | N → false | false | ✓ | Y/N → bool |
Not available from UCP model matrix
| BQ column | Entity field | BQ 1 (267S) | code note |
|---|---|---|---|
| year | model_year | 2026 | BQ only |
| body_style | body_type_name | Sedan | BQ only (UCP has code bodyStyle, not long-form name) |
| price | base_price | 168500 | BQ only |
| m_performance_flag (BOOL) | m_performance_flag | true | BQ only |
Notes
- UCP bmwCar vs bmwi series_name / model_range_code conflict: The UCP catalog is queried once per brand per locale (
bmwCarandbmwi). For EV variants, the samemodel_codeappears in both catalogs with different values:bmwiappends anEsuffix tomodel_range_code(e.g.G26EvsbmwCar'sG26). Stripping the trailingEfrom a bmwi range code yields the bmwCar equivalent.series_namealso diverges: bmwCar assigns M-performance models to their numeric or X series (e.g."3","X"), while bmwi classifies them as"M"or"i". A model can simultaneously be a numeric/X series and M-performance — the two catalogs are different perspectives on the same physical model. The tiebreak onmodel_range_codemin, thenseries_namemin, is arbitrary; it consistently picks the bmwCar row for EV variants (shorter range code sorts lower) and the non-M/i classification for M-performance models (digits and uppercase letters sort beforeM/i). seriesvsseries_codein BQ: identical for i-models (i7). For standard numeric series likely match too — to confirm with a broader sample.
BmwFeatureEntity field mapping (UCP)
Status (2026-07-03):
extract_features_catalog(UCP details) andextract_features_pricing(UCP pricing) are live.family_typeis on the entity but itsmodel_to_visible_featuresextractor is pending.
UCP sources: prod.ucp.bmw.cloud model details endpoint · pricing endpoint
Note on extraction: BmwFeatureEntity records are built directly in extractor functions — there is no Pydantic alias subclass. extract_features_catalog handles UCP details; extract_features_pricing handles UCP pricing. Fields populated depend on the source — see the field matrix.
Composite primary key: feature_code + model_code
UCP model details samples (IE fixture):
| feature_code | name | feature_type | family_code | is_standard | description |
|---|---|---|---|---|---|
PIP6H | PIP6H | I | SPPNT | false | Fashion Grey solid |
P0C6R | P0C6R | P | MET | false | BMW Individual Frozen Portimao Blue |
UCP IE fixture:
namefield returns the option code itself (not a localised string) — locale artifact of the IE test environment.
Field × source matrix (UCP columns)
| Entity field | BQ column | UCP details path | UCP pricing path | manual check | code note |
|---|---|---|---|---|---|
| feature_code | code | response key (option code) | priceTreeNode.subNodes[*].key | ✓ | codes are 5-char alphanumeric (S05DM, P0C6R); format consistent across all sources |
| model_code | product_code | request param model_id | request param model_id | ✓ | BQ uses product_code column (not model_code) |
| brand | — | request param brand | request param brand | ✓ | not in BQ raw_feature_metadata — must be set from request context |
| name | name | {code}.option | — | ✓ | EU fixture (UCP IE): raw option code; BQ: English — locale differs, concept same |
| description | long_description | {code}.phrases.description or .longDescription | — | ✓ | BQ long_description empty for service-type options (S08S*) |
| feature_type | type | {code}.type | — | ✓ | all sources use same single-letter codes (S, F, I, P) — format consistent ✓ |
| family_code | family_code | {code}.familyCode | — | ✓ | BQ: empty string for service options; concept same |
| is_standard | is_standard | {code}.std | — | ✓ | all sources: boolean — format matches ✓ |
| price_gross | list_price | — | subNodes[*].effectivePrice.grossListPrice | ✓ | UCP IE all 0.0 (staging) |
| price_net | — | — | subNodes[*].effectivePrice.netListPrice | ✓ | not in BQ — only available from GL Products and UCP pricing sources |
| currency | — | — | metadata.currency | ✓ | not in BQ — EUR from UCP pricing metadata |