feat: add pvlib pv forecast provider (#1214)

Add a PV forecast provider that calculates the forecast using a PVLib system model
and weather forecast from the EOS weather forecast provider.

Additional module and inverter models can be easily added as the database
is build from PVLib and SAM databases and a bundled csv file.

The module model and inververt model names are provided by new endpoints
to be used in configuration.

The provider is based on the fantastic work of EMHASS. See
https://github.com/davidusb-geek/emhass/blob/master/src/emhass/forecast.py

A short description of the provider is added to the documentation.

Besides the new features there are the fixes and improvements:

* feat: improve EOSdash config page

* fix: kex_to_series for start_datetime

  Make key_to_series always start the series at start_datetime.

* fix: default provider for GENETIC and GENETIC0 optimization

  To make the default less dependent on internet servers (with API changes and
  availability issues) the default for PVForecast is set to PVForecastPVLib
  and for ElecPrice to ElecPriceFixed. The default weather provider is changed
  to OpenMeteo.

* fix: EOSdash display resampled prediction values

  Make EOSdash display resampled prediction values where  resampling fits to
  the prediction value type. Use bar width that fits to 15 minutes value samples.

* chore: add a UI hints system to EOSdash

  The UI hints system eases the definition of forms for configuration items.
  There are also forms for items in maps and lists. These forms allow to add and delete
  items to/ from  maps and lists. The forms ensure that all required fields of newly
  added items are filled.

* chore: Create an enum for valid optimization algorithms

* chore. Make config also provide the available energy management modes.

  Used for configuration hints.

* chore: Randomize default device id in configuration

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-08-07 13:13:17 +02:00
committed by GitHub
parent 9189fc890e
commit 894790f577
49 changed files with 29242 additions and 667 deletions
+73
View File
@@ -1,7 +1,10 @@
import bz2
import hashlib
import json
import logging
import os
import pickle
import signal
import subprocess
import sys
@@ -14,6 +17,7 @@ from pathlib import Path
from typing import Generator, Optional, Union
from unittest.mock import PropertyMock, patch
import pandas as pd
import pendulum
import psutil
import pytest
@@ -176,6 +180,74 @@ def cfg_non_existent(request):
# ------------------------------------
@pytest.fixture(scope="session")
def cec_databases_data() -> tuple[pd.DataFrame, pd.DataFrame]:
"""Load CEC test databases once per test session."""
DIR_TESTDATA = Path(__file__).parent / "testdata" / "pvforecastpvlib"
FILE_TESTDATA_CEC_INVERTERS_PBZ2 = DIR_TESTDATA / "cec_inverters.pbz2"
FILE_TESTDATA_CEC_MODULES_PBZ2 = DIR_TESTDATA / "cec_modules.pbz2"
with bz2.BZ2File(FILE_TESTDATA_CEC_MODULES_PBZ2, "rb") as f:
modules: pd.DataFrame = pickle.load(f)
with bz2.BZ2File(FILE_TESTDATA_CEC_INVERTERS_PBZ2, "rb") as f:
inverters: pd.DataFrame = pickle.load(f)
return modules, inverters
@pytest.fixture(autouse=True)
def cec_databases(monkeypatch, cec_databases_data) -> tuple[pd.DataFrame, pd.DataFrame]:
"""Short-circuit CEC database access for every test (per-test patch, session-cached data).
Config requests the database in PVForecastPVLibCommonSettings by a computed_field.
To undo this fixture in a specific class do:
@pytest.fixture(autouse=True)
def cec_databases(self):
yield None
"""
modules, inverters = cec_databases_data
def fake_update_cec_database() -> None:
#print(f"_update_cec_database faked")
pass
def fake_load_cec_database(path: Path) -> pd.DataFrame:
#print(f"_loadcec_database faked")
if "inverter" in path.name:
return inverters
if "module" in path.name:
return modules
raise ValueError(f"Unexpected CEC database path in test: {path}")
def fake_cec_inverters() -> pd.DataFrame:
#print(f"_cec_inverters faked")
return inverters
def fake_cec_modules() -> pd.DataFrame:
#print(f"_cec_modules faked")
return modules
monkeypatch.setattr(
"akkudoktoreos.prediction.pvforecastpvlib._update_cec_database",
fake_update_cec_database,
)
monkeypatch.setattr(
"akkudoktoreos.prediction.pvforecastpvlib._load_cec_database",
fake_load_cec_database,
)
monkeypatch.setattr(
"akkudoktoreos.prediction.pvforecastpvlib._cec_inverters",
fake_cec_inverters,
)
monkeypatch.setattr(
"akkudoktoreos.prediction.pvforecastpvlib._cec_modules",
fake_cec_modules,
)
return modules, inverters
@pytest.fixture
def config_default_dirs(tmpdir):
"""Fixture that provides a list of directories to be used as config dir."""
@@ -234,6 +306,7 @@ def user_data_dir(config_default_dirs):
@pytest.fixture
def config_eos_factory(
cec_databases,
disable_debug_logging,
user_config_dir,
user_data_dir,