euromod_linking.scenarios

Running a linking scenario: validate, transform, (optionally) simulate.

Two entry points:

apply_scenario(system, data, scenario)

The core. Turns external-model shocks into transformed EUROMOD input — the counterfactual and, when a methodology restructures rows, a matching baseline built on the same rows. Executes nothing.

run_scenario(system, scenario, input_path=...)

Convenience: loads the dataset, applies the scenario and runs both halves through System.run().

Applications that need caching, retries or their own response envelope build on apply_scenario and run the frames themselves; that is why nothing here knows about caches or run ids.

The API exposes shocks, not methodologies: each shock channel in the table is dispatched to the methodology that consumes it, and the resolved methods are reported back. A table carrying several channels is handled by several methods, run one after another in their declared stage order — see euromod_linking.registry for why the order is theirs and not the caller’s. An optional methodology pin exists only for exact reproduction or disambiguation.

Constants semantics:

  • scenario constants are context, applied to BOTH runs, so the delta isolates the shock;

  • shock records with channel: constant are part of the shock and apply to the COUNTERFACTUAL only (metric = constant name, period = constant group, op must be set). A constants-only shock table needs no methodology.

Errors are exceptions (ScenarioError carrying .problems, RunError), not error dicts — callers that need a machine-readable envelope build it themselves.

Exceptions

NoEffectError

The methodology transformed the input but the engine produced identical

ScenarioError

A scenario cannot be applied. .problems lists every problem found,

Functions

apply_scenario(→ dict)

Transform input microdata according to a scenario. Executes nothing.

cell_population(→ dict)

Weighted people matched by each distinct shock cell.

check_scenario(→ dict)

Validate everything that does not need the model or the data.

fingerprint(→ str)

Canonical scenario hash for a result-cache key. Shocks are represented by

own_shocks(population_shocks, spec)

The records a method is handed: those of the channels it consumes.

params_schema(→ dict)

The one params schema a scenario is validated against: the union of

run_arguments(→ tuple[list, list])

Add-ons and extension switches the run needs: every method's own

run_scenario(→ dict)

Apply a scenario and run both halves through System.run().

Module Contents

exception euromod_linking.scenarios.NoEffectError[source]

The methodology transformed the input but the engine produced identical output — the run is invalid.

Emphatically not “the reform has no impact”: it means the model never acted on the transformation (typically a required add-on or switch is not available for this country/system).

exception euromod_linking.scenarios.ScenarioError(problems: list[str])[source]

A scenario cannot be applied. .problems lists every problem found, not just the first, so one call surfaces everything to fix.

problems
euromod_linking.scenarios.apply_scenario(system, data, scenario: dict, *, dataset_name: str | None = None, validate_only: bool = False) dict[source]

Transform input microdata according to a scenario. Executes nothing.

Returns a dict with:

counterfactual  the transformed input
baseline        the matching baseline (same rows) when a methodology
                restructures rows, else None — run the untouched `data`
diagnostics     the methods' account of what they did: flat for one
                method, nested under each method's name (with ``order``)
                for several
methodology     the resolved reference, e.g. "lma_labour_alignment" or
                "scale_variables+lma_labour_alignment" (run order)
methods, stages what ran, and in which order
constants       {(name, group): value} for the counterfactual run
context_constants  ... applied to BOTH runs
addons, extensions   what the runs must activate
compatibility   what each method needs from the model and whether this
                model has it: a list of CompatibilityReport, one per
                method (None entries when the model could not be inspected)

Several methods run in stage order, each on the frame the previous one produced. The paired baseline is the one built by the last method that restructured rows, on the frame it received — so it carries every earlier stage’s changes, which is what keeps it row-aligned with the counterfactual.

Raises ScenarioError (with .problems) when the scenario cannot be applied.

euromod_linking.scenarios.cell_population(data, population_shocks) dict[source]

Weighted people matched by each distinct shock cell.

Surfacing this before a run is what turns a mistyped cell (deh=34) into a visible zero rather than a shock that silently hits nobody.

euromod_linking.scenarios.check_scenario(scenario: dict) dict[source]

Validate everything that does not need the model or the data.

Structure, shock resolution, methodology dispatch, declared params and the methods’ contracts. Separated so a caller can reject a malformed scenario without paying to load a model. Raises ScenarioError with .problems; returns the resolved pieces for apply_scenario.

euromod_linking.scenarios.fingerprint(scenario: dict, shock_table_id: str, methodology: str, code_fingerprint: str = '') str[source]

Canonical scenario hash for a result-cache key. Shocks are represented by the table’s content id, so inline vs stored vs re-ingested identical shocks hit the same cache entry. Excludes the free-text ‘name’ label.

methodology is the run’s method reference (registry.pipeline_name) and code_fingerprint its registry.pipeline_fingerprint: both carry the order the methods ran in, which is part of what the run did.

euromod_linking.scenarios.own_shocks(population_shocks, spec)[source]

The records a method is handed: those of the channels it consumes.

euromod_linking.scenarios.params_schema(specs) dict[source]

The one params schema a scenario is validated against: the union of its methods’ schemas.

params are scenario semantics — which external-model period, a reporting threshold — and one scenario has one set of them however many methods it runs, so every method reads the same period. The union keeps additionalProperties: false, which is what blocks methodology knobs: a param no method declares is still an error.

euromod_linking.scenarios.run_arguments(scenario: dict, specs) tuple[list, list][source]

Add-ons and extension switches the run needs: every method’s own requirements (with {cc} resolved) plus anything the scenario adds.

euromod_linking.scenarios.run_scenario(system, scenario: dict, *, input_path: str, paired_baseline: bool = False) dict[source]

Apply a scenario and run both halves through System.run().

paired_baseline runs the baseline on the counterfactual’s own rows, so the two outputs are observation-paired — needed by any baseline-vs-reform comparison that works row by row, such as one applying a fixed poverty line or baseline-defined decile groups.

Returns the apply_scenario dict with baseline_output and counterfactual_output frames added. Raises NoEffectError when the methodology changed people but the outputs are identical.