Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c6d8899
add thermal test that fails for both storages
MartinBelthle Aug 20, 2026
a05e743
remove dead code
MartinBelthle Aug 20, 2026
086671a
prepare code in common
MartinBelthle Aug 20, 2026
0b07434
continue simplification
MartinBelthle Aug 20, 2026
1b88432
continue removing
MartinBelthle Aug 20, 2026
8402dae
continue
MartinBelthle Aug 20, 2026
712aeea
centralize logic
MartinBelthle Aug 20, 2026
995f7a2
fix mypy issue
MartinBelthle Aug 20, 2026
2740595
move code
MartinBelthle Aug 20, 2026
7985563
add test for parquet too
MartinBelthle Aug 20, 2026
a74f298
continue
MartinBelthle Aug 20, 2026
2393638
slight change of logic
MartinBelthle Aug 20, 2026
98a5e51
f
MartinBelthle Aug 21, 2026
07bd1c0
continue fixing tests
MartinBelthle Aug 21, 2026
16537db
continue
MartinBelthle Aug 21, 2026
c9d065b
adapt code
MartinBelthle Aug 21, 2026
1d76736
adapt test
MartinBelthle Aug 21, 2026
38b7e4f
add failing test for thermal
MartinBelthle Aug 21, 2026
1aa63da
fix first issue for thermal
MartinBelthle Aug 21, 2026
2c885d6
fix the issue for sts too
MartinBelthle Aug 21, 2026
697d66c
add sts test that fails for the moment
MartinBelthle Aug 21, 2026
e4ec053
fix the issue ?
MartinBelthle Aug 21, 2026
7a4f654
fixed issue
MartinBelthle Aug 21, 2026
307e924
adapt code
MartinBelthle Aug 21, 2026
ea9a9d1
put code in common
MartinBelthle Aug 21, 2026
daacb36
added a test
MartinBelthle Aug 21, 2026
bccc373
continue
MartinBelthle Aug 21, 2026
07ea48a
code the rest of the issue
MartinBelthle Aug 21, 2026
76e19a1
fix final test
MartinBelthle Aug 21, 2026
4c8c69c
resolve naming comment
MartinBelthle Aug 24, 2026
d49d360
resolve copilot comment
MartinBelthle Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions antarest/study/dao/api/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def check_thermal_symmetries_integrity(study_dao: "StudyDao", new_symmetries: Th

for area_id, value in new_symmetries.items():
# Handle the case where no symmetries are given. Means we only want to clear them all.
if all(symmetries == [[]] for symmetries in value.values()):
if not (any(symmetry for symmetry in value.values())):
continue

if area_id not in existing_certifications:
Expand Down Expand Up @@ -77,7 +77,7 @@ def check_st_storage_symmetries_integrity(

for area_id, value in new_symmetries.items():
# Handle the case where no symmetries are given. Means we only want to clear them all.
if all(symmetries == [[]] for symmetries in value.values()):
if not (any(symmetry for symmetry in value.values())):
continue

if area_id not in existing_certifications:
Expand All @@ -102,7 +102,8 @@ def check_st_storage_symmetries_integrity(


def remove_reserve_symmetries_by_cascade(
symmetries_dict: dict[str, ReserveSymmetries], reserve_ids_to_remove: set[ReserveDefinitionId]
symmetries_dict: dict[str, ReserveSymmetries],
reserves_to_remove: dict[str, set[ReserveDefinitionId]] | set[ReserveDefinitionId],
) -> dict[str, ReserveSymmetries] | None:
"""
When removing a reserve, we should also remove it from the symmetries.
Expand All @@ -111,9 +112,17 @@ def remove_reserve_symmetries_by_cascade(
The updated symmetries dictionary or None if no symmetries were updated.
"""
should_update_symmetries = False
for symmetries in symmetries_dict.values():
for object_id, symmetries in symmetries_dict.items():
for i, symmetry in enumerate(symmetries):
symmetries[i] = [reserve_id for reserve_id in symmetry if reserve_id not in reserve_ids_to_remove]
new_symmetry = []
for reserve_id in symmetry:
if isinstance(reserves_to_remove, set):
if reserve_id not in reserves_to_remove:
new_symmetry.append(reserve_id)
else:
if reserve_id not in reserves_to_remove.get(object_id, []):
new_symmetry.append(reserve_id)
symmetries[i] = new_symmetry
if len(symmetries[i]) != len(symmetry):
should_update_symmetries = True
if len(symmetries[i]) == 1:
Expand Down
98 changes: 95 additions & 3 deletions antarest/study/dao/database/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,28 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
from typing import TYPE_CHECKING
import json
from enum import StrEnum
from typing import TYPE_CHECKING, Any, Sequence, cast

from sqlalchemy import Table, select
from sqlalchemy import Row, Table, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from antarest.core.exceptions import AreaNotFound
from antarest.core.utils.sql_utils import upsert_multiple
from antarest.dbmodel import get_row_representation_as_dict
from antarest.study.business.model.area_properties_model import FILTER_OPTIONS, FrequencyFilter, sort_filter_options
from antarest.study.dao.common import AreaSeriesMapping
from antarest.study.business.model.reserve_certification_model import (
ReserveCertification,
)
from antarest.study.business.model.reserve_symmetries_model import ReserveSymmetries
from antarest.study.dao.common import AreaSeriesMapping, ReserveSymmetriesMapping
from antarest.study.dao.database.models.area import AREA_TABLE
from antarest.study.dao.database.models.st_storage_reserve_certification import ST_STORAGE_RESERVE_CERTIFICATION_TABLE
from antarest.study.dao.database.models.st_storage_reserve_symmetries import ST_STORAGE_RESERVE_SYMMETRIES_TABLE
from antarest.study.dao.database.models.thermal_reserve_certification import THERMAL_RESERVE_CERTIFICATION_TABLE
from antarest.study.dao.database.models.thermal_reserve_symmetries import THERMAL_RESERVE_SYMMETRIES_TABLE

if TYPE_CHECKING:
from antarest.study.dao.database.database_study_dao import DatabaseStudyDao
Expand All @@ -37,6 +48,14 @@ def area_exists(session: Session, study_data_id: int, area_id: str) -> bool:
return session.execute(stmt).fetchone() is not None


def validate_areas_exist(session: Session, study_data_id: int, area_ids: set[str]) -> None:
stmt = select(AREA_TABLE.c.area_id).where((AREA_TABLE.c.study_data_id == study_data_id))
rows = session.execute(stmt).fetchall()
existing_area_ids = {row.area_id for row in rows}
if invalid_areas := area_ids - existing_area_ids:
raise AreaNotFound(*invalid_areas)


def save_area_matrix(dao: "DatabaseStudyDao", series: AreaSeriesMapping, table: Table) -> None:
session = dao._db_session
study_data_id = dao._study_data_id
Expand Down Expand Up @@ -87,3 +106,76 @@ def serialize_frequency_filters(encoded_value: set[FrequencyFilter]) -> str:
if isinstance(encoded_value, str):
return encoded_value
return ", ".join(sort_filter_options(encoded_value))


"""
Reserve types
"""


def _convert_row_to_symmetries(row: Row[Any]) -> ReserveSymmetries:
return cast(ReserveSymmetries, json.loads(row.symmetries))


class ReserveObjectType(StrEnum):
THERMAL = "thermal"
ST_STORAGE = "st_storage"

def _db_key(self) -> str:
if self == ReserveObjectType.THERMAL:
return "thermal_id"
else:
return "st_storage_id"

def db_symmetry_table(self) -> Table:
if self == ReserveObjectType.THERMAL:
return THERMAL_RESERVE_SYMMETRIES_TABLE
else:
return ST_STORAGE_RESERVE_SYMMETRIES_TABLE

def db_certification_table(self) -> Table:
if self == ReserveObjectType.THERMAL:
return THERMAL_RESERVE_CERTIFICATION_TABLE
else:
return ST_STORAGE_RESERVE_CERTIFICATION_TABLE

def convert_symmetry_to_row(
self, study_data_id: int, area_id: str, object_id: str, symmetries: ReserveSymmetries
) -> dict[str, Any]:
return {
"study_data_id": study_data_id,
"area_id": area_id,
"symmetries": json.dumps([symmetry for symmetry in symmetries if symmetry]),
self._db_key(): object_id,
}

def convert_all_rows_to_symmetries(self, rows: Sequence[Row[Any]]) -> dict[str, ReserveSymmetries]:
result = {}
for row in rows:
row_as_dict = get_row_representation_as_dict(row)
result[row_as_dict[self._db_key()]] = _convert_row_to_symmetries(row)
return result

def convert_all_rows_to_dict_of_symmetries(self, rows: Sequence[Row[Any]]) -> ReserveSymmetriesMapping:
result: ReserveSymmetriesMapping = {}
for row in rows:
row_as_dict = get_row_representation_as_dict(row)
result.setdefault(row.area_id, {})[row_as_dict[self._db_key()]] = _convert_row_to_symmetries(row)
return result

def convert_certification_to_row(
self, study_data_id: int, area_id: str, object_id: str, reserve_id: str, certification: ReserveCertification
) -> dict[str, Any]:
return {
"study_data_id": study_data_id,
"area_id": area_id,
"reserve_id": reserve_id,
self._db_key(): object_id,
**certification.model_dump(),
}

def convert_row_to_mapping(self, row: Row[Any]) -> dict[str, Any]:
data = get_row_representation_as_dict(row)
for key in ("study_data_id", "area_id", self._db_key(), "reserve_id"):
del data[key]
return data
7 changes: 2 additions & 5 deletions antarest/study/dao/database/database_renewable_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from typing_extensions import override

from antarest.core.exceptions import (
AreaNotFound,
RenewableClusterNotFound,
RenewableClustersNotFound,
)
Expand All @@ -35,7 +34,7 @@
)
from antarest.study.dao.api.renewable_dao import RenewableDao
from antarest.study.dao.common import AreaId, RenewableId, RenewableSeriesMapping
from antarest.study.dao.database.common import validate_area_exists
from antarest.study.dao.database.common import validate_area_exists, validate_areas_exist
from antarest.study.dao.database.dao_context import DatabaseDaoBase
from antarest.study.dao.database.models.renewable import RENEWABLE_CLUSTER_TABLE, RENEWABLE_SERIES_TABLE
from antarest.study.storage.rawstudy.model.filesystem.matrix.simulator_default import default_scenario_hourly
Expand Down Expand Up @@ -63,9 +62,7 @@ def _raise_the_right_renewable_exception(
self, data: dict[AreaId, list[RenewableId]], exc: IntegrityError | None = None
) -> NoReturn:
# Checks if some areas are missing
existing_ids = set(self.get_impl().get_all_area_ids())
if invalid_areas := set(data) - existing_ids:
raise AreaNotFound(*invalid_areas)
validate_areas_exist(self._db_session, self._study_data_id, set(data))

# Means the issue lies in the renewables
all_existing_renewables = self.get_all_renewables()
Expand Down
Loading
Loading