Skip to content
Merged
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
7 changes: 4 additions & 3 deletions apps/predbat/load_ml_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ def initialize(self, load_ml_enable, load_ml_source=True, load_ml_max_days_histo

self.ml_learning_rate = 0.001
self.ml_epochs_initial = 100
self.ml_epochs_update = 20
self.ml_epochs_update = 30
self.ml_patience_initial = 20
self.ml_patience_update = 10
self.ml_min_days = 1
self.ml_validation_threshold = 2.0
self.ml_time_decay_days = 30
Expand All @@ -85,7 +87,6 @@ def initialize(self, load_ml_enable, load_ml_source=True, load_ml_max_days_histo
self.ml_max_days_history = load_ml_max_days_history
self.load_ml_database_days = load_ml_database_days
self.ml_validation_holdout_hours = 24
self.ml_epochs_patience = 5

# Data state
self.load_data = None
Expand Down Expand Up @@ -864,7 +865,7 @@ async def _do_training(self, is_initial):
epochs = self.ml_epochs_initial if is_initial else self.ml_epochs_update
time_decay = min(self.ml_time_decay_days, self.load_data_age_days)
holdout_hours = self.ml_validation_holdout_hours
patience = self.ml_epochs_patience
patience = self.ml_patience_initial if is_initial else self.ml_patience_update
# Lock released - event loop is free during training

try:
Expand Down
15 changes: 12 additions & 3 deletions apps/predbat/tests/test_load_ml.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,13 +1112,13 @@ def _test_real_data_training():
has_rates = import_rates_data and export_rates_data and len(import_rates_data) > 0 and len(export_rates_data) > 0
rates_info = " + import/export rates" if has_rates else ""
print(f" Training on real load + {data_source} PV/temperature{rates_info} with {len(load_data)} points...")
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=True, epochs=50, time_decay_days=7, validation_holdout_hours=48)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=True, epochs=100, time_decay_days=30, validation_holdout_hours=48, patience=20)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The test uses validation_holdout_hours=48 for training, but the component default is ml_validation_holdout_hours = 24. This inconsistency means the test doesn't validate the actual component behavior. Consider either updating the test to use the component default of 24 hours, or documenting why the test intentionally uses a different value for more thorough validation.

Copilot uses AI. Check for mistakes.

assert success, "Training on real data should succeed"
assert predictor.model_initialized, "Model should be initialized after training"

success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=50, time_decay_days=7, validation_holdout_hours=48)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=50, time_decay_days=7, validation_holdout_hours=48)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=20, time_decay_days=30, validation_holdout_hours=48, patience=10)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=20, time_decay_days=30, validation_holdout_hours=48, patience=10)
Comment on lines +1120 to +1121
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The test uses epochs=20 for update training, but the component default was changed to ml_epochs_update = 30 in load_ml_component.py. This inconsistency means the test is not validating the actual component behavior. Consider updating the test to use epochs=30 to match the component default, or add a comment explaining why the test intentionally uses different values.

Suggested change
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=20, time_decay_days=30, validation_holdout_hours=48, patience=10)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=20, time_decay_days=30, validation_holdout_hours=48, patience=10)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=30, time_decay_days=30, validation_holdout_hours=48, patience=10)
success = predictor.train(load_data, now_utc, pv_minutes=pv_data, temp_minutes=temp_data, import_rates=import_rates_data, export_rates=export_rates_data, is_initial=False, epochs=30, time_decay_days=30, validation_holdout_hours=48, patience=10)

Copilot uses AI. Check for mistakes.

# Make predictions
print(f" Generating predictions with PV + temperature{rates_info} forecasts...")
Expand Down Expand Up @@ -1248,6 +1248,15 @@ def _test_real_data_training():
val_pred_minutes.append(minute)
val_pred_energy.append(dp4(energy_kwh))

# --- Day 7 total comparison ---
actual_day7_total = sum(val_actual_energy)
predicted_day7_total = sum(val_pred_energy)
error_kwh = predicted_day7_total - actual_day7_total
error_pct = (error_kwh / actual_day7_total * 100) if actual_day7_total > 0 else 0.0
print(f" Day 7 actual total : {actual_day7_total:.3f} kWh")
print(f" Day 7 predicted total: {predicted_day7_total:.3f} kWh")
print(f" Day 7 error : {error_kwh:+.3f} kWh ({error_pct:+.1f}%)")
Comment on lines +1252 to +1258
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The Day 7 total comparison code runs unconditionally, even if shifted_load_data is empty and no validation predictions were generated. This could result in comparing actual data to an empty prediction list (sum of empty list = 0), producing misleading error metrics. Consider adding a check to only perform the Day 7 comparison when val_pred_energy is non-empty, or add a guard condition like if val_pred_energy: before the comparison block.

Suggested change
actual_day7_total = sum(val_actual_energy)
predicted_day7_total = sum(val_pred_energy)
error_kwh = predicted_day7_total - actual_day7_total
error_pct = (error_kwh / actual_day7_total * 100) if actual_day7_total > 0 else 0.0
print(f" Day 7 actual total : {actual_day7_total:.3f} kWh")
print(f" Day 7 predicted total: {predicted_day7_total:.3f} kWh")
print(f" Day 7 error : {error_kwh:+.3f} kWh ({error_pct:+.1f}%)")
if val_pred_energy and val_actual_energy:
actual_day7_total = sum(val_actual_energy)
predicted_day7_total = sum(val_pred_energy)
error_kwh = predicted_day7_total - actual_day7_total
error_pct = (error_kwh / actual_day7_total * 100) if actual_day7_total > 0 else 0.0
print(f" Day 7 actual total : {actual_day7_total:.3f} kWh")
print(f" Day 7 predicted total: {predicted_day7_total:.3f} kWh")
print(f" Day 7 error : {error_kwh:+.3f} kWh ({error_pct:+.1f}%)")
else:
print(" Day 7 comparison skipped: no validation predictions available")

Copilot uses AI. Check for mistakes.

# Convert predictions (cumulative kWh) to energy per step (kWh) for plotting
# predict() returns cumulative format; compute deltas for the chart
pred_minutes = []
Expand Down