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