Skip to content

Commit 229b162

Browse files
Wadjetztheocrsb
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 2050f05 commit 229b162

File tree

14 files changed

+127
-89
lines changed

14 files changed

+127
-89
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: 100 additions & 86 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

@@ -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())
@@ -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())
@@ -620,9 +627,7 @@ pub(in crate::views) async fn etcs_braking_curves(
620627
Query(ElectricalProfileSetIdQueryParam {
621628
electrical_profile_set_id,
622629
}): Query<ElectricalProfileSetIdQueryParam>,
623-
Query(TrainScheduleExceptionQueryParam { exception_id }): Query<
624-
TrainScheduleExceptionQueryParam,
625-
>,
630+
Query(ExceptionQueryParam { exception_id }): Query<ExceptionQueryParam>,
626631
) -> Result<Json<core_client::etcs_braking_curves::Response>> {
627632
let authorized = auth
628633
.check_roles([authz::Role::OperationalStudies, authz::Role::Stdcm].into())
@@ -1692,6 +1697,7 @@ mod tests {
16921697
use crate::models::fixtures::create_paced_train_with_exceptions;
16931698
use crate::models::fixtures::create_simple_paced_train;
16941699
use crate::models::fixtures::create_small_infra;
1700+
use crate::models::fixtures::create_timetable;
16951701
use crate::models::fixtures::create_timetable_with_train_schedule_set;
16961702
use crate::models::fixtures::create_train_schedule_exception;
16971703
use crate::models::fixtures::create_train_schedule_set;
@@ -2228,66 +2234,72 @@ mod tests {
22282234

22292235
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
22302236
async fn paced_train_simulation_summary() {
2231-
let (app, infra_id, paced_train_id, _exception) =
2232-
app_infra_id_paced_train_id_for_simulation_tests().await;
2233-
let request = app.get(format!("/train_schedules/{paced_train_id}").as_str());
2234-
let mut paced_train_response: TrainScheduleResponse = app
2235-
.fetch(request)
2237+
// Setup tests tools
2238+
let core = mocked_core_pathfinding_sim_and_proj();
2239+
let app = TestAppBuilder::new()
2240+
.db_pool(DbConnectionPoolV2::for_tests())
2241+
.core_client(core.into())
2242+
.build();
2243+
let db_pool = app.db_pool();
2244+
2245+
// Setup tests data
2246+
let small_infra = create_small_infra(&mut db_pool.get_ok()).await;
2247+
2248+
let (timetable, train_schedule_set) =
2249+
create_timetable_with_train_schedule_set(&mut db_pool.get_ok()).await;
2250+
create_fast_rolling_stock(&mut db_pool.get_ok(), "R2D2").await;
2251+
2252+
let train_schedule = TrainSchedule {
2253+
train_occurrence: schemas::TrainOccurrence::fake(),
2254+
paced: Some(Paced {
2255+
time_window: Duration::hours(1).try_into().unwrap(),
2256+
interval: Duration::minutes(15).try_into().unwrap(),
2257+
exceptions: vec![],
2258+
}),
2259+
};
2260+
let train_schedule: TrainScheduleChangeset = train_schedule.into();
2261+
let train_schedule = train_schedule
2262+
.train_schedule_set_id(train_schedule_set.id)
2263+
.create(&mut db_pool.get_ok())
22362264
.await
2237-
.assert_status(StatusCode::OK)
2238-
.json_into();
2239-
// First remove all already generated exceptions
2240-
paced_train_response
2241-
.train_schedule
2242-
.paced
2243-
.as_mut()
2244-
.unwrap()
2245-
.exceptions
2246-
.clear();
2265+
.expect("Failed to create train schedule");
2266+
22472267
// Add one exception which will not change the simulation from base
2248-
paced_train_response
2249-
.train_schedule
2250-
.paced
2251-
.as_mut()
2252-
.unwrap()
2253-
.exceptions
2254-
.push(PacedTrainException {
2255-
key: "change_train_name".to_string(),
2256-
change_groups: TrainScheduleExceptionChangeGroups {
2257-
train_name: Some(TrainNameChangeGroup {
2258-
value: "exception_name_but_same_simulation".into(),
2259-
}),
2260-
..Default::default()
2261-
},
2268+
let _change_train_name_exception = create_train_schedule_exception(
2269+
&mut db_pool.get_ok(),
2270+
timetable.id,
2271+
train_schedule.id,
2272+
None,
2273+
Some("change_train_name".to_string()),
2274+
Some(TrainScheduleExceptionChangeGroups {
2275+
train_name: Some(TrainNameChangeGroup {
2276+
value: "exception_name_but_same_simulation".into(),
2277+
}),
22622278
..Default::default()
2263-
});
2279+
}),
2280+
)
2281+
.await;
2282+
22642283
// Add one exception which will change the simulation from base
2265-
// and therefore add another entry in the response (field `exceptions`)
2266-
paced_train_response
2267-
.train_schedule
2268-
.paced
2269-
.as_mut()
2270-
.unwrap()
2271-
.exceptions
2272-
.push(PacedTrainException {
2273-
key: "change_initial_speed".to_string(),
2274-
change_groups: TrainScheduleExceptionChangeGroups {
2275-
initial_speed: Some(InitialSpeedChangeGroup { value: 1.23 }),
2276-
..Default::default()
2277-
},
2284+
let _change_train_name_exception = create_train_schedule_exception(
2285+
&mut db_pool.get_ok(),
2286+
timetable.id,
2287+
train_schedule.id,
2288+
None,
2289+
Some("change_initial_speed".to_string()),
2290+
Some(TrainScheduleExceptionChangeGroups {
2291+
initial_speed: Some(InitialSpeedChangeGroup { value: 1.23 }),
22782292
..Default::default()
2279-
});
2280-
let request = app
2281-
.put(format!("/train_schedules/{paced_train_id}").as_str())
2282-
.json(&json!(paced_train_response.train_schedule));
2283-
app.fetch(request)
2284-
.await
2285-
.assert_status(StatusCode::NO_CONTENT);
2293+
}),
2294+
)
2295+
.await;
2296+
22862297
let request = app
22872298
.post("/train_schedules/simulation_summary")
22882299
.json(&json!({
2289-
"infra_id": infra_id,
2290-
"ids": vec![paced_train_id],
2300+
"infra_id": small_infra.id,
2301+
"timetable_id": timetable.id,
2302+
"ids": vec![train_schedule.id],
22912303
}));
22922304

22932305
let response: HashMap<i64, TrainScheduleSummaryResponse> = app
@@ -2297,7 +2309,7 @@ mod tests {
22972309
.json_into();
22982310
assert_eq!(response.len(), 1);
22992311
assert_eq!(
2300-
*response.get(&paced_train_id).unwrap(),
2312+
*response.get(&train_schedule.id).unwrap(),
23012313
TrainScheduleSummaryResponse {
23022314
train_schedule: SummaryResponse::Success {
23032315
length: 15_050_000,
@@ -2334,10 +2346,12 @@ mod tests {
23342346
async fn paced_train_simulation_summary_not_found() {
23352347
let (app, infra_id, _paced_train_id, _exception) =
23362348
app_infra_id_paced_train_id_for_simulation_tests().await;
2349+
let timetable = create_timetable(&mut app.db_pool().get_ok()).await;
23372350
let request = app
23382351
.post("/train_schedules/simulation_summary")
23392352
.json(&json!({
23402353
"infra_id": infra_id,
2354+
"timetable_id": timetable.id,
23412355
"ids": vec![0],
23422356
}));
23432357
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
}

editoast/src/views/timetable/tests/train_schedule_too_fast.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": "id935",
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": "id916",
2729
"location": {
30+
"type": "track_offset",
2831
"track": "TH1",
2932
"offset": 4095000
3033
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
{
99
"id": "idA",
1010
"location": {
11+
"type": "operational_point_part_reference",
1112
"operational_point": {
1213
"uic": 87700000,
1314
"secondary_code": "BV",

0 commit comments

Comments
 (0)