Config and Platform Setup in Practice
Track 2 - This Solution · Module 2.6
All analysts
Course home
Learning ObjectivesModule 2.6 · ~45 min
Name the top-level key groups of an mmx_tool_config YAML and state what each block controls downstream.
Read a platform_cat_parameter_ranges block correctly: alpha/beta as discrete candidate grids, differentiation carried by alpha.
Apply the naming-semantics rules (keyword-matched vs exact-name blocks, the source__platform__metric convention) without silent no-ops.
Wire the L1 → L2 → L3 contribution chain and the priority_order model-selection ranking into a new config, avoiding the classic pitfalls.
What the config is, and how many you write

The config is a .yaml file carrying the channel- and platform-level mappings, the transformation parameters with their search grids, and the bounds used from channel down to campaign level. It is the single steering file for a cell's model runs - module 1.5 covered why the parameters mean what they mean; this module is the practitioner's "how do I actually build one".

Per the Config Creation sheet, each brand/cell generally gets four config files: ST unconstrained, ST constrained, LT unconstrained, LT constrained - kept separate so a run can be revisited or re-iterated cleanly when something goes wrong. In practice you rarely write one from scratch: you copy the most recent config and modify it. That habit is efficient and dangerous in equal measure - the pitfalls list below is mostly a catalogue of what copied configs silently carry over.

The top-level keys cluster into six working groups:

GroupKeysControls
Scope & grain
year1, year2, date_col, period, group_cols, segment_col, train_splitWhich two modelling years, weekly grain (period 52) vs monthly (12), and the composite key defining one-model-per-combination (e.g. Company + Brand + Model)
Variable rosters
variables, distribution, promo, price, base_internal, base_external, base_list_lt_l2, exclude_Scaling, include_Scaling_LTWhich exact columns belong to each variable group, the base regressor set, and scaling opt-outs. Media columns are resolved via platform keywords instead of a list
Targets
target_col, sales_col, target_col_l2, target_col_l3The dependent variable per level - the L1 volume column, the value column for conversions, and the contribution-chained L2/L3 targets
Transforms & grids
transform_mapping, platform_cat_parameter_ranges, lambda_rangeWhich groups receive which derived transforms, and the per-platform adstock/saturation candidate grids
Constraints & special handling
neg_col, halo_column_keywordSign constraints and halo-column identification
Model selection
priority_orderThe ranked diagnostics that decide which candidate model wins

Some configs in the family also carry platform_categories_no_keywords (keyword exclusions per category), no_l2_list, use_spends, spend_keywords / impression_keywords, and the bounds dictionaries (media_bounds, Platform_bounds, Campaign_bounds, plus the LT variants with per-pillar bounds). Their absence from a given file is valid - do not "fix" a config by adding keys it never had.

transform_mapping: groups, not columns

Three derived transforms are mapped to variable groups - never to individual columns:

  • salience → applied to media and distribution (a share-of-presence style transform)
  • meaningful → applied to media only
  • differentiation → applied to media and price (relative-to-competition, e.g. price vs comp average)

The mapping resolves through the group-to-column lists (distribution:, price:, media via platform keywords). Consequence worth tattooing somewhere: adding a column to a group implicitly enrols it in every transform that names that group. Drop a new column into price: and it silently starts receiving the differentiation transform.

platform_cat_parameter_ranges: grids, not ranges

The production config carries 19 platform categories, each with an alpha_range (adstock decay) and a beta_range (Hill saturation shape): radio, digital_audio, digital_av, digital_display, digital_native, digital_video, digital_youtube, a brand-specific youtube split-out, digital_partnerships, a campaign-specific partnership split-out, ecomm_display, ecomm_search, ooh, paid_social, a brand-specific social split-out, print, search, tv, and the brand's halo platform. A header comment in the file states that the alpha/beta chosen at L1 are retained in L2 and L3 modelling - the grid search happens once.

Two structural facts that correct an older framing you may have met in module 1.5's early curriculum draft:

  • Despite the name, these are NOT [low, high] bounds. Each "range" is a discrete candidate grid - the tool only ever tries the listed values. A value between two grid points is never searched.
  • The beta grid is identical across every platform in this config (~14 candidates spanning below 1 to well above 1, so concave and S-shaped saturation are both searchable). All cross-platform differentiation is carried by alpha: TV gets the highest, widest, finest decay grid; video/AV/OOH sit in an upper-middle band; display/native/print/radio in the middle; search, ecomm and paid social get coarse fast-decay grids. Broadcast brand-building media decay slowly, performance/intent media decay fast.
Check with SMEShirsha
The curriculum's module 1.5 assessment premise "a narrower beta range implies stronger priors on that platform's saturation" does not match the production config (beta is one identical grid for all platforms; only alpha differentiates) - confirm the reality taught here and reword that assessment item.
Review flag: if a config turns up with one platform's beta grid hand-edited "to match TV", treat it as a red flag - beta is deliberately undifferentiated in this family, and a lone edited grid usually means someone misread grids as bounds.
Names and their semantics: keyword vs exact-match

Different config blocks interpret their strings differently, and mixing the two semantics is the classic silent failure:

  • Keyword-matched blocks: the platform-category names and halo_column_keyword are matched as substrings against ADS column names.
  • Exact-name blocks: neg_col, exclude_Scaling, base_internal, and the group lists must match column names verbatim - every character, including whitespace, casing, and underscores.

A keyword fragment placed in an exact-name list (or vice versa) simply matches nothing - no error, no warning, no constraint applied.

neg_col holds the variables constrained to negative coefficients: own price, promo price index, inflation, and competitor media spends. The competitor columns follow the source__platform__metric double-underscore convention - e.g. competitor_media_spends_data__tv__spend. Economically the block reads as: own price up, sales down; inflation up, sales down; competitor spend up, own sales down. There is no pos_col - media positivity is enforced by the tool by default.

halo_column_keyword identifies halo media columns (a keyword of the form digital_<brand>_halo) for dropping from the focal model's decomposition. Note the double-entry: the same halo token also appears as a platform category with its own parameter grids - the halo media is parameterised in the pipeline even though it is excluded here. Removing one entry without the other changes behaviour in non-obvious ways.

priority_order and the contribution chain

priority_order is the ranked list of diagnostics the tool uses to sort and select among candidate models - remember from module 2.1 that each level fits many candidates across the alpha/beta grid. The production sequence, exactly:

mape → mape_test → adj_r_squared → r_squared → dw_stat → shapiro_stat → shapiro_p_value → high_vif_count → low_t_stat_count → bp_stat → bp_pval
accuracy first (train then test MAPE), then fit, then residual diagnostics, then multicollinearity/significance counts, then heteroscedasticity - later entries act as tie-breakers

The list mixes lower-is-better and higher-is-better metrics; the tool encodes each metric's direction internally. What you must internalize: reordering this list changes which model gets selected. It is not documentation - it is a control.

The multi-level chain is wired through the target keys, and it is off by one on purpose: target_col names the L1 dependent variable (a sales-volume column); target_col_l2 points at Contribution_L1; target_col_l3 points at Contribution_L2. The dependent variable of level N is the contribution output of level N-1 - exactly the decomposition chain from module 2.1. Analysts routinely mis-set these by matching the level numbers (target_col_l2: Contribution_L2 looks right and is wrong).

The shape of the file (fabricated values)

The skeleton below shows the structure with a representative subset of keys. Every value is fabricated for teaching - grids, years, splits and column names alike. Never copy any of it into a real config; the real exhibit is referenced at the end of this module.

# ALL VALUES FABRICATED - structure illustration only
transform_mapping:
  salience: [media, distribution]
  meaningful: [media]
  differentiation: [media, price]

year1: 2021
year2: 2022

halo_column_keyword: ['digital_brandx_halo']

neg_col:
  - PRICE_PER_SALES_VOLUME
  - PROMO_PRICE_INDEX
  - inflation
  - competitor_media_spends_data__tv__spend
  - competitor_media_spends_data__ooh__spend

variables: [media, distribution, promo, price]
date_col: 'Date'
group_cols: [Company, Brand, Model]
segment_col: 'Segment'

distribution: [TDP_ABS, TDP_PROMO_ABS]
promo: []                  # declared but empty - the empty list must stay
price: [PRICE_PER_SALES_VOLUME, PROMO_PRICE_INDEX]
base_internal: [Inflation, CONSUMER_CONFIDENCE_INDEX,
  PRICE_PER_SALES_VOLUME, TDP_ABS,
  competitor_media_spends_data__tv__spend]
base_external: [seasonality_stl]
base_list_lt_l2: [TDP_ABS, TDP_PROMO_ABS,
  PRICE_PER_SALES_VOLUME, PROMO_PRICE_INDEX]

period: 52
train_split: 0.9
target_col: sales_volume_kgs
sales_col: sales_value

priority_order: [mape, mape_test, adj_r_squared, r_squared,
  dw_stat, shapiro_stat, shapiro_p_value,
  high_vif_count, low_t_stat_count, bp_stat, bp_pval]

target_col_l2: Contribution_L1     # level N models level N-1's output
target_col_l3: Contribution_L2

platform_cat_parameter_ranges:
  tv:                              # slowest decay, finest grid
    alpha_range: [0.55, 0.60, 0.65, 0.68, 0.70, 0.72, 0.75, 0.80]
    beta_range:  [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.8]
  digital_video:                   # upper-middle band
    alpha_range: [0.45, 0.48, 0.50, 0.52, 0.55]
    beta_range:  [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.8]
  search:                          # fast decay, coarse grid
    alpha_range: [0.10, 0.20, 0.30]
    beta_range:  [0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.8]

Note in passing how the fabricated grids still teach the real pattern: alpha bands differ by platform (tv > digital_video > search), beta is one shared grid, and the promo group is declared with an empty list rather than removed.

Pitfalls: what copied configs silently carry
  • "range" is not a range. Grids are discrete; a value between grid points is never searched. The real TV grid contains several many-decimal values - previously fitted optima pinned back into the grid. Copying the config to a new market silently carries those stale pins.
  • Key casing is inconsistent and exact-match. exclude_Scaling and include_Scaling_LT carry a capital S while everything else is lower snake_case. Writing exclude_scaling is silently ignored - YAML enforces no schema here.
  • Keyword shadowing. Generic and brand-specific categories coexist (digital_youtube vs the brand-specific youtube category; paid_social vs the brand-specific social one). A column containing the brand token also contains the generic token as a substring, so matching order/specificity decides which parameter grid it receives. The Config Creation sheet's own worked case is a retailer whose name is a prefix of two sibling retailers' names - the fix applied there was renaming the ADS columns to collision-free abbreviations before configuring. Longest-match or careful naming is not optional.
  • Verbatim column-name fragility. Exact-name lists must match names containing spaces, commas and currency symbols character-for-character; near-duplicate variants of the same driver (differently prefixed income columns, inflation lowercase in one list and capitalised in another) must each be matched verbatim. One wrong character = silent no-op.
  • Cross-list consistency is manual. base_list_lt_l2 duplicates the distribution + price lists by hand; adding a column to price: without updating it (or exclude_Scaling) creates silent divergence between the L1 and LT models.
  • Comments are load-bearing. Commented-out blocks record tool defaults and previous settings (an older lambda shape, an alternative train split) - read them, never trust them as active config, and remember the Config Creation sheet's final check: save the file.

The real exhibit

The production artifact this module describes is Onboarding Docs\mmx_tool_config_UK_knorr_new_iteration-latest May12.yaml, paired 1:1 with the methodology workbook's Config Creation sheet. Open it alongside this module - reference only: it contains real client parameter values and brand tokens, so study the structure and never lift values into your own configs or notes.

Check Yourself
A brand-specific platform category exists alongside its generic sibling (e.g. a brand-tokened youtube category next to digital_youtube). A brand-tokened ADS column arrives. What is the risk?
Why: platform categories are keyword/substring-matched, not exact-name. A brand-tokened column matches both the specific and the generic keyword, so without longest-match handling or collision-free naming it can land in the wrong grid with no error raised. This is the keyword-shadowing pitfall, and renaming ADS columns to unambiguous tokens is the documented fix.
What does priority_order actually control?
Why: the grid search produces many candidates per level; priority_order (mape first, then mape_test, adj R squared, and so on down to bp_pval) is the sort key that picks the winner, with later entries as tie-breakers. It is a live control, not cosmetics.
You are wiring the level targets for a new cell. Which is correct?
Why: the hierarchy is a chain of decompositions: L2 splits what L1 attributed, so its target is Contribution_L1 (and L3's is Contribution_L2). Matching the level numbers looks natural and is the documented routine mis-setting.
Sources
Authored from:
  • Course build notes yaml_config.md - structural documentation of the real production config (key inventory, transform_mapping, the 19 platform categories, alpha band structure, shared beta grid, neg_col convention, priority_order sequence, target chain, pitfalls list); all numeric values in that source are described directionally only, and every number in this module's YAML block is fabricated
  • MathCo Methodology Understanding_UL.xlsx sheet Config Creation (four-configs-per-cell practice, platform_categories keyword mapping, the column-name collision case and its rename fix, parameter dictionary, Quick Checks)
  • Real exhibit (reference only, no values lifted): Onboarding Docs\mmx_tool_config_UK_knorr_new_iteration-latest May12.yaml