UX-08 2d: plain log lines, the rest of the status page, and DOCS #136

Merged
admin merged 2 commits from UX-08-2d-logs-docs into release/1.0 2026-09-27 08:40:34 +02:00
Owner

UX-08 phase 2d, the last phase (Trello UX-08). Log lines, the status-page leftovers, the 2c follow-ups (including the 2c test report's list) and the remaining DOCS.

Allowlist: 43 → 0 (cap 0). All 43 allowlisted WARNING/ERROR lines, the 10 DROP-to-DEBUG lines among them, and 8 more lines the log semicolon rule found (it now covers logs). Reasoning moved to DEBUG, not deleted.

Before → after

  1. Log (ERROR): SAFETY-02 TRIPPED on inverter-b - <reason>. Commanding 0 W on that inverter and holding it until control is started again. This survives an add-on restart; restarting will not clear it. → inverter-b is set to 0 W because its power went the wrong way: <reason>. Only starting control clears this.
  2. Log (ERROR): SAFETY-37: inverter-a battery-power reading did not move under a command that did, WHICH IS NOT BY ITSELF A VERDICT ON THE REGISTER - <reason>. Treating it as MISSING … (73 words) → inverter-a: battery power reading stopped changing. Inverters go to 0 W until it changes, or 17 s after the command drops below 250 W: <reason>
  3. Row: 2026-08: peak 4998 W, set by the quarter at 2026-08-12T17:15:00+00:00 - 143 % of the 3500 W soft limit, 120 quarter(s) banked, on start: fresh: no checkpoint → Month peak: 4 998 W on 12 Aug at 19:15, 143 % of the peak limit and 15-minute periods measured this month: 120
  4. Row: PEAK GUARD BLIND (no open quarter - …) - the battery may not import while the billed quarter cannot be measured; commanding no charging (floor 0 W) → Peak guard: waiting for meter readings, so the battery does not charge meanwhile
  5. Top table / hint line: Why: loop -> 440 W, limited by peak(quarter peak) → Keeping the grid near zero: 440 W, held back by the peak limit; Arbitrage: off - \arbitrage_enabled` is false, so nothing trades→Off, “Trade on the price spread” is not switched on; hint 12:03:07 OUTPUT_CLAMP episode 3: … BUG SIGNAL …→14:03 Power request too high`
  6. Refusal: “Your own injection formula” cannot be used. price_export_formula does not match its own card: it gives 15.5471 c_eur_per_kwh and the card says 15.05. Check the units, the VAT and that vat_pct is 0 for injection. (35 words) → … price_export_formula gives 15.5471 c/kWh, the card says 15.05. Check the units, and that the VAT is 0 for injection. (28)

What changed

  • Logs: no card-id prefixes. Nothing in the repos greps them. The one outside grep is /home/homelab/clampwatch.sh, a 2026-09-05 site watch that greps OUTPUT_CLAMP (still in the quoted reason) and Engagements so far (now "Cut back N times so far"). The reason texts a line quotes (STALE_READING …, OUTPUT_CLAMP …) are unchanged evidence.
  • Status page: month-peak row split in two, peak-guard idle/blind/engaged rows, stale-reading row, Why, Tariff, Price history, Arbitrage, Shadow mode (no "ON THIS CYCLE WOULD HAVE COMMANDED"), labels (Battery level, Solar production, Power request), Stopped banner, direction texts ("went the wrong way"), range "up to 5 000 W each way", "Check for inverters working against each other". The line under the buttons shows messages.hint() for each event; the raw action log is kept, not shown.
  • UX-09: the Supervisor question at start is one INFO line with a real reason (a timeout's text is empty, so its type is named).
  • 2c follow-ups and the 2c test report: refusals counted as shown (labels expanded) plus a driven check of the 8 long cases; 7 shortened; card and parse errors plain ("not valid near character N"); blank values read "empty"; "Clear the one you do not use" names what; panel key match no longer files inverters.max_w under max_w (with a test); refusal banner, panel-note semicolon; saturation_w → "At-limit shortfall"; integrator_max_w → "Catch-up limit" with a plain description; "(JSON)" out of 4 names; NL option texts now checked too (capacity_kwh shortened).
  • DOCS: glossary (control board, isolator, Developer Tools and 6 more), card ids out of 17 headings onto a For installers line, anchors fixed, the home-owner intro and status table plain. STYLE.md and DECISIONS (phase 2d section) updated.

Safety

No level, id, trigger, cadence, colour, row visibility or banner order changed. Every refusal count is unchanged. SAFETY-57's escape clause stays on the ERROR line as an ordered pair, and the release line still refuses to claim a recovery. SAFETY-38's ordered pair stays on the peak row. N=1 texts still name the meter and N≥2 texts "own power". SAFETY-25's sentence and every latch's "how it clears" are untouched. Tests pin meaning; test files were updated where they pinned old words.

Full suite green locally (36 files, with sim/hwsim.py). The infra pin bump follows after merge (manager).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p

UX-08 phase 2d, the last phase (Trello UX-08). Log lines, the status-page leftovers, the 2c follow-ups (including the 2c test report's list) and the remaining DOCS. **Allowlist: 43 → 0 (cap 0).** All 43 allowlisted WARNING/ERROR lines, the 10 DROP-to-DEBUG lines among them, and 8 more lines the log semicolon rule found (it now covers logs). Reasoning moved to DEBUG, not deleted. ## Before → after 1. **Log (ERROR)**: `SAFETY-02 TRIPPED on inverter-b - <reason>. Commanding 0 W on that inverter and holding it until control is started again. This survives an add-on restart; restarting will not clear it.` → `inverter-b is set to 0 W because its power went the wrong way: <reason>. Only starting control clears this.` 2. **Log (ERROR)**: `SAFETY-37: inverter-a battery-power reading did not move under a command that did, WHICH IS NOT BY ITSELF A VERDICT ON THE REGISTER - <reason>. Treating it as MISSING …` (73 words) → `inverter-a: battery power reading stopped changing. Inverters go to 0 W until it changes, or 17 s after the command drops below 250 W: <reason>` 3. **Row**: `2026-08: peak 4998 W, set by the quarter at 2026-08-12T17:15:00+00:00 - 143 % of the 3500 W soft limit, 120 quarter(s) banked, on start: fresh: no checkpoint` → `Month peak: 4 998 W on 12 Aug at 19:15, 143 % of the peak limit` and `15-minute periods measured this month: 120` 4. **Row**: `PEAK GUARD BLIND (no open quarter - …) - the battery may not import while the billed quarter cannot be measured; commanding no charging (floor 0 W)` → `Peak guard: waiting for meter readings, so the battery does not charge meanwhile` 5. **Top table / hint line**: `Why: loop -> 440 W, limited by peak(quarter peak)` → `Keeping the grid near zero: 440 W, held back by the peak limit`; `Arbitrage: off - \`arbitrage_enabled\` is false, so nothing trades` → `Off, “Trade on the price spread” is not switched on`; hint `12:03:07 OUTPUT_CLAMP episode 3: … BUG SIGNAL …` → `14:03 Power request too high` 6. **Refusal**: `“Your own injection formula” cannot be used. price_export_formula does not match its own card: it gives 15.5471 c_eur_per_kwh and the card says 15.05. Check the units, the VAT and that vat_pct is 0 for injection.` (35 words) → `… price_export_formula gives 15.5471 c/kWh, the card says 15.05. Check the units, and that the VAT is 0 for injection.` (28) ## What changed - **Logs**: no card-id prefixes. Nothing in the repos greps them. The one outside grep is `/home/homelab/clampwatch.sh`, a 2026-09-05 site watch that greps `OUTPUT_CLAMP` (still in the quoted reason) and `Engagements so far` (now "Cut back N times so far"). The reason texts a line quotes (`STALE_READING …`, `OUTPUT_CLAMP …`) are unchanged evidence. - **Status page**: month-peak row split in two, peak-guard idle/blind/engaged rows, stale-reading row, `Why`, `Tariff`, `Price history`, `Arbitrage`, `Shadow mode` (no "ON THIS CYCLE WOULD HAVE COMMANDED"), labels (Battery level, Solar production, Power request), `Stopped` banner, direction texts ("went the wrong way"), range "up to 5 000 W each way", "Check for inverters working against each other". The line under the buttons shows `messages.hint()` for each event; the raw action log is kept, not shown. - **UX-09**: the Supervisor question at start is one INFO line with a real reason (a timeout's text is empty, so its type is named). - **2c follow-ups and the 2c test report**: refusals counted as shown (labels expanded) plus a driven check of the 8 long cases; 7 shortened; card and parse errors plain ("not valid near character N"); blank values read "empty"; "Clear the one you do not use" names what; panel key match no longer files `inverters.max_w` under `max_w` (with a test); refusal banner, panel-note semicolon; `saturation_w` → "At-limit shortfall"; `integrator_max_w` → "Catch-up limit" with a plain description; "(JSON)" out of 4 names; NL option texts now checked too (capacity_kwh shortened). - **DOCS**: glossary (control board, isolator, Developer Tools and 6 more), card ids out of 17 headings onto a *For installers* line, anchors fixed, the home-owner intro and status table plain. STYLE.md and DECISIONS (phase 2d section) updated. ## Safety No level, id, trigger, cadence, colour, row visibility or banner order changed. Every refusal count is unchanged. SAFETY-57's escape clause stays on the ERROR line as an ordered pair, and the release line still refuses to claim a recovery. SAFETY-38's ordered pair stays on the peak row. N=1 texts still name the meter and N≥2 texts "own power". SAFETY-25's sentence and every latch's "how it clears" are untouched. Tests pin meaning; test files were updated where they pinned old words. Full suite green locally (36 files, with `sim/hwsim.py`). The infra pin bump follows after merge (manager). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
admin added 1 commit 2026-09-27 08:01:51 +02:00
UX-08 2d: plain log lines, the rest of the status page, and DOCS
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 8m55s
6a95447248
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
Author
Owner

Review of UX-08 2d (head 6a95447)

Safety: no logic change found. I parsed old and new app/*.py with every string blanked and diffed the ASTs. The only changes are these:

  • hint= arguments added to every log_event() call (45 calls, none without one, per test_messages);
  • _LOG.debug lines added, and two _LOG.error banner lines dropped from the refusal banner (same level);
  • display-only why_words and messages.* row builders;
  • the peak-guard row branch split into engaged and idle, with the same ok/warn values as before;
  • a new periods_measured row that is always green;
  • the Supervisor ask() now returns a pair.

Nothing changed in log levels, notification ids, triggers, cadence, row colours, banner order or refusal counts. The two ordered pairs still hold, and I checked both by mutation:

  • SAFETY-57's escape clause: swapping release_s and move_w turns test_freshness red;
  • SAFETY-38's idle pair: pinned at test_peakguard §13.

Points:

  1. The hidden action log is not "still in the logs". log_event() writes only to Controller.events, in memory. Some entries have no _LOG twin, so after this PR they reach no operator surface at all: a dropped inverter's reason, the unplaced-watts explanation, the peak guard's engage/idle sentence with its projection, the shadow decision, and inputs missing. DOCS still tells installers to read the action log (the STALE / STALE WITHDRAWN table, the peak guard and the arbiter sections). Fix pushed: status_payload() also returns events (the 40-entry ring). The page does not render it. DOCS "For installers", STYLE and DECISIONS say where to find it. test_watchdog pins the hint and events after a direction trip.
  2. The engaged peak-guard row had no ordered-pair pin. "asks the battery for X to stay under Y" puts two wattages side by side, and a swap would read plausibly. Fix pushed: a pin in test_peakguard (engaged section), and swapping the arguments in main.py turns it red.
  3. The shown-text 30-word scan missed keys named by an imported constant. For example, S(TAXES_PROFILE_OPTION) in validate.py was counted as 1 word. The driven cases caught my mutation (32 words), but the static scan did not. Fix pushed: collect() also resolves from .x import NAME constants. It still passes with no new failures, and the same mutation now fails both checks.
  4. Readability:
    • p1.py "Meter reading refused" became "A meter message could not be read". "Refused" suggested a decision.
    • The DOCS glossary "Control board" entry no longer opens with "RS485 meter bus (T-CAN485 / ESP32)". The hardware moved to an installer note.
    • The rest reads well for a home owner, including "Month peak: 4 998 W on 12 Aug at 19:15, 143 % of the peak limit", the Why line and the hints.
  5. Grep dependencies:
    • Nothing in either repo (tests, tools, CI, docs/pickup.md) greps the old log text.
    • /home/homelab/clampwatch.sh (outside the repos, not edited) still finds OUTPUT_CLAMP, but its grep -o 'Engagements so far: [0-9]*' now matches nothing. The text is now "Cut back N times so far" (ERROR) or "Held back N times so far" (WARNING).

Tests I ran (CI image, locally): test_messages, test_peakguard, test_watchdog, test_freshness, test_p1 and test_translations are green on the fixed tree.
Mutations: the panel lookbehind without . goes red; a refusal padded to 32 shown words goes red; the direction row without "start control" turns test_watchdog red.
The cap is 0, and the test demands len == ALLOWLIST_MAX.

## Review of UX-08 2d (head 6a95447) **Safety: no logic change found.** I parsed old and new `app/*.py` with every string blanked and diffed the ASTs. The only changes are these: - `hint=` arguments added to every `log_event()` call (45 calls, none without one, per `test_messages`); - `_LOG.debug` lines added, and two `_LOG.error` banner lines dropped from the refusal banner (same level); - display-only `why_words` and `messages.*` row builders; - the peak-guard row branch split into engaged and idle, with the same `ok`/`warn` values as before; - a new `periods_measured` row that is always green; - the Supervisor `ask()` now returns a pair. Nothing changed in log levels, notification ids, triggers, cadence, row colours, banner order or refusal counts. The two ordered pairs still hold, and I checked both by mutation: - SAFETY-57's escape clause: swapping `release_s` and `move_w` turns `test_freshness` red; - SAFETY-38's idle pair: pinned at `test_peakguard` §13. **Points:** 1. **The hidden action log is not "still in the logs".** `log_event()` writes only to `Controller.events`, in memory. Some entries have no `_LOG` twin, so after this PR they reach no operator surface at all: a dropped inverter's reason, the unplaced-watts explanation, the peak guard's engage/idle sentence with its projection, the shadow decision, and `inputs missing`. DOCS still tells installers to read the action log (the STALE / STALE WITHDRAWN table, the peak guard and the arbiter sections). **Fix pushed:** `status_payload()` also returns `events` (the 40-entry ring). The page does not render it. DOCS "For installers", STYLE and DECISIONS say where to find it. `test_watchdog` pins the hint and `events` after a direction trip. 2. **The engaged peak-guard row had no ordered-pair pin.** "asks the battery for X to stay under Y" puts two wattages side by side, and a swap would read plausibly. **Fix pushed:** a pin in `test_peakguard` (engaged section), and swapping the arguments in `main.py` turns it red. 3. **The shown-text 30-word scan missed keys named by an imported constant.** For example, `S(TAXES_PROFILE_OPTION)` in `validate.py` was counted as 1 word. The driven cases caught my mutation (32 words), but the static scan did not. **Fix pushed:** `collect()` also resolves `from .x import NAME` constants. It still passes with no new failures, and the same mutation now fails both checks. 4. **Readability:** - `p1.py` "Meter reading refused" became "A meter message could not be read". "Refused" suggested a decision. - The DOCS glossary "Control board" entry no longer opens with "RS485 meter bus (T-CAN485 / ESP32)". The hardware moved to an installer note. - The rest reads well for a home owner, including "Month peak: 4 998 W on 12 Aug at 19:15, 143 % of the peak limit", the Why line and the hints. 5. **Grep dependencies:** - Nothing in either repo (tests, tools, CI, `docs/pickup.md`) greps the old log text. - `/home/homelab/clampwatch.sh` (outside the repos, not edited) still finds `OUTPUT_CLAMP`, but its `grep -o 'Engagements so far: [0-9]*'` now matches nothing. The text is now "Cut back N times so far" (ERROR) or "Held back N times so far" (WARNING). **Tests I ran** (CI image, locally): test_messages, test_peakguard, test_watchdog, test_freshness, test_p1 and test_translations are green on the fixed tree. Mutations: the panel lookbehind without `.` goes red; a refusal padded to 32 shown words goes red; the direction row without "start control" turns `test_watchdog` red. The cap is 0, and the test demands `len == ALLOWLIST_MAX`.
admin added 1 commit 2026-09-27 08:29:17 +02:00
UX-08 2d review: action log reachable in status JSON, engaged peak row pinned, imported keys expanded
infra-pin / fresh (pull_request) Successful in 1s
checks / unit (pull_request) Successful in 8m54s
569f16aa91
- status_payload() also returns the action log as `events`. The page
  does not render it. Some entries (a dropped inverter's reason, the peak
  guard's projection, unplaced watts) are written nowhere else, and DOCS
  still points installers at the action log.
- test_peakguard pins the engaged row's ordered pair (the battery's share,
  then the limit).
- test_messages expands setting keys named by constants imported from
  another app module, so the shown-text 30-word scan counts them.
- p1.py: 'A meter message could not be read'. DOCS glossary: control
  board in plain words, with the hardware in an installer note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

All review points are resolved in 569f16a, which checks / unit and infra-pin / fresh both pass. Points 1 to 4 are fixed as described in the review comment. Point 5 (clampwatch.sh, outside the repos) is reported to the manager and was not edited. Merging with head_commit_id pinned.

All review points are resolved in 569f16a, which checks / unit and infra-pin / fresh both pass. Points 1 to 4 are fixed as described in the review comment. Point 5 (clampwatch.sh, outside the repos) is reported to the manager and was not edited. Merging with head_commit_id pinned.
admin merged commit 32b9e56357 into release/1.0 2026-09-27 08:40:34 +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#136