SAFETY-136: less export when Home Assistant readings arrive 10-15 s late on a 5 s meter, through a calmer loop (not detection).
What. On a meter whose readings come 4 s or more apart (judged from the readings that arrive, no new option), the loop runs at half its gain after a move of 150 W or more, until the meter has fallen by half of that move or 2 cycles have passed. With no lag the meter shows the move at the next reading, so nothing waits. Meters of 1-2 s are untouched. app/settle.py, three small calls in cycle() / read_inputs().
Numbers (SAFETY-129 sim, real cycle(), 5 s meter; export Wh summed over flat, oven, kettle + hob, owner shape over the oven and over the heat pump, step down, step up):
10 s and 15 s together: -31 %. At 15 s the oven and the owner's oven are not better; the kettle and the steps are. All 63 runs at 1 s and 2 s meters give the same commands as base; the recorded-day digests at 1 s and 2 s are identical. See DECISIONS/SAFETY-136-slow-meter-settle.md and the dev report for the candidate table and every scenario.
Changelog line (for the release, not committed: the version gate refuses an ## Unreleased block):
With a 5 s meter and Home Assistant readings 10 to 15 seconds late, control now follows the meter more gently after a change, so less power goes to the grid when loads switch on and off (SAFETY-136).
Tests: test_safety136.py. No option, no page text.
Mutants (MUTATION.md, purged copies, test_safety136.py; all red): slow() always true (1-2 s identity and the interval checks fail); WAIT_CYCLES 0 (9 fail); half gain removed (8 fail); readings counted one in five (the export and the waits fail).
Existing test changed:test_safety129.py part 1 "without the gate the export goes below -1 kW" compares with the tree before SAFETY-128; with this card the same ungated run already exports only -645 W (not -1 325 W), so that one run now switches the settle off (no_settle). Nothing else in it changed.
Local suite on this head (c50e227): goodwe-addon-checks:1, --network none, pin 3b4ff84 sim, GOODWE_REQUIRE_HWSIM=1, .gitea/ci/run-tests.sh: 103 files, 0 failed, 1 754 s.
SAFETY-136: less export when Home Assistant readings arrive 10-15 s late on a 5 s meter, through a calmer loop (not detection).
**What.** On a meter whose readings come 4 s or more apart (judged from the readings that arrive, no new option), the loop runs at half its gain after a move of 150 W or more, until the meter has fallen by half of that move or 2 cycles have passed. With no lag the meter shows the move at the next reading, so nothing waits. Meters of 1-2 s are untouched. `app/settle.py`, three small calls in `cycle()` / `read_inputs()`.
**Numbers** (SAFETY-129 sim, real `cycle()`, 5 s meter; export Wh summed over flat, oven, kettle + hob, owner shape over the oven and over the heat pump, step down, step up):
| | base | head |
|---|---|---|
| no lag | 64.4 | 66.5 (import 62.0 -> 62.0, quarter peak 26 -> 26 W) |
| 10 s late | 339.9 (import 363.0) | 189.0, -44 % (import 223.3) |
| 15 s late | 363.4 (import 280.9) | 295.5, -19 % (import 287.7) |
10 s and 15 s together: -31 %. At 15 s the oven and the owner's oven are not better; the kettle and the steps are. All 63 runs at 1 s and 2 s meters give the same commands as base; the recorded-day digests at 1 s and 2 s are identical. See DECISIONS/SAFETY-136-slow-meter-settle.md and the dev report for the candidate table and every scenario.
**Changelog line (for the release, not committed: the version gate refuses an `## Unreleased` block):**
With a 5 s meter and Home Assistant readings 10 to 15 seconds late, control now follows the meter more gently after a change, so less power goes to the grid when loads switch on and off ([[SAFETY-136]]).
Tests: `test_safety136.py`. No option, no page text.
**Mutants** (MUTATION.md, purged copies, `test_safety136.py`; all red): `slow()` always true (1-2 s identity and the interval checks fail); `WAIT_CYCLES` 0 (9 fail); half gain removed (8 fail); readings counted one in five (the export and the waits fail).
**Existing test changed:** `test_safety129.py` part 1 "without the gate the export goes below -1 kW" compares with the tree before SAFETY-128; with this card the same ungated run already exports only -645 W (not -1 325 W), so that one run now switches the settle off (`no_settle`). Nothing else in it changed.
Local suite on this head (c50e227): `goodwe-addon-checks:1`, `--network none`, pin 3b4ff84 sim, `GOODWE_REQUIRE_HWSIM=1`, `.gitea/ci/run-tests.sh`: 103 files, 0 failed, 1 754 s.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
On a meter whose readings come 4 s or more apart, the loop runs at half its
gain after a move of 150 W or more, until the meter has fallen by half of the
move or 2 cycles have passed. With readings 10 s late on a 5 s meter the export
falls 44 % over oven, kettle and hob, owner shape, steps; 1-2 s meters are
untouched.
Co-Authored-By: Claude <noreply@anthropic.com>
Independent review (Opus) of c50e227: MERGE-READY if the owner accepts trade-offs T1-T3. Not merged.
Reproduced. I reran 157 sim runs per tree with the dev's harness. All 136 of the dev's runs give identical row hashes, base and head. Export sums at a 5 s meter:
no lag: 64.4 -> 66.5 Wh
10 s late: 339.9 -> 189.0 Wh (-44 %)
15 s late: 363.4 -> 295.5 Wh (-19 %)
Every 1-2 s run is identical to base, and the "readings late" seconds are identical in all 157 runs. CI run 1591 is green on c50e227. The local suite (hwsim 3b4ff84, --network none) gives 103 files, 0 failed.
Trade-offs (the owner decides):
T1, 15 s late is only -19 %. I tried four variants: 3 cycles, gain share 0.35, 3 cycles + 0.35, 3 cycles + 0.25. Each was worse at 15 s (301-335 Wh) and/or worse on the slow-battery harm runs. 3 cycles + 0.25 drives dead12_p5_n1_harm import to 850 Wh with a 1.5 kW quarter. A gain share of 0.35 helps only at 10 s (165 Wh). The current constants are the best point I found.
T2, harm runs (+17 Wh import, +11.5 Wh export, one quarter 51 -> 124 W). That costs under 1 cent on the owner's tariff. The highest quarter in any head run is 507 W, the same as base, so no run can raise a month peak. In real use the cost is bounded: at most 2 cycles at half gain, about 5-10 W on one quarter per event. The peak guard, readings late, charge and hold are claims outside compute(); they are not slowed, and any override ends the wait.
T3, "slow" from HA arrivals. HA gives no arrival signal for a repeated value (p1.py and freshness.py record the measurement), so the code cannot see a better one. I simulated a 1 s meter whose reading reaches the add-on only when it changes:
20 rig scenarios (holds of 4-30 s, night, PV noon, 8 and 12 s dead times): same commands as base in all. On a 1 s meter the move shows at the next reading, so each wait ends before the half gain matters.
Recorded day at 1 s, N=1, with HA delivery: "slow" in 33 % of cycles, 83 cycles at half gain, 15 422 commands in both trees (4 016 with another value, at most 190 W apart), and a net cost of 0.61 EUR in both.
Findings (all low, none blocks):
F1test_safety136.py part 5, last check: "a 1 s meter whose value changes every 5 s: not slow". That is true only when every reading arrives. On ha_signed the same meter is slow. Please reword it so it does not read as a production guarantee.
F2ctl_cycle.py:745-746 and settle.py:67: two mutants survive test_safety136. One drops the reset on an override or mode trade. The other makes the "shown" test abs(), so it no longer checks the direction. Add one check each.
F3settle.py:3,7 says "4 s or more" and "under 4 s", and DECISIONS says "1-3 s". The rule is a median gap over 3.75 s; make the wording match.
F4 DECISIONS "What it costs": "quarter at most 35 W, once 73 W". dead10_p6 also rises +40 W (-6 -> 34 W).
Info: no separate export limit exists. A same-direction disturbance during a wait is followed at half gain for at most 2 cycles, by design.
Full report: reports/SAFETY-136-review-report.md.
**Independent review (Opus) of c50e227: MERGE-READY if the owner accepts trade-offs T1-T3. Not merged.**
**Reproduced.** I reran 157 sim runs per tree with the dev's harness. All 136 of the dev's runs give identical row hashes, base and head. Export sums at a 5 s meter:
- no lag: 64.4 -> 66.5 Wh
- 10 s late: 339.9 -> 189.0 Wh (-44 %)
- 15 s late: 363.4 -> 295.5 Wh (-19 %)
Every 1-2 s run is identical to base, and the "readings late" seconds are identical in all 157 runs. CI run 1591 is green on c50e227. The local suite (hwsim 3b4ff84, `--network none`) gives 103 files, 0 failed.
**Trade-offs (the owner decides):**
- **T1, 15 s late is only -19 %.** I tried four variants: 3 cycles, gain share 0.35, 3 cycles + 0.35, 3 cycles + 0.25. Each was worse at 15 s (301-335 Wh) and/or worse on the slow-battery harm runs. 3 cycles + 0.25 drives `dead12_p5_n1_harm` import to 850 Wh with a 1.5 kW quarter. A gain share of 0.35 helps only at 10 s (165 Wh). The current constants are the best point I found.
- **T2, harm runs** (+17 Wh import, +11.5 Wh export, one quarter 51 -> 124 W). That costs under 1 cent on the owner's tariff. The highest quarter in any head run is 507 W, the same as base, so no run can raise a month peak. In real use the cost is bounded: at most 2 cycles at half gain, about 5-10 W on one quarter per event. The peak guard, readings late, charge and hold are claims outside `compute()`; they are not slowed, and any override ends the wait.
- **T3, "slow" from HA arrivals.** HA gives no arrival signal for a repeated value (`p1.py` and `freshness.py` record the measurement), so the code cannot see a better one. I simulated a 1 s meter whose reading reaches the add-on only when it changes:
- 20 rig scenarios (holds of 4-30 s, night, PV noon, 8 and 12 s dead times): **same commands as base in all**. On a 1 s meter the move shows at the next reading, so each wait ends before the half gain matters.
- Recorded day at 1 s, N=1, with HA delivery: "slow" in 33 % of cycles, 83 cycles at half gain, 15 422 commands in both trees (4 016 with another value, at most 190 W apart), and a net cost of 0.61 EUR in both.
**Findings (all low, none blocks):**
- **F1** `test_safety136.py` part 5, last check: "a 1 s meter whose value changes every 5 s: not slow". That is true only when every reading arrives. On `ha_signed` the same meter *is* slow. Please reword it so it does not read as a production guarantee.
- **F2** `ctl_cycle.py:745-746` and `settle.py:67`: two mutants survive `test_safety136`. One drops the reset on an override or mode trade. The other makes the "shown" test `abs()`, so it no longer checks the direction. Add one check each.
- **F3** `settle.py:3,7` says "4 s or more" and "under 4 s", and DECISIONS says "1-3 s". The rule is a median gap over 3.75 s; make the wording match.
- **F4** DECISIONS "What it costs": "quarter at most 35 W, once 73 W". `dead10_p6` also rises +40 W (-6 -> 34 W).
- Info: no separate export limit exists. A same-direction disturbance during a wait is followed at half gain for at most 2 cycles, by design.
Full report: `reports/SAFETY-136-review-report.md`.
F1 the on-change case is tested as slow, F2 override and direction checks,
F3 the 3.75 s rule in the texts, F4 the quarter figures and the live note.
No behaviour change.
Co-Authored-By: Claude <noreply@anthropic.com>
admin
merged commit a8ecf171d6 into release/1.02026-11-09 22:35:22 +01:00
Round 2 re-check (Opus): MERGED as a8ecf17 into release/1.0, pinned to head 0f9c119.
Only the settle.py docstring changed in app/ since c50e227. F1-F4 are fixed.
My two surviving mutants are now red: removing the override reset fails 2b, and the sign-blind "shown" test fails part 1.
Dropping only or mode_trade still survives. I left it without a test: a mode trade is a P_ARBITRAGE SET claim that outranks the loop, so the arbiter's target is not loop_w, moved is true and the wait resets anyway, and the loop's gain does not reach the wire during a trade.
CI 1593 checks / unit is green on 0f9c119. The local suite on 0f9c119 (hwsim 3b4ff84, --network none) gives 103 files, 0 failed.
**Round 2 re-check (Opus): MERGED** as a8ecf17 into `release/1.0`, pinned to head 0f9c119.
- Only the `settle.py` docstring changed in `app/` since c50e227. F1-F4 are fixed.
- My two surviving mutants are now red: removing the override reset fails 2b, and the sign-blind "shown" test fails part 1.
- Dropping only `or mode_trade` still survives. I left it without a test: a mode trade is a P_ARBITRAGE SET claim that outranks the loop, so the arbiter's target is not `loop_w`, `moved` is true and the wait resets anyway, and the loop's gain does not reach the wire during a trade.
- CI 1593 `checks / unit` is green on 0f9c119. The local suite on 0f9c119 (hwsim 3b4ff84, `--network none`) gives 103 files, 0 failed.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
SAFETY-136: less export when Home Assistant readings arrive 10-15 s late on a 5 s meter, through a calmer loop (not detection).
What. On a meter whose readings come 4 s or more apart (judged from the readings that arrive, no new option), the loop runs at half its gain after a move of 150 W or more, until the meter has fallen by half of that move or 2 cycles have passed. With no lag the meter shows the move at the next reading, so nothing waits. Meters of 1-2 s are untouched.
app/settle.py, three small calls incycle()/read_inputs().Numbers (SAFETY-129 sim, real
cycle(), 5 s meter; export Wh summed over flat, oven, kettle + hob, owner shape over the oven and over the heat pump, step down, step up):10 s and 15 s together: -31 %. At 15 s the oven and the owner's oven are not better; the kettle and the steps are. All 63 runs at 1 s and 2 s meters give the same commands as base; the recorded-day digests at 1 s and 2 s are identical. See DECISIONS/SAFETY-136-slow-meter-settle.md and the dev report for the candidate table and every scenario.
Changelog line (for the release, not committed: the version gate refuses an
## Unreleasedblock):With a 5 s meter and Home Assistant readings 10 to 15 seconds late, control now follows the meter more gently after a change, so less power goes to the grid when loads switch on and off (SAFETY-136).
Tests:
test_safety136.py. No option, no page text.Mutants (MUTATION.md, purged copies,
test_safety136.py; all red):slow()always true (1-2 s identity and the interval checks fail);WAIT_CYCLES0 (9 fail); half gain removed (8 fail); readings counted one in five (the export and the waits fail).Existing test changed:
test_safety129.pypart 1 "without the gate the export goes below -1 kW" compares with the tree before SAFETY-128; with this card the same ungated run already exports only -645 W (not -1 325 W), so that one run now switches the settle off (no_settle). Nothing else in it changed.Local suite on this head (
c50e227):goodwe-addon-checks:1,--network none, pin 3b4ff84 sim,GOODWE_REQUIRE_HWSIM=1,.gitea/ci/run-tests.sh: 103 files, 0 failed, 1 754 s.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
Independent review (Opus) of
c50e227: MERGE-READY if the owner accepts trade-offs T1-T3. Not merged.Reproduced. I reran 157 sim runs per tree with the dev's harness. All 136 of the dev's runs give identical row hashes, base and head. Export sums at a 5 s meter:
Every 1-2 s run is identical to base, and the "readings late" seconds are identical in all 157 runs. CI run 1591 is green on
c50e227. The local suite (hwsim 3b4ff84,--network none) gives 103 files, 0 failed.Trade-offs (the owner decides):
dead12_p5_n1_harmimport to 850 Wh with a 1.5 kW quarter. A gain share of 0.35 helps only at 10 s (165 Wh). The current constants are the best point I found.compute(); they are not slowed, and any override ends the wait.p1.pyandfreshness.pyrecord the measurement), so the code cannot see a better one. I simulated a 1 s meter whose reading reaches the add-on only when it changes:Findings (all low, none blocks):
test_safety136.pypart 5, last check: "a 1 s meter whose value changes every 5 s: not slow". That is true only when every reading arrives. Onha_signedthe same meter is slow. Please reword it so it does not read as a production guarantee.ctl_cycle.py:745-746andsettle.py:67: two mutants survivetest_safety136. One drops the reset on an override or mode trade. The other makes the "shown" testabs(), so it no longer checks the direction. Add one check each.settle.py:3,7says "4 s or more" and "under 4 s", and DECISIONS says "1-3 s". The rule is a median gap over 3.75 s; make the wording match.dead10_p6also rises +40 W (-6 -> 34 W).Full report:
reports/SAFETY-136-review-report.md.Round 2 re-check (Opus): MERGED as
a8ecf17intorelease/1.0, pinned to head0f9c119.settle.pydocstring changed inapp/sincec50e227. F1-F4 are fixed.or mode_tradestill survives. I left it without a test: a mode trade is a P_ARBITRAGE SET claim that outranks the loop, so the arbiter's target is notloop_w,movedis true and the wait resets anyway, and the loop's gain does not reach the wire during a trade.checks / unitis green on0f9c119. The local suite on0f9c119(hwsim 3b4ff84,--network none) gives 103 files, 0 failed.