Source code for mmm_framework.config.levers
"""Price and promotion levers (#138).
Price and promotion are among the largest sales drivers for CPG / retail / DTC
and are *decisions the client controls*, so planners want an elasticity and a
promo ROI — not a nuisance control coefficient. These configs promote a control
column to a first-class lever with its own transform and prior:
- **Price** enters as ``beta_price · log(price / reference)`` (a log-price /
discount-depth term) with a sign guard (elasticity ≤ 0).
- **Promotion** enters as ``beta_promo · adstock(promo)`` — a lift with its own
carryover (pull-forward / decay), distinct from an instantaneous linear bump.
A variable named here is removed from the linear control block, so it is not
double-counted.
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
[docs]
class PriceConfig(BaseModel):
"""A price lever (log-price elasticity with an optional reference / gap)."""
#: Name of the price column (a control variable in the data).
variable: str
#: Reference / regular price the model responds to the gap from. A float is
#: an absolute reference; ``"mean"`` / ``"median"`` / ``"max"`` derive it from
#: the data (median ≈ the usual "regular" price). ``None`` uses ``log(price)``
#: with the constant absorbed by the intercept (still a valid elasticity).
reference: float | Literal["mean", "median", "max"] | None = "median"
#: Prior sigma of the (sign-guarded, ≤ 0) elasticity coefficient.
elasticity_prior_sigma: float = Field(default=0.5, gt=0)
model_config = {"extra": "forbid"}