Skip to content

docs(debug-journal): fold in 2026-09-23 queue slice; correct entries for the #5100/#5102 merge batch - #5199

Merged
springfall2008 merged 3 commits into
mainfrom
bot/debug-journal-2026-09-23
Sep 24, 2026
Merged

springfall2008 merged 3 commits into
mainfrom
bot/debug-journal-2026-09-23

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Automated journal flush from the triage bot's 2026-09-23 run — per-candidate outcomes below; the daemon opened this PR from branch bot/debug-journal-2026-09-23.

Took the first 15 candidates from the queue in plain filename sort order: 2513, 2689, 2716, 5099, 5100, 5101, 5103, 5105, 5106, 5107, 5112, 5116, 5121, 5122, 5125. 47 candidates remain queued (62 at the top level minus this slice); a slice drains per day.

Every candidate's claims were re-verified against current main (8b9fe029, v9.1.0-58) before folding: each cited symbol was grepped/read on the working tree, and PR merge states were checked via git log --grep.

Folded in (11)

  • 2513 — no charge-start hysteresis exists: the execute-side hold band is gated inv_target_soc_percent < 100.0, so a 100% target re-commands the window at SoC 99% (execute.py hysteresis verified on main). Folded into the keep-mechanisms paragraph, with battery_charge_power_curve's model-only scope and the Improvement Proposal: SoC Buffer Handling in Predbat #2628 deadband link.
  • 2689 — find_charge_curve IndexError guard fixed in PR Fixes for discharge/charge curve calc #2814 (first in v8.26.2); the four-way guard verified correct at inverter.py on main. Folded into the Charge/discharge curve row with the stale-version triage rule.
  • 5099 — two facts: (a) Inverter objects are rebuilt every 5-minute cycle (fetch_inverter_data() defaults create=True; verified execute.py + predbat.py call sites on main) — folded as a correction to the self.base.args trap entry, which said rebuilds happened only on the balance path (which no longer constructs anything since Fold balance_inverters into execute: one source of inverter rate writes #5149); (b) battery_calibration literal-value semantics vs GivTCP's != "Off" detection (re-verified both sides on main) — folded into the GivTCP REST row. The candidate's pre-Fold balance_inverters into execute: one source of inverter rate writes #5149 balance-path claim was dropped as overtaken.
  • 5100 — storage expiry is judged by the real clock; save_plan() fixed by PR fix(plan): save_plan() expiry computed from the wrong clock (#5079) #5100. Folded as a new Storage row, including the counterweight rule against "build test dates from Predbat's clock" and the run-the-victim-alone-at-two-times-of-day fuzzer tip.
  • 5101 — non-string givtcp_rest entry crashes the v9 poll loop anonymously; re-verified the unvalidated InverterRestState construction (givtcp.py) and the rest_api + "/" + api crash line (givtcp_rest.py) on main. Folded into the GivTCP REST row with the log signature (no discovered N inverter(s) line anywhere; malformed entry produces no Errno-22 retries of its own).
  • 5103 — GE Cloud EMS: /settings re-read only on the first tick once an EMS sets polling_mode = False; re-verified the gate and the settings_from_cache startup skip on main (line drift 1808→1978 noted; symbols named). Folded into the GE Cloud row.
  • 5105 — the three independent manual-dropdown builders and manual_select()'s inability to drive manual_api; re-verified the three builders and their values not in time_values tails on main. New Manual override selects row.
  • 5107 — marginal-rate binary sensors are plan-relative what-if predictions; verified marginal.py exists on main with the same MARGINAL_TIME_OFFSETS/threshold structure and the plan.py call site. New Marginal rate sensors row.
  • 5112 — watchdog timeout path cancels the awaiter, not an asyncio.to_thread worker, and never sets api_stop; the wait_for/shield/cancel() shape re-verified on component_base.py on main. Folded into the Component health row, kept marked as a finding on the still-open PR Run ML training off the event loop and let a shutdown abandon it #5112.
  • 5116 + 5121 — PV calibration self-reference. Rewritten rather than folded as written: the candidates predate the rest of the fix — main has since merged PR fix(solar): calibrate PV against the raw provider forecast, not Predbat's own calibrated output #5121 plus follow-ups (d9592461, 91b2d8a7) that went further than the candidate's fix idea: a dedicated sensor.<prefix>_pv_forecast_h0_uncalibrated published unconditionally, per-point fallback_to_state, and the h0-now-attribute/state legacy fallback — all verified on main (solcast.py, utils.py, db_engine.py). Folded into the Solcast row as fixed-since with the mechanism kept, plus the surviving history_attribute/db_engine facts and the calibration-test fidelity trap.
  • 2716 — the "hold charges at peak" three-path map (plan-side price gate exempts levelled windows; "Hold charging" can be the armed-window fallback; the pre-window freeze write programs 100%); the key execute.py/plan.py lines re-verified on main. New symptom-table row, including the Hold charging target X%-Y% = SoC–target-pair decoder and the unresolved SoC-rising-past-target branch. The candidate's suspected items (what selected markturner's 17:30 window) were dropped as issue-specific.
  • 5122 — ECO-branch structural gap ("import instead of discharge" is not a modelled action outside selected windows); verified the ECO Mode branch at prediction.py on main. New symptom-table row.
  • 5125 — split in two: the header-icon wiring (published entities, three render sites, CSS in get_header_html) folded into the Web row; the vacuous-red-gate trap (a test importing a not-yet-added constant fails at import and skips every test in the module) added as a new bullet under "Traps when investigating".

Existing entries corrected (3)

Dropped (0)

None — every candidate in the slice verified or was rewritten with its mechanism kept.

Notes

  • One new cspell term: uncalibrated (introduced to the codebase by the merged fix(solar): calibrate PV against the raw provider forecast, not Predbat's own calibrated output #5121).
  • Quality gate: run_pre_commit ran end to end; the pre-commit hooks (cspell, markdownlint, formatting) passed — the run then aborted at test_teslemetry_local_weekday_follows_the_base_clock at 00:09 BST, the known host-clock-window failure documented in this journal ("Build test dates from Predbat's clock": the test asserts its no-base fallback against the host's datetime.now().weekday() while the fallback is datetime.now(timezone.utc), so it crashes in the hour after local midnight until 01:00). Docs-only change; unrelated to this diff.
  • Not merged here — a human merge is the review gate on this file.

…for the #5100/#5102 merge batch

Co-Authored-By: Claude Code <noreply@anthropic.com>
@springfall2008
springfall2008 marked this pull request as ready for review September 23, 2026 21:17
Copilot AI lite review requested due to automatic review settings September 23, 2026 21:17
@springfall2008 springfall2008 added the BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push label Sep 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Four documentation corrections remain unresolved, warranting human review.

Review effort: Lite
Findings: 2 Low severity

Open (2)
What changed in this PR

Updates the debug journal with verified findings and corrections from the 2026-09-23 queue slice.

Changes:

  • Adds troubleshooting guidance for battery control, integrations, storage, UI, and symptoms.
  • Corrects test-isolation, clock, inverter-rebuild, and PV-calibration documentation.
  • Adds uncalibrated to the cspell dictionary.
File Description
tools/​debug-journal.md Adds and corrects triage guidance and verified findings.
.cspell/​custom-dictionary-workspace.txt Adds the uncalibrated term.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/debug-journal.md Outdated
@@ -49,6 +50,8 @@ A `predbat_debug.yaml` or debug-history snapshot attached to a bug report can ca

- **Three different "keep" mechanisms** get conflated in reports, and only two of them can hold a battery up. `best_soc_keep` is *soft*: purely a cost-model penalty added when predicted `soc <= best_soc_keep` (the `metric_keep +=` line in `prediction.py`), scaled by `best_soc_keep_weight` and ramped in over the first four hours of the horizon. It never creates a charge window and never raises the reserve, so an automation setting it to 3 kWh cannot pin a full 8.64 kWh battery at 100% (GH#4961). `manual_soc_keep` (the web-UI `manual_soc` override) and weather-alert keeps are *hard-ish*: both merge into `all_active_keep`, which forces `keep_minute_scaling` to at least 10.0 - a 10x-import-rate penalty that effectively forbids discharging below the keep percent. `set_reserve_enable` with a high `reserve` is the only true hard floor. Related, because it comes up in the same reports: `set_discharge_during_charge` **defaults True** (`config.py`), and even when it is on, `prediction.py` still pins the discharge rate to `battery_rate_min` while SoC sits within 1% of the window's charge limit. That at-target hold is what `HoldChrg` means, and it is not `FrzChrg` - `is_freeze_charge()` requires the limit's percent to equal `reserve_percent`.

There is no *charge-start* hysteresis anywhere (GH#2513, enhancement — nothing in `config.py`, only predheat has hysteresis settings): the execute-side hold band is additionally gated on `inv_target_soc_percent < 100.0` (`execute.py`), so with a 100% target an inverter sitting at 99% misses the hold gate entirely and the plain `Charging` branch re-commands the window every 5-minute cycle while the BMS tapers. `battery_charge_power_curve` shapes only the *modelled* rate — a 0.0 taper above ~97% is honoured in the model through the `battery_rate_min` default of 0 — and cannot stop the writes. #2628 is the generic deadband request, so design any fix with it. Whether the ±1% band itself still produces GH#5083's charge/hold thrash on current main is untested — the gate exists in the same shape.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated reply from the triage bot.

Correct, and the entry now says so. Verified on main: execute.py:453 passes self.battery_charge_power_curve into find_charge_rate(), the first return value becomes new_charge_rate → charge_rate (execute.py:490) and is written by adjust_charge_rate() (execute.py:290), so "shapes only the modelled rate" was wrong.

Two qualifications went in with the correction, because both bear on whether the curve could be the mechanism here:

  • The curve only selects the written rate when set_charge_low_power is on. With it off, find_charge_rate() returns max_rate unchanged (utils.py, the else at the end of the low-power branch) and the curve shapes only the second return value, which execute.py logs as real and never writes.
  • Even in low-power mode the rate search floors at max(400W, battery_rate_min * MINUTE_WATT), so the 0.0 taper above ~97% that get_charge_rate_curve_cached() honours in the model (max(min(rate, max_charge_rate), battery_rate_min), utils.py:2395) never becomes a zero write.

The re-command/hysteresis gap is left as the separate mechanism it is — the curve cannot stop the window being re-commanded on either path.

Comment thread tools/debug-journal.md Outdated
@springfall2008 springfall2008 removed the BOT_CLEANUP Trigger: bot should address PR review feedback and CI failures, then commit and push label Sep 23, 2026
@springfall2008
springfall2008 merged commit 47f1f1e into main Sep 24, 2026
2 checks passed
@springfall2008
springfall2008 deleted the bot/debug-journal-2026-09-23 branch September 24, 2026 06:55
springfall2008 pushed a commit that referenced this pull request Sep 24, 2026
Resolve the tools/debug-journal.md conflict with the now-merged 2026-09-23
slice (#5199) by a word-level splice: the two slices edited disjoint spans of
the same table rows. Where both appended a sentence to the end of the same
cell (GivTCP REST, Component health) both are kept, #5199's text first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants