From a233fef749ed16e7824ccb1b2dedc58097d6d204 Mon Sep 17 00:00:00 2001 From: Koen van Greevenbroek Date: Tue, 28 Jul 2026 13:47:08 -0700 Subject: [PATCH] docs: align prose with current implementation --- config/calibration/stability.yaml | 2 +- config/default.yaml | 12 ++++++------ docs/calibration.rst | 2 +- docs/crop_production.rst | 6 +++--- docs/current_diets.rst | 6 +++--- docs/data_sources.rst | 4 ++-- docs/environment.rst | 2 +- docs/tutorial.rst | 2 +- tests/test_compute_gleam3_feed_fractions.py | 6 ++---- tests/test_extract_health_impacts.py | 15 +++++---------- tests/test_sensitivity.py | 8 ++------ workflow/rules/common.smk | 11 +++-------- .../scripts/build_frt_area_attribution.py | 17 +++++------------ workflow/scripts/build_model/grassland.py | 6 ++---- workflow/scripts/build_model/land.py | 5 +---- workflow/scripts/build_model/trade.py | 2 +- workflow/scripts/build_region_watergap.py | 7 +++---- .../scripts/calculate_manure_emissions.py | 9 ++++----- .../scripts/calibrate_deviation_penalty.py | 2 +- .../scripts/diet/food_group_projection.py | 10 +++++----- workflow/scripts/estimate_baseline_diet.py | 6 ++---- .../scripts/generate_rr_age_attenuation.py | 3 +-- .../scripts/plotting/plot_food_consumption.py | 5 +---- .../prepare_faostat_animal_production.py | 5 ++--- .../scripts/prepare_gbd_food_group_intake.py | 2 +- workflow/scripts/solve_model/sensitivity.py | 5 ++--- workflow/validation/__init__.py | 2 -- workflow/validation/restricted_data.py | 19 ------------------- 28 files changed, 61 insertions(+), 120 deletions(-) delete mode 100644 workflow/validation/restricted_data.py diff --git a/config/calibration/stability.yaml b/config/calibration/stability.yaml index d0c67432..73125b87 100644 --- a/config/calibration/stability.yaml +++ b/config/calibration/stability.yaml @@ -10,7 +10,7 @@ # one paired solve (baseline with `enforce_baseline_diet=true` to derive # consumer values, then main with piecewise utility active). # -# Warm-starts from the previously calibrated YAML when one exists on disk; +# Warm-starts from an existing calibrated YAML when one exists on disk; # otherwise uses the seed values in `deviation_penalty.calibration.seeds`. # # Usage: diff --git a/config/default.yaml b/config/default.yaml index c7f43fe3..f34259d4 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -325,7 +325,7 @@ water: eta_max: 5.0 # Consumed fraction C/W (consumption over withdrawal). Used to report # withdrawal in the analysis and to convert the current_use availability - # pool from withdrawal to consumption basis. Global constant for now. + # pool from withdrawal to consumption basis. Global constant. consumed_fraction: 0.58 # --- section: water_scarcity --- @@ -968,10 +968,10 @@ diet: # residual fix whenever whole_grains is anchored -- see docs/current_diets.rst. # CALIBRATION COUPLING: this flag changes the baseline diet, which the # calibration artefacts under data/curated/calibration/ are fit against. - # Changing it (directly or via health.enabled) means the `default` artefact - # set no longer matches; point calibration.source at the `gbd-anchored` set - # (fit with anchoring on) or recalibrate. The provenance check errors on a - # mismatch (see docs/calibration.rst). + # The calibration artefact set must match this choice. Point + # calibration.source at the `gbd-anchored` set when anchoring is enabled, + # or recalibrate. The provenance check errors on a mismatch (see + # docs/calibration.rst). anchor_groups_to_gbd: match_health # Foods whose per-country intake is computed directly from FAOSTAT Food # Balance Sheet supply rather than disaggregated from GDD/GBD group @@ -1424,7 +1424,7 @@ health: risk_cause_map: fruits: [CHD, Stroke, T2DM] vegetables: [CHD, Stroke] - nuts_seeds: [CHD] # GBD 2023 BoP no longer links nuts/seeds to T2DM + nuts_seeds: [CHD] legumes: [CHD] red_meat: [CHD, Stroke, T2DM, CRC] whole_grains: [CHD, Stroke, T2DM, CRC] diff --git a/docs/calibration.rst b/docs/calibration.rst index ac711bc2..f8ec376f 100644 --- a/docs/calibration.rst +++ b/docs/calibration.rst @@ -454,7 +454,7 @@ Each iteration is one paired solve (baseline with ``enforce_baseline_diet=true`` to derive consumer values, then main with piecewise utility active). Convergence is typically reached in 3–5 iterations from a cold start and 1–2 from a warm start (the -previously calibrated YAML is auto-detected and used as the seed). The +existing calibrated YAML is auto-detected and used as the seed). The initial Jacobian is :math:`\mathrm{diag}(-1, -1)`, which is the exact log-log slope for a relationship of the form :math:`\text{dev} \propto 1/\ell_1`. diff --git a/docs/crop_production.rst b/docs/crop_production.rst index cd1ceb4a..3b75eea2 100644 --- a/docs/crop_production.rst +++ b/docs/crop_production.rst @@ -509,9 +509,9 @@ numeric codes as: * 7 – triple cropping (≤2 wetland rice crops) * 8 – triple rice cropping (up to three wetland rice crops) -Relay cropping opportunities mentioned for the C/F zones are intentionally ignored for now; we -only construct sequential crop chains. This assumption is called out in the configuration and -model framework documentation so users know the limitation. +Relay cropping opportunities mentioned for the C/F zones are not modeled; we only construct +sequential crop chains. This limitation is also noted in the configuration and model framework +documentation. During ``build_model`` each (combination, region, resource class) creates a single rainfed or irrigated multi-output link (carrier ``crop_production_multi``) that: diff --git a/docs/current_diets.rst b/docs/current_diets.rst index 436a0a02..0cd0020d 100644 --- a/docs/current_diets.rst +++ b/docs/current_diets.rst @@ -705,9 +705,9 @@ multipliers, so the diet mass-balances against the food bus. total for red meat sat ~24 Mt/yr above what total world supply (production net of feed/non-food/exports, after post-loss and consumer waste) can deliver — physically impossible — and - previously inflated the calibrated ``feed`` L1 cost ninefold - because the deviation-penalty calibration was forced to fight - intake-derived consumer values that were structurally above supply. + inflates the calibrated ``feed`` L1 cost because the deviation-penalty + calibration must otherwise accommodate intake-derived consumer values + that exceed supply. 2. **Trade is handled implicitly.** FBS supply per country already encodes ``production + imports − exports − feed − seed − non-food diff --git a/docs/data_sources.rst b/docs/data_sources.rst index 7c825849..fefc7f8a 100644 --- a/docs/data_sources.rst +++ b/docs/data_sources.rst @@ -351,7 +351,7 @@ FAOSTAT Land Use (RL) **Retrieval**: Downloaded as bulk CSV (``Inputs_LandUse_E_All_Data_(Normalized).zip``), converted to Parquet, and processed by ``workflow/scripts/prepare_faostat_pasture_area.py``. -**Usage**: Provides per-country permanent pasture area used to scale down satellite grassland area in ``build_model.py``, replacing the previous forage overlap subtraction approach. +**Usage**: Provides per-country permanent pasture area used to scale down satellite grassland area in ``build_model.py``. FAOSTAT Food Balance Sheets (FBS) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1140,7 +1140,7 @@ Redistributing datasets via Zenodo ---------------------------------- Some upstream datasets are free to use but sit behind an API key or registration -wall (historically the Copernicus land cover data). Where the licence permits +wall. Where the licence permits redistribution, GLADE mirrors the exact slice it needs to `Zenodo `__ and downloads it during builds with a plain HTTP request. This removes the per-user credential, pins an immutable, citable diff --git a/docs/environment.rst b/docs/environment.rst index fa687344..b36ab906 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -391,7 +391,7 @@ Manure emissions are calculated in ``workflow/scripts/calculate_manure_emissions 1. **Preprocessing** (``calculate_manure_emissions.py``): * Calculate VS excretion for each feed category using digestibility and ash content - * Average MCF across climate zones for each management system (temporary simplification) + * Average MCF across climate zones for each management system because model regions do not carry climate-zone detail * Compute weighted MCF for each animal product using GLEAM MMS distributions * Calculate CH₄ emissions per kg feed intake: VS × B₀ × MCF\ :sub:`weighted` × 0.67 * Generate ``processing/{name}/manure_ch4_emission_factors.csv`` with emissions by country, product, and feed category diff --git a/docs/tutorial.rst b/docs/tutorial.rst index e56875ca..7c3887b9 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -199,7 +199,7 @@ The key differences from Part 1: mutually exclusive — attempting to combine them raises a validation error. * The ``ghg_mid`` and ``ghg_high`` scenarios inherit the top-level - ``food_utility_piecewise`` settings and no longer set + ``food_utility_piecewise`` settings and do not set ``enforce_baseline_diet``, so consumption is free. The piecewise-utility parameters themselves are worth a brief look: diff --git a/tests/test_compute_gleam3_feed_fractions.py b/tests/test_compute_gleam3_feed_fractions.py index bd10b45a..747a0bba 100644 --- a/tests/test_compute_gleam3_feed_fractions.py +++ b/tests/test_compute_gleam3_feed_fractions.py @@ -227,10 +227,8 @@ def test_mixed_endogenous_exogenous_preserves_exogenous_share( crop_production: pd.DataFrame, ) -> None: """A bucket with both endogenous and exogenous items must emit an - exogenous row alongside the endogenous fractions; previously the - exogenous share was silently absorbed into the endogenous categories - (the real-world failure was GLEAM's 'Grass and leaves' bucket dropping - the LEAVES item).""" + exogenous row alongside the endogenous fractions. The exogenous share + must remain separate from the endogenous categories.""" # Build a Grains bucket with two endogenous items (maize, barley) and # one unmapped GLEAM code that will be flagged as exogenous. xlsx_items = pd.DataFrame( diff --git a/tests/test_extract_health_impacts.py b/tests/test_extract_health_impacts.py index 507331bd..af4b4b5f 100644 --- a/tests/test_extract_health_impacts.py +++ b/tests/test_extract_health_impacts.py @@ -4,21 +4,16 @@ """Regression tests for ``workflow.scripts.analysis.extract_health_impacts``. -These tests pin down three invariants that previously failed silently: +These tests pin down three analysis invariants: 1. Intake fed into the dose-response chain must come from the post-waste - food-group *store level*, not the pre-waste *food-bus withdrawal*. Earlier - versions of the analysis used ``food_group_consumption.parquet`` for - intake, which is the link-level withdrawal at the food bus -- i.e. the - retail-supply (pre-waste) mass. The LP applies the consumer waste - multiplier on the food_consumption link's group leg, so the store level - is lower. Using the wrong basis silently inflated intake by 10-30%. + food-group *store level*, not the pre-waste *food-bus withdrawal*. + ``food_group_consumption.parquet`` contains the link-level withdrawal at + the food bus, while the store level includes the consumer waste multiplier. 2. Dose-response curves must be keyed per ``(health_cluster, risk_factor)``. ``risk_breakpoints.csv`` carries a ``health_cluster`` column because - age-weighted effective RR differs across clusters. A bug pooled curves - across clusters via ``pivot_table(aggfunc='first')``, silently keeping - one cluster's curve everywhere. + age-weighted effective RR differs across clusters. 3. ``RR_d(log_total)`` must be evaluated via the chord PWL of exp() through ``cause_log_breakpoints``, matching ``_add_stage2_lp_tangent``. Using diff --git a/tests/test_sensitivity.py b/tests/test_sensitivity.py index 5c34c64b..1f5c9280 100644 --- a/tests/test_sensitivity.py +++ b/tests/test_sensitivity.py @@ -722,11 +722,7 @@ def test_full_config(self, mock_network): np.testing.assert_allclose(result_ch4, original_ch4 * 1.1) def test_health_rr_config_ignored_at_build_time(self, mock_network): - """Test that health_relative_risk in config is ignored at build time. - - Health RR sensitivity is now applied at solve time via per-risk-factor - quantile interpolation, not at build time. - """ + """Health relative-risk sensitivity is applied at solve time.""" n = mock_network original_rr = n.stores.static.loc[ n.stores.static["carrier"].str.startswith("yll_"), "rr_ref" @@ -737,7 +733,7 @@ def test_health_rr_config_ignored_at_build_time(self, mock_network): } apply_sensitivity_factors(n, cfg) - # rr_ref should be unchanged — health RR is now handled at solve time + # Build-time reference curves remain unchanged. result_rr = n.stores.static.loc[ n.stores.static["carrier"].str.startswith("yll_"), "rr_ref" ] diff --git a/workflow/rules/common.smk b/workflow/rules/common.smk index bb96e798..8fad4845 100644 --- a/workflow/rules/common.smk +++ b/workflow/rules/common.smk @@ -76,14 +76,9 @@ def get_effective_config(scenario_name): # We convert config to dict because it might be a Config object eff_config = copy.deepcopy(dict(config)) - if scenario_name: - if scenario_name not in scenario_defs: - # If scenario is not found, maybe raise warning or error? - # For now, we assume if it's not in cache, no overrides (or invalid scenario handled elsewhere) - pass - else: - overrides = scenario_defs[scenario_name] - _recursive_update(eff_config, overrides) + if scenario_name and scenario_name in scenario_defs: + overrides = scenario_defs[scenario_name] + _recursive_update(eff_config, overrides) return eff_config diff --git a/workflow/scripts/build_frt_area_attribution.py b/workflow/scripts/build_frt_area_attribution.py index f7a745db..417e7c32 100644 --- a/workflow/scripts/build_frt_area_attribution.py +++ b/workflow/scripts/build_frt_area_attribution.py @@ -5,16 +5,10 @@ """Per-(country, modelled-fruit) target harvested area for the FRT pool. The FAO/GAEZ Module-VI ``FRT`` raster bundles ~45 QCL fruit + grape + -tree-nut items at the cell level. Historically the model attributed FRT -cell area to citrus/mango/watermelon via national crop-mix scalars, which -silently placed e.g. citrus area in cells where GAEZ says citrus cannot -grow; those cells were then dropped at build time, losing ~13 Mha of -fruit area globally. - -This script replaces the country-share approach with a per-(country, -modelled-fruit) **target area** table that downstream rules use together -with a cell-level yield x suitability weighting to ensure -agroecologically consistent placement. +tree-nut items at the cell level. This script produces a per-(country, +modelled-fruit) **target area** table that downstream rules combine with +cell-level yield x suitability weighting for agroecologically consistent +placement. For each country and baseline year: @@ -90,8 +84,7 @@ 234, # Other nuts (excluding wild edible nuts and groundnuts), n.e.c. ) -# All FRT-pool QCL items (fruits + grapes + tree nuts). Mirrors -# build_frt_kept_area_share.FRT_ITEM_CODES at the time this file replaced it. +# All FRT-pool QCL items (fruits + grapes + tree nuts). FRT_FRUIT_ITEM_CODES: tuple[int, ...] = ( # Melons, tropical and subtropical fruits 567, # Watermelons diff --git a/workflow/scripts/build_model/grassland.py b/workflow/scripts/build_model/grassland.py index ac52e92f..c4dacc9f 100644 --- a/workflow/scripts/build_model/grassland.py +++ b/workflow/scripts/build_model/grassland.py @@ -220,10 +220,8 @@ def add_grassland_feed_links( # Defensive guard: only create links whose pasture-pool bus exists. land.py # builds a pasture pool for every region/class with grassland (cropland or - # grazing-only) supply, so this should retain all rows; but with the - # left-join above we no longer rely on land_rainfed membership to guarantee - # a pool, so drop (and report) any orphan rather than leave an unbalanced - # bus reference. + # grazing-only) supply. Drop and report any orphan rather than leave an + # unbalanced bus reference. missing_pool = ~work["bus0"].isin(n.buses.static.index) if missing_pool.any(): logger.warning( diff --git a/workflow/scripts/build_model/land.py b/workflow/scripts/build_model/land.py index bc579f8e..89350049 100644 --- a/workflow/scripts/build_model/land.py +++ b/workflow/scripts/build_model/land.py @@ -261,10 +261,7 @@ def add_land_components( .astype(float) .rename("area_ha") ) - # Work on a defensive copy: the function used to mutate the caller's - # DataFrame in place via `total_land_area["area_ha"] = ...`. No current - # caller reuses the input after this call, but the side effect is - # invisible from the signature and a latent footgun for future code. + # Work on a defensive copy so callers retain the input DataFrame. total_land_area = total_land_area.copy() total_area = total_land_area["area_ha"].astype(float) # FAOSTAT-baked baseline cropland sometimes exceeds the GAEZ-derived diff --git a/workflow/scripts/build_model/trade.py b/workflow/scripts/build_model/trade.py index bc5a8c7e..c8bff394 100644 --- a/workflow/scripts/build_model/trade.py +++ b/workflow/scripts/build_model/trade.py @@ -151,7 +151,7 @@ def _add_trade_hubs_and_links( # valid_countries filters out anything outside country_to_hub.index, # so .map should never introduce NaN and the int dtype is preserved. # If a NaN slipped through, astype(str) would produce "0.0" strings - # that no longer match the integer-suffix hub bus names. + # that do not match the integer-suffix hub bus names. assert pairs["hub_idx"].notna().all() hub_idx_str = pairs["hub_idx"].astype(int).astype(str) diff --git a/workflow/scripts/build_region_watergap.py b/workflow/scripts/build_region_watergap.py index bc15c218..c5c888eb 100644 --- a/workflow/scripts/build_region_watergap.py +++ b/workflow/scripts/build_region_watergap.py @@ -43,7 +43,7 @@ - **mined groundwater** = the groundwater-storage decline. The trend reflects all users, so irrigation's part is attributed by its share of potential groundwater consumption (``pirrusegw / ptotusegw``, same basis and window); - a basin mined by municipal or industrial pumping then no longer zeroes + a basin mined by municipal or industrial pumping does not zero irrigation's renewable band. - **renewable groundwater** = ``max(pirrusegw - mined_irrigation, 0)``: the recharged part of irrigation groundwater consumption. @@ -307,9 +307,8 @@ def monthly_to_regions(path, variable): # Total irrigation consumption (pirruse, annual): the demand anchor for # eta_c and the groundwater mining ceiling, on the same basis, simulation - # and reference window as the supply envelope above. Same schema as the - # legacy AWARE anchor (region, agri_consumption_m3) so consumers are - # source-agnostic. + # and reference window as the supply envelope above. The shared schema + # keeps consumers independent of the source dataset. agri = pd.DataFrame({"region": region_index}).assign( agri_consumption_m3=lambda d: d["region"].map(irr_total.sum(axis=1)).fillna(0.0) ) diff --git a/workflow/scripts/calculate_manure_emissions.py b/workflow/scripts/calculate_manure_emissions.py index 05de5e16..5936363b 100644 --- a/workflow/scripts/calculate_manure_emissions.py +++ b/workflow/scripts/calculate_manure_emissions.py @@ -132,8 +132,8 @@ def average_mcf_over_climate_zones( ) -> pd.DataFrame: """Average MCF values across climate zones for each manure management system. - NOTE: This is a temporary simplification. Will be refined when climate zone - data is added to modeling regions. + Climate-zone variation is averaged because model regions do not carry + climate-zone detail. Parameters ---------- @@ -518,9 +518,8 @@ def calculate_manure_ch4_for_product( row["storage_n2o_ef"], ) - # Expand to all countries (same values for now, will be refined later) - # NOTE: Emission factors are currently identical across countries - # Will be differentiated by climate zone and region in future + # Emission factors are shared across countries because the source data is + # not spatially resolved to model regions. country_emissions = [] for country in countries: df = emissions.copy() diff --git a/workflow/scripts/calibrate_deviation_penalty.py b/workflow/scripts/calibrate_deviation_penalty.py index 2609dd19..969b4d63 100644 --- a/workflow/scripts/calibrate_deviation_penalty.py +++ b/workflow/scripts/calibrate_deviation_penalty.py @@ -373,7 +373,7 @@ def main() -> None: max_iter = int(smk.params.max_iter) trust_log = float(smk.params.trust_region_log) - # Warm-start from a previously calibrated YAML when available. The path + # Warm-start from an existing calibrated YAML when available. The path # is passed as a param (not an input) to avoid a Snakemake self-loop on # calibrated_yaml. The script loads it iff the file exists on disk. prev_yaml = getattr(smk.params, "previous_yaml", None) diff --git a/workflow/scripts/diet/food_group_projection.py b/workflow/scripts/diet/food_group_projection.py index 3723470b..e7f80c38 100644 --- a/workflow/scripts/diet/food_group_projection.py +++ b/workflow/scripts/diet/food_group_projection.py @@ -87,11 +87,11 @@ "apple", ) FRUITS_FRT_POOL_ITEM_CODES: tuple[int, ...] = ( - 2611, # Oranges, Mandarines (explicit; previously routed to citrus only) - 2612, # Lemons, Limes (explicit; previously routed to citrus only) - 2613, # Grapefruit (explicit; previously routed to citrus only) - 2614, # Citrus, Other (explicit; previously routed to citrus only) - 2617, # Apples (explicit; previously routed to apple only) + 2611, # Oranges, Mandarines (explicit citrus allocation) + 2612, # Lemons, Limes (explicit citrus allocation) + 2613, # Grapefruit (explicit citrus allocation) + 2614, # Citrus, Other (explicit citrus allocation) + 2617, # Apples (explicit apple allocation) 2618, # Pineapples (residual) 2619, # Dates (residual) 2625, # Fruits, Other (residual covering mango, watermelon, etc.) diff --git a/workflow/scripts/estimate_baseline_diet.py b/workflow/scripts/estimate_baseline_diet.py index 1019ee9c..4e29f528 100644 --- a/workflow/scripts/estimate_baseline_diet.py +++ b/workflow/scripts/estimate_baseline_diet.py @@ -725,11 +725,9 @@ def build_within_group_shares( ].to_dict() # Fail fast if any pooled projection references an FBS code that the - # FBS fetch never pulled. This catches silent drift between + # FBS fetch never pulled. This catches drift between # ``POOL_PROJECTIONS`` here and the fetch list in - # ``prepare_faostat_fbs_items.py`` (which historically defaulted such - # codes to 0 supply and made entire pools — plantain, apples, - # pineapples, dates — silently vanish from the projection). + # ``prepare_faostat_fbs_items.py``. # Only validate sub-specs whose projection_foods intersect with the # included foods (the rest are no-ops anyway) so that downstream tests # can exercise subsets without supplying every code. diff --git a/workflow/scripts/generate_rr_age_attenuation.py b/workflow/scripts/generate_rr_age_attenuation.py index 33d11de0..02005eaf 100644 --- a/workflow/scripts/generate_rr_age_attenuation.py +++ b/workflow/scripts/generate_rr_age_attenuation.py @@ -8,8 +8,7 @@ relative-risk curves. GBD applies age-specific RRs for cardiovascular outcomes (the proportional effect attenuates with age); diabetes and colorectal cancer carry no age attenuation. We reconstruct that age structure once and freeze it -into a curated table so the per-build workflow no longer depends on the GBD 2019 -relative-risk workbook. +into a curated table used by the per-build workflow. Method ------ diff --git a/workflow/scripts/plotting/plot_food_consumption.py b/workflow/scripts/plotting/plot_food_consumption.py index fa60fa57..074bb44c 100644 --- a/workflow/scripts/plotting/plot_food_consumption.py +++ b/workflow/scripts/plotting/plot_food_consumption.py @@ -7,8 +7,6 @@ from pathlib import Path import matplotlib - -matplotlib.use("pdf") import matplotlib.pyplot as plt import pandas as pd @@ -16,8 +14,7 @@ from workflow.scripts.logging_config import setup_script_logging from workflow.scripts.plotting.color_utils import categorical_colors -# Alias for backwards compatibility with modules that import from here -KCAL_PER_PJ = PJ_TO_KCAL +matplotlib.use("pdf") def _load_global_consumption( diff --git a/workflow/scripts/prepare_faostat_animal_production.py b/workflow/scripts/prepare_faostat_animal_production.py index 2556e284..73ea090d 100644 --- a/workflow/scripts/prepare_faostat_animal_production.py +++ b/workflow/scripts/prepare_faostat_animal_production.py @@ -133,9 +133,8 @@ def main() -> None: ) # Egg unit handling. Only hen eggs are mapped to the "eggs" model - # product (config/default.yaml), so the 60 g/egg conversion is a - # reasonable approximation for the legacy "1000 No" vintage. Current - # FAOSTAT QCL reports tonnes ("t") and the legacy branch is dormant. + # product (config/default.yaml). FAOSTAT may report egg production in + # tonnes ("t") or in thousands of eggs ("1000 No"). egg_mask_raw = df["product"] == "eggs" if egg_mask_raw.any(): if "Unit" not in df.columns: diff --git a/workflow/scripts/prepare_gbd_food_group_intake.py b/workflow/scripts/prepare_gbd_food_group_intake.py index 94ab9f57..02102e71 100644 --- a/workflow/scripts/prepare_gbd_food_group_intake.py +++ b/workflow/scripts/prepare_gbd_food_group_intake.py @@ -42,7 +42,7 @@ therefore reconstruct the 25+ exposure ourselves by population-weighting the adult 5-year buckets (25-29 .. 95+) using per-country age-bucket population for the reference year. - - Sex: the 2023 bulk files no longer ship an IHME-computed "Both" + - Sex: the 2023 bulk files do not ship an IHME-computed "Both" aggregate (only Male and Female), and our population table is not sex-split. We average the two sexes unweighted within each age bucket before age-weighting. Adult sex ratios are close to parity, diff --git a/workflow/scripts/solve_model/sensitivity.py b/workflow/scripts/solve_model/sensitivity.py index 1a23f785..bdd7fcc9 100644 --- a/workflow/scripts/solve_model/sensitivity.py +++ b/workflow/scripts/solve_model/sensitivity.py @@ -68,9 +68,8 @@ def _output_port_columns(links: pd.DataFrame) -> list[tuple[str, str, str]]: number as a string, useful for deriving sibling columns like ``loss_multiplier{N}``. - This helper exists so the bus-iteration pattern (which used to be - duplicated across sensitivity scalers) reads consistently and the - bus1-vs-busN naming quirk is documented in one place. + This helper centralizes the bus1-vs-busN naming convention used by + sensitivity scalers. """ pairs: list[tuple[int, str, str, str]] = [] for col in links.columns: diff --git a/workflow/validation/__init__.py b/workflow/validation/__init__.py index 80bd8fcf..9c1bbc10 100644 --- a/workflow/validation/__init__.py +++ b/workflow/validation/__init__.py @@ -30,7 +30,6 @@ from .multi_cropping import validate_multi_cropping from .nutrition import validate_nutrition from .optimal_taxes import validate_optimal_taxes -from .restricted_data import validate_restricted_data from .secrets import load_secrets_with_env_fallback from .seed_rates import validate_seed_rates from .sensitivity_generator import validate_sensitivity_generator @@ -43,7 +42,6 @@ "calibration": validate_calibration, "calibration_provenance": validate_calibration_provenance, "commodities": validate_commodities, - "restricted_data": validate_restricted_data, "consumer_values": validate_consumer_values, "optimal_taxes": validate_optimal_taxes, "country_regions": validate_country_regions, diff --git a/workflow/validation/restricted_data.py b/workflow/validation/restricted_data.py deleted file mode 100644 index 16e7e47f..00000000 --- a/workflow/validation/restricted_data.py +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Koen van Greevenbroek -# -# SPDX-License-Identifier: GPL-3.0-or-later - -"""Check that restricted (non-redistributable) data files are present. - -Currently no restricted data files are required — the GLEAM 2.0 Mottet et al. -(2017) dependency was replaced by bundled GLEAM 3.0 data. -""" - -from pathlib import Path - - -def validate_restricted_data(config: dict, root: Path) -> None: - """Raise if required restricted data files are missing. - - Currently a no-op; retained for interface compatibility with the - validation registry. - """