euromod_linking.scenarios ========================= .. py:module:: euromod_linking.scenarios .. autoapi-nested-parse:: 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 ---------- .. autoapisummary:: euromod_linking.scenarios.NoEffectError euromod_linking.scenarios.ScenarioError Functions --------- .. autoapisummary:: euromod_linking.scenarios.apply_scenario euromod_linking.scenarios.cell_population euromod_linking.scenarios.check_scenario euromod_linking.scenarios.fingerprint euromod_linking.scenarios.run_arguments euromod_linking.scenarios.run_scenario Module Contents --------------- .. py:exception:: NoEffectError 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). .. py:exception:: ScenarioError(problems: list[str]) A scenario cannot be applied. ``.problems`` lists every problem found, not just the first, so one call surfaces everything to fix. .. py:attribute:: problems .. py:function:: apply_scenario(system, data, scenario: dict, *, dataset_name: str | None = None, validate_only: bool = False) -> dict 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. .. py:function:: cell_population(data, population_shocks) -> dict 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. .. py:function:: check_scenario(scenario: dict) -> dict 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`. .. py:function:: fingerprint(scenario: dict, shock_table_id: str, methodology: str, code_fingerprint: str = '') -> str 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. .. py:function:: run_arguments(scenario: dict, spec) -> tuple[list, list] Add-ons and extension switches the run needs: the methodology's own requirements (with ``{cc}`` resolved) plus anything the scenario adds. .. py:function:: run_scenario(system, scenario: dict, *, input_path: str, paired_baseline: bool = False) -> dict 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.