MAINT-13 round 2: read the price gate without a new main.py option read
checks / unit (pull_request) Successful in 34m35s
infra-pin / fresh (pull_request) Successful in 1s

test_static_tariff forbids main.py reading `static_profile`; a small
static_gated() in static_tariff.py answers it, and the Controller keeps the
answer in `_price_gated` beside `cheap`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
glenn schrooyen
2026-10-08 11:10:33 +02:00
co-authored by Claude Sonnet 5.5
parent 6f05b2e4be
commit f7b83cc75c
3 changed files with 33 additions and 9 deletions
+5 -5
View File
@@ -91,7 +91,7 @@ from .price import Absence, Uncertainty, brussels, price_option, provider_config
from .price import required_spread as price_required_spread
from .price import uncertainty_of as price_uncertainty_of
from .price_history import PriceHistory
from .static_tariff import resolve_tariff, static_status
from .static_tariff import resolve_tariff, static_gated, static_status
from .tariff import PeakStore, PeakTracker, next_quarter
from .store import migrate_maint_keys, migrate_unit_keys
from .validate import (FIRMWARE_INGEST_VETO_S, ConfigRefused, refuse,
@@ -1188,6 +1188,7 @@ class Controller:
self.grid = None
self.peak_fc = None
self.cheap = False
self._price_gated = False # MAINT-13: is a price gate set up
# TARIFF-03. One line of prose for the status page saying WHICH tariff
# window we are in and why it is or is not cheap - so "Cheap window:
# no" is an evidenced answer rather than a bare no.
@@ -3312,6 +3313,7 @@ class Controller:
now_p = await self.hass.number(now_e)
avg_p = await self.hass.number(avg_e)
self.cheap = (now_p is not None and avg_p is not None and now_p <= avg_p)
self._price_gated = True
self.tariff_words = "Prices from Home Assistant entities"
else:
# TARIFF-03. §9.2 makes a static day/night tariff a first-class
@@ -3337,6 +3339,7 @@ class Controller:
# one and the loser is silent.
self.cheap, self.tariff_words = static_status(
o, datetime.now(timezone.utc))
self._price_gated = static_gated(o, datetime.now(timezone.utc))
# ⚠️ TARIFF-11. THE PROVIDER PLAN REACHES THE *Tariff* ROW HERE, AND
# THIS IS THE ONLY PLACE THE TWO SOURCES MEET. Everything above is the
@@ -3883,10 +3886,7 @@ class Controller:
really taking charge (sun outside cheap hours). Closed only when a gate
exists, it is shut, and the pack is not charging.
"""
o = self.o
gated = bool((o.get("price_now_entity") and o.get("price_avg_entity"))
or o.get("static_profile"))
if not gated or self.cheap:
if not self._price_gated or self.cheap:
return True
batt = inv.batt
return batt is not None and float(batt) <= -MAINT_CHARGING_W
+10
View File
@@ -1232,6 +1232,16 @@ def static_plan(opts: dict, since: datetime, until: datetime, *,
profile, "fallback" if provisional else "static")
def static_gated(opts: dict, now: datetime) -> bool:
"""MAINT-13: is a fixed tariff set up, so that a cheap window can hold a
charge back? False for no profile or one that cannot be used."""
try:
return bool(static_plan(opts, now, now + timedelta(days=MEAN_WINDOW_DAYS),
now=now).has_prices)
except ProfileInvalid:
return False
def static_status(opts: dict, now: datetime):
"""(cheap, words) for the status page and for main.py's cheap-window flag.