mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-08 23:46:38 +00:00
feat(pvforecast): keep outages out of the local provider's calibration
A battery or inverter failure limits PV to local demand for days. The calibration read that as the plant's true output and learned it as a permanent model loss, so one outage degraded the forecast long after the hardware was fixed. Calibration now estimates the healthy plant ratio over `calibration_reference_days`, excludes days below `calibration_outage_threshold` of it, and falls back to the most recent `calibration_min_healthy_days` when the normal window is contaminated. `calibration_outage_filter_enabled` turns this off for plants where measured curtailment, not available potential, is the prediction target. The fit also uses native 15-minute meter readings when every configured PV meter supplies them - never interpolating hourly counters into an invented quarter-hour profile - interpolates azimuth factors smoothly between bin centres instead of stepping the EMS input curve, and normalizes the shape per forecast day so it redistributes energy without changing that day's kWh correction. The default azimuth bin widens from 15 to 45 degrees, which is what a typical window actually supports. Fixes the calibration window itself: it was derived from the measurement store as a whole rather than from the configured PV production meters. A load meter reaching further than the PV meter placed the window where no PV reading exists, so calibration silently fell back to hourly fitting or skipped itself. Also fixes `Measurement.load()`, which discarded every stored record. It validated the file into a "temporary" Measurement, but Measurement is a singleton, so that instance was the live one and the parsed records were dropped.
This commit is contained in:
@@ -118,6 +118,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Add `scripts/pvforecast_backtest.py`, which scores PV forecast configuration variants against
|
||||
the stored meter readings straight away instead of waiting for new forecasts to come true, and
|
||||
`Measurement.pv_production_total_kwh()` alongside the existing load total.
|
||||
- The local PV provider's calibration now excludes probable outage and curtailment days instead of
|
||||
learning them as permanent model losses: `calibration_outage_filter_enabled` (default on),
|
||||
`calibration_outage_threshold`, `calibration_reference_days` and `calibration_min_healthy_days`
|
||||
estimate the healthy plant ratio and fall back to the most recent healthy days. Calibration also
|
||||
uses native 15-minute meter readings when every configured PV meter supplies them, interpolates
|
||||
azimuth factors smoothly between bin centres instead of stepping, and normalizes the fitted
|
||||
shape per forecast day so it redistributes energy without changing that day's kWh correction.
|
||||
The default `calibration_azimuth_bin_degrees` moves from 15 to 45, which is what a typical
|
||||
calibration window actually supports.
|
||||
- Separate the control horizon from the battery lookahead. `optimization.horizon_hours` remains
|
||||
the only span that receives control commands; the new `optimization.tail_horizon_hours`
|
||||
(default 48 h) is a forecast lookahead that never produces a command. In `AUTO` terminal-value
|
||||
@@ -219,6 +228,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
provider could 404 the whole load prediction. It now defaults to a cache-aware update and
|
||||
accepts an optional `force_update` flag in the request body for callers that still want
|
||||
to force.
|
||||
- The local PV provider derived its calibration window from the measurement store as a whole
|
||||
instead of from the configured PV production meters. A load meter reaching further than the PV
|
||||
meter placed the window where no PV reading exists, so calibration silently fell back to hourly
|
||||
fitting or skipped itself entirely. The window now follows the PV meters.
|
||||
- `Measurement.load()` silently discarded every stored record. It validated the file into a
|
||||
temporary `Measurement`, but `Measurement` is a singleton, so the "temporary" instance was the
|
||||
already initialized one and the parsed records were dropped. The records are now validated
|
||||
individually and inserted directly.
|
||||
- A rejected configuration update no longer damages the running configuration.
|
||||
`merge_settings_from_dict` validated the merged candidate only while reinitializing the
|
||||
singleton, so an invalid update could leave EOS half-updated. The candidate is validated first.
|
||||
|
||||
Reference in New Issue
Block a user