UX-08 2c: short refusals, error texts and option texts #135

Merged
admin merged 2 commits from UX-08-2c-refusals into release/1.0 2026-09-27 07:01:26 +02:00
Owner

UX-08 phase 2c (card 6ab8460b7699423062157634): validator refusals, the errors they carry, and every option name and description are now short and plain. Only texts changed: no rule, key, schema, default or exit code. Over 36 spoiled configurations the old and new validators refuse the same number of times.

Refusals name the setting by its label and then its key, for example “Inverter rating” (max_w), via messages.setting(). The key stays because the config panel files a reason under every key it names. test_messages.py checks each quoted label against en.yaml. Tests that pinned wording now pin the rule, the setting and the value.

Manager's default, applied: citations and derivations moved out of the option descriptions into DOCS "For installers: where the numbers come from". The UX-01 citation check in test_translations.py is relaxed to match. It now checks that each derived option's description tells the reader to leave it, and that DOCS names the source record.

Before and after

Before After
Refusal, rating above the device max_w is 8000 W but number.goodwe_setpoint accepts at most 5000 W - THE DEVICE WINS, so the 8000 W you configured would silently never happen. Lower max_w to 5000 W or less, or point setpoint_entity at a device that can take 8000 W. “Inverter rating” (max_w) is 8000 W, but number.goodwe_setpoint accepts at most 5000 W. Lower the rating to 5000 W or less.
Refusal, slew_w 193 words: the site rail, the crossing time, NOTES.md §5.10 and the 2026-09-05 measurement “Largest change per cycle” (slew_w) is 1000 W, too fast for a 5000 W site. Set it to 0 or to at most 556 W.
Refusal, withdrawn price source 224 words, citing TARIFF-07, the endpoint and §11.4 “Dynamic price source” (price_provider) is 'eneco_eddy', which was withdrawn and never fetched a price. This site ran on no tariff at all. Choose 'generic_dayahead', 'manual_csv' or 'off'.
Option sign_disagree_w Sign watchdog threshold (W) — measured, do not change, with a 90-word description citing SAFETY-02 Wrong-direction threshold (W): A wrong direction counts only when the command and the meter reading both exceed this. Measured value, leave it unchanged.
Option integrator_max_w Integrator bound (W, 0 means the same as the rating), 98 words Error build-up limit (W): Limits how much old error control can carry, and 0 means “Inverter rating”. Do not raise it above the rating.
Option meter_freeze_s Frozen-meter alarm after (s) — measured, do not change, 114 words citing SAFETY-18 Stuck meter alarm after (s): An alarm when the meter repeats one reading this long while the command is at full power, with homewizard_local only. Measured value, leave it unchanged.

NL spot-checks

  • sign_disagree_w: "Drempel tekenbewaking (W) — gemeten, niet wijzigen" + "GEMETEN, NIET GEKOZEN (SAFETY-02)…" → "Drempel verkeerde richting (W)" + "…Gemeten waarde, laat ze ongewijzigd."
  • integrator_max_w → "Grens opgebouwde afwijking (W)". Jargon is gone from the tab: register, arbiter, integrator, verzadiging, hartslag. ESP32 → "stuurprint".
  • Shouting is gone: "DIT IS UW GETAL" → "Uw eigen getal:", "ALLEEN BIJ TWEE OF MEER OMVORMERS" → "Bij twee of meer omvormers:". NL stays key for key with EN, and there is no fr file.

Allowlist

167 → 43: refusal 34 → 0, option-name 27 → 0, option-desc 63 → 0. All 43 left are log lines for phase 2d. The cap equals the length. Refusal and option texts are now also checked for semicolons.

Not rewritten, and why

  • Errors only a code defect can raise, such as plugin-contract and argument-type checks in price.py, price_forecast.py, static_tariff.py, arbitrage.py and main.py.
  • INFO lines such as the tariff-formula preview.

A few reasons no longer mention keys they only named in passing, for example price_markup_eur_kwh in the withdrawn-provider reason. The panel therefore shows those reasons beside fewer settings. This is recorded in DECISIONS/UX-08-short-notifications.md.

Local run in the CI image: all 36 test_*.py pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

UX-08 phase 2c (card 6ab8460b7699423062157634): validator refusals, the errors they carry, and every option name and description are now short and plain. **Only texts changed**: no rule, key, schema, default or exit code. Over 36 spoiled configurations the old and new validators refuse the same number of times. **Refusals** name the setting by its label and then its key, for example “Inverter rating” (max_w), via `messages.setting()`. The key stays because the config panel files a reason under every key it names. `test_messages.py` checks each quoted label against en.yaml. Tests that pinned wording now pin the rule, the setting and the value. **Manager's default, applied:** citations and derivations moved out of the option descriptions into DOCS "For installers: where the numbers come from". The UX-01 citation check in `test_translations.py` is relaxed to match. It now checks that each derived option's description tells the reader to leave it, and that DOCS names the source record. ### Before and after | | Before | After | |---|---|---| | Refusal, rating above the device | `max_w is 8000 W but number.goodwe_setpoint accepts at most 5000 W - THE DEVICE WINS, so the 8000 W you configured would silently never happen. Lower max_w to 5000 W or less, or point setpoint_entity at a device that can take 8000 W.` | “Inverter rating” (max_w) is 8000 W, but number.goodwe_setpoint accepts at most 5000 W. Lower the rating to 5000 W or less. | | Refusal, `slew_w` | 193 words: the site rail, the crossing time, NOTES.md §5.10 and the 2026-09-05 measurement | “Largest change per cycle” (slew_w) is 1000 W, too fast for a 5000 W site. Set it to 0 or to at most 556 W. | | Refusal, withdrawn price source | 224 words, citing TARIFF-07, the endpoint and §11.4 | “Dynamic price source” (price_provider) is 'eneco_eddy', which was withdrawn and never fetched a price. This site ran on no tariff at all. Choose 'generic_dayahead', 'manual_csv' or 'off'. | | Option `sign_disagree_w` | **Sign watchdog threshold (W) — measured, do not change**, with a 90-word description citing SAFETY-02 | **Wrong-direction threshold (W)**: A wrong direction counts only when the command and the meter reading both exceed this. Measured value, leave it unchanged. | | Option `integrator_max_w` | **Integrator bound (W, 0 means the same as the rating)**, 98 words | **Error build-up limit (W)**: Limits how much old error control can carry, and 0 means “Inverter rating”. Do not raise it above the rating. | | Option `meter_freeze_s` | **Frozen-meter alarm after (s) — measured, do not change**, 114 words citing SAFETY-18 | **Stuck meter alarm after (s)**: An alarm when the meter repeats one reading this long while the command is at full power, with homewizard_local only. Measured value, leave it unchanged. | ### NL spot-checks - `sign_disagree_w`: "Drempel tekenbewaking (W) — gemeten, niet wijzigen" + "GEMETEN, NIET GEKOZEN (SAFETY-02)…" → "Drempel verkeerde richting (W)" + "…Gemeten waarde, laat ze ongewijzigd." - `integrator_max_w` → "Grens opgebouwde afwijking (W)". Jargon is gone from the tab: register, arbiter, integrator, verzadiging, hartslag. ESP32 → "stuurprint". - Shouting is gone: "DIT IS UW GETAL" → "Uw eigen getal:", "ALLEEN BIJ TWEE OF MEER OMVORMERS" → "Bij twee of meer omvormers:". NL stays key for key with EN, and there is no fr file. ### Allowlist 167 → **43**: refusal 34 → 0, option-name 27 → 0, option-desc 63 → 0. All 43 left are `log` lines for phase 2d. The cap equals the length. Refusal and option texts are now also checked for semicolons. ### Not rewritten, and why - Errors only a code defect can raise, such as plugin-contract and argument-type checks in `price.py`, `price_forecast.py`, `static_tariff.py`, `arbitrage.py` and `main.py`. - INFO lines such as the tariff-formula preview. A few reasons no longer mention keys they only named in passing, for example `price_markup_eur_kwh` in the withdrawn-provider reason. The panel therefore shows those reasons beside fewer settings. This is recorded in `DECISIONS/UX-08-short-notifications.md`. Local run in the CI image: all 36 `test_*.py` pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 05:56:08 +02:00
UX-08 2c: short refusals, error texts and option texts
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 2s
2c516a032c
Validator refusals name the setting by its label and key, say what is
wrong and what to change, and keep the rejected value and limit. Option
names and descriptions are short in en and nl; citations moved to DOCS
"For installers" and the test_translations citation check now pins the
instruction on the tab and the record in DOCS. No rule, key, schema,
default or exit code changed. Allowlist 167 -> 43 (log lines only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
Author
Owner

Review UX-08 2c (head 2c516a0)

Behaviour: identical. config.yaml is byte-identical to release/1.0. An AST diff of every changed app/*.py with all string literals neutralised differs only in the new S()/_labels()/messages.setting() helpers and in three locals that were only used to build text (superseded, ticks, ratio). I recorded every validate_startup_config() call the whole suite makes (292 calls, 167 refusing, 207 reasons) and ran each through the old and the new validator: same number of reasons on every call. Exit 78 comes from main.py, which is unchanged.

Panel side effect: 9 distinct cases, the same 9 you found. For 8 of them the setting that fixes it is still named:

  • setpoint_entity + roster; inverters not a list; the withdrawn provider, with and without static_profile; slew_w negative; max_w above the device: the lost keys were passing mentions.
  • #1 (fix pushed): the unit_sign_disagree_w refusal lost inverters only because the old text said "at two inverters". The real gap is wider. On a roster, every per-unit refusal (sign_disagree_w, unit_sign_disagree_w, maintenance vs rating, peak reserve vs cut-out) names (max_w) for inverter-a. The panel files that under the flat max_w, which a roster does not read, and not under inverters, where the per-unit rating is changed. Proposed fix: messages.setting(key, unit) renders “Inverter rating” for inverter-a (inverters.max_w). That is the Key column of DOCS "Settings at a glance", and the panel's key regex files it under inverters.
  • #2 (fix pushed): the tariff-formula card reason for price_formula lost price_taxes_eur_kwh. Its advice, "network charges are not in the offset", only works if the reader knows where they do go. Name “Distribution, levies and VAT” (price_taxes_eur_kwh).

Readability, pushing text fixes:

  • #3 "the “Inverter list” (inverters) has 1 entries": fix the grammar.
  • #4 static_profile vs price entities: "A and B and C are both set" names 3 settings and is 32 words. Rewrite.
  • #5 integrator_max_w "Limits how much old error control can carry, and 0 means..." reads badly. capacity_kwh "the work is then shared by energy left" is unclear too.
  • #6 Safety: the wrong-direction refusals say "this check can never trip". Say "this safety stop can never trip", so an installer sees what is disarmed. The sign_disagree_w option text does not say what the value protects. Add "Part of the safety stop for an inverter pushing the wrong way."
  • The option names "Error build-up limit", "Wrong-direction threshold" and "Largest change per cycle" are fine as they are.

NL:

  • #7 "stuurprint" (2×) → "stuurmodule". An installer understands "print", a home owner often does not.
  • peak_soc_floor "Het afvlakken van de piek" → "De piekbeperking", matching "Piekgrens".
  • The rest reads as plain, correct Dutch, and the terms match EN (Piekreserve, Uitschakelniveau, veiligheidsstop).

Tests: mutation-checked.

  • Dropping the limit from the max_w-vs-device refusal: test_validate red.
  • A wrong label (slew_w for max_w) in the sign refusal: test_validate red.
  • The wrong setting in the withdrawn-provider refusal: test_price red.
  • A drifted LABELS entry: test_messages red (label sync).
  • An allowlist line removed: red on the new-string check and on the cap check.
  • A refusal over 30 words: red.

Follow-up, not blocking: the 30-word cap counts the source literal, where {S(...)} is one word. Rendered refusals reach 31–38 words (card reasons, roster reasons). A cap on the rendered text, for example over the recorded suite calls, would close that.

## Review UX-08 2c (head 2c516a0) **Behaviour: identical.** `config.yaml` is byte-identical to release/1.0. An AST diff of every changed `app/*.py` with all string literals neutralised differs only in the new `S()`/`_labels()`/`messages.setting()` helpers and in three locals that were only used to build text (`superseded`, `ticks`, `ratio`). I recorded every `validate_startup_config()` call the whole suite makes (292 calls, 167 refusing, 207 reasons) and ran each through the old and the new validator: same number of reasons on every call. Exit 78 comes from `main.py`, which is unchanged. **Panel side effect: 9 distinct cases, the same 9 you found.** For 8 of them the setting that fixes it is still named: - setpoint_entity + roster; inverters not a list; the withdrawn provider, with and without static_profile; slew_w negative; max_w above the device: the lost keys were passing mentions. - #1 (fix pushed): the `unit_sign_disagree_w` refusal lost `inverters` only because the old text said "at two inverters". The real gap is wider. On a roster, every per-unit refusal (`sign_disagree_w`, `unit_sign_disagree_w`, maintenance vs rating, peak reserve vs cut-out) names `(max_w) for inverter-a`. The panel files that under the flat `max_w`, which a roster does not read, and not under `inverters`, where the per-unit rating is changed. Proposed fix: `messages.setting(key, unit)` renders `“Inverter rating” for inverter-a (inverters.max_w)`. That is the Key column of DOCS "Settings at a glance", and the panel's key regex files it under `inverters`. - #2 (fix pushed): the tariff-formula card reason for `price_formula` lost `price_taxes_eur_kwh`. Its advice, "network charges are not in the offset", only works if the reader knows where they do go. Name “Distribution, levies and VAT” (price_taxes_eur_kwh). **Readability, pushing text fixes:** - #3 "the “Inverter list” (inverters) has 1 entries": fix the grammar. - #4 static_profile vs price entities: "A and B and C are both set" names 3 settings and is 32 words. Rewrite. - #5 `integrator_max_w` "Limits how much old error control can carry, and 0 means..." reads badly. `capacity_kwh` "the work is then shared by energy left" is unclear too. - #6 Safety: the wrong-direction refusals say "this check can never trip". Say "this safety stop can never trip", so an installer sees what is disarmed. The `sign_disagree_w` option text does not say what the value protects. Add "Part of the safety stop for an inverter pushing the wrong way." - The option names "Error build-up limit", "Wrong-direction threshold" and "Largest change per cycle" are fine as they are. **NL:** - #7 "stuurprint" (2×) → "stuurmodule". An installer understands "print", a home owner often does not. - `peak_soc_floor` "Het afvlakken van de piek" → "De piekbeperking", matching "Piekgrens". - The rest reads as plain, correct Dutch, and the terms match EN (Piekreserve, Uitschakelniveau, veiligheidsstop). **Tests: mutation-checked.** - Dropping the limit from the max_w-vs-device refusal: test_validate red. - A wrong label (`slew_w` for `max_w`) in the sign refusal: test_validate red. - The wrong setting in the withdrawn-provider refusal: test_price red. - A drifted LABELS entry: test_messages red (label sync). - An allowlist line removed: red on the new-string check and on the cap check. - A refusal over 30 words: red. **Follow-up, not blocking:** the 30-word cap counts the source literal, where `{S(...)}` is one word. Rendered refusals reach 31–38 words (card reasons, roster reasons). A cap on the rendered text, for example over the recorded suite calls, would close that.
admin added 1 commit 2026-09-27 06:52:00 +02:00
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
c84d5fd14f
- 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>
Author
Owner

Review fixes pushed: c84d5fd (CI green: checks / unit, infra-pin / fresh)

  • #1 panel, roster: messages.setting(key, unit) now renders “Inverter rating” for inverter-b (inverters.max_w), the Key column of "Settings at a glance". I re-ran all 292 recorded suite calls old vs new: reason counts are still identical. The per-unit reasons (sign_disagree, maintenance vs rating, peak reserve vs cut-out) are now also filed under inverters. STYLE, DECISIONS and the pins in test_messages/test_validate/test_inverter are updated.
  • #2: the card reason for price_formula now says "that network charges are in “Distribution, levies and VAT” (price_taxes_eur_kwh), not in the offset". The plausibility reason names that setting too, because its total includes it: an out-of-band total can be fixed there.
  • #3: "has 1 entries" is now "is also filled in".
  • #4: “Fixed or day/night tariff” (static_profile) is set, but “Current price entity” (price_now_entity) and “Average price entity” (price_avg_entity) would win over it. Clear the one you do not use.
  • #5: integrator_max_w: "Caps the error that control saves up from past cycles. 0 means “Inverter rating”. Do not raise it above the rating." capacity_kwh: "…The add-on then shares the work by the energy each battery has left."
  • #6: both wrong-direction reasons say "so this safety stop can never trip". sign_disagree_w now opens with "Part of the safety stop for an inverter pushing the wrong way." NL matches.
  • #7 NL: stuurprint → stuurmodule, "De piekbeperking laat…". The integrator, capacity and sign texts follow EN.

Panel verdict, the 9 cases: after this commit, each reason is shown beside the setting that fixes it. The keys still lost are passing mentions:

  • setpoint_entity + roster: soc/batt/max_w
  • inverters not a list: the flat keys
  • withdrawn provider: markup/taxes/static
  • max_w above the device: setpoint_entity (the fix is the rating)
  • slew_w negative: max_w
  • injection card: price_formula/taxes
  • unit_sign single-inverter: the English word "inverters"

Everything is fixed. Merging.

## Review fixes pushed: c84d5fd (CI green: checks / unit, infra-pin / fresh) - **#1 panel, roster:** `messages.setting(key, unit)` now renders `“Inverter rating” for inverter-b (inverters.max_w)`, the Key column of "Settings at a glance". I re-ran all 292 recorded suite calls old vs new: reason counts are still identical. The per-unit reasons (sign_disagree, maintenance vs rating, peak reserve vs cut-out) are now also filed under `inverters`. STYLE, DECISIONS and the pins in test_messages/test_validate/test_inverter are updated. - **#2:** the card reason for `price_formula` now says "that network charges are in “Distribution, levies and VAT” (price_taxes_eur_kwh), not in the offset". The plausibility reason names that setting too, because its total includes it: an out-of-band total can be fixed there. - **#3:** "has 1 entries" is now "is also filled in". - **#4:** “Fixed or day/night tariff” (static_profile) is set, but “Current price entity” (price_now_entity) and “Average price entity” (price_avg_entity) would win over it. Clear the one you do not use. - **#5:** integrator_max_w: "Caps the error that control saves up from past cycles. 0 means “Inverter rating”. Do not raise it above the rating." capacity_kwh: "…The add-on then shares the work by the energy each battery has left." - **#6:** both wrong-direction reasons say "so this safety stop can never trip". sign_disagree_w now opens with "Part of the safety stop for an inverter pushing the wrong way." NL matches. - **#7 NL:** stuurprint → stuurmodule, "De piekbeperking laat…". The integrator, capacity and sign texts follow EN. **Panel verdict, the 9 cases:** after this commit, each reason is shown beside the setting that fixes it. The keys still lost are passing mentions: - setpoint_entity + roster: soc/batt/max_w - inverters not a list: the flat keys - withdrawn provider: markup/taxes/static - max_w above the device: setpoint_entity (the fix is the rating) - slew_w negative: max_w - injection card: price_formula/taxes - unit_sign single-inverter: the English word "inverters" Everything is fixed. Merging.
admin merged commit a02cd4b713 into release/1.0 2026-09-27 07:01:26 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#135