Skip to content

fix: November council fixes from 9 contributor PRs (Gedling, Canterbury, Newcastle, Charnwood, Lancaster, East Herts, East Suffolk, Torfaen, East Hampshire) - #2343

Merged
robbrad merged 25 commits into
masterfrom
claude/november-release
Nov 14, 2026
Merged

robbrad merged 25 commits into
masterfrom
claude/november-release

Conversation

@robbrad

@robbrad robbrad commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Please merge with a merge commit or rebase, not a squash

This branch carries 15 commits authored by the original contributors, cherry-picked without -n so each keeps their authorship. Squashing would re-author all of them to whoever merges (the problem described in the playbook after #2276). My follow-up commits are separate and labelled. Every commit title is conventional (fix:/test:/docs:), so the release triggers either way.

Included from contributor PRs

From Author Council What
#2315 @InertiaUK East Hampshire adds the missing LAD24CD (E07000085)
#2314 @InertiaUK Torfaen sends a desktop User-Agent (the site 403s HeadlessChrome) and adds LAD24CD
#2313 @InertiaUK Newcastle address matching is whole-word and postcode-constrained, and raises instead of falling back to parcels[0]
#2323 @adammccormack162 Charnwood restores legacy property-URL support, strictly validated
#2324 @craiggraham Lancaster splits the red and yellow recycling streams
#2336 @Russell-IO East Herts follows the council's new lookup id and reports the council's own error
#2308 @InertiaUK Canterbury moves to the council's new bin-dates endpoint (old one 403'd for everyone, see #2215)
#2300 @bfayers East Suffolk strips bracketed bin descriptors
#2295 @ryanocallaghan Gedling rewrites onto the council's new Netcall portal with plain requests
#2330 contributor HA calendar events overlapping the requested range are returned (was start-only)
#2310 @InertiaUK Wolverhampton handles the council's new page layout

My follow-up commits

I read every diff and added what I'd have asked the contributors for:

  • Canterbury: a 403 now says what it usually means instead of a bare HTTPError; 6 tests (PR had none).
  • East Suffolk: the bracket rule replaced the " - standard bin" rule, so old wording would regress. Both handled in a testable function; 7 tests.
  • Newcastle: 12 tests pinning that "1" must not match 10, 11, 1A or a flat in No. 12 (the failure that silently returns another household's days).
  • Gedling: two addresses starting the same way now raise and list them; a page with no collections raises instead of returning []; 2 tests.
  • HA calendar: BST-aware test that fails on the old code.
  • Wolverhampton: address matching is start-anchored and never guesses (it previously matched the number anywhere and fell back to the first address); only the innermost col-* columns are read (the outer wrapper repeated its first service); dates with/without a year; raises if no bins found. 25 tests, mutation-checked.
  • Nuneaton & Bedworth (Targets #2334): five Exception(...) objects were built and never raised, so an unknown street returned an empty result and a failed street page crashed with UnboundLocalError. They now raise; an exact street name wins over longer ones containing it; ambiguity lists the candidates. 7 tests, all fail on the old code.
  • Sevenoaks (Targets #2305): the dropdown was matched with paon in option.text, so "40" selected "140 ..." / "Flat 40" or nothing, and the page then timed out. Now start-anchored, whole-word, raises on none/ambiguous. 9 tests added; the existing test's mock dropdown had no "select an address" prompt, so I gave it one like the real page.
  • docs: November notes in ISSUE_RESOLUTION_PROGRESS.md and a lessons section in the playbook.

Heads-up for the maintainer

  • Lancaster is a breaking change for Home Assistant users. The single "Recycling" sensor becomes "Recycling Red" and "Recycling Yellow". Worth a line in the release notes.
  • Gedling's test data looks residential. input.json gets NG5 6NQ / 11 (Gedling Road, Arnold). I haven't changed it because I can't verify a replacement offline. Decision for you.
  • Live evidence is limited: this sandbox can't reach council sites, and CI can't reach Lancaster (Whitespace). The earlier CI run on the contributor batch: 6 of 8 council integration tests passed; Lancaster CI-blocked; Canterbury unverifiable (its API rejects unrecognised callers). Wolverhampton, Nuneaton and Sevenoaks have only offline tests so far; their CI run is on the latest head.
  • Also noticed, not fixed here: Lancaster's address loop keeps the last matching address (its comment says "first").

Not included, with reasons

Test plan

  • make unit-tests on Python 3.14 with Home Assistant at the latest head: 630 passed; the 7 failures are the 3 sandbox-only network tests plus Orkney and Broxbourne ×3, all fixed by test: stop the Orkney and Broxbourne tests failing on the calendar and the network #2341.
  • Every commit passes commitlint with the same rules as lint.yml.
  • Offline parity: no missing or orphaned council keys.
  • Contributor commits applied cleanly with authorship preserved.
  • CI live results for the latest head: pending.

🤖 Generated with Claude Code

https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4

@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6c749914-1909-4b62-b49a-c977b90af3e1
Commits

Reviewing files that changed from the base of the PR and between 1fb4588 and a1c2617.

Files selected for processing: 26

  • ISSUE_RESOLUTION_PROGRESS.md
  • custom_components/uk_bin_collection/calendar.py
  • custom_components/uk_bin_collection/tests/test_calendar.py
  • docs/MAINTAINER_PLAYBOOK.md
  • uk_bin_collection/tests/input.json
  • uk_bin_collection/tests/test_canterbury_city_council.py
  • uk_bin_collection/tests/test_charnwood_borough_council.py
  • uk_bin_collection/tests/test_east_herts_council.py
  • uk_bin_collection/tests/test_east_suffolk_council.py
  • uk_bin_collection/tests/test_gedling_borough_council.py
  • uk_bin_collection/tests/test_lancaster_city_council.py
  • uk_bin_collection/tests/test_newcastle_city_council.py
  • uk_bin_collection/tests/test_nuneaton_bedworth_borough_council.py
  • uk_bin_collection/tests/test_sevenoaks_district_council.py
  • uk_bin_collection/tests/test_wolverhampton_city_council.py
  • uk_bin_collection/uk_bin_collection/councils/CanterburyCityCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/EastSuffolkCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/GedlingBoroughCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/NewcastleCityCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/SevenoaksDistrictCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/TorfaenCouncil.py
  • uk_bin_collection/uk_bin_collection/councils/WolverhamptonCityCouncil.py
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@codecov

codecov Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
11 2 9 0
View the top 1 failed test(s) by shortest run time
uk_bin_collection.tests.step_defs.test_validate_council::test_scenario_outline[LancasterCityCouncil]
Stack Traces | 0.444s run time
fixturefunc = <function scrape_step at 0x738aa34e0250>
request = <FixtureRequest for <Function test_scenario_outline[LancasterCityCouncil]>>
kwargs = {'context': <test_validate_council.Context object at 0x738aa35470e0>, 'headless_mode': 'True', 'local_browser': 'False', 'selenium_url': 'http://localhost:4444'}

    def call_fixture_func(
        fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
    ) -> FixtureValue:
        if inspect.isgeneratorfunction(fixturefunc):
            fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc)
            generator = fixturefunc(**kwargs)
            try:
                fixture_result = next(generator)
            except StopIteration:
                raise ValueError(f"{request.fixturename} did not yield a value") from None
            finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
            request.addfinalizer(finalizer)
        else:
            fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)
>           fixture_result = fixturefunc(**kwargs)
                             ^^^^^^^^^^^^^^^^^^^^^

../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14.../site-packages/_pytest/fixtures.py:1005: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../tests/step_defs/test_validate_council.py:101: in scrape_step
    context.parse_result = CollectData.run()
                           ^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/collect_data.py:109: in run
    return self.client_code(
uk_bin_collection/uk_bin_collection/collect_data.py:130: in client_code
    return get_bin_data_class.template_method(address_url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/get_bin_data.py:71: in template_method
    bin_data_dict = self.get_and_parse_data(this_url, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/get_bin_data.py:94: in get_and_parse_data
    bin_data_dict = self.parse_data("", url=address_url, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.../uk_bin_collection/councils/LancasterCityCouncil.py:53: in parse_data
    response = session.get(portal_link)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14........./site-packages/requests/sessions.py:671: in get
    return self.request("GET", url, params=params, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14........./site-packages/requests/sessions.py:635: in request
    prep = self.prepare_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14........./site-packages/requests/sessions.py:541: in prepare_request
    p.prepare(
../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14....../site-packages/requests/models.py:439: in prepare
    self.prepare_url(url, params)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <PreparedRequest [GET]>, url = '', params = OrderedDict()

    def prepare_url(
        self,
        url: _t.UriType,
        params: _t.ParamsType,
    ) -> None:
        """Prepares the given HTTP URL."""
        #: Accept objects that have string representations.
        #: We're unable to blindly call unicode/str functions
        #: as this will include the bytestring indicator (b'')
        #: on python 3.x.
        #: https://github..../requests/pull/2238
        if isinstance(url, bytes):
            url = url.decode("utf8")
        else:
            url = str(url)
    
        # Remove leading whitespaces from url
        url = url.lstrip()
    
        # Don't do any URL preparation for non-HTTP schemes like `mailto`,
        # `data` etc to work around exceptions from `url_parse`, which
        # handles RFC 3986 only.
        if ":" in url and not url.lower().startswith("http"):
            self.url = url
            return
    
        # Support for unicode domain names and paths.
        try:
            scheme, auth, host, port, path, query, fragment = parse_url(url)
        except LocationParseError as e:
            raise InvalidURL(*e.args)
    
        if not scheme:
>           raise MissingSchema(
                f"Invalid URL {url!r}: No scheme supplied. "
                f"Perhaps you meant https://{url}?"
            )
E           requests.exceptions.MissingSchema: Invalid URL '': No scheme supplied. Perhaps you meant https://?

../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14....../site-packages/requests/models.py:514: MissingSchema
View the full list of 1 ❄️ flaky test(s)
uk_bin_collection.tests.step_defs.test_validate_council::test_scenario_outline[CanterburyCityCouncil]

Flake rate in main: 99.71% (Passed 2 times, Failed 693 times)

Stack Traces | 0.382s run time
fixturefunc = <function scrape_step at 0x79fb070d4720>
request = <FixtureRequest for <Function test_scenario_outline[CanterburyCityCouncil]>>
kwargs = {'context': <test_validate_council.Context object at 0x79fb06f7bb60>, 'headless_mode': 'True', 'local_browser': 'False', 'selenium_url': 'http://localhost:4444'}

    def call_fixture_func(
        fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
    ) -> FixtureValue:
        if inspect.isgeneratorfunction(fixturefunc):
            fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc)
            generator = fixturefunc(**kwargs)
            try:
                fixture_result = next(generator)
            except StopIteration:
                raise ValueError(f"{request.fixturename} did not yield a value") from None
            finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
            request.addfinalizer(finalizer)
        else:
            fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)
>           fixture_result = fixturefunc(**kwargs)
                             ^^^^^^^^^^^^^^^^^^^^^

../../../..../pypoetry/virtualenvs/uk-bin-collection-EwS6Gn8s-py3.14/lib/python3.14.../site-packages/_pytest/fixtures.py:1005: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../tests/step_defs/test_validate_council.py:101: in scrape_step
    context.parse_result = CollectData.run()
                           ^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/collect_data.py:109: in run
    return self.client_code(
uk_bin_collection/uk_bin_collection/collect_data.py:130: in client_code
    return get_bin_data_class.template_method(address_url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/get_bin_data.py:71: in template_method
    bin_data_dict = self.get_and_parse_data(this_url, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
uk_bin_collection/uk_bin_collection/get_bin_data.py:94: in get_and_parse_data
    bin_data_dict = self.parse_data("", url=address_url, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <CanterburyCityCouncil.CouncilClass object at 0x79fb06e2c2f0>, page = ''
kwargs = {'url': 'https://www.canterbury.gov.uk', 'postcode': None, 'paon': None, 'uprn': '10094583181', ...}

    def parse_data(self, page: str, **kwargs) -> dict:
        user_uprn = kwargs.get("uprn")
        check_uprn(user_uprn)
        bindata = {"bins": []}
    
        # The API keys on the UPRN; the USRN is required in the payload but
        # its value is not used for the lookup.
        data = {"uprn": str(user_uprn), "usrn": "1"}
    
        # The API Gateway rejects requests without a browser-like User-Agent.
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
            "Accept": "application/json",
            "Origin": "https://www.canterbury.gov.uk",
            "Referer": "https://www.canterbury.gov.uk/",
        }
    
        response = requests.post(self.API_URL, json=data, headers=headers, timeout=30)
        if response.status_code == 403:
>           raise ConnectionError(
                "Canterbury's bin collection API returned 403 Forbidden. It "
                "rejects callers it doesn't recognise, so this is a block on "
                "your network or an outage on the council's side, not a bad UPRN."
            )
E           ConnectionError: Canterbury's bin collection API returned 403 Forbidden. It rejects callers it doesn't recognise, so this is a block on your network or an outage on the council's side, not a bad UPRN.

.../uk_bin_collection/councils/CanterburyCityCouncil.py:53: ConnectionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@robbrad
robbrad force-pushed the claude/november-release branch from 794ee32 to c9092c4 Compare November 14, 2026 06:34
@robbrad
robbrad force-pushed the claude/november-release branch from c9092c4 to 5cecd3f Compare November 14, 2026 06:35
craiggraham and others added 19 commits November 14, 2026 06:39
Lancaster's portal lists "Recycling Red Collection Service" and "Recycling
Yellow Collection Service", but prefix matching against "Recycling" collapsed
both into a single Recycling bin. Match the specific streams first, keeping
"Recycling" as the fallback for the unspecified-stream wording.
East Herts retired AchieveForms lookup 683d9ff0e299d. It still answers
HTTP 200, but with {"error": "User Package  Not Found"}, so the scraper
crashed with KeyError: 'integration'. The "When are my Bin Collection
days?" form now calls lookup 6accfd6441228. The form and process URIs,
the posted field (Collection Days / inputUPRN) and the response path
(integration.transformed.rows_data["0"]) are unchanged.

- Use the new lookup id.
- Raise a ValueError naming the council's message when the lookup
  returns an error body, so the next form change is obvious.
- Strip the whitespace the council now wraps around service names.
- Sort bins by collection date.
…a browser

Gedling now publishes bin days through a Netcall Liberty Create portal
(waste.digital.gedling.gov.uk). The gbcbincalendars.co.uk API the scraper
used reads the old calendars: it takes 30-45 seconds, a bare postcode
always fails, and many addresses fail with "Failed to get yearly
calendar page".

Search the portal by postcode, choose the address that starts with the
house number or name, and read its next four weeks of collections. Plain
requests, about 1.5 seconds. Takes postcode and house_number, as the
current scraper does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instead of searching a malformed URL, which would surface as a
misleading "no addresses for postcode" error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new endpoint is called without the old outage handling, so a 403 would
surface as a bare HTTPError. Say what it usually means (a network block or
a council-side outage, not a bad UPRN). Adds tests for both response shapes,
the bin-type mapping and ordering, the 403 message and the no-data errors,
which the original change did not include.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…kets

The bracket-stripping change replaced the " - standard bin" rule, so any row
still using that wording would regress. Both are handled now, in a small
function so each wording is covered by a test, and a bracket in the middle of
a name no longer leaves a double space.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…ther home

The address search is a fuzzy prefix match, and the previous code fell back to
the first parcel when nothing matched, which silently returned a different
household's days. The contributor's change makes the match whole-word and
postcode-constrained; these tests lock that in ("1" must not match 10, 11,
1A or a flat in No. 12; an unknown number or wrong postcode is refused).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
If two addresses at a postcode started the same way the first was chosen
silently, which returns another household's collection days. It now raises
and lists the matches. A page that parses to no dated collections also
raises instead of returning an empty list (which surfaced downstream as an
opaque "[] should be non-empty"). Tests added for both.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
async_get_events compared collection_date <= end_date.date(), so a
collection on the day a midnight-bounded range ends was returned. Asking
for tomorrow's events (tomorrow 00:00 to the day after 00:00) returned
the day after tomorrow's collection, and asking for today's events
returned tomorrow's, so dashboards built on calendar.get_events showed
bin days one day early.

Compare the all-day event's [start, start + 1 day) span against the
requested range with an exclusive end instead.

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

The contributor's tests use naive datetimes. Home Assistant localises both
dates (dt_util.as_local) before calling async_get_events, so in summer a
"tomorrow" range starts at 23:00 UTC the day before. These tests use that real
shape (Europe/London, BST) and assert that today's, tomorrow's and the day
after's collections each land in exactly one of the two ranges. They fail on
the old inclusive-end code and pass with the fix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…olumns

Follow-up to the layout fix (#2310, which found the council had moved from
col-md-4 to col-md-3 and dropped the year from dates; this is why #2334
reports a blank result).

- Address matching could return another household. The fallback matched the
  house number anywhere in the label, and with no house number the first
  address for the postcode was taken. It now matches only at the start of the
  label, raises when more than one address starts the same way, and accepts a
  missing house number only for a postcode with a single address.
- Matching every col-* div also matched outer wrapper columns, which pair
  their first heading with the first date they contain. If the first service
  has no date, that is the next service's date under the wrong name. Only the
  innermost columns are read now, and repeated (service, date) pairs dropped.
- The page parsing and address matching are pure functions with 25 tests
  built from the layouts in the PR, using fixed "today" values so none expire.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…ad of returning nothing

The council's error paths built Exception(...) objects without raising them,
so an unknown or ambiguous street fell through to an empty result (or an
UnboundLocalError on the street page). Raise real errors, prefer an exact
street-name match, and list the candidates when more than one street matches.

Targets #2334

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…not one containing it

The dropdown was matched with `paon in option.text`, so "40" selected the
first label containing 40 anywhere ("140 ...", "Flat 40") or nothing at all,
after which the page timed out waiting for collections. Match whole-word at the
start of the label and raise on no match or on ambiguity.

Targets #2305

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…ybook

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
…address

The contributor's input.json entry and test fixtures used a residential
address. Use Gedling Civic Centre (NG5 6LU) in input.json and a fictional
address on the same postcode in the unit-test fixtures.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MaCS29izthZp2dwtvLwf4
@robbrad
robbrad force-pushed the claude/november-release branch from 5cecd3f to a1c2617 Compare November 14, 2026 06:39
@robbrad
robbrad merged commit 939b84a into master Nov 14, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants