Source code for mmm_framework.config.events
"""Holiday / event effect configuration (#137... #143).
A first-class way to declare sharp, date-specific effects — Black Friday, Prime
Day, a product launch, a one-off PR event — that the smooth Fourier seasonality
cannot represent. Each event becomes a regressor column (built by
:func:`mmm_framework.transforms.events.build_event_regressors`) with its own
coefficient, so its contribution is estimated and reported *separately* from
seasonality.
"""
from __future__ import annotations
from pydantic import BaseModel, Field
[docs]
class EventSpec(BaseModel):
"""One named event (or recurring holiday).
Exactly one of ``dates`` (explicit) or ``holiday`` (a named holiday looked up
from the country calendar) identifies when the event occurs.
"""
name: str
#: Explicit event dates (any pandas-parseable date strings), e.g. a launch.
dates: list[str] | None = None
#: A named holiday from the config's ``country`` calendar (e.g. "Christmas
#: Day", "Thanksgiving"). Case-insensitive substring match on the calendar's
#: holiday names. Recurs every year in the data window.
holiday: str | None = None
#: Weeks of "shoulder" before / after the event that also carry an effect
#: (0 = the event period only).
pre_weeks: int = Field(default=0, ge=0)
post_weeks: int = Field(default=0, ge=0)
#: Geometric decay applied across the shoulder periods (0 = flat 1.0 over the
#: whole window; 0.5 = each step away from the peak is half as strong).
decay: float = Field(default=0.0, ge=0.0, lt=1.0)
#: Prior sigma for this event's coefficient (overrides the group default).
prior_sigma: float | None = None
model_config = {"extra": "forbid"}
[docs]
class EventsConfig(BaseModel):
"""A set of holiday / event regressors to add to the model.
Off by default (``ModelConfig.events is None``). Effects enter as an additive
``event_component`` distinct from the Fourier ``seasonality_component``, so
they do not double-count.
"""
#: Country code for named-holiday lookups (e.g. "US", "GB"). Requires the
#: optional ``holidays`` package. ``None`` ⇒ only ``custom_events`` are used.
country: str | None = None
#: Which of the country calendar's holidays to include as regressors. Empty
#: ⇒ every holiday in the calendar (one regressor per distinct holiday name).
holidays: list[str] = Field(default_factory=list)
#: User-defined events (launches, PR moments, or overrides of a named holiday
#: with custom windows/decay).
custom_events: list[EventSpec] = Field(default_factory=list)
#: Default coefficient prior sigma for events without their own.
prior_sigma: float = Field(default=0.5, gt=0)
#: Shoulder weeks / decay applied to the country-holiday regressors.
holiday_pre_weeks: int = Field(default=0, ge=0)
holiday_post_weeks: int = Field(default=1, ge=0)
holiday_decay: float = Field(default=0.5, ge=0.0, lt=1.0)
model_config = {"extra": "forbid"}
[docs]
def is_empty(self) -> bool:
return not self.custom_events and not (self.country or self.holidays)