UX-54 round 4: modes off reads tomorrow at Home's count, no relabel in the walk
checks / unit (pull_request) Successful in 48m51s
infra-pin / fresh (pull_request) Successful in 2s

Modes off has no curve (no forecast is read, no house profile kept), so
dayview._levels() never ran there and tomorrow's legs were drawn from the
bound. homeview.trade_runs(ahead=f) now reads each leg at f(name, kind,
start), as mode_runs() does: no give while the emptiest battery is at the
floor, each share sized from that level. dayview._counted() gives a give
the level by Home's own count (the level now, plus what the blocks before
it put in, less what they take out); a charge is read from the floor,
because a full battery covers the house before the cheap hour.

The battery line says a give is planned when one is and the battery has
no give block, also with modes off.

test_ux54.py: the modes-on walk no longer relabels a GIVE role at the
floor; it asks control's own discharge envelope (packshare.envelope())
with each pack's cut-out at the floor. The peak-guard 20 % case asserts
the curve is at the floor at the 17:00 give start. A modes-off walk on a
cloudy day (arbitrage.decide() with the cycle's inputs, the packs moved by
what control does and by the house), cases A-D: control charges and gives
in exactly the quarters the tab has a block. trade_runs(ahead=f) unit
check. _stops() ends a give block where the level the tab shows is at the
floor (LEVEL_DP).

No decision, command, timing or option key changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
glenn schrooyen
2026-10-27 03:06:12 +01:00
co-authored by Claude
parent 5ab8622265
commit 4256bd3eda
4 changed files with 326 additions and 42 deletions
@@ -24,6 +24,16 @@ battery's expected level with its blocks. It follows the add-on, as Home does
from the floor and a give from the ceiling. The blocks and the curve follow
each other and are taken to a fixed point. The gate (`mode_gate`, UX-53) is
not bypassed.
- **Modes off has no curve** (no forecast is read, no house profile kept), so
`trade_runs(ahead=f)` reads each leg at Home's own count
(`dayview._counted()`): the level now, plus what the blocks before it put in,
less what they take out. No sun is read and the house only takes, so no give
block where the emptiest battery is at the floor by that count, and control
does not give there. A charge is read from the floor: a full battery covers
the house before the cheap hour, and control charges it back then. Walked
every 15 minutes on a cloudy day with the house (`test_ux54.py` part 7,
cases A-C), control charges and gives in exactly the quarters the tab has a
block.
- **A block lasts as long as control acts.** A cheap run is sized for all the
batteries charging together, so a slower one is still below
`modetrade.charge_to()` when it ends, and control plans the next cheap slots
@@ -33,8 +43,10 @@ battery's expected level with its blocks. It follows the add-on, as Home does
then ends where the curve has it at its own level (the curve puts a point
there), and a give block where it reaches the floor (a step end, within the
0.1 % the tab shows). Control, walked every 15 minutes through tomorrow
(`test_ux54.py` part 7, no tolerance), charges and gives in exactly the
quarters the tab has a block.
(`test_ux54.py` part 7), charges and gives in exactly the quarters the tab
has a block. A GIVE role counts only where control's own discharge envelope
(`packshare.envelope()`) is open; the walk puts each pack's cut-out at the
floor.
- **The sun** is what `solarfc` reads. An hourly split (Solcast
`detailedForecast`, Open-Meteo `wh_period`) is used as given. A daily total
(Forecast.Solar, and any other state-only sensor, the owner's three "today
@@ -85,8 +97,16 @@ is `SolarToday.add()` after the solar read, in a `try`, in memory.
- Tomorrow shows price blocks only. A monthly cycle is drawn today, where the
curve then stops for that battery.
- The curve uses the arbitrage floor and ceiling, as the plan does, not the
battery's own cut-out level. The house profile is a median per hour, net of
the solar the house used.
battery's own cut-out level. Control stops a pack at its cut-out, so with a
cut-out under the floor a battery in a give run covers the house below the
floor; Home draws no block there either. The house profile is a median per
hour, net of the solar the house used.
- Modes off: the count knows no sun and no house. A battery at the floor that
the sun fills before a give gets no block until then (the tab says "The
add-on plans to give power. No block shows while the battery has no energy
to give."), and a small charge may be used by the house before the give.
A leg is the verdict's as it stands now, one price period, as on Home; with
several equal prices in a row control takes the next one as each passes.
- Measured solar starts when the add-on starts; the tab says from when.
## Found, not changed