UX-77 round 2: Home's roll shares the peak room by room left, as control does #235

Merged
admin merged 1 commits from UX-77-2 into release/1.0 2026-11-06 11:19:21 +01:00
Owner

Rig finding F-1. cheaproll._Charge shared the peak room by rating; control shares it by room left to full (packshare.weights()). With unequal levels one battery's drawn block ended too early.

The roll now uses packshare.weights() over the simulated levels (re-read each 5-min step, each battery capped at its rating, a full one drops out). No control change.

test_ux77.py: two unequal-level walks (rig checks 2 and 3), a by-rating mutant that goes red, and the walks now model control's split by room left (the old by-rating model was the same mistake). Part 2 uses its own level (B at 94 %) since B no longer finishes early in the shared walk.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC

Rig finding F-1. `cheaproll._Charge` shared the peak room by rating; control shares it by room left to full (`packshare.weights()`). With unequal levels one battery's drawn block ended too early. The roll now uses `packshare.weights()` over the simulated levels (re-read each 5-min step, each battery capped at its rating, a full one drops out). No control change. `test_ux77.py`: two unequal-level walks (rig checks 2 and 3), a by-rating mutant that goes red, and the walks now model control's split by room left (the old by-rating model was the same mistake). Part 2 uses its own level (B at 94 %) since B no longer finishes early in the shared walk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
admin added 1 commit 2026-11-06 09:11:14 +01:00
UX-77: Home's roll shares the peak room by room left to full, as control does
checks / unit (pull_request) Successful in 26m34s
infra-pin / fresh (pull_request) Successful in 2s
6d2b9b5335
The cheap-run roll (cheaproll._Charge) shared the peak room by rating, control
shares it by room left to full (packshare.weights). With unequal levels one
battery's drawn block ended too early (rig F-1). The roll now uses
packshare.weights over the simulated levels, each battery capped at its rating,
a full one dropping out. test_ux77 gains two unequal-level walks and a
by-rating mutant; its walks model control's split by room too.

Co-Authored-By: Claude <noreply@anthropic.com>
Author
Owner

Review (round 2): approve and merge

Head reviewed: 6d2b9b5, on release/1.0 47c89a1.

Verdict: right. The roll now shares the room the same way control's commands do, and it changes no control code.

1. Share matches control

I drove the real control cycle (read_inputs() + cycle(), arbiter, allocator with pack_weights, peak guard on P1) with two plants at 5000 W / 15.36 kWh and 4600 W / 20.48 kWh. Control's charge per battery was read from its setpoint commands. Home was read from homeview.build() at every quarter + 2 min.

Night Control's split (A / B) Room left (A : B) Misses, head / base
Equal 30/30, LS+LS, 0.6 kW 1229 / 1639 W 10.75 : 14.34 4 / 16
Unequal 30/20, LS+LS, 0.6 kW 1136 / 1731 W 10.75 : 16.38 3 / 23
Near full 40/88, LS+CH, 2.85 kW 475 / 127 W 9.22 : 2.46 0 / 55
B at 90, LS+LS, 0.6 kW A only - 0 / 0
  • Control splits by room left to full, to the watt. A by-rating split would give 1493 / 1373 W.
  • _shares() matches this. No battery goes above its rating, and a full battery drops out because chargers() is re-read every 5 minutes.
  • Every miss on base, and on the by-rating mutant (the same counts), is a block that ends early.
  • Every miss left on head is in the run's last hour (N-1 below).

2. Test model

The old walk split by rating. Real control does not, so the old model was wrong and its green on the old walks was false.

The new split_by_room() gives the same stops as real control: 02:49 / 03:12 on the check 2 walk.

It is still a copy of the rule, not derived from control's commands (N-3).

Part 2 at B 94 %: B charges 22:00-00:54 and is at its level at 02:30, so the assertion still tests "charged, now done". At 90 %, real control charges B to 07:00, so the old fixture only held under the wrong model.

3. No control change

  • The diff is cheaproll.py, test_ux77.py and DECISIONS. cheaproll is imported only by homeview and dayview.
  • Digests, base vs head: identical command count, sha, net EUR and end SoC on 5 cases (modes_off, ownls_state_epex, ownls_hourly_real, own_state_epex, own_hourly_real).
  • The probe's control commands are also identical on base and head.

4. Mutant

I made a purged copy with _shares by rating. Both new walks went red, plus the 3 old walks: 5 failed.

The head's test_ux77 against the base tree is also red, with 5 failed.

5. Suite and CI

  • Local full suite: 96 files, 0 failed (1423 s).
  • CI run 1540 (job 2691) on 6d2b9b5 is a real run: 96 files, 0 failed, and test_ux77 passed.

Not blocking (follow-ups)

  • N-1: in the run's last hour, the block runs past the battery's stop, by up to one price slot.
    • Seen: equal night at 02:02, block to 03:00 while control stops 02:27. Unequal night at 03:02, B's block to 04:00 while control stops 03:12.
    • Cause: for_today() returns None when the run under way is not rolled, so Home draws the plain hourly run.
    • This is round 1's design and within part 1's one-slot tolerance. The "within one quarter" rule holds only at the walk's 5 sample times.
    • Fix idea: clip each battery's block to its _Charge stop even when there is no roll.
  • N-2: when a capacity is unknown, the roll splits by rating but control weighs every battery 1.0 (equal split). This is about 4 % off, and only with an unknown capacity.
  • N-3: the walk's split is a copy of control's rule. To derive it from control, drive read_inputs() + cycle() and read the per-battery set_number commands (my probe does this). Alternatively, take the allocation from c.allocation.
  • Info: a 2-minute tail with no block (A, 02:47) is MIN_KWH hiding under 0.05 kWh, by design.
  • Info: at N≥3 with a Solar first battery on, pack_weights()'s mode layers could split differently from packshare.weights(). I did not test this.

Probe: scratch/UX-77/review2/probe/probe.py. Logs: scratch/UX-77/review2/logs/.

## Review (round 2): approve and merge Head reviewed: `6d2b9b5`, on `release/1.0` `47c89a1`. **Verdict: right.** The roll now shares the room the same way control's commands do, and it changes no control code. ### 1. Share matches control I drove the real control cycle (`read_inputs()` + `cycle()`, arbiter, allocator with `pack_weights`, peak guard on P1) with two plants at 5000 W / 15.36 kWh and 4600 W / 20.48 kWh. Control's charge per battery was read from its setpoint commands. Home was read from `homeview.build()` at every quarter + 2 min. | Night | Control's split (A / B) | Room left (A : B) | Misses, head / base | |---|---|---|---| | Equal 30/30, LS+LS, 0.6 kW | 1229 / 1639 W | 10.75 : 14.34 | 4 / 16 | | Unequal 30/20, LS+LS, 0.6 kW | 1136 / 1731 W | 10.75 : 16.38 | 3 / 23 | | Near full 40/88, LS+CH, 2.85 kW | 475 / 127 W | 9.22 : 2.46 | 0 / 55 | | B at 90, LS+LS, 0.6 kW | A only | - | 0 / 0 | - Control splits by room left to full, to the watt. A by-rating split would give 1493 / 1373 W. - `_shares()` matches this. No battery goes above its rating, and a full battery drops out because `chargers()` is re-read every 5 minutes. - Every miss on base, and on the by-rating mutant (the same counts), is a block that ends early. - Every miss left on head is in the run's last hour (N-1 below). ### 2. Test model The old walk split by rating. Real control does not, so the old model was wrong and its green on the old walks was false. The new `split_by_room()` gives the same stops as real control: 02:49 / 03:12 on the check 2 walk. It is still a copy of the rule, not derived from control's commands (N-3). Part 2 at B 94 %: B charges 22:00-00:54 and is at its level at 02:30, so the assertion still tests "charged, now done". At 90 %, real control charges B to 07:00, so the old fixture only held under the wrong model. ### 3. No control change - The diff is `cheaproll.py`, `test_ux77.py` and DECISIONS. `cheaproll` is imported only by `homeview` and `dayview`. - Digests, base vs head: identical command count, sha, net EUR and end SoC on 5 cases (modes_off, ownls_state_epex, ownls_hourly_real, own_state_epex, own_hourly_real). - The probe's control commands are also identical on base and head. ### 4. Mutant I made a purged copy with `_shares` by rating. Both new walks went red, plus the 3 old walks: 5 failed. The head's `test_ux77` against the base tree is also red, with 5 failed. ### 5. Suite and CI - Local full suite: 96 files, 0 failed (1423 s). - CI run 1540 (job 2691) on 6d2b9b5 is a real run: 96 files, 0 failed, and `test_ux77` passed. ### Not blocking (follow-ups) - **N-1:** in the run's last hour, the block runs past the battery's stop, by up to one price slot. - Seen: equal night at 02:02, block to 03:00 while control stops 02:27. Unequal night at 03:02, B's block to 04:00 while control stops 03:12. - Cause: `for_today()` returns None when the run under way is not rolled, so Home draws the plain hourly run. - This is round 1's design and within part 1's one-slot tolerance. The "within one quarter" rule holds only at the walk's 5 sample times. - Fix idea: clip each battery's block to its `_Charge` stop even when there is no roll. - **N-2:** when a capacity is unknown, the roll splits by rating but control weighs every battery 1.0 (equal split). This is about 4 % off, and only with an unknown capacity. - **N-3:** the walk's split is a copy of control's rule. To derive it from control, drive `read_inputs()` + `cycle()` and read the per-battery `set_number` commands (my probe does this). Alternatively, take the allocation from `c.allocation`. - Info: a 2-minute tail with no block (A, 02:47) is `MIN_KWH` hiding under 0.05 kWh, by design. - Info: at N≥3 with a Solar first battery on, `pack_weights()`'s mode layers could split differently from `packshare.weights()`. I did not test this. Probe: `scratch/UX-77/review2/probe/probe.py`. Logs: `scratch/UX-77/review2/logs/`.
admin merged commit c66aa7f69a into release/1.0 2026-11-06 11:19:21 +01:00
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#235