mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-11-05 09:16:21 +00:00
fix: load data for automatic optimization (#731)
Automatic optimization used to take the adjusted load data even if there were no measurements leading to 0 load values. Split LoadAkkudoktor into LoadAkkudoktor and LoadAkkudoktorAdjusted. This allows to select load data either purely from the load data database or load data additionally adjusted by load measurements. Some value names have been adapted to denote also the unit of a value. For better load bug squashing the optimization solution data availability was improved. For better data visbility prediction data can now be distinguished from solution data in the generic optimization solution. Some predictions that may be of interest to understand the solution were added. Documentation was updated to resemble the addition load prediction provider and the value name changes. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -24,7 +24,7 @@ MIGRATION_MAP: Dict[str, Union[str, Tuple[str, Callable[[Any], Any]], None]] = {
|
||||
"elecprice/provider_settings/import_json": "elecprice/provider_settings/ElecPriceImport/import_json",
|
||||
"load/provider_settings/import_file_path": "load/provider_settings/LoadImport/import_file_path",
|
||||
"load/provider_settings/import_json": "load/provider_settings/LoadImport/import_json",
|
||||
"load/provider_settings/loadakkudoktor_year_energy": "load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy",
|
||||
"load/provider_settings/loadakkudoktor_year_energy": "load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/load_vrm_idsite": "load/provider_settings/LoadVrm/load_vrm_idsite",
|
||||
"load/provider_settings/load_vrm_token": "load/provider_settings/LoadVrm/load_vrm_token",
|
||||
"logging/level": "logging/console_level",
|
||||
@@ -123,6 +123,9 @@ def migrate_config_file(config_file: Path, backup_file: Path) -> bool:
|
||||
|
||||
old_value = _get_json_nested_value(config_data, old_path)
|
||||
if old_value is None:
|
||||
migrated_source_paths.add(old_path.strip("/"))
|
||||
mapped_count += 1
|
||||
logger.debug(f"✅ Migrated mapped '{old_path}' → 'None'")
|
||||
continue
|
||||
|
||||
try:
|
||||
|
||||
@@ -1664,11 +1664,11 @@ class DataImportMixin:
|
||||
{
|
||||
"start_datetime": "2024-11-10 00:00:00"
|
||||
"interval": "30 minutes"
|
||||
"load_mean": [20.5, 21.0, 22.1],
|
||||
"loadforecast_power_w": [20.5, 21.0, 22.1],
|
||||
"other_xyz: [10.5, 11.0, 12.1],
|
||||
}
|
||||
```
|
||||
and `key_prefix = "load"`, only the "load_mean" key will be processed even though
|
||||
and `key_prefix = "load"`, only the "loadforecast_power_w" key will be processed even though
|
||||
both keys are in the record.
|
||||
"""
|
||||
# Try pandas dataframe with orient="split"
|
||||
@@ -1738,11 +1738,11 @@ class DataImportMixin:
|
||||
Given a JSON file with the following content:
|
||||
```json
|
||||
{
|
||||
"load_mean": [20.5, 21.0, 22.1],
|
||||
"loadforecast_power_w": [20.5, 21.0, 22.1],
|
||||
"other_xyz: [10.5, 11.0, 12.1],
|
||||
}
|
||||
```
|
||||
and `key_prefix = "load"`, only the "load_mean" key will be processed even though
|
||||
and `key_prefix = "load"`, only the "loadforecast_power_w" key will be processed even though
|
||||
both keys are in the record.
|
||||
"""
|
||||
with import_file_path.open("r", encoding="utf-8", newline=None) as import_file:
|
||||
|
||||
@@ -735,7 +735,7 @@ class PydanticDateTimeData(RootModel):
|
||||
{
|
||||
"start_datetime": "2024-01-01 00:00:00", # optional
|
||||
"interval": "1 Hour", # optional
|
||||
"load_mean": [20.5, 21.0, 22.1],
|
||||
"loadforecast_power_w": [20.5, 21.0, 22.1],
|
||||
"load_min": [18.5, 19.0, 20.1]
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -301,8 +301,8 @@ class GeneticOptimizationParameters(
|
||||
# Retry
|
||||
continue
|
||||
try:
|
||||
load_mean_adjusted = cls.prediction.key_to_array(
|
||||
key="load_mean_adjusted",
|
||||
loadforecast_power_w = cls.prediction.key_to_array(
|
||||
key="loadforecast_power_w",
|
||||
start_datetime=parameter_start_datetime,
|
||||
end_datetime=parameter_end_datetime,
|
||||
interval=interval,
|
||||
@@ -319,7 +319,7 @@ class GeneticOptimizationParameters(
|
||||
"provider": "LoadAkkudoktor",
|
||||
"provider_settings": {
|
||||
"LoadAkkudoktor": {
|
||||
"loadakkudoktor_year_energy": "1000",
|
||||
"loadakkudoktor_year_energy_kwh": "3000",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -607,7 +607,7 @@ class GeneticOptimizationParameters(
|
||||
pv_prognose_wh=pvforecast_ac_power,
|
||||
strompreis_euro_pro_wh=elecprice_marketprice_wh,
|
||||
einspeiseverguetung_euro_pro_wh=feed_in_tariff_wh,
|
||||
gesamtlast=load_mean_adjusted,
|
||||
gesamtlast=loadforecast_power_w,
|
||||
preis_euro_pro_wh_akku=battery_lcos_kwh / 1000,
|
||||
),
|
||||
temperature_forecast=weather_temp_air,
|
||||
|
||||
@@ -231,6 +231,7 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
config = get_config()
|
||||
start_datetime = get_ems().start_datetime
|
||||
interval_hours = 1
|
||||
power_to_energy_per_interval_factor = 1.0
|
||||
|
||||
# --- Create index based on list length and interval ---
|
||||
n_points = len(self.result.Kosten_Euro_pro_Stunde)
|
||||
@@ -241,11 +242,9 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
)
|
||||
end_datetime = start_datetime.add(hours=n_points)
|
||||
|
||||
# Fill data into dataframe with correct column names
|
||||
# Fill solution into dataframe with correct column names
|
||||
# - load_energy_wh: Load of all energy consumers in wh"
|
||||
# - grid_energy_wh: Grid energy feed in (negative) or consumption (positive) in wh"
|
||||
# - pv_prediction_energy_wh: PV energy prediction (positive) in wh"
|
||||
# - elec_price_prediction_amt_kwh: Electricity price prediction in money per kwh"
|
||||
# - costs_amt: Costs in money amount"
|
||||
# - revenue_amt: Revenue in money amount"
|
||||
# - losses_energy_wh: Energy losses in wh"
|
||||
@@ -254,7 +253,7 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
# - <device-id>_soc_factor: State of charge of a battery/ electric vehicle device as factor of total capacity."
|
||||
# - <device-id>_energy_wh: Energy consumption (positive) of a device in wh."
|
||||
|
||||
data = pd.DataFrame(
|
||||
solution = pd.DataFrame(
|
||||
{
|
||||
"date_time": time_index,
|
||||
"load_energy_wh": self.result.Last_Wh_pro_Stunde,
|
||||
@@ -269,7 +268,7 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
)
|
||||
|
||||
# Add battery data
|
||||
data["battery1_soc_factor"] = [v / 100 for v in self.result.akku_soc_pro_stunde]
|
||||
solution["battery1_soc_factor"] = [v / 100 for v in self.result.akku_soc_pro_stunde]
|
||||
operation: dict[str, list[float]] = {}
|
||||
for hour, rate in enumerate(self.ac_charge):
|
||||
if hour >= n_points:
|
||||
@@ -290,13 +289,13 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
operation[mode_key].append(0.0)
|
||||
operation[factor_key].append(0.0)
|
||||
for key in operation.keys():
|
||||
data[key] = operation[key]
|
||||
solution[key] = operation[key]
|
||||
|
||||
# Add EV battery data
|
||||
# Add EV battery solution
|
||||
if self.eauto_obj:
|
||||
if self.eautocharge_hours_float is None:
|
||||
# Electric vehicle is full enough. No load times.
|
||||
data[f"{self.eauto_obj.device_id}_soc_factor"] = [
|
||||
solution[f"{self.eauto_obj.device_id}_soc_factor"] = [
|
||||
self.eauto_obj.initial_soc_percentage / 100.0
|
||||
] * n_points
|
||||
# operation modes
|
||||
@@ -305,13 +304,13 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
mode_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_mode"
|
||||
factor_key = f"{self.eauto_obj.device_id}_{mode.lower()}_op_factor"
|
||||
if mode == operation_mode:
|
||||
data[mode_key] = [1.0] * n_points
|
||||
data[factor_key] = [1.0] * n_points
|
||||
solution[mode_key] = [1.0] * n_points
|
||||
solution[factor_key] = [1.0] * n_points
|
||||
else:
|
||||
data[mode_key] = [0.0] * n_points
|
||||
data[factor_key] = [0.0] * n_points
|
||||
solution[mode_key] = [0.0] * n_points
|
||||
solution[factor_key] = [0.0] * n_points
|
||||
else:
|
||||
data[f"{self.eauto_obj.device_id}_soc_factor"] = [
|
||||
solution[f"{self.eauto_obj.device_id}_soc_factor"] = [
|
||||
v / 100 for v in self.result.EAuto_SoC_pro_Stunde
|
||||
]
|
||||
operation = {}
|
||||
@@ -334,18 +333,30 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
operation[mode_key].append(0.0)
|
||||
operation[factor_key].append(0.0)
|
||||
for key in operation.keys():
|
||||
data[key] = operation[key]
|
||||
solution[key] = operation[key]
|
||||
|
||||
# Add home appliance data
|
||||
if self.washingstart:
|
||||
data["homeappliance1_energy_wh"] = self.result.Home_appliance_wh_per_hour
|
||||
solution["homeappliance1_energy_wh"] = self.result.Home_appliance_wh_per_hour
|
||||
|
||||
# Add important predictions that are not already available from the GenericSolution
|
||||
prediction = get_prediction()
|
||||
power_to_energy_per_interval_factor = 1.0
|
||||
if "pvforecast_ac_power" in prediction.record_keys:
|
||||
data["pv_prediction_energy_wh"] = (
|
||||
prediction.key_to_array(
|
||||
# Fill prediction into dataframe with correct column names
|
||||
# - pvforecast_ac_energy_wh_energy_wh: PV energy prediction (positive) in wh
|
||||
# - elec_price_amt_kwh: Electricity price prediction in money per kwh
|
||||
# - weather_temp_air_celcius: Temperature in °C"
|
||||
# - loadforecast_energy_wh: Load energy prediction in wh
|
||||
# - loadakkudoktor_std_energy_wh: Load energy standard deviation prediction in wh
|
||||
# - loadakkudoktor_mean_energy_wh: Load mean energy prediction in wh
|
||||
prediction = pd.DataFrame(
|
||||
{
|
||||
"date_time": time_index,
|
||||
},
|
||||
index=time_index,
|
||||
)
|
||||
pred = get_prediction()
|
||||
|
||||
if "pvforecast_ac_power" in pred.record_keys:
|
||||
prediction["pvforecast_ac_energy_wh"] = (
|
||||
pred.key_to_array(
|
||||
key="pvforecast_ac_power",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
@@ -354,18 +365,82 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
)
|
||||
* power_to_energy_per_interval_factor
|
||||
).tolist()
|
||||
if "weather_temp_air" in prediction.record_keys:
|
||||
data["weather_temp_air"] = (
|
||||
prediction.key_to_array(
|
||||
key="weather_temp_air",
|
||||
if "pvforecast_dc_power" in pred.record_keys:
|
||||
prediction["pvforecast_dc_energy_wh"] = (
|
||||
pred.key_to_array(
|
||||
key="pvforecast_dc_power",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
)
|
||||
* power_to_energy_per_interval_factor
|
||||
).tolist()
|
||||
if "elecprice_marketprice_wh" in pred.record_keys:
|
||||
prediction["elec_price_amt_kwh"] = (
|
||||
pred.key_to_array(
|
||||
key="elecprice_marketprice_wh",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="ffill",
|
||||
)
|
||||
* 1000
|
||||
).tolist()
|
||||
if "feed_in_tariff_wh" in pred.record_keys:
|
||||
prediction["feed_in_tariff_amt_kwh"] = (
|
||||
pred.key_to_array(
|
||||
key="feed_in_tariff_wh",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
)
|
||||
* 1000
|
||||
).tolist()
|
||||
if "weather_temp_air" in pred.record_keys:
|
||||
prediction["weather_air_temp_celcius"] = pred.key_to_array(
|
||||
key="weather_temp_air",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
).tolist()
|
||||
if "loadforecast_power_w" in pred.record_keys:
|
||||
prediction["loadforecast_energy_wh"] = (
|
||||
pred.key_to_array(
|
||||
key="loadforecast_power_w",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
)
|
||||
* power_to_energy_per_interval_factor
|
||||
).tolist()
|
||||
if "loadakkudoktor_std_power_w" in pred.record_keys:
|
||||
prediction["loadakkudoktor_std_energy_wh"] = (
|
||||
pred.key_to_array(
|
||||
key="loadakkudoktor_std_power_w",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
)
|
||||
* power_to_energy_per_interval_factor
|
||||
).tolist()
|
||||
if "loadakkudoktor_mean_power_w" in pred.record_keys:
|
||||
prediction["loadakkudoktor_mean_energy_wh"] = (
|
||||
pred.key_to_array(
|
||||
key="loadakkudoktor_mean_power_w",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
interval=to_duration(f"{interval_hours} hours"),
|
||||
fill_method="linear",
|
||||
)
|
||||
* power_to_energy_per_interval_factor
|
||||
).tolist()
|
||||
|
||||
solution = OptimizationSolution(
|
||||
optimization_solution = OptimizationSolution(
|
||||
id=f"optimization-genetic@{to_datetime(as_string=True)}",
|
||||
generated_at=to_datetime(),
|
||||
comment="Optimization solution derived from GeneticSolution.",
|
||||
@@ -374,10 +449,11 @@ class GeneticSolution(GeneticParametersBaseModel):
|
||||
total_losses_energy_wh=self.result.Gesamt_Verluste,
|
||||
total_revenues_amt=self.result.Gesamteinnahmen_Euro,
|
||||
total_costs_amt=self.result.Gesamtkosten_Euro,
|
||||
data=PydanticDateTimeDataFrame.from_dataframe(data),
|
||||
prediction=PydanticDateTimeDataFrame.from_dataframe(prediction),
|
||||
solution=PydanticDateTimeDataFrame.from_dataframe(solution),
|
||||
)
|
||||
|
||||
return solution
|
||||
return optimization_solution
|
||||
|
||||
def energy_management_plan(self) -> EnergyManagementPlan:
|
||||
"""Provide the genetic solution as an energy management plan."""
|
||||
|
||||
@@ -110,13 +110,24 @@ class OptimizationSolution(PydanticBaseModel):
|
||||
|
||||
total_costs_amt: float = Field(description="The total costs [money amount].")
|
||||
|
||||
data: PydanticDateTimeDataFrame = Field(
|
||||
prediction: PydanticDateTimeDataFrame = Field(
|
||||
description=(
|
||||
"Datetime data frame with time series optimization data per optimization interval:"
|
||||
"Datetime data frame with time series prediction data per optimization interval:"
|
||||
"- pv_energy_wh: PV energy prediction (positive) in wh"
|
||||
"- elec_price_amt_kwh: Electricity price prediction in money per kwh"
|
||||
"- feed_in_tariff_amt_kwh: Feed in tariff prediction in money per kwh"
|
||||
"- weather_temp_air_celcius: Temperature in °C"
|
||||
"- loadforecast_energy_wh: Load mean energy prediction in wh"
|
||||
"- loadakkudoktor_std_energy_wh: Load energy standard deviation prediction in wh"
|
||||
"- loadakkudoktor_mean_energy_wh: Load mean energy prediction in wh"
|
||||
)
|
||||
)
|
||||
|
||||
solution: PydanticDateTimeDataFrame = Field(
|
||||
description=(
|
||||
"Datetime data frame with time series solution data per optimization interval:"
|
||||
"- load_energy_wh: Load of all energy consumers in wh"
|
||||
"- grid_energy_wh: Grid energy feed in (negative) or consumption (positive) in wh"
|
||||
"- pv_prediction_energy_wh: PV energy prediction (positive) in wh"
|
||||
"- elec_price_prediction_amt_kwh: Electricity price prediction in money per kwh"
|
||||
"- costs_amt: Costs in money amount"
|
||||
"- revenue_amt: Revenue in money amount"
|
||||
"- losses_energy_wh: Energy losses in wh"
|
||||
|
||||
@@ -15,12 +15,8 @@ from akkudoktoreos.prediction.predictionabc import PredictionProvider, Predictio
|
||||
class LoadDataRecord(PredictionRecord):
|
||||
"""Represents a load data record containing various load attributes at a specific datetime."""
|
||||
|
||||
load_mean: Optional[float] = Field(default=None, description="Predicted load mean value (W).")
|
||||
load_std: Optional[float] = Field(
|
||||
default=None, description="Predicted load standard deviation (W)."
|
||||
)
|
||||
load_mean_adjusted: Optional[float] = Field(
|
||||
default=None, description="Predicted load mean value adjusted by load measurement (W)."
|
||||
loadforecast_power_w: Optional[float] = Field(
|
||||
default=None, description="Predicted load mean value (W)."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,26 +7,97 @@ from loguru import logger
|
||||
from pydantic import Field
|
||||
|
||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||
from akkudoktoreos.prediction.loadabc import LoadProvider
|
||||
from akkudoktoreos.prediction.loadabc import LoadDataRecord, LoadProvider
|
||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
||||
|
||||
|
||||
class LoadAkkudoktorCommonSettings(SettingsBaseModel):
|
||||
"""Common settings for load data import from file."""
|
||||
|
||||
loadakkudoktor_year_energy: Optional[float] = Field(
|
||||
loadakkudoktor_year_energy_kwh: Optional[float] = Field(
|
||||
default=None, description="Yearly energy consumption (kWh).", examples=[40421]
|
||||
)
|
||||
|
||||
|
||||
class LoadAkkudoktorDataRecord(LoadDataRecord):
|
||||
"""Represents a load data record with extra fields for LoadAkkudoktor."""
|
||||
|
||||
loadakkudoktor_mean_power_w: Optional[float] = Field(
|
||||
default=None, description="Predicted load mean value (W)."
|
||||
)
|
||||
|
||||
loadakkudoktor_std_power_w: Optional[float] = Field(
|
||||
default=None, description="Predicted load standard deviation (W)."
|
||||
)
|
||||
|
||||
|
||||
class LoadAkkudoktor(LoadProvider):
|
||||
"""Fetch Load forecast data from Akkudoktor load profiles."""
|
||||
|
||||
records: list[LoadAkkudoktorDataRecord] = Field(
|
||||
default_factory=list, description="List of LoadAkkudoktorDataRecord records"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def provider_id(cls) -> str:
|
||||
"""Return the unique identifier for the LoadAkkudoktor provider."""
|
||||
return "LoadAkkudoktor"
|
||||
|
||||
def load_data(self) -> np.ndarray:
|
||||
"""Loads data from the Akkudoktor load file."""
|
||||
load_file = self.config.package_root_path.joinpath("data/load_profiles.npz")
|
||||
data_year_energy = None
|
||||
try:
|
||||
file_data = np.load(load_file)
|
||||
profile_data = np.array(
|
||||
list(zip(file_data["yearly_profiles"], file_data["yearly_profiles_std"]))
|
||||
)
|
||||
# Calculate values in W by relative profile data and yearly consumption given in kWh
|
||||
data_year_energy = (
|
||||
profile_data
|
||||
* self.config.load.provider_settings.LoadAkkudoktor.loadakkudoktor_year_energy_kwh
|
||||
* 1000
|
||||
)
|
||||
except FileNotFoundError:
|
||||
error_msg = f"Error: File {load_file} not found."
|
||||
logger.error(error_msg)
|
||||
raise FileNotFoundError(error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"An error occurred while loading data: {e}"
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
return data_year_energy
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
"""Adds the load means and standard deviations."""
|
||||
data_year_energy = self.load_data()
|
||||
# We provide prediction starting at start of day, to be compatible to old system.
|
||||
# End date for prediction is prediction hours from now.
|
||||
date = self.ems_start_datetime.start_of("day")
|
||||
end_date = self.ems_start_datetime.add(hours=self.config.prediction.hours)
|
||||
while compare_datetimes(date, end_date).lt:
|
||||
# Extract mean (index 0) and standard deviation (index 1) for the given day and hour
|
||||
# Day indexing starts at 0, -1 because of that
|
||||
hourly_stats = data_year_energy[date.day_of_year - 1, :, date.hour]
|
||||
values = {
|
||||
"loadforecast_power_w": hourly_stats[0],
|
||||
"loadakkudoktor_mean_power_w": hourly_stats[0],
|
||||
"loadakkudoktor_std_power_w": hourly_stats[1],
|
||||
}
|
||||
self.update_value(date, values)
|
||||
date += to_duration("1 hour")
|
||||
# We are working on fresh data (no cache), report update time
|
||||
self.update_datetime = to_datetime(in_timezone=self.config.general.timezone)
|
||||
|
||||
|
||||
class LoadAkkudoktorAdjusted(LoadAkkudoktor):
|
||||
"""Fetch Load forecast data from Akkudoktor load profiles with adjustment by measurements."""
|
||||
|
||||
@classmethod
|
||||
def provider_id(cls) -> str:
|
||||
"""Return the unique identifier for the LoadAkkudoktor provider."""
|
||||
return "LoadAkkudoktorAdjusted"
|
||||
|
||||
def _calculate_adjustment(self, data_year_energy: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Calculate weekday and week end adjustment from total load measurement data.
|
||||
|
||||
@@ -79,31 +150,6 @@ class LoadAkkudoktor(LoadProvider):
|
||||
|
||||
return (weekday_adjust, weekend_adjust)
|
||||
|
||||
def load_data(self) -> np.ndarray:
|
||||
"""Loads data from the Akkudoktor load file."""
|
||||
load_file = self.config.package_root_path.joinpath("data/load_profiles.npz")
|
||||
data_year_energy = None
|
||||
try:
|
||||
file_data = np.load(load_file)
|
||||
profile_data = np.array(
|
||||
list(zip(file_data["yearly_profiles"], file_data["yearly_profiles_std"]))
|
||||
)
|
||||
# Calculate values in W by relative profile data and yearly consumption given in kWh
|
||||
data_year_energy = (
|
||||
profile_data
|
||||
* self.config.load.provider_settings.LoadAkkudoktor.loadakkudoktor_year_energy
|
||||
* 1000
|
||||
)
|
||||
except FileNotFoundError:
|
||||
error_msg = f"Error: File {load_file} not found."
|
||||
logger.error(error_msg)
|
||||
raise FileNotFoundError(error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"An error occurred while loading data: {e}"
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
return data_year_energy
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
"""Adds the load means and standard deviations."""
|
||||
data_year_energy = self.load_data()
|
||||
@@ -117,8 +163,8 @@ class LoadAkkudoktor(LoadProvider):
|
||||
# Day indexing starts at 0, -1 because of that
|
||||
hourly_stats = data_year_energy[date.day_of_year - 1, :, date.hour]
|
||||
values = {
|
||||
"load_mean": hourly_stats[0],
|
||||
"load_std": hourly_stats[1],
|
||||
"loadakkudoktor_mean_power_w": hourly_stats[0],
|
||||
"loadakkudoktor_std_power_w": hourly_stats[1],
|
||||
}
|
||||
if date.day_of_week < 5:
|
||||
# Monday to Friday (0..4)
|
||||
@@ -126,7 +172,7 @@ class LoadAkkudoktor(LoadProvider):
|
||||
else:
|
||||
# Saturday, Sunday (5, 6)
|
||||
value_adjusted = hourly_stats[0] + weekend_adjust[date.hour]
|
||||
values["load_mean_adjusted"] = max(0, value_adjusted)
|
||||
values["loadforecast_power_w"] = max(0, value_adjusted)
|
||||
self.update_value(date, values)
|
||||
date += to_duration("1 hour")
|
||||
# We are working on fresh data (no cache), report update time
|
||||
|
||||
@@ -78,7 +78,7 @@ class LoadVrm(LoadProvider):
|
||||
return to_datetime(timestamp / 1000, in_timezone=self.config.general.timezone)
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
"""Fetch and store VRM load forecast as load_mean and related values."""
|
||||
"""Fetch and store VRM load forecast as loadforecast_power_w and related values."""
|
||||
start_date = self.ems_start_datetime.start_of("day")
|
||||
end_date = self.ems_start_datetime.add(hours=self.config.prediction.hours)
|
||||
start_ts = int(start_date.timestamp())
|
||||
@@ -87,19 +87,19 @@ class LoadVrm(LoadProvider):
|
||||
logger.info(f"Updating Load forecast from VRM: {start_date} to {end_date}")
|
||||
vrm_forecast_data = self._request_forecast(start_ts, end_ts)
|
||||
|
||||
load_mean_data = []
|
||||
loadforecast_power_w_data = []
|
||||
for timestamp, value in vrm_forecast_data.records.vrm_consumption_fc:
|
||||
date = self._ts_to_datetime(timestamp)
|
||||
rounded_value = round(value, 2)
|
||||
|
||||
self.update_value(
|
||||
date,
|
||||
{"load_mean": rounded_value, "load_std": 0.0, "load_mean_adjusted": rounded_value},
|
||||
{"loadforecast_power_w": rounded_value},
|
||||
)
|
||||
|
||||
load_mean_data.append((date, rounded_value))
|
||||
loadforecast_power_w_data.append((date, rounded_value))
|
||||
|
||||
logger.debug(f"Updated load_mean with {len(load_mean_data)} entries.")
|
||||
logger.debug(f"Updated loadforecast_power_w with {len(loadforecast_power_w_data)} entries.")
|
||||
self.update_datetime = to_datetime(in_timezone=self.config.general.timezone)
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,10 @@ from akkudoktoreos.prediction.elecpriceenergycharts import ElecPriceEnergyCharts
|
||||
from akkudoktoreos.prediction.elecpriceimport import ElecPriceImport
|
||||
from akkudoktoreos.prediction.feedintarifffixed import FeedInTariffFixed
|
||||
from akkudoktoreos.prediction.feedintariffimport import FeedInTariffImport
|
||||
from akkudoktoreos.prediction.loadakkudoktor import LoadAkkudoktor
|
||||
from akkudoktoreos.prediction.loadakkudoktor import (
|
||||
LoadAkkudoktor,
|
||||
LoadAkkudoktorAdjusted,
|
||||
)
|
||||
from akkudoktoreos.prediction.loadimport import LoadImport
|
||||
from akkudoktoreos.prediction.loadvrm import LoadVrm
|
||||
from akkudoktoreos.prediction.predictionabc import PredictionContainer
|
||||
@@ -94,6 +97,7 @@ class Prediction(PredictionContainer):
|
||||
FeedInTariffFixed,
|
||||
FeedInTariffImport,
|
||||
LoadAkkudoktor,
|
||||
LoadAkkudoktorAdjusted,
|
||||
LoadVrm,
|
||||
LoadImport,
|
||||
PVForecastAkkudoktor,
|
||||
@@ -112,9 +116,10 @@ elecprice_energy_charts = ElecPriceEnergyCharts()
|
||||
elecprice_import = ElecPriceImport()
|
||||
feedintariff_fixed = FeedInTariffFixed()
|
||||
feedintariff_import = FeedInTariffImport()
|
||||
load_akkudoktor = LoadAkkudoktor()
|
||||
load_vrm = LoadVrm()
|
||||
load_import = LoadImport()
|
||||
loadforecast_akkudoktor = LoadAkkudoktor()
|
||||
loadforecast_akkudoktor_adjusted = LoadAkkudoktorAdjusted()
|
||||
loadforecast_vrm = LoadVrm()
|
||||
loadforecast_import = LoadImport()
|
||||
pvforecast_akkudoktor = PVForecastAkkudoktor()
|
||||
pvforecast_vrm = PVForecastVrm()
|
||||
pvforecast_import = PVForecastImport()
|
||||
@@ -134,9 +139,10 @@ def get_prediction() -> Prediction:
|
||||
elecprice_import,
|
||||
feedintariff_fixed,
|
||||
feedintariff_import,
|
||||
load_akkudoktor,
|
||||
load_vrm,
|
||||
load_import,
|
||||
loadforecast_akkudoktor,
|
||||
loadforecast_akkudoktor_adjusted,
|
||||
loadforecast_vrm,
|
||||
loadforecast_import,
|
||||
pvforecast_akkudoktor,
|
||||
pvforecast_vrm,
|
||||
pvforecast_import,
|
||||
|
||||
@@ -7,6 +7,7 @@ from bokeh.plotting import figure
|
||||
from loguru import logger
|
||||
from monsterui.franken import (
|
||||
Card,
|
||||
CardTitle,
|
||||
Details,
|
||||
Div,
|
||||
DivLAligned,
|
||||
@@ -33,10 +34,33 @@ from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
|
||||
# bar width for 1 hour bars (time given in millseconds)
|
||||
BAR_WIDTH_1HOUR = 1000 * 60 * 60
|
||||
|
||||
|
||||
# Tailwind compatible color palette
|
||||
color_palette = {
|
||||
"red-500": "#EF4444", # red-500
|
||||
"orange-500": "#F97316", # orange-500
|
||||
"amber-500": "#F59E0B", # amber-500
|
||||
"yellow-500": "#EAB308", # yellow-500
|
||||
"lime-500": "#84CC16", # lime-500
|
||||
"green-500": "#22C55E", # green-500
|
||||
"emerald-500": "#10B981", # emerald-500
|
||||
"teal-500": "#14B8A6", # teal-500
|
||||
"cyan-500": "#06B6D4", # cyan-500
|
||||
"sky-500": "#0EA5E9", # sky-500
|
||||
"blue-500": "#3B82F6", # blue-500
|
||||
"indigo-500": "#6366F1", # indigo-500
|
||||
"violet-500": "#8B5CF6", # violet-500
|
||||
"purple-500": "#A855F7", # purple-500
|
||||
"pink-500": "#EC4899", # pink-500
|
||||
"rose-500": "#F43F5E", # rose-500
|
||||
}
|
||||
colors = list(color_palette.keys())
|
||||
|
||||
# Current state of solution displayed
|
||||
solution_visible: dict[str, bool] = {
|
||||
"pv_prediction_energy_wh": True,
|
||||
"elec_price_prediction_amt_kwh": True,
|
||||
"pv_energy_wh": True,
|
||||
"elec_price_amt_kwh": True,
|
||||
"feed_in_tariff_amt_kwh": True,
|
||||
}
|
||||
solution_color: dict[str, str] = {}
|
||||
|
||||
@@ -75,6 +99,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
Args:
|
||||
data (Optional[dict]): Incoming data containing action and category for processing.
|
||||
"""
|
||||
global colors, color_palette
|
||||
category = "solution"
|
||||
dark = False
|
||||
if data and data.get("category", None) == category:
|
||||
@@ -86,11 +111,34 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
if data and data.get("dark", None) == "true":
|
||||
dark = True
|
||||
|
||||
df = solution.data.to_dataframe()
|
||||
df = solution.solution.to_dataframe()
|
||||
if df.empty or len(df.columns) <= 1:
|
||||
raise ValueError(f"DataFrame is empty or missing plottable columns: {list(df.columns)}")
|
||||
raise ValueError(
|
||||
f"Solution DataFrame is empty or missing plottable columns: {list(df.columns)}"
|
||||
)
|
||||
if "date_time" not in df.columns:
|
||||
raise ValueError(f"DataFrame is missing column 'date_time': {list(df.columns)}")
|
||||
raise ValueError(f"Solution DataFrame is missing column 'date_time': {list(df.columns)}")
|
||||
solution_columns = list(df.columns)
|
||||
instruction_columns = [
|
||||
instruction
|
||||
for instruction in solution_columns
|
||||
if instruction.endswith("op_mode") or instruction.endswith("op_factor")
|
||||
]
|
||||
solution_columns = [x for x in solution_columns if x not in instruction_columns]
|
||||
|
||||
prediction_df = solution.prediction.to_dataframe()
|
||||
if prediction_df.empty or len(prediction_df.columns) <= 1:
|
||||
raise ValueError(
|
||||
f"Prediction DataFrame is empty or missing plottable columns: {list(prediction_df.columns)}"
|
||||
)
|
||||
if "date_time" not in prediction_df.columns:
|
||||
raise ValueError(
|
||||
f"Prediction DataFrame is missing column 'date_time': {list(prediction_df.columns)}"
|
||||
)
|
||||
prediction_columns = list(prediction_df.columns)
|
||||
|
||||
prediction_columns_to_join = prediction_df.columns.difference(df.columns)
|
||||
df = df.join(prediction_df[prediction_columns_to_join], how="inner")
|
||||
|
||||
# Remove time offset from UTC to get naive local time and make bokey plot in local time
|
||||
dst_offsets = df.index.map(lambda x: x.dst().total_seconds() / 3600)
|
||||
@@ -192,7 +240,6 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
|
||||
# Create line renderers for each column
|
||||
renderers = {}
|
||||
colors = ["black", "blue", "cyan", "green", "orange", "pink", "purple"]
|
||||
|
||||
for i, col in enumerate(sorted(df.columns)):
|
||||
# Exclude some columns that are currently not used or are covered by others
|
||||
@@ -218,24 +265,24 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
solution_visible[col] = visible
|
||||
if col in solution_color:
|
||||
color = solution_color[col]
|
||||
elif col == "pv_prediction_energy_wh":
|
||||
color = "yellow"
|
||||
elif col == "pv_energy_wh":
|
||||
color = "yellow-500"
|
||||
solution_color[col] = color
|
||||
elif col == "elec_price_prediction_amt_kwh":
|
||||
color = "red"
|
||||
elif col == "elec_price_amt_kwh":
|
||||
color = "red-500"
|
||||
solution_color[col] = color
|
||||
else:
|
||||
color = colors[i % len(colors)]
|
||||
solution_color[col] = color
|
||||
if visible:
|
||||
if col == "pv_prediction_energy_wh":
|
||||
if col == "pv_energy_wh":
|
||||
r = plot.vbar(
|
||||
x="date_time",
|
||||
top=col,
|
||||
source=source,
|
||||
width=BAR_WIDTH_1HOUR * 0.8,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
level="underlay",
|
||||
)
|
||||
elif col.endswith("energy_wh"):
|
||||
@@ -245,7 +292,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
mode="before",
|
||||
source=source,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
)
|
||||
elif col.endswith("factor"):
|
||||
r = plot.step(
|
||||
@@ -254,7 +301,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
mode="before",
|
||||
source=source,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
y_range_name="factor",
|
||||
)
|
||||
elif col.endswith("mode"):
|
||||
@@ -264,7 +311,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
mode="before",
|
||||
source=source,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
y_range_name="factor",
|
||||
)
|
||||
elif col.endswith("amt_kwh"):
|
||||
@@ -274,7 +321,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
mode="before",
|
||||
source=source,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
y_range_name="amt_kwh",
|
||||
)
|
||||
elif col.endswith("amt"):
|
||||
@@ -284,7 +331,7 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
mode="before",
|
||||
source=source,
|
||||
legend_label=col,
|
||||
color=color,
|
||||
color=color_palette[color],
|
||||
y_range_name="amt",
|
||||
)
|
||||
else:
|
||||
@@ -298,34 +345,93 @@ def SolutionCard(solution: OptimizationSolution, config: SettingsEOS, data: Opti
|
||||
|
||||
# --- CheckboxGroup to toggle datasets ---
|
||||
Checkbox = Grid(
|
||||
*[
|
||||
LabelCheckboxX(
|
||||
label=renderer,
|
||||
id=f"{renderer}-visible",
|
||||
name=f"{renderer}-visible",
|
||||
value="true",
|
||||
checked=solution_visible[renderer],
|
||||
hx_post="/eosdash/plan",
|
||||
hx_target="#page-content",
|
||||
hx_swap="innerHTML",
|
||||
hx_vals='js:{ "category": "solution", "action": "visible", "renderer": '
|
||||
+ '"'
|
||||
+ f"{renderer}"
|
||||
+ '", '
|
||||
+ '"dark": window.matchMedia("(prefers-color-scheme: dark)").matches '
|
||||
+ "}",
|
||||
lbl_cls=f"text-{solution_color[renderer]}-500",
|
||||
)
|
||||
for renderer in list(renderers.keys())
|
||||
],
|
||||
cols=2,
|
||||
Card(
|
||||
Grid(
|
||||
*[
|
||||
LabelCheckboxX(
|
||||
label=renderer,
|
||||
id=f"{renderer}-visible",
|
||||
name=f"{renderer}-visible",
|
||||
value="true",
|
||||
checked=solution_visible[renderer],
|
||||
hx_post="/eosdash/plan",
|
||||
hx_target="#page-content",
|
||||
hx_swap="innerHTML",
|
||||
hx_vals='js:{ "category": "solution", "action": "visible", "renderer": '
|
||||
+ '"'
|
||||
+ f"{renderer}"
|
||||
+ '", '
|
||||
+ '"dark": window.matchMedia("(prefers-color-scheme: dark)").matches '
|
||||
+ "}",
|
||||
lbl_cls=f"text-{solution_color[renderer]}",
|
||||
)
|
||||
for renderer in list(renderers.keys())
|
||||
if renderer in prediction_columns
|
||||
],
|
||||
cols=2,
|
||||
),
|
||||
header=CardTitle("Prediction"),
|
||||
),
|
||||
Card(
|
||||
Grid(
|
||||
*[
|
||||
LabelCheckboxX(
|
||||
label=renderer,
|
||||
id=f"{renderer}-visible",
|
||||
name=f"{renderer}-visible",
|
||||
value="true",
|
||||
checked=solution_visible[renderer],
|
||||
hx_post="/eosdash/plan",
|
||||
hx_target="#page-content",
|
||||
hx_swap="innerHTML",
|
||||
hx_vals='js:{ "category": "solution", "action": "visible", "renderer": '
|
||||
+ '"'
|
||||
+ f"{renderer}"
|
||||
+ '", '
|
||||
+ '"dark": window.matchMedia("(prefers-color-scheme: dark)").matches '
|
||||
+ "}",
|
||||
lbl_cls=f"text-{solution_color[renderer]}",
|
||||
)
|
||||
for renderer in list(renderers.keys())
|
||||
if renderer in solution_columns
|
||||
],
|
||||
cols=2,
|
||||
),
|
||||
header=CardTitle("Solution"),
|
||||
),
|
||||
Card(
|
||||
Grid(
|
||||
*[
|
||||
LabelCheckboxX(
|
||||
label=renderer,
|
||||
id=f"{renderer}-visible",
|
||||
name=f"{renderer}-visible",
|
||||
value="true",
|
||||
checked=solution_visible[renderer],
|
||||
hx_post="/eosdash/plan",
|
||||
hx_target="#page-content",
|
||||
hx_swap="innerHTML",
|
||||
hx_vals='js:{ "category": "solution", "action": "visible", "renderer": '
|
||||
+ '"'
|
||||
+ f"{renderer}"
|
||||
+ '", '
|
||||
+ '"dark": window.matchMedia("(prefers-color-scheme: dark)").matches '
|
||||
+ "}",
|
||||
lbl_cls=f"text-{solution_color[renderer]}",
|
||||
)
|
||||
for renderer in list(renderers.keys())
|
||||
if renderer in instruction_columns
|
||||
],
|
||||
cols=2,
|
||||
),
|
||||
header=CardTitle("Instruction"),
|
||||
),
|
||||
cols=1,
|
||||
)
|
||||
|
||||
return Grid(
|
||||
Bokeh(plot),
|
||||
Card(
|
||||
Checkbox,
|
||||
),
|
||||
Checkbox,
|
||||
cls="w-full space-y-3 space-x-3",
|
||||
)
|
||||
|
||||
|
||||
@@ -153,9 +153,9 @@ def WeatherIrradianceForecast(
|
||||
def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dark: bool) -> FT:
|
||||
source = ColumnDataSource(predictions)
|
||||
provider = config["load"]["provider"]
|
||||
if provider == "LoadAkkudoktor":
|
||||
if provider == "LoadAkkudoktorAdjusted":
|
||||
year_energy = config["load"]["provider_settings"]["LoadAkkudoktor"][
|
||||
"loadakkudoktor_year_energy"
|
||||
"loadakkudoktor_year_energy_kwh"
|
||||
]
|
||||
provider = f"{provider}, {year_energy} kWh"
|
||||
|
||||
@@ -168,8 +168,8 @@ def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dar
|
||||
height=400,
|
||||
)
|
||||
# Add secondary y-axis for stddev
|
||||
stddev_min = predictions["load_std"].min()
|
||||
stddev_max = predictions["load_std"].max()
|
||||
stddev_min = predictions["loadakkudoktor_std_power_w"].min()
|
||||
stddev_max = predictions["loadakkudoktor_std_power_w"].max()
|
||||
plot.extra_y_ranges["stddev"] = Range1d(start=stddev_min - 5, end=stddev_max + 5)
|
||||
y2_axis = LinearAxis(y_range_name="stddev", axis_label="Load Standard Deviation [W]")
|
||||
y2_axis.axis_label_text_color = "green"
|
||||
@@ -177,21 +177,21 @@ def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dar
|
||||
|
||||
plot.line(
|
||||
"date_time",
|
||||
"load_mean",
|
||||
"loadforecast_power_w",
|
||||
source=source,
|
||||
legend_label="Load mean value",
|
||||
legend_label="Load forcast value (adjusted by measurement)",
|
||||
color="red",
|
||||
)
|
||||
plot.line(
|
||||
"date_time",
|
||||
"load_mean_adjusted",
|
||||
"loadakkudoktor_mean_power_w",
|
||||
source=source,
|
||||
legend_label="Load adjusted by measurement",
|
||||
legend_label="Load mean value",
|
||||
color="blue",
|
||||
)
|
||||
plot.line(
|
||||
"date_time",
|
||||
"load_std",
|
||||
"loadakkudoktor_std_power_w",
|
||||
source=source,
|
||||
legend_label="Load standard deviation",
|
||||
color="green",
|
||||
@@ -233,9 +233,9 @@ def Prediction(eos_host: str, eos_port: Union[str, int], data: Optional[dict] =
|
||||
"weather_ghi",
|
||||
"weather_dni",
|
||||
"weather_dhi",
|
||||
"load_mean",
|
||||
"load_std",
|
||||
"load_mean_adjusted",
|
||||
"loadforecast_power_w",
|
||||
"loadakkudoktor_std_power_w",
|
||||
"loadakkudoktor_mean_power_w",
|
||||
],
|
||||
}
|
||||
result = requests.get(f"{server}/v1/prediction/dataframe", params=params, timeout=10)
|
||||
|
||||
@@ -1243,7 +1243,7 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
||||
filled with the first available prediction value.
|
||||
|
||||
Note:
|
||||
Use '/v1/prediction/list?key=load_mean_adjusted' instead.
|
||||
Use '/v1/prediction/list?key=loadforecast_power_w' instead.
|
||||
Load energy meter readings to be added to EOS measurement by:
|
||||
'/v1/measurement/value' or
|
||||
'/v1/measurement/series' or
|
||||
@@ -1255,10 +1255,10 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
||||
"hours": request.hours,
|
||||
},
|
||||
"load": {
|
||||
"provider": "LoadAkkudoktor",
|
||||
"provider": "LoadAkkudoktorAdjusted",
|
||||
"provider_settings": {
|
||||
"LoadAkkudoktor": {
|
||||
"loadakkudoktor_year_energy": request.year_energy,
|
||||
"loadakkudoktor_year_energy_kwh": request.year_energy,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1317,7 +1317,7 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
||||
end_datetime = start_datetime.add(days=2)
|
||||
try:
|
||||
prediction_list = prediction_eos.key_to_array(
|
||||
key="load_mean_adjusted",
|
||||
key="loadforecast_power_w",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
).tolist()
|
||||
@@ -1347,14 +1347,14 @@ async def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
||||
Set LoadAkkudoktor as provider, then update data with
|
||||
'/v1/prediction/update'
|
||||
and then request data with
|
||||
'/v1/prediction/list?key=load_mean' instead.
|
||||
'/v1/prediction/list?key=loadforecast_power_w' instead.
|
||||
"""
|
||||
settings = SettingsEOS(
|
||||
load=LoadCommonSettings(
|
||||
provider="LoadAkkudoktor",
|
||||
provider_settings=LoadCommonProviderSettings(
|
||||
LoadAkkudoktor=LoadAkkudoktorCommonSettings(
|
||||
loadakkudoktor_year_energy=year_energy / 1000, # Convert to kWh
|
||||
loadakkudoktor_year_energy_kwh=year_energy / 1000, # Convert to kWh
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -1378,7 +1378,7 @@ async def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
||||
end_datetime = start_datetime.add(days=2)
|
||||
try:
|
||||
prediction_list = prediction_eos.key_to_array(
|
||||
key="load_mean",
|
||||
key="loadforecast_power_w",
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
).tolist()
|
||||
|
||||
Reference in New Issue
Block a user