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 the 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: the transformation methodology is resolved from the shock table’s channels/metrics and reported back. 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

run_arguments(→ tuple[list, list])

Add-ons and extension switches the run needs: the methodology'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 the methodology
                restructures rows, else None — run the untouched `data`
diagnostics     the methodology's account of what it did
methodology     the resolved reference, e.g. "lma_labour_alignment"
constants       {(name, group): value} for the counterfactual run
context_constants  ... applied to BOTH runs
addons, extensions   what the runs must activate
compatibility   what the methodology needs from the model and whether
                this model has it (a CompatibilityReport), or None when
                the model could not be inspected

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 methodology’s contract. 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.

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

Add-ons and extension switches the run needs: the methodology’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.