PRICE-01 round 2: the token on no page, fixed-tariff note, token length, F3 words, test gaps
checks / unit (pull_request) Successful in 39m0s
infra-pin / fresh (pull_request) Successful in 2s

F1: every password option is shown as ******** (set) or empty in /config/model
(options, running, ui) and in a stale refusal; the classic page draws it as a
password field; a save or validate that sends ******** or nothing keeps the
stored value. F2: a static_profile beside price_service is said once at start
(notification and log), not refused. F3 (owner, 2026-10-14): the no-price row,
Home line and notification only while maintenance_enabled is on. F4: tests for
the reviewer's R01 and R10. F5: price_service_token must be 16+ characters.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
This commit is contained in:
glenn schrooyen
2026-10-14 18:56:51 +02:00
co-authored by Claude
parent 4eb88f7fda
commit 0801776645
12 changed files with 767 additions and 17 deletions
+21 -2
View File
@@ -90,7 +90,8 @@ from .p1 import P1Ingest, build_source, is_enabled
from .price import Absence, Uncertainty, brussels, price_option, provider_configured
from .price import required_spread as price_required_spread
from .price import uncertainty_of as price_uncertainty_of
from .price_chain import PriceWatch, gate_for, uses_chain
from .price_chain import (PriceWatch, fixed_tariff_unused, gate_for,
uses_chain)
from .price_history import PriceHistory
from .static_tariff import resolve_tariff, static_gated, static_status
from .tariff import PeakStore, PeakTracker, next_quarter
@@ -1213,6 +1214,7 @@ class Controller:
self.price_gap = False
self.price_gap_since = None
self._price_gap_announced = False
self._fixed_unused_said = False
self._gated_key = None # MAINT-13: (options, hour) of the cache
# 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:
@@ -4030,7 +4032,10 @@ class Controller:
gate = gate_for(published, now)
self.cheap, self._price_gated = gate.cheap, True
self.tariff_words = gate.words
self.price_gap = (not gate.covered) and self.tariff_at is not None
# Owner, 2026-10-14: the words say 'maintenance waits', so they show
# only while the maintenance charge is on.
self.price_gap = ((not gate.covered) and self.tariff_at is not None
and self.o.get("maintenance_enabled") is True)
last = self._last_published
if gate.covered or last is None:
self.price_gap_since = None
@@ -5586,6 +5591,7 @@ class Controller:
# Not a safety check, so it is one of the cheap ones: an attribute test
# and, on a change only, one Home Assistant call.
await self.check_price_gap()
await self.check_fixed_unused()
# --- SAFETY-31's alarm, and its boundary is the LATCH, not a number ---
# Unconstrained where the two above are not: it grades
@@ -6223,6 +6229,19 @@ class Controller:
await self.hass.call("persistent_notification", "dismiss",
messages.prices_back().data())
async def check_fixed_unused(self) -> None:
"""PRICE-01 F2. A `static_profile` beside `price_service` no longer
drives the cheap window or the maintenance gate. Not refused: said once,
at the first cycle, in the log and as one notification."""
if self._fixed_unused_said:
return
self._fixed_unused_said = True
if fixed_tariff_unused(self.o):
_LOG.warning("The fixed tariff is not used, because the price "
"service gives the prices.")
await self.hass.call("persistent_notification", "create",
messages.fixed_tariff_unused().data())
def _gap_args(self) -> tuple:
"""`(since, now)` in Brussels time for the gap's words. `since` is None
when no price was ever published."""