Skip to main content

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/:

ComponentOutputPurpose
DealerRulesComponentacceptance/dealer_rules (Iceberg)Inference
DealerRulesMongoComponentacceptance/<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_rate off the configured attribute_stats asset, and zero-fill against the mapped attribute universe from attributes/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.