Files
goodwe-addon/goodwe_controller/DECISIONS/TARIFF-24-modes-need-the-tariff-task.md
T
glenn schrooyenandClaude 1c9fefd9dd 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>
2026-10-30 08:11:42 +01:00

71 lines
4.0 KiB
Markdown

# 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.