Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/predbat/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,11 @@ def fetch_sensor_data(self, save=True):
self.log("Using load_power data to fill gaps in load_today data")
load_power_data, _ = self.minute_data_load(self.now_utc, "load_power", self.max_days_previous, required_unit="W", load_scaling=1.0, interpolate=True)
self.load_minutes = self.fill_load_from_power(self.load_minutes, load_power_data)
# Recalculate load_minutes_now and load_last_period using the improved data from fill_load_from_power
# This ensures that if the original load_today data had a transient zero or gap at minute 0,
# the power-based fill is reflected in the current load estimate instead of leaving it at 0
self.load_minutes_now = get_now_from_cumulative(self.load_minutes, self.minutes_now, backwards=True)
self.load_last_period = (self.load_minutes.get(0, 0) - self.load_minutes.get(PREDICT_STEP, 0)) * 60 / PREDICT_STEP
Comment on lines +749 to +750
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

load_last_period can become negative if the cumulative series has a transient reset/glitch (e.g., load_minutes[0] < load_minutes[PREDICT_STEP]). That negative kW then feeds into dynamic_load() logic. Consider clamping this computed value to >= 0 (and potentially logging when a negative delta is observed) to avoid impossible negative load rates influencing planning.

Copilot uses AI. Check for mistakes.
Comment on lines +746 to +750
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This recalculation is only applied in the non-GE-cloud path. In the GE Cloud path above, self.load_minutes can also be replaced by fill_load_from_power(...) (lines 728-733), but load_minutes_now/load_last_period are not recomputed afterwards, so current load can still reflect the pre-fill data in that mode. Apply the same recomputation after the GE Cloud fill_load_from_power call to keep behavior consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +746 to +750
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

As-is, this change won’t prevent a short transient zero at minute 0 from propagating to load_minutes_now, because fill_load_from_power only treats flat/zero runs as fillable when their length is >= gap_size (defaulting to plan_interval_minutes, typically 30). For the reported ~5-minute glitch, fill_load_from_power will likely leave self.load_minutes[0] at 0, and this recomputation will still produce 0. To fully fix the issue, fill_load_from_power needs to handle short start-of-series glitches (or accept a smaller threshold for the initial segment when power data is present).

Copilot uses AI. Check for mistakes.
else:
if self.load_forecast:
self.log("Using load forecast from load_forecast sensor")
Expand Down
51 changes: 51 additions & 0 deletions apps/predbat/tests/test_fill_load_from_power.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,56 @@ def test_fill_load_from_power_backwards_time():
print("Test 6 PASSED")


def test_fill_load_from_power_zero_glitch_at_minute_0():
"""
Test the specific bug scenario where load_today briefly glitches to 0 at minute 0.
The sensor reading resets to 0 for one cycle, but load_power data is still valid.
fill_load_from_power should detect the zero period at minute 0 and fill it with
power-integrated data, restoring the correct current load estimate.

This is the scenario described in the bug report where current load glitches to 0
a few times a day. The fix is to recalculate load_minutes_now after fill_load_from_power.
"""
print("\n=== Test 7: Zero glitch at minute 0 (bug fix verification) ===")

fetch = TestFetch()

# Simulate: sensor was accumulating fine (17.61 kWh by yesterday)
# At minute 0 (now), the load_today sensor glitched to 0
# Minutes 1-4 are also 0 (filled from the glitched reading)
# Minutes 5+ have the correct cumulative data
load_minutes = {}
load_minutes[0] = 0.0 # Glitch: should be ~17.61 kWh
load_minutes[1] = 0.0
load_minutes[2] = 0.0
load_minutes[3] = 0.0
load_minutes[4] = 0.0
# From minute 5 onwards, data is correct (10.0 kWh for testing)
for minute in range(5, 35):
load_minutes[minute] = 10.0 - (minute - 5) * (1.0 / 30.0) # Slowly decreasing from 10.0
load_minutes[35] = 9.0

# Power data is still valid (sensor didn't glitch, ~3kW load)
load_power_data = {}
for minute in range(0, 35):
load_power_data[minute] = 3000.0 # 3 kW = 0.05 kWh/minute

result = fetch.fill_load_from_power(load_minutes, load_power_data)

# After fill_load_from_power, minute 0 should be non-zero
# (the power data filled the zero gap)
assert result[0] > 0.0, f"Minute 0 should be non-zero after power fill, got {result[0]}"

# Minute 0 should reflect the accumulated power consumption for the zero period
# 5 minutes at 3kW = 5 * 3/60 = 0.25 kWh
# So minute 0 should be approximately 0.25 kWh (power-integrated)
assert result[0] >= 0.1, f"Minute 0 should be at least 0.1 kWh from power fill, got {result[0]}"
Comment on lines +326 to +351
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

This test’s assertions don’t line up with the current fill_load_from_power behavior and with cumulative-load semantics. With TestFetch.plan_interval_minutes = 30 and get_arg() returning defaults, gap_size becomes 30 so a 5-minute zero run at minute 0 is intentionally not treated as a fillable zero period; result[0] will remain 0 and the test will fail. Also, if minute 5 is ~10.0kWh and power is 3kW for 5 minutes, the corrected minute 0 should be close to 10.25kWh (minute5 + integrated energy), not merely >0.1kWh. Either adjust the test setup (e.g., override load_filter_threshold/plan_interval_minutes or make the glitch >= gap_size) or implement the corresponding algorithm change, then assert the expected corrected magnitude.

Copilot uses AI. Check for mistakes.

print(f"✓ Zero glitch at minute 0 fixed: result[0] = {dp4(result[0])} kWh")
print(f" (was 0.0 kWh, now restored from power data)")
print("Test 7 PASSED")


def run_all_tests(my_predbat=None):
"""Run all tests"""
print("\n" + "=" * 60)
Expand All @@ -318,6 +368,7 @@ def run_all_tests(my_predbat=None):
test_fill_load_from_power_single_minute_period()
test_fill_load_from_power_zero_load()
test_fill_load_from_power_backwards_time()
test_fill_load_from_power_zero_glitch_at_minute_0()

print("\n" + "=" * 60)
print("✅ ALL TESTS PASSED")
Expand Down
Loading