diff --git a/src/components/computing-status/computing-type.ts b/src/components/computing-status/computing-type.ts index 277316620d..d29760f096 100644 --- a/src/components/computing-status/computing-type.ts +++ b/src/components/computing-status/computing-type.ts @@ -13,6 +13,7 @@ export enum ComputingType { SHORT_CIRCUIT = 'SHORT_CIRCUIT', SHORT_CIRCUIT_ONE_BUS = 'SHORT_CIRCUIT_ONE_BUS', DYNAMIC_SIMULATION = 'DYNAMIC_SIMULATION', + DYNAMIC_SECURITY_ANALYSIS = 'DYNAMIC_SECURITY_ANALYSIS', VOLTAGE_INITIALIZATION = 'VOLTAGE_INITIALIZATION', STATE_ESTIMATION = 'STATE_ESTIMATION', } @@ -37,6 +38,8 @@ export const formatComputingTypeLabel = (type: ComputingType): string | undefine return 'VoltageInit'; case ComputingType.DYNAMIC_SIMULATION: return 'DynamicSimulation'; + case ComputingType.DYNAMIC_SECURITY_ANALYSIS: + return 'DynamicSecurityAnalysis'; case ComputingType.STATE_ESTIMATION: return 'StateEstimation'; default: diff --git a/src/components/computing-status/use-all-computing-status.ts b/src/components/computing-status/use-all-computing-status.ts index ca5387e013..84c1b17695 100644 --- a/src/components/computing-status/use-all-computing-status.ts +++ b/src/components/computing-status/use-all-computing-status.ts @@ -7,14 +7,15 @@ import { useComputingStatus } from './use-computing-status'; import { - getSecurityAnalysisRunningStatus, - getSensitivityAnalysisRunningStatus, - getShortCircuitAnalysisRunningStatus, + getDynamicSecurityAnalysisRunningStatus, getDynamicSimulationRunningStatus, - getVoltageInitRunningStatus, getLoadFlowRunningStatus, getNonEvacuatedEnergyRunningStatus, + getSecurityAnalysisRunningStatus, + getSensitivityAnalysisRunningStatus, + getShortCircuitAnalysisRunningStatus, getStateEstimationRunningStatus, + getVoltageInitRunningStatus, } from '../utils/running-status'; import { UUID } from 'crypto'; @@ -32,9 +33,9 @@ import { OptionalServicesNames } from '../utils/optional-services'; import { useOptionalServiceStatus } from '../../hooks/use-optional-service-status'; import { fetchNonEvacuatedEnergyStatus } from '../../services/study/non-evacuated-energy'; import { fetchStateEstimationStatus } from '../../services/study/state-estimation'; +import { fetchDynamicSecurityAnalysisStatus } from '../../services/study/dynamic-security-analysis'; const loadFlowStatusInvalidations = ['loadflow_status', 'loadflow_failed']; - const securityAnalysisStatusInvalidations = ['securityAnalysis_status', 'securityAnalysis_failed']; const sensitivityAnalysisStatusInvalidations = ['sensitivityAnalysis_status', 'sensitivityAnalysis_failed']; const nonEvacuatedEnergyStatusInvalidations = ['nonEvacuatedEnergy_status', 'nonEvacuatedEnergy_failed']; @@ -44,10 +45,11 @@ const oneBusShortCircuitAnalysisStatusInvalidations = [ 'oneBusShortCircuitAnalysis_failed', ]; const dynamicSimulationStatusInvalidations = ['dynamicSimulation_status', 'dynamicSimulation_failed']; +const dynamicSecurityAnalysisStatusInvalidations = ['dynamicSecurityAnalysis_status', 'dynamicSecurityAnalysis_failed']; const voltageInitStatusInvalidations = ['voltageInit_status', 'voltageInit_failed']; const stateEstimationStatusInvalidations = ['stateEstimation_status', 'stateEstimation_failed']; -const loadFlowStatusCompletions = ['loadflowResult', 'loadflow_failed']; +const loadFlowStatusCompletions = ['loadflowResult', 'loadflow_failed']; const securityAnalysisStatusCompletions = ['securityAnalysisResult', 'securityAnalysis_failed']; const sensitivityAnalysisStatusCompletions = ['sensitivityAnalysisResult', 'sensitivityAnalysis_failed']; const nonEvacuatedEnergyStatusCompletions = ['nonEvacuatedEnergyResult', 'nonEvacuatedEnergy_failed']; @@ -57,14 +59,17 @@ const oneBusShortCircuitAnalysisStatusCompletions = [ 'oneBusShortCircuitAnalysis_failed', ]; const dynamicSimulationStatusCompletions = ['dynamicSimulationResult', 'dynamicSimulation_failed']; +const dynamicSecurityAnalysisStatusCompletions = ['dynamicSecurityAnalysisResult', 'dynamicSecurityAnalysis_failed']; const voltageInitStatusCompletions = ['voltageInitResult', 'voltageInit_failed']; const stateEstimationStatusCompletions = ['stateEstimationResult', 'stateEstimation_failed']; + // this hook loads all current computation status into redux then keeps them up to date according to notifications export const useAllComputingStatus = (studyUuid: UUID, currentNodeUuid: UUID, currentRootNetworkUuid: UUID): void => { const securityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.SecurityAnalysis); const sensitivityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const nonEvacuatedEnergyAvailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const dynamicSimulationAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSimulation); + const dynamicSecurityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSecurityAnalysis); const voltageInitAvailability = useOptionalServiceStatus(OptionalServicesNames.VoltageInit); const shortCircuitAvailability = useOptionalServiceStatus(OptionalServicesNames.ShortCircuit); const stateEstimationAvailability = useOptionalServiceStatus(OptionalServicesNames.StateEstimation); @@ -152,6 +157,18 @@ export const useAllComputingStatus = (studyUuid: UUID, currentNodeUuid: UUID, cu dynamicSimulationAvailability ); + useComputingStatus( + studyUuid, + currentNodeUuid, + currentRootNetworkUuid, + fetchDynamicSecurityAnalysisStatus, + dynamicSecurityAnalysisStatusInvalidations, + dynamicSecurityAnalysisStatusCompletions, + getDynamicSecurityAnalysisRunningStatus, + ComputingType.DYNAMIC_SECURITY_ANALYSIS, + dynamicSecurityAnalysisAvailability + ); + useComputingStatus( studyUuid, currentNodeUuid, diff --git a/src/components/dialogs/parameters/common/provider-parameter.tsx b/src/components/dialogs/parameters/common/provider-parameter.tsx new file mode 100644 index 0000000000..f9e6ac0493 --- /dev/null +++ b/src/components/dialogs/parameters/common/provider-parameter.tsx @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { Grid, SelectChangeEvent } from '@mui/material'; +import { DropDown } from '../parameters'; +import { useCallback } from 'react'; + +export interface ProviderParameterProps { + providers: Record; + provider: string | undefined; + onChangeProvider: (provider: string) => void; +} + +export default function ProviderParameter({ providers, provider, onChangeProvider }: Readonly) { + const handleUpdateProvider = useCallback( + (evt: SelectChangeEvent) => { + onChangeProvider(evt.target.value); + }, + [onChangeProvider] + ); + + return ( + + {providers && provider && ( + + )} + + ); +} diff --git a/src/components/dialogs/parameters/dynamic-security-analysis/contingency-parameters.tsx b/src/components/dialogs/parameters/dynamic-security-analysis/contingency-parameters.tsx new file mode 100644 index 0000000000..bcaeb0074c --- /dev/null +++ b/src/components/dialogs/parameters/dynamic-security-analysis/contingency-parameters.tsx @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import yup from '../../../utils/yup-config'; +import { Grid } from '@mui/material'; +import { makeComponents, TYPES } from '../util/make-component-utils'; +import ParameterLineDirectoryItemsInput from '../widget/parameter-line-directory-items-input'; +import { ElementType } from '@gridsuite/commons-ui'; +import { ID, NAME } from '../../../utils/field-constants'; + +export const CONTINGENCIES_START_TIME = 'contingenciesStartTime'; +export const CONTINGENCIES_LIST_INFOS = 'contingencyListInfos'; + +export const formSchema = yup.object().shape({ + [CONTINGENCIES_START_TIME]: yup.number().required(), + [CONTINGENCIES_LIST_INFOS]: yup + .array() + .of( + yup.object().shape({ + [ID]: yup.string().required(), + [NAME]: yup.string().required(), + }) + ) + .required(), +}); + +export const emptyFormData = { + [CONTINGENCIES_START_TIME]: 0, + [CONTINGENCIES_LIST_INFOS]: [], +}; + +const defParams = { + [CONTINGENCIES_START_TIME]: { + type: TYPES.FLOAT, + label: 'DynamicSecurityAnalysisContingenciesStartTime', + }, +}; + +function ContingencyParameters({ path }: Readonly<{ path: string }>) { + return ( + + {makeComponents(defParams, path)} + + + ); +} + +export default ContingencyParameters; diff --git a/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx b/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx new file mode 100644 index 0000000000..0cc3b6cf48 --- /dev/null +++ b/src/components/dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters.tsx @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import yup from '../../../utils/yup-config'; +import { Grid, Tab, Tabs } from '@mui/material'; +import { FormattedMessage } from 'react-intl'; + +import { FunctionComponent, SyntheticEvent, useCallback, useEffect, useState } from 'react'; +import ScenarioParameters, { + emptyFormData as scenarioEmptyFormData, + formSchema as scenarioFormSchema, + SCENARIO_DURATION, +} from './scenario-parameters'; +import { + fetchDefaultDynamicSecurityAnalysisProvider, + fetchDynamicSecurityAnalysisParameters, + fetchDynamicSecurityAnalysisProviders, + updateDynamicSecurityAnalysisParameters, + updateDynamicSecurityAnalysisProvider, +} from '../../../../services/study/dynamic-security-analysis'; +import { OptionalServicesNames } from '../../../utils/optional-services'; +import { useOptionalServiceStatus } from '../../../../hooks/use-optional-service-status'; +import { mergeSx } from '../../../utils/functions'; +import { CustomFormProvider, SubmitButton } from '@gridsuite/commons-ui'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { FieldErrors, useForm } from 'react-hook-form'; +import { getTabStyle } from '../../../utils/tab-utils'; +import ComputingType from '../../../computing-status/computing-type'; +import LineSeparator from '../../commons/line-separator'; +import { User } from 'oidc-client'; + +import { LabelledButton, styles, TabPanel, useParametersBackend } from '../parameters'; +import ContingencyParameters, { + CONTINGENCIES_LIST_INFOS, + CONTINGENCIES_START_TIME, + emptyFormData as contingencyEmptyFormData, + formSchema as contingencyFormSchema, +} from './contingency-parameters'; +import ProviderParameter from '../common/provider-parameter'; + +enum TAB_VALUES { + SCENARIO = 'scenario', + CONTINGENCY = 'contingency', +} + +interface DynamicSecurityAnalysisParametersProps { + user: User | null; + setHaveDirtyFields: (haveDirtyFields: boolean) => void; +} + +const formSchema = yup.object().shape({ + [TAB_VALUES.SCENARIO]: scenarioFormSchema, + [TAB_VALUES.CONTINGENCY]: contingencyFormSchema, +}); + +const emptyFormData = { + [TAB_VALUES.SCENARIO]: { ...scenarioEmptyFormData }, + [TAB_VALUES.CONTINGENCY]: { ...contingencyEmptyFormData }, +}; + +export type DynamicSecurityAnalysisParametersForm = yup.InferType; + +const DynamicSecurityAnalysisParameters: FunctionComponent = ({ + user, + setHaveDirtyFields, +}) => { + const dynamicSecurityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSecurityAnalysis); + + const [providers, provider, updateProvider, resetProvider, parameters, updateParameters, resetParameters] = + useParametersBackend( + user, + ComputingType.DYNAMIC_SECURITY_ANALYSIS, + dynamicSecurityAnalysisAvailability, + fetchDynamicSecurityAnalysisProviders, + null, + fetchDefaultDynamicSecurityAnalysisProvider, + updateDynamicSecurityAnalysisProvider, + fetchDynamicSecurityAnalysisParameters, + updateDynamicSecurityAnalysisParameters + ); + + const [tabIndex, setTabIndex] = useState(TAB_VALUES.SCENARIO); + const [tabIndexesWithError, setTabIndexesWithError] = useState([]); + + const handleResetParametersAndProvider = useCallback(() => { + resetProvider(); + resetParameters(); + }, [resetParameters, resetProvider]); + + const formMethods = useForm({ + defaultValues: emptyFormData, + resolver: yupResolver(formSchema), + }); + + const { reset, handleSubmit, formState } = formMethods; + + const onError = useCallback( + (errors: FieldErrors) => { + const tabsInError = []; + // do not show error when being in the current tab + if (errors?.[TAB_VALUES.SCENARIO] && TAB_VALUES.SCENARIO !== tabIndex) { + tabsInError.push(TAB_VALUES.SCENARIO); + } + if (errors?.[TAB_VALUES.CONTINGENCY] && TAB_VALUES.CONTINGENCY !== tabIndex) { + tabsInError.push(TAB_VALUES.CONTINGENCY); + } + + if (tabsInError.includes(tabIndex)) { + // error in current tab => do not change tab systematically but remove current tab in error list + setTabIndexesWithError(tabsInError.filter((errorTabIndex) => errorTabIndex !== tabIndex)); + } else if (tabsInError.length > 0) { + // switch to the first tab in the list then remove the tab in the error list + setTabIndex(tabsInError[0]); + setTabIndexesWithError(tabsInError.filter((errorTabIndex, index, arr) => errorTabIndex !== arr[0])); + } + }, + [tabIndex] + ); + + const onSubmit = useCallback( + (newParams: DynamicSecurityAnalysisParametersForm) => { + // use updater to set with new parameters + updateParameters({ + ...parameters, + ...newParams[TAB_VALUES.SCENARIO], + [CONTINGENCIES_START_TIME]: newParams[TAB_VALUES.CONTINGENCY][CONTINGENCIES_START_TIME], + [CONTINGENCIES_LIST_INFOS]: newParams[TAB_VALUES.CONTINGENCY][CONTINGENCIES_LIST_INFOS], + }); + }, + [parameters, updateParameters] + ); + + useEffect(() => { + if (parameters) { + reset({ + [TAB_VALUES.SCENARIO]: { + [SCENARIO_DURATION]: parameters[SCENARIO_DURATION], + }, + [TAB_VALUES.CONTINGENCY]: { + [CONTINGENCIES_START_TIME]: parameters[CONTINGENCIES_START_TIME], + [CONTINGENCIES_LIST_INFOS]: parameters[CONTINGENCIES_LIST_INFOS], + }, + }); + } + }, [reset, parameters]); + + const handleTabChange = useCallback((event: SyntheticEvent, newValue: TAB_VALUES) => { + setTabIndex(newValue); + }, []); + + useEffect(() => { + setHaveDirtyFields(!!Object.keys(formState.dirtyFields).length); + }, [formState, setHaveDirtyFields]); + + return ( + + + + + + + + + + + } + value={TAB_VALUES.SCENARIO} + sx={getTabStyle(tabIndexesWithError, TAB_VALUES.SCENARIO)} + /> + } + value={TAB_VALUES.CONTINGENCY} + /> + + + + + + + + + + + + + + + + + + ); +}; + +export default DynamicSecurityAnalysisParameters; diff --git a/src/components/dialogs/parameters/dynamic-security-analysis/scenario-parameters.tsx b/src/components/dialogs/parameters/dynamic-security-analysis/scenario-parameters.tsx new file mode 100644 index 0000000000..c2aef6b5e5 --- /dev/null +++ b/src/components/dialogs/parameters/dynamic-security-analysis/scenario-parameters.tsx @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import yup from '../../../utils/yup-config'; +import { Grid } from '@mui/material'; +import { makeComponents, TYPES } from '../util/make-component-utils'; + +export const SCENARIO_DURATION = 'scenarioDuration'; + +export const formSchema = yup + .object() + .shape({ + [SCENARIO_DURATION]: yup.number().required(), + }) + .required(); + +export const emptyFormData = { + [SCENARIO_DURATION]: 0, +}; + +const defParams = { + [SCENARIO_DURATION]: { + type: TYPES.FLOAT, + label: 'DynamicSecurityAnalysisScenarioDuration', + }, +}; + +function ScenarioParameters({ path }: Readonly<{ path: string }>) { + return ( + + {makeComponents(defParams, path)} + + ); +} + +export default ScenarioParameters; diff --git a/src/components/dialogs/parameters/dynamicsimulation/curve-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/curve-parameters.tsx index 5915507c3b..d511caeeea 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/curve-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/curve-parameters.tsx @@ -6,7 +6,7 @@ */ import yup from '../../../utils/yup-config'; -import { Grid, Typography, Box, useTheme } from '@mui/material'; +import { Box, Grid, Typography, useTheme } from '@mui/material'; import { useCallback, useMemo, useRef, useState } from 'react'; import GridButtons from './curve/grid-buttons'; import { useIntl } from 'react-intl'; @@ -154,7 +154,7 @@ const CurveParameters = ({ path }: { path: string }) => { return ( <> - + {/* header toolbar of the aggrid */} diff --git a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx index 4fc2131138..fdf205feef 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/dynamic-simulation-parameters.tsx @@ -6,7 +6,7 @@ */ import yup from '../../../utils/yup-config'; -import { Grid, SelectChangeEvent, Tab, Tabs } from '@mui/material'; +import { Grid, Tab, Tabs } from '@mui/material'; import { FormattedMessage } from 'react-intl'; import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; @@ -27,7 +27,7 @@ import MappingParameters, { formSchema as mappingFormSchema, MAPPING, } from './mapping-parameters'; -import { DropDown, LabelledButton, styles, TabPanel, useParametersBackend } from '../parameters'; +import { LabelledButton, styles, TabPanel, useParametersBackend } from '../parameters'; import NetworkParameters, { emptyFormData as networkEmptyFormData, formSchema as networkFormSchema, @@ -51,12 +51,13 @@ import { useOptionalServiceStatus } from '../../../../hooks/use-optional-service import { mergeSx } from '../../../utils/functions'; import { CustomFormProvider, SubmitButton } from '@gridsuite/commons-ui'; import { yupResolver } from '@hookform/resolvers/yup'; -import { useForm } from 'react-hook-form'; +import { FieldErrors, useForm } from 'react-hook-form'; import { getTabStyle } from '../../../utils/tab-utils'; import ComputingType from '../../../computing-status/computing-type'; import LineSeparator from '../../commons/line-separator'; import { User } from 'oidc-client'; import { SolverInfos } from 'services/study/dynamic-simulation.type'; +import ProviderParameter from '../common/provider-parameter'; enum TAB_VALUES { TIME_DELAY = 'timeDelay', @@ -104,13 +105,6 @@ const DynamicSimulationParameters: FunctionComponent([]); - const handleUpdateProvider = useCallback( - (evt: SelectChangeEvent) => { - updateProvider(evt.target.value); - }, - [updateProvider] - ); - const handleResetParametersAndProvider = useCallback(() => { resetProvider(); resetParameters(); @@ -134,7 +128,7 @@ const DynamicSimulationParameters: FunctionComponent) => { + (errors: FieldErrors) => { const tabsInError = []; // do not show error when being in the current tab if (errors?.[TAB_VALUES.TIME_DELAY] && TAB_VALUES.TIME_DELAY !== tabValue) { @@ -157,13 +151,6 @@ const DynamicSimulationParameters: FunctionComponent convert to json to activate useEffect - const errorsJSON = JSON.stringify(formState.errors); - - useEffect(() => { - onError(JSON.parse(errorsJSON)); - }, [errorsJSON, onError]); - const onSubmit = useCallback( (newParams: DynamicSimulationForm) => { // use updater to set with new parameters @@ -232,31 +219,8 @@ const DynamicSimulationParameters: FunctionComponent - - {providers && provider && ( - >( - (obj, [key, value]) => { - obj[key] = `DynamicSimulationProvider${value}`; - return obj; - }, - {} - )} - callback={handleUpdateProvider} - /> - )} - - - + + @@ -325,7 +289,7 @@ const DynamicSimulationParameters: FunctionComponent - + diff --git a/src/components/dialogs/parameters/dynamicsimulation/mapping-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/mapping-parameters.tsx index fc0b5242dd..ddae80bf1f 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/mapping-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/mapping-parameters.tsx @@ -58,7 +58,7 @@ const MappingParameters: FunctionComponent = ({ mapping, }; return ( - + {makeComponents(defParams, path)} ); diff --git a/src/components/dialogs/parameters/dynamicsimulation/network-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/network-parameters.tsx index f735fa44f4..f7a6ac4250 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/network-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/network-parameters.tsx @@ -163,7 +163,7 @@ const defParams = { const NetworkParameters = ({ path }: { path: string }) => { return ( - + {makeComponents(defParams, path)} ); diff --git a/src/components/dialogs/parameters/dynamicsimulation/solver-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/solver-parameters.tsx index 1da8eca272..d0da422ece 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/solver-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/solver-parameters.tsx @@ -90,7 +90,7 @@ const SolverParameters: FunctionComponent = ({ solver, pa }; return ( - + {makeComponents(defParams, path)} diff --git a/src/components/dialogs/parameters/dynamicsimulation/time-delay-parameters.tsx b/src/components/dialogs/parameters/dynamicsimulation/time-delay-parameters.tsx index a4cb5741ba..78f6b3f451 100644 --- a/src/components/dialogs/parameters/dynamicsimulation/time-delay-parameters.tsx +++ b/src/components/dialogs/parameters/dynamicsimulation/time-delay-parameters.tsx @@ -13,7 +13,7 @@ export const START_TIME = 'startTime'; export const STOP_TIME = 'stopTime'; export const formSchema = yup.object().shape({ - [START_TIME]: yup.number().required().nonNullable(), + [START_TIME]: yup.number().required(), [STOP_TIME]: yup .number() .required() @@ -43,7 +43,7 @@ const defParams = { const TimeDelayParameters = ({ path }: { path: string }) => { return ( - + {makeComponents(defParams, path)} ); diff --git a/src/components/dialogs/parameters/parameters.type.ts b/src/components/dialogs/parameters/parameters.type.ts index d228f5aa4a..5eebdf8c06 100644 --- a/src/components/dialogs/parameters/parameters.type.ts +++ b/src/components/dialogs/parameters/parameters.type.ts @@ -11,6 +11,7 @@ import { ILimitReductionsByVoltageLevel, ISAParameters } from './common/limitred import { NonEvacuatedEnergyParametersInfos } from 'services/study/non-evacuated-energy.type'; import { LoadFlowParametersInfos } from 'services/study/loadflow.type'; import { DynamicSimulationParametersFetchReturn } from 'services/study/dynamic-simulation.type'; +import { DynamicSecurityAnalysisParametersFetchReturn } from '../../../services/study/dynamic-security-analysis.type'; enum ParameterType { BOOLEAN = 'BOOLEAN', @@ -40,6 +41,8 @@ export type ParametersInfos = T extends ComputingType.S ? LoadFlowParametersInfos : T extends ComputingType.DYNAMIC_SIMULATION ? DynamicSimulationParametersFetchReturn + : T extends ComputingType.DYNAMIC_SECURITY_ANALYSIS + ? DynamicSecurityAnalysisParametersFetchReturn : Record; export type UseParametersBackendReturnProps = [ diff --git a/src/components/dialogs/parameters/widget/parameter-line-directory-items-input.tsx b/src/components/dialogs/parameters/widget/parameter-line-directory-items-input.tsx index 54ca48821f..928597b113 100644 --- a/src/components/dialogs/parameters/widget/parameter-line-directory-items-input.tsx +++ b/src/components/dialogs/parameters/widget/parameter-line-directory-items-input.tsx @@ -13,7 +13,7 @@ import { styles } from '../parameters'; type DirectoryItemsInputLineProps = { label: string; name: string; - equipmentTypes: string[]; + equipmentTypes?: string[]; elementType: string; hideErrorMessage: boolean; }; @@ -26,7 +26,7 @@ const ParameterLineDirectoryItemsInput = ({ hideErrorMessage, }: DirectoryItemsInputLineProps) => { return ( - + diff --git a/src/components/parameters-tabs.tsx b/src/components/parameters-tabs.tsx index 5aee5e4a32..c90d141749 100644 --- a/src/components/parameters-tabs.tsx +++ b/src/components/parameters-tabs.tsx @@ -60,6 +60,7 @@ import RunningStatus from './utils/running-status'; import GlassPane from './results/common/glass-pane'; import { SecurityAnalysisParameters } from './dialogs/parameters/security-analysis/security-analysis-parameters'; import { NetworkVisualizationsParameters } from './dialogs/parameters/network-visualizations/network-visualizations-parameters'; +import DynamicSecurityAnalysisParameters from './dialogs/parameters/dynamic-security-analysis/dynamic-security-analysis-parameters'; const stylesLayout = { // need attention with parents flex @@ -137,6 +138,7 @@ enum TAB_VALUES { nonEvacuatedEnergyParamsTabValue = 'NonEvacuatedEnergyAnalysis', shortCircuitParamsTabValue = 'ShortCircuit', dynamicSimulationParamsTabValue = 'DynamicSimulation', + dynamicSecurityAnalysisParamsTabValue = 'DynamicSecurityAnalysis', advancedParamsTabValue = 'Advanced', voltageInitParamsTabValue = 'VoltageInit', networkVisualizationsParams = 'NetworkVisualizations', @@ -171,6 +173,7 @@ const ParametersTabs: FunctionComponent = (props) => { const sensitivityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const nonEvacuatedEnergyAvailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const dynamicSimulationAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSimulation); + const dynamicSecurityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSecurityAnalysis); const voltageInitAvailability = useOptionalServiceStatus(OptionalServicesNames.VoltageInit); const shortCircuitAvailability = useOptionalServiceStatus(OptionalServicesNames.ShortCircuit); @@ -250,7 +253,8 @@ const ParametersTabs: FunctionComponent = (props) => { (oldValue === TAB_VALUES.sensitivityAnalysisParamsTabValue || oldValue === TAB_VALUES.nonEvacuatedEnergyParamsTabValue || oldValue === TAB_VALUES.shortCircuitParamsTabValue || - oldValue === TAB_VALUES.dynamicSimulationParamsTabValue) + oldValue === TAB_VALUES.dynamicSimulationParamsTabValue || + oldValue === TAB_VALUES.dynamicSecurityAnalysisParamsTabValue) ) { return TAB_VALUES.securityAnalysisParamsTabValue; } @@ -298,6 +302,8 @@ const ParametersTabs: FunctionComponent = (props) => { ); case TAB_VALUES.dynamicSimulationParamsTabValue: return ; + case TAB_VALUES.dynamicSecurityAnalysisParamsTabValue: + return ; case TAB_VALUES.voltageInitParamsTabValue: return ; case TAB_VALUES.advancedParamsTabValue: @@ -368,6 +374,13 @@ const ParametersTabs: FunctionComponent = (props) => { value={TAB_VALUES.dynamicSimulationParamsTabValue} /> ) : null} + {enableDeveloperMode ? ( + } + value={TAB_VALUES.dynamicSecurityAnalysisParamsTabValue} + /> + ) : null} } diff --git a/src/components/result-view-tab.tsx b/src/components/result-view-tab.tsx index 8278d67f56..06257060b4 100644 --- a/src/components/result-view-tab.tsx +++ b/src/components/result-view-tab.tsx @@ -18,7 +18,7 @@ import { computingTypeToRootTabRedirection, ResultTabIndexRedirection, useResult import SensitivityAnalysisResultTab from './results/sensitivity-analysis/sensitivity-analysis-result-tab'; import { NonEvacuatedEnergyResultTab } from './results/sensitivity-analysis/non-evacuated-energy/non-evacuated-energy-result-tab'; import { OptionalServicesNames, OptionalServicesStatus } from './utils/optional-services'; -import { CurrentTreeNode, AppState } from '../redux/reducer'; +import { AppState, CurrentTreeNode } from '../redux/reducer'; import { UUID } from 'crypto'; import { useOptionalServiceStatus } from '../hooks/use-optional-service-status'; import { SecurityAnalysisResultTab } from './results/securityanalysis/security-analysis-result-tab'; @@ -26,8 +26,9 @@ import { LoadFlowResultTab } from './results/loadflow/load-flow-result-tab'; import ComputingType from './computing-status/computing-type'; import { useSelector } from 'react-redux'; import { usePrevious } from './utils/utils'; -import { Box, Tabs, Tab, Paper } from '@mui/material'; +import { Box, Paper, Tab, Tabs } from '@mui/material'; import { StateEstimationResultTab } from './results/stateestimation/state-estimation-result-tab'; +import DynamicSecurityAnalysisResultTab from './results/dynamic-security-analysis/dynamic-security-analysis-result-tab'; const styles = { table: { @@ -88,6 +89,7 @@ export const ResultViewTab: FunctionComponent = ({ const sensitivityAnalysisUnavailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const nonEvacuatedEnergyUnavailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const dynamicSimulationAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSimulation); + const dynamicSecurityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSecurityAnalysis); const voltageInitAvailability = useOptionalServiceStatus(OptionalServicesNames.VoltageInit); const shortCircuitAvailability = useOptionalServiceStatus(OptionalServicesNames.ShortCircuit); const stateEstimationAvailability = useOptionalServiceStatus(OptionalServicesNames.StateEstimation); @@ -178,6 +180,14 @@ export const ResultViewTab: FunctionComponent = ({ ); }, [studyUuid, currentNode, currentRootNetworkUuid]); + const renderDynamicSecurityAnalysisResult = useMemo(() => { + return ( + + + + ); + }, [studyUuid, currentNode]); + const renderStateEstimationResult = useMemo(() => { return ( @@ -228,6 +238,12 @@ export const ResultViewTab: FunctionComponent = ({ displayed: enableDeveloperMode && dynamicSimulationAvailability === OptionalServicesStatus.Up, renderResult: renderDynamicSimulationResult, }, + { + id: 'DynamicSecurityAnalysis', + computingType: [ComputingType.DYNAMIC_SECURITY_ANALYSIS], + displayed: enableDeveloperMode && dynamicSecurityAnalysisAvailability === OptionalServicesStatus.Up, + renderResult: renderDynamicSecurityAnalysisResult, + }, { id: 'VoltageInit', computingType: [ComputingType.VOLTAGE_INITIALIZATION], @@ -246,11 +262,13 @@ export const ResultViewTab: FunctionComponent = ({ nonEvacuatedEnergyUnavailability, securityAnalysisAvailability, dynamicSimulationAvailability, + dynamicSecurityAnalysisAvailability, voltageInitAvailability, shortCircuitAvailability, stateEstimationAvailability, enableDeveloperMode, renderDynamicSimulationResult, + renderDynamicSecurityAnalysisResult, renderSecurityAnalysisResult, renderSensitivityAnalysisResult, renderNonEvacuatedEnergyResult, diff --git a/src/components/results/common/result-cell-renderers.tsx b/src/components/results/common/result-cell-renderers.tsx index 61b35bbcd8..d4bbbea97a 100644 --- a/src/components/results/common/result-cell-renderers.tsx +++ b/src/components/results/common/result-cell-renderers.tsx @@ -28,7 +28,7 @@ const styles = { export const StatusCellRender = (cellData: ICellRendererParams) => { const status = cellData.value; - const color = status === 'CONVERGED' ? styles.succeed : styles.fail; + const color = status === 'CONVERGED' || status === 'SUCCEED' ? styles.succeed : styles.fail; return (
diff --git a/src/components/results/common/utils.ts b/src/components/results/common/utils.ts index f997c9d4f3..968e75d6dd 100644 --- a/src/components/results/common/utils.ts +++ b/src/components/results/common/utils.ts @@ -9,6 +9,10 @@ import { NA_Value } from '../../spreadsheet/utils/cell-renderers'; export const PERMANENT_LIMIT_NAME = 'permanent'; +export const MIN_COLUMN_WIDTH = 160; +export const MEDIUM_COLUMN_WIDTH = 220; +export const LARGE_COLUMN_WIDTH = 340; + export const translateLimitNameBackToFront = (limitName: string | null | undefined, intl: IntlShape) => { switch (limitName) { case PERMANENT_LIMIT_NAME: diff --git a/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-logs.tsx b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-logs.tsx new file mode 100644 index 0000000000..a628f96dbb --- /dev/null +++ b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-logs.tsx @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { ComputationReportViewer } from '../common/computation-report-viewer'; +import { memo, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from '../../../redux/reducer'; +import ComputingType from '../../computing-status/computing-type'; +import RunningStatus from '../../utils/running-status'; +import { useIntlResultStatusMessages } from '../../utils/aggrid-rows-handler'; +import { useIntl } from 'react-intl'; +import Overlay from '../common/Overlay'; + +const DynamicSecurityAnalysisResultLogs = memo(() => { + const dynamicSecurityAnalysisStatus = useSelector( + (state: AppState) => state.computingStatus[ComputingType.DYNAMIC_SECURITY_ANALYSIS] + ); + + const intl = useIntl(); + const messages = useIntlResultStatusMessages(intl); + + const overlayMessage = useMemo(() => { + switch (dynamicSecurityAnalysisStatus) { + case RunningStatus.IDLE: + return messages.noCalculation; + case RunningStatus.RUNNING: + return messages.running; + case RunningStatus.FAILED: + case RunningStatus.SUCCEED: + return undefined; + default: + return messages.noCalculation; + } + }, [dynamicSecurityAnalysisStatus, messages]); + return ( + + + + ); +}); + +export default DynamicSecurityAnalysisResultLogs; diff --git a/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-synthesis.tsx b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-synthesis.tsx new file mode 100644 index 0000000000..b875e4d587 --- /dev/null +++ b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-synthesis.tsx @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { useIntl } from 'react-intl'; +import { Box, LinearProgress } from '@mui/material'; +import { memo, useMemo } from 'react'; +import { useNodeData } from '../../study-container'; +import { useSelector } from 'react-redux'; +import ComputingType from '../../computing-status/computing-type'; +import { getNoRowsMessage, useIntlResultStatusMessages } from '../../utils/aggrid-rows-handler'; +import { makeAgGridCustomHeaderColumn } from '../../custom-aggrid/custom-aggrid-header-utils'; +import { DefaultCellRenderer } from '../../spreadsheet/utils/cell-renderers'; +import { StatusCellRender } from '../common/result-cell-renderers'; +import { UUID } from 'crypto'; +import RunningStatus from '../../utils/running-status'; +import { AppState } from '../../../redux/reducer'; +import { CustomAGGrid } from '@gridsuite/commons-ui'; +import { fetchDynamicSecurityAnalysisStatus } from '../../../services/study/dynamic-security-analysis'; +import { dynamicSecurityAnalysisResultInvalidations } from './utils/dynamic-security-analysis-result-utils'; +import { MEDIUM_COLUMN_WIDTH } from '../common/utils'; + +const styles = { + loader: { + height: '4px', + }, +}; + +const defaultColDef = { + filter: true, + sortable: true, + resizable: true, + lockPinned: true, + suppressMovable: true, + wrapHeaderText: true, + autoHeaderHeight: true, + cellRenderer: DefaultCellRenderer, +}; + +type DynamicSecurityAnalysisResultSynthesisProps = { + studyUuid: UUID; + nodeUuid: UUID; + currentRootNetworkUuid: UUID; +}; + +const DynamicSecurityAnalysisResultSynthesis = memo( + ({ nodeUuid, studyUuid, currentRootNetworkUuid }: DynamicSecurityAnalysisResultSynthesisProps) => { + const intl = useIntl(); + + const [result, isLoading] = useNodeData( + studyUuid, + nodeUuid, + currentRootNetworkUuid, + fetchDynamicSecurityAnalysisStatus, + dynamicSecurityAnalysisResultInvalidations, + null, + (status: RunningStatus) => + status && [ + { + status, + }, + ] + ); + + const columnDefs = useMemo( + () => [ + makeAgGridCustomHeaderColumn({ + headerName: intl.formatMessage({ + id: 'status', + }), + colId: 'status', + field: 'status', + width: MEDIUM_COLUMN_WIDTH, + cellRenderer: StatusCellRender, + }), + ], + [intl] + ); + + // messages to show when no data + const dynamicSecurityAnalysisStatus = useSelector( + (state: AppState) => state.computingStatus[ComputingType.DYNAMIC_SECURITY_ANALYSIS] + ); + const messages = useIntlResultStatusMessages(intl, true); + const overlayMessage = useMemo( + () => getNoRowsMessage(messages, result, dynamicSecurityAnalysisStatus, !isLoading), + [messages, result, dynamicSecurityAnalysisStatus, isLoading] + ); + + const rowDataToShow = useMemo(() => (overlayMessage ? [] : result), [result, overlayMessage]); + + return ( + <> + {isLoading && ( + + + + )} + + + ); + } +); + +export default DynamicSecurityAnalysisResultSynthesis; diff --git a/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-tab.tsx b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-tab.tsx new file mode 100644 index 0000000000..d990b64b5b --- /dev/null +++ b/src/components/results/dynamic-security-analysis/dynamic-security-analysis-result-tab.tsx @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { SyntheticEvent, useState } from 'react'; +import { Box, Tab, Tabs } from '@mui/material'; +import { useIntl } from 'react-intl'; +import TabPanelLazy from '../common/tab-panel-lazy'; +import { UUID } from 'crypto'; +import DynamicSecurityAnalysisResultSynthesis from './dynamic-security-analysis-result-synthesis'; +import DynamicSecurityAnalysisResultLogs from './dynamic-security-analysis-result-logs'; + +const styles = { + resultContainer: { + flexGrow: 1, + }, +}; + +const TAB_INDEX_STATUS = 'DynamicSecurityAnalysisTabStatus'; +const TAB_INDEX_LOGS = 'ComputationResultsLogs'; + +interface DynamicSecurityAnalysisResultTabProps { + studyUuid: UUID; + nodeUuid: UUID; + currentRootNetworkUuid: UUID; +} + +function DynamicSecurityAnalysisResultTab({ + studyUuid, + nodeUuid, + currentRootNetworkUuid, +}: Readonly) { + const intl = useIntl(); + + const [tabIndex, setTabIndex] = useState(TAB_INDEX_STATUS); + + const handleTabChange = (event: SyntheticEvent, newTabIndex: string) => { + setTabIndex(newTabIndex); + }; + + return ( + <> + + + + + + + + + + + + + + + + ); +} + +export default DynamicSecurityAnalysisResultTab; diff --git a/src/components/results/dynamic-security-analysis/utils/dynamic-security-analysis-result-utils.ts b/src/components/results/dynamic-security-analysis/utils/dynamic-security-analysis-result-utils.ts new file mode 100644 index 0000000000..b056274299 --- /dev/null +++ b/src/components/results/dynamic-security-analysis/utils/dynamic-security-analysis-result-utils.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export const dynamicSecurityAnalysisResultInvalidations = ['dynamicSecurityAnalysisResult']; diff --git a/src/components/results/dynamicsimulation/dynamic-simulation-result-synthesis.tsx b/src/components/results/dynamicsimulation/dynamic-simulation-result-synthesis.tsx index 40331e760d..0c4d06c835 100644 --- a/src/components/results/dynamicsimulation/dynamic-simulation-result-synthesis.tsx +++ b/src/components/results/dynamicsimulation/dynamic-simulation-result-synthesis.tsx @@ -10,7 +10,7 @@ import { Box, LinearProgress } from '@mui/material'; import { memo, useMemo } from 'react'; import { useNodeData } from '../../study-container'; import { fetchDynamicSimulationStatus } from '../../../services/study/dynamic-simulation'; -import { dynamicSimulationResultInvalidations, MEDIUM_COLUMN_WIDTH } from './utils/dynamic-simulation-result-utils'; +import { dynamicSimulationResultInvalidations } from './utils/dynamic-simulation-result-utils'; import { useSelector } from 'react-redux'; import ComputingType from '../../computing-status/computing-type'; import { getNoRowsMessage, useIntlResultStatusMessages } from '../../utils/aggrid-rows-handler'; @@ -21,6 +21,7 @@ import { UUID } from 'crypto'; import RunningStatus from '../../utils/running-status'; import { AppState } from '../../../redux/reducer'; import { CustomAGGrid } from '@gridsuite/commons-ui'; +import { MEDIUM_COLUMN_WIDTH } from '../common/utils'; const styles = { loader: { diff --git a/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx b/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx index aa0233b4a2..fc9b6d0227 100644 --- a/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx +++ b/src/components/results/dynamicsimulation/dynamic-simulation-result-timeline.tsx @@ -22,12 +22,7 @@ import ComputingType from '../../computing-status/computing-type'; import { updateFilters } from '../../custom-aggrid/custom-aggrid-filters/utils/aggrid-filters-utils'; import { TimelineEventKeyType } from './types/dynamic-simulation-result.type'; -import { - dynamicSimulationResultInvalidations, - LARGE_COLUMN_WIDTH, - MEDIUM_COLUMN_WIDTH, - MIN_COLUMN_WIDTH, -} from './utils/dynamic-simulation-result-utils'; +import { dynamicSimulationResultInvalidations } from './utils/dynamic-simulation-result-utils'; import { useNodeData } from '../../study-container'; import { fetchDynamicSimulationResultTimeline } from '../../../services/dynamic-simulation'; import { NumberCellRenderer } from '../common/result-cell-renderers'; @@ -36,6 +31,7 @@ import { CustomAGGrid } from '@gridsuite/commons-ui'; import { CustomAggridComparatorFilter } from '../../custom-aggrid/custom-aggrid-filters/custom-aggrid-comparator-filter'; import { AgGridReact } from 'ag-grid-react'; import { FilterType } from '../../../types/custom-aggrid-types'; +import { LARGE_COLUMN_WIDTH, MEDIUM_COLUMN_WIDTH, MIN_COLUMN_WIDTH } from '../common/utils'; const styles = { loader: { diff --git a/src/components/results/dynamicsimulation/utils/dynamic-simulation-result-utils.ts b/src/components/results/dynamicsimulation/utils/dynamic-simulation-result-utils.ts index 0b25004358..89ef72397d 100644 --- a/src/components/results/dynamicsimulation/utils/dynamic-simulation-result-utils.ts +++ b/src/components/results/dynamicsimulation/utils/dynamic-simulation-result-utils.ts @@ -6,8 +6,3 @@ */ export const dynamicSimulationResultInvalidations = ['dynamicSimulationResult']; - -// These constants are taken from components/spreadsheet/utils/config-tables.js -export const MIN_COLUMN_WIDTH = 160; -export const MEDIUM_COLUMN_WIDTH = 220; -export const LARGE_COLUMN_WIDTH = 340; diff --git a/src/components/run-button-container.jsx b/src/components/run-button-container.jsx index 7bc95f9372..6d784034a3 100644 --- a/src/components/run-button-container.jsx +++ b/src/components/run-button-container.jsx @@ -8,7 +8,7 @@ import { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { setComputingStatus, setComputationStarting, setLogsFilter } from '../redux/actions'; +import { setComputationStarting, setComputingStatus, setLogsFilter } from '../redux/actions'; import { useDispatch, useSelector } from 'react-redux'; import RunningStatus from './utils/running-status'; @@ -34,6 +34,7 @@ import { startVoltageInit, stopVoltageInit } from '../services/study/voltage-ini import { startStateEstimation, stopStateEstimation } from '../services/study/state-estimation'; import { OptionalServicesNames, OptionalServicesStatus } from './utils/optional-services'; import { useOptionalServiceStatus } from '../hooks/use-optional-service-status'; +import { startDynamicSecurityAnalysis, stopDynamicSecurityAnalysis } from '../services/study/dynamic-security-analysis'; export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkUuid, disabled }) { const loadFlowStatus = useSelector((state) => state.computingStatus[ComputingType.LOAD_FLOW]); @@ -50,6 +51,9 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU ); const dynamicSimulationStatus = useSelector((state) => state.computingStatus[ComputingType.DYNAMIC_SIMULATION]); + const dynamicSecurityAnalysisStatus = useSelector( + (state) => state.computingStatus[ComputingType.DYNAMIC_SECURITY_ANALYSIS] + ); const voltageInitStatus = useSelector((state) => state.computingStatus[ComputingType.VOLTAGE_INITIALIZATION]); const stateEstimationStatus = useSelector((state) => state.computingStatus[ComputingType.STATE_ESTIMATION]); @@ -74,6 +78,7 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU const nonEvacuatedEnergyUnavailability = useOptionalServiceStatus(OptionalServicesNames.SensitivityAnalysis); const dynamicSimulationAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSimulation); + const dynamicSecurityAnalysisAvailability = useOptionalServiceStatus(OptionalServicesNames.DynamicSecurityAnalysis); const voltageInitAvailability = useOptionalServiceStatus(OptionalServicesNames.VoltageInit); const shortCircuitAvailability = useOptionalServiceStatus(OptionalServicesNames.ShortCircuit); const stateEstimationAvailability = useOptionalServiceStatus(OptionalServicesNames.StateEstimation); @@ -267,6 +272,25 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU ); }, }, + [ComputingType.DYNAMIC_SECURITY_ANALYSIS]: { + messageId: 'DynamicSecurityAnalysis', + startComputation() { + startComputationAsync( + ComputingType.DYNAMIC_SECURITY_ANALYSIS, + null, + () => startDynamicSecurityAnalysis(studyUuid, currentNode?.id, currentRootNetworkUuid), + () => {}, + null, + 'startDynamicSecurityAnalysisError' + ); + }, + actionOnRunnable() { + actionOnRunnables(ComputingType.DYNAMIC_SECURITY_ANALYSIS, () => + stopDynamicSecurityAnalysis(studyUuid, currentNode?.id, currentRootNetworkUuid) + ); + }, + }, + [ComputingType.VOLTAGE_INITIALIZATION]: { messageId: 'VoltageInit', startComputation() { @@ -332,6 +356,8 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU return allBusesShortCircuitAnalysisStatus; case ComputingType.DYNAMIC_SIMULATION: return dynamicSimulationStatus; + case ComputingType.DYNAMIC_SECURITY_ANALYSIS: + return dynamicSecurityAnalysisStatus; case ComputingType.VOLTAGE_INITIALIZATION: return voltageInitStatus; case ComputingType.STATE_ESTIMATION: @@ -347,6 +373,7 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU nonEvacuatedEnergyStatus, allBusesShortCircuitAnalysisStatus, dynamicSimulationStatus, + dynamicSecurityAnalysisStatus, voltageInitStatus, stateEstimationStatus, ] @@ -367,6 +394,9 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU ...(dynamicSimulationAvailability === OptionalServicesStatus.Up && enableDeveloperMode ? [ComputingType.DYNAMIC_SIMULATION] : []), + ...(dynamicSecurityAnalysisAvailability === OptionalServicesStatus.Up && enableDeveloperMode + ? [ComputingType.DYNAMIC_SECURITY_ANALYSIS] + : []), ...(voltageInitAvailability === OptionalServicesStatus.Up ? [ComputingType.VOLTAGE_INITIALIZATION] : []), ...(stateEstimationAvailability === OptionalServicesStatus.Up && enableDeveloperMode ? [ComputingType.STATE_ESTIMATION] @@ -374,6 +404,7 @@ export function RunButtonContainer({ studyUuid, currentNode, currentRootNetworkU ]; }, [ dynamicSimulationAvailability, + dynamicSecurityAnalysisAvailability, securityAnalysisAvailability, sensitivityAnalysisUnavailability, nonEvacuatedEnergyUnavailability, diff --git a/src/components/run-button.jsx b/src/components/run-button.jsx index 83dac6df24..183a283424 100644 --- a/src/components/run-button.jsx +++ b/src/components/run-button.jsx @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { useEffect, useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; @@ -50,16 +50,26 @@ const RunButton = ({ runnables, activeRunnables, getStatus, computationStopped, if (selectedRunnable === ComputingType.LOAD_FLOW) { // We run once loadflow analysis, as it will always return the same result for one hypothesis return getRunningStatus() !== RunningStatus.IDLE; - } else if (selectedRunnable === ComputingType.DYNAMIC_SIMULATION) { + } + + if (selectedRunnable === ComputingType.DYNAMIC_SIMULATION) { // Load flow button's status must be "SUCCEED" return ( getRunningStatus() === RunningStatus.RUNNING || getStatus(ComputingType.LOAD_FLOW) !== RunningStatus.SUCCEED ); - } else { - // We can run only 1 computation at a time - return getRunningStatus() === RunningStatus.RUNNING; } + + if (selectedRunnable === ComputingType.DYNAMIC_SECURITY_ANALYSIS) { + // Dynamic simulation button's status must be "SUCCEED" + return ( + getRunningStatus() === RunningStatus.RUNNING || + getStatus(ComputingType.DYNAMIC_SIMULATION) !== RunningStatus.SUCCEED + ); + } + + // We can run only 1 computation at a time + return getRunningStatus() === RunningStatus.RUNNING; } return ( diff --git a/src/components/study-container.jsx b/src/components/study-container.jsx index c80371b204..31a6f1060f 100644 --- a/src/components/study-container.jsx +++ b/src/components/study-container.jsx @@ -13,24 +13,24 @@ import { useDispatch, useSelector } from 'react-redux'; import { PARAMS_LOADED } from '../utils/config-params'; import { closeStudy, + limitReductionModified, loadNetworkModificationTreeSuccess, openStudy, - studyUpdated, - setCurrentTreeNode, resetEquipments, resetEquipmentsPostLoadflow, + setCurrentTreeNode, setStudyIndexationStatus, - limitReductionModified, setCurrentRootNetwork, + studyUpdated, } from '../redux/actions'; import { fetchRootNetworks } from 'services/root-network'; import WaitingLoader from './utils/waiting-loader'; -import { useIntlRef, useSnackMessage } from '@gridsuite/commons-ui'; +import { fetchDirectoryElementPath, useIntlRef, useSnackMessage } from '@gridsuite/commons-ui'; import NetworkModificationTreeModel from './graph/network-modification-tree-model'; import { getFirstNodeOfType, isNodeBuilt, isNodeRenamed, isSameNode } from './graph/util/model-functions'; import { RunningStatus } from './utils/running-status'; -import { computePageTitle, computeFullPath } from '../utils/compute-title'; +import { computeFullPath, computePageTitle } from '../utils/compute-title'; import { directoriesNotificationType } from '../utils/directories-notification-type'; import { BUILD_STATUS } from './network/constants'; import { connectNotificationsWebsocket } from '../services/study-notification'; @@ -48,7 +48,6 @@ import { invalidateLoadFlowStatus } from 'services/study/loadflow'; import { HttpStatusCode } from 'utils/http-status-code'; import { usePrevious } from './utils/utils'; import { StudyIndexationStatus } from 'redux/reducer'; -import { fetchDirectoryElementPath } from '@gridsuite/commons-ui'; import { NodeType } from './graph/tree-node.type'; function isWorthUpdate( @@ -331,6 +330,12 @@ export function StudyContainer({ view, onChangeTab }) { messageTxt: errorMessage, }); } + if (updateTypeHeader === 'dynamicSecurityAnalysis_failed') { + snackError({ + headerId: 'DynamicSecurityAnalysisRunError', + messageTxt: errorMessage, + }); + } if (updateTypeHeader === 'voltageInit_failed') { snackError({ headerId: 'voltageInitError', diff --git a/src/components/utils/optional-services.ts b/src/components/utils/optional-services.ts index 6b67668ad6..d298648730 100644 --- a/src/components/utils/optional-services.ts +++ b/src/components/utils/optional-services.ts @@ -9,6 +9,7 @@ export enum OptionalServicesNames { SecurityAnalysis = 'SecurityAnalysis', SensitivityAnalysis = 'SensitivityAnalysis', DynamicSimulation = 'DynamicSimulation', + DynamicSecurityAnalysis = 'DynamicSecurityAnalysis', ShortCircuit = 'ShortCircuit', VoltageInit = 'VoltageInit', StateEstimation = 'StateEstimation', @@ -31,6 +32,8 @@ export const getOptionalServiceByServerName = (serverName: string): OptionalServ return OptionalServicesNames.SensitivityAnalysis; case 'dynamic-simulation-server': return OptionalServicesNames.DynamicSimulation; + case 'dynamic-security-analysis-server': + return OptionalServicesNames.DynamicSecurityAnalysis; case 'shortcircuit-server': return OptionalServicesNames.ShortCircuit; case 'voltage-init-server': diff --git a/src/components/utils/running-status.ts b/src/components/utils/running-status.ts index 6f59245620..e97a667b09 100644 --- a/src/components/utils/running-status.ts +++ b/src/components/utils/running-status.ts @@ -104,6 +104,21 @@ export function getDynamicSimulationRunningStatus(dynamicSimulationStatus: strin } } +export function getDynamicSecurityAnalysisRunningStatus(dynamicSecurityAnalysisStatus: string): RunningStatus { + switch (dynamicSecurityAnalysisStatus) { + case 'SUCCEED': + return RunningStatus.SUCCEED; + case 'FAILED': + return RunningStatus.FAILED; + case 'RUNNING': + return RunningStatus.RUNNING; + case 'NOT_DONE': + return RunningStatus.IDLE; + default: + return RunningStatus.IDLE; + } +} + export function getVoltageInitRunningStatus(voltageInitStatus: string): RunningStatus { switch (voltageInitStatus) { case 'OK': diff --git a/src/hooks/use-computation-results-count.ts b/src/hooks/use-computation-results-count.ts index 70a5c1ebd0..a3c404cf38 100644 --- a/src/hooks/use-computation-results-count.ts +++ b/src/hooks/use-computation-results-count.ts @@ -43,6 +43,10 @@ export const useComputationResultsCount = () => { (state: AppState) => state.computingStatus[ComputingType.DYNAMIC_SIMULATION] ); + const dynamicSecurityAnalysisStatus = useSelector( + (state: AppState) => state.computingStatus[ComputingType.DYNAMIC_SECURITY_ANALYSIS] + ); + const voltageInitStatus = useSelector( (state: AppState) => state.computingStatus[ComputingType.VOLTAGE_INITIALIZATION] ); @@ -72,6 +76,10 @@ export const useComputationResultsCount = () => { const dynamicSimulationResultPresent = dynamicSimulationStatus === RunningStatus.SUCCEED || dynamicSimulationStatus === RunningStatus.FAILED; // Can be failed for technical reasons (e.g., server not responding or computation divergence) + const dynamicSecurityAnalysisResultPresent = + dynamicSecurityAnalysisStatus === RunningStatus.SUCCEED || + dynamicSecurityAnalysisStatus === RunningStatus.FAILED; // Can be failed for technical reasons (e.g., server not responding or computation divergence) + const stateEstimationResultPresent = enableDeveloperMode && (stateEstimationStatus === RunningStatus.SUCCEED || voltageInitStatus === RunningStatus.FAILED); // Can be failed for technical reasons (e.g., server not responding or computation divergence) @@ -85,6 +93,7 @@ export const useComputationResultsCount = () => { oneBusShortCircuitResultPresent, voltageInitResultPresent, dynamicSimulationResultPresent, + dynamicSecurityAnalysisResultPresent, stateEstimationResultPresent, ].filter(Boolean).length; }; diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index baddede487..f1558ee251 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -286,9 +286,9 @@ import { import { UUID } from 'crypto'; import { Filter } from '../components/results/common/results-global-filter'; import { + EQUIPMENT_TYPES as NetworkViewerEquipmentType, LineFlowColorMode, LineFlowMode, - EQUIPMENT_TYPES as NetworkViewerEquipmentType, } from '@powsybl/network-viewer'; import type { UnknownArray, ValueOf } from 'type-fest'; import { Node } from '@xyflow/react'; @@ -402,6 +402,7 @@ export interface ComputingStatus { [ComputingType.SHORT_CIRCUIT]: RunningStatus; [ComputingType.SHORT_CIRCUIT_ONE_BUS]: RunningStatus; [ComputingType.DYNAMIC_SIMULATION]: RunningStatus; + [ComputingType.DYNAMIC_SECURITY_ANALYSIS]: RunningStatus; [ComputingType.VOLTAGE_INITIALIZATION]: RunningStatus; [ComputingType.STATE_ESTIMATION]: RunningStatus; } @@ -576,6 +577,7 @@ const initialLogsFilterState: LogsFilterState = { [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.SHORT_CIRCUIT]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.SHORT_CIRCUIT_ONE_BUS]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.DYNAMIC_SIMULATION]: [], + [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.DYNAMIC_SECURITY_ANALYSIS]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.VOLTAGE_INITIALIZATION]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.STATE_ESTIMATION]: [], [COMPUTING_AND_NETWORK_MODIFICATION_TYPE.NON_EVACUATED_ENERGY_ANALYSIS]: [], @@ -690,6 +692,7 @@ const initialState: AppState = { [ComputingType.SHORT_CIRCUIT]: RunningStatus.IDLE, [ComputingType.SHORT_CIRCUIT_ONE_BUS]: RunningStatus.IDLE, [ComputingType.DYNAMIC_SIMULATION]: RunningStatus.IDLE, + [ComputingType.DYNAMIC_SECURITY_ANALYSIS]: RunningStatus.IDLE, [ComputingType.VOLTAGE_INITIALIZATION]: RunningStatus.IDLE, [ComputingType.STATE_ESTIMATION]: RunningStatus.IDLE, }, diff --git a/src/services/directory.ts b/src/services/directory.ts index 21f9ae7ba4..c76ac20568 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -7,10 +7,11 @@ import { backendFetchJson, getRequestParamFromList } from './utils'; import { UnknownArray } from 'type-fest'; +import { ElementAttributes } from '@gridsuite/commons-ui'; const PREFIX_DIRECTORY_SERVER_QUERIES = import.meta.env.VITE_API_GATEWAY + '/directory'; -export function fetchContingencyAndFiltersLists(listIds: UnknownArray) { +export function fetchContingencyAndFiltersLists(listIds: UnknownArray): Promise { console.info('Fetching contingency and filters lists'); // Add params to Url diff --git a/src/services/study/dynamic-security-analysis.ts b/src/services/study/dynamic-security-analysis.ts new file mode 100644 index 0000000000..6628964340 --- /dev/null +++ b/src/services/study/dynamic-security-analysis.ts @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { getStudyUrl, getStudyUrlWithNodeUuidAndRootNetworkUuid, PREFIX_STUDY_QUERIES } from './index'; + +import { backendFetch, backendFetchJson, backendFetchText } from '../utils'; +import { UUID } from 'crypto'; +import { + DynamicSecurityAnalysisParametersFetchReturn, + DynamicSecurityAnalysisParametersInfos, +} from './dynamic-security-analysis.type'; +import { fetchContingencyAndFiltersLists } from '../directory'; + +const PREFIX_DYNAMIC_SECURITY_ANALYSIS_SERVER_QUERIES = import.meta.env.VITE_API_GATEWAY + '/dynamic-security-analysis'; + +function getDynamicSecurityAnalysisUrl() { + return `${PREFIX_DYNAMIC_SECURITY_ANALYSIS_SERVER_QUERIES}/v1/`; +} + +export function fetchDynamicSecurityAnalysisProviders() { + console.info('fetch dynamic simulation providers'); + const url = getDynamicSecurityAnalysisUrl() + 'providers'; + console.debug(url); + return backendFetchJson(url); +} + +export function startDynamicSecurityAnalysis(studyUuid: UUID, currentNodeUuid: UUID, currentRootNetworkUuid: UUID) { + console.info(`Running dynamic security analysis on '${studyUuid}' and node '${currentNodeUuid}' ...`); + + const startDynamicSecurityAnalysisUrl = `${getStudyUrlWithNodeUuidAndRootNetworkUuid( + studyUuid, + currentNodeUuid, + currentRootNetworkUuid + )}/dynamic-security-analysis/run`; + + console.debug({ startDynamicSecurityAnalysisUrl }); + + return backendFetch(startDynamicSecurityAnalysisUrl, { + method: 'post', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }); +} + +export function stopDynamicSecurityAnalysis(studyUuid: UUID, currentNodeUuid: UUID, currentRootNetworkUuid: UUID) { + console.info(`Stopping dynamic security analysis on '${studyUuid}' and node '${currentNodeUuid}' ...`); + const stopDynamicSecurityAnalysisUrl = + getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, currentNodeUuid, currentRootNetworkUuid) + + '/dynamic-security-analysis/stop'; + console.debug(stopDynamicSecurityAnalysisUrl); + return backendFetch(stopDynamicSecurityAnalysisUrl, { method: 'put' }); +} + +export function fetchDynamicSecurityAnalysisStatus( + studyUuid: UUID, + currentNodeUuid: UUID, + currentRootNetworkUuid: UUID +) { + console.info(`Fetching dynamic security analysis status on '${studyUuid}' and node '${currentNodeUuid}' ...`); + const url = + getStudyUrlWithNodeUuidAndRootNetworkUuid(studyUuid, currentNodeUuid, currentRootNetworkUuid) + + '/dynamic-security-analysis/status'; + console.debug(url); + return backendFetchJson(url); +} + +export function fetchDefaultDynamicSecurityAnalysisProvider() { + console.info('fetch default dynamic security analysis provider'); + const url = PREFIX_STUDY_QUERIES + '/v1/dynamic-security-analysis-default-provider'; + console.debug(url); + return backendFetchText(url); +} + +export function updateDynamicSecurityAnalysisProvider(studyUuid: UUID, newProvider: string) { + console.info('update dynamic security analysis provider'); + const url = getStudyUrl(studyUuid) + '/dynamic-security-analysis/provider'; + console.debug(url); + return backendFetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: newProvider, + }); +} + +export function fetchDynamicSecurityAnalysisParameters( + studyUuid: UUID +): Promise { + console.info(`Fetching dynamic security analysis parameters on '${studyUuid}' ...`); + const url = getStudyUrl(studyUuid) + '/dynamic-security-analysis/parameters'; + console.debug(url); + const parametersPromise: Promise = backendFetchJson(url); + + // enrich contingency list uuids by contingency list infos with id and name + return parametersPromise.then((parameters) => { + if (parameters?.contingencyListIds) { + return fetchContingencyAndFiltersLists(parameters?.contingencyListIds).then((contingencyListInfos) => { + delete parameters.contingencyListIds; + return { + ...parameters, + contingencyListInfos: contingencyListInfos?.map((info) => ({ + id: info.elementUuid, + name: info.elementName, + })), + }; + }); + } + delete parameters.contingencyListIds; + return { + ...parameters, + contingencyListInfos: [], + }; + }); +} + +export function updateDynamicSecurityAnalysisParameters( + studyUuid: UUID, + newParams: DynamicSecurityAnalysisParametersFetchReturn | null +): Promise { + console.info('set dynamic security analysis parameters'); + const url = getStudyUrl(studyUuid) + '/dynamic-security-analysis/parameters'; + console.debug(url); + + // send to back contingency list uuids instead of contingency list infos + const newParameters = + newParams != null + ? { + ...newParams, + contingencyListIds: newParams?.contingencyListInfos?.map((info) => info.id), + } + : newParams; + + delete newParameters?.contingencyListInfos; + + return backendFetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newParameters), + }); +} diff --git a/src/services/study/dynamic-security-analysis.type.ts b/src/services/study/dynamic-security-analysis.type.ts new file mode 100644 index 0000000000..4ac9b9e1d4 --- /dev/null +++ b/src/services/study/dynamic-security-analysis.type.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +export type DynamicSecurityAnalysisParametersInfos = { + provider?: string; + scenarioDuration?: number; + contingenciesStartTime?: number; + contingencyListIds?: string[]; +}; + +export type DynamicSecurityAnalysisParametersFetchReturn = Exclude< + DynamicSecurityAnalysisParametersInfos, + 'contingencyListIds' +> & { + contingencyListInfos?: { id: string; name: string }[]; +}; diff --git a/src/translations/en.json b/src/translations/en.json index 3033e1c97e..1bde968be2 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -226,6 +226,7 @@ "maxSelection": "selections maximum", "options": "options", + "Dynawo": "Dynawo", "DynamicSimulation": "Dynamic simulation", "DynamicSimulationGetMappingError": "Get dynamic mappings", "DynamicSimulationParametersChangeError": "Dynamic simulation parameters change error", @@ -254,7 +255,6 @@ "DynamicSimulationSeriesListAvailableCurves": "Available curves", "DynamicSimulationSeriesListRightAxis": "Right axis", "DynamicSimulationResultLayoutCols": "Columns", - "DynamicSimulationProviderDynawo": "Dynawo", "DynamicSimulationTimeDelay": "Time delay", "DynamicSimulationSolver": "Solver", "DynamicSimulationMapping": "Mapping", @@ -370,6 +370,15 @@ "DynamicSimulationTimelineEventModelName": "Equipment/Model", "DynamicSimulationTimelineEventModelMessage": "Message", + "startDynamicSecurityAnalysisError": "An error occurred while starting the dynamic security analysis", + "DynamicSecurityAnalysis": "Dynamic security analysis", + "DynamicSecurityAnalysisRunError": "An error occurred while executing the dynamic security analysis", + "DynamicSecurityAnalysisContingency": "Contingency", + "DynamicSecurityAnalysisScenario": "Scenario", + "DynamicSecurityAnalysisScenarioDuration": "Scenario duration", + "DynamicSecurityAnalysisContingenciesStartTime": "Contingencies start time", + "DynamicSecurityAnalysisTabStatus": "Status", + "TwoSides.ONE": "Origin side", "TwoSides.TWO": "Extremity side", diff --git a/src/translations/fr.json b/src/translations/fr.json index c44091b8b5..12422a1380 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -255,7 +255,6 @@ "DynamicSimulationSeriesListAvailableCurves": "Courbes disponibles", "DynamicSimulationSeriesListRightAxis": "Axe droit", "DynamicSimulationResultLayoutCols": "Colonnes", - "DynamicSimulationProviderDynawo": "Dynawo", "DynamicSimulationTimeDelay": "Temporisation", "DynamicSimulationSolver": "Solveur", "DynamicSimulationMapping": "Mappage", @@ -371,6 +370,15 @@ "DynamicSimulationTimelineEventModelName": "Ouvrage/Modèle", "DynamicSimulationTimelineEventModelMessage": "Message", + "startDynamicSecurityAnalysisError": "Une erreur est survenue lors du lancement de l'analyse de sécurité dynamique", + "DynamicSecurityAnalysis": "Analyse de sécurité dynamique", + "DynamicSecurityAnalysisRunError": "Une erreur est survenue lors de l'exécution de l'analyse de sécurité dynamique", + "DynamicSecurityAnalysisContingency": "Aléas", + "DynamicSecurityAnalysisScenario": "Scénario", + "DynamicSecurityAnalysisScenarioDuration": "Duration du scenario", + "DynamicSecurityAnalysisContingenciesStartTime": "Temps de début des aléas", + "DynamicSecurityAnalysisTabStatus": "Statut", + "TwoSides.ONE": "Côté 1", "TwoSides.TWO": "Côté 2",