🐛 monotone reconcile skips small backward corrections #66

Merged
mat merged 3 commits from monotone-reconcile into main 2026-07-25 18:11:26 +00:00
Owner

Stop the HA recorder total_increasing warnings (state is not strictly increasing...) that spam the logs every hour on the Comwatt *_total_energy sensors.

Problem

PR #64 made the hourly reconciliation against server QUANTITY/HOUR buckets unit-aware, but it applies EVERY backward correction. Measured live backward drift is small (~0.05 to 2.6 Wh/h per device) but constant — each one makes HA recorder log a WARNING. The *_total_energy sensors are state_class: TOTAL_INCREASING, so any backward state-change is flagged.

Fix

  • New constant _RECONCILE_BACKWARD_DRIFT_TOLERANCE_WH = 5.0.
  • In _fetch_device_metrics reconciliation (else: branch when the stream owns the live total), after val_wh = _server_bucket_to_wh(val, live_wh):
    • Forward (correction >= 0): apply as before (the common case — missed stream samples under-count).
    • Large backward (-correction > 5.0): apply the snap as before (genuine drift must be corrected — once PR #65 persists the live total across restarts, skipping ALL backward corrections would lock in permanent over-counting).
    • Small backward (0 < -correction <= 5.0): SKIP the correction — don't mutate live_total_wh, don't overwrite live_by_hour[hour]. The bucket is still marked done (last_bucket_ts = bucket_dt runs unconditionally) so it's not reconsidered. The entity state stays unchanged ⇒ no recorder warning.
  • sensor.py / entity state_class unchanged.

Tests

4 new (RED→GREEN): small-backward skip + HWM not reconsidered, large-backward snap, inclusive boundary at exactly 5 Wh, forward-unchanged regression guard. 84 total passing, ruff + mypy clean. Existing test_reconcile_* audited — every backward correction uses −10 Wh (> tolerance), all stay GREEN unchanged.

Scope

coordinator.py + tests/test_coordinator.py only. Base main.

Interaction with PR #65 (persist-energy-state)

Orthogonal semantics. #65 touches the if live_total is None legacy-seed path; this slice touches the else: reconciliation branch. A trivial textual conflict may occur on shared context lines if this slice merges first — resolvable by keeping both sets of additions (no shared logic). Merging #65 first avoids it.

Stop the HA recorder `total_increasing` warnings (`state is not strictly increasing...`) that spam the logs every hour on the Comwatt `*_total_energy` sensors. ## Problem PR #64 made the hourly reconciliation against server `QUANTITY/HOUR` buckets unit-aware, but it applies EVERY backward correction. Measured live backward drift is small (~0.05 to 2.6 Wh/h per device) but constant — each one makes HA recorder log a WARNING. The `*_total_energy` sensors are `state_class: TOTAL_INCREASING`, so any backward state-change is flagged. ## Fix - New constant `_RECONCILE_BACKWARD_DRIFT_TOLERANCE_WH = 5.0`. - In `_fetch_device_metrics` reconciliation (`else:` branch when the stream owns the live total), after `val_wh = _server_bucket_to_wh(val, live_wh)`: - **Forward** (`correction >= 0`): apply as before (the common case — missed stream samples under-count). - **Large backward** (`-correction > 5.0`): apply the snap as before (genuine drift must be corrected — once PR #65 persists the live total across restarts, skipping ALL backward corrections would lock in permanent over-counting). - **Small backward** (`0 < -correction <= 5.0`): SKIP the correction — don't mutate `live_total_wh`, don't overwrite `live_by_hour[hour]`. The bucket is still marked done (`last_bucket_ts = bucket_dt` runs unconditionally) so it's not reconsidered. The entity state stays unchanged ⇒ no recorder warning. - `sensor.py` / entity `state_class` unchanged. ## Tests 4 new (RED→GREEN): small-backward skip + HWM not reconsidered, large-backward snap, inclusive boundary at exactly 5 Wh, forward-unchanged regression guard. 84 total passing, ruff + mypy clean. Existing `test_reconcile_*` audited — every backward correction uses −10 Wh (> tolerance), all stay GREEN unchanged. ## Scope `coordinator.py` + `tests/test_coordinator.py` only. Base `main`. ## Interaction with PR #65 (persist-energy-state) Orthogonal semantics. #65 touches the `if live_total is None` legacy-seed path; this slice touches the `else:` reconciliation branch. A trivial textual conflict may occur on shared context lines if this slice merges first — resolvable by keeping both sets of additions (no shared logic). Merging #65 first avoids it.
The *_total_energy sensors declare state_class: TOTAL_INCREASING. Most
hourly backward corrections from the server QUANTITY/HOUR bucket are tiny
drift (measured live: ~0.05 to 2.6 Wh/hour); each one made HA recorder
log a WARNING ("state is not strictly increasing... please create a bug
report"), spamming logs. Large backward corrections, however, indicate
genuine drift and must still be applied — otherwise (with the live total
now surviving restarts via PR #65) skipping them all would lock in
permanent over-count forever.

Introduce _RECONCILE_BACKWARD_DRIFT_TOLERANCE_WH = 5.0 (inclusive <=):
backwards at or below the tolerance are skipped silently (live total and
live_by_hour[hour] unchanged; high-water mark still advances so the bucket
is not reconsidered next fetch), forwards and large backwards still snap.
Document the rationale in the _fetch_device_metrics docstring.

Add four tests (skips small backward, applies large backward, boundary at
tolerance is skipped, forward correction unchanged). Audit of existing
reconcile tests confirms none use a backward correction within (0, 5] Wh,
so none broke and none required modification.
📝 polish: docstring compounding tradeoff + realistic large-backward fixture
All checks were successful
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 3m4s
Validate / type-check-mypy (push) Successful in 3m7s
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 3m1s
Validate / type-check-mypy (pull_request) Successful in 3m6s
06529544b4
mat changed title from WIP: fix: monotone reconcile skips small backward corrections (Problem 2) to WIP: 🐛 monotone reconcile skips small backward corrections (Problem 2) 2026-07-25 18:06:57 +00:00
mat changed title from WIP: 🐛 monotone reconcile skips small backward corrections (Problem 2) to WIP: 🐛 monotone reconcile skips small backward corrections 2026-07-25 18:08:17 +00:00
mat changed title from WIP: 🐛 monotone reconcile skips small backward corrections to 🐛 monotone reconcile skips small backward corrections 2026-07-25 18:08:22 +00:00
mat merged commit 74df03aa61 into main 2026-07-25 18:11:26 +00:00
mat deleted branch monotone-reconcile 2026-07-25 18:11:26 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mat/homeassistant-comwatt!66
No description provided.