fix(home-load): timezone-aware history, additive backfill and retrain feedback (#42)#43
Open
markoceri wants to merge 5 commits into
Open
Conversation
…manual collection The load-history pipeline mixed naive and timezone-aware datetimes, raising "can't compare offset-naive and offset-aware datetimes" while building the per-device consumption in the optimization loop. The 24h look-back window, the merged-consumption timestamp and the purge cut-off now use UTC-aware timestamps consistently. Manual collection also ignored `lookback_hours` whenever data already existed: it only fetched incrementally from the last stored point, so requesting e.g. 30 days returned just the few most recent points and never filled internal gaps. A manual `collect_devices` call now performs an additive full-window backfill -- `get_power_points` gains a `force_refresh` flag that re-fetches the whole window from Home Assistant and merges it into the store (de-duplicated by the (device_id, timestamp) primary key) without dropping existing data. The scheduled `collect_all` stays incremental. - optimization_service: UTC-aware look-back window and consumption timestamp - home_load_history_service: UTC-aware purge cut-off; force_full_window backfill - history provider port + HA/dummy adapters: force_refresh on get_power_points
After a manual per-device history collection completes, prompt the user to retrain that device's forecast model with the freshly collected data. The prompt only appears when the device has a forecast provider configured; on confirmation it triggers per-device training and refreshes the forecast panel. A small toast reports progress and surfaces training errors. Wires a new trainDevice call through the home-loads service and store onto the existing training-trigger endpoint. Scheduled nightly training is unchanged.
…he UI train_device now returns a LoadTrainingResult value object describing the outcome -- trained (with best adapter, MAE and sample count), skipped (with the reason, e.g. insufficient history) or failed -- instead of returning nothing. The training endpoint surfaces it, so a real retraining can be told apart from a silent skip; previously it always reported "completed". The device history modal shows the outcome in a status toast (success/warning/ error) and only refreshes the forecast when a model was actually (re)trained. The toast is teleported to <body> with a high z-index so it stays visible above the open History & Forecast modal instead of behind it.
… feedback Introduces a top-level CHANGELOG.md (same format as core/CHANGELOG.md) and records, under Unreleased, the timezone fix, the additive history backfill on manual collection, the forecast retrain prompt and the training-outcome reporting.
There was a problem hiding this comment.
Pull request overview
This PR addresses Home Loads history correctness and UX by standardizing UTC-aware timestamps, making manual history collection perform additive backfill, and adding a user-triggered per-device forecast retrain that reports concrete outcomes to the UI.
Changes:
- Fix naive-vs-aware datetime comparisons by using UTC-aware timestamps in the optimization loop and history purge cutoff.
- Add additive backfill behavior for manual device history collection via a
force_refresh/force_full_windowpath. - Add per-device model retrain trigger returning a structured outcome (
LoadTrainingResult), surfaced through the API and displayed in the device history modal.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/core/stores/homeLoadsProfileStore.ts | Exposes a trainDevice store action for the UI to trigger retraining. |
| frontend/src/core/services/homeLoadsProfileService.ts | Adds client call to the per-device training trigger endpoint. |
| frontend/src/components/homeLoads/LoadDeviceHistoryModal.vue | Prompts to retrain after manual collection and shows retrain outcome via a toast. |
| core/tests/unit/adapters/domain/home_load/test_home_load_api_endpoints.py | Updates/extends endpoint tests for new training outcome semantics. |
| core/edge_mining/domain/home_load/value_objects.py | Introduces LoadTrainingResult for structured training outcomes. |
| core/edge_mining/domain/home_load/ports.py | Extends history provider port with force_refresh for backfill support. |
| core/edge_mining/application/services/optimization_service.py | Uses UTC-aware timestamps for the 24h lookback window and merged-consumption timestamp. |
| core/edge_mining/application/services/load_forecast_training_service.py | Makes train_device return LoadTrainingResult with trained/skipped/failed outcomes. |
| core/edge_mining/application/services/home_load_history_service.py | Implements additive backfill behavior for manual collection and fixes UTC cutoff in purge. |
| core/edge_mining/application/interfaces.py | Updates interface signatures for collect_devices and train_device to match new behavior/return type. |
| core/edge_mining/adapters/domain/home_load/history_providers/home_assistant_api_history.py | Adds force_refresh path to refetch entire window from Home Assistant for backfill. |
| core/edge_mining/adapters/domain/home_load/history_providers/dummy.py | Updates dummy provider signature/docs to accept force_refresh. |
| core/edge_mining/adapters/domain/home_load/fast_api/router.py | Updates training trigger endpoint to return detailed retrain outcomes. |
| CHANGELOG.md | Documents the fixes/additions (timezone handling, backfill, retrain outcome reporting). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+138
to
+142
| if force_refresh: | ||
| fetched = await self._fetch_from_home_assistant(start, end) | ||
| if fetched: | ||
| self._history_repo.add_power_points(self.device_id, fetched) | ||
| return self._history_repo.get_power_points(self.device_id, start, end) |
Comment on lines
+929
to
+933
| if result.status == "trained" and result.best_adapter is not None: | ||
| detail = ( | ||
| f"Model retrained for '{result.device_name}': best={result.best_adapter.value} " | ||
| f"MAE={result.best_mae:.1f} ({result.samples_used} samples)." | ||
| ) |
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.
Closes #42
Summary
Fixes the home load history timezone bug and makes manual collection actually backfill, then adds a user-driven forecast retrain with clear outcome feedback.
Fixed — timezone-aware history datetimes
The optimization loop built its 24h look-back window with a naive
datetime.now(), which clashed with the timezone-aware (UTC) timestamps from Home Assistant and the persistence layer, raising "can't compare offset-naive and offset-aware datetimes". The look-back window, the merged-consumption timestamp and the history purge cut-off now use UTC-aware timestamps consistently.Added — additive backfill on manual collection
A manual per-device collection previously ignored
lookback_hourswhenever data already existed (it only ingested incrementally from the last stored point), so requesting e.g. 30 days returned just the few most recent points and never filled internal gaps. It now re-fetches the whole requested window from the provider and merges it into the store, de-duplicated by the(device_id, timestamp)primary key, without dropping existing data (bounded by Home Assistant's recorder retention).EnergyLoadHistoryProviderPort.get_power_pointsgains aforce_refreshflag; the scheduled collection stays incremental.Added — forecast retrain after collection, with outcome feedback
After a manual collection the device history modal prompts the user to retrain that device's forecast model.
train_devicenow returns aLoadTrainingResultvalue object reporting whether a model was actuallytrained(best adapter, MAE, samples),skipped(with reason, e.g. insufficient history) orfailed, instead of always reporting a generic "completed". The UI shows the outcome in a status toast (teleported above the modal).Testing
pytestfor the training, history and API endpoint suites — all green.vue-tsc --noEmit— no type errors.