Repository navigation
fix(component_base): stop components reading calculate_yesterday's faked minutes_now - #5086
Conversation
…ked minutes_now The other half of GH#4804. #5054 stopped components seeing the midnight_utc that calculate_yesterday() (output.py) rewinds; it fakes minutes_now to 0 in the same breath, and that passthrough was left alone because it could not be fixed safely at the time - base.minutes_now was measured from a different clock than now_utc, so deriving it would have made components disagree with the planner wherever the container's timezone differed from the timezone: setting. #5084 removed that second clock, so the derivation is now exactly the calculation update_time() makes - through the same helper. 0 is the nastier of the two fakes: a rewound date is obviously wrong, while 0 reads as a legitimate "just after midnight". It reaches 39 component-side call sites - octopus.py's dispatch-slot arithmetic (which stops dropping expired slots and shifts every published offset), solcast.py's actual-PV alignment, load_ml_component.py's slot alignment, web.py's SOC history, and the AlphaESS/Deye/Sunsynk adapters that pick the live TOU slot by time of day while writing to a real inverter. The AlphaESS test doubles set base.minutes_now directly on a MagicMock base, so they answered arithmetic with more mocks rather than failing; they get a real clock and a set_mock_clock() helper that moves it. Mutation-tested: reverting the property fails the new ComponentBase test with "Faked base.minutes_now leaked into the component: 0". ./run_all --quick green (314 tests) under both a mismatched host timezone and TZ=Europe/London; pre-commit clean. Stacked on #5084 - merge that first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Three moderate findings remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This follow-up prevents components from reading calculate_yesterday()’s temporary minutes_now = 0 by deriving time from the stable clock.
Changes:
- Derives component minutes from shared time state.
- Adds rewind regression coverage.
- Updates AlphaESS tests with coherent mock clocks.
File summaries
| File | Review summary |
|---|---|
apps/predbat/component_base.py |
Two moderate findings: snapshot now_utc consistently (3 votes) and update Deye/Sunsynk test doubles (2 votes). |
apps/predbat/tests/test_component_base.py |
Moderate finding (3 votes): restore all fields mutated by update_time(). |
apps/predbat/tests/test_alphaess_control.py |
Reviewed mock-clock updates. |
apps/predbat/tests/test_alphaess_api.py |
Reviewed coherent mock-clock handling. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…e mock bases All three review findings held up. - minutes_now read base.now_utc twice: once directly and once through self.midnight_utc, which goes back to the same field. update_time() runs on the main thread while components run on theirs, so the two reads can straddle it - at a day boundary that subtracts the new day's midnight from the old day's timestamp and collapses 23:59 to 0, reading as midnight rather than as an error. Snapshot it once instead. A base whose clock rolls over between reads reproduces that deterministically, with no threads (mutation-tested). - The Deye and Sunsynk doubles carried the same MagicMock base AlphaESS did, so the derivation got arithmetic of its own rather than failing: their comment promised "tests set this for time-aware control" while a test setting base.minutes_now would have been silently ignored. Both get a real clock and the same set_mock_clock() helper. - The new ComponentBase test restored three of the seven fields update_time() writes, leaking this test's wall-clock state into later ones. It restores the full set now, as tests/test_clock.py already does. ./run_all --quick green (314 tests), pre-commit clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Posted by Claude on behalf of @springfall2008. All three findings held up this time. Fixed in d4eb4b6. 1. Once directly, once through Snapshotted once now. A base whose 2. Deye and Sunsynk doubles — real, same shape as AlphaESS. Confirmed: 3. Incomplete restore in the new test — real. It saved three of the seven fields
|
Posted by Claude on behalf of @springfall2008.
Stacked on #5084 — merge that first. Base is set to its branch, so this diff shows only the change itself.
The other half of #4804. #5054 stopped components seeing the
midnight_utcthatcalculate_yesterday()rewinds. It fakesminutes_nowto0in the same breath (output.py:3291), and that passthrough was deliberately left alone at the time:base.minutes_nowwas measured from a different clock thannow_utc, so deriving it would have made components disagree with the planner wherever the container's timezone differed from thetimezone:setting. #5084 removes that second clock, so the derivation is now exactly the calculationupdate_time()makes — through the same helper.Why this one matters more than the date
A rewound date is obviously wrong.
0reads as a legitimate "just after midnight", so it fails quietly. It reaches 39 component-side call sites:minutes_now = 0octopus.pyend_minutes <= self.minutes_nowstops dropping expired dispatch slots;start_offset = start_minutes - self.minutes_nowshifts every published offset by the real time of dayload_ml_component.pyminutes_now % 30slot alignment, history windows, ML feature payloadsolcast.pyminute_absolute = self.minutes_now - minute, today's actual-PV alignmentweb.pyalphaess.py,deye.py,sunsynk.py_now_minutes()/_window_active()pick the live TOU slot while writing to a real inverter —0reads as midnightaxle.pyalso readsbase.minutes_now, butload_axle_slot()is called fromfetch.pyon the base's own thread — it should see the faked values, and this does not touch it.Tests
ComponentBasegets a test that compares the derived value againstupdate_time()'s on the real base rather than restating the formula, so the two cannot drift, plus the rewind case itself.The AlphaESS doubles set
base.minutes_nowon aMagicMockbase, so they were answering arithmetic with more mocks rather than failing. They get a real clock and aset_mock_clock()helper that moves it — writingminutes_nowdirectly no longer does anything, which is the point.Mutation-tested: reverting the property fails with
Faked base.minutes_now leaked into the component: 0../run_all --quickgreen (314 tests) under both a mismatched host timezone andTZ=Europe/London./run_pre_commitgreen🤖 Generated with Claude Code