Skip to content

fix(car): low-load test needs live load power to be low too before cancelling an Intelligent dispatch - #5462

Merged
springfall2008 merged 2 commits into
mainfrom
fix/iog-low-load-live-power-5461
Oct 9, 2026
Merged

springfall2008 merged 2 commits into
mainfrom
fix/iog-low-load-live-power-5461

Conversation

@springfall2008

Copy link
Copy Markdown
Owner

Fixes #5461.

Problem

With octopus_intelligent_dynamic on and no car_charging_now sensor, the slot confirmation falls back to the house load. It cancelled a genuine dispatch while a 7.4 kW car was charging in view of the CT: the 5-minute load figure read 4.80 kW against a 5.4 kW cut-off, at a moment when the inverter reported 7410 W. Once the slots were cancelled the battery hold was released and the battery discharged into the car.

The 5-minute figure lags a car that is still ramping up, and a cloud inverter whose readings arrive late. The load path has no start band, so the ramp window itself can start the grace clock and one more marginal window cancels.

Change

  • Live load must be low too. In dynamic_load_car_evidence(), a low 5-minute figure only counts as "not charging" when the live load_power total is also under the cut-off.
  • Mixed evidence is no evidence. A low 5-minute figure with live load that is not low, or has no reading, returns "no evidence": it neither cancels the car nor resumes it, and it resets the grace clock.
  • Cut-off lowered from 90% to 85% of car_charging_threshold (5.1 kW on the 6 kW default), as DYNAMIC_LOAD_CAR_LOW_FACTOR. This gives margin for a load sensor that under-reads while the battery is grid-charging.
  • Installs without load_power keep the 5-minute figure alone, as before.
  • Logging. Each low window logs the 5-minute figure, the cut-off and the live figure.

The live figure is read from the load_power sensors directly and summed across inverters, because dynamic_load_car_check() runs before fetch_inverter_data() and the inverter objects still hold the previous cycle's value. If any inverter's sensor is unknown, unavailable or missing, there is no total.

Things to review

  • dynamic_load_classify() is shared with dynamic_load(), so the 85% factor also moves the published load_energy_last_period status: a load between 85% and 90% of the threshold now reads "baseline" where it read "low". The battery-rate 0.9 in the same test is unchanged.
  • A cancelled car resumes on the next 5-minute figure, not on the live reading alone.
  • Confirming the half hour (keeping its cheap rate once the car stops) still needs load at the full car_charging_threshold. Not changed here.
  • Not covered: a "live" value that is itself minutes stale and was taken during the ramp reads low on both measures. Skipping the first window after the dispatch starts would close that; it is left out of this PR.

Testing

  • Tests 60-66 in tests/test_dynamic_load_car.py (./run_all --test dynamic_load_car_not_charging): the 4.80 kW / 7410 W case from the issue, both figures low, two inverters summed, unavailable / partly unavailable / missing sensors, no load_power, a cancelled car with high live load, and the 85% boundary on both figures. They failed before the change.
  • The test fixture's apps.yaml sets load_power, so the module removes it for the older scenarios, which judge the 5-minute figure alone.
  • ./run_all --quick: all tests passed (4 slow tests skipped).
  • pre-commit clean on the changed files.

Docs: docs/car-charging.md, "Checking Intelligent dispatches against the car".

🤖 Generated with Claude Code

…ncelling an Intelligent dispatch (#5461)

The Octopus Intelligent slot confirmation cancelled a genuine dispatch while a
7.4kW car was charging in view of the CT: the 5 minute load figure read 4.80kW
against a 5.4kW cut-off while the inverter reported 7410W. The 5 minute figure
lags a car that is still ramping up, and a cloud inverter's late readings.

- A low 5 minute figure now only counts as "not charging" when the live
  load_power total is also under the cut-off. Live load that is not low, or
  has no reading, makes the window no evidence either way.
- Installs without load_power keep the 5 minute figure alone.
- The cut-off drops from 90% to 85% of car_charging_threshold, for a load
  sensor that under-reads while the battery is charging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 9, 2026 18:34

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.

🟡 Changes recommended

Live-load aggregation omits configured load_power_N sensors, allowing false cancellations on split-load installations.

1 open finding
What changed in this PR

Prevents false Intelligent dispatch cancellations by requiring both averaged and live load to be low.

Changes:

  • Adds an 85% low-load threshold and live-load cross-check.
  • Treats mixed or unavailable live readings as no evidence.
  • Adds documentation and regression tests.
File Description
apps/​predbat/​const.py Defines the 85% factor.
apps/​predbat/​plan.py Implements live-load evidence checks.
apps/​predbat/​tests/​test_dynamic_load.py Updates threshold commentary.
apps/​predbat/​tests/​test_dynamic_load_car.py Adds live-load regression scenarios.
docs/​car-charging.md Documents dispatch-check behavior.

🧠 Review effort: Balanced


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

Comment thread apps/predbat/plan.py Outdated
Comment on lines +405 to +409
for inverter_n, entity_id in enumerate(entities):
try:
power = float(self.resolve_arg("load_power", entity_id, default=None, quiet=True, required_unit="W"))
except (ValueError, TypeError):
return True, None
…e load (#5461)

Review of #5462: the live load total read load_power only, while the inverter
sums load_power plus the type's extra load_power_N sensors (Solis reports the
bypass load on load_power_1). A car visible only on the extra sensor could
still read as low live load and have its genuine dispatch cancelled.

The total is now summed as Inverter.update_status() sums it; an extra sensor
that is set but unreadable leaves no total.

Co-Authored-By: Claude Opus 5.5 <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.

Octopus Intelligent: low-load test cancels a genuine dispatch while the car is charging in view of the CT

2 participants