Skip to content

Commit f33e22c

Browse files
committed
front: add logic to handle non-feasibility in stdcm simulations
Signed-off-by: Achraf Mohyeddine <[email protected]>
1 parent aaae986 commit f33e22c

File tree

16 files changed

+495
-105
lines changed

16 files changed

+495
-105
lines changed

front/public/locales/en/stdcm.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@
4747
"downloadSimulationSheet": "Download simulation report sheet",
4848
"formatCreationDate": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}",
4949
"gesicoRequest": "and attach this document to your GESICO DSDM request.",
50-
"notFound": "No solution has been found for this configuration.",
50+
"notFound": "No solution was found for this configuration",
51+
"conflictsTitle": "Here are the problems encountered when calculating the shortest path:",
52+
"trackConflict": "conflict with a train path between <strong>{{waypointBefore}}</strong> and <strong>{{waypointAfter}}</strong> from {{startDate}} at {{startTime}} to {{endDate}} at {{endTime}}",
53+
"trackConflictSameDay": "conflict with a train path between <strong>{{waypointBefore}}</strong> and <strong>{{waypointAfter}}</strong> on {{startDate}} between {{startTime}} and {{endTime}}",
54+
"remainingTrackConflicts": "{{count}} other conflicts with other train paths",
55+
"remainingTrackConflicts_one": "one other conflict with another train path",
56+
"remainingWorkConflicts": "{{count}} other works in conflict",
57+
"remainingWorkConflicts_one": "one other work in conflict",
58+
"workConflictSameDay": "scheduled work from <strong>{{waypointBefore}}</strong> to <strong>{{waypointAfter}}</strong> on {{startDate}} between {{startTime}} and {{endTime}}",
59+
"workConflict": "scheduled work from <strong>{{waypointBefore}}</strong> to <strong>{{waypointAfter}}</strong> from {{startDate}} at {{startTime}} to {{endDate}} at {{endTime}}",
60+
"changeSearchCriteria": "You can modify your search criteria to find a solution",
5161
"retainThisSimulation": "Retain this simulation",
5262
"simulationName": {
5363
"withOutputs": "Simulation n°{{id}}",

front/public/locales/fr/stdcm.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@
4747
"downloadSimulationSheet": "Télécharger la fiche de simulation",
4848
"formatCreationDate": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}",
4949
"gesicoRequest": "et joignez ce document à votre demande GESICO DSDM",
50-
"notFound": "Aucune solution n’a été trouvée pour cette configuration",
50+
"notFound": "Aucune solution n'a été trouvée pour cette configuration",
51+
"conflictsTitle": "Voici les problèmes rencontrés au moment du calcul sur le chemin le plus court :",
52+
"trackConflict": "conflit avec un autre sillon entre <strong>{{waypointBefore}}</strong> et <strong>{{waypointAfter}}</strong> du {{startDate}} à {{startTime}} au {{endDate}} à {{endTime}}",
53+
"trackConflictSameDay": "conflit avec un sillon entre <strong>{{waypointBefore}}</strong> et <strong>{{waypointAfter}}</strong> le {{startDate}} entre {{startTime}} et {{endTime}}",
54+
"remainingTrackConflicts": "{{count}} autres conflits avec d'autres sillons",
55+
"remainingTrackConflicts_one": "un autre conflit avec un autre sillon",
56+
"remainingWorkConflicts": "{{count}} autres travaux en conflit",
57+
"remainingWorkConflicts_one": "un autre conflit avec des travaux",
58+
"workConflictSameDay": "travaux de <strong>{{waypointBefore}}</strong> à <strong>{{waypointAfter}}</strong> le {{startDate}} entre {{startTime}} et {{endTime}}",
59+
"workConflict": "travaux de <strong>{{waypointBefore}}</strong> à <strong>{{waypointAfter}}</strong> du {{startDate}} à {{startTime}} au {{endDate}} à {{endTime}}",
60+
"changeSearchCriteria": "Vous pouvez modifier vos critères de recherche pour trouver une solution",
5161
"retainThisSimulation": "Retenir cette simulation",
5262
"simulationName": {
5363
"withOutputs": "Simulation n°{{id}}",

front/src/applications/stdcm/components/StdcmResults/StdcmDebugResults.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
44

55
import { STDCM_TRAIN_ID } from 'applications/stdcm/consts';
66
import useProjectedTrainsForStdcm from 'applications/stdcm/hooks/useProjectedTrainsForStdcm';
7-
import type { StdcmSimulationOutputs } from 'applications/stdcm/types';
7+
import type { StdcmResultsOutput } from 'applications/stdcm/types';
88
import { osrdEditoastApi, type TrackRange } from 'common/api/osrdEditoastApi';
99
import { useOsrdConfSelectors } from 'common/osrdContext';
1010
import i18n from 'i18n';
@@ -17,7 +17,7 @@ const HANDLE_TAB_RESIZE_HEIGHT = 20;
1717

1818
type StdcmDebugResultsProps = {
1919
pathTrackRanges: TrackRange[];
20-
simulationOutputs: StdcmSimulationOutputs;
20+
simulationOutputs: StdcmResultsOutput;
2121
};
2222

2323
const StdcmDebugResults = ({

front/src/applications/stdcm/components/StdcmResults/StdcmResults.tsx

+55-22
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { useMemo, useState } from 'react';
22

33
import { Button } from '@osrd-project/ui-core';
44
import { PDFDownloadLink } from '@react-pdf/renderer';
5-
import { useTranslation } from 'react-i18next';
5+
import { useTranslation, Trans } from 'react-i18next';
66

7+
import useConflictsMessages from 'applications/stdcm/hooks/useConflictsMessages';
78
import type { StdcmSimulation } from 'applications/stdcm/types';
89
import {
910
generateCodeNumber,
1011
getOperationalPointsWithTimes,
1112
} from 'applications/stdcm/utils/formatSimulationReportSheet';
13+
import { hasConflicts, hasResults } from 'applications/stdcm/utils/simulationOutputUtils';
1214
import { type TrackRange } from 'common/api/osrdEditoastApi';
1315
import { Map } from 'modules/trainschedule/components/ManageTrainSchedule';
1416

@@ -43,25 +45,31 @@ const StcdmResults = ({
4345
pathTrackRanges,
4446
}: StcdmResultsProps) => {
4547
const { t } = useTranslation('stdcm', { keyPrefix: 'simulation.results' });
46-
4748
const [mapCanvas, setMapCanvas] = useState<string>();
4849

4950
const selectedSimulation = simulationsList[selectedSimulationIndex];
50-
const simulationReportSheetNumber = generateCodeNumber();
51+
const { outputs } = selectedSimulation || {};
52+
53+
const hasConflictResults = hasConflicts(outputs);
54+
const hasSimulationResults = hasResults(outputs);
5155

56+
const { trackConflicts, workConflicts } = useConflictsMessages(t, outputs);
57+
58+
const simulationReportSheetNumber = generateCodeNumber();
5259
const isSelectedSimulationRetained = selectedSimulationIndex === retainedSimulationIndex;
5360

5461
const operationalPointsList = useMemo(() => {
55-
if (!selectedSimulation || !selectedSimulation.outputs) {
56-
return [];
57-
}
58-
62+
if (!hasSimulationResults) return [];
5963
return getOperationalPointsWithTimes(
60-
selectedSimulation.outputs.pathProperties?.suggestedOperationalPoints || [],
61-
selectedSimulation.outputs.results.simulation,
62-
selectedSimulation.outputs.results.departure_time
64+
outputs.pathProperties?.suggestedOperationalPoints || [],
65+
outputs.results.simulation,
66+
outputs.results.departure_time
6367
);
64-
}, [selectedSimulation]);
68+
}, [outputs]);
69+
70+
const simulationPathSteps = hasSimulationResults
71+
? outputs.results.simulationPathSteps
72+
: undefined;
6573

6674
return (
6775
<>
@@ -74,10 +82,10 @@ const StcdmResults = ({
7482
retainedSimulationIndex={retainedSimulationIndex}
7583
/>
7684
<div className="simulation-results">
77-
{selectedSimulation.outputs ? (
85+
{hasSimulationResults && !hasConflictResults ? (
7886
<div className="results-and-sheet">
7987
<StcdmResultsTable
80-
stdcmData={selectedSimulation.outputs.results}
88+
stdcmData={outputs.results}
8189
consist={selectedSimulation.inputs.consist}
8290
isSimulationRetained={isSelectedSimulationRetained}
8391
operationalPointsList={operationalPointsList}
@@ -90,7 +98,7 @@ const StcdmResults = ({
9098
document={
9199
<SimulationReportSheet
92100
stdcmLinkedPaths={selectedSimulation.inputs.linkedPaths}
93-
stdcmData={selectedSimulation.outputs.results}
101+
stdcmData={outputs.results}
94102
consist={selectedSimulation.inputs.consist}
95103
simulationReportSheetNumber={simulationReportSheetNumber}
96104
mapCanvas={mapCanvas}
@@ -114,7 +122,34 @@ const StcdmResults = ({
114122
) : (
115123
<div className="simulation-failure">
116124
<span className="title">{t('notFound')}</span>
117-
<span className="change-criteria">{t('changeCriteria')}</span>
125+
<span className="change-criteria">{t('conflictsTitle')}</span>
126+
127+
{trackConflicts.length > 0 && (
128+
<ul>
129+
{trackConflicts.map((message, index) => (
130+
<li key={index}>
131+
<span>
132+
<Trans>&bull; {message}</Trans>
133+
</span>
134+
</li>
135+
))}
136+
</ul>
137+
)}
138+
139+
{trackConflicts.length > 0 && workConflicts.length > 0 && <br />}
140+
141+
{workConflicts.length > 0 && (
142+
<ul>
143+
{workConflicts.map((message, index) => (
144+
<li key={index}>
145+
<span>
146+
<Trans>&bull; {message}</Trans>
147+
</span>
148+
</li>
149+
))}
150+
</ul>
151+
)}
152+
<span>{t('changeSearchCriteria')}</span>
118153
</div>
119154
)}
120155
<div className="osrd-config-item-container osrd-config-item-container-map map-results no-pointer-events">
@@ -123,17 +158,15 @@ const StcdmResults = ({
123158
isReadOnly
124159
hideAttribution
125160
showStdcmAssets
161+
isFeasible={!hasConflictResults}
126162
setMapCanvas={setMapCanvas}
127-
pathGeometry={selectedSimulation.outputs?.pathProperties.geometry}
128-
simulationPathSteps={selectedSimulation.outputs?.results.simulationPathSteps}
163+
pathGeometry={outputs?.pathProperties?.geometry}
164+
simulationPathSteps={simulationPathSteps}
129165
/>
130166
</div>
131167
</div>
132-
{isDebugMode && pathTrackRanges && selectedSimulation.outputs && (
133-
<StdcmDebugResults
134-
pathTrackRanges={pathTrackRanges}
135-
simulationOutputs={selectedSimulation.outputs}
136-
/>
168+
{isDebugMode && pathTrackRanges && hasSimulationResults && (
169+
<StdcmDebugResults pathTrackRanges={pathTrackRanges} simulationOutputs={outputs} />
137170
)}
138171
</>
139172
);

front/src/applications/stdcm/components/StdcmResults/StdcmSimulationNavigator.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
44

55
import useHorizontalScroll from 'applications/stdcm/hooks/useHorizontalScroll';
66
import type { StdcmSimulation } from 'applications/stdcm/types';
7+
import { hasConflicts } from 'applications/stdcm/utils/simulationOutputUtils';
78
import { formatDateToString } from 'utils/date';
89

910
export const SIMULATION_ITEM_CLASSNAME = 'simulation-item';
@@ -66,7 +67,7 @@ const StdcmSimulationNavigator = ({
6667
>
6768
<div className="simulation-name">
6869
<div>
69-
{outputs
70+
{outputs && !hasConflicts(outputs)
7071
? t('simulationName.withOutputs', { id })
7172
: t('simulationName.withoutOutputs')}
7273
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import type { TFunction } from 'i18next';
4+
5+
import { formatConflicts } from 'applications/stdcm/utils/formatConflicts';
6+
import { hasConflicts } from 'applications/stdcm/utils/simulationOutputUtils';
7+
8+
import type { StdcmSimulationOutputs } from '../types';
9+
10+
const useConflictsMessages = (t: TFunction, selectedSimulationOutput?: StdcmSimulationOutputs) => {
11+
const [trackConflicts, setTrackConflicts] = useState<string[]>([]);
12+
const [workConflicts, setWorkConflicts] = useState<string[]>([]);
13+
14+
useEffect(() => {
15+
if (!hasConflicts(selectedSimulationOutput)) return;
16+
17+
const generateConflictMessages = () => {
18+
const { pathProperties } = selectedSimulationOutput;
19+
const { trackConflictsData, workConflictsData } = formatConflicts(
20+
selectedSimulationOutput?.conflicts,
21+
pathProperties
22+
);
23+
24+
const trackMessages: string[] = [];
25+
trackConflictsData.slice(0, 2).forEach((conflict) => {
26+
const { waypointBefore, waypointAfter, startDate, endDate, startTime, endTime } = conflict;
27+
28+
if (startDate === endDate) {
29+
trackMessages.push(
30+
t('trackConflictSameDay', {
31+
waypointBefore,
32+
waypointAfter,
33+
startTime,
34+
endTime,
35+
startDate,
36+
})
37+
);
38+
} else {
39+
trackMessages.push(
40+
t('trackConflict', {
41+
waypointBefore,
42+
waypointAfter,
43+
startDate,
44+
endDate,
45+
startTime,
46+
endTime,
47+
})
48+
);
49+
}
50+
});
51+
52+
const remainingTrackConflicts = trackConflictsData.length - 2;
53+
if (remainingTrackConflicts > 0) {
54+
trackMessages.push(t('remainingTrackConflicts', { count: remainingTrackConflicts }));
55+
}
56+
57+
const workMessages: string[] = [];
58+
workConflictsData.slice(0, 2).forEach((conflict) => {
59+
const { waypointBefore, waypointAfter, startDate, endDate, startTime, endTime } = conflict;
60+
61+
if (startDate === endDate) {
62+
workMessages.push(
63+
t('workConflictSameDay', {
64+
waypointBefore,
65+
waypointAfter,
66+
startDate,
67+
startTime,
68+
endTime,
69+
})
70+
);
71+
} else {
72+
workMessages.push(
73+
t('workConflict', {
74+
waypointBefore,
75+
waypointAfter,
76+
startDate,
77+
startTime,
78+
endDate,
79+
endTime,
80+
})
81+
);
82+
}
83+
});
84+
85+
const remainingWorkConflicts = workConflictsData.length - 2;
86+
if (remainingWorkConflicts > 0) {
87+
workMessages.push(t('remainingWorkConflicts', { count: remainingWorkConflicts }));
88+
}
89+
90+
// Update the state with generated messages
91+
setTrackConflicts(trackMessages);
92+
setWorkConflicts(workMessages);
93+
};
94+
95+
generateConflictMessages();
96+
}, [t, selectedSimulationOutput]);
97+
98+
return { trackConflicts, workConflicts };
99+
};
100+
101+
export default useConflictsMessages;

0 commit comments

Comments
 (0)