Repository navigation
docs(debug-journal): fold in the 2026-10-12 queue slice (11 candidates); mark GH#5438/#5492 fixed (#5439/#5493), GH#5472 and the GH#4900 compare-plan leak fixed (#5488/#5465), sigenergy docs split fixed (#5464), ML retrain interval configurable (#5433) - #5528
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Three journal statements are inaccurate or stale and could misdirect future triage.
1 open finding
What changed in this PR
Updates the debug journal with newly verified diagnostic findings and merged-fix status.
Changes:
- Adds findings from 11 triage candidates.
- Updates entries for recently merged fixes.
- Adds
entityfilterto the spelling dictionary.
| File | Description |
|---|---|
tools/debug-journal.md |
Updates diagnostic guidance and issue history. |
.cspell/custom-dictionary-workspace.txt |
Adds the new recorder-filter term. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| | Solis Modbus (`GS`, `GS_fb00` in `inverter.py`) | The Energy Storage Control Switch (Modbus 43110, `energy_control_switch`) is driven by `alt_charge_discharge_enable()`, gated on the `has_solis_energy_control` capability. `GS` reaches it through `mimic_target_soc()` and uses the Timed Charge/Discharge bit as the charge enable (35/33). `GS_fb00` has a target SoC, so it never reached `mimic_target_soc()` - until the fix it never wrote the switch at all, and a switch left on `Self-Use - No Grid Charging` (1, bit 5 clear) made every charge slot hold the battery at 0 W while Predbat logged a correct slot, target and rate. For FB00 it is driven through `write_solis_energy_control()` from `adjust_charge_immediate()` on non-exporting cycles - `Backup/Reserve` (49) to charge or idle, `Backup/Reserve - No Grid Charging` (17) for a freeze or a car/iBoost/reserve hold - and from `adjust_export_immediate()` on exporting cycles - `Feed-in priority - No Grid Charging` (64) for a freeze export, `Backup/Reserve` for a real export (PR #5258). The Battery Reserve bit (bit 4) is what makes the template's `reserve` (`backup_mode_soc`, 43024) a discharge floor, so the reserve holds in `execute_plan()` work unchanged; grid charging goes off during holds because the reserve is raised to SoC + 1 and the inverter would import to reach it, again each cycle. `execute_plan()` never calls both immediate methods with a real target in one cycle, and the idle `adjust_export_immediate(100)` deliberately leaves the switch alone; GS still writes from `mimic_target_soc()`. Adding a writer anywhere else (e.g. `adjust_battery_target()`) flips the switch twice per cycle. Older logs: PR #5256 (merged 2026-09-26) held it on `Self-Use` (33) from `adjust_battery_target()`, and before that GS_fb00 never wrote the switch at all. A SolisCloud session is the usual way the switch ends up at 1: `solis.py` maps `"Self-Use - No Timed Charge/Discharge"` to `ENUM_SELF_USE_NO_GRID_CHARGING`, which clears bit 5, and writes it whenever no slot is active - so stopping the cloud component outside a slot leaves grid charging disallowed. The FB00 Solax Modbus plugin's option names differ from the pre-FB00 plugin's (`Self-Use` is 33 there, 35 here) - `SOLAX_SOLIS_MODES_FB00`. Verified from a user log (26 Sep 2026) and the plugin source. | `./run_all --test inverter` (`solis_fb00_*`, `solis_gs_*`) | | ||
| | SolaX (`solax.py`) | Code `10402` is a token/auth failure, retried in-request rather than waiting for the next cycle. SolaX clamps battery minimum SOC at 10% (`SOLAX_MIN_RESERVE_PERCENT`), so `battery_min_soc` is auto-configured to stop Predbat writing limits the inverter will reject. **Follow-up on the GH#4993 thread (2026-09-21, from a 23-hour reporter log):** `10402` repeating every cycle with data still flowing is **server-side token invalidation between cycles**, not broken auth or lost data — Predbat's in-request refresh recovers every cycle. The tell: the same token value succeeds mid-cycle and is rejected next cycle while Predbat holds it untouched in memory between cycles (the auth-error branch of `_request_get_impl()` clears it, and `request_wrapper()` retries with a fresh token instead of failing the fetch), and a stated ~30-day `expires_in` against a real ~1-cycle token lifetime. Prime suspect is a second client sharing the same `solax_client_id`/`solax_client_secret` — the reporter ran the separate SolaX Developer API HA integration — **suspected, not proven** (one-active-token-per-app is common but could not be confirmed against SolaX docs); the decisive test is disabling the second client for an hour or registering each client its own app. Count `error 10402` lines against `Fetched ... successfully` lines before calling data lost, and note each 10402 also increments the component's lifetime error counter (`solax.py` auth-error branch), which is what accumulates in the Components panel — do not "fix" this by removing the in-request retry. Secondary, same thread: `Warn: SolarAPI: No solar forecast data was returned from HA sensors.` with the SolaX Cloud template means **no PV forecast source is configured at all** — with `forecast_solar`/`open_meteo`/`solcast_host`+`solcast_api_key` all absent, `fetch_pv_forecast()` falls through to the HA-sensor branch (`solcast.py` sets `active_source = "ha_sensors"`) and the template's `pv_forecast_today`-style `re:` regexes (`templates/solax_cloud.yaml`) match nothing unless the Solcast HA integration is installed. Check which source the log names ("Obtaining solar forecast from Forecast Solar API" vs "Using Solcast integration from inside HA") — a working source dropped during a template migration looks exactly like this. **Plant `totalYield` overstates PV on a mixed plant (GH#5356, code-verified on main 2026-10-02, payload semantics rest on the reporter's plant+device captures):** `automatic_config()` binds `pv_today` to the plant-level `sensor.<prefix>_solax_<plant>_total_yield` (solax.py:447), `publish_plant_info()` fills that sensor straight from plant realtime `totalYield` (solax.py:2640), and `total_load` adds the same `totalYield` to imported+discharged−exported−charged (solax.py:2709) — but SolaX's plant total is PV **plus** every deviceType-1 inverter's AC output, so on a plant mixing a PV-only inverter with a separate AC-coupled battery inverter, battery discharge is counted twice (once in `totalDischarged`, once inside `totalYield`), PV history is inflated (reporter's calibration scaled 2.89x, ~1.6x legitimate), and the class presents as "calibration too high / overnight under-charging / wrong savings". `pv_power` is unaffected (per-device `pvMap`/`mpptMap` sums, solax.py:2276-2285). The ingredient a fix needs all already exist: a device-level `..._<sn>_total_yield` sensor (solax.py:2366), failed reads keep the previous value (solax.py:2245), and `plant_inverters[plant_id]` holds every deviceType-1 device (solax.py:1483). Not a regression — the plant wiring dates to original SolaX Cloud development (#3140). The reporter's proposed fix direction (maintainer call pending): sum device `totalYield` over `plant_inverters` into a new `pv_today` sensor; caveat for whoever builds it — the plant **lifetime** total does not decompose as PV+discharge on that site, so derive from the daily relationship (which matched exactly on three days), not the lifetime number. **PR #5357:** `pv_today` is now `..._pv_yield`, summed by `get_plant_pv_yield()` from each inverter's **last known** `totalYield` (kept per inverter, seeded after a restart from its own `..._<sn>_total_yield` sensor), so a failed read or a dead inverter does not drop out of the sum; an inverter with no `totalYield` of its own counts as 0, and the value is held only when an inverter that `onlineStatus` says should answer failed its read and has never given a yield. `pv_power`/`grid_power` are plant sensors summed over every inverter, no longer the first inverter listed. Device `dailyYield` reads 0.0 once the inverter sleeps in the evening, so it is no use as a daily counter. On a live plant of two DC-coupled hybrids with panels (2026-10-04) device `dailyYield` (23.3, 2.6) and `dailyACOutput` (24.1, 9.5) were separate counters and the plant `dailyYield` (33.6) was exactly the sum of the AC outputs, so the plant figure is inverter AC output on every plant shape seen and device yield is not; device yield was not checked against an independent PV measurement. Still wrong after the fix: a plant whose only inverter is an X1-AC (no `totalYield`, empty `mpptMap`/`pvMap`) falls back to the plant figure, which there is battery discharge (`dailyYield` = `dailyACOutput` = `dailyDischarged`), and its `load_power` goes negative when a third-party PV inverter SolaX cannot see is exporting. `plant_inverters` is keyed by the plant ID as SolaX gives it while entity names use the lowercased form - look up with the raw ID. **GH#5388 (2026-10-04):** a plant lifetime counter can dip and recover within a day (`totalYield` 4437.8 → 4260.4 → 4441.7, `totalDischarged` with it) and, published as received, the recovery was recorded as 181.3 kWh of PV that day - an absurd `pv_today` or `export_today` on one day followed by a PV forecast several times the array size is this, via calibration. An upward step of thousands of kWh on the same counters did not show in `pv_today`. `hold_counter_dip()` now holds the five plant totals and each inverter's `total_yield` at the last published value and takes a lower value as a genuine reset only after `SOLAX_COUNTER_DIP_HOLD_HOURS`; how long a real dip lasts was not measured. After a restart the last value is seeded from the sensor, or from the `counters` entry in storage when the sensor is gone (Predbat's sensors do not survive a Home Assistant restart); the time a dip started is saved there too, without it a restart began the hold again and a system restarted daily would never accept a genuine reset. A plant where no inverter has PV inputs (X1-AC only: no `totalYield`, empty `mpptMap`/`pvMap`) now publishes **0** for `pv_yield` instead of the plant total, with a one-off `has no inverter with PV inputs` warning; `total_load` then has no PV term, so on a site with third-party PV it under-reads by what that PV supplies and can fall while the site exports. Nothing in the SolaX data measures that PV: a 12 hour dump of `/openapi/v2/device/history_data` (params `snList`, `deviceType`, `startTime`/`endTime` in ms, `timeInterval` minutes, `businessType`) carried the same fields as realtime, yield null and `gridPowerM2` 0.0 throughout daylight, and no plant checked had a `deviceType=3` meter device. `M2` fields are meter 2, positive = export per the SolaX API text; `solax.py` does not read them, and a meter 2 that was once fitted shows as a non-zero `totalExportEnergyM2` that no longer moves. A hybrid with no panels still reports a populated `mpptMap` of zeros, so it counts as having PV inputs and is not covered by the 0. | `solax` | | ||
| | Sigenergy (`sigenergy.py`) | Lifetime/history totals are cumulative server-side and reset around EU midnight, which showed up as an overnight dip; `fetch_history_totals` applies a monotonic clamp. "Energy totals went backwards overnight" starts here. Separately, a user `apps.yaml` override of `inverter.has_reserve_soc: true` (stock `SIG` default is `False`, deliberately — reverted in `5bfc5c80` after #2873/#3124) bypasses two guards that normally make reserve writes inert for Sigenergy (`execute.py:947-950`, `inverter.py:547-549`), so `adjust_reserve()` writes straight to `number.sigen_plant_ess_discharge_cut_off_state_of_charge`. Confirmed from a reporter's log: zero occurrences of the "Inverter does not support reserve" disable message the first guard would normally log, plus 56 reserve writes landing on that register (GH#4728). The stock template's `reserve:` line also points at that same entity rather than `ess_backup_state_of_charge`, which is what makes the override immediately destructive rather than merely inert. `Applying mode=eco ... duration=720min` is emitted only by the no-window fall-through branch (`sigenergy.py:2199`), so seeing it while a plan export window is live means the component's control snapshot never saw the window. Two code-verified routes to that, both still live: with `sigenergy_automatic: False`, `automatic_config()` - the only wiring of the component's control entities into Predbat - never runs, so Predbat's plan writes land on its own simulated dummy entities (`inverter.py:539-545`) and a `True == True` read-back is Predbat comparing against itself rather than reading a register; and `fetch_controls` runs on the first tick only (`if first:`, `sigenergy.py:2658`), so a single missed `call_service` event leaves the component's internal state permanently out of step while Predbat's write-skip comparison guarantees the healing write is never re-issued (GH#4894, probe-verified in `test_sigenergy.py`). `_publish_mqtt` logged the live `accessToken` on every command until PR #4926 added `SigenergyAPI.redact()`. The docs template still writes `number.sigen_plant_grid_import_limitation` to 0 in both freeze branches (`docs/inverter-setup.md:1936-1940` and `:1971-1975`), and Sigenergy's own EVAC/EVDC chargers are EMS-throttled to the plant import limit - so the block lands exactly when Predbat asks for Freeze Charging to hold for a car (GH#4911). On the **stock** `has_reserve_soc: False` shape the planned reserve floor is model-only and the battery drains to the inverter's own cut-off — see the "Plan shows a flat hold at the reserve" symptom row (GH#5358). **Every power lever on SIGCLOUD has been silently inert (GH#5376, hardware-verified by the reporter's live MQTT log and battery data; **fixed in PR #5377, merged 2026-10-03 and first shipped in v9.3.5** — the six optional fields are now built into the command object, `sigenergy.py` — keep the pre-#5377 reading for older logs):** `send_battery_command()` writes its six optional power/priority fields — `chargingPower`, `pvPower`, `maxSellPower`, `maxPurchasePower`, `chargePriorityType`, `dischargePriorityType` — at the **top level** of the payload while the required command fields sit inside `commands[0]` (since `cba12cc3`, first released v8.36.11); the API reads the levers from the command objects, so the top-level fields are silently ignored, and planned charge rate, bidirectional export cap, freeze-charge/`freeze_export_hold` `chargingPower: 0` and both priority types (all set in `apply_controls()`, `sigenergy.py`) have never reached the hardware. The suite passed while the bug was live because `test_sigenergy.py` asserted `payload["chargingPower"]` at the top level while checking `commands[0]` for the required fields — **a test that locks in the implementation's current output makes suite-green meaningless for that surface**; the #5377 fix rewrote the test to assert inside `commands[0]` (its "optional fields that were not passed are omitted" cases are the in-tree example of testing the fixed shape). Suspect, not verified: the fixed surface has never been hardware-tested by the triage bot, so a post-v9.3.5 regression report about holds/exports on SIGCLOUD may be that surface going live for the first time; pre-#5377, the #4761 freeze-export reasoning and the `freeze_export_hold` branch never acted with real effect. **Docs entity-name split (GH#5459, docs defect, fix in flight on PR #5464):** `docs/inverter-setup.md`'s `predbat_max_*_limit_action` automations reference `sensor.sigen_inverter_ess_rated_charging_power`/`..._discharging_power` - inverter level with the plant suffix - while the upstream integration defines the rated-power pairs at two levels with different suffix spellings: plant `plant_ess_rated_charging/discharging_power` (-ing) and inverter `inverter_ess_rated_charge/discharge_power` (no -ing), so the docs names match neither set; the docs' own rename note points at the inverter alternates, and the template binds `battery_rate_max` to the plant-level `-ing` name (the right clamp level for the plant-level limit numbers the automations write). A Sigenergy "float got invalid input 'unknown'" automation error resolves by mapping level first (plant vs inverter), then suffix; the automations are docs content and no Predbat code creates them. | `sigenergy` | | ||
| | Sigenergy (`sigenergy.py`) | Lifetime/history totals are cumulative server-side and reset around EU midnight, which showed up as an overnight dip; `fetch_history_totals` applies a monotonic clamp. "Energy totals went backwards overnight" starts here. Separately, a user `apps.yaml` override of `inverter.has_reserve_soc: true` (stock `SIG` default is `False`, deliberately — reverted in `5bfc5c80` after #2873/#3124) bypasses two guards that normally make reserve writes inert for Sigenergy (`execute.py:947-950`, `inverter.py:547-549`), so `adjust_reserve()` writes straight to `number.sigen_plant_ess_discharge_cut_off_state_of_charge`. Confirmed from a reporter's log: zero occurrences of the "Inverter does not support reserve" disable message the first guard would normally log, plus 56 reserve writes landing on that register (GH#4728). The stock template's `reserve:` line also points at that same entity rather than `ess_backup_state_of_charge`, which is what makes the override immediately destructive rather than merely inert. `Applying mode=eco ... duration=720min` is emitted only by the no-window fall-through branch (`sigenergy.py:2199`), so seeing it while a plan export window is live means the component's control snapshot never saw the window. Two code-verified routes to that, both still live: with `sigenergy_automatic: False`, `automatic_config()` - the only wiring of the component's control entities into Predbat - never runs, so Predbat's plan writes land on its own simulated dummy entities (`inverter.py:539-545`) and a `True == True` read-back is Predbat comparing against itself rather than reading a register; and `fetch_controls` runs on the first tick only (`if first:`, `sigenergy.py:2658`), so a single missed `call_service` event leaves the component's internal state permanently out of step while Predbat's write-skip comparison guarantees the healing write is never re-issued (GH#4894, probe-verified in `test_sigenergy.py`). `_publish_mqtt` logged the live `accessToken` on every command until PR #4926 added `SigenergyAPI.redact()`. The docs template still writes `number.sigen_plant_grid_import_limitation` to 0 in both freeze branches (`docs/inverter-setup.md:1936-1940` and `:1971-1975`), and Sigenergy's own EVAC/EVDC chargers are EMS-throttled to the plant import limit - so the block lands exactly when Predbat asks for Freeze Charging to hold for a car (GH#4911). On the **stock** `has_reserve_soc: False` shape the planned reserve floor is model-only and the battery drains to the inverter's own cut-off — see the "Plan shows a flat hold at the reserve" symptom row (GH#5358). **Every power lever on SIGCLOUD has been silently inert (GH#5376, hardware-verified by the reporter's live MQTT log and battery data; **fixed in PR #5377, merged 2026-10-03 and first shipped in v9.3.5** — the six optional fields are now built into the command object, `sigenergy.py` — keep the pre-#5377 reading for older logs):** `send_battery_command()` writes its six optional power/priority fields — `chargingPower`, `pvPower`, `maxSellPower`, `maxPurchasePower`, `chargePriorityType`, `dischargePriorityType` — at the **top level** of the payload while the required command fields sit inside `commands[0]` (since `cba12cc3`, first released v8.36.11); the API reads the levers from the command objects, so the top-level fields are silently ignored, and planned charge rate, bidirectional export cap, freeze-charge/`freeze_export_hold` `chargingPower: 0` and both priority types (all set in `apply_controls()`, `sigenergy.py`) have never reached the hardware. The suite passed while the bug was live because `test_sigenergy.py` asserted `payload["chargingPower"]` at the top level while checking `commands[0]` for the required fields — **a test that locks in the implementation's current output makes suite-green meaningless for that surface**; the #5377 fix rewrote the test to assert inside `commands[0]` (its "optional fields that were not passed are omitted" cases are the in-tree example of testing the fixed shape). Suspect, not verified: the fixed surface has never been hardware-tested by the triage bot, so a post-v9.3.5 regression report about holds/exports on SIGCLOUD may be that surface going live for the first time; pre-#5377, the #4761 freeze-export reasoning and the `freeze_export_hold` branch never acted with real effect. **Docs entity-name split (GH#5459, docs defect, fixed in PR #5464, `c0d901db`, 2026-10-12):** `docs/inverter-setup.md`'s `predbat_max_*_limit_action` automations reference `sensor.sigen_inverter_ess_rated_charging_power`/`..._discharging_power` - inverter level with the plant suffix - while the upstream integration defines the rated-power pairs at two levels with different suffix spellings: plant `plant_ess_rated_charging/discharging_power` (-ing) and inverter `inverter_ess_rated_charge/discharge_power` (no -ing), so the docs names match neither set; the docs' own rename note points at the inverter alternates, and the template binds `battery_rate_max` to the plant-level `-ing` name (the right clamp level for the plant-level limit numbers the automations write). A Sigenergy "float got invalid input 'unknown'" automation error resolves by mapping level first (plant vs inverter), then suffix; the automations are docs content and no Predbat code creates them. | `sigenergy` | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Automated journal flush — generated by the triage bot's
/journal-updaterun of 2026-10-13. The queue held 11 candidates (limit 15), so none are left queued. Docs-only change:tools/debug-journal.mdplus one genuine term added to.cspell/custom-dictionary-workspace.txt(entityfilter, the HA recorder-filter module cited by the new note). Every candidate was re-verified against main at40b9ce28before folding in; verification status is stated per item below.Quality gate: all pre-commit hooks green (cspell, markdownlint, formatters, and the two test suites wired into hooks) after rewording four ordinary-English words and one inline-HTML token. The quick Predbat suite (
coverage/run_all --quick) did not run this session — its runner is not permitted in the session's permission mode. This is a docs-only change, so the docs hooks are the gate, and they passed; saying so plainly rather than implying a suite run.Folded in (10 of 11 candidates; one folded only as a pointer)
if changed_start_end:is unconditional (inverter.py),has_service_apihas zero occurrences. Sharpened the two existing sleep mentions (Solis row, GE-discharge-window symptom row): the v8.37.xrest_dataGivTCP-REST exemption is gone since refactor(givtcp): move GivTCP REST handling into its own component #4864, so the sleep applies to GivTCP-REST too, and the charge path never reaches it. Per-issue call-site detail stays in the issue.async_fetch_standing_charges()readsresults[0].value_inc_vatunfiltered (kraken.py), live EDF returned the overlappingNON_DIRECT_DEBITrow first (61.186 vs the Direct-Debit 52.317), the unit rate survives only by row order inminute_data()'s last-write-wins merge,filter_payment_method()(utils.py, fix(octopus): prefer the direct debit rate when both payment methods overlap #5145) replays correct on those rows, and the GraphQL fallback readsapplicable_charges[0]with no method field requested. The 36-row structure comes from that run's live probe (2026-10-12); the code side re-verified here.fetch_octopus_rates()concatenates previous→current→next event slices (octopus.py) withminute_data()last-write-wins, and the next-day read is the only one of the three with no empty-warn. The public-API provenance recipe and the same-code tariff-renewal trap are carried as probe-verified in that run.docs/faq.mdnow ships the include glob; compare entities arepredbat.compare_tariff_<id>), folded into the compare row: HA's recorder filter has two fall-through modes and any domain/glob include excludes everything matching no include rule; the no-flip rescue lists eachpredbat.compare_tariff_<id>underinclude.entitiesexplicitly.battery_to_minon fleet SoC + theexecute.pyfleet sums, both re-verified), dup target Predbat takes account of dissimilar battery capacities in the plan #830, umbrella Multi-inverter rework: tracking issue #4856, and the two-grep class-check against a future per-inverter prediction rework.best_soc_keepis the knob) plus dump-reading notes in the replay section (plan-relative vs absolute minutes;export_window_best[].target≠ the packedexport_limits_best[]level; the table card's rate columns are not SoC).manual_rates()prunes elapsed slots and rewrites every cycle, the dropdown offers only future slots,today_cost()re-prices from the live tables, and Retain past manual import/export rate overrides so cost_today and the savings baseline price consumed energy correctly #5078/Persist effective rates for historical cost calculations #5425 are still open). Folded into the manual-override-selects row.cost_todayfamily, with the "Elapsed minutes keep theirs" comment trap, the Octopus Intelligent: in-progress flexPlannedDispatches promoted to completed creates phantom cheap slots when EV unplugged #4114 no-promotion constraint, and thecalculate_yesterdayfreeze note.trim_started_dispatch()/dispatch_billed_off_peak()and the round-out re-verified.kraken_providerarg does not exist on main — the provider is a component instance attribute (self.provider), not a shared arg, so a provider-dependent pricing rule needs new wiring, not an existing knob (stated as such in the entry).soc_kw = soc_percent × soc_max / 100with no staleness guard, inverter.py) and folded into the SoC symptom row: the fingerprint, the mixed-GivTCP-prefix apps.yaml trap, and the "helper averages a different prefix, so both sides were truthful" reading.load_forecast_onlygates the holiday-aware forecast behindnot self.load_forecast_only; zero holiday mentions inload_ml_component.py;load_ml_sourceis apps.yaml-only) and folded into the holiday row.Existing entries corrected against the 2026-10-12 merges
b88cbd8d):catalogue_value()picks deliberately (min for charger power, max for battery size) with a once-per-ambiguity warning; invalidates the "last match wins / ambiguity never exercised" wording.0bca948b):flexPlannedDispatches is Nonenow routes into the keep-last-known branch with a warn, and the shape is test-covered; invalidates the "fallback covers only the fully-failed query" wording.a73bfefe):check_compare_list()/valid_compare_list()wired at load/publish/run_all/web; invalidates the crash-loop fix-direction wording (the signature stays for pre-fix(compare): skip malformed compare_list entries instead of crashing, with one shared check #5488 logs).dcc6017d): compare now restores the saved plan state includingplan_preclipin afinally.using new planlogged from inside a compare scenario") — rewritten as explained: the compare scenario'scalculate_plan()replacedplan_preclipand the restore missed it, so the first re-plan after midnight scored a tariff scenario as the incumbent — the exact mechanism PR fix(compare): stop the midnight tariff comparison changing the live plan's next decision #5465 fixes.c0d901db); invalidates "fix in flight on PR docs(sigenergy): fix rate automation sensor names, re-apply mode on restart, stop zeroing import limit in freeze #5464".load_ml_retrain_interval_hours(1–48h, PR feat: configure ML retraining interval in apps.yaml #5433,4e169b1f); invalidates "hardcoded, only knob is RETRAIN_INTERVAL_SECONDS" — with the note that the schema max equals the 48h staleness cap.c000b53a): the mixin rework changed the call graph (ohme.pycallscharger_control_release()too), so grep before relying on it.Replay input:lines (PR feat(log): log the inputs each plan uses as "Replay input:" lines, so a log can be replayed exactly #5427,9e5f0024: a log without a dump can reproduce plan inputs; a comparison run logs none) and a one-line note that triage mode denies a bare--debug_file.0dbcce38) logs repeated GraphQL requests/responses briefly between full logs (RepeatLogGate) — "no response lines" can be the gate summarising, not the poll stopping.Not counted, and honest caveats
kraken_providerarg (does not exist on main).🤖 Generated with Claude Code