Skip to content

fix: #2204 recompute bin countdown at local midnight - #2205

Merged
robbrad merged 1 commit into
masterfrom
fix/2204-midnight-countdown
Aug 3, 2026
Merged

fix: #2204 recompute bin countdown at local midnight#2205
robbrad merged 1 commit into
masterfrom
fix/2204-midnight-countdown

Conversation

@robbrad

@robbrad robbrad commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

The main bin sensor's state ("In N days" / "Tomorrow" / "Today") and its days attribute are cached and only recomputed inside update_state(), which runs at init and on _handle_coordinator_update(). The entity doesn't poll and had no time-based recompute, so the countdown is frozen between coordinator refreshes — it only advances when new data is fetched.

This surfaced in #2204: the "Next Collection Date" attribute (read live from the stored date) looked correct while the main sensor's "in two days" text was wrong. That's the exact fingerprint of a frozen relative countdown — the absolute date is still a valid future date, but the days-until value is stale.

It's most visible alongside #2193 (where automatic polling was disabled entirely by the manual_refresh_only flip), but it's a real gap on its own: even with a healthy 12-hour refresh, the countdown would only update on coordinator ticks rather than at the day boundary, so it could read a day off.

Change

Register an async_track_time_change callback at local midnight (00:00:00) in async_added_to_hass. It recomputes the date-relative state and writes it, keeping the countdown correct independent of data fetches. Registered through async_on_remove so it's torn down with the entity.

Only the date-relative fields are recomputed from already-fetched coordinator data — this does not trigger any council scraping at midnight.

Tests

  • test_bin_sensor_midnight_recompute_advances_countdown — "In 5 days" → after a day passes with no new data, the midnight recompute yields "In 4 days" (and days == 4).
  • test_bin_sensor_registers_midnight_trackerasync_added_to_hass schedules the tracker at hour=0, minute=0, second=0.
poetry run pytest custom_components/uk_bin_collection/tests/test_sensor.py

All 69 sensor tests pass locally; black clean.

Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Bin collection sensors now automatically refresh their date-based countdowns at local midnight.
    • Sensor states remain accurate even when no coordinator update occurs.
  • Tests

    • Added coverage for midnight countdown updates and daily refresh scheduling.

The main bin sensor caches its state string ("In N days" / "Tomorrow" /
"Today") and the `days` attribute, recomputing them only when the
coordinator pushes an update. Between coordinator refreshes the countdown
is frozen, so a sensor whose data updates infrequently (or, per #2193, not
at all in the mislabelled manual-refresh mode) shows a stale "In N days"
while the underlying next-collection date remains correct — exactly the
symptom reported in #2204.

Register an `async_track_time_change` callback at local midnight that
recomputes the date-relative state and writes it, so the countdown stays
correct independent of data fetches. Cleaned up automatically via
`async_on_remove`.

Adds tests for the midnight recompute advancing the countdown and for the
tracker being registered on `async_added_to_hass`.

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

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The sensor now tracks local midnight changes. Each main bin sensor recalculates its date-relative state and days remaining, then writes the updated state. Tests cover countdown updates and callback registration.

Changes

Midnight refresh

Layer / File(s) Summary
Register and validate midnight refresh
custom_components/uk_bin_collection/sensor.py, custom_components/uk_bin_collection/tests/test_sensor.py
The sensor registers a removable local-midnight callback. The callback recalculates the collection state and days remaining, then writes the entity state. Tests verify countdown updates and callback registration.

Estimated code review effort: 3 (Moderate) | ~15 minutes

Possibly related PRs

🚥 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 describes the main change: recomputing the bin countdown at local midnight.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/2204-midnight-countdown

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)
custom_components/uk_bin_collection/tests/test_sensor.py (1)

1541-1551: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify the callback and cleanup registration.

The test verifies only the midnight fields. It does not verify that async_track_time_change receives sensor._async_midnight_update or that async_on_remove stores the unsubscribe callback. A change that leaves a daily callback active after entity removal will pass this test.

Proposed test assertions
-    mock_track.assert_called_once()
-    # Fires at local midnight (00:00:00).
-    assert mock_track.call_args.kwargs == {"hour": 0, "minute": 0, "second": 0}
+    mock_track.assert_called_once_with(
+        hass,
+        sensor._async_midnight_update,
+        hour=0,
+        minute=0,
+        second=0,
+    )
+    sensor.async_on_remove.assert_called_once_with(mock_track.return_value)
🤖 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 `@custom_components/uk_bin_collection/tests/test_sensor.py` around lines 1541 -
1551, Extend the test around async_added_to_hass to assert that
async_track_time_change receives sensor._async_midnight_update as its callback
and that the unsubscribe function it returns is registered through
sensor.async_on_remove. Preserve the existing midnight argument assertions.
🤖 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 `@custom_components/uk_bin_collection/tests/test_sensor.py`:
- Around line 1541-1551: Extend the test around async_added_to_hass to assert
that async_track_time_change receives sensor._async_midnight_update as its
callback and that the unsubscribe function it returns is registered through
sensor.async_on_remove. Preserve the existing midnight argument assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 19736fbc-8656-46a0-bcc0-01b7a9403d47

📥 Commits

Reviewing files that changed from the base of the PR and between e0a16ca and 46931aa.

📒 Files selected for processing (2)
  • custom_components/uk_bin_collection/sensor.py
  • custom_components/uk_bin_collection/tests/test_sensor.py

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.30%. Comparing base (9070968) to head (46931aa).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2205      +/-   ##
==========================================
+ Coverage   80.04%   83.30%   +3.26%     
==========================================
  Files          12       12              
  Lines        1393     1402       +9     
==========================================
+ Hits         1115     1168      +53     
+ Misses        278      234      -44     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@robbrad
robbrad merged commit 96e91c8 into master Aug 3, 2026
26 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.

1 participant