From 426358579e41b4e963fe5f4f05412ae140eee90e Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Wed, 31 Jan 2024 16:42:21 +0100 Subject: [PATCH 01/10] feat(api-hydro): add inflow structure form service and routes --- .../study/business/areas/hydro_management.py | 44 +++++++++++++++++++ antarest/study/web/study_data_blueprint.py | 41 ++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/antarest/study/business/areas/hydro_management.py b/antarest/study/business/areas/hydro_management.py index 85050d76d9..51b339012a 100644 --- a/antarest/study/business/areas/hydro_management.py +++ b/antarest/study/business/areas/hydro_management.py @@ -7,6 +7,15 @@ from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig +INFLOW_PATH = "input/hydro/prepro/{area_id}/prepro/prepro" + + +class InflowStructure(FormFieldsBaseModel): + """Represents the inflow structure form values in the hydrolic configuration.""" + + # NOTE: Currently, there is only one field for the inflow structure model due to the scope of hydro config requirements, it may change. + inter_monthly_correlation: float = Field(default=0.5, ge=0, le=1, description="Inter-monthly correlation value") + class ManagementOptionsFormFields(FormFieldsBaseModel): inter_daily_breakdown: Optional[float] = Field(ge=0) @@ -121,3 +130,38 @@ def set_field_values( if len(commands) > 0: file_study = self.storage_service.get_storage(study).get_raw(study) execute_or_add_commands(study, file_study, commands, self.storage_service) + + def get_inflowstructure(self, study: Study, area_id: str) -> InflowStructure: + """ + Retrieves inflow structure values for a specific area within a study. + + Returns: + InflowStructure: The inflow structure values. + """ + # NOTE: Focusing on the single field "intermonthly-correlation" due to current model scope. + path = INFLOW_PATH.format(area_id=area_id) + file_study = self.storage_service.get_storage(study).get_raw(study) + inter_monthly_correlation = file_study.tree.get(path.split("/")).get("intermonthly-correlation", 0.5) + return InflowStructure(inter_monthly_correlation=inter_monthly_correlation) + + def update_inflowstructure(self, study: Study, area_id: str, values: InflowStructure) -> None: + """ + Updates inflow structure values for a specific area within a study. + + Parameters: + study: The study instance to update the inflow data for. + area_id: The area identifier to update data for. + values (InflowStructure): The new inflow structure values to be updated. + + Raises: + RequestValidationError: If the provided `values` parameter is None or invalid. + """ + # NOTE: Updates only "intermonthly-correlation" due to current model scope. + path = INFLOW_PATH.format(area_id=area_id) + command = UpdateConfig( + target=path, + data={"intermonthly-correlation": values.inter_monthly_correlation}, + command_context=self.storage_service.variant_study_service.command_factory.command_context, + ) + file_study = self.storage_service.get_storage(study).get_raw(study) + execute_or_add_commands(study, file_study, [command], self.storage_service) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index a1d74379ba..1467dfdca7 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -18,7 +18,7 @@ from antarest.study.business.advanced_parameters_management import AdvancedParamsFormFields from antarest.study.business.allocation_management import AllocationFormFields, AllocationMatrix from antarest.study.business.area_management import AreaCreationDTO, AreaInfoDTO, AreaType, AreaUI, LayerInfoDTO -from antarest.study.business.areas.hydro_management import ManagementOptionsFormFields +from antarest.study.business.areas.hydro_management import ManagementOptionsFormFields, InflowStructure from antarest.study.business.areas.properties_management import PropertiesFormFields from antarest.study.business.areas.renewable_management import ( RenewableClusterCreation, @@ -423,6 +423,45 @@ def set_hydro_form_values( study_service.hydro_manager.set_field_values(study, data, area_id) + @bp.get( + "/studies/{study_id}/areas/{area_id}/hydro/inflowstructure", + tags=[APITag.study_data], + summary="Get inflow structure form values", + response_model=InflowStructure, + ) + def get_inflowstructure( + study_id: str, + area_id: str, + current_user: JWTUser = Depends(auth.get_current_user), + ) -> InflowStructure: + logger.info( + msg=f"Getting inflow structure values for area {area_id} of study {study_id}", + extra={"user": current_user.id}, + ) + params = RequestParameters(user=current_user) + study = study_service.check_study_access(study_id, StudyPermissionType.READ, params) + return study_service.hydro_manager.get_inflowstructure(study, area_id) + + @bp.put( + "/studies/{study_id}/areas/{area_id}/hydro/inflowstructure", + tags=[APITag.study_data], + summary="Update inflow structure form values", + response_model=InflowStructure, + ) + def update_inflowstructure( + study_id: str, + area_id: str, + values: InflowStructure, + current_user: JWTUser = Depends(auth.get_current_user), + ) -> None: + logger.info( + msg=f"Updating inflow structure values for area {area_id} of study {study_id}", + extra={"user": current_user.id}, + ) + params = RequestParameters(user=current_user) + study = study_service.check_study_access(study_id, StudyPermissionType.WRITE, params) + return study_service.hydro_manager.update_inflowstructure(study, area_id, values) + @bp.put( "/studies/{uuid}/matrix", tags=[APITag.study_data], From 78559485b8454c840a7cd541d4ab66ea7b6579e0 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Wed, 31 Jan 2024 14:35:37 +0100 Subject: [PATCH 02/10] feat(ui-hydro): add form prop to `SplitHydroMatrix` --- .../Modelization/Areas/Hydro/SplitHydroMatrix.tsx | 14 +++++++++----- .../explore/Modelization/Areas/Hydro/utils.ts | 3 +++ webapp/src/components/App/index.tsx | 9 ++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx index ee6a85c450..685ef372ad 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx @@ -6,14 +6,18 @@ interface Props { types: [HydroMatrixType, HydroMatrixType]; direction?: SplitViewProps["direction"]; sizes: [number, number]; + form?: React.ComponentType; } -function SplitHydroMatrix({ types, direction, sizes }: Props) { +function SplitHydroMatrix({ types, direction, sizes, form: Form }: Props) { return ( - - - - + <> + {Form &&
} + + + + + ); } diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts index c0825e885c..8f86810165 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts @@ -2,6 +2,7 @@ import { MatrixStats, MatrixType } from "../../../../../../../common/types"; import { SplitViewProps } from "../../../../../../common/SplitView"; import { getAllocationMatrix } from "./Allocation/utils"; import { getCorrelationMatrix } from "./Correlation/utils"; +import InflowStructure from "./InflowStructure"; //////////////////////////////////////////////////////////////// // Enums @@ -48,6 +49,7 @@ export interface HydroRoute { partnerType: HydroMatrixType; sizes: [number, number]; }; + form?: React.ComponentType; } export interface AreaCoefficientItem { @@ -69,6 +71,7 @@ export const HYDRO_ROUTES: HydroRoute[] = [ partnerType: HydroMatrixType.OverallMonthlyHydro, sizes: [50, 50], }, + form: InflowStructure, }, { path: "dailypower&energy", diff --git a/webapp/src/components/App/index.tsx b/webapp/src/components/App/index.tsx index 584bb1bee4..be247e296b 100644 --- a/webapp/src/components/App/index.tsx +++ b/webapp/src/components/App/index.tsx @@ -109,7 +109,13 @@ function App() { element={} /> {HYDRO_ROUTES.map( - ({ path, type, isSplitView, splitConfig }) => { + ({ + path, + type, + isSplitView, + splitConfig, + form, + }) => { return isSplitView && splitConfig ? ( } /> From 53a371a460f793802c48a1bb1bb162de024bb8b5 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Wed, 31 Jan 2024 14:36:25 +0100 Subject: [PATCH 03/10] feat(ui-hydro): add inflow structure form UI --- .../Areas/Hydro/InflowStructure/index.tsx | 79 +++++++++++++++++++ .../Areas/Hydro/InflowStructure/utils.ts | 34 ++++++++ 2 files changed, 113 insertions(+) create mode 100644 webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx create mode 100644 webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx new file mode 100644 index 0000000000..426e8a0c63 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx @@ -0,0 +1,79 @@ +import { useOutletContext } from "react-router"; +import { StudyMetadata } from "../../../../../../../../common/types"; +import useAppSelector from "../../../../../../../../redux/hooks/useAppSelector"; +import { getCurrentAreaId } from "../../../../../../../../redux/selectors"; +import Form from "../../../../../../../common/Form"; +import { + type InflowStructureFields, + getInflowStructureFields, + updatetInflowStructureFields, +} from "./utils"; +import NumberFE from "../../../../../../../common/fieldEditors/NumberFE"; +import { SubmitHandlerPlus } from "../../../../../../../common/Form/types"; +import { Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +function InflowStructure() { + const [t] = useTranslation(); + const { study } = useOutletContext<{ study: StudyMetadata }>(); + const areaId = useAppSelector(getCurrentAreaId); + + //////////////////////////////////////////////////////////////// + // Event handlers + //////////////////////////////////////////////////////////////// + + const handleSubmit = (data: SubmitHandlerPlus) => { + return updatetInflowStructureFields(study.id, areaId, data.values); + }; + + //////////////////////////////////////////////////////////////// + // JSX + //////////////////////////////////////////////////////////////// + + return ( + + getInflowStructureFields(study.id, areaId), + }} + onSubmit={handleSubmit} + sx={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + py: 1, + }} + enableUndoRedo + > + {({ control }) => ( + + )} + + + ); +} + +export default InflowStructure; diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts new file mode 100644 index 0000000000..71fce61808 --- /dev/null +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts @@ -0,0 +1,34 @@ +import { type StudyMetadata } from "../../../../../../../../common/types"; +import client from "../../../../../../../../services/api/client"; + +//////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////// + +export interface InflowStructureFields { + interMonthlyCorrelation: number; +} + +//////////////////////////////////////////////////////////////// +// Utils +//////////////////////////////////////////////////////////////// + +function makeRequestURL(studyId: StudyMetadata["id"], areaId: string): string { + return `v1/studies/${studyId}/areas/${areaId}/hydro/inflowstructure`; +} + +export async function getInflowStructureFields( + studyId: StudyMetadata["id"], + areaId: string, +): Promise { + const res = await client.get(makeRequestURL(studyId, areaId)); + return res.data; +} + +export function updatetInflowStructureFields( + studyId: StudyMetadata["id"], + areaId: string, + values: InflowStructureFields, +): Promise { + return client.put(makeRequestURL(studyId, areaId), values); +} From 2915a315bdc78345d54aacfe8f4ac80652f884f9 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:17:04 +0100 Subject: [PATCH 04/10] feat(ui-common): add `miniSubmitButton` prop in Form component --- webapp/src/components/common/Form/index.tsx | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/webapp/src/components/common/Form/index.tsx b/webapp/src/components/common/Form/index.tsx index 71ad936d14..6d10b60cce 100644 --- a/webapp/src/components/common/Form/index.tsx +++ b/webapp/src/components/common/Form/index.tsx @@ -74,6 +74,7 @@ export interface FormProps< | React.ReactNode; submitButtonText?: string; submitButtonIcon?: LoadingButtonProps["startIcon"]; + miniSubmitButton?: boolean; hideSubmitButton?: boolean; onStateChange?: (state: FormState) => void; autoSubmit?: boolean | AutoSubmitConfig; @@ -96,7 +97,8 @@ function Form( onInvalid, children, submitButtonText, - submitButtonIcon, + submitButtonIcon = , + miniSubmitButton, hideSubmitButton, onStateChange, autoSubmit, @@ -358,20 +360,19 @@ function Form( <> - ) - } - > - {submitButtonText || t("global.save")} - + {...(miniSubmitButton + ? { + children: submitButtonIcon, + } + : { + loadingPosition: "start", + startIcon: submitButtonIcon, + variant: "contained", + children: submitButtonText || t("global.save"), + })} + /> {enableUndoRedo && ( )} From 16c9f84ad5120537c154f442cdcf90d413f764f9 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:13:32 +0100 Subject: [PATCH 05/10] style(ui-hydro): fix typo --- .../Modelization/Areas/Hydro/InflowStructure/index.tsx | 4 ++-- .../explore/Modelization/Areas/Hydro/InflowStructure/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx index 426e8a0c63..57402941a3 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx @@ -6,7 +6,7 @@ import Form from "../../../../../../../common/Form"; import { type InflowStructureFields, getInflowStructureFields, - updatetInflowStructureFields, + updateInflowStructureFields, } from "./utils"; import NumberFE from "../../../../../../../common/fieldEditors/NumberFE"; import { SubmitHandlerPlus } from "../../../../../../../common/Form/types"; @@ -23,7 +23,7 @@ function InflowStructure() { //////////////////////////////////////////////////////////////// const handleSubmit = (data: SubmitHandlerPlus) => { - return updatetInflowStructureFields(study.id, areaId, data.values); + return updateInflowStructureFields(study.id, areaId, data.values); }; //////////////////////////////////////////////////////////////// diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts index 71fce61808..fe3c551d30 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts @@ -25,7 +25,7 @@ export async function getInflowStructureFields( return res.data; } -export function updatetInflowStructureFields( +export function updateInflowStructureFields( studyId: StudyMetadata["id"], areaId: string, values: InflowStructureFields, From 217782b60da60321f556a8826402f238cb595ce8 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:15:13 +0100 Subject: [PATCH 06/10] feat(ui-hydro): update Inflow Structure form style --- .../Areas/Hydro/InflowStructure/index.tsx | 70 ++++++++----------- .../Areas/Hydro/SplitHydroMatrix.tsx | 7 +- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx index 57402941a3..d5f38785b8 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/index.tsx @@ -10,7 +10,6 @@ import { } from "./utils"; import NumberFE from "../../../../../../../common/fieldEditors/NumberFE"; import { SubmitHandlerPlus } from "../../../../../../../common/Form/types"; -import { Box } from "@mui/material"; import { useTranslation } from "react-i18next"; function InflowStructure() { @@ -31,48 +30,37 @@ function InflowStructure() { //////////////////////////////////////////////////////////////// return ( - getInflowStructureFields(study.id, areaId), }} + onSubmit={handleSubmit} + miniSubmitButton + enableUndoRedo + sx={{ display: "flex", alignItems: "center", ".Form__Footer": { p: 0 } }} > -
getInflowStructureFields(study.id, areaId), - }} - onSubmit={handleSubmit} - sx={{ - display: "flex", - alignItems: "center", - justifyContent: "center", - py: 1, - }} - enableUndoRedo - > - {({ control }) => ( - - )} - -
+ {({ control }) => ( + + )} + ); } diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx index 685ef372ad..484ad0a87e 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/SplitHydroMatrix.tsx @@ -1,3 +1,4 @@ +import { Box } from "@mui/material"; import SplitView, { SplitViewProps } from "../../../../../../common/SplitView"; import HydroMatrix from "./HydroMatrix"; import { HydroMatrixType } from "./utils"; @@ -12,7 +13,11 @@ interface Props { function SplitHydroMatrix({ types, direction, sizes, form: Form }: Props) { return ( <> - {Form &&
} + {Form && ( + + + + )} From 52eb7b953aff37f65cdbea460839c379086ad5a0 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Thu, 1 Feb 2024 11:33:32 +0100 Subject: [PATCH 07/10] chore(ui-hydro): correct typo in Inflow Structure model --- antarest/study/business/areas/hydro_management.py | 15 +++++++++------ antarest/study/web/study_data_blueprint.py | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/antarest/study/business/areas/hydro_management.py b/antarest/study/business/areas/hydro_management.py index 51b339012a..6eda54787a 100644 --- a/antarest/study/business/areas/hydro_management.py +++ b/antarest/study/business/areas/hydro_management.py @@ -11,9 +11,10 @@ class InflowStructure(FormFieldsBaseModel): - """Represents the inflow structure form values in the hydrolic configuration.""" + """Represents the inflow structure form values in the hydraulic configuration.""" - # NOTE: Currently, there is only one field for the inflow structure model due to the scope of hydro config requirements, it may change. + # NOTE: Currently, there is only one field for the inflow structure model + # due to the scope of hydro config requirements, it may change. inter_monthly_correlation: float = Field(default=0.5, ge=0, le=1, description="Inter-monthly correlation value") @@ -131,7 +132,8 @@ def set_field_values( file_study = self.storage_service.get_storage(study).get_raw(study) execute_or_add_commands(study, file_study, commands, self.storage_service) - def get_inflowstructure(self, study: Study, area_id: str) -> InflowStructure: + # noinspection SpellCheckingInspection + def get_inflow_structure(self, study: Study, area_id: str) -> InflowStructure: """ Retrieves inflow structure values for a specific area within a study. @@ -144,14 +146,15 @@ def get_inflowstructure(self, study: Study, area_id: str) -> InflowStructure: inter_monthly_correlation = file_study.tree.get(path.split("/")).get("intermonthly-correlation", 0.5) return InflowStructure(inter_monthly_correlation=inter_monthly_correlation) - def update_inflowstructure(self, study: Study, area_id: str, values: InflowStructure) -> None: + # noinspection SpellCheckingInspection + def update_inflow_structure(self, study: Study, area_id: str, values: InflowStructure) -> None: """ Updates inflow structure values for a specific area within a study. - Parameters: + Args: study: The study instance to update the inflow data for. area_id: The area identifier to update data for. - values (InflowStructure): The new inflow structure values to be updated. + values: The new inflow structure values to be updated. Raises: RequestValidationError: If the provided `values` parameter is None or invalid. diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 1467dfdca7..3d7a986434 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -429,7 +429,7 @@ def set_hydro_form_values( summary="Get inflow structure form values", response_model=InflowStructure, ) - def get_inflowstructure( + def get_inflow_structure( study_id: str, area_id: str, current_user: JWTUser = Depends(auth.get_current_user), @@ -440,7 +440,7 @@ def get_inflowstructure( ) params = RequestParameters(user=current_user) study = study_service.check_study_access(study_id, StudyPermissionType.READ, params) - return study_service.hydro_manager.get_inflowstructure(study, area_id) + return study_service.hydro_manager.get_inflow_structure(study, area_id) @bp.put( "/studies/{study_id}/areas/{area_id}/hydro/inflowstructure", @@ -448,7 +448,7 @@ def get_inflowstructure( summary="Update inflow structure form values", response_model=InflowStructure, ) - def update_inflowstructure( + def update_inflow_structure( study_id: str, area_id: str, values: InflowStructure, @@ -460,7 +460,7 @@ def update_inflowstructure( ) params = RequestParameters(user=current_user) study = study_service.check_study_access(study_id, StudyPermissionType.WRITE, params) - return study_service.hydro_manager.update_inflowstructure(study, area_id, values) + return study_service.hydro_manager.update_inflow_structure(study, area_id, values) @bp.put( "/studies/{uuid}/matrix", From 27a72026153a9fb4d2463ab71f8ac4a0beafafca Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Thu, 1 Feb 2024 11:42:36 +0100 Subject: [PATCH 08/10] feat(ui-hydro): the endpoints are renamed `hydro/inflow-structure` (with a dash) --- antarest/study/web/study_data_blueprint.py | 6 +++--- .../Modelization/Areas/Hydro/InflowStructure/utils.ts | 2 +- .../Singlestudy/explore/Modelization/Areas/Hydro/index.tsx | 2 +- .../Singlestudy/explore/Modelization/Areas/Hydro/utils.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 3d7a986434..9f9eda88f0 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -18,7 +18,7 @@ from antarest.study.business.advanced_parameters_management import AdvancedParamsFormFields from antarest.study.business.allocation_management import AllocationFormFields, AllocationMatrix from antarest.study.business.area_management import AreaCreationDTO, AreaInfoDTO, AreaType, AreaUI, LayerInfoDTO -from antarest.study.business.areas.hydro_management import ManagementOptionsFormFields, InflowStructure +from antarest.study.business.areas.hydro_management import InflowStructure, ManagementOptionsFormFields from antarest.study.business.areas.properties_management import PropertiesFormFields from antarest.study.business.areas.renewable_management import ( RenewableClusterCreation, @@ -424,7 +424,7 @@ def set_hydro_form_values( study_service.hydro_manager.set_field_values(study, data, area_id) @bp.get( - "/studies/{study_id}/areas/{area_id}/hydro/inflowstructure", + "/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", tags=[APITag.study_data], summary="Get inflow structure form values", response_model=InflowStructure, @@ -443,7 +443,7 @@ def get_inflow_structure( return study_service.hydro_manager.get_inflow_structure(study, area_id) @bp.put( - "/studies/{study_id}/areas/{area_id}/hydro/inflowstructure", + "/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", tags=[APITag.study_data], summary="Update inflow structure form values", response_model=InflowStructure, diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts index fe3c551d30..e8fdc21be5 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/InflowStructure/utils.ts @@ -14,7 +14,7 @@ export interface InflowStructureFields { //////////////////////////////////////////////////////////////// function makeRequestURL(studyId: StudyMetadata["id"], areaId: string): string { - return `v1/studies/${studyId}/areas/${areaId}/hydro/inflowstructure`; + return `v1/studies/${studyId}/areas/${areaId}/hydro/inflow-structure`; } export async function getInflowStructureFields( diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/index.tsx b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/index.tsx index 27c43e68c5..289913bcbe 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/index.tsx @@ -19,7 +19,7 @@ function Hydro() { return [ { label: "Management options", path: `${basePath}/management` }, - { label: "Inflow structure", path: `${basePath}/inflowstructure` }, + { label: "Inflow structure", path: `${basePath}/inflow-structure` }, { label: "Allocation", path: `${basePath}/allocation` }, { label: "Correlation", path: `${basePath}/correlation` }, { diff --git a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts index 8f86810165..b18c084852 100644 --- a/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Modelization/Areas/Hydro/utils.ts @@ -63,7 +63,7 @@ export interface AreaCoefficientItem { export const HYDRO_ROUTES: HydroRoute[] = [ { - path: "inflowstructure", + path: "inflow-structure", type: HydroMatrixType.InflowPattern, isSplitView: true, splitConfig: { From 84fda19c7a3c19d89090c82054440d3dbf8c6296 Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Thu, 1 Feb 2024 15:01:49 +0100 Subject: [PATCH 09/10] feat(ui-hydro): add endpoints documentation --- .../study/business/areas/hydro_management.py | 10 ++++++-- .../root/input/hydro/prepro/area/prepro.py | 10 ++++++++ antarest/study/web/study_data_blueprint.py | 23 +++++++++++-------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/antarest/study/business/areas/hydro_management.py b/antarest/study/business/areas/hydro_management.py index 6eda54787a..a6a8742168 100644 --- a/antarest/study/business/areas/hydro_management.py +++ b/antarest/study/business/areas/hydro_management.py @@ -11,11 +11,17 @@ class InflowStructure(FormFieldsBaseModel): - """Represents the inflow structure form values in the hydraulic configuration.""" + """Represents the inflow structure values in the hydraulic configuration.""" # NOTE: Currently, there is only one field for the inflow structure model # due to the scope of hydro config requirements, it may change. - inter_monthly_correlation: float = Field(default=0.5, ge=0, le=1, description="Inter-monthly correlation value") + inter_monthly_correlation: float = Field( + default=0.5, + ge=0, + le=1, + description="Average correlation between the energy of a month and that of the next month", + title="Inter-monthly correlation", + ) class ManagementOptionsFormFields(FormFieldsBaseModel): diff --git a/antarest/study/storage/rawstudy/model/filesystem/root/input/hydro/prepro/area/prepro.py b/antarest/study/storage/rawstudy/model/filesystem/root/input/hydro/prepro/area/prepro.py index 37403cd412..7298464e1a 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/root/input/hydro/prepro/area/prepro.py +++ b/antarest/study/storage/rawstudy/model/filesystem/root/input/hydro/prepro/area/prepro.py @@ -3,7 +3,17 @@ from antarest.study.storage.rawstudy.model.filesystem.ini_file_node import IniFileNode +# noinspection SpellCheckingInspection class InputHydroPreproAreaPrepro(IniFileNode): + """ + Configuration for the hydraulic Inflow Structure: + + - intermonthly-correlation: Average correlation between the energy of a month and + that of the next month (inter-monthly correlation). + + See: https://antares-simulator.readthedocs.io/en/latest/reference-guide/04-active_windows/#hydro + """ + def __init__(self, context: ContextServer, config: FileStudyTreeConfig): types = {"prepro": {"intermonthly-correlation": float}} IniFileNode.__init__(self, context, config, types) diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 9f9eda88f0..0ebd603a21 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -423,43 +423,46 @@ def set_hydro_form_values( study_service.hydro_manager.set_field_values(study, data, area_id) + # noinspection SpellCheckingInspection @bp.get( - "/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + "/studies/{uuid}/areas/{area_id}/hydro/inflow-structure", tags=[APITag.study_data], - summary="Get inflow structure form values", + summary="Get inflow structure values", response_model=InflowStructure, ) def get_inflow_structure( - study_id: str, + uuid: str, area_id: str, current_user: JWTUser = Depends(auth.get_current_user), ) -> InflowStructure: + """Get the configuration for the hydraulic inflow structure of the given area.""" logger.info( - msg=f"Getting inflow structure values for area {area_id} of study {study_id}", + msg=f"Getting inflow structure values for area {area_id} of study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - study = study_service.check_study_access(study_id, StudyPermissionType.READ, params) + study = study_service.check_study_access(uuid, StudyPermissionType.READ, params) return study_service.hydro_manager.get_inflow_structure(study, area_id) @bp.put( - "/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + "/studies/{uuid}/areas/{area_id}/hydro/inflow-structure", tags=[APITag.study_data], - summary="Update inflow structure form values", + summary="Update inflow structure values", response_model=InflowStructure, ) def update_inflow_structure( - study_id: str, + uuid: str, area_id: str, values: InflowStructure, current_user: JWTUser = Depends(auth.get_current_user), ) -> None: + """Update the configuration for the hydraulic inflow structure of the given area.""" logger.info( - msg=f"Updating inflow structure values for area {area_id} of study {study_id}", + msg=f"Updating inflow structure values for area {area_id} of study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - study = study_service.check_study_access(study_id, StudyPermissionType.WRITE, params) + study = study_service.check_study_access(uuid, StudyPermissionType.WRITE, params) return study_service.hydro_manager.update_inflow_structure(study, area_id, values) @bp.put( From 428383383246d02b9bf28cad7c6e2c3fcbc8eecc Mon Sep 17 00:00:00 2001 From: Laurent LAPORTE Date: Thu, 1 Feb 2024 16:13:56 +0100 Subject: [PATCH 10/10] test(hydro): add unit test for inflow structure --- .../test_hydro_inflow_structure.py | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 tests/integration/study_data_blueprint/test_hydro_inflow_structure.py diff --git a/tests/integration/study_data_blueprint/test_hydro_inflow_structure.py b/tests/integration/study_data_blueprint/test_hydro_inflow_structure.py new file mode 100644 index 0000000000..0a8bb82ed6 --- /dev/null +++ b/tests/integration/study_data_blueprint/test_hydro_inflow_structure.py @@ -0,0 +1,155 @@ +from http import HTTPStatus +from unittest.mock import ANY + +import pytest +from starlette.testclient import TestClient + + +@pytest.mark.unit_test +class TestHydroInflowStructure: + """ + Test the end points related to hydraulic inflow-structure. + + Those tests use the "examples/studies/STA-mini.zip" Study, + which contains the following areas: ["de", "es", "fr", "it"]. + """ + + def test_get_inflow_structure( + self, + client: TestClient, + user_access_token: str, + study_id: str, + ): + user_header = {"Authorization": f"Bearer {user_access_token}"} + area_id = "fr" + + # ==================== + # Use case : RAW study + # ==================== + + # Check that the default values are returned + res = client.get( + f"/v1/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + expected = {"interMonthlyCorrelation": 0.5} + assert actual == expected + + # Update the values + obj = {"interMonthlyCorrelation": 0.8} + res = client.put( + f"/v1/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + json=obj, + ) + assert res.status_code == HTTPStatus.OK, res.json() + + # Check that the right values are returned + res = client.get( + f"/v1/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + expected = {"interMonthlyCorrelation": 0.8} + assert actual == expected + + # ======================== + # Use case : Variant study + # ======================== + + # Create a managed study from the RAW study. + res = client.post( + f"/v1/studies/{study_id}/copy", + headers={"Authorization": f"Bearer {user_access_token}"}, + params={"dest": "Clone", "with_outputs": False, "use_task": False}, + ) + res.raise_for_status() + managed_id = res.json() + assert managed_id is not None + + # Create a variant from the managed study. + res = client.post( + f"/v1/studies/{managed_id}/variants", + headers={"Authorization": f"Bearer {user_access_token}"}, + params={"name": "Variant"}, + ) + res.raise_for_status() + variant_id = res.json() + assert variant_id is not None + + # Check that the return values match the RAW study values + res = client.get( + f"/v1/studies/{variant_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + expected = {"interMonthlyCorrelation": 0.8} + assert actual == expected + + # Update the values + obj = {"interMonthlyCorrelation": 0.9} + res = client.put( + f"/v1/studies/{variant_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + json=obj, + ) + assert res.status_code == HTTPStatus.OK, res.json() + + # Check that the right values are returned + res = client.get( + f"/v1/studies/{variant_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + expected = {"interMonthlyCorrelation": 0.9} + assert actual == expected + + # Check the variant commands + res = client.get( + f"/v1/studies/{variant_id}/commands", + headers=user_header, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + assert len(actual) == 2 + expected = { + "id": ANY, + "action": "update_config", + "args": { + "target": "input/hydro/prepro/fr/prepro/prepro", + "data": {"intermonthly-correlation": 0.9}, + }, + "version": 1, + } + assert actual[1] == expected + + def test_update_inflow_structure__invalid_values( + self, + client: TestClient, + user_access_token: str, + study_id: str, + ): + user_header = {"Authorization": f"Bearer {user_access_token}"} + area_id = "fr" + + # Update the values with invalid values + obj = {"interMonthlyCorrelation": 1.1} + res = client.put( + f"/v1/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + json=obj, + ) + assert res.status_code == HTTPStatus.UNPROCESSABLE_ENTITY, res.json() + + obj = {"interMonthlyCorrelation": -0.1} + res = client.put( + f"/v1/studies/{study_id}/areas/{area_id}/hydro/inflow-structure", + headers=user_header, + json=obj, + ) + assert res.status_code == HTTPStatus.UNPROCESSABLE_ENTITY, res.json()