Sync 0.19: the Forecast tab extends a cheap run with control's gap (TARIFF-24)
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>
This commit is contained in:
co-authored by
Claude
parent
8cca671a05
commit
1c9fefd9dd
@@ -50,9 +50,10 @@ was one input judged twice.
|
||||
**The rule** (`arbitrage.weather_uncertainty()`). With battery modes on and a
|
||||
usable forecast (`solar_fc` not None, the day plan's own test) the doubt is
|
||||
not added: the gap needed is the cycle cost, widened by the price product only.
|
||||
No forecast, or a stale or unusable one: 1.5 x, as before. It is read in three
|
||||
No forecast, or a stale or unusable one: 1.5 x, as before. It is read in four
|
||||
places that must agree: the run plan (`_price_runs()`), the charge gate
|
||||
(`_mode_roles()`) and Home's mirror (`homeview.trade_verdict()`). The forecast
|
||||
(`_mode_roles()`), Home's mirror (`homeview.trade_verdict()`) and the Forecast
|
||||
tab's longer cheap run (`dayview.py`, from release/1.0). The forecast
|
||||
is read at most every 60 s and the plan key already holds "forecast or not", so
|
||||
the gap follows in the cycle the read changes, both ways, with one exception:
|
||||
a run planned with a forecast that is under way keeps that gap to its end
|
||||
|
||||
@@ -23,7 +23,7 @@ from . import arbitrage, dayplan, homeview as hv, modetrade, priceruns
|
||||
from . import solarfc
|
||||
from .daycurve import Batt, curve
|
||||
from .packmode import SOLAR_FIRST
|
||||
from .price import Uncertainty, brussels, required_spread, uncertainty_of
|
||||
from .price import brussels, required_spread, uncertainty_of
|
||||
|
||||
BLOCKS = ("grid", "give", "cycle") # the kinds a battery's blocks carry
|
||||
LEVEL_DP = 1 # the tab shows a level to 0.1 %
|
||||
@@ -204,9 +204,12 @@ def _levels(c, est, fc, trust, now, end, runs, floor, ceil, notes_b, shadow,
|
||||
end_t = None
|
||||
if plan_on and cheap is not None and rp.charge_useful() \
|
||||
and n0 <= cheap.start < n1 and plan is not None:
|
||||
# The gap control's run plan needs (TARIFF-24): no x1.5 with a
|
||||
# forecast read.
|
||||
required = required_spread(
|
||||
arbitrage.threshold_eur_kwh(c.o),
|
||||
uncertainty_of(plan.read.product) | {Uncertainty.NO_WEATHER})
|
||||
uncertainty_of(plan.read.product)
|
||||
| arbitrage.weather_uncertainty(True, c.solar_fc))
|
||||
end_t = cheap.end
|
||||
for _ in range(24):
|
||||
ext = dataclasses.replace(rp, cheap=priceruns.Run(
|
||||
|
||||
@@ -1456,6 +1456,35 @@ hi_ = tab(c_t, at(10, 7))["days"][0]["batteries"][0]["levels"]
|
||||
check("on a controller the curve is built with the plan's trust "
|
||||
"(`solar_trust_pct`)", hi_ and max(v for _h, v in hi_) > hi_[0][1])
|
||||
|
||||
# TARIFF-24 (sync 0.19): the tab extends tomorrow's cheap run as control
|
||||
# re-plans it, so it asks control's gap: the cycle cost with a forecast read,
|
||||
# not 1.5 x it.
|
||||
from app.price import required_spread, uncertainty_of # noqa: E402
|
||||
gaps, _plan_real = [], dayview.priceruns.plan
|
||||
|
||||
|
||||
def _spy(*a, **k):
|
||||
gaps.append(k.get("required_eur_kwh"))
|
||||
return _plan_real(*a, **k)
|
||||
|
||||
|
||||
c_g = S.make("dynamic", at(17, 0))
|
||||
dayview.priceruns.plan = _spy
|
||||
try:
|
||||
tab(c_g, at(17, 0))
|
||||
finally:
|
||||
dayview.priceruns.plan = _plan_real
|
||||
_p = c_g.fresh_tariff_plan(at(17, 0))
|
||||
want = required_spread(arbitrage.threshold_eur_kwh(c_g.o),
|
||||
uncertainty_of(_p.read.product)
|
||||
| arbitrage.weather_uncertainty(True, c_g.solar_fc))
|
||||
print(f" dynamic 17:00: the tab's extension asks {gaps}, control needs {want}")
|
||||
check("the tab extends tomorrow's cheap run with control's gap (with a "
|
||||
"forecast: the cycle cost, not 1.5 x it)",
|
||||
c_g.solar_fc is not None and bool(gaps)
|
||||
and all(abs(g - want) < 1e-9 for g in gaps)
|
||||
and abs(want - arbitrage.threshold_eur_kwh(c_g.o)) < 1e-9)
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print(f"{len(fails)} FAILED")
|
||||
|
||||
Reference in New Issue
Block a user