Dealer Rules
Infers per-dealer must-have (MH) and must-not-have (MNH) preferences from observed take rates, and syncs the option-code subset to the MongoDB collection the ordering app reads.
Two components, both writing under <oem>/<market>/acceptance/:
| Component | Output | Purpose |
|---|---|---|
DealerRulesComponent | acceptance/dealer_rules (Iceberg) | Inference |
DealerRulesMongoComponent | acceptance/<name> (no table) | MongoDB sync |
Inference
DealerRulesComponent classifies each (dealer_id, base_model_id, attribute_type, attribute_code) cell:
- take rate ≥
must_have_threshold→ MH - take rate ≤
must_not_have_threshold→ MNH - otherwise no rule is emitted
must_have_threshold and must_not_have_threshold are required config and must
be disjoint. A cell is only classified when its group observation count clears
NOBS_MIN
(ai_dagster.derived.dealer_rules_columns) and its rate is finite; otherwise the
cell is treated as no-signal rather than as a zero. Sentinel NO_* codes are never marked MH —
they encode absence.
Two rule paths, two universes
Attribute rules and option-code rules run through the same classifier, but draw their signal and their eligible universe from different places:
- Attribute rules read the recency-weighted
sales_hl90_take_rateoff the configuredattribute_statsasset, and zero-fill against the mapped attribute universe fromattributes/features. - Option-code rules compute an unweighted share of the dealer's vehicles
carrying each raw option code, from
consolidated/inventory×consolidated/features, and zero-fill against the raw option-code universe.
Each path renames its metric into demand_rate on the way in, so the classifier
reads one column that has no indication of its source. Because the two metrics are on
different scales, oc_must_have_threshold and oc_must_not_have_threshold are
required config: the option-code path always runs, and allowed_attribute_types
governs only the attribute path.
Zero-filling is what lets a never-observed code surface as MNH. It applies only to dealer×model pairs that already carry stats; pairs excluded upstream stay excluded. Eligibility is conditioned on national observation so model-year-transition options (configurable but not yet sold anywhere) don't flood in as zero-rate MNH candidates.
Configuration
Only the two attribute_stats keys are configurable — AttributeStatisticsComponent
has both a configurable name and configurable aggregation keys. Every other
upstream (attributes/features, consolidated/models, consolidated/inventory,
consolidated/features, consolidated/dealers) has a fixed key derived from
(oem, market).
type: ai_dagster.components.DealerRulesComponent
attributes:
start_date: "{{ start_date }}"
attribute_stats_asset: stellantis/us/attribute_stats/country_by_dealer_base_model
global_attribute_stats_asset: stellantis/us/attribute_stats/country_by_country_base_model
must_have_threshold: 0.92
must_not_have_threshold: 0.05
oc_must_have_threshold: 0.60
oc_must_not_have_threshold: 0.20
allowed_attribute_types: [PAINT_COLOR, ...]
overrides pins a rule by hand, winning over inference on key conflict:
overrides:
- dealer_filter: {dealer_code: "44394"}
attribute_type: TRANSMISSION_TYPE
attribute_code: MANUAL
rule: must_not_have
MongoDB sync
DealerRulesMongoComponent upserts the OPTION_CODE rules that carry a verdict
into collection, one document per (dealer_id, model_key, attribute_type, attribute_code) — the unique index it also ensures. The internal hashed
dealer_id is resolved to the OEM-facing identifier named by
dealer_id_field; an unresolvable dealer raises rather than writing a
null-keyed document.
type: ai_dagster.components.DealerRulesMongoComponent
attributes:
name: option_code_rules_mongo
start_date: "{{ start_date }}"
collection: dealer_rules
base_model_id_fields: [model_code, trim_identifier]
active_dealers_column: dc_active_first_seen
Setting active_dealers_column restricts the sync to dealers with that column
non-null on consolidated/inventory, and scopes retirement to them. Leaving it
unset syncs every dealer present in the rules frame.
Dealer locks and retirement
A document with isDealerSet: true has been edited in the app and is never
overwritten: the upsert filter carries isDealerSet: {$ne: true}, so a locked
document fails to match and the resulting duplicate-key error is counted as a
skip rather than a failure.
Every document written in a run is stamped with that run's sync_batch_id.
After upserting, any non-dealer-set document in scope whose sync_batch_id
differs is deleted — that is how a rule that went neutral, or a code that fell
out of the eligible universe, disappears. A document carrying no sync_batch_id
at all is retired the same way.
The connection comes from the mongo resource
(ai_dagster.resources.mongo.MongoResource), which resolves its URI from
MONGO_PPM_URI in prod and MONGO_PPM_URI_DEV on branch deployments.