mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-10 08:26:38 +00:00
fix(config): preserve device identities and storage costs during migration
This commit is contained in:
@@ -47,15 +47,20 @@ def _list_to_device_dict(
|
||||
"""
|
||||
|
||||
def _transform(value: Any) -> Any:
|
||||
if not isinstance(value, list):
|
||||
return value # already a dict or something unexpected – leave as-is
|
||||
if not isinstance(value, (list, dict)):
|
||||
return value
|
||||
result: Dict[str, Any] = {}
|
||||
for i, item in enumerate(value):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
key = item.get("device_id") or f"{prefix}{i}"
|
||||
# Ensure device_id is stored inside the dict so Pydantic can validate it
|
||||
entries = enumerate(value) if isinstance(value, list) else value.items()
|
||||
for index, original in entries:
|
||||
if not isinstance(original, dict):
|
||||
raise ValueError("Device settings must be an object")
|
||||
item = dict(original)
|
||||
key = (item.get("device_id") or f"{prefix}{index}") if isinstance(value, list) else index
|
||||
if key in result:
|
||||
raise ValueError(f"Duplicate device_id: {key!r}")
|
||||
item.setdefault("device_id", key)
|
||||
if "levelized_cost_of_storage_kwh" in item:
|
||||
item.setdefault("levelized_cost_of_storage_amt_kwh", item.pop("levelized_cost_of_storage_kwh"))
|
||||
result[key] = item
|
||||
return result
|
||||
|
||||
@@ -190,7 +195,7 @@ MIGRATION_MAP: Dict[
|
||||
"optimization/interval": None,
|
||||
"optimization/horizon_hours": "optimization/genetic0/horizon_hours",
|
||||
"optimization/ev_available_charge_rates_percent": (
|
||||
"devices/electric_vehicles/0/charge_rates",
|
||||
"devices/electric_vehicles/ev0/charge_rates",
|
||||
lambda v: [x / 100 for x in v],
|
||||
),
|
||||
"optimization/hours": "optimization/genetic0/horizon_hours",
|
||||
@@ -288,6 +293,16 @@ def migrate_config_data(config_data: Dict[str, Any]) -> "SettingsEOSDefaults":
|
||||
try:
|
||||
if transform:
|
||||
old_value = transform(old_value)
|
||||
if old_path == "optimization/ev_available_charge_rates_percent":
|
||||
from akkudoktoreos.devices.settings.batterysettings import BatteriesCommonSettings
|
||||
|
||||
vehicles = new_config.devices.electric_vehicles
|
||||
if not vehicles:
|
||||
new_config.devices.electric_vehicles = {
|
||||
"ev0": BatteriesCommonSettings(device_id="ev0")
|
||||
}
|
||||
device_id = next(iter(new_config.devices.electric_vehicles))
|
||||
new_path = f"devices/electric_vehicles/{device_id}/charge_rates"
|
||||
new_config.set_nested_value(new_path, old_value)
|
||||
migrated_source_paths.add(old_path.strip("/"))
|
||||
mapped_count += 1
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
from typing import Any, Optional, TextIO, cast
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import Field, computed_field, model_validator
|
||||
from pydantic import Field, computed_field, field_validator, model_validator
|
||||
|
||||
from akkudoktoreos.config.configabc import ConfigScope, SettingsBaseModel
|
||||
from akkudoktoreos.core.cache import CacheFileStore
|
||||
@@ -113,6 +113,25 @@ class DevicesCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
@field_validator("batteries", "electric_vehicles", "inverters", "home_appliances", mode="before")
|
||||
@classmethod
|
||||
def validate_device_ids(cls, value: Any) -> Any:
|
||||
"""Keep map keys and device identities consistent without mutating callers."""
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
result = {}
|
||||
for key, device in value.items():
|
||||
if isinstance(device, dict):
|
||||
device = dict(device)
|
||||
device.setdefault("device_id", key)
|
||||
device_id = device["device_id"]
|
||||
else:
|
||||
device_id = device.device_id
|
||||
if device_id != key:
|
||||
raise ValueError(f"device_id {device_id!r} must match map key {key!r}")
|
||||
result[key] = device
|
||||
return result
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def measurement_keys(self) -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user