Skip to content

fix(dwt): codex P1-5 — reset daily counters on cross-midnight restart#110

Merged
Artic0din merged 1 commit into
devfrom
fix/codex-p1-5-dwt-state-date
May 23, 2026
Merged

fix(dwt): codex P1-5 — reset daily counters on cross-midnight restart#110
Artic0din merged 1 commit into
devfrom
fix/codex-p1-5-dwt-state-date

Conversation

@Artic0din
Copy link
Copy Markdown
Owner

Summary

Codex P1-5: `DynamicWholesaleTariffProvider.from_dict(data, today)` required `today` as a contract argument but immediately `del today`'d it and restored daily counters unconditionally. A restart that crosses midnight resurrected yesterday's daily counters as today's.

Relationship to PR #109

PR #109's P0-2 added `reset_daily()` to the coordinator's midnight rollover branch — that covers the "HA stays running through midnight" case. This PR covers the "HA restarts after midnight" case where `async_restore_state` pulls stored daily counters from the JSON store.

Two different paths, both needed.

Fix

  • `to_dict()` persists `state_date = _last_tick.date().isoformat()` — the HA-tz date the daily counters apply to. `None` on a fresh provider.
  • `from_dict()` compares stored `state_date` with the supplied `today` HA-tz date. Match = restore counters (existing contract). Mismatch = zero counters and log INFO.
  • Missing `state_date` (pre-fix snapshots) + malformed strings → safe default = zero.
  • `last_price` + `last_tick` still restored regardless — they survive midnight cleanly.

Tests

```
1082 passing (was 1078 — +4)
ruff: All checks passed
```

4 regression tests in `tests/test_dynamic_wholesale_tariff_provider.py`:

Test Pins
`test_from_dict_resets_daily_counters_on_cross_midnight_restart` the codex case
`test_from_dict_preserves_counters_on_same_day_restart` existing contract
`test_from_dict_resets_when_state_date_missing` back-compat with pre-fix snapshots
`test_from_dict_resets_when_state_date_malformed` corrupted JSON store

Test plan

  • pytest — 1082 passing
  • ruff — clean
  • Deploy to live HA — restart at 23:55 local, confirm `sensor.pricehawk_today_cost` keeps its accruing value (same-day restart)
  • Wait until 00:05 next day, restart HA, confirm sensor zeroes (cross-midnight restart)

Codex queue remaining

  • P1-1 options-flow named-comparator reads legacy `hass.data`
  • P1-2 services captured per-entry instead of resolved at call time
  • P1-3 external-stats cumulative `sum` adds net (can go negative) — needs HA-docs research
  • P1-4 cheap-rank only reads `timeOfUseRates` (single-rate plans silently excluded)

Codex full-repo review (2026-05-23, P1-5): the DWT provider's
``from_dict(data, today)`` required ``today`` as a contract argument
but then immediately ``del today``'d it and restored daily counters
unconditionally. After a restart that crosses midnight, yesterday's
``import_kwh_today`` / ``import_cost_today_c`` / export equivalents
were resurrected as today's — corrupting Energy Dashboard cost
sensors and the external statistics push for the rest of the day.

This is closely related to the codex P0-2 fix (PR #109) but covers a
different code path. P0-2 added ``reset_daily()`` to the coordinator's
midnight rollover branch — that handles the "HA stays running"
midnight case. P1-5 covers the "HA restarts after midnight" case —
``async_restore_state`` runs and pulls the stored daily counters from
the JSON store; without a date check those counters land in today's
running totals.

Fix:
- ``to_dict()`` persists ``state_date`` = ``_last_tick.date().isoformat()``,
  the HA-tz date the daily counters apply to. ``None`` when no tick
  has run yet (fresh provider, treated as cross-midnight on restore).
- ``from_dict()`` parses ``state_date``, compares to the supplied
  ``today`` HA-tz date. If they match, restore counters (existing
  roundtrip contract). If not, zero counters and log INFO with the
  two dates so operators can see why the dashboard reset.
- Missing ``state_date`` (pre-fix snapshots) and malformed values are
  treated as cross-midnight — safe default = zero rather than risk
  carrying yesterday's totals. Malformed strings additionally emit a
  WARNING for triage.
- ``last_price`` and ``last_tick`` are still restored regardless —
  they are not daily accumulators and survive midnight cleanly,
  giving the new day a known rate to start with.

4 regression tests in test_dynamic_wholesale_tariff_provider.py:
- cross-midnight restart resets counters (the codex case)
- same-day restart preserves counters (existing contract)
- missing state_date treated as cross-midnight (back-compat)
- malformed state_date treated as cross-midnight

1082 passing (was 1078), ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Artic0din Artic0din merged commit 408a8b0 into dev May 23, 2026
8 of 10 checks passed
@Artic0din Artic0din deleted the fix/codex-p1-5-dwt-state-date branch May 23, 2026 05:38
Artic0din added a commit that referenced this pull request May 24, 2026
)

Bumps manifest to 1.6.0-beta.2 — first HACS-beta tag carrying the full
Phase 7-11 work landed since 1.6.0-beta.1. Live UAT against the production
HA install on 2026-05-24 confirmed the prior fixes (NEMWeb regex #107,
statistic_id sanitization #114/#145, bootstrap block #107, codex P0/P1
work in #109/#110/#111) had not reached users because the last tagged
release was v1.4.0-beta.1 from 2026-05-02.

Also fixes a UAT-surfaced bug not previously caught:

explanation.py — DWT winners no longer produce empty bullets.
  ``build_explanation`` branched on literal provider IDs ("amber",
  "globird", "flow_power", "localvolts") but Dynamic Wholesale Tariff
  providers carry IDs like "dwt_aemo_direct" / "dwt_openelectricity",
  so every DWT win fell through to an empty bullet list. The Best
  Provider sensor's winner_explanation attribute showed bullets=[]
  with margin_aud=0 — no "why" surfaced to the user. Added a
  ``winner_id.startswith("dwt_")`` branch + ``_dwt_won_bullets``
  builder using the standard provider snapshot shape: wholesale spot
  rate (c/kWh derived from $/MWh), today's import volume + cost,
  daily supply charge, and a stale-price warning when the wholesale
  price is over 10 min old. 5 regression tests in
  ``TestDwtWinnerBullets``.

Full test suite: 1125 passing.

Tag + GitHub pre-release follow immediately after this merge.
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