Skip to content

Commit

Permalink
front: align margins display and computation to spec in timestops table
Browse files Browse the repository at this point in the history
  • Loading branch information
Synar committed Nov 22, 2024
1 parent 28d2249 commit 0048fba
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 40 deletions.
85 changes: 51 additions & 34 deletions front/src/modules/timesStops/helpers/computeMargins.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,51 @@
import type { TrainScheduleResult } from 'common/api/osrdEditoastApi';
import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/Timetable/types';
import type {
TheoreticalMarginsRecord,
TrainScheduleWithDetails,
} from 'modules/trainschedule/components/Timetable/types';
import { ms2sec } from 'utils/timeManipulation';

import { formatDigitsAndUnit } from './utils';
import type { ScheduleEntry } from '../types';

function getTheoreticalMargin(selectedTrainSchedule: TrainScheduleResult, pathStepId: string) {
if (selectedTrainSchedule.path.length === 0) {
return undefined;
}
// pathStep is starting point => we take the first margin
if (selectedTrainSchedule.path[0].id === pathStepId) {
return selectedTrainSchedule.margins?.values[0];
}
const theoreticalMarginBoundaryIndex = selectedTrainSchedule.margins?.boundaries?.findIndex(
(id) => id === pathStepId
);
if (
theoreticalMarginBoundaryIndex === undefined ||
theoreticalMarginBoundaryIndex < 0 ||
theoreticalMarginBoundaryIndex > selectedTrainSchedule.margins!.values.length - 2
) {
return undefined;
}

return selectedTrainSchedule.margins!.values[theoreticalMarginBoundaryIndex + 1];
export function getTheoreticalMargins(selectedTrainSchedule: TrainScheduleResult) {
const theoreticalMargins: TheoreticalMarginsRecord = {};
let marginIndex = 0;
selectedTrainSchedule.path.forEach((step, index) => {
if (step.id === selectedTrainSchedule.margins?.boundaries[marginIndex]) {
marginIndex += 1;
theoreticalMargins[step.id] = {
theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex],
isBoundary: true,
};
} else {
theoreticalMargins[step.id] = {
theoreticalMargin: selectedTrainSchedule.margins?.values[marginIndex],
isBoundary: index === 0,
};
}
});
return theoreticalMargins;
}

function computeMargins(
theoreticalMargins: TheoreticalMarginsRecord,
selectedTrainSchedule: TrainScheduleResult,
scheduleByAt: Record<string, ScheduleEntry>,
pathStepIndex: number,
pathItemTimes: NonNullable<TrainScheduleWithDetails['pathItemTimes']> // in ms
) {
const { path, margins } = selectedTrainSchedule;
const pathStepId = path[pathStepIndex].id;
if (
!margins ||
(margins.values.length === 1 && margins.values[0] === '0%') ||
pathStepIndex === selectedTrainSchedule.path.length - 1
pathStepIndex === selectedTrainSchedule.path.length - 1 ||
!theoreticalMargins[pathStepId] ||
!(
scheduleByAt[pathStepId]?.arrival ||
scheduleByAt[pathStepId]?.stop_for ||
theoreticalMargins[pathStepId].isBoundary
)
) {
return {
theoreticalMargin: undefined,
Expand All @@ -45,15 +55,21 @@ function computeMargins(
};
}

const pathStepId = path[pathStepIndex].id;
const theoreticalMargin = getTheoreticalMargin(selectedTrainSchedule, pathStepId);
const { theoreticalMargin, isBoundary } = theoreticalMargins[pathStepId];

// find the next pathStep where constraints are defined
let nextIndex = path.length - 1;

// find the previous pathStep where margin was defined
let prevIndex = 0;
// eslint-disable-next-line no-plusplus
for (let index = 1; index < pathStepIndex; index++) {
if (margins.boundaries.includes(path[index].id)) {
prevIndex = index;
for (let index = pathStepIndex + 1; index < path.length; index += 1) {
const curStepId = path[index].id;
const curStepSchedule = scheduleByAt[curStepId];
if (
theoreticalMargins[curStepId]?.isBoundary ||
curStepSchedule?.arrival ||
curStepSchedule?.stop_for
) {
nextIndex = index;
break;
}
}

Expand All @@ -62,16 +78,17 @@ function computeMargins(
// provisional = margins
// final = margin + requested arrival times
const { base, provisional, final } = pathItemTimes;
const baseDuration = ms2sec(base[pathStepIndex + 1] - base[prevIndex]);
const provisionalDuration = ms2sec(provisional[pathStepIndex + 1] - provisional[prevIndex]);
const finalDuration = ms2sec(final[pathStepIndex + 1] - final[prevIndex]);
const baseDuration = ms2sec(base[nextIndex] - base[pathStepIndex]);
const provisionalDuration = ms2sec(provisional[nextIndex] - provisional[pathStepIndex]);
const finalDuration = ms2sec(final[nextIndex] - final[pathStepIndex]);

// how much longer it took (s) with the margin than without
const provisionalLostTime = provisionalDuration - baseDuration;
const finalLostTime = finalDuration - baseDuration;

return {
theoreticalMargin: formatDigitsAndUnit(theoreticalMargin),
isTheoreticalMarginBoundary: isBoundary,
theoreticalMarginSeconds: `${Math.round(provisionalLostTime)} s`,
calculatedMargin: `${Math.round(finalLostTime)} s`,
diffMargins: `${Math.round(finalLostTime - provisionalLostTime)} s`,
Expand Down
2 changes: 1 addition & 1 deletion front/src/modules/timesStops/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const getDigits = (unit: string | undefined) =>
unit === MarginUnit.second || unit === MarginUnit.percent ? 0 : 1;

export function formatDigitsAndUnit(fullValue: string | number | undefined, unit?: string) {
if (fullValue === undefined || fullValue === '0%') {
if (fullValue === undefined) {
return '';
}
if (typeof fullValue === 'number') {
Expand Down
19 changes: 16 additions & 3 deletions front/src/modules/timesStops/hooks/useOutputTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { TrainScheduleWithDetails } from 'modules/trainschedule/components/
import { dateToHHMM } from 'utils/date';

import { computeInputDatetimes } from '../helpers/arrivalTime';
import computeMargins from '../helpers/computeMargins';
import computeMargins, { getTheoreticalMargins } from '../helpers/computeMargins';
import { formatSchedule } from '../helpers/scheduleData';
import { type ScheduleEntry, type TimeStopsRow } from '../types';

Expand All @@ -28,6 +28,7 @@ const useOutputTableData = (
const { t } = useTranslation('timesStops');

const scheduleByAt: Record<string, ScheduleEntry> = keyBy(selectedTrainSchedule.schedule, 'at');
const theoreticalMargins = getTheoreticalMargins(selectedTrainSchedule);

const startDatetime = new Date(selectedTrainSchedule.start_time);

Expand All @@ -49,8 +50,19 @@ const useOutputTableData = (
computedArrival,
schedule
);
const { theoreticalMargin, theoreticalMarginSeconds, calculatedMargin, diffMargins } =
computeMargins(selectedTrainSchedule, index, trainSummary.pathItemTimes);
const {
theoreticalMargin,
isTheoreticalMarginBoundary,
theoreticalMarginSeconds,
calculatedMargin,
diffMargins,
} = computeMargins(
theoreticalMargins,
selectedTrainSchedule,
scheduleByAt,
index,
trainSummary.pathItemTimes
);

const inputs = computeInputDatetimes(startDatetime, previousTime, schedule, {
isDeparture: index === 0,
Expand All @@ -70,6 +82,7 @@ const useOutputTableData = (
onStopSignal,
shortSlipDistance,
theoreticalMargin,
isTheoreticalMarginBoundary,

theoreticalMarginSeconds,
calculatedMargin,
Expand Down
7 changes: 5 additions & 2 deletions front/src/modules/timesStops/hooks/useTimeStopsColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export const useTimeStopsColumns = <T extends TimeStopsRow>(
continuousUpdates: false,
placeholder: !isOutputTable ? t('theoreticalMarginPlaceholder') : '',
formatBlurredInput: (value) => {
if (!value || value === '0%') return '';
if (!value) return '';
if (!isOutputTable && !marginRegExValidation.test(value)) {
return `${value}${t('theoreticalMarginPlaceholder')}`;
}
Expand All @@ -168,7 +168,10 @@ export const useTimeStopsColumns = <T extends TimeStopsRow>(
})
),
cellClassName: ({ rowData }) =>
cx({ invalidCell: !isOutputTable && !rowData.isMarginValid }),
cx({
invalidCell: !isOutputTable && !rowData.isMarginValid,
repeatedValue: !rowData.isTheoreticalMarginBoundary,
}),
title: t('theoreticalMargin'),
headerClassName: 'padded-header',
minWidth: 100,
Expand Down
4 changes: 4 additions & 0 deletions front/src/modules/timesStops/styles/_timesStopsDatasheet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
color: red !important;
}

.repeatedValue {
color: var(--grey30);
}

.warning-schedule {
background: var(--warning30);
}
Expand Down
1 change: 1 addition & 0 deletions front/src/modules/timesStops/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type TimeStopsRow = {
onStopSignal?: boolean;
shortSlipDistance?: boolean;
theoreticalMargin?: string; // value asked by user
isTheoreticalMarginBoundary?: boolean; // tells whether the theoreticalMargin value was inputted for this line or if it is repeated from a previous line

theoreticalMarginSeconds?: string;
calculatedMargin?: string;
Expand Down
5 changes: 5 additions & 0 deletions front/src/modules/trainschedule/components/Timetable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ export type InvalidReason =
| Extract<SimulationSummaryResult['status'], 'pathfinding_failure' | 'simulation_failed'>
| PathfindingNotFound['error_type']
| PathfindingInputError['error_type'];

export type TheoreticalMarginsRecord = Record<
string,
{ theoreticalMargin: string | undefined; isBoundary: boolean } | undefined
>;

0 comments on commit 0048fba

Please sign in to comment.