From 7c1cb3f0d4bafef8edb5025b05981ee8fe2bbc2a Mon Sep 17 00:00:00 2001 From: Youness CHRIFI ALAOUI Date: Tue, 4 Mar 2025 11:05:13 +0100 Subject: [PATCH] editoast: add simulation summary and simulation endpoints for paced train Signed-off-by: Youness CHRIFI ALAOUI --- editoast/openapi.yaml | 39 ++- editoast/src/models/paced_train.rs | 23 ++ editoast/src/views/mod.rs | 4 +- editoast/src/views/paced_train.rs | 282 ++++++++++++++++--- editoast/src/views/timetable.rs | 2 +- editoast/src/views/train_schedule.rs | 82 +++--- front/public/locales/en/errors.json | 5 +- front/public/locales/fr/errors.json | 5 +- front/src/common/api/generatedEditoastApi.ts | 6 +- front/src/config/openapi-editoast-config.cts | 2 + 10 files changed, 351 insertions(+), 99 deletions(-) diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index e6eb16f1d91..a150373e76e 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -5140,9 +5140,10 @@ components: - $ref: '#/components/schemas/EditoastOperationErrorInvalidPatch' - $ref: '#/components/schemas/EditoastOperationErrorModifyId' - $ref: '#/components/schemas/EditoastOperationErrorObjectNotFound' - - $ref: '#/components/schemas/EditoastPacedTrainErrorBatchPacedTrainNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorBatchNotFound' - $ref: '#/components/schemas/EditoastPacedTrainErrorDatabase' - - $ref: '#/components/schemas/EditoastPacedTrainErrorPacedTrainNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorInfraNotFound' + - $ref: '#/components/schemas/EditoastPacedTrainErrorNotFound' - $ref: '#/components/schemas/EditoastPaginationErrorInvalidPage' - $ref: '#/components/schemas/EditoastPaginationErrorInvalidPageSize' - $ref: '#/components/schemas/EditoastPathfindingErrorInfraNotFound' @@ -5637,7 +5638,7 @@ components: type: string enum: - editoast:operation:ObjectNotFound - EditoastPacedTrainErrorBatchPacedTrainNotFound: + EditoastPacedTrainErrorBatchNotFound: type: object required: - type @@ -5660,7 +5661,7 @@ components: type: type: string enum: - - editoast:paced_train:BatchPacedTrainNotFound + - editoast:paced_train:BatchNotFound EditoastPacedTrainErrorDatabase: type: object required: @@ -5680,7 +5681,31 @@ components: type: string enum: - editoast:paced_train:Database - EditoastPacedTrainErrorPacedTrainNotFound: + EditoastPacedTrainErrorInfraNotFound: + type: object + required: + - type + - status + - message + properties: + context: + type: object + required: + - infra_id + properties: + infra_id: + type: integer + message: + type: string + status: + type: integer + enum: + - 404 + type: + type: string + enum: + - editoast:paced_train:InfraNotFound + EditoastPacedTrainErrorNotFound: type: object required: - type @@ -5703,7 +5728,7 @@ components: type: type: string enum: - - editoast:paced_train:PacedTrainNotFound + - editoast:paced_train:NotFound EditoastPaginationErrorInvalidPage: type: object required: @@ -8570,7 +8595,7 @@ components: timetable_id: type: integer format: int64 - description: Timetable attached to the train schedule + description: Timetable attached to the paced train nullable: true PacedTrainResult: allOf: diff --git a/editoast/src/models/paced_train.rs b/editoast/src/models/paced_train.rs index 014e766443a..b742a980573 100644 --- a/editoast/src/models/paced_train.rs +++ b/editoast/src/models/paced_train.rs @@ -1,3 +1,4 @@ +use crate::models::train_schedule::TrainSchedule; use chrono::DateTime; use chrono::Duration as ChronoDuration; use chrono::Utc; @@ -101,3 +102,25 @@ impl From for PacedTrainBase { } } } + +impl From for TrainSchedule { + fn from(paced_train: PacedTrain) -> Self { + Self { + id: paced_train.id, + train_name: paced_train.train_name, + labels: paced_train.labels.into(), + rolling_stock_name: paced_train.rolling_stock_name, + timetable_id: paced_train.timetable_id, + path: paced_train.path, + start_time: paced_train.start_time, + schedule: paced_train.schedule, + margins: paced_train.margins, + initial_speed: paced_train.initial_speed, + comfort: paced_train.comfort, + constraint_distribution: paced_train.constraint_distribution, + speed_limit_tag: paced_train.speed_limit_tag, + power_restrictions: paced_train.power_restrictions, + options: paced_train.options, + } + } +} diff --git a/editoast/src/views/mod.rs b/editoast/src/views/mod.rs index 430e440974b..936992f62a5 100644 --- a/editoast/src/views/mod.rs +++ b/editoast/src/views/mod.rs @@ -170,9 +170,9 @@ pub struct InfraIdQueryParam { infra_id: i64, } -#[derive(Debug, Serialize, ToSchema)] +#[derive(Debug, PartialEq, Serialize, Deserialize, ToSchema)] #[serde(tag = "status", rename_all = "snake_case")] -enum SimulationSummaryResult { +pub enum SimulationSummaryResult { /// Minimal information on a simulation's result Success { /// Length of a path in mm diff --git a/editoast/src/views/paced_train.rs b/editoast/src/views/paced_train.rs index 9bc670b776e..be99c681664 100644 --- a/editoast/src/views/paced_train.rs +++ b/editoast/src/views/paced_train.rs @@ -1,6 +1,13 @@ use std::collections::HashMap; use std::collections::HashSet; +use crate::core::simulation::SimulationResponse; +use crate::error::Result; +use crate::models::prelude::*; +use crate::models::train_schedule::TrainSchedule; +use crate::models::Infra; +use crate::views::projection::ProjectPathTrainResult; +use crate::views::ListId; use axum::extract::Json; use axum::extract::Path; use axum::extract::Query; @@ -11,6 +18,8 @@ use editoast_authz::BuiltinRole; use editoast_derive::EditoastError; use editoast_models::DbConnectionPoolV2; use editoast_schemas::paced_train::PacedTrainBase; + +use itertools::Itertools; use serde::Deserialize; use serde::Serialize; use thiserror::Error; @@ -19,17 +28,14 @@ use utoipa::ToSchema; use super::path::pathfinding::PathfindingResult; use super::projection::ProjectPathForm; +use super::train_schedule::simulation_response; +use super::train_schedule::train_simulation_batch; use super::AppState; use super::AuthenticationExt; use super::InfraIdQueryParam; use super::SimulationSummaryResult; -use crate::core::simulation::SimulationResponse; -use crate::error::Result; use crate::models::paced_train::PacedTrain; -use crate::models::prelude::*; -use crate::views::projection::ProjectPathTrainResult; use crate::views::AuthorizationError; -use crate::views::ListId; crate::routes! { "/paced_train" => { @@ -56,10 +62,13 @@ editoast_common::schemas! { enum PacedTrainError { #[error("{count} paced train(s) could not be found")] #[editoast_error(status = 404)] - BatchPacedTrainNotFound { count: usize }, + BatchNotFound { count: usize }, + #[error("Paced train '{paced_train_id}', could not be found")] #[editoast_error(status = 404)] - #[error("Paced train {paced_train_id} does not exist")] - PacedTrainNotFound { paced_train_id: i64 }, + NotFound { paced_train_id: i64 }, + #[error("Infra '{infra_id}', could not be found")] + #[editoast_error(status = 404)] + InfraNotFound { infra_id: i64 }, #[error(transparent)] #[editoast_error(status = 500)] Database(#[from] editoast_models::model::Error), @@ -67,7 +76,7 @@ enum PacedTrainError { #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct PacedTrainForm { - /// Timetable attached to the train schedule + /// Timetable attached to the paced train pub timetable_id: Option, #[serde(flatten)] pub paced_train_base: PacedTrainBase, @@ -121,7 +130,7 @@ async fn get_by_id( let conn = &mut db_pool.get().await?; let paced_train = PacedTrain::retrieve_or_fail(conn, paced_train_id, || { - PacedTrainError::PacedTrainNotFound { paced_train_id } + PacedTrainError::NotFound { paced_train_id } }) .await?; @@ -156,7 +165,7 @@ async fn delete( let conn = &mut db_pool.get().await?; PacedTrain::delete_batch_or_fail(conn, paced_train_ids, |count| { - PacedTrainError::BatchPacedTrainNotFound { count } + PacedTrainError::BatchNotFound { count } }) .await?; @@ -202,19 +211,62 @@ struct SimulationBatchForm { )] async fn simulation_summary( State(AppState { - db_pool: _db_pool, - valkey: _valkey_client, - core_client: _core, + db_pool, + valkey: valkey_client, + core_client: core, .. }): State, - Extension(_auth): AuthenticationExt, + Extension(auth): AuthenticationExt, Json(SimulationBatchForm { - infra_id: _infra_id, - electrical_profile_set_id: _electrical_profile_set_id, - ids: _paced_train_ids, + infra_id, + electrical_profile_set_id, + ids: paced_train_ids, }): Json, ) -> Result>> { - todo!(); + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + + let conn = &mut db_pool.get().await?; + + let infra = Infra::retrieve_or_fail(conn, infra_id, || PacedTrainError::InfraNotFound { + infra_id, + }) + .await?; + + let paced_trains: Vec = + PacedTrain::retrieve_batch_or_fail(conn, paced_train_ids, |missing| { + PacedTrainError::BatchNotFound { + count: missing.len(), + } + }) + .await?; + let paced_trains_to_ts: Vec = + paced_trains.clone().into_iter().map_into().collect(); + + let simulations = train_simulation_batch( + conn, + valkey_client, + core, + &paced_trains_to_ts, + &infra, + electrical_profile_set_id, + ) + .await?; + + // Transform simulations to simulation summary + let mut simulation_summaries = HashMap::new(); + for (paced_train, sim) in paced_trains.into_iter().zip(simulations) { + let (sim, _) = sim; + let simulation_summary_result = simulation_response(sim); + simulation_summaries.insert(paced_train.id, simulation_summary_result); + } + + Ok(Json(simulation_summaries)) } /// Get a path from a paced train given an infrastructure id and a paced train id @@ -262,23 +314,53 @@ pub struct ElectricalProfileSetIdQueryParam { )] async fn simulation( State(AppState { - valkey: _valkey_client, - core_client: _core_client, - db_pool: _db_pool, + valkey: valkey_client, + core_client, + db_pool, .. }): State, - Extension(_auth): AuthenticationExt, - Path(PacedTrainIdParam { - id: _paced_train_id, - }): Path, - Query(InfraIdQueryParam { - infra_id: _infra_id, - }): Query, + Extension(auth): AuthenticationExt, + Path(PacedTrainIdParam { id: paced_train_id }): Path, + Query(InfraIdQueryParam { infra_id }): Query, Query(ElectricalProfileSetIdQueryParam { - electrical_profile_set_id: _electrical_profile_set_id, + electrical_profile_set_id, }): Query, ) -> Result> { - todo!(); + let authorized = auth + .check_roles([BuiltinRole::OperationalStudies].into()) + .await + .map_err(AuthorizationError::AuthError)?; + if !authorized { + return Err(AuthorizationError::Forbidden.into()); + } + + // Retrieve infra or fail + let infra = Infra::retrieve_or_fail(&mut db_pool.get().await?, infra_id, || { + PacedTrainError::InfraNotFound { infra_id } + }) + .await?; + + // Retrieve paced_train or fail + let paced_train = + PacedTrain::retrieve_or_fail(&mut db_pool.get().await?, paced_train_id, || { + PacedTrainError::NotFound { paced_train_id } + }) + .await?; + + // Compute simulation of a paced_train + let (simulation, _) = train_simulation_batch( + &mut db_pool.get().await?, + valkey_client, + core_client, + &[paced_train.into()], + &infra, + electrical_profile_set_id, + ) + .await? + .pop() + .unwrap(); + + Ok(Json(simulation)) } /// Projects the space time curves and paths of a number of paced trains onto a given path @@ -315,19 +397,32 @@ async fn project_path( #[cfg(test)] mod tests { - use axum::http::StatusCode; - use pretty_assertions::assert_eq; + use std::collections::HashMap; + + use chrono::Duration; + use editoast_models::DbConnectionPoolV2; + use editoast_schemas::paced_train::{Paced, PacedTrainBase}; + use editoast_schemas::train_schedule::TrainScheduleBase; use rstest::rstest; use serde_json::json; - use crate::error::InternalError; - use crate::models::fixtures::create_simple_paced_train; - use crate::models::fixtures::create_timetable; - use crate::models::fixtures::simple_paced_train_base; - use crate::models::paced_train::PacedTrain; + use crate::core::simulation::{CompleteReportTrain, ElectricalProfiles, ReportTrain, SimulationResponse, SpeedLimitProperties}; + use crate::models::fixtures::create_fast_rolling_stock; + use crate::models::fixtures::create_small_infra; + use crate::models::paced_train::PacedTrainChangeset; use crate::models::prelude::*; - use crate::views::paced_train::PacedTrainResult; - use crate::views::test_app::TestAppBuilder; + use crate::views::SimulationSummaryResult; + use crate::views::test_app::TestApp; + use crate::views::train_schedule::tests::mocked_core_pathfinding_sim_and_proj; + use crate::views::InternalError; + use crate::{ + models::{ + fixtures::{create_simple_paced_train, create_timetable, simple_paced_train_base}, + paced_train::PacedTrain, + }, + views::{paced_train::PacedTrainResult, test_app::TestAppBuilder}, + }; + use axum::http::StatusCode; #[rstest] async fn paced_train_post() { @@ -379,10 +474,9 @@ mod tests { assert_eq!( &response.error_type, - "editoast:paced_train:PacedTrainNotFound" + "editoast:paced_train:NotFound" ) } - #[rstest] async fn get_paced_train() { let app = TestAppBuilder::default_app(); @@ -406,4 +500,108 @@ mod tests { ); assert_eq!(paced_train.step, response.paced_train.paced.step.into()); } + + async fn app_infra_id_paced_train_id_for_simulation_tests() -> (TestApp, i64, i64) { + let db_pool = DbConnectionPoolV2::for_tests(); + let small_infra = create_small_infra(&mut db_pool.get_ok()).await; + let timetable = create_timetable(&mut db_pool.get_ok()).await; + let rolling_stock = + create_fast_rolling_stock(&mut db_pool.get_ok(), "simulation_rolling_stock").await; + let paced_train_base: PacedTrainBase = PacedTrainBase { + train_schedule_base: TrainScheduleBase { + rolling_stock_name: rolling_stock.name.clone(), + ..serde_json::from_str(include_str!("../tests/train_schedules/simple.json")) + .expect("Unable to parse") + }, + paced: Paced { + duration: Duration::hours(1).try_into().unwrap(), + step: Duration::minutes(15).try_into().unwrap(), + }, + }; + let paced_train: PacedTrainChangeset = paced_train_base.into(); + let paced_train = paced_train + .timetable_id(timetable.id) + .create(&mut db_pool.get_ok()) + .await + .expect("Failed to create paced train"); + let core = mocked_core_pathfinding_sim_and_proj(paced_train.id); + let app = TestAppBuilder::new() + .db_pool(db_pool.clone()) + .core_client(core.into()) + .build(); + (app, small_infra.id, paced_train.id) + } + + #[rstest] + async fn paced_train_simulation() { + let (app, infra_id, train_schedule_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = app.get( + format!( + "/paced_train/{}/simulation/?infra_id={}", + train_schedule_id, infra_id + ) + .as_str(), + ); + let response: SimulationResponse = app.fetch(request).assert_status(StatusCode::OK).json_into(); + + assert_eq!(response, SimulationResponse::Success { + base: ReportTrain { + positions: vec![], + times: vec![], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000], + }, + provisional: ReportTrain { + positions: vec![], + times: vec![0, 10], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000], + }, + final_output: CompleteReportTrain { + report_train: ReportTrain { + positions: vec![], + times: vec![], + speeds: vec![], + energy_consumption: 0.0, + path_item_times: vec![0, 1000, 2000, 3000], + }, + signal_critical_positions: vec![], + zone_updates: vec![], + spacing_requirements: vec![], + routing_requirements: vec![], + }, + mrsp: SpeedLimitProperties { + boundaries: vec![], + values: vec![], + }, + electrical_profiles: ElectricalProfiles { + boundaries: vec![], + values: vec![], + }, + }); + } + + #[rstest] + async fn paced_train_simulation_summary() { + let (app, infra_id, paced_train_id) = + app_infra_id_paced_train_id_for_simulation_tests().await; + let request = app.post("/paced_train/simulation_summary").json(&json!({ + "infra_id": infra_id, + "ids": vec![paced_train_id], + })); + let response: HashMap = app.fetch(request).assert_status(StatusCode::OK).json_into(); + assert_eq!(response.len(), 1); + assert_eq!(*response.get(&paced_train_id).unwrap(), + SimulationSummaryResult::Success { + length: 0, + time: 0, + energy_consumption: 0.0, + path_item_times_final: vec![], + path_item_times_provisional: vec![], + path_item_times_base: vec![], + }); + } } diff --git a/editoast/src/views/timetable.rs b/editoast/src/views/timetable.rs index 57e8797b041..637f11cb9a1 100644 --- a/editoast/src/views/timetable.rs +++ b/editoast/src/views/timetable.rs @@ -313,7 +313,7 @@ async fn post_paced_train( let changesets = paced_trains .into_iter() .map(PacedTrainChangeset::from) - .map(|cs| cs.timetable_id(timetable_id)) + .map(|cs: PacedTrainChangeset| cs.timetable_id(timetable_id)) .collect::>(); // Create a batch of paced trains diff --git a/editoast/src/views/train_schedule.rs b/editoast/src/views/train_schedule.rs index 565cce6e9e7..dff896d7290 100644 --- a/editoast/src/views/train_schedule.rs +++ b/editoast/src/views/train_schedule.rs @@ -636,6 +636,45 @@ struct SimulationBatchForm { ids: HashSet, } +pub fn simulation_response(sim: SimulationResponse) -> SimulationSummaryResult { + match sim { + SimulationResponse::Success { + final_output, + provisional, + base, + .. + } => { + let report = final_output.report_train; + SimulationSummaryResult::Success { + length: *report.positions.last().unwrap(), + time: *report.times.last().unwrap(), + energy_consumption: report.energy_consumption, + path_item_times_final: report.path_item_times.clone(), + path_item_times_provisional: provisional.path_item_times.clone(), + path_item_times_base: base.path_item_times.clone(), + } + } + SimulationResponse::PathfindingFailed { pathfinding_failed } => match pathfinding_failed { + PathfindingFailure::InternalError { core_error } => { + SimulationSummaryResult::PathfindingFailure { core_error } + } + + PathfindingFailure::PathfindingInputError(input_error) => { + SimulationSummaryResult::PathfindingInputError(input_error) + } + + PathfindingFailure::PathfindingNotFound(not_found) => { + SimulationSummaryResult::PathfindingNotFound(not_found) + } + }, + SimulationResponse::SimulationFailed { core_error } => { + SimulationSummaryResult::SimulationFailed { + error_type: core_error.get_type().into(), + } + } + } +} + /// Associate each train id with its simulation summary response /// If the simulation fails, it associates the reason: pathfinding failed or running time failed #[utoipa::path( @@ -696,44 +735,7 @@ async fn simulation_summary( let mut simulation_summaries = HashMap::new(); for (train_schedule, sim) in train_schedules.iter().zip(simulations) { let (sim, _) = sim; - let simulation_summary_result = match sim { - SimulationResponse::Success { - final_output, - provisional, - base, - .. - } => { - let report = final_output.report_train; - SimulationSummaryResult::Success { - length: *report.positions.last().unwrap(), - time: *report.times.last().unwrap(), - energy_consumption: report.energy_consumption, - path_item_times_final: report.path_item_times.clone(), - path_item_times_provisional: provisional.path_item_times.clone(), - path_item_times_base: base.path_item_times.clone(), - } - } - SimulationResponse::PathfindingFailed { pathfinding_failed } => { - match pathfinding_failed { - PathfindingFailure::InternalError { core_error } => { - SimulationSummaryResult::PathfindingFailure { core_error } - } - - PathfindingFailure::PathfindingInputError(input_error) => { - SimulationSummaryResult::PathfindingInputError(input_error) - } - - PathfindingFailure::PathfindingNotFound(not_found) => { - SimulationSummaryResult::PathfindingNotFound(not_found) - } - } - } - SimulationResponse::SimulationFailed { core_error } => { - SimulationSummaryResult::SimulationFailed { - error_type: core_error.get_type().into(), - } - } - }; + let simulation_summary_result = simulation_response(sim); simulation_summaries.insert(train_schedule.id, simulation_summary_result); } @@ -996,7 +998,7 @@ async fn project_path( } #[cfg(test)] -mod tests { +pub mod tests { use axum::http::StatusCode; use chrono::DateTime; use chrono::Utc; @@ -1105,7 +1107,7 @@ mod tests { ) } - fn mocked_core_pathfinding_sim_and_proj(train_id: i64) -> MockingClient { + pub(in crate::views) fn mocked_core_pathfinding_sim_and_proj(train_id: i64) -> MockingClient { let mut core = MockingClient::new(); core.stub("/v2/pathfinding/blocks") .method(reqwest::Method::POST) diff --git a/front/public/locales/en/errors.json b/front/public/locales/en/errors.json index c67412b5f62..eb9f62a05a0 100644 --- a/front/public/locales/en/errors.json +++ b/front/public/locales/en/errors.json @@ -241,8 +241,9 @@ }, "paced_train": { "Database": "Internal error (database)", - "BatchPacedTrainNotFound": "Some Paced trains could not be found", - "PacedTrainNotFound": "Paced train '{{paced_train_id}}' not found" + "BatchNotFound": "'{{count}}' paced trains could not be found", + "NotFound": "Paced train '{{paced_train_id}}' could not be found", + "InfraNotFound": "Infrastructure '{{infra_id}}' could not be found" }, "url": { "InvalidUrl": "Invalid url '{{url}}'" diff --git a/front/public/locales/fr/errors.json b/front/public/locales/fr/errors.json index e433ed94c18..838e932d649 100644 --- a/front/public/locales/fr/errors.json +++ b/front/public/locales/fr/errors.json @@ -238,8 +238,9 @@ }, "paced_train": { "Database": "Erreur interne (base de données)", - "BatchPacedTrainNotFound": "Certaines missions sont introuvables", - "PacedTrainNotFound": "Mission '{{paced_train_id}}' non trouvée" + "BatchNotFound": "'{{count}}' missions sont introuvables", + "NotFound": "Mission '{{paced_train_id}}' non trouvée", + "InfraNotFound": "Infrastructure '{{infra_id}}' non trouvée" }, "url": { "InvalidUrl": "Url invalide '{{url}}'" diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index ab8bba6e601..f347220a4ed 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -493,7 +493,7 @@ const injectedRtkApi = api }), invalidatesTags: ['paced_train'], }), - postPacedTrainSimulationSummary: build.mutation< + postPacedTrainSimulationSummary: build.query< PostPacedTrainSimulationSummaryApiResponse, PostPacedTrainSimulationSummaryApiArg >({ @@ -502,7 +502,7 @@ const injectedRtkApi = api method: 'POST', body: queryArg.body, }), - invalidatesTags: ['paced_train'], + providesTags: ['paced_train'], }), getPacedTrainById: build.query({ query: (queryArg) => ({ url: `/paced_train/${queryArg.id}` }), @@ -3250,7 +3250,7 @@ export type PacedTrainResult = PacedTrainBase & { timetable_id: number; }; export type PacedTrainForm = PacedTrainBase & { - /** Timetable attached to the train schedule */ + /** Timetable attached to the paced train */ timetable_id?: number | null; }; export type ReportTrain = { diff --git a/front/src/config/openapi-editoast-config.cts b/front/src/config/openapi-editoast-config.cts index dd5f3cd7b71..68d325efe6a 100644 --- a/front/src/config/openapi-editoast-config.cts +++ b/front/src/config/openapi-editoast-config.cts @@ -15,8 +15,10 @@ const config: ConfigFile = { 'postInfraByInfraIdPathfinding', 'postInfraByInfraIdPathfindingBlocks', 'postInfraByInfraIdPathProperties', + 'postPacedTrainSimulationSummary', 'postTrainSchedule', 'postTrainScheduleSimulationSummary', + 'postPacedTrainSimulationSummary', 'postTrainScheduleProjectPath', 'postWorkSchedulesProjectPath', ],