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>
4.0 KiB
TARIFF-24 - battery modes need the tariff task
Status: FIXED in 0.19.1 (hotfix from 0.19.0, owner go 2026-10-28).
Follows TARIFF-10 (fetching is not trading). Tests: test_tariff24.py.
The defect. The control cycle gets prices only from
Controller.tariff_plan, which only run_tariff_refresh() stores. amain()
starts that task when tariff_refresh_wanted(opts), which was
provider_configured(opts) or arbitrage.enabled(opts). Battery modes read the
plan but were not a term. With battery modes on, a day/night or fixed
static_profile, price_provider: off and arbitrage_enabled: false (the
site trade switch is not read with modes on) no task ran. Then
fresh_tariff_plan() was None, _price_runs() was None and mode_runs was
None, so Leftover solar and Cheap hours never charged from the grid. The
Batteries screen said "No prices, so it follows the house" while Home priced
the day from static_plan() itself (homeview.build_plan()).
The fix (main.py, tariff_refresh_wanted()). packmode.enabled(opts)
is a third term. The gate stays a widening: nothing that started the task
before stops. No HTTPS call is added: with price_provider: off,
resolve_tariff() is the pure static_plan().
Not changed. Modes off, arbitrage off, no feed still starts no task. The control law, the price runs and every message are untouched.
One rule, not two. Gating on "modes on and a static profile" was
considered. It saves one arithmetic pass every 30 min on a site with modes on
and no prices at all, and it adds a second condition that has to track what
resolve_tariff() accepts. With no profile and no feed the plan is no_data,
exactly what the cycle sees without the task.
UX-55. Home said "no prices have arrived" (mode_runs None) with every
battery on Solar first. The same cause, and this fix clears it for a site with
a static profile (test_tariff24.py part 5). A site with no profile and no
feed still has no prices, and Home still says so.
Round 2 (AUDIT-02 F1): the forecast replaces the "no weather" doubt
Decided 2026-10-28 (owner), in the same hotfix. The price gap a mode needs
was cycle_cost_threshold_eur x 1.5 (Uncertainty.NO_WEATHER) whether or not
a solar forecast was read. At the shipped cost 0.10 that is 0.15, so a
day/night gap (0.08) and many dynamic days never traded, and the screens said
only "Prices differ too little".
Why the doubt goes with a forecast. The widening stands in for not knowing the weather. The forecast is that knowledge, and the day plan already trusts it for how much to charge. Trusting it for the plan but doubting it for the gap 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 four
places that must agree: the run plan (_price_runs()), the charge gate
(_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
(Controller.weather_held()), so a sensor that goes unavailable does not stop
or restart a Cheap hours charge (a Leftover solar battery still stops: without
a forecast it has no planned level). The wider gap applies from the next run.
A run that has started runs to its end: a give run that starts during the hold
is kept after it (keep_started()), with no grid charge or sale.
Not changed. Modes off (the site trade) keeps the doubt always. The shipped cost 0.10 still needs a gap of 0.10 even with a forecast: a 0.08 day/night site must set Battery cycle cost to 0.08 or less. Showing the two numbers on the screens (AUDIT-02 F1 option b) is a separate card.