Skip to content

fix: pair Lichfield bin headings with dates within their own card - #2187

Closed
alec-pinson wants to merge 1 commit into
robbrad:masterfrom
alec-pinson:fix/lichfield-bin-date-pairing
Closed

fix: pair Lichfield bin headings with dates within their own card#2187
alec-pinson wants to merge 1 commit into
robbrad:masterfrom
alec-pinson:fix/lichfield-bin-date-pairing

Conversation

@alec-pinson

@alec-pinson alec-pinson commented Jul 25, 2026

Copy link
Copy Markdown

What

LichfieldDistrictCouncil collects two page-wide lists and zips them together by index:

bins  = soup.find_all("h3", class_="bin-collection-tasks__heading")
dates = soup.find_all("p",  class_="bin-collection-tasks__date")
for i in range(len(dates)):
    bint = " ".join(bins[i].text.split()[2:4])
    date = dates[i].text

That holds only while every heading has a corresponding date element. Two things on the current page break the assumption:

  1. The calendar download link (Download your bin collection calendar) reuses the bin-collection-tasks__heading class but is not a bin, so it pads bins.
  2. A bin with no scheduled collection renders a bin-collection-tasks__frequency (Collected every Monday) instead of a bin-collection-tasks__date. Food Waste Caddy is in this state on at least some addresses.

When a dateless card sits before the others, every bin is shifted onto the following bin's date and the final bin drops off the end of the loop. The failure is silent — output stays schema-valid, it's just wrong.

Reproduction

This affects the repo's own test UPRN for Lichfield, 100031694085. Ground truth from the page:

Card Date shown
Black Bin 30th July
Food Waste Caddy (none — "Collected every Thursday")
Blue Bin 6th August
Brown Bin 6th August
Blue Bag 6th August

Before this PR:

{"bins": [
  {"type": "Black Bin",  "collectionDate": "30/07/2026"},
  {"type": "Food Waste", "collectionDate": "06/08/2026"},
  {"type": "Blue Bin",   "collectionDate": "06/08/2026"},
  {"type": "Brown Bin",  "collectionDate": "06/08/2026"}
]}

Food Waste is given a date the page never showed, and Blue Bag is missing entirely.

After this PR:

{"bins": [
  {"type": "Black Bin", "collectionDate": "30/07/2026"},
  {"type": "Blue Bin",  "collectionDate": "06/08/2026"},
  {"type": "Brown Bin", "collectionDate": "06/08/2026"},
  {"type": "Blue Bag",  "collectionDate": "06/08/2026"}
]}

On a second address (394018646) the misalignment was more visible, because the dateless Food Waste Caddy card is first:

Before After Page
Food Waste 27/07 (omitted) no date
Blue Bin 27/07 27/07 27th July
Blue Bag 03/08 27/07 27th July
Black Bin missing 03/08 3rd August

How

  • Iterate the headings and read the date from within the same card, skipping cards that have no date. Removes the positional coupling between the two lists.
  • Derive the bin name from the heading with its visually-hidden Your next / collection spans removed, rather than slicing words [2:4]. The slice truncated three-word names (Food Waste CaddyFood Waste). Prefix/suffix stripping is kept as a fallback should those spans ever go away.

No change to the date parsing or year-rollover logic.

Notes

Reported downstream as bins going unavailable in Home Assistant — once a bin stops appearing in the output the coordinator stops creating its entities. Likely related to #1905, which presented the same way and was closed as council-side; this looks like the underlying parser fragility that made the page change bite.

Verified against the live site for both UPRNs above. black --check clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved bin collection information parsing for Lichfield District Council.
    • Preserved multi-word bin names while removing hidden display text.
    • Improved matching between each bin type and its scheduled collection date.
    • Excluded bins without a scheduled collection date to prevent incomplete results.

LichfieldDistrictCouncil built two page-wide lists and zipped them by
index:

    bins  = soup.find_all("h3", class_="bin-collection-tasks__heading")
    dates = soup.find_all("p",  class_="bin-collection-tasks__date")
    for i in range(len(dates)):
        bint = " ".join(bins[i].text.split()[2:4])
        date = dates[i].text

That only holds while every heading has a matching date. Two things on
the page break it:

- The calendar download link ("Download your bin collection calendar")
  reuses the bin-collection-tasks__heading class but is not a bin.
- A bin with no scheduled collection renders a
  bin-collection-tasks__frequency ("Collected every Monday") instead of
  a bin-collection-tasks__date. Food Waste Caddy is currently in this
  state on at least some addresses.

When a dateless card appears before the others, every bin is shifted
onto the following bin's date and the last bin is dropped entirely.
This is silent - the output stays schema-valid, it is just wrong.

The repository's own test UPRN (100031694085) is affected. Ground truth
from the page:

    Black Bin         30th July
    Food Waste Caddy  (no date - collected every Thursday)
    Blue Bin          6th August
    Brown Bin         6th August
    Blue Bag          6th August

Before:

    Black Bin   30/07/2026
    Food Waste  06/08/2026   <- invented, the page shows no date
    Blue Bin    06/08/2026
    Brown Bin   06/08/2026
    (Blue Bag missing)

After:

    Black Bin   30/07/2026
    Blue Bin    06/08/2026
    Brown Bin   06/08/2026
    Blue Bag    06/08/2026

Iterate the headings instead and read the date from the same card,
skipping cards that have no date.

Bin names now come from the heading with its visually-hidden "Your
next" / "collection" spans removed, rather than slicing words 2:4.
The slice truncated three-word names ("Food Waste Caddy" -> "Food
Waste"). Prefix/suffix stripping is kept as a fallback in case those
spans go away.

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

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

LichfieldDistrictCouncil.parse_data now derives cleaned bin names and matches each bin heading with a date from the same card, skipping cards without scheduled dates.

Changes

Lichfield bin parsing

Layer / File(s) Summary
Card-local bin extraction
uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py
Adds bin-name normalization that excludes visually hidden text and replaces index-based pairing with card-local date extraction, skipping unscheduled cards.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: pairing Lichfield bin headings with dates inside each card.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py (1)

40-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the card-local pairing.

Please add fixtures asserting that a dateless card does not shift subsequent dates, calendar headings are ignored, and multi-word names such as “Food Waste Caddy” are preserved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py`
around lines 40 - 71, The current card-local parsing logic lacks regression
fixtures for its key pairing and heading-handling cases. Add fixtures and tests
covering a dateless card, ensuring later cards retain their own dates; calendar
download headings are ignored; and multi-word names such as “Food Waste Caddy”
are preserved by bin_name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py`:
- Around line 40-71: The current card-local parsing logic lacks regression
fixtures for its key pairing and heading-handling cases. Add fixtures and tests
covering a dateless card, ensuring later cards retain their own dates; calendar
download headings are ignored; and multi-word names such as “Food Waste Caddy”
are preserved by bin_name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d4dbe72c-67d7-4d1c-abcb-8a739bfc2652

📥 Commits

Reviewing files that changed from the base of the PR and between 36d9205 and 97667fa.

📒 Files selected for processing (1)
  • uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py

@alec-pinson

Copy link
Copy Markdown
Author

Lichfield recently sent out Food waste bins in the past week or 2 and broke the black bin
image

Tested the fix:
image

Will get claude to review code rabbit nitpick

@alec-pinson

Copy link
Copy Markdown
Author

Re the nitpick asking for regression coverage on the card-local pairing:

The principle is right, and arguably this bug is the argument for it — the mis-pairing was silent precisely because the shifted output stayed schema-valid. The existing checks can't catch that class of failure by construction.

The obstacle is that there's no precedent for it here. As far as I can tell:

  • uk_bin_collection/tests/ has no per-council unit tests and no HTML fixtures
  • councils are validated only by validate_council_outputs.feature, which scrapes the live site and validates the result against output.schema
  • nothing imports a council module directly for offline parsing

So adding fixtures for Lichfield alone would introduce an offline test pattern that none of the other 351 councils follow. That seemed like a maintainer decision rather than something to fold into a single-council bugfix, so I've left this PR as just the fix.

@robbrad — happy to add offline fixture tests here (dateless card not shifting subsequent dates, calendar heading ignored, multi-word Food Waste Caddy preserved by bin_name) if you'd like that pattern established, or to keep this PR minimal and raise the test-infrastructure question separately. Either works — let me know which you'd prefer.

robbrad commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Thanks for this — the visually-hidden-span extraction approach for the bin name is cleaner than the original word-slicing and is exactly what got used. #2179 fixed the same underlying bug independently and also handled a case yours doesn't: a card with no date but a recognizable weekly frequency (e.g. Food Waste Caddy) now gets a derived next-occurrence date instead of being dropped from the output entirely, which matters for keeping that Home Assistant entity from going unavailable. Rather than pick one over the other, I synthesized both into a single fix in the consolidated "July Part 2" release: #2189 — your heading-text extraction combined with #2179's frequency fallback and year-rollover fix. Closing this one out — credited in the PR.


Generated by Claude Code

@robbrad robbrad closed this Jul 25, 2026
pull Bot pushed a commit to mrw298/UKBinCollectionData that referenced this pull request Jul 25, 2026
LichfieldDistrictCouncil zipped a page-wide heading list and a
page-wide date list together by index. Two things on the current page
break that: the calendar-download link reuses the heading class but
has no date, and a bin with no scheduled collection (Food Waste Caddy)
renders a "Collected every <day>" frequency instead of a date. Either
one shifts every later bin onto the wrong date and drops the last bin.

Iterate each card and read its own date instead of pairing by
position. When a card has no date but does have a weekly frequency,
derive the next occurrence of that weekday so the entity keeps
reporting (previously it silently inherited a neighboring bin's date).
Extract the bin name by removing the visually-hidden heading spans
rather than slicing words, so three-word names like "Food Waste Caddy"
survive; that value is then mapped back to "Food Waste" to preserve
the existing Home Assistant entity identity. Also fix year rollover to
compare against today's date directly instead of a month heuristic.

Synthesizes PR robbrad#2179 by JasSmiths and PR robbrad#2187 by alec-pinson, which
independently reported and fixed this bug.
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.

2 participants