UX-08 2d: plain log lines, the rest of the status page, and DOCS
Every WARNING and ERROR line is one plain line of at most 25 words, with no card ids, capitals or semicolons; the reasoning of the ten longest is on a DEBUG line. The ux08 allowlist is empty and its cap is 0 (43 -> 0). The status page: the month-peak and peak-guard rows, the stale-reading row, the top table (Why, Tariff, Arbitrage, Shadow mode, labels), the Stopped banner and the line under the buttons, which now shows the plain hint of each event instead of the raw action log. The Supervisor question at start logs one line with a real reason. Refusals are counted as shown (labels expanded, long cases driven), seven were shortened, the card and parse errors lost their internals, a blank value reads "empty", and the panel no longer files inverters.max_w under the flat max_w. Option names lose "(JSON)", the NL texts are checked too. DOCS gets a glossary (control board, isolator, Developer Tools) and card ids move from headings to a "For installers" line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
This commit is contained in:
@@ -2189,7 +2189,7 @@ _p = validate_startup_config(
|
||||
check("a provider AND the Home Assistant price entities is refused - two price "
|
||||
"sources on one site", len(_p) == 1)
|
||||
check("...and the message says which one actually wins",
|
||||
why(_p, "are also set and would win"))
|
||||
why(_p, "would win over it"))
|
||||
# TARIFF-03 added a second source with the same silent-loser shape.
|
||||
_ps = validate_startup_config(
|
||||
vbase(static_profile='{"periods": [{"from":"00:00","to":"24:00",'
|
||||
@@ -3427,8 +3427,8 @@ check(f"network charges folded into `offset` move the card's own price to "
|
||||
isinstance(_double_price, float) and abs(_double_price - 15.55) > 10)
|
||||
check(f"...and it is refused, by the CARD rule, naming the discrepancy and "
|
||||
f"the network-charge cause ({_double[:60]!r})",
|
||||
"does not match its own card" in _double and "the card says" in _double
|
||||
and "(price_taxes_eur_kwh), not in the offset" in _double)
|
||||
"the card says" in _double
|
||||
and "network charges are not in the offset" in _double)
|
||||
|
||||
_CORRUPTIONS = {
|
||||
"VAT left out (vat_pct 0)": spoilt(vat_pct=0),
|
||||
@@ -3447,7 +3447,7 @@ for _name, _blob in sorted(_CORRUPTIONS.items()):
|
||||
_why = refusal(_blob)
|
||||
check(f"{_name}: refused, and by the CARD rule rather than by another one "
|
||||
f"({_why[:48]!r})",
|
||||
"does not match its own card" in _why and "the card says" in _why)
|
||||
"the card says" in _why)
|
||||
|
||||
# ⚠️ THE TOLERANCE IS A REAL EDGE AND IT IS GRADED FROM BOTH SIDES, or
|
||||
# "within 0.01" and "within anything" would be the same check.
|
||||
@@ -3459,7 +3459,7 @@ _outside = refusal(spoilt(card_expect_incl_vat=round(
|
||||
+ CARD_TOLERANCE * 2.0, 6)))
|
||||
check(f"half a tolerance out passes and twice a tolerance out does not, so "
|
||||
f"{CARD_TOLERANCE} is the number being enforced rather than a shape",
|
||||
_inside == "" and "does not match its own card" in _outside)
|
||||
_inside == "" and "the card says" in _outside)
|
||||
|
||||
# A card that is absent is refused, and NOT by the generic unknown-field rule.
|
||||
check(f"a formula with NO card block is refused, and the message says why the "
|
||||
@@ -3851,12 +3851,12 @@ check("a formula that does not reproduce its card is refused AT STARTUP, "
|
||||
why(validate_startup_config(
|
||||
vbase(price_provider="custom_formula",
|
||||
price_formula=json.dumps(spoilt(offset=13.0))),
|
||||
-5000, 5000), "does not match its own card"))
|
||||
-5000, 5000), "the card says"))
|
||||
check("...and a formula that is not JSON at all is refused with the shape it "
|
||||
"wanted, rather than with a traceback",
|
||||
why(validate_startup_config(
|
||||
vbase(price_provider="custom_formula", price_formula="0.102*spot+1"),
|
||||
-5000, 5000), "not valid JSON"))
|
||||
-5000, 5000), "price_formula is not valid near character"))
|
||||
check(f"...and a card outside the plausible band is refused too, WITH THE "
|
||||
f"SHIPPED taxes in place - the flat "
|
||||
f"{PRICE_OPTION_DEFAULTS['price_taxes_eur_kwh']} must not be able to "
|
||||
@@ -4100,13 +4100,14 @@ def _t11_render(over=None, *, plan=None, plan_age_s=0.0, soc=55.0, cycles=1,
|
||||
# row that is WRONG IN EXACTLY THE WAY THE DEFECT WAS, and both answers are
|
||||
# asserted.
|
||||
_T11_SLOTS = {
|
||||
"points": r"(\d+) points over \d+ h",
|
||||
"hours": r"\d+ points over (\d+) h",
|
||||
"firm": r"(\d+) firm / \d+ carried",
|
||||
"carried": r"\d+ firm / (\d+) carried",
|
||||
"horizon": r"for a (\d+) h horizon",
|
||||
"age_s": r"fetched (\d+) s ago",
|
||||
"spacings": r"([\d+]+) s spacing",
|
||||
# UX-08 2d: the row is in plain words; every figure keeps its own slot.
|
||||
"points": r"(\d+) prices over \d+ hours",
|
||||
"hours": r"\d+ prices over (\d+) hours",
|
||||
"firm": r"(\d+) published and \d+ estimated",
|
||||
"carried": r"\d+ published and (\d+) estimated",
|
||||
"horizon": r"for (\d+) hours ahead",
|
||||
"age_s": r"Price plan from (\d+) s ago",
|
||||
"spacings": r"([\d+]+) s apart",
|
||||
# ⚠️ THE *Arbitrage* ROW'S OWN AGE SLOT, and it is deliberately the same
|
||||
# pattern for "decided N s ago" and "the last one was taken N s ago" -
|
||||
# WHICH of the two sentences was rendered is graded by inequality below,
|
||||
@@ -4144,13 +4145,13 @@ def _slot(row, name):
|
||||
return derived(_get)
|
||||
|
||||
|
||||
_T11_GOOD = ("x (recomputed this tick); provider plan fetched 7 s ago for a "
|
||||
"48 h horizon: dynamic_dayahead from generic_dayahead, "
|
||||
"132 points over 33 h, 36 firm / 96 carried, 900+3600 s spacing")
|
||||
_T11_SHIPPED_DEFECT = ("x (recomputed this tick); provider plan fetched 7 s "
|
||||
"ago for a 48 h horizon: dynamic_dayahead from "
|
||||
"generic_dayahead, 132 points over 132 h, "
|
||||
"96 firm / 36 carried, 900+3600 s spacing")
|
||||
_T11_GOOD = ("x. Price plan from 7 s ago, for 48 hours ahead: day-ahead "
|
||||
"prices from the price feed, 132 prices over 33 hours, "
|
||||
"36 published and 96 estimated, 900+3600 s apart")
|
||||
_T11_SHIPPED_DEFECT = ("x. Price plan from 7 s ago, for 48 hours ahead: "
|
||||
"day-ahead prices from the price feed, 132 prices over "
|
||||
"132 hours, 96 published and 36 estimated, 900+3600 s "
|
||||
"apart")
|
||||
# The slots divide into the ones a *Tariff*/*Arbitrage* ROW carries and the
|
||||
# ones a REFUSAL sentence carries; each group is calibrated against its own
|
||||
# well-formed example, because a slot asked of the wrong kind of sentence is
|
||||
@@ -4399,11 +4400,11 @@ check(f"AC, AND THE WHOLE OF DEFECT 4: 'trading is off' and 'trading is on "
|
||||
f"in the options blob",
|
||||
_T11_OFF[0]["Arbitrage"] != _T11_ON_HELD[0]["Arbitrage"])
|
||||
check(f"...and the armed-but-held row carries the CONTROL LOOP'S OWN REASON "
|
||||
f"({_T11_ON_HELD[1].reason!r}), so the operator is told which input is "
|
||||
f"({_T11_ON_HELD[1].why_words!r}), so the operator is told which input is "
|
||||
f"missing rather than being sent to look at a switch that is already on. "
|
||||
f"The reason comes from main.py, not from this file",
|
||||
_T11_ON_HELD[1].reason
|
||||
and _T11_ON_HELD[1].reason in _T11_ON_HELD[0]["Arbitrage"])
|
||||
_T11_ON_HELD[1].why_words
|
||||
and _T11_ON_HELD[1].why_words in _T11_ON_HELD[0]["Arbitrage"])
|
||||
check(f"...and a cycle that DID reach the step renders the strategy's own "
|
||||
f"sentence - {_T11_ON_OK[1].arb.explain()[:50]!r} - rather than a "
|
||||
f"re-derivation of it, which is the guarantee OPT-04 put on this row and "
|
||||
@@ -4449,10 +4450,10 @@ check(f"DRIVEN, no clock touched: a decision stands "
|
||||
and _T11_DROP_STALE is True
|
||||
and _T11_ON_DROPPED[0]["Arbitrage"] != _T11_ON_OK[0]["Arbitrage"])
|
||||
check(f"...and it names the reason the cycle stopped "
|
||||
f"({_T11_ON_DROPPED[1].reason!r}) beside the age of the decision it is "
|
||||
f"({_T11_ON_DROPPED[1].why_words!r}) beside the age of the decision it is "
|
||||
f"still showing, so 'the strategy decided this' and 'the strategy last "
|
||||
f"decided this' are not one sentence",
|
||||
_T11_ON_DROPPED[1].reason in _T11_ON_DROPPED[0]["Arbitrage"]
|
||||
_T11_ON_DROPPED[1].why_words in _T11_ON_DROPPED[0]["Arbitrage"]
|
||||
and isinstance(_slot(_T11_ON_DROPPED[0]["Arbitrage"], "arb_age_s"), int))
|
||||
|
||||
check("...and `main.py`'s 'if and only if' is graded as BEHAVIOUR rather than "
|
||||
@@ -4585,8 +4586,8 @@ check(f"...and the row an operator reads names the SPACING that makes those "
|
||||
check("...and this is the same wording FCST-04's *Price history* row already "
|
||||
"used, so the two rows describing the same feed can be compared rather "
|
||||
"than being two conventions on one page",
|
||||
"points over" in _T11_E2E_ROW
|
||||
and "points over" in ("" if isinstance(_T11_E2E, _Raised)
|
||||
"prices over" in _T11_E2E_ROW
|
||||
and "prices over" in ("" if isinstance(_T11_E2E, _Raised)
|
||||
else _T11_E2E["Price history"]))
|
||||
|
||||
|
||||
@@ -5360,14 +5361,14 @@ _T16_DIFFER = [
|
||||
# UX-08 2c: a negative factor gets one sentence on both legs now ("put the
|
||||
# minus sign on the offset"), so it left this table.
|
||||
("a card miss -> what else to check on THIS leg",
|
||||
spoilt(factor=0.120), "(price_taxes_eur_kwh), not in the offset",
|
||||
"vat_pct is 0 for injection"),
|
||||
spoilt(factor=0.120), "network charges are not in the offset",
|
||||
"the VAT is 0 for injection"),
|
||||
("invalid JSON -> WHEN this option is read at all, which is not the "
|
||||
"same answer on the two legs: `price_formula` decides nothing unless "
|
||||
"price_provider is 'custom_formula', and the injection leg is read "
|
||||
"whatever the consumption provider is",
|
||||
"{not json", "(price_provider) is 'custom_formula'",
|
||||
"used whatever the price source is"),
|
||||
"{not json", "unless “Dynamic price source” is 'custom_formula'",
|
||||
"only if your injection price follows the market"),
|
||||
]
|
||||
for _what, _blob, _cons_mark, _inj_mark in _T16_DIFFER:
|
||||
_a, _b = ((_reason(lambda b=_blob: parse_price_formula(b)),
|
||||
@@ -5549,7 +5550,7 @@ check(f"a card block ON THIS LEG is graded by the same rung at the same "
|
||||
f"15.55) is refused, naming the option the operator typed "
|
||||
f"({_T16_SPOILT_CARD[:64]!r})",
|
||||
_T16_WITH_CARD == ""
|
||||
and "does not match its own card" in _T16_SPOILT_CARD
|
||||
and "the card says" in _T16_SPOILT_CARD
|
||||
and _T16_SPOILT_CARD.startswith(EXPORT_FORMULA_OPTION))
|
||||
check(f"...and half a tolerance out still passes, so {CARD_TOLERANCE} is "
|
||||
f"enforced on this leg too rather than 'something'",
|
||||
|
||||
Reference in New Issue
Block a user