Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative solutions proposals for stdcm #10970

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions front/public/locales/en/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,17 @@
"calculatingSimulation": "Calculation in progress...",
"getSimulation": "Get the simulation",
"infoMessage": "This simulation doesn't ensure the availability of the path.",
"additionalResults": "No result was found for your request, other alternatives are being computed.",
"modifySearchCriteria": "You can modify your search criteria to find a solution.",
"pendingSimulation": "Simulation in progress",
"results": {
"changeCriteria": "You can modify your search criterias to find a solution.",
"displayAll": "Display all operational points",
"displayMain": "Display main operational points",
"downloadSimulationSheet": "Download simulation report sheet",
"simulationDownstream": "Simulation automatically computed as no capacity was found for the requested criteria: tolerance <underline>before</underline> departure time was increased.",
"simulationUpstream": "Simulation automatically computed as no capacity was found for the requested criteria: tolerance <underline>after</underline> departure time was increased.",
"simulationsWithConflicts": "Two other simulations were automatically computed based on this configuration.",
"formatCreationDate": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}",
"gesicoRequest": "and attach this document to your GESICO request.",
"notFound": "No path was found for this configuration",
Expand Down
4 changes: 4 additions & 0 deletions front/public/locales/fr/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,17 @@
"calculatingSimulation": "Calcul en cours...",
"getSimulation": "Obtenir la simulation",
"infoMessage": "Cette simulation n'offre pas la garantie de disponibilité du sillon.",
"additionalResults": "Pas de résultat pour votre demande, d’autres possibilités sont en cours de calcul.",
"modifySearchCriteria": "Vous pouvez modifier vos critères de recherche pour trouver une solution.",
"pendingSimulation": "simulation en cours",
"results": {
"changeCriteria": "Vous pouvez modifier vos critères de recherche pour trouver une solution.",
"displayAll": "Afficher tous les jalons",
"displayMain": "Afficher les jalons principaux",
"downloadSimulationSheet": "Télécharger la fiche de simulation",
"simulationDownstream": "Simulation lancée automatiquement en raison de l’absence de capacité avec les paramètres demandés : la tolérance <underline>avant</underline> l’heure de départ a été augmentée.",
"simulationUpstream": "Simulation lancée automatiquement en raison de l’absence de capacité avec les paramètres demandés : la tolérance <underline>après</underline> l’heure de départ a été augmentée.",
"simulationsWithConflicts": "Deux autres simulations ont été calculées automatiquement sur la base de cette configuration.",
"formatCreationDate": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}",
"gesicoRequest": "et joignez ce document à votre demande GESICO.",
"notFound": "Aucun passage n'a été trouvé pour cette configuration",
Expand Down
29 changes: 29 additions & 0 deletions front/src/applications/operationalStudies/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { TFunction } from 'i18next';
import type { Dictionary } from 'lodash';

import type {
PathfindingItem,
PathfindingResultSuccess,
PathProperties,
SimulationSummaryResult,
Expand Down Expand Up @@ -293,3 +294,31 @@ export const isTooFast = (
}
return false;
};

/**
* Adjusts a step's arrival time by a given tolerance.
*
* @param step - The step with timing data.
* @param toleranceSec - Seconds to adjust the arrival time.
* A positive value sets `arrival_time_tolerance_after`, and a negative value sets `arrival_time_tolerance_before`.
* @returns The updated step with the new arrival time and tolerance values.
*/
export const modifyStepTolerance = (
step: PathfindingItem,
toleranceSec: number
): PathfindingItem => {
if (!step.timing_data || !step.timing_data.arrival_time) {
return step;
}
const oldTime = new Date(step.timing_data.arrival_time);
const newTime = new Date(oldTime.getTime() + toleranceSec * 1000);
return {
...step,
timing_data: {
...step.timing_data,
arrival_time: newTime.toISOString(),
arrival_time_tolerance_before: toleranceSec < 0 ? Math.abs(toleranceSec) : 0,
arrival_time_tolerance_after: toleranceSec > 0 ? toleranceSec : 0,
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { extractMarkersInfo } from 'applications/stdcm/utils';
import DefaultBaseMap from 'common/Map/DefaultBaseMap';
import useInfraStatus from 'modules/pathfinding/hooks/useInfraStatus';
import {
addStdcmSimulation,
resetMargins,
restoreStdcmConfig,
updateStdcmPathStep,
addStdcmSimulation,
} from 'reducers/osrdconf/stdcmConf';
import {
getStdcmDestination,
Expand Down Expand Up @@ -53,6 +53,7 @@ declare global {
type StdcmConfigProps = {
isDebugMode: boolean;
isPending: boolean;
isPendingAdditional: boolean;
retainedSimulationIndex?: number;
showBtnToLaunchSimulation: boolean;
skipPathfindingStatusMessage: boolean;
Expand All @@ -64,6 +65,7 @@ type StdcmConfigProps = {
const StdcmConfig = ({
isDebugMode,
isPending,
isPendingAdditional,
retainedSimulationIndex,
showBtnToLaunchSimulation,
skipPathfindingStatusMessage,
Expand Down Expand Up @@ -95,12 +97,12 @@ const StdcmConfig = ({

const [formErrors, setFormErrors] = useState<StdcmConfigErrors>();

const currentSimulationInputs = useStdcmForm();

const disabled = isPending || retainedSimulationIndex !== undefined;

const markersInfo = useMemo(() => extractMarkersInfo(pathSteps), [pathSteps]);

const currentSimulationInputs = useStdcmForm();

const startSimulation = async () => {
const formErrorsStatus = checkStdcmConfigErrors(pathSteps, t, pathfinding?.status);
if (pathfinding?.status === 'success' && !formErrorsStatus) {
Expand Down Expand Up @@ -260,8 +262,9 @@ const StdcmConfig = ({
</div>
</div>
)}
{isPending && (
{(isPending || isPendingAdditional) && (
<StdcmLoader
isPendingAdditional={isPendingAdditional}
cancelStdcmRequest={cancelStdcmRequest}
launchButtonRef={launchButtonRef}
formRef={formRef}
Expand Down
16 changes: 14 additions & 2 deletions front/src/applications/stdcm/components/StdcmLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ const LOADER_HEIGHT = 176;
const LOADER_OFFSET = 32;

type StdcmLoaderProps = {
isPendingAdditional: boolean;
cancelStdcmRequest: () => void;
launchButtonRef: RefObject<HTMLDivElement>;
formRef: RefObject<HTMLDivElement>;
};

const StdcmLoader = ({ cancelStdcmRequest, launchButtonRef, formRef }: StdcmLoaderProps) => {
const StdcmLoader = ({
cancelStdcmRequest,
launchButtonRef,
formRef,
isPendingAdditional,
}: StdcmLoaderProps) => {
const { t } = useTranslation('stdcm');
const loaderRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -86,7 +92,13 @@ const StdcmLoader = ({ cancelStdcmRequest, launchButtonRef, formRef }: StdcmLoad
})}
>
<div className="stdcm-loader__wrapper">
<h2>{t('simulation.calculatingSimulation')}</h2>
<h2>
{t(
isPendingAdditional
? 'simulation.additionalResults'
: 'simulation.calculatingSimulation'
)}
</h2>
<div className="stdcm-loader__cancel-btn">
<Button
data-testid="cancel-simulation-button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const StdcmDebugResults = ({ simulationOutputs }: StdcmDebugResultsProps) => {
<div className="stdcm-debug-results">
{projectedData &&
projectedData?.spaceTimeData.length > 0 &&
pathProperties.manchetteOperationalPoints && (
pathProperties?.manchetteOperationalPoints && (
<ResizableSection
height={manchetteWithSpaceTimeChartHeight}
setHeight={setManchetteWithSpaceTimeChartHeight}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const StcdmResults = ({
const selectedSimulation = useSelector(getSelectedSimulation);
const retainedSimulationIndex = useSelector(getRetainedSimulationIndex);

const { outputs } = selectedSimulation;
const { outputs, alternativePath, upstreamPath } = selectedSimulation;

const hasConflictResults = hasConflicts(outputs);
const hasSimulationResults = hasResults(outputs);
Expand Down Expand Up @@ -91,6 +91,17 @@ const StcdmResults = ({
/>
{outputs && (
<>
{alternativePath && (
<div className="alternative-path-message">
<span className="stdcm-header__notification alternative-path">
{upstreamPath ? (
<Trans components={{ underline: <u /> }}>{t('simulationUpstream')}</Trans>
) : (
<Trans components={{ underline: <u /> }}>{t('simulationDownstream')}</Trans>
)}
</span>
</div>
)}
<div className="simulation-results">
{hasSimulationResults && !hasConflictResults ? (
<div className="results-and-sheet">
Expand Down Expand Up @@ -176,6 +187,11 @@ const StcdmResults = ({
</ul>
)}
<span>{t('changeSearchCriteria')}</span>
{!alternativePath && (
<div className="alternative-simulations-info">
{t('simulationsWithConflicts')}
</div>
)}
</div>
)}
<div className="osrd-config-item-container osrd-config-item-container-map map-results">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CheckCircle, ChevronLeft, ChevronRight } from '@osrd-project/ui-icons';
import { CheckCircle, ChevronLeft, ChevronRight, Sparkle } from '@osrd-project/ui-icons';
import cx from 'classnames';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
Expand Down Expand Up @@ -57,7 +57,7 @@ const StdcmSimulationNavigator = ({
</div>
)}
<div className="simulation-list" ref={scrollableRef}>
{completedSimulations.map(({ index, creationDate, outputs }) => {
{completedSimulations.map(({ index, creationDate, outputs, alternativePath }) => {
let formatedTotalLength = '';
let formatedTripDuration = '';

Expand Down Expand Up @@ -99,6 +99,9 @@ const StdcmSimulationNavigator = ({
{retainedSimulationIndex === index && (
<CheckCircle className="check-circle" variant="fill" />
)}
{alternativePath && (
<Sparkle className="alternative-simulation" variant="fill" />
)}
</div>
<div className="simulation-metadata" key={index}>
<span className="creation-date">
Expand Down
5 changes: 5 additions & 0 deletions front/src/applications/stdcm/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const STDCM_REQUEST_STATUS = Object.freeze({
rejected: 'REJECTED',
canceled: 'CANCELED',
noresults: 'NORESULTS',
pendingadditional: 'PENDINGADDITIONAL',
});

export const STDCM_TRAIN_ID = -10;
Expand All @@ -26,3 +27,7 @@ export const COMPOSITION_CODES_MAX_SPEEDS: Record<string, number | undefined> =
export const DEFAULT_COMPOSITION_CODE = 'MA100';

export const COMPOSITION_CODES = Object.keys(COMPOSITION_CODES_MAX_SPEEDS);

export const DOWNSTREAM_TOLERANCE_SECONDS = -1800; // subtract 30 minutes

export const UPSTREAM_TOLERANCE_SECONDS = 7200; // add 120 minutes
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const useConflictsMessages = (t: TFunction, selectedSimulationOutput?: StdcmSimu
const [workConflicts, setWorkConflicts] = useState<string[]>([]);

useEffect(() => {
if (!hasConflicts(selectedSimulationOutput)) return;
if (!hasConflicts(selectedSimulationOutput) || !selectedSimulationOutput.pathProperties) return;

const generateConflictMessages = () => {
const { pathProperties } = selectedSimulationOutput;
Expand Down
89 changes: 89 additions & 0 deletions front/src/applications/stdcm/hooks/useFetchPathProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// useFetchPathProperties.ts
import { getEntities } from 'applications/editor/data/api';
import type { TrackSectionEntity } from 'applications/editor/tools/trackEdition/types';
import type { StdcmPathProperties } from 'applications/stdcm/types';
import type {
PostInfraByInfraIdPathPropertiesApiArg,
PathfindingResultSuccess,
} from 'common/api/osrdEditoastApi';
import { osrdEditoastApi } from 'common/api/osrdEditoastApi';
import { formatSuggestedOperationalPoints } from 'modules/pathfinding/utils';
import type { SuggestedOP } from 'modules/trainschedule/components/ManageTrainSchedule/types';
import type { AppDispatch } from 'store';

/**
* Custom hook that returns a function to fetch and format path properties.
*/
const useFetchPathProperties = () => {
const [trigger] = osrdEditoastApi.endpoints.postInfraByInfraIdPathProperties.useLazyQuery();

const fetchPathProperties = async (
path: PathfindingResultSuccess,
infraId: number,
dispatch: AppDispatch
): Promise<StdcmPathProperties | null> => {
const pathPropertiesParams: PostInfraByInfraIdPathPropertiesApiArg = {
infraId,
props: ['geometry', 'operational_points', 'zones'],
pathPropertiesInput: {
track_section_ranges: path.track_section_ranges,
},
};

try {
const result = await trigger(pathPropertiesParams).unwrap();

if (!result.geometry || !result.operational_points || !result.zones || !infraId) {
return null;
}

const trackIds = result.operational_points.map((op) => op.part.track);
const trackSections = await getEntities<TrackSectionEntity>(
infraId,
trackIds,
'TrackSection',
dispatch
);

const operationalPointsWithMetadata = result.operational_points.map((op) => {
const associatedTrackSection = trackSections[op.part.track];
const sncf = associatedTrackSection?.properties?.extensions?.sncf;
const metadata =
sncf && Object.values(sncf).every((value) => value !== undefined)
? {
lineCode: sncf.line_code!,
lineName: sncf.line_name!,
trackName: sncf.track_name!,
trackNumber: sncf.track_number!,
}
: undefined;
return { ...op, metadata };
});

const operationalPointsWithUniqueIds = result.operational_points.map((op, index) => ({
...op,
id: `${op.id}-${op.position}-${index}`,
}));

const suggestedOperationalPoints: SuggestedOP[] = formatSuggestedOperationalPoints(
operationalPointsWithMetadata,
result.geometry,
path.length
);

return {
manchetteOperationalPoints: operationalPointsWithUniqueIds,
geometry: result.geometry,
suggestedOperationalPoints,
zones: result.zones,
} as StdcmPathProperties;
} catch (error) {
console.error('Error fetching path properties:', error);
return null;
}
};

return fetchPathProperties;
};

export default useFetchPathProperties;
Loading
Loading