Skip to content

Commit 463cc1d

Browse files
Wadjetzyounesschrifi
authored andcommitted
editoast: adapt paced train simulation summary (#15591)
fix #15592 part of #15202 Adapt GET /paced_train/simulation_summary endpoint with new exceptions Add timetable_id parameter to the simulation summary endpoint, because exceptions are now linked to a timetable Fix tests and frontend > [!NOTE] > This PR targets a feature branch and may contain all its commits, as it has not been rebased yet. Only the last commit need to be reviewed. Signed-off-by: Egor <egor@berezify.fr>
1 parent 744d14c commit 463cc1d

File tree

14 files changed

+130
-92
lines changed

14 files changed

+130
-92
lines changed

editoast/openapi.yaml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editoast/src/views/timetable/paced_train.rs

Lines changed: 103 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ pub(in crate::views) async fn delete(
245245
#[derive(Debug, Clone, Deserialize, ToSchema)]
246246
pub(in crate::views) struct SimulationBatchForm {
247247
infra_id: i64,
248+
timetable_id: i64,
248249
electrical_profile_set_id: Option<i64>,
249250
ids: HashSet<i64>,
250251
}
@@ -287,6 +288,7 @@ pub(in crate::views) async fn simulation_summary(
287288
Extension(auth): AuthenticationExt,
288289
Json(SimulationBatchForm {
289290
infra_id,
291+
timetable_id,
290292
electrical_profile_set_id,
291293
ids: paced_train_ids,
292294
}): Json<SimulationBatchForm>,
@@ -315,31 +317,46 @@ pub(in crate::views) async fn simulation_summary(
315317
.await?;
316318

317319
let paced_trains: Vec<models::TrainSchedule> =
318-
models::TrainSchedule::retrieve_batch_or_fail(conn, paced_train_ids, |missing| {
320+
models::TrainSchedule::retrieve_batch_or_fail(conn, paced_train_ids.clone(), |missing| {
319321
TrainScheduleError::BatchNotFound {
320322
count: missing.len(),
321323
}
322324
})
323325
.await?;
324326

325-
let simulation_contexts: Vec<SimulationContext> =
326-
paced_trains
327-
.iter()
328-
.flat_map(|paced_train| {
329-
std::iter::once(SimulationContext {
330-
paced_train_id: paced_train.id,
331-
exception_key: None,
332-
train_schedule: paced_train.clone().into_train_occurrence(),
333-
})
334-
.chain(paced_train.exceptions.iter().map(|exception| {
335-
SimulationContext {
336-
paced_train_id: paced_train.id,
337-
exception_key: Some(exception.key.clone()),
338-
train_schedule: paced_train.apply_exception(exception),
339-
}
340-
}))
327+
let mut exceptions = TrainScheduleException::retrieve_exceptions_by_train_schedules(
328+
conn,
329+
timetable_id,
330+
paced_train_ids.into_iter().collect(),
331+
)
332+
.await?
333+
.into_iter()
334+
.map_into::<schemas::TrainScheduleException>()
335+
.into_group_map_by(|e| e.train_schedule_id);
336+
337+
let simulation_contexts: Vec<SimulationContext> = paced_trains
338+
.iter()
339+
.flat_map(|paced_train| {
340+
let ts_exceptions = exceptions.remove(&paced_train.id).unwrap_or_default();
341+
std::iter::once(SimulationContext {
342+
paced_train_id: paced_train.id,
343+
exception_key: None,
344+
train_schedule: paced_train.clone().into_train_occurrence(),
341345
})
342-
.collect();
346+
.chain(
347+
ts_exceptions
348+
.into_iter()
349+
.map(|exception| {
350+
SimulationContext {
351+
paced_train_id: paced_train.id,
352+
exception_key: exception.key.clone(), // TODO use exception.id
353+
train_schedule: paced_train.apply_train_schedule_exception(&exception),
354+
}
355+
})
356+
.collect::<Vec<_>>(),
357+
)
358+
})
359+
.collect();
343360

344361
let schedules: Vec<schemas::TrainOccurrence> = simulation_contexts
345362
.iter()
@@ -398,12 +415,6 @@ pub(in crate::views) async fn simulation_summary(
398415
#[derive(Debug, Default, Clone, Serialize, Deserialize, IntoParams, ToSchema)]
399416
#[into_params(parameter_in = Query)]
400417
pub(in crate::views) struct ExceptionQueryParam {
401-
exception_key: Option<String>,
402-
}
403-
404-
#[derive(Debug, Default, Clone, Serialize, Deserialize, IntoParams, ToSchema)]
405-
#[into_params(parameter_in = Query)]
406-
pub(in crate::views) struct TrainScheduleExceptionQueryParam {
407418
exception_id: Option<i64>,
408419
}
409420

@@ -412,7 +423,7 @@ pub(in crate::views) struct TrainScheduleExceptionQueryParam {
412423
#[utoipa::path(
413424
get, path = "",
414425
tags = ["paced_train", "pathfinding"],
415-
params(TrainScheduleIdParam, InfraIdQueryParam, TrainScheduleExceptionQueryParam),
426+
params(TrainScheduleIdParam, InfraIdQueryParam, ExceptionQueryParam),
416427
responses(
417428
(status = 200, description = "The path", body = PathfindingResult),
418429
(status = 404, description = "Infrastructure or Train schedule not found")
@@ -431,9 +442,7 @@ pub(in crate::views) async fn get_path(
431442
id: train_schedule_id,
432443
}): Path<TrainScheduleIdParam>,
433444
Query(InfraIdQueryParam { infra_id }): Query<InfraIdQueryParam>,
434-
Query(TrainScheduleExceptionQueryParam { exception_id }): Query<
435-
TrainScheduleExceptionQueryParam,
436-
>,
445+
Query(ExceptionQueryParam { exception_id }): Query<ExceptionQueryParam>,
437446
) -> Result<Json<PathfindingResult>> {
438447
let authorized = auth
439448
.check_roles([authz::Role::OperationalStudies, authz::Role::Stdcm].into())
@@ -503,7 +512,7 @@ pub struct ElectricalProfileSetIdQueryParam {
503512
#[utoipa::path(
504513
get, path = "",
505514
tag = "paced_train",
506-
params(TrainScheduleIdParam, InfraIdQueryParam, ElectricalProfileSetIdQueryParam, TrainScheduleExceptionQueryParam),
515+
params(TrainScheduleIdParam, InfraIdQueryParam, ElectricalProfileSetIdQueryParam, ExceptionQueryParam),
507516
responses(
508517
(status = 200, description = "Simulation Output", body = simulation::Response),
509518
),
@@ -524,9 +533,7 @@ pub(in crate::views) async fn simulation(
524533
Query(ElectricalProfileSetIdQueryParam {
525534
electrical_profile_set_id,
526535
}): Query<ElectricalProfileSetIdQueryParam>,
527-
Query(TrainScheduleExceptionQueryParam { exception_id }): Query<
528-
TrainScheduleExceptionQueryParam,
529-
>,
536+
Query(ExceptionQueryParam { exception_id }): Query<ExceptionQueryParam>,
530537
) -> Result<Json<simulation::Response>> {
531538
let authorized = auth
532539
.check_roles([authz::Role::OperationalStudies].into())
@@ -597,7 +604,7 @@ pub(in crate::views) async fn simulation(
597604
#[utoipa::path(
598605
get, path = "",
599606
tags = ["paced_train", "etcs_braking_curves"],
600-
params(TrainScheduleIdParam, InfraIdQueryParam, ElectricalProfileSetIdQueryParam, TrainScheduleExceptionQueryParam),
607+
params(TrainScheduleIdParam, InfraIdQueryParam, ElectricalProfileSetIdQueryParam, ExceptionQueryParam),
601608
responses(
602609
(status = 200, description = "ETCS Braking Curves Output", body = core_client::etcs_braking_curves::Response),
603610
),
@@ -618,9 +625,7 @@ pub(in crate::views) async fn etcs_braking_curves(
618625
Query(ElectricalProfileSetIdQueryParam {
619626
electrical_profile_set_id,
620627
}): Query<ElectricalProfileSetIdQueryParam>,
621-
Query(TrainScheduleExceptionQueryParam { exception_id }): Query<
622-
TrainScheduleExceptionQueryParam,
623-
>,
628+
Query(ExceptionQueryParam { exception_id }): Query<ExceptionQueryParam>,
624629
) -> Result<Json<core_client::etcs_braking_curves::Response>> {
625630
let authorized = auth
626631
.check_roles([authz::Role::OperationalStudies, authz::Role::Stdcm].into())
@@ -1671,6 +1676,7 @@ mod tests {
16711676
use crate::models::fixtures::create_paced_train_with_exceptions;
16721677
use crate::models::fixtures::create_simple_paced_train;
16731678
use crate::models::fixtures::create_small_infra;
1679+
use crate::models::fixtures::create_timetable;
16741680
use crate::models::fixtures::create_timetable_with_train_schedule_set;
16751681
use crate::models::fixtures::create_train_schedule_exception;
16761682
use crate::models::fixtures::create_train_schedule_set;
@@ -2207,66 +2213,72 @@ mod tests {
22072213

22082214
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
22092215
async fn paced_train_simulation_summary() {
2210-
let (app, infra_id, paced_train_id, _exception) =
2211-
app_infra_id_paced_train_id_for_simulation_tests().await;
2212-
let request = app.get(format!("/train_schedules/{paced_train_id}").as_str());
2213-
let mut paced_train_response: TrainScheduleResponse = app
2214-
.fetch(request)
2216+
// Setup tests tools
2217+
let core = mocked_core_pathfinding_sim_and_proj();
2218+
let app = TestAppBuilder::new()
2219+
.db_pool(DbConnectionPoolV2::for_tests())
2220+
.core_client(core.into())
2221+
.build();
2222+
let db_pool = app.db_pool();
2223+
2224+
// Setup tests data
2225+
let small_infra = create_small_infra(&mut db_pool.get_ok()).await;
2226+
2227+
let (timetable, train_schedule_set) =
2228+
create_timetable_with_train_schedule_set(&mut db_pool.get_ok()).await;
2229+
create_fast_rolling_stock(&mut db_pool.get_ok(), "R2D2").await;
2230+
2231+
let train_schedule = TrainSchedule {
2232+
train_occurrence: schemas::TrainOccurrence::fake(),
2233+
paced: Some(Paced {
2234+
time_window: Duration::hours(1).try_into().unwrap(),
2235+
interval: Duration::minutes(15).try_into().unwrap(),
2236+
exceptions: vec![],
2237+
}),
2238+
};
2239+
let train_schedule: TrainScheduleChangeset = train_schedule.into();
2240+
let train_schedule = train_schedule
2241+
.train_schedule_set_id(train_schedule_set.id)
2242+
.create(&mut db_pool.get_ok())
22152243
.await
2216-
.assert_status(StatusCode::OK)
2217-
.json_into();
2218-
// First remove all already generated exceptions
2219-
paced_train_response
2220-
.train_schedule
2221-
.paced
2222-
.as_mut()
2223-
.unwrap()
2224-
.exceptions
2225-
.clear();
2244+
.expect("Failed to create train schedule");
2245+
22262246
// Add one exception which will not change the simulation from base
2227-
paced_train_response
2228-
.train_schedule
2229-
.paced
2230-
.as_mut()
2231-
.unwrap()
2232-
.exceptions
2233-
.push(PacedTrainException {
2234-
key: "change_train_name".to_string(),
2235-
change_groups: TrainScheduleExceptionChangeGroups {
2236-
train_name: Some(TrainNameChangeGroup {
2237-
value: "exception_name_but_same_simulation".into(),
2238-
}),
2239-
..Default::default()
2240-
},
2247+
let _change_train_name_exception = create_train_schedule_exception(
2248+
&mut db_pool.get_ok(),
2249+
timetable.id,
2250+
train_schedule.id,
2251+
None,
2252+
Some("change_train_name".to_string()),
2253+
Some(TrainScheduleExceptionChangeGroups {
2254+
train_name: Some(TrainNameChangeGroup {
2255+
value: "exception_name_but_same_simulation".into(),
2256+
}),
22412257
..Default::default()
2242-
});
2258+
}),
2259+
)
2260+
.await;
2261+
22432262
// Add one exception which will change the simulation from base
2244-
// and therefore add another entry in the response (field `exceptions`)
2245-
paced_train_response
2246-
.train_schedule
2247-
.paced
2248-
.as_mut()
2249-
.unwrap()
2250-
.exceptions
2251-
.push(PacedTrainException {
2252-
key: "change_initial_speed".to_string(),
2253-
change_groups: TrainScheduleExceptionChangeGroups {
2254-
initial_speed: Some(InitialSpeedChangeGroup { value: 1.23 }),
2255-
..Default::default()
2256-
},
2263+
let _change_train_name_exception = create_train_schedule_exception(
2264+
&mut db_pool.get_ok(),
2265+
timetable.id,
2266+
train_schedule.id,
2267+
None,
2268+
Some("change_initial_speed".to_string()),
2269+
Some(TrainScheduleExceptionChangeGroups {
2270+
initial_speed: Some(InitialSpeedChangeGroup { value: 1.23 }),
22572271
..Default::default()
2258-
});
2259-
let request = app
2260-
.put(format!("/train_schedules/{paced_train_id}").as_str())
2261-
.json(&json!(paced_train_response.train_schedule));
2262-
app.fetch(request)
2263-
.await
2264-
.assert_status(StatusCode::NO_CONTENT);
2272+
}),
2273+
)
2274+
.await;
2275+
22652276
let request = app
22662277
.post("/train_schedules/simulation_summary")
22672278
.json(&json!({
2268-
"infra_id": infra_id,
2269-
"ids": vec![paced_train_id],
2279+
"infra_id": small_infra.id,
2280+
"timetable_id": timetable.id,
2281+
"ids": vec![train_schedule.id],
22702282
}));
22712283

22722284
let response: HashMap<i64, TrainScheduleSummaryResponse> = app
@@ -2276,7 +2288,7 @@ mod tests {
22762288
.json_into();
22772289
assert_eq!(response.len(), 1);
22782290
assert_eq!(
2279-
*response.get(&paced_train_id).unwrap(),
2291+
*response.get(&train_schedule.id).unwrap(),
22802292
TrainScheduleSummaryResponse {
22812293
train_schedule: SummaryResponse::Success {
22822294
length: 15_050_000,
@@ -2313,10 +2325,12 @@ mod tests {
23132325
async fn paced_train_simulation_summary_not_found() {
23142326
let (app, infra_id, _paced_train_id, _exception) =
23152327
app_infra_id_paced_train_id_for_simulation_tests().await;
2328+
let timetable = create_timetable(&mut app.db_pool().get_ok()).await;
23162329
let request = app
23172330
.post("/train_schedules/simulation_summary")
23182331
.json(&json!({
23192332
"infra_id": infra_id,
2333+
"timetable_id": timetable.id,
23202334
"ids": vec![0],
23212335
}));
23222336
let response: InternalError = app

editoast/src/views/timetable/tests/train_schedule_honored.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
{
99
"id": "id440",
1010
"location": {
11+
"type": "track_offset",
1112
"track": "TA0",
1213
"offset": 1299000
1314
}
1415
},
1516
{
1617
"id": "id450",
1718
"location": {
19+
"type": "track_offset",
1820
"track": "TG1",
1921
"offset": 644000
2022
}

editoast/src/views/timetable/tests/train_schedule_no_schedule.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
{
99
"id": "id440",
1010
"location": {
11+
"type": "track_offset",
1112
"track": "TA0",
1213
"offset": 1299000
1314
}
1415
},
1516
{
1617
"id": "id450",
1718
"location": {
19+
"type": "track_offset",
1820
"track": "TG1",
1921
"offset": 644000
2022
}

editoast/src/views/timetable/tests/train_schedule_not_honored.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
{
99
"id": "id440",
1010
"location": {
11+
"type": "track_offset",
1112
"track": "TA0",
1213
"offset": 1299000
1314
}
1415
},
1516
{
1617
"id": "id584",
1718
"location": {
19+
"type": "operational_point_part_reference",
1820
"operational_point": {
1921
"uic": 4,
2022
"secondary_code": "BV",
@@ -25,6 +27,7 @@
2527
{
2628
"id": "id450",
2729
"location": {
30+
"type": "track_offset",
2831
"track": "TG1",
2932
"offset": 644000
3033
}

0 commit comments

Comments
 (0)