Source code for euromod_linking.methods.base
"""What every linking method is handed and what it returns.
A method implements ``apply`` and ``check_dataset``, plus an optional
``preview``, and must be deterministic: same input data, same shocks, same
params gives bit-identical output.
"""
from dataclasses import dataclass, field
import pandas as pd
[docs]
class MethodError(ValueError):
"""Raised by a method for domain failures (infeasible targets, bad state)."""
[docs]
@dataclass(frozen=True)
class MethodContext:
# Which model, country, system and dataset the run is against. A method
# needing anything further from the model — an income list's membership, a
# country's full-time week — looks it up itself through `euromod_linking.query`
# using these, and keeps that knowledge in its own module. The context
# carries no method-specific fields, so adding a method never widens it.
country_code: str
system_name: str
dataset_name: str | None = None
# Extension switches the scenario turns on or off. Model lookups are
# extension-aware, so a method must pass these through when it resolves one.
extensions: list | None = None
[docs]
@dataclass
class MethodResult:
data: pd.DataFrame # transformed input (counterfactual)
diagnostics: dict = field(default_factory=dict)
constants: dict | None = None # optional {(name, group): value} the method emits
# The *same rows* with the shock not applied. Methods that restructure the
# microdata (household weight splits) must supply this: statistics that
# compare a baseline with a reform pair the two runs observation by
# observation — fixed poverty line, baseline-defined decile groups — so the
# two frames have to share one row axis. Emitting it here means one
# alignment produces both runs. None => the unmodified input is already a
# valid, row-aligned baseline.
baseline: pd.DataFrame | None = None
# The method interface, written out because it is a contract rather than a base
# class — a method is any object with these callables:
#
# apply(data, shocks, params, ctx) -> MethodResult
# Produce the counterfactual input from the shocks. Deterministic.
#
# check_dataset(columns, shocks) -> list[str]
# Dataset-aware validation problems ([] = ok), from the column list alone,
# before any expensive work.
#
# preview(data, shocks, params, ctx) -> dict (optional)
# What apply() would target, without doing the work: the per-cell baseline
# population, the resulting targets, the implied change, feasibility
# warnings. Must reuse apply()'s own target construction — a preview
# computed a second way can disagree with the run it previews — and must
# not fit models or align, since it runs on the validation path.