Sync 0.19.x into release/1.0: TARIFF-24 and SAFETY-125, with the forward-port fixes #220

Merged
admin merged 30 commits from sync-0.19 into release/1.0 2026-10-30 18:51:54 +01:00
Owner

Brings 0.19.1 (TARIFF-24) and 0.19.2 (SAFETY-125) from master (f1c9733) into release/1.0 (b6be312), as a real merge (--no-ff, no rebase, no squash).

Merge. Git merged every file without a text conflict. Files both sides touched: main.py, homeview.py, ui_text/en.json + nl.json, DOCS.md, test_modes_p4.py, all in separate hunks. .gitea/ is release/1.0's (CI-15) unchanged: master did not touch it.

Version. config.yaml is 0.19.2, the newest CHANGELOG section. test_version_gate.py (PKG-14 11b) requires the two to be equal, so 0.19.0 with the 0.19.2 section fails it. The 0.19.1 and 0.19.2 sections come in as history.

On top of the merge:

  • tools/ux54_scenarios.py: OPT-27's 6656535, cherry-picked. With a forecast, the scenarios set cost 0.15, the gate they were written for.
  • dayview.py: the Forecast tab extends tomorrow's cheap run with control's gap (arbitrage.weather_uncertainty()), not a fixed x1.5. Display only. It is pinned in test_ux54.py, and the old line fails that check.
  • TARIFF-24 review F1/F2: test_tariff24_weather.py part 6 (probes R4, R5). Mutant M2 and a "give run not kept" mutant both fail it.
  • TARIFF-24 DECISIONS: F2 and F3 sentences. CHANGELOG 0.19.2 nit ("would go over the limit").

Report: reports/SYNC-0.19-dev-report.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC

Brings 0.19.1 (TARIFF-24) and 0.19.2 (SAFETY-125) from `master` (f1c9733) into `release/1.0` (b6be312), as a real merge (`--no-ff`, no rebase, no squash). **Merge.** Git merged every file without a text conflict. Files both sides touched: `main.py`, `homeview.py`, `ui_text/en.json` + `nl.json`, `DOCS.md`, `test_modes_p4.py`, all in separate hunks. `.gitea/` is release/1.0's (CI-15) unchanged: master did not touch it. **Version.** `config.yaml` is **0.19.2**, the newest CHANGELOG section. `test_version_gate.py` (PKG-14 11b) requires the two to be equal, so 0.19.0 with the 0.19.2 section fails it. The 0.19.1 and 0.19.2 sections come in as history. **On top of the merge:** - `tools/ux54_scenarios.py`: OPT-27's 6656535, cherry-picked. With a forecast, the scenarios set cost 0.15, the gate they were written for. - `dayview.py`: the Forecast tab extends tomorrow's cheap run with control's gap (`arbitrage.weather_uncertainty()`), not a fixed x1.5. Display only. It is pinned in `test_ux54.py`, and the old line fails that check. - TARIFF-24 review F1/F2: `test_tariff24_weather.py` part 6 (probes R4, R5). Mutant M2 and a "give run not kept" mutant both fail it. - TARIFF-24 DECISIONS: F2 and F3 sentences. CHANGELOG 0.19.2 nit ("would go over the limit"). Report: `reports/SYNC-0.19-dev-report.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
admin added 30 commits 2026-10-30 08:30:45 +01:00
master is what Home Assistant clones. SAFETY-57 fixed a deadlock that parked
the reference install's battery for ~13 minutes while the site exported
2.4 kW, and the Supervisor compares config.yaml version strings - so the fix
could not reach an installed 0.10.0 without this bump reaching master.

Brings SAFETY-57 (#104) and the 0.10.1 cut (#105). master was a strict
ancestor; no conflict was possible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
master is what the Home Assistant Supervisor clones, and the Supervisor
compares config.yaml version strings - so 0.11.0 could not be offered to an
installed 0.10.1 without this bump reaching master.

Carries SAFETY-56 (#112), TEL-11 (#113), the CI-9 follow-up (#102) and SEC-2's
docs/RELEASE.md (#108), by way of the 0.11.0 cut (#114).

NOT A FAST-FORWARD. master was not an ancestor of release/1.0: 0f14c2a, the
pre-SEC-2 direct push, exists only on master. Its tree is the same object as
the merge base b0b24e5 (c1fef26), so it carries no content delta, and
`git merge-tree --write-tree` over the two branches returns a tree bit-identical
to cccb2fa's own (6670502). This merge commit therefore contributes exactly
release/1.0's tree - master synthesises nothing.

Reviewed as the release decision, not a re-review of content. Verified from the
remote: slug goodwe_controller and every identity key unchanged (cross-checked
against the live box's Supervisor export), version 0.10.1 -> 0.11.0, and the
green is this PR's own - run 828 / job 1953, path checks.yml@refs/pull/115/head,
PRNUM 115, GRADED refs/pull/115/head merged into master, run_attempt 1, 31 test
files all passing in 8m01s of real work.

predeploy_check.py re-run against the owner's current options (exported
2026-09-24 20:02, 75 keys, pv_entity unset): CLEARED, exit 0. That retires the
stale-2026-09-07-export warning in the pull request body. Option surface
75 -> 76 with only pv_entity added, str? defaulting to "", no key removed, no
default moved, no rule added to validate.py - no SystemExit(78) on this hop.

Merging is not installing. No tag is cut here and `ha store reload` /
`ha addons update` have not been run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
Reviewed-on: #166
Deploy 0.14.0 to master (#170)
Deploy 0.15.0 to master (#174)
Reviewed-on: #208
With battery modes on, a day/night or fixed static_profile, no price feed and
arbitrage off, tariff_refresh_wanted() was false: no tariff task, no
tariff_plan, mode_runs None, so Leftover solar / Cheap hours never charged from
the grid and the Batteries screen said "No prices". packmode.enabled(opts) is
now a third term of the gate. With price_provider off the task is pure
arithmetic on static_profile, so no HTTPS call is added.

Also: version 0.19.1, CHANGELOG entry, DECISIONS note, test_tariff24.py, and a
shorter tariff_refresh_wanted() docstring (history stays in DECISIONS).

Co-Authored-By: Claude <noreply@anthropic.com>
TARIFF-24: test file follows the repo's suite rules (MUTATION.md pointer, named absence)
checks / unit (pull_request) Successful in 49m23s
infra-pin / fresh (pull_request) Successful in 2s
230ba0b57a
Co-Authored-By: Claude <noreply@anthropic.com>
With battery modes on, the price gap a mode needs was always widened x1.5 by
Uncertainty.NO_WEATHER, even with a solar forecast read, so a 0.08 day/night
gap never traded at the shipped cycle cost. The doubt is now added only when
no usable forecast is read (solar_fc is None, the day plan's own test), in the
run plan (_price_runs), the charge gate (_mode_roles) and Home's mirror
(homeview.trade_verdict). The rule is one helper, arbitrage.weather_uncertainty();
the modes-off trade path (main.py cycle) keeps its literal NO_WEATHER.

DOCS "Battery modes" sentence, CHANGELOG 0.19.1 line and the DECISIONS note
follow. Tests: test_tariff24_weather.py.

Co-Authored-By: Claude <noreply@anthropic.com>
TARIFF-24 round 2: test_modes_p4 scenarios keep the 0.30 gate they were written for
checks / unit (pull_request) Successful in 49m18s
infra-pin / fresh (pull_request) Successful in 1s
d424b862d8
With a forecast read the gap needed is the cycle cost (0.20), not 1.5 x (0.30),
so a midday 0.21 gap now plans a grid top-up that four scenarios (sunny, Cheap
hours partly, forecast too high, refusal) are not about. They ask for cost 0.30
with the forecast; the no-forecast leg keeps 0.20 (= 0.30 widened).

Co-Authored-By: Claude <noreply@anthropic.com>
TARIFF-24 round 3: a run planned with a forecast keeps its gap to its end
checks / unit (pull_request) Successful in 51m57s
infra-pin / fresh (pull_request) Successful in 1s
97686319f3
Losing the solar forecast mid-run used to widen the gap at once and stop a
grid charge (and a sensor that went unavailable for a minute stopped and
restarted it). A run under way when the forecast is lost now keeps the gap it
was planned with until it ends (Controller.weather_held(), recorded by
_wx_track() each cycle a forecast is read); the wider gap applies from the next
run. The plan key holds the hold state so the plan rebuilds when it ends. Home's
mirror (homeview.trade_verdict) asks the same weather_held(now).

Tests: test_tariff24_weather.py part 4 (going: the charge continues to the run's
end, the next run is not_worth; flap of one and two reads: no stop, no restart;
Home agrees) and a test_modes_p4.py scenario that runs a forecast day at a gap
between 1x and 1.5x the cycle cost. DECISIONS wording follows.

Co-Authored-By: Claude <noreply@anthropic.com>
Reviewed head 9768631 (Opus review). CI run 1422 green on that sha.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #213
SAFETY-125: a grid charge counts a late meter reading's seconds; the guard row says "over" when it is
checks / unit (pull_request) Successful in 56m44s
infra-pin / fresh (pull_request) Successful in 1s
88ab8a50e3
A meter reading late near a quarter's end left seconds the projection did
not count (SAFETY-99), and charge_headroom_w() read them as room: the cheap
run (and price trading, and the N=2 maintenance charge) went back to full
power in the quarter's last seconds and the quarter could land over a banked
month peak or the cap. peakguard.charge_room_w() counts those seconds at the
held reading for the charge only; the guard's floor is unchanged.

The guard row said "idle, heading for 5 814 W, under this month's 5 413 W
peak". Over the limit at the current rate the row and the month-peak line
now say so, and that the grid charge is cut back.

Version 0.19.2, CHANGELOG, DECISIONS. test_safety125.py new; the lag pins
in test_price_run_fixes.py and test_opt20.py and the AST pin in
test_arbitrage.py follow the change (see the pull request).

Co-Authored-By: Claude <noreply@anthropic.com>
SAFETY-125 review: guard row and Home line within UX-08 limits; N=1 maintenance named
checks / unit (pull_request) Successful in 56m45s
infra-pin / fresh (pull_request) Successful in 1s
6eebd4882b
- The over-the-limit rows ran over STYLE's 15 words (EN 16/19, NL 16/21)
  and two Dutch Home sentences over 12 words; they were not in the samples,
  so test_messages/test_ux46 never saw them. Now:
  "Peak guard: grid charge limited, at this rate 5 814 W, over 5 413 W" /
  "Piekbewaking: laden beperkt, aan dit tempo 5 814 W, boven 5 413 W",
  "...idle, at this rate ..., over ..." / "...in rust, aan dit tempo ...",
  NL Home "Laadt minder van het net om onder 5,4 kW te blijven." and
  "Zou minder van het net laden om onder 5,4 kW te blijven."
  The rows and the line are added to the samples. The under row is unchanged.
- CHANGELOG known issues and the DECISIONS note: at N=1 the maintenance
  charge is not sized to the room, so SAFETY-99 still applies to it.
- DOCS: the peak guard row's new words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SAFETY-125 round 2: the one-inverter maintenance charge is sized to the room too
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 1h0m42s
2929ece7da
Owner 2026-10-29: fix N=1 in this hotfix. _charge_headroom() no longer
returns early at one inverter, so the N=1 cheap-window maintenance charge
is held to charge_room_w() as at N=2; SAFETY-84's site-stop rule stays N>=2
only (at N=1 such a stop outranks the shaping anyway). The guard no longer
steps in and gives power at each quarter's end during that charge.

test_safety125.py: N=1 maintenance on 5 s and 10 s meters with late
readings, with and without a banked peak; the row in an N=2 maintenance
charge and in shadow mode; edge cases (blind past the hold, a start
mid-quarter, the summer-time change, the forecast in W, a 10 s meter, the
last second). test_safety84/87: three N=1 pins move. test_safety119: its
quarter-edge hand-back runs keep the old N=1 sizing as a fixture, plus a
check that with the new sizing nothing hands back. CHANGELOG, DECISIONS,
DOCS follow.

Co-Authored-By: Claude <noreply@anthropic.com>
Merge pull request 'SAFETY-125: a grid charge stays under the peak aim (hotfix 0.19.2)' (#214) from SAFETY-125 into hotfix-0.19.2
checks / unit (pull_request) Successful in 1h3m7s
infra-pin / fresh (pull_request) Successful in 2s
8966867968
Merge hotfix-0.19.2 (SAFETY-125): release 0.19.2
Brings in 0.19.1 (TARIFF-24) and 0.19.2 (SAFETY-125). Git merged every file
without a text conflict. config.yaml's version becomes 0.19.2, the newest
CHANGELOG section: test_version_gate.py (PKG-14, 11b) requires the two to be
equal, and release/1.0 took 0.19.0 the same way (#207). The 0.19.1 and 0.19.2
CHANGELOG sections come in as history.

Co-Authored-By: Claude <noreply@anthropic.com>
With master merged in (TARIFF-24), a forecast replaces the 1.5x widening, so
the shipped cost 0.10 is now the whole gap with a forecast. test_ux54.py's
modes-on scenarios were written for 0.15 and 21 of its checks failed on
release/1.0 + master (they pass on release/1.0 alone). They now set the cost
to 0.15 where they have a forecast: the same gate as before. Scenarios with no
forecast (nofc, stale) and modes off keep 0.10, widened as before.
(cherry picked from commit 665653517c, branch OPT-27)

Co-Authored-By: Claude <noreply@anthropic.com>
Part 6 of test_tariff24_weather.py, the review's probes R4 and R5:
- R4: a forecast lost while only the day's give run is under way holds to
  that run's end (22:00) and no further; the night's cheap run then needs the
  wider gap. Pins the `solar_fc is None` return in `_wx_track()`: without it
  the hold chains into the cheap run and B charges (mutant M2, now red).
- R5: a give run that starts during a hold runs to its end (`keep_started()`),
  with no grid charge after the hold. Behaviour unchanged; a give run not kept
  goes red.

Co-Authored-By: Claude <noreply@anthropic.com>
- TARIFF-24 DECISIONS: the hold keeps a Cheap hours charge; a Leftover solar
  battery still stops without a forecast (it has no planned level, still true
  after OPT-28: `dayplan.plan()` returns None and `charge_to()` gives None).
  Adds "a run that has started runs to its end" for a give run begun in a hold.
- CHANGELOG 0.19.2: "when the period is over" -> "when the period would go
  over the limit".

Co-Authored-By: Claude <noreply@anthropic.com>
UX-54's Forecast tab (release/1.0) re-plans tomorrow's cheap run as control
will, with the gap `required`. It still added the "no weather" x1.5 always,
while control (TARIFF-24, from master) drops it when a forecast is read. In
the UX-54 scenarios the tab asked 0.225 where control needs 0.15. It now asks
`arbitrage.weather_uncertainty(True, c.solar_fc)`, so the two agree again, as
they did on release/1.0 alone. Display only; no control change. The tab's
blocks in every UX-54 scenario, every 2 h, are identical to release/1.0.

test_ux54.py pins it (the old line fails it). The TARIFF-24 DECISIONS note
names the tab as the fourth place that reads the rule.

Co-Authored-By: Claude <noreply@anthropic.com>
TARIFF-24 forward-port: part 6 reads its last row through nth() (test_named_absence)
checks / unit (pull_request) Successful in 28m5s
infra-pin / fresh (pull_request) Successful in 2s
60a3fa615e
Co-Authored-By: Claude <noreply@anthropic.com>
Author
Owner

SYNC-0.19 review: approve and merge (head 60a3fa6)

No router verdict exists for this PR, so this review is the only independent check. I pushed nothing.

What I checked

  • The merge is real. 8bf54ec is a --no-ff merge with parents b6be312 and f1c9733. I redid git merge --no-ff f1c9733 on b6be312 myself. The tree came out identical (e680543), and --remerge-diff is empty. So the merge has no hand edits. Every difference from my redo comes from the dev's 5 listed commits.
  • .gitea/ is unchanged from release/1.0 (CI-15).
  • CI. Run 1452 checks / unit graded pull/220 merged into release/1.0: 90 PASS, test_freeze on the rig hwsim, infra 3b4ff84. Run 1453 infra-pin is green.
  • Local suite (goodwe-addon-checks:1, --network none, CI-15 run-tests.sh, GOODWE_REQUIRE_HWSIM=1): 90/90.
  • Mutants, all caught:
    • F1/M2 (main.py:2245 return dropped): 2 FAIL. The hold runs to 00:00 UTC and B charges.
    • F2 (priceruns.py:193, g_old = None): 1 FAIL. B goes house.
    • 1c9fefd reverted (dayview.py:212): 1 FAIL. The tab asks for a 0.225 gap where control uses 0.15.
  • Digests. I re-ran 4 cases on base, master and head, plus 3 on head with D1 (the x1.5 doubt always on). My results match the dev's exactly:
    • modes off: no change;
    • Cheap hours with a forecast: head equals master;
    • Leftover solar (recorded/periods and perfect/state): the result differs from both base and master;
    • D1 on head reproduces base for all 3 cases.
  • Clashes. TARIFF-24's weather hold (weather_held) and SAFETY-125's charge_room_w reach every caller on release/1.0. The modes-off site trade keeps x1.5 on purpose (main.py:4950). UX-53's leg_taken and price_windows use the same rule as control. The only clash git missed is the dayview.py one the dev found.
  • 1c9fefd: keep it. Tomorrow's stretched cheap run starts after midnight, so no hold can still be running at its re-plan. Leaving held out is therefore exact. It is a display-only change, and the tab now matches control (UX-54).
  • Version 0.19.2. test_version_gate 11b requires the newest CHANGELOG heading to match config.yaml, so 0.19.2 is the only version that passes. predeploy_check --deployed-version 0.19.2 on this head refuses: "would be a no-op, 14 runtime commits". The next 1.0 release must bump past 0.19.2.

Findings (none block)

  • Info, test_ux54.py:1474-1485: the test computes want with the function the commit adds. It still also checks want == cycle cost, and the revert mutant goes red.
  • Info, outside this PR (priceruns.py:191-197, keep_started): in the R5 fixture, the old cheap run (05:00-07:45) is kept next to the new plan's give run (07:00-10:00). So B charges from the grid at 0.30 from 07:00 to 07:45 while a give run is on. master f1c9733 does exactly the same, so the merge did not cause it. Card sketch is in the report.

OPT-27 (#219), for its rebase. Drop 7433786 (master 0.19.1, now in through 8bf54ec) and 6656535 (identical hunk, now 5ac1017). In a trial pick of cdcbe7d onto 60a3fa6, three files conflicted:

  • homeview.py solar_block(): keep kept= and also the line_uncovered line.
  • en.json and nl.json "solar": keep both the uncovered and the kept keys.

1eb572f and f095ca9 then apply cleanly. test_opt27, test_tariff24_weather, test_ux54 and test_opt23_28 pass on the trial tree.

Report: reports/SYNC-0.19-review-report.md.

## SYNC-0.19 review: approve and merge (head 60a3fa6) No router verdict exists for this PR, so this review is the only independent check. I pushed nothing. **What I checked** - **The merge is real.** 8bf54ec is a `--no-ff` merge with parents b6be312 and f1c9733. I redid `git merge --no-ff f1c9733` on b6be312 myself. The tree came out identical (e680543), and `--remerge-diff` is empty. So the merge has no hand edits. Every difference from my redo comes from the dev's 5 listed commits. - **`.gitea/`** is unchanged from release/1.0 (CI-15). - **CI.** Run 1452 `checks / unit` graded pull/220 merged into release/1.0: 90 PASS, `test_freeze` on the rig hwsim, infra 3b4ff84. Run 1453 `infra-pin` is green. - **Local suite** (`goodwe-addon-checks:1`, `--network none`, CI-15 `run-tests.sh`, `GOODWE_REQUIRE_HWSIM=1`): 90/90. - **Mutants**, all caught: - F1/M2 (`main.py:2245` return dropped): 2 FAIL. The hold runs to 00:00 UTC and B charges. - F2 (`priceruns.py:193`, `g_old = None`): 1 FAIL. B goes `house`. - 1c9fefd reverted (`dayview.py:212`): 1 FAIL. The tab asks for a 0.225 gap where control uses 0.15. - **Digests.** I re-ran 4 cases on base, master and head, plus 3 on head with D1 (the x1.5 doubt always on). My results match the dev's exactly: - modes off: no change; - Cheap hours with a forecast: head equals master; - Leftover solar (recorded/periods and perfect/state): the result differs from both base and master; - D1 on head reproduces base for all 3 cases. - **Clashes.** TARIFF-24's weather hold (`weather_held`) and SAFETY-125's `charge_room_w` reach every caller on release/1.0. The modes-off site trade keeps x1.5 on purpose (`main.py:4950`). UX-53's `leg_taken` and `price_windows` use the same rule as control. The only clash git missed is the `dayview.py` one the dev found. - **1c9fefd: keep it.** Tomorrow's stretched cheap run starts after midnight, so no hold can still be running at its re-plan. Leaving `held` out is therefore exact. It is a display-only change, and the tab now matches control (UX-54). - **Version 0.19.2.** `test_version_gate` 11b requires the newest CHANGELOG heading to match `config.yaml`, so 0.19.2 is the only version that passes. `predeploy_check --deployed-version 0.19.2` on this head refuses: "would be a no-op, 14 runtime commits". The next 1.0 release must bump past 0.19.2. **Findings** (none block) - Info, `test_ux54.py:1474-1485`: the test computes `want` with the function the commit adds. It still also checks `want == cycle cost`, and the revert mutant goes red. - Info, outside this PR (`priceruns.py:191-197`, `keep_started`): in the R5 fixture, the old cheap run (05:00-07:45) is kept next to the new plan's give run (07:00-10:00). So B charges from the grid at 0.30 from 07:00 to 07:45 while a give run is on. master f1c9733 does exactly the same, so the merge did not cause it. Card sketch is in the report. **OPT-27 (#219), for its rebase.** Drop 7433786 (master 0.19.1, now in through 8bf54ec) and 6656535 (identical hunk, now 5ac1017). In a trial pick of cdcbe7d onto 60a3fa6, three files conflicted: - `homeview.py` `solar_block()`: keep `kept=` and also the `line_uncovered` line. - `en.json` and `nl.json` `"solar"`: keep both the `uncovered` and the `kept` keys. 1eb572f and f095ca9 then apply cleanly. `test_opt27`, `test_tariff24_weather`, `test_ux54` and `test_opt23_28` pass on the trial tree. Report: `reports/SYNC-0.19-review-report.md`.
admin merged commit f1b72443ff into release/1.0 2026-10-30 18:51:54 +01:00
Author
Owner

Merged as f1b72443ff (merge commit, parents b6be312 and 60a3fa6, head_commit_id set to 60a3fa6). The tree is identical to 60a3fa6. ls-remote shows release/1.0 at f1b7244, and master, OPT-27 and sync-0.19 have not moved.

Merged as f1b72443ff425606b9ecb8ce9e3da02271a537c7 (merge commit, parents b6be312 and 60a3fa6, `head_commit_id` set to 60a3fa6). The tree is identical to 60a3fa6. ls-remote shows release/1.0 at f1b7244, and master, OPT-27 and sync-0.19 have not moved.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#220