Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Copyright (c) 2026, 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 { FunctionComponent, useMemo } from 'react';
import { useFieldArray } from 'react-hook-form';
import { useIntl } from 'react-intl';
import { IconButton, Tooltip } from '@mui/material';
import InfoIcon from '@mui/icons-material/Info';
import { DndColumn, DndColumnType, DndTable, ElementType, EquipmentType } from '@gridsuite/commons-ui';
import { AREA_INVALIDATIONS, FILTER, INVALIDATE, INVALIDATION_TYPE } from '../../../utils/field-constants';
import { InvalidationType, TabValue } from './state-estimation-parameters-utils';

const invalidationTypeLabelIds: Record<InvalidationType, string> = {
[InvalidationType.ERRONEOUS_TOPOLOGY_PV]: 'StateEstimationParametersInvalidationTypeErroneousTopologyPv',
[InvalidationType.ERRONEOUS_TOPOLOGY_PQ]: 'StateEstimationParametersInvalidationTypeErroneousTopologyPq',
[InvalidationType.NOT_FULLY_DESCRIBED]: 'StateEstimationParametersInvalidationTypeNotFullyDescribed',
};

export const StateEstimationAreaInvalidationParameters: FunctionComponent = () => {
const intl = useIntl();
const formName = `${TabValue.AREA_INVALIDATION}.${AREA_INVALIDATIONS}`;

const columnsDefinition = useMemo<DndColumn[]>(() => {
const filtersHeaderTooltip = (
<Tooltip
title={intl.formatMessage({ id: 'StateEstimationParametersAreaInvalidationTooltip' })}
placement="right"
>
<span>
<IconButton disabled size="small">
<InfoIcon fontSize="small" />
</IconButton>
</span>
</Tooltip>
);

return [
{
label: intl.formatMessage({ id: 'StateEstimationParametersInvalidateLabel' }),
dataKey: INVALIDATE,
initialValue: false,
editable: true,
type: DndColumnType.SWITCH,
dataTestId: 'SubstationAreaInvalidationInvalidateInput',
},
{
label: intl.formatMessage({ id: 'StateEstimationParametersSubstationFilterLabel' }),
dataKey: FILTER,
initialValue: [],
editable: true,
type: DndColumnType.DIRECTORY_ITEMS,
equipmentTypes: [EquipmentType.SUBSTATION],
elementType: ElementType.FILTER,
titleId: 'StateEstimationParametersAreaInvalidationFilterSelection',
dataTestId: 'SubstationAreaInvalidationFilterInput',
extra: filtersHeaderTooltip,
},
{
label: intl.formatMessage({ id: 'StateEstimationParametersInvalidationTypeLabel' }),
dataKey: INVALIDATION_TYPE,
initialValue: InvalidationType.ERRONEOUS_TOPOLOGY_PV,
editable: true,
type: DndColumnType.SELECT,
options: Object.values(InvalidationType).map((invalidationType) => ({
id: invalidationType,
label: intl.formatMessage({ id: invalidationTypeLabelIds[invalidationType] }),
})),
dataTestId: 'SubstationAreaInvalidationInvalidationTypeInput',
},
] satisfies DndColumn[];
Comment thread
benrejebmoh marked this conversation as resolved.
}, [intl]);

const useFieldArrayOutput = useFieldArray({ name: formName });

const createRows = () => {
const newRowData: Record<string, unknown> = {};
columnsDefinition.forEach((column) => (newRowData[column.dataKey] = column.initialValue));
return [newRowData];
};

return (
<DndTable
name={formName}
useFieldArrayOutput={useFieldArrayOutput}
createRows={createRows}
columnsDefinition={columnsDefinition}
tableHeight={440}
withAddRowsDialog={false}
rowDataTestId="SubstationAreaInvalidationLine"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import StateEstimationGeneralParameters from './state-estimation-general-paramet
import { StateEstimationWeightsParameters } from './state-estimation-weights-parameters';
import { StateEstimationQualityParameters } from './state-estimation-quality-parameters';
import { StateEstimationLoadboundsParameters } from './state-estimation-loadbounds-parameters';
import { StateEstimationAreaInvalidationParameters } from './state-estimation-area-invalidation-parameters';
import { getTabStyle } from '../../../utils/tab-utils';

interface StateEstimationParametersFormProps {
Expand Down Expand Up @@ -67,6 +68,11 @@ export const StateEstimationParametersForm = ({
value={TabValue.LOADBOUNDS}
sx={getTabStyle(tabIndexesWithError, TabValue.LOADBOUNDS)}
/>
<Tab
label={<FormattedMessage id="StateEstimationParametersAreaInvalidationTabLabel" />}
value={TabValue.AREA_INVALIDATION}
sx={getTabStyle(tabIndexesWithError, TabValue.AREA_INVALIDATION)}
/>
</Tabs>
<Grid container sx={styles.container}>
<Grid sx={styles.maxWidth}>
Expand All @@ -82,6 +88,9 @@ export const StateEstimationParametersForm = ({
<TabPanel value={tabValue} index={TabValue.LOADBOUNDS}>
<StateEstimationLoadboundsParameters />
</TabPanel>
<TabPanel value={tabValue} index={TabValue.AREA_INVALIDATION}>
<StateEstimationAreaInvalidationParameters />
</TabPanel>
</Grid>
</Grid>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import * as yup from 'yup';
import { YUP_REQUIRED } from '@gridsuite/commons-ui';
import {
DEFAULT_BOUNDS,
DEFAULT_FIXED_BOUNDS,
ESTIM_ALGO_TYPE,
ESTIM_LOG_LEVEL,
FILTER,
INVALIDATE,
INVALIDATION_TYPE,
P_MAX,
P_MIN,
AREA_INVALIDATIONS,
PRINCIPAL_OBSERVABLE_ZONE,
UNIQUE_PHASE,
Q_MAX,
Expand Down Expand Up @@ -63,6 +68,13 @@ export enum TabValue {
WEIGHTS = 'weights',
QUALITY = 'quality',
LOADBOUNDS = 'loadBounds',
AREA_INVALIDATION = 'areaInvalidation',
}

export enum InvalidationType {
ERRONEOUS_TOPOLOGY_PV = 'ERRONEOUS_TOPOLOGY_PV',
ERRONEOUS_TOPOLOGY_PQ = 'ERRONEOUS_TOPOLOGY_PQ',
NOT_FULLY_DESCRIBED = 'NOT_FULLY_DESCRIBED',
}

export const TAB_VALUES = Object.values(TabValue);
Expand Down Expand Up @@ -163,6 +175,15 @@ interface ThresholdsPerVoltageLevel extends ThresholdVoltageLevelCode {
interface ThresholdsPerVoltageLevelForm
extends VoltageLevelLabel, Omit<ThresholdsPerVoltageLevel, 'thresholdVoltageLevel'> {}

// one entry per substation filter; applied top to bottom, a later "invalidate" entry overrides an earlier
// one for the same substation (see the Area invalidation tab tooltip, StateEstimationParametersAreaInvalidationTooltip)
export interface AreaInvalidationInfos {
invalidate: boolean;
invalidationType: string;
filterUuid: string | null;
filterName: string | null;
}

export interface StateEstimationParameters {
estimParameters: {
principalObservableZone: boolean;
Expand Down Expand Up @@ -190,6 +211,7 @@ export interface StateEstimationParameters {
defaultFixedBounds: LoadBoundsDetailsParameters[];
};
};
areaInvalidations: AreaInvalidationInfos[];
}

const ESTIM_PARAMETERS = 'estimParameters';
Expand Down Expand Up @@ -265,6 +287,12 @@ export const fromStateEstimationParametersFormToParamValues = (
})),
},
},
[AREA_INVALIDATIONS]: params.areaInvalidation[AREA_INVALIDATIONS]?.map((areaInvalidation) => ({
[INVALIDATE]: areaInvalidation.invalidate,
[INVALIDATION_TYPE]: areaInvalidation.invalidationType,
filterUuid: areaInvalidation.filter?.[0]?.id ?? null,
filterName: areaInvalidation.filter?.[0]?.name ?? null,
})),
});

const mapVoltageLevelData = <T extends VoltageLevelCode | ThresholdVoltageLevelCode, U extends VoltageLevelLabel>(
Expand All @@ -279,34 +307,48 @@ const mapVoltageLevelData = <T extends VoltageLevelCode | ThresholdVoltageLevelC
});

export const fromStateEstimationParametersParamToFormValues = (
values: StateEstimationParameters['estimParameters']
): StateEstimationParametersForm => ({
[TabValue.GENERAL]: {
[PRINCIPAL_OBSERVABLE_ZONE]: values.principalObservableZone,
[UNIQUE_PHASE]: values.uniquePhase,
[ESTIM_LOG_LEVEL]: values.estimLogLevel,
[ESTIM_ALGO_TYPE]: values.estimAlgoType,
},
[TabValue.WEIGHTS]: {
[WEIGHTS_PARAMETERS]: mapVoltageLevelData<WeightsParameters, WeightParametersForm>(
values.weights.weightsParameters
),
},
[TabValue.QUALITY]: {
...values.quality,
[THRESHOLD_PER_VOLTAGE_LEVEL]: mapVoltageLevelData<ThresholdsPerVoltageLevel, ThresholdsPerVoltageLevelForm>(
values.quality.thresholdsPerVoltageLevel
),
},
[TabValue.LOADBOUNDS]: {
[DEFAULT_BOUNDS]: mapVoltageLevelData<LoadBoundsDetailsParameters, LoadBoundsDetailsParametersForm>(
values.loadBounds.defaultBounds
),
[DEFAULT_FIXED_BOUNDS]: mapVoltageLevelData<LoadBoundsDetailsParameters, LoadBoundsDetailsParametersForm>(
values.loadBounds.defaultFixedBounds
),
},
});
parameters: StateEstimationParameters
): StateEstimationParametersForm => {
const values = parameters.estimParameters;
return {
[TabValue.GENERAL]: {
[PRINCIPAL_OBSERVABLE_ZONE]: values.principalObservableZone,
[UNIQUE_PHASE]: values.uniquePhase,
[ESTIM_LOG_LEVEL]: values.estimLogLevel,
[ESTIM_ALGO_TYPE]: values.estimAlgoType,
},
[TabValue.WEIGHTS]: {
[WEIGHTS_PARAMETERS]: mapVoltageLevelData<WeightsParameters, WeightParametersForm>(
values.weights.weightsParameters
),
},
[TabValue.QUALITY]: {
...values.quality,
[THRESHOLD_PER_VOLTAGE_LEVEL]: mapVoltageLevelData<
ThresholdsPerVoltageLevel,
ThresholdsPerVoltageLevelForm
>(values.quality.thresholdsPerVoltageLevel),
},
[TabValue.LOADBOUNDS]: {
[DEFAULT_BOUNDS]: mapVoltageLevelData<LoadBoundsDetailsParameters, LoadBoundsDetailsParametersForm>(
values.loadBounds.defaultBounds
),
[DEFAULT_FIXED_BOUNDS]: mapVoltageLevelData<LoadBoundsDetailsParameters, LoadBoundsDetailsParametersForm>(
values.loadBounds.defaultFixedBounds
),
},
[TabValue.AREA_INVALIDATION]: {
[AREA_INVALIDATIONS]: (parameters.areaInvalidations ?? []).map((areaInvalidation) => ({
[INVALIDATE]: areaInvalidation.invalidate,
[INVALIDATION_TYPE]: areaInvalidation.invalidationType,
[FILTER]:
areaInvalidation.filterUuid != null
? [{ id: areaInvalidation.filterUuid, name: areaInvalidation.filterName ?? '' }]
: [],
})),
},
};
};

export const stateEstimationParametersFormSchema = yup.object().shape({
[TabValue.GENERAL]: yup.object().shape({
Expand Down Expand Up @@ -388,6 +430,28 @@ export const stateEstimationParametersFormSchema = yup.object().shape({
)
.required(),
}),
[TabValue.AREA_INVALIDATION]: yup.object().shape({
[AREA_INVALIDATIONS]: yup
.array()
.of(
yup.object().shape({
[INVALIDATE]: yup.boolean().required(),
[FILTER]: yup
.array()
.of(
yup.object().shape({
id: yup.string().required(),
name: yup.string(),
})
)
.min(1, YUP_REQUIRED)
.max(1)
.required(),
[INVALIDATION_TYPE]: yup.string().required(),
})
)
.required(),
}),
});

export type StateEstimationParametersForm = yup.InferType<typeof stateEstimationParametersFormSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ export const StateEstimationParameters = ({
const [stateEstimationParams, setStateEstimationParams] = useStateEstimationParameters;

const initialFormValues = useMemo(
() =>
stateEstimationParams
? fromStateEstimationParametersParamToFormValues(stateEstimationParams?.estimParameters)
: {},
() => (stateEstimationParams ? fromStateEstimationParametersParamToFormValues(stateEstimationParams) : {}),
[stateEstimationParams]
);

Expand Down Expand Up @@ -91,7 +88,7 @@ export const StateEstimationParameters = ({

useEffect(() => {
if (stateEstimationParams) {
reset(fromStateEstimationParametersParamToFormValues(stateEstimationParams.estimParameters));
reset(fromStateEstimationParametersParamToFormValues(stateEstimationParams));
}
}, [reset, stateEstimationParams]);

Expand Down
5 changes: 5 additions & 0 deletions src/components/utils/field-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ export const P_MAX = 'pmax';
export const Q_MIN = 'qmin';
export const Q_MAX = 'qmax';

export const AREA_INVALIDATIONS = 'areaInvalidations';
export const INVALIDATE = 'invalidate';
export const FILTER = 'filter';
export const INVALIDATION_TYPE = 'invalidationType';

/* State estimation extensions */
export const STATE_ESTIMATION = 'stateEstimation';
// Measurements common to Branch (line/2wt)
Expand Down
10 changes: 10 additions & 0 deletions src/translations/messages-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,7 @@ const messages_en = {
StateEstimationParametersWeightsTabLabel: 'Weights',
StateEstimationParametersQualityTabLabel: 'Thresholds',
StateEstimationParametersLoadboundsTabLabel: 'Loadbounds',
StateEstimationParametersAreaInvalidationTabLabel: 'Area invalidation',
StateEstimationParametersPrincipalObservableZoneLabel: 'Estimation on main observable zone',
StateEstimationParametersUniquePhaseLabel: 'Unique phase',
StateEstimationParametersAlgoTypeLabel: 'Algorithm',
Expand Down Expand Up @@ -1153,6 +1154,15 @@ const messages_en = {
pmax: 'Max P',
qmin: 'Min Q',
qmax: 'Max Q',
StateEstimationParametersInvalidateLabel: 'Invalidate',
StateEstimationParametersSubstationFilterLabel: 'Substations',
StateEstimationParametersAreaInvalidationFilterSelection: 'Substation filters selection',
StateEstimationParametersInvalidationTypeLabel: 'Invalidation type',
StateEstimationParametersInvalidationTypeErroneousTopologyPv: 'Erroneous topology PV',
StateEstimationParametersInvalidationTypeErroneousTopologyPq: 'Erroneous topology PQ',
StateEstimationParametersInvalidationTypeNotFullyDescribed: 'Not fully described',
StateEstimationParametersAreaInvalidationTooltip:
'Lines with an activated "Invalidate" option are applied one after another starting at the top of the list. If a substation is included in more than one filter, an entry can therefore be replaced by a subsequent entry.',
nodeAliasesRetrievingError: 'An error occured while retrieving node aliases',
nodeAliasesUpdateError: 'An error occured while updating node aliases',
previousConnection: 'Prev. connection',
Expand Down
10 changes: 10 additions & 0 deletions src/translations/messages-fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ const messages_fr = {
StateEstimationParametersWeightsTabLabel: 'Pondération',
StateEstimationParametersQualityTabLabel: 'Seuils',
StateEstimationParametersLoadboundsTabLabel: 'Bornes des consos',
StateEstimationParametersAreaInvalidationTabLabel: 'Invalidation par zone',
StateEstimationParametersPrincipalObservableZoneLabel: 'Estimation sur la zone principale observable',
StateEstimationParametersUniquePhaseLabel: "Frontières d'observabilité : minimisation des injections",
StateEstimationParametersAlgoTypeLabel: 'Algorithme',
Expand Down Expand Up @@ -1168,6 +1169,15 @@ const messages_fr = {
pmax: 'P Max',
qmin: 'Q Min',
qmax: 'Q Max',
StateEstimationParametersInvalidateLabel: 'Invalider',
StateEstimationParametersSubstationFilterLabel: 'Sites',
StateEstimationParametersAreaInvalidationFilterSelection: 'Sélection des filtres de sites',
StateEstimationParametersInvalidationTypeLabel: "Type d'invalidation",
StateEstimationParametersInvalidationTypeErroneousTopologyPv: 'Inobservable PV',
StateEstimationParametersInvalidationTypeErroneousTopologyPq: 'Inobservable PQ',
StateEstimationParametersInvalidationTypeNotFullyDescribed: 'Injection libre',
StateEstimationParametersAreaInvalidationTooltip:
'Les lignes avec l\'option "Invalider" activée sont appliquées dans l\'ordre de la liste (en remplaçant éventuellement des saisies au fur et à mesure si une partie du réseau est incluse dans plusieurs filtres).',
nodeAliasesRetrievingError: 'Erreur lors de la récupération des alias de nœuds',
nodeAliasesUpdateError: 'Erreur lors de la mise à jour des alias de nœuds',
previousConnection: 'Connexion préc.',
Expand Down
Loading