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

editoast: stdcm: add arrival time parameters #7827

Merged
merged 1 commit into from
Jun 27, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class DurationAdapter : JsonAdapter<Duration?>() {
*/
class DateAdapter : JsonAdapter<ZonedDateTime>() {
@FromJson
override fun fromJson(reader: JsonReader): ZonedDateTime {
override fun fromJson(reader: JsonReader): ZonedDateTime? {
if (reader.peek() == JsonReader.Token.NULL) {
reader.skipValue()
return null
}
return ZonedDateTime.parse(reader.nextString())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ class STDCMRequestV2(
class STDCMPathItem(
val locations: List<TrackLocation>,
@Json(name = "stop_duration") val stopDuration: Duration?,
@Json(name = "step_timing_data") val stepTimingData: StepTimingData?,
)

data class StepTimingData(
@Json(name = "arrival_time") val arrivalTime: ZonedDateTime,
@Json(name = "arrival_time_tolerance_before") val arrivalTimeToleranceBefore: Duration,
@Json(name = "arrival_time_tolerance_after") val arrivalTimeToleranceAfter: Duration,
)

class TrackOffset(val track: String, val offset: Offset<TrackSection>)
Expand Down
39 changes: 35 additions & 4 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3017,7 +3017,6 @@ paths:
type: object
description: An STDCM request
required:
- start_time
- steps
- rolling_stock_id
- comfort
Expand All @@ -3035,7 +3034,9 @@ paths:
maximum_departure_delay:
type: integer
format: int64
description: By how long we can shift the departure time in milliseconds
description: |-
By how long we can shift the departure time in milliseconds
Deprecated, first step data should be used instead
default: 432000
minimum: 0
maximum_run_time:
Expand All @@ -3054,6 +3055,8 @@ paths:
start_time:
type: string
format: date-time
description: Deprecated, first step arrival time should be used instead
nullable: true
steps:
type: array
items:
Expand Down Expand Up @@ -8549,6 +8552,10 @@ components:
minimum: 0
location:
$ref: '#/components/schemas/PathItemLocation'
timing_data:
allOf:
- $ref: '#/components/schemas/StepTimingData'
nullable: true
PathfindingOutput:
type: object
required:
Expand Down Expand Up @@ -10177,7 +10184,6 @@ components:
type: object
description: An STDCM request
required:
- start_time
- steps
- rolling_stock_id
- comfort
Expand All @@ -10195,7 +10201,9 @@ components:
maximum_departure_delay:
type: integer
format: int64
description: By how long we can shift the departure time in milliseconds
description: |-
By how long we can shift the departure time in milliseconds
Deprecated, first step data should be used instead
default: 432000
minimum: 0
maximum_run_time:
Expand All @@ -10214,6 +10222,8 @@ components:
start_time:
type: string
format: date-time
description: Deprecated, first step arrival time should be used instead
nullable: true
steps:
type: array
items:
Expand Down Expand Up @@ -11487,6 +11497,27 @@ components:
type: array
items:
$ref: '#/components/schemas/RangeAllowance'
StepTimingData:
type: object
required:
- arrival_time
- arrival_time_tolerance_before
- arrival_time_tolerance_after
properties:
arrival_time:
type: string
format: date-time
description: Time at which the train should arrive at the location
arrival_time_tolerance_after:
type: integer
format: int64
description: The train may arrive up to this duration after the expected arrival time
minimum: 0
arrival_time_tolerance_before:
type: integer
format: int64
description: The train may arrive up to this duration before the expected arrival time
minimum: 0
Study:
type: object
required:
Expand Down
13 changes: 13 additions & 0 deletions editoast/src/core/v2/stdcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ pub struct STDCMPathItem {
pub locations: Vec<TrackOffset>,
/// Stop duration in milliseconds. None if the train does not stop at this path item.
pub stop_duration: Option<u64>,
/// If specified, describes when the train may arrive at the location
pub step_timing_data: Option<STDCMStepTimingData>,
}

/// Contains the data of a step timing, when it is specified
#[derive(Debug, Serialize)]
pub struct STDCMStepTimingData {
/// Time the train should arrive at this point
pub arrival_time: DateTime<Utc>,
/// Tolerance for the arrival time, when it arrives before the expected time, in ms
pub arrival_time_tolerance_before: u64,
/// Tolerance for the arrival time, when it arrives after the expected time, in ms
pub arrival_time_tolerance_after: u64,
}

/// Lighter description of a work schedule, only contains what's relevant
Expand Down
75 changes: 64 additions & 11 deletions editoast/src/views/v2/timetable/stdcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use actix_web::web::Json;
use actix_web::web::Path;
use actix_web::web::Query;
use chrono::Utc;
use chrono::{DateTime, NaiveDateTime, TimeZone};
use chrono::{DateTime, Duration, NaiveDateTime, TimeZone};
use editoast_derive::EditoastError;
use editoast_schemas::train_schedule::MarginValue;
use editoast_schemas::train_schedule::PathItemLocation;
Expand All @@ -19,10 +19,10 @@ use utoipa::IntoParams;
use utoipa::ToSchema;

use crate::core::v2::simulation::SimulationResponse;
use crate::core::v2::stdcm::STDCMRequest;
use crate::core::v2::stdcm::STDCMResponse;
use crate::core::v2::stdcm::TrainRequirement;
use crate::core::v2::stdcm::{STDCMPathItem, STDCMWorkSchedule, UndirectedTrackRange};
use crate::core::v2::stdcm::{STDCMRequest, STDCMStepTimingData};
use crate::core::AsCoreRequest;
use crate::core::CoreClient;
use crate::error::Result;
Expand All @@ -48,6 +48,7 @@ crate::routes! {
editoast_common::schemas! {
STDCMRequestPayload,
PathfindingItem,
StepTimingData,
}

#[derive(Debug, Error, EditoastError, Serialize)]
Expand All @@ -70,11 +71,13 @@ enum STDCMError {
/// An STDCM request
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub struct STDCMRequestPayload {
start_time: DateTime<Utc>,
/// Deprecated, first step arrival time should be used instead
start_time: Option<DateTime<Utc>>,
steps: Vec<PathfindingItem>,
rolling_stock_id: i64,
comfort: Comfort,
/// By how long we can shift the departure time in milliseconds
/// Deprecated, first step data should be used instead
#[serde(default = "default_maximum_departure_delay")]
#[schema(default = default_maximum_departure_delay)]
maximum_departure_delay: u64,
Expand Down Expand Up @@ -106,6 +109,18 @@ struct PathfindingItem {
duration: Option<u64>,
/// The associated location
location: PathItemLocation,
/// Time at which the train should arrive at the location, if specified
timing_data: Option<StepTimingData>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
struct StepTimingData {
/// Time at which the train should arrive at the location
arrival_time: DateTime<Utc>,
/// The train may arrive up to this duration before the expected arrival time
arrival_time_tolerance_before: u64,
/// The train may arrive up to this duration after the expected arrival time
arrival_time_tolerance_after: u64,
}

const TWO_HOURS_IN_MILLISECONDS: u64 = 2 * 60 * 60 * 60;
Expand Down Expand Up @@ -209,6 +224,8 @@ async fn stdcm(
}
};

let departure_time = get_earliest_departure_time(&data, maximum_run_time);

// 3. Parse stdcm path items
let path_items = parse_stdcm_steps(conn, &data, &infra).await?;

Expand All @@ -223,7 +240,7 @@ async fn stdcm(
.clone(),
comfort: data.comfort,
path_items,
start_time: data.start_time,
start_time: departure_time,
trains_requirements,
maximum_departure_delay: Some(data.maximum_departure_delay),
maximum_run_time,
Expand All @@ -234,7 +251,7 @@ async fn stdcm(
time_step: Some(2000),
work_schedules: build_work_schedules(
conn,
data.start_time,
departure_time,
data.maximum_departure_delay,
maximum_run_time,
)
Expand All @@ -246,6 +263,34 @@ async fn stdcm(
Ok(Json(stdcm_response))
}

/// Returns the earliest time at which the train may start
fn get_earliest_departure_time(data: &STDCMRequestPayload, maximum_run_time: u64) -> DateTime<Utc> {
// Prioritize: start time, or first step time, or (first specified time - max run time)
data.start_time.unwrap_or(
data.steps
.first()
.and_then(|step| step.timing_data.clone())
.and_then(|data| Option::from(data.arrival_time))
.unwrap_or(
get_earliest_step_time(data) - Duration::milliseconds(maximum_run_time as i64),
),
)
}

/// Returns the earliest time that has been set on any step
fn get_earliest_step_time(data: &STDCMRequestPayload) -> DateTime<Utc> {
// Get the earliest time that has been specified for any step
data.start_time
.or_else(|| {
data.steps
.iter()
.flat_map(|step| step.timing_data.iter())
.map(|data| data.arrival_time)
.next()
})
.expect("No time specified for stdcm request")
}

/// get the maximum run time, compute it if unspecified.
/// returns an enum with either the result or a SimulationResponse if it failed
async fn get_maximum_run_time(
Expand All @@ -263,13 +308,16 @@ async fn get_maximum_run_time(
});
}

// Doesn't matter for now, but eventually it will affect tmp speed limits
let approx_start_time = get_earliest_step_time(data);

let train_schedule = TrainSchedule {
id: 0,
train_name: "".to_string(),
labels: vec![],
rolling_stock_name: rolling_stock.name.clone(),
timetable_id,
start_time: data.start_time,
start_time: approx_start_time,
schedule: vec![],
margins: build_single_margin(data.margin),
initial_speed: 0.0,
Expand Down Expand Up @@ -372,21 +420,26 @@ async fn parse_stdcm_steps(
) -> Result<Vec<STDCMPathItem>> {
let path_items = data.steps.clone();
let mut locations = Vec::with_capacity(path_items.len());
let mut durations = Vec::with_capacity(path_items.len());
for item in path_items {
locations.push(item.location);
durations.push(item.duration);
}

let track_offsets = extract_location_from_path_items(conn, infra.id, &locations).await?;
let track_offsets = track_offsets.map_err::<STDCMError, _>(|err| err.into())?;

Ok(track_offsets
.iter()
.zip(durations)
.map(|(track_offset, duration)| STDCMPathItem {
stop_duration: duration,
.zip(&data.steps)
.map(|(track_offset, path_item)| STDCMPathItem {
stop_duration: path_item.duration,
locations: track_offset.to_vec(),
step_timing_data: path_item.timing_data.as_ref().map(|timing_data| {
STDCMStepTimingData {
arrival_time: timing_data.arrival_time,
arrival_time_tolerance_before: timing_data.arrival_time_tolerance_before,
arrival_time_tolerance_after: timing_data.arrival_time_tolerance_after,
}
}),
})
.collect())
}
Expand Down
15 changes: 13 additions & 2 deletions front/src/common/api/generatedEditoastApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1751,14 +1751,16 @@ export type PostV2TimetableByIdStdcmApiArg = {
comfort: Comfort;
/** Can be a percentage `X%`, a time in minutes per 100 kilometer `Xmin/100km` or `None` */
margin?: string | null;
/** By how long we can shift the departure time in milliseconds */
/** By how long we can shift the departure time in milliseconds
Deprecated, first step data should be used instead */
maximum_departure_delay?: number;
/** Specifies how long the total run time can be in milliseconds */
maximum_run_time?: number | null;
rolling_stock_id: number;
/** Train categories for speed limits */
speed_limit_tags?: string | null;
start_time: string;
/** Deprecated, first step arrival time should be used instead */
start_time?: string | null;
steps: PathfindingItem[];
/** Margin after the train passage in milliseconds

Expand Down Expand Up @@ -3684,10 +3686,19 @@ export type PathItemLocation =
/** The [UIC](https://en.wikipedia.org/wiki/List_of_UIC_country_codes) code of an operational point */
uic: number;
};
export type StepTimingData = {
/** Time at which the train should arrive at the location */
arrival_time: string;
/** The train may arrive up to this duration after the expected arrival time */
arrival_time_tolerance_after: number;
/** The train may arrive up to this duration before the expected arrival time */
arrival_time_tolerance_before: number;
};
export type PathfindingItem = {
/** The stop duration in milliseconds, None if the train does not stop. */
duration?: number | null;
location: PathItemLocation;
timing_data?: StepTimingData | null;
};
export type Distribution = 'STANDARD' | 'MARECO';
export type TrainScheduleBase = {
Expand Down
Loading