diff --git a/antarest/study/storage/rawstudy/model/filesystem/matrix/input_series_matrix.py b/antarest/study/storage/rawstudy/model/filesystem/matrix/input_series_matrix.py index d2ac83c19c..e23cb9842a 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/matrix/input_series_matrix.py +++ b/antarest/study/storage/rawstudy/model/filesystem/matrix/input_series_matrix.py @@ -150,3 +150,7 @@ def get_file_content(self) -> OriginalFile: else: content = self.config.path.read_bytes() return OriginalFile(content=content, suffix=suffix, filename=filename) + + @override + def get_default_empty_matrix(self) -> t.Optional[npt.NDArray[np.float64]]: + return self.default_empty diff --git a/antarest/study/storage/rawstudy/model/filesystem/matrix/matrix.py b/antarest/study/storage/rawstudy/model/filesystem/matrix/matrix.py index 7897715f92..6009fef526 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/matrix/matrix.py +++ b/antarest/study/storage/rawstudy/model/filesystem/matrix/matrix.py @@ -16,7 +16,9 @@ from pathlib import Path from typing import List, Optional, Union, cast +import numpy as np import pandas as pd +from numpy import typing as npt from typing_extensions import override from antarest.core.model import JSON @@ -127,16 +129,25 @@ def load( formatted: bool = True, ) -> Union[bytes, JSON]: file_path, tmp_dir = self._get_real_file_path() - if not formatted: - if file_path.exists(): - return file_path.read_bytes() + if formatted: + return self.parse_as_json(file_path) + + if not file_path.exists(): logger.warning(f"Missing file {self.config.path}") if tmp_dir: tmp_dir.cleanup() return b"" - return self.parse_as_json(file_path) + file_content = file_path.read_bytes() + if file_content != b"": + return file_content + + # If the content is empty, we should return the default matrix to do the same as `parse_as_json()` + default_matrix = self.get_default_empty_matrix() + if default_matrix is None: + return b"" + return default_matrix.tobytes() @abstractmethod def parse_as_json(self, file_path: Optional[Path] = None) -> JSON: @@ -145,6 +156,13 @@ def parse_as_json(self, file_path: Optional[Path] = None) -> JSON: """ raise NotImplementedError() + @abstractmethod + def get_default_empty_matrix(self) -> Optional[npt.NDArray[np.float64]]: + """ + Returns the default matrix to return when the existing one is empty + """ + raise NotImplementedError() + @override def dump( self, diff --git a/tests/storage/integration/test_STA_mini.py b/tests/storage/integration/test_STA_mini.py index 7b1c1ee690..a7b36555e8 100644 --- a/tests/storage/integration/test_STA_mini.py +++ b/tests/storage/integration/test_STA_mini.py @@ -18,6 +18,7 @@ from typing import Union from unittest.mock import Mock +import numpy as np import pytest from fastapi import FastAPI from starlette.testclient import TestClient @@ -172,12 +173,9 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) @pytest.mark.integration_test @pytest.mark.parametrize( - "url, expected_output", + "url, expected_output, formatted", [ - ( - f"/v1/studies/{UUID}/raw?path=input/bindingconstraints/bindingconstraints", - {}, - ), + (f"/v1/studies/{UUID}/raw?path=input/bindingconstraints/bindingconstraints", {}, True), ( f"/v1/studies/{UUID}/raw?path=input/hydro/series/de/mod", { @@ -185,24 +183,17 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(365)), "data": [[0.0]] * 365, }, + True, ), - ( - f"/v1/studies/{UUID}/raw?path=input/areas/list", - ["DE", "ES", "FR", "IT"], - ), - ( - f"/v1/studies/{UUID}/raw?path=input/areas/sets/all areas/output", - False, - ), + (f"/v1/studies/{UUID}/raw?path=input/areas/list", ["DE", "ES", "FR", "IT"], True), + (f"/v1/studies/{UUID}/raw?path=input/areas/sets/all areas/output", False, True), ( f"/v1/studies/{UUID}/raw?path=input/areas/de/optimization/nodal optimization/spread-spilled-energy-cost", 0, + True, ), - (f"/v1/studies/{UUID}/raw?path=input/areas/de/ui/layerX/0", 1), - ( - f"/v1/studies/{UUID}/raw?path=input/hydro/allocation/de/[allocation]/de", - 1, - ), + (f"/v1/studies/{UUID}/raw?path=input/areas/de/ui/layerX/0", 1, True), + (f"/v1/studies/{UUID}/raw?path=input/hydro/allocation/de/[allocation]/de", 1, True), ( f"/v1/studies/{UUID}/raw?path=input/hydro/common/capacity/reservoir_fr", { @@ -210,6 +201,7 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(365)), "data": [[0, 0.5, 1]] * 365, }, + True, ), ( f"/v1/studies/{UUID}/raw?path=input/thermal/series/fr/05_nuclear/series", @@ -218,35 +210,19 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[2000]] * 8760, }, + True, ), - ( - f"/v1/studies/{UUID}/raw?path=input/hydro/prepro/correlation/general/mode", - "annual", - ), - ( - f"/v1/studies/{UUID}/raw?path=input/hydro/prepro/fr/prepro/prepro/intermonthly-correlation", - 0.5, - ), + (f"/v1/studies/{UUID}/raw?path=input/hydro/prepro/correlation/general/mode", "annual", True), + (f"/v1/studies/{UUID}/raw?path=input/hydro/prepro/fr/prepro/prepro/intermonthly-correlation", 0.5, True), ( f"/v1/studies/{UUID}/raw?path=input/hydro/prepro/fr/energy", {"data": [[]], "index": [0], "columns": []}, - ), - ( - f"/v1/studies/{UUID}/raw?path=input/hydro/hydro/inter-monthly-breakdown/fr", - 1, - ), - ( - f"/v1/studies/{UUID}/raw?path=input/thermal/areas/unserverdenergycost/de", - 3000.0, - ), - ( - f"/v1/studies/{UUID}/raw?path=input/thermal/clusters/fr/list/05_nuclear/marginal-cost", - 50, - ), - ( - f"/v1/studies/{UUID}/raw?path=input/links/fr/properties/it/hurdles-cost", True, ), + (f"/v1/studies/{UUID}/raw?path=input/hydro/hydro/inter-monthly-breakdown/fr", 1, True), + (f"/v1/studies/{UUID}/raw?path=input/thermal/areas/unserverdenergycost/de", 3000.0, True), + (f"/v1/studies/{UUID}/raw?path=input/thermal/clusters/fr/list/05_nuclear/marginal-cost", 50, True), + (f"/v1/studies/{UUID}/raw?path=input/links/fr/properties/it/hurdles-cost", True, True), ( f"/v1/studies/{UUID}/raw?path=input/links/fr/it", { @@ -254,11 +230,9 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[100000, 100000, 0.01, 0.01, 0, 0, 0, 0]] * 8760, }, + True, ), - ( - f"/v1/studies/{UUID}/raw?path=input/load/prepro/fr/k", - {"data": [[]], "index": [0], "columns": []}, - ), + (f"/v1/studies/{UUID}/raw?path=input/load/prepro/fr/k", {"data": [[]], "index": [0], "columns": []}, True), ( f"/v1/studies/{UUID}/raw?path=input/load/series", { @@ -267,6 +241,7 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "load_fr": "matrixfile://load_fr.txt", "load_it": "matrixfile://load_it.txt", }, + True, ), ( f"/v1/studies/{UUID}/raw?path=input/load/series/load_fr", @@ -275,14 +250,23 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[i % 168 * 100] for i in range(8760)], }, + True, ), - ( + pytest.param( f"/v1/studies/{UUID}/raw?path=input/misc-gen/miscgen-fr", { "columns": [0, 1, 2, 3, 4, 5, 6, 7], "index": list(range(8760)), "data": [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] * 8760, }, + True, + id="empty_matrix_formatted", + ), + pytest.param( + f"/v1/studies/{UUID}/raw?path=input/misc-gen/miscgen-fr", + np.array([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]] * 8760).tobytes(), + False, + id="empty_matrix_unformatted", ), ( f"/v1/studies/{UUID}/raw?path=input/reserves/fr", @@ -291,11 +275,9 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[0.0]] * 8760, }, + True, ), - ( - f"/v1/studies/{UUID}/raw?path=input/solar/prepro/fr/k", - {"data": [[]], "index": [0], "columns": []}, - ), + (f"/v1/studies/{UUID}/raw?path=input/solar/prepro/fr/k", {"data": [[]], "index": [0], "columns": []}, True), ( f"/v1/studies/{UUID}/raw?path=input/solar/series/solar_fr", { @@ -303,11 +285,9 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[0.0]] * 8760, }, + True, ), - ( - f"/v1/studies/{UUID}/raw?path=input/wind/prepro/fr/k", - {"data": [[]], "index": [0], "columns": []}, - ), + (f"/v1/studies/{UUID}/raw?path=input/wind/prepro/fr/k", {"data": [[]], "index": [0], "columns": []}, True), ( f"/v1/studies/{UUID}/raw?path=input/wind/series/wind_fr", { @@ -315,15 +295,12 @@ def test_sta_mini_study_antares(storage_service, url: str, expected_output: str) "index": list(range(8760)), "data": [[0.0]] * 8760, }, + True, ), ], ) -def test_sta_mini_input(storage_service, url: str, expected_output: dict): - assert_with_errors( - storage_service=storage_service, - url=url, - expected_output=expected_output, - ) +def test_sta_mini_input(storage_service, url: str, expected_output: dict, formatted: bool): + assert_with_errors(storage_service=storage_service, url=url, expected_output=expected_output, formatted=formatted) @pytest.mark.integration_test diff --git a/tests/storage/repository/filesystem/matrix/test_matrix_node.py b/tests/storage/repository/filesystem/matrix/test_matrix_node.py index 38dce45486..936a54b6ea 100644 --- a/tests/storage/repository/filesystem/matrix/test_matrix_node.py +++ b/tests/storage/repository/filesystem/matrix/test_matrix_node.py @@ -14,7 +14,9 @@ from typing import List, Optional from unittest.mock import Mock +import numpy as np import pandas as pd # type: ignore +from numpy import typing as npt from antarest.core.model import JSON from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig @@ -41,6 +43,9 @@ def __init__(self, context: ContextServer, config: FileStudyTreeConfig) -> None: def parse_as_json(self, file_path: Optional[Path] = None) -> JSON: return MOCK_MATRIX_JSON + def get_default_empty_matrix(self) -> Optional[npt.NDArray[np.float64]]: + pass + def check_errors(self, data: str, url: Optional[List[str]] = None, raising: bool = False) -> List[str]: pass # not used