UX-13 P5: Dutch text, STYLE check, DOCS screenshots, period editor
- app/ui_text/nl.json and app/uitext.py: every page word in Dutch, English fallback per key; the page picks by browser language. Home's sentences move into ui_text `home_words`, so /status?lang=nl builds them in Dutch. - test_ui_text.py: EN/NL key and placeholder parity, STYLE limits, jargon lists for both languages, no inline details, untranslated Dutch caught. Shared rules moved unchanged to tools/style_rules.py. - tools/ux13_screenshots.sh/.py: DOCS screenshots from the demo fixtures in headless Chromium; docs/img/ux13/. Battery modes (P3): no screenshot. - "Other time windows": a list editor (ui.periods) that builds static_profile and names bad times, empty prices, overlaps and gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -217,6 +217,109 @@ check("the hook answers None while battery modes do not exist",
|
||||
check("so the toggle is stored as the page has it",
|
||||
edit(DEFAULTS, arbitrage_enabled=True)["arbitrage_enabled"] is True)
|
||||
|
||||
print("\n7. the time-window editor (UX-13 P5)")
|
||||
|
||||
|
||||
def rows(*spec):
|
||||
return [{"from": a, "to": b, "days": d, "price": p} for a, b, d, p in spec]
|
||||
|
||||
|
||||
def periods(r, base=SITE):
|
||||
return edit(base, ui__contract="custom", ui__fallback="custom",
|
||||
ui__periods=r)
|
||||
|
||||
|
||||
def refused(r):
|
||||
"""(code, values) of the DeriveError the rows give, or None."""
|
||||
try:
|
||||
periods(r)
|
||||
except U.DeriveError as err:
|
||||
return err.code, err.values
|
||||
return None
|
||||
|
||||
|
||||
# Five windows the day/night preset cannot say: a weekday peak, a weekday
|
||||
# night, Saturday split at noon, and Sunday.
|
||||
_five = rows(("06:30", "21:15", "mon-fri", 0.31), ("21:15", "06:30", "mon-fri", 0.19),
|
||||
("00:00", "12:00", "sat", 0.2), ("12:00", "00:00", "sat", 0.25),
|
||||
("00:00", "24:00", "sun", 0.18))
|
||||
_p5 = periods(_five)
|
||||
_prof5 = parse_static_profile(_p5["static_profile"])
|
||||
check("five odd windows write a profile the tariff parser reads, with every "
|
||||
"price in place", [p.eur_per_kwh for p in _prof5.periods]
|
||||
== [0.31, 0.19, 0.2, 0.25, 0.18] and _prof5.periods[3].end_min == 1440)
|
||||
check("...that the shipped start checks accept, and that reads back as the "
|
||||
"same rows", not validate_startup_config(_p5)
|
||||
and U.options_to_ui(_p5)["ui.contract"] == "custom"
|
||||
and [(r["from"], r["to"], r["days"], r["price"])
|
||||
for r in U.options_to_ui(_p5)["ui.periods"]]
|
||||
== [("06:30", "21:15", "mon-fri", 0.31), ("21:15", "06:30", "mon-fri", 0.19),
|
||||
("00:00", "12:00", "sat", 0.2), ("12:00", "24:00", "sat", 0.25),
|
||||
("00:00", "24:00", "sun", 0.18)])
|
||||
_named = dict(SITE, static_profile=json.dumps({"periods": [
|
||||
{"name": "peak", "from": "08:00", "to": "20:00", "days": "mon-sun",
|
||||
"eur_per_kwh": 0.33},
|
||||
{"name": "offpeak", "from": "20:00", "to": "08:00", "days": "mon-sun",
|
||||
"eur_per_kwh": 0.21}]}))
|
||||
_rows = U.options_to_ui(_named)["ui.periods"]
|
||||
_rows[1]["price"] = 0.2
|
||||
check("a stored profile's window names are kept when a price changes",
|
||||
[p.name for p in parse_static_profile(periods(_rows, base=_named)
|
||||
["static_profile"]).periods]
|
||||
== ["peak", "offpeak"])
|
||||
check("the day and night preset shows as three rows, so 'Other time windows' "
|
||||
"starts from what is stored",
|
||||
[r["days"] for r in U.options_to_ui(
|
||||
edit(SITE, ui__contract="daynight", ui__day_price=0.3,
|
||||
ui__night_price=0.2))["ui.periods"]]
|
||||
== ["mon-fri", "mon-fri", "sat,sun"])
|
||||
check("an overlap is named: the two windows, the day and the times",
|
||||
refused(rows(("07:00", "23:00", "mon-fri", 0.3),
|
||||
("22:00", "07:00", "mon-fri", 0.2),
|
||||
("00:00", "24:00", "sat,sun", 0.2)))
|
||||
== ("period_overlap", {"day": "mon", "from": "22:00", "to": "23:00",
|
||||
"a": 1, "b": 2}))
|
||||
check("a gap is named: the day and the times",
|
||||
refused(rows(("07:00", "22:00", "mon-sun", 0.3),
|
||||
("22:00", "24:00", "mon-sun", 0.2)))
|
||||
== ("period_gap", {"day": "mon", "from": "00:00", "to": "07:00"}))
|
||||
check("a gap on one day only is found on that day",
|
||||
refused(rows(("00:00", "24:00", "mon-fri", 0.3),
|
||||
("00:00", "24:00", "sat", 0.2)))
|
||||
== ("period_gap", {"day": "sun", "from": "00:00", "to": "24:00"}))
|
||||
check("bad times are named by window: '7h', '25:00', '07:60', '24:00' as a "
|
||||
"start", all(refused(rows(("00:00", "24:00", "mon-sun", 0.2), (a, b, "mon", 0.1)))
|
||||
== ("period_time", {"row": 2}) for a, b in
|
||||
(("7h", "08:00"), ("25:00", "08:00"), ("07:60", "08:00"),
|
||||
("24:00", "08:00"), ("07:00", "")))
|
||||
and refused(rows(("7:00", "07:00", "mon-sun", 0.2)))
|
||||
== ("period_zero", {"row": 1}))
|
||||
check("an empty price, and a price in cents, are named by window",
|
||||
refused(rows(("00:00", "24:00", "mon-sun", None)))
|
||||
== ("period_price", {"row": 1})
|
||||
and refused(rows(("00:00", "24:00", "mon-sun", 34)))
|
||||
== ("period_price_range", {"row": 1}))
|
||||
check("no windows at all is refused", refused([]) == ("no_periods", {}))
|
||||
from app import uipage # noqa: E402
|
||||
_msg = uipage.derive_message(U.DeriveError("x", "period_overlap", "ui.periods",
|
||||
day="sat", a=1, b=3, **{
|
||||
"from": "06:00", "to": "07:00"}))
|
||||
_nl = uipage.derive_message(U.DeriveError("x", "period_gap", "ui.periods",
|
||||
day="sun", **{"from": "00:00",
|
||||
"to": "07:00"}), "nl")
|
||||
check(f"the page shows the error beside the editor, with the day by name "
|
||||
f"({_msg['text']!r}, {_nl['text']!r})",
|
||||
_msg["field"] == "ui.periods" and "Windows 1 and 3" in _msg["text"]
|
||||
and "Saturday 06:00 to 07:00" in _msg["text"]
|
||||
and "zondag 00:00 tot 07:00" in _nl["text"])
|
||||
_raw = json.dumps({"periods": [
|
||||
{"from": "00:00", "to": "24:00", "days": "mon,wed", "eur_per_kwh": 0.2},
|
||||
{"from": "00:00", "to": "24:00", "days": "tue,thu-sun", "eur_per_kwh": 0.3}]})
|
||||
check("days the editor has no choice for stay in the Configuration tab, and "
|
||||
"survive a save", U.options_to_ui(dict(SITE, static_profile=_raw))
|
||||
["ui.periods"] is None and edit(dict(SITE, static_profile=_raw), gain=0.5)
|
||||
["static_profile"] == _raw)
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print(f"{len(fails)} FAILED")
|
||||
|
||||
Reference in New Issue
Block a user