fix(genetic): allow optimization without fresh SoC (#1403)

This commit is contained in:
Andreas
2026-10-13 19:02:19 +02:00
committed by GitHub
parent fab3e17446
commit 49ea062dc8
8 changed files with 103 additions and 26 deletions
@@ -3,6 +3,7 @@
import math
from typing import Annotated, Any, Optional
from loguru import logger
from pydantic import AliasChoices, Field, field_validator
from akkudoktoreos.core.coreabc import (
@@ -48,7 +49,11 @@ class RuntimeForecasts(GeneticParametersBaseModel):
class ConfigOptimizationRequest(
ConfigMixin, MeasurementMixin, PredictionMixin, GeneticParametersBaseModel
):
"""Supply observations while hardware, tariffs and scheduling remain in config."""
"""Supply observations while hardware, tariffs and scheduling remain in config.
Explicit SoC values take precedence over fresh measurements. Without a usable
measurement, assume 0% initial SoC for the affected device and continue.
"""
soc: dict[str, Annotated[int, Field(ge=0, le=100)]] = Field(default_factory=dict)
forecasts: RuntimeForecasts = Field(default_factory=RuntimeForecasts)
@@ -115,9 +120,13 @@ class ConfigOptimizationRequest(
raise ValueError("Invalid or stale SoC factor")
return int(value * 100)
except (ValueError, KeyError) as exc:
raise ValueError(
f"Fresh SoC missing for {device.device_id}; supply soc or a measurement."
) from exc
logger.warning(
"No usable fresh SoC for {}; assuming 0% initial SoC for this "
"optimization (not a measurement): {}",
device.device_id,
exc,
)
return 0
battery = None
if groups["batteries"]:
@@ -154,7 +154,10 @@ class GeneticCommonSettings(SettingsBaseModel):
measurement_max_age_seconds: int = Field(
default=300,
gt=0,
description="Maximum age of SoC measurements for configuration-based optimization [s].",
description=(
"Maximum age of SoC measurements used for configuration-based optimization [s]. "
"Without an explicit SoC or usable measurement, the initial SoC defaults to 0%."
),
)
tail_horizon_hours: int = Field(
+4 -2
View File
@@ -2224,7 +2224,9 @@ async def fastapi_optimize_config(
Static settings belong in configuration; query overrides are rejected.
Forecast arrays start at local
midnight and contain Wh per configured GENETIC slot; prices are currency/Wh.
An empty body uses configured providers and fresh measured states of charge.
An empty body uses configured providers. Explicit SoC values take precedence over
fresh measured states of charge; missing usable measurements default to an assumed
initial SoC of 0% without blocking a plan.
The deprecated /optimize endpoint continues to run hourly GENETIC0.
"""
if request.query_params:
@@ -2241,7 +2243,7 @@ async def fastapi_optimize_config(
raise EOSProblem(
status=503,
title="GENETIC optimization failed",
detail="No new solution was produced. Check configured devices, fresh SoC and forecast coverage.",
detail="No new solution was produced. Check configured devices and forecast coverage.",
)
if not isinstance(solution, GeneticSolution):
raise EOSProblem(