UX-77: Home draws today's grid charge for the hours control will really charge
The cheap run is sized at the full rating, so under the peak limit a battery is still short when it ends, and control plans the next run then. Home drew only the run under way. `cheaproll` rolls the run on the way control does (`modetrade.energy()` + `priceruns.plan()` at each run end, lifted from `dayview._levels()`), reading the levels at the charge power the peak limit leaves (peak aim less the house, else the rating). `homeview.mode_runs()` and `price_windows()` use it for today: the grid block, the Today line and the chart's cheap bars run to where control stops, each battery's block to its own stop. Display only: no control change. test_ux54: today's grid block of the night-before scenario now follows the roll. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
af2d0d2f61
commit
45535e19d7
@@ -19,7 +19,7 @@ section at the bottom and is held to STYLE.md by test_messages.py.
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from . import arbitrage, messages, peakguard, uitext
|
||||
from . import arbitrage, cheaproll, messages, peakguard, priceruns, uitext
|
||||
from .dayplan import EFF
|
||||
from .maintenance import CHARGE as M_CHARGE, DRAIN as M_DRAIN, HOLD as M_HOLD
|
||||
from .maintenance import IDLE as M_IDLE, next_slot_start
|
||||
@@ -218,14 +218,20 @@ def price_windows(c, plan, verdict, now=None):
|
||||
"""`[(kind, start, end)]`: where the add-on itself buys ("cheap") and
|
||||
gives ("dear"). With battery modes on that is the kept `priceruns.RunPlan`
|
||||
(`c.mode_runs`): the cheap run when charging in it pays and the trade gate
|
||||
allows it (UX-53), and every give run. With them off it is the arbitrage
|
||||
verdict's legs the add-on will take, the ones `trade_runs()` draws
|
||||
(`trade_legs()`, given `now`). Nothing is classified here."""
|
||||
allows it (UX-53), and every give run. UX-77: the cheap run is drawn to
|
||||
where control stops charging as it rolls on (`cheaproll`). With them off
|
||||
it is the arbitrage verdict's legs the add-on will take, the ones
|
||||
`trade_runs()` draws (`trade_legs()`, given `now`). Nothing is classified
|
||||
here."""
|
||||
if getattr(c, "modes_on", False):
|
||||
rp = getattr(c, "mode_runs", None)
|
||||
if rp is None or not rp.on or not _price_mode_in_use(c):
|
||||
return []
|
||||
out = [("cheap", rp.cheap.start, rp.cheap.end)] \
|
||||
end = rp.cheap.end
|
||||
roll = None if now is None else cheaproll.for_today(c, rp, now)
|
||||
if roll is not None:
|
||||
end = roll.last(end) # UX-77: to where control stops
|
||||
out = [("cheap", rp.cheap.start, end)] \
|
||||
if rp.charge_useful() and getattr(c, "mode_gate", None) is None \
|
||||
else []
|
||||
return out + [("dear", g.start, g.end) for g in rp.give]
|
||||
@@ -422,6 +428,7 @@ def mode_runs(c, day0, day1, now, shadow, ahead=False, rp=None):
|
||||
`ahead=True`) a charge from the floor and a give from the ceiling. The
|
||||
same filter, the same `MIN_KWH`. `rp`: a run plan to read instead of
|
||||
`c.mode_runs`. Home never passes either."""
|
||||
today = rp is None and not ahead
|
||||
rp = rp if rp is not None else getattr(c, "mode_runs", None)
|
||||
if getattr(c, "mode_tick", None) is None:
|
||||
return [], None # no cycle yet: nothing is known
|
||||
@@ -431,6 +438,9 @@ def mode_runs(c, day0, day1, now, shadow, ahead=False, rp=None):
|
||||
return [], "not_worth"
|
||||
floor, ceiling = arbitrage.soc_bounds(c.o)
|
||||
gate = getattr(c, "mode_gate", None)
|
||||
# UX-77: today's grid block runs to where control stops charging (the run
|
||||
# rolls on when it ends), each battery's to its own stop.
|
||||
roll = cheaproll.for_today(c, rp, now) if today else None
|
||||
out = []
|
||||
for i, inv in enumerate(c.inverters):
|
||||
pm = c.pack_mode(inv)
|
||||
@@ -465,7 +475,10 @@ def mode_runs(c, day0, day1, now, shadow, ahead=False, rp=None):
|
||||
if gate is None and rp.charge_useful() and (
|
||||
need > 0.0 if need is not None
|
||||
else pm.mode in GRID_CHARGE_MODES):
|
||||
blocks.append(("grid", rp.cheap))
|
||||
cheap = rp.cheap if roll is None else priceruns.Run(
|
||||
rp.cheap.start, roll.end_for(inv.name, rp.cheap.end),
|
||||
rp.cheap.mean_eur_kwh)
|
||||
blocks.append(("grid", cheap))
|
||||
sell = rp.sell if pm.sell and pm.can_sell else None
|
||||
for g in rp.give:
|
||||
if sell is not None and g.start <= sell.start < g.end:
|
||||
|
||||
Reference in New Issue
Block a user