Skip to content

Moving waste to a city's existing gas capture changes its emissions - #75

Merged
HughRunyan merged 4 commits into
mainfrom
move-to-existing-gas-capture-takes-effect
Oct 17, 2026
Merged

HughRunyan merged 4 commits into
mainfrom
move-to-existing-gas-capture-takes-effect

Conversation

@HughRunyan

@HughRunyan HughRunyan commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Bug

The City DST's "% of waste diverted to existing landfill with gas capture" slider (move_gas, existing_gas_pct in City.implement_dst_changes_simple_v1_5) did nothing. At every setting it returned the no-change scenario exactly.

Environment: SWEET_python main at d5c592e, the generic branch of implement_dst_changes_simple_v1_5 (if not scenario_parameters.sites_method:). WasteMAP /v1/city_emissions/implement_dst_simple_v1_5, with the June 2026 cities table (cities_for_map_0626.csv) and load_city replicated offline.

Reproduction: load Lahore (34.4% of disposal to landfills with gas capture). Run the scenario twice with its own diversions and implement_year=2026: once with move_gas=0, once with move_gas=1, existing_gas_pct=0.9.

Expected: moving waste from the landfill without capture to the one with it lowers emissions after 2026.

Actual: both give 92,872 t CH₄ in 2040. The scenario's split_fractions came out as 0.9 / 1.714 / 0, and the landfills kept 0.344 / 0.656 / 0. Mexico City (2.3%) gave 0.9 / 38.48 / 0, also with no change. The same no-op held for all 7 cities in the June table where the slider is live (0 < share < 99%): Mexico City, Lahore, Santiago, Amman, Tegucigalpa, Cebu City, Tunis.

Root cause

  1. The new split never reached the landfills. LandfillWasteMassDF.create reads each landfill's fraction_of_waste. Only the if new_gas_pct > 0: block copied split_fractions there. WasteMAP's frontend offers this slider only to a city that already captures gas, and disables "new landfill with gas capture" for exactly those cities (useChartData.ts), so the two never ran together.
  2. The arithmetic scaled the wrong way. It set the capture share to existing_gas_pct, then multiplied the shares without capture by existing / original, scaling them up. With no capture to start from, it zeroed them. Combined with new_gas_pct, the inflated split failed its sum check, and that check built CustomError with one argument, so it raised TypeError (an API 500).

Fix

  • New _move_to_existing_gas_capture(split_fractions, existing_gas_pct) gives capture existing_gas_pct. The landfill and dumpsite without capture take the rest, 1 − existing_gas_pct, in the proportions they had.
  • The move_gas block applies that split to landfills[0..2].fraction_of_waste. As with every lever, years before the implementation year are still copied from the baseline.
  • A request the lever can't carry out raises INVALID_PARAMETERS (400 at the API) instead of silently returning the no-change scenario:
    • a share outside [0, 1];
    • a share above 0 for a city with no gas capture (the UI doesn't offer the lever there);
    • less than 100% for a city that sends everything to capture (the UI disables the slider for those 41 cities and sends 100%, which leaves the split as it is).
  • The new-landfill sum check builds CustomError("INVALID_PARAMETERS", …).

Model output (model-output-change)

2040 emissions against the no-change scenario, on the June table:

City Share to capture → 0% → 50% → 90% → 100%
Lahore 34.4% +28% −13% −45% −53%
Mexico City 2.3% +1.1% −23% −43% −48%
Cebu City 40% +24% −5.9% −30% −36%
Amman 90% +44% +20% 0% −4.9%
Tegucigalpa 94.7% +76% +36% +3.7% −4.2%
Tunis 95.9% +47% +22% +2.9% −2.0%
Santiago 98.8% +96% +47% +8.5% −1.2%

Before the fix every cell was 0%.

Unchanged, bit for bit: the no-change scenario and every request with move_gas=0 (all other levers), plus all 41 cities at 100% for everything the UI sends.

Visible side effect in WasteMAP: an untouched slider sends the city's share rounded to a whole percent. So Lahore's default scenario now sits 0.35% above its baseline in 2040, Tegucigalpa's 0.25% below, and Santiago, Mexico City and Tunis move less than 0.2%. The diversion sliders already round the same way: Mexico City's untouched scenario was 0.72% below its baseline before this fix.

Fixed separately: "Add gas capture" loses its oxidation settings

In the same generic branch, add_gas sets oxidation to 0.22 for the landfill without capture and 0.1 for the converted dumpsite after the implementation year, and an override at the end of the method reset both to the no-capture defaults. That is fixed in #78 (with RMI/WasteMAP#856), so the Known limitations entry this PR added to changelog/2026-10.md has been dropped (f69fdac).

Tests

tests/test_move_to_existing_gas_capture.py: 16 tests. They build a city from country defaults (dst_baseline_blank) with a 30/50/20 split and run the real implement_dst_changes_simple_v1_5, with no database.

  • 15 of 16 fail on main, each on the defect: no emissions change, the landfills keep the baseline split, Lahore's split comes out 0.9 / 1.714 / 0, TypeError with new_gas_pct, and no error for impossible requests.
  • The one that passes on main is the guard that a city's own share reproduces the no-change scenario.
  • Also pinned: the moved waste is reallocated, not created or lost. From the implementation year each landfill's mass is the no-change mass × new share / old share, and the total landfilled each year is unchanged.
  • Full suite: 288 passed.

The WasteMAP side, RMI/WasteMAP#854 (same branch name, so its CI installs this branch), adds an endpoint test on Lahore's real table row through load_city. Two of its four tests fail against main here. Merge this PR first. WasteMAP CI falls back to SWEET_python main once this branch is gone, so RMI/WasteMAP#854 is green on main only after this lands.

Acceptance criteria

  • move_gas=1 with a share different from the city's own changes emissions from the year after implementation, and never before it.
  • Capture gets exactly existing_gas_pct. The landfill and dumpsite without capture share the rest in their original proportions, and the split sums to 1.
  • Moving waste conserves mass: per-year landfilled totals match the no-change scenario.
  • A city's own share, move_gas=0, and the 41 cities at 100% give the same results as before.
  • Impossible requests return INVALID_PARAMETERS rather than a silent no-op or a 500.
  • Changelog entry (changelog/2026-10.md), cross-linked with WasteMAP's backend changelog.

Definition of done

  • Acceptance criteria met
  • Tests and checks pass (SWEET_python: 288 passed; WasteMAP tests/fast against this branch: 580 passed, 1 skipped)
  • Docs updated (both changelogs)
  • Reviewed and merged (before RMI/WasteMAP#854)

🤖 Generated with Claude Code

The City DST's "move waste to existing gas capture" lever (move_gas and
existing_gas_pct in implement_dst_changes_simple_v1_5) rewrote split_fractions
in the generic branch but never passed the split to the landfills, whose waste
comes from their own fraction_of_waste. Only the new-landfill-with-capture block
did that, and WasteMAP switches that lever off for every city it offers this one
to. So the slider returned the no-change scenario at every setting for all 7
cities in the June 2026 table where it is live.

The arithmetic was wrong too: it multiplied the shares without capture by
existing / original, scaling them up (Lahore's landfill without capture came out
at 171% of its waste) and zeroing them for a city with no capture.

_move_to_existing_gas_capture gives capture existing_gas_pct and the landfill
and dumpsite without capture the rest, in their original proportions, and the
scenario applies that split to the landfills. A request the lever can't carry
out raises INVALID_PARAMETERS. The new-landfill sum check now builds CustomError
with both arguments; it raised TypeError, a 500 at the API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HughRunyan HughRunyan added bug Something isn't working model-output-change Changes model OUTPUT values (expected progress, not breaking); results differ from prior runs test Adds or modifies tests labels Oct 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Add gas capture" losing the oxidation it sets is fixed in #78 (with
RMI/WasteMAP#856), so this changelog no longer lists it as a known limitation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The October changelog and its README line keep main's entries and add this PR's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HughRunyan
HughRunyan merged commit fc7d018 into main Oct 17, 2026
2 checks passed
@HughRunyan
HughRunyan deleted the move-to-existing-gas-capture-takes-effect branch October 17, 2026 20:22
HughRunyan added a commit that referenced this pull request Oct 17, 2026
* Bump the python-minor-and-patch group with 2 updates

Bumps the python-minor-and-patch group with 2 updates: [fastapi](https://github.com/fastapi/fastapi) and [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy).


Updates `fastapi` from 0.141.1 to 0.142.2
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.141.1...0.142.2)

Updates `sqlalchemy` from 2.1.1 to 2.1.3
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.142.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-minor-and-patch
- dependency-name: sqlalchemy
  dependency-version: 2.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: python-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Indonesia sizes its waste from its own IPCC row

msw_per_capita_country is IPCC 2019 Refinement Vol. 5 Ch. 2 Table 2A.1
(Updated), 2010 column. All 167 of its rows match the IPCC's, but Indonesia's
(0.19 t/person/yr) was never typed in, so Indonesia fell back to the
South-Eastern Asia default, 0.46: 2.4x its own row. Adds the row and cites the
source tables in comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: Indonesia's per-capita waste row (#76)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* SWEET can name its next release, with tests for the counting

scripts/next_release.py turns a list of git tags into the next free
sweet/YYYY.MM.N for a month (N from 0, one past the highest used),
checks a hand-typed release name, and picks the newest release in a
list. Tags that are not release names -- other projects' wastemap/ and
submission/ tags, a stray sweet/test -- are ignored. Standard library
only, tags on stdin, so the release workflow can run it before
installing anything and the tests need no git or network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A one-click workflow cuts a dated SWEET release from main

.github/workflows/release.yml (Actions > Release > Run workflow) tags a
commit on main as sweet/YYYY.MM.N -- by default the tip of main and the
next free number for the current UTC month -- and publishes a GitHub
Release whose notes list the pull requests merged since the previous
release, with a link to the month's changelog file. The job summary
records the tag, the commit and the links.

Before anything is tagged it refuses: a commit main does not contain;
a name that already exists, or one that would sort before the newest
release; a commit that is already a release; a commit that does not
contain the newest release; and a commit whose own test suite fails
(the same install and pytest command as tests.yml, run against that
commit). The tag is annotated and pushed without force, so a release
can never be moved. Only main's copy of the workflow may run, and one
release runs at a time so two cannot take the same number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* setup.py and the package docstring say releases are sweet/ tags

Both said SWEET has no releases. They now say a release is a git tag
sweet/YYYY.MM.N cut by the Release workflow, that setup.py's "0.1" is
still decoration and never bumped, and that __version__ stays the
commit: a release names one commit, the commit is all an installed
copy can report without a repository, and it is the only name a
branch install has. No code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The README says how to cut, name and pin a SWEET release

The README's release section described a shared vYYYY.MM.DD tag that
was renamed to wastemap/YYYY.MM.DD in September and is not SWEET's own
release anyway. It now explains sweet/YYYY.MM.N: what the name means,
that a release is never moved or reused, how to cut one from the
Actions tab, what the workflow refuses, and how an app pins one (a
release name, or a SWEET branch only on a feature branch).

Its Usage section pointed at sweet_tools_obj.py, model.py and
load_from_database, none of which exist any more; it is now a short
map of the modules and entry points the two apps actually call.

changelog/README.md no longer says the project publishes no tags. It
says releases are cut from main with generated notes listing the PRs
since the last release, and that the monthly files keep the
human-written entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The recovery command writes the same notes the Release step would have

If the tag is pushed but the Release fails, the command in the job summary now
includes the changelog link (and the first release's note), quoted for bash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A copy built from git knows its commit, without the app having to say

setup.py writes the commit of a clean checkout into the built package as
SWEET_python/_build_sha.py, which is how pip builds a git install, and
__version__ reads it first (source "build"), then SWEET_GIT_SHA, then .git.
The stamp goes only into the build directory, never the source tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The README says how an installed copy knows its commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* SWEET's docs say its population table is the only copy

Climate TRACE's pipeline and its offline country-data build now read
SWEET_python/pops_yearly.csv (RMI_Climate_TRACE_Waste_Methane#208), so the
"two copies, nothing keeps them in step" text in constants.py and
population.py is out of date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A site with no recorded type is typed by one rule, the same one Climate TRACE uses

SWEET guessed a missing site type in seven places, from one of two region
lists (one with Eastern Europe, one with Australia and New Zealand instead).
Climate TRACE's pipeline used a fuller rule: income, region and named
countries. So a Kazakh site with no type was a sanitary landfill in Climate
TRACE and an open dump in WasteMAP's site tool.

The rule now lives in SWEET_python/site_type_defaults.py, moved unchanged from
Climate TRACE's landfill_table_ops.fill_landfill_types. A site is a sanitary
landfill where its country is high income, or its region is one of
landfill_default_regions plus Western Asia and Central Asia, or it is China,
Russia, Mexico, Brazil or Costa Rica. Otherwise, and always for Mongolia and
Yemen, it is an open dump.

Income comes from SWEET_python/country_income.py, copied from the World Bank
table Climate TRACE reads (What a Waste 2.0's country file, 217 countries).

Every guess in city_params now calls it: the city split's last fallback (three
branches), site_only_estimate, both trace paths, and the site tool's prefill
by location and for a listed site with no type. dst_baseline_blank's fallback
is left to #74, which rewrites it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: a site with no recorded type is guessed the same way everywhere

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gas capture and oxidation follow a site's reports year by year when its rows carry their year

The Climate TRACE pipeline's consolidated input gives a site one row per year and
marks each with `row_year`. On those rows a reported gas collection efficiency or
oxidation belongs to its year: one value is used in every model year, and several
are carried back and forward -- the first back to the start, each forward to the
next report, the latest to the end.

Two consequences. A modelled site, which has no reported-emissions year, keeps its
measured efficiency instead of falling to the site-type default. And if upstream
starts resolving these two per year, the series follows the reports with no change
here.

Rows without `row_year` -- the legacy input and every other caller -- keep the old
rule (site mean, overwritten in reported years).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The carried series reads nullable year and value columns

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: gas capture and oxidation follow a site's yearly reports when its rows carry their year

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Rows that carry their year never fall back to the emissions-year rule

If every row_year on a site's rows was blank, both builders fell through to the
legacy rule, which places a value by its reported-emissions year. For gas capture
that brought back the fault this branch fixes: a modelled site, with no such year,
lost its measured efficiency to the site-type default. Now an undated value on these
rows is the site's value in every year, and a site with no value at all gets the
gated default as before. Raised in review of #84.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The places SWEET applies a Climate TRACE modelling decision carry a comment naming it

Seven decisions from RMI_Climate_TRACE_Waste_Methane's docs/MODEL_DECISIONS.md are
tagged where SWEET applies them: MCF, site type, waste composition, gas capture,
oxidation, flaring and population growth. That repo's test checks the comments once a
SWEET release containing them is pinned. Comments only; no number moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Bump the python-minor-and-patch group with 5 updates

Bumps the python-minor-and-patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [asyncpg](https://github.com/MagicStack/asyncpg) | `0.31.0` | `0.32.0` |
| [fastapi](https://github.com/fastapi/fastapi) | `0.142.2` | `0.143.0` |
| [numpy](https://github.com/numpy/numpy) | `2.5.3` | `2.5.4` |
| [pydantic](https://github.com/pydantic/pydantic) | `2.13.5` | `2.14.0` |
| [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) | `2.1.3` | `2.1.4` |


Updates `asyncpg` from 0.31.0 to 0.32.0
- [Release notes](https://github.com/MagicStack/asyncpg/releases)
- [Commits](MagicStack/asyncpg@v0.31.0...v0.32.0)

Updates `fastapi` from 0.142.2 to 0.143.0
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.142.2...0.143.0)

Updates `numpy` from 2.5.3 to 2.5.4
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](numpy/numpy@v2.5.3...v2.5.4)

Updates `pydantic` from 2.13.5 to 2.14.0
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](pydantic/pydantic@v2.13.5...v2.14.0)

Updates `sqlalchemy` from 2.1.3 to 2.1.4
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: asyncpg
  dependency-version: 0.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-minor-and-patch
- dependency-name: fastapi
  dependency-version: 0.143.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-minor-and-patch
- dependency-name: numpy
  dependency-version: 2.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: python-minor-and-patch
- dependency-name: pydantic
  dependency-version: 2.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: python-minor-and-patch
- dependency-name: sqlalchemy
  dependency-version: 2.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: python-minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* The README explains decision comments, and a pull-request template asks about them

A "Decision comments" section says what a `# decision:` comment marks, that the list
lives in the Climate TRACE pipeline repo, which four tagged decisions WasteMAP shares,
and that changing a tagged line needs a paired Climate TRACE pull request updating its
entry. A new pull-request template asks whether a change touches a tagged line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Seven more Climate TRACE modelling decisions are marked where SWEET applies them

Comments naming entries in RMI_Climate_TRACE_Waste_Methane's docs/MODEL_DECISIONS.md:
waste-before-1970-is-zero, intake-ends-before-close-year, intake-follows-population,
decay-rate-from-climate, methane-potential-by-waste-type, monthly-from-decay-model and
particulate-factors. Comments only; no number moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: seven decisions, not nine

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* City DST scenarios run for São Paulo and Brasília: load_csv_new normalizes a city's composition (#73)

* load_csv_new normalizes a city's composition, so the city DST runs for São Paulo and Brasília

WasteMAP's cities table publishes Brazil's precomputed rows with their source
composition as it is: São Paulo's ten Waste Components sum to 99%, Brasília's
to 87%, and Brazil's national default to 100.1%. load_csv_new was the one
loader that took a composition as published. implement_dst_changes_simple_v1_5
therefore rejected São Paulo and Brasília outright with "Invalid waste
fractions" (São Paulo by float rounding alone: 1 - 0.99 = 0.010000000000000009).
Every other Brazilian city's typed masses missed or exceeded its waste, and its
no-change scenario drifted from the baseline.

Normalize proportionally, as load_andre_params and dst_baseline_blank do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: load_csv_new normalizes a city's composition

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* A Custom Location buries or dumps its waste in every country (#74)

* A Custom Location buries or dumps its waste in every country

City.dst_baseline_blank looked a country's disposal split up with
.get(iso3, .get(region, 0)). Southern Asia, South-Eastern Asia and
Southern Africa have no row in fraction_open_dumped / fraction_landfilled
(no label mismatch: the other regional tables carry them as 0.0 # np.nan),
and Canada's, Germany's and Switzerland's country rows are all zeros, so
31 countries got a (0, 0, 0) split. Every landfill took 0% of the waste
and the city modelled no landfill methane; the except-KeyError fallback
never ran because .get never raises.

defaults_2019.disposal_split_for gives the split load_andre_params gives
a cities-table row with no landfill data: the country's row if it sends
waste anywhere, else the region's, else all landfill in
landfill_default_regions and all dumpsite elsewhere. load_andre_params is
unchanged, so the cities table doesn't move; the other 218 countries'
Custom Location output is bit-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* The changelog links the WasteMAP half

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A Custom Location's fallback split follows Climate TRACE's site-type rule

main's #83 moved load_andre_params's no-data fallback to
site_type_defaults.landfill_is_default. disposal_split_for still used
landfill_default_regions, so the parity test failed for Brunei (high income):
the cities table landfills it, a Custom Location dumped it. Calling the shared
rule makes the two agree for all 249 countries; only Brunei moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Moving waste to a city's existing gas capture changes its emissions (#75)

* Moving waste to a city's existing gas capture changes its emissions

The City DST's "move waste to existing gas capture" lever (move_gas and
existing_gas_pct in implement_dst_changes_simple_v1_5) rewrote split_fractions
in the generic branch but never passed the split to the landfills, whose waste
comes from their own fraction_of_waste. Only the new-landfill-with-capture block
did that, and WasteMAP switches that lever off for every city it offers this one
to. So the slider returned the no-change scenario at every setting for all 7
cities in the June 2026 table where it is live.

The arithmetic was wrong too: it multiplied the shares without capture by
existing / original, scaling them up (Lahore's landfill without capture came out
at 171% of its waste) and zeroing them for a city with no capture.

_move_to_existing_gas_capture gives capture existing_gas_pct and the landfill
and dumpsite without capture the rest, in their original proportions, and the
scenario applies that split to the landfills. A request the lever can't carry
out raises INVALID_PARAMETERS. The new-landfill sum check now builds CustomError
with both arguments; it raised TypeError, a 500 at the API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Link the changelog to the PRs it ships in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the add_gas oxidation known limitation: #78 fixes it

"Add gas capture" losing the oxidation it sets is fixed in #78 (with
RMI/WasteMAP#856), so this changelog no longer lists it as a known limitation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* The City DST's "add gas capture" applies the oxidation it sets (#78)

* The City DST's "add gas capture" applies the oxidation it sets

In the generic branch of implement_dst_changes_simple_v1_5, add_gas gives the
landfill without capture an oxidation factor of 0.22 after the implementation
year and the dumpsite it converts to a controlled dumpsite 0.1, and sets
skip_ox = True to keep them. The emissions loop at the end replaced that with
skip_ox = scenario_parameters.sites_method, which is false on this branch, so
Landfill.estimate_emissions reset both from gas_capture (still False) to 0.1
and 0: the gas capture applied, the oxidation never did. Each branch now sets
skip_ox itself and the loop uses it; the real-sites branch keeps True.

Every add_gas result falls another 10.0-13.3% after the implementation year; a
city that landfills everything goes from -60.0% to -65.3% against no change in
2040. Across WasteMAP's June 2026 table and a Custom Location in every country,
the no-change scenario, the other levers and every year up to the
implementation year are bit-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Link the changelog to the PRs it ships in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* A Custom Location in Kosovo builds, and the name "Kosovo" no longer means Serbia (#77)

* A Custom Location in Kosovo builds: SWEET's own country table before pycountry

pycountry has no Kosovo. _iso3_for asked it first, so XKX, the code SWEET's
defaults and WasteMAP's country list use, raised LookupError, and
dst_baseline_blank and cityparams_obj_for_blank_site raised "Country 'XKX' not
found." (WasteMAP's city tool answered 500). By name, the fuzzy search resolved
"Kosovo" to Serbia, so sdst_prepopulate prefilled a point in Kosovo as Serbia.

_iso3_for now tries region_lookup_iso3 by code, then country_to_iso3 by name,
both ignoring case, before pycountry. The other 249 codes WasteMAP offers
resolve as before, so their output is bit-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog links the paired WasteMAP PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* A blank or junk country is not found, and the site tool's prefill finds every country the geocoder names (#80)

* A Custom Location in Kosovo builds: SWEET's own country table before pycountry

pycountry has no Kosovo. _iso3_for asked it first, so XKX, the code SWEET's
defaults and WasteMAP's country list use, raised LookupError, and
dst_baseline_blank and cityparams_obj_for_blank_site raised "Country 'XKX' not
found." (WasteMAP's city tool answered 500). By name, the fuzzy search resolved
"Kosovo" to Serbia, so sdst_prepopulate prefilled a point in Kosovo as Serbia.

_iso3_for now tries region_lookup_iso3 by code, then country_to_iso3 by name,
both ignoring case, before pycountry. The other 249 codes WasteMAP offers
resolve as before, so their output is bit-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* A blank or junk country is not found; the prefill finds every country the geocoder names

_iso3_for ended with pycountry's fuzzy search, which scores every country for
an empty name and scores a country for any of its subdivisions that match. So
"" and " " were the United Kingdom (the most subdivisions), "None" Italy
(Frosinone), "nan" Thailand (Nan province), and None raised AttributeError
rather than LookupError.

_iso3_for now raises LookupError for anything that isn't a non-blank string,
strips the name, and takes the fuzzy answer only when the name is whole words
of that country's own name or its initials ("UK", "UAE").

The site tool's prefill (sdst_prepopulate) crashed with AttributeError for a
point Nominatim can't place or gives no country, and failed for 11 English names
Nominatim uses that neither SWEET nor pycountry spell its way (Turkey, Ivory
Coast, Cape Verde, ...). _iso3_at tries the geocoder's name, then its ISO 3166-1
code, and the prefill says "No country at <lat>, <lon>." where there is none.

Over 306 reverse-geocoded points (222 country names): 210 resolve as before,
the 11 now resolve, Sint Maarten moves from NLD to SXM. Every code and name in
SWEET's own table resolves as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Changelog: cite #80 and RMI/WasteMAP#859

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working model-output-change Changes model OUTPUT values (expected progress, not breaking); results differ from prior runs test Adds or modifies tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant