UX-08 2c review: unit settings anchor to inverters, plainer texts
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 2s

- messages.setting() for one inverter in the list reads
  “Inverter rating” for inverter-b (inverters.max_w), so the config panel
  files per-unit reasons under `inverters`, where the value is changed.
- The tariff card and plausibility reasons name
  “Distribution, levies and VAT” (price_taxes_eur_kwh) again.
- The wrong-direction reasons say "safety stop", the sign_disagree_w
  option says what it protects, and the roster, price-entity,
  integrator_max_w and capacity_kwh texts read more plainly.
- NL: stuurprint -> stuurmodule, "De piekbeperking".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goodwe-review
2026-09-27 06:51:57 +02:00
parent 2c516a032c
commit c84d5fd14f
13 changed files with 51 additions and 49 deletions
+7 -8
View File
@@ -292,7 +292,8 @@ _LEGS = {
neg_factor="Put the minus sign on the offset, not on the factor",
card_required=True,
card_note="The add-on needs it to check your two numbers",
card_causes="that network charges are not in the offset",
card_causes=("that network charges are in “Distribution, levies and VAT” "
"(price_taxes_eur_kwh), not in the offset"),
),
EXPORT_FORMULA_OPTION: _Leg(
option=EXPORT_FORMULA_OPTION,
@@ -678,10 +679,8 @@ def parse_export_formula(value):
def card_refusal(spec: FormulaSpec, delta: float) -> str:
"""The sentence an operator reads when their formula misses its own card.
⚠️ IT NAMES THE DISCREPANCY AND THE SIX WAYS IT HAPPENS. "Invalid
configuration" would be true and useless: every one of these errors looks
exactly like a working tariff from the status page, which is why the check
exists at all.
It names both numbers and what to check. The usual causes are in DOCS.md,
because each of these errors looks like a working tariff on the status page.
"""
got = in_card_units(spec, spec.card_index_eur_per_mwh)
leg = _LEGS[spec.option]
@@ -744,9 +743,9 @@ def implausible_all_in(spec: FormulaSpec, taxes_eur_kwh: float):
return None
return (
f"{spec.option} gives {energy:.5f} EUR/kWh for energy and "
f"{all_in:.5f} EUR/kWh in total on its own card, outside the "
f"plausible band of {PLAUSIBLE_LO_EUR_KWH}-{PLAUSIBLE_HI_EUR_KWH} "
f"EUR/kWh. Check \"units\" against the unit beside your formula.")
f"{all_in:.5f} EUR/kWh with “Distribution, levies and VAT” "
f"(price_taxes_eur_kwh), outside the plausible band of "
f"{PLAUSIBLE_LO_EUR_KWH}-{PLAUSIBLE_HI_EUR_KWH} EUR/kWh. Check \"units\" against the unit beside your formula.")
def implausible_injection(spec: FormulaSpec):
+6 -2
View File
@@ -844,5 +844,9 @@ LABELS = {
def setting(key: str, unit: str = "") -> str:
"""`max_w` -> `“Inverter rating” (max_w)`, plus ` for inverter-b` in a list."""
return f"“{LABELS[key]}” ({key})" + (f" for {unit}" if unit else "")
"""`max_w` -> `“Inverter rating” (max_w)`. For one inverter in a list it is
`“Inverter rating” for inverter-b (inverters.max_w)`, so the config panel
also shows the reason beside `inverters`, where that value is changed."""
if unit:
return f"“{LABELS[key]}” for {unit} (inverters.{key})"
return f"“{LABELS[key]}” ({key})"
+8 -9
View File
@@ -703,8 +703,8 @@ def _roster_problems(opts: dict) -> list[str]:
if entries and flat_sp:
problems.append(
f"{S('setpoint_entity')} is set to {flat_sp}, but the "
f"{S('inverters')} has {len(entries)} entries. Move that inverter "
"into the list and clear this setting.")
f"{S('inverters')} is also filled in. Move that inverter into the "
"list and clear this setting.")
return problems
@@ -924,8 +924,8 @@ def validate_startup_config(opts: dict, dev_min=None, dev_max=None) -> list[str]
where = _unit_label(opts, min(caps_ix, key=lambda p: p[1])[0])
problems.append(
f"{S('sign_disagree_w')} is {sign_w:.0f} W, but the "
f"{S('max_w', where)} is {weakest:.0f} W, so this check can never "
f"trip. Lower it below {weakest:.0f} W, or raise the rating.")
f"{S('max_w', where)} is {weakest:.0f} W, so this safety stop can "
f"never trip. Lower it below {weakest:.0f} W, or raise the rating.")
# ⚠️ AND THE SAME RULE FOR SAFETY-30's PER-UNIT THRESHOLD, WHICH IS A
# SEPARATE COMPARISON AND THEREFORE A SEPARATE WAY TO DISARM. It grades a
@@ -978,8 +978,8 @@ def validate_startup_config(opts: dict, dev_min=None, dev_max=None) -> list[str]
elif weakest is not None and weakest > 0 and unit_sign_w >= weakest:
problems.append(
f"{S('unit_sign_disagree_w')} is {unit_sign_w:.0f} W, but the "
f"{S('max_w', unit_where)} is {weakest:.0f} W, so this check can "
f"never trip. Lower it below {weakest:.0f} W, or raise the "
f"{S('max_w', unit_where)} is {weakest:.0f} W, so this safety stop "
f"can never trip. Lower it below {weakest:.0f} W, or raise the "
"rating.")
# --- 6. a maintenance charge the ceiling can actually accommodate --------
@@ -1301,9 +1301,8 @@ def _price_problems(opts: dict) -> list[str]:
# day/night tariff has no way to see that it lost.
if ha_price and str(opts.get("static_profile", "") or "").strip():
problems.append(
f"{S('static_profile')} and {_labels(ha_price)} are both set, and "
"only the Home Assistant price entities would count. Clear one of "
"the two.")
f"{S('static_profile')} is set, but {_labels(ha_price)} would win "
"over it. Clear the one you do not use.")
# ⚠️ THE PROFILE ITSELF, PARSED AT STARTUP RATHER THAN AT 02:00. A profile
# that does not tile the day has hours with no price, and the only ways to