Skip to content

Commit 90fd83e

Browse files
committed
editoast: refactor path not found handling
Signed-off-by: hamz2a <[email protected]>
1 parent 3ef7876 commit 90fd83e

File tree

3 files changed

+160
-129
lines changed

3 files changed

+160
-129
lines changed

editoast/src/views/timetable.rs

+31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
pub mod path_not_found_handler;
12
pub mod stdcm;
23
pub mod stdcm_request_payload;
34

5+
use std::cmp::max;
46
use std::collections::HashMap;
57

68
use axum::extract::Json;
@@ -10,6 +12,10 @@ use axum::extract::State;
1012
use axum::http::StatusCode;
1113
use axum::response::IntoResponse;
1214
use axum::Extension;
15+
use chrono::DateTime;
16+
use chrono::NaiveDateTime;
17+
use chrono::TimeZone;
18+
use chrono::Utc;
1319
use derivative::Derivative;
1420
use editoast_authz::BuiltinRole;
1521
use editoast_derive::EditoastError;
@@ -25,13 +31,15 @@ use crate::core::conflict_detection::Conflict;
2531
use crate::core::conflict_detection::ConflictDetectionRequest;
2632
use crate::core::conflict_detection::TrainRequirements;
2733
use crate::core::simulation::SimulationResponse;
34+
use crate::core::stdcm::UndirectedTrackRange;
2835
use crate::core::AsCoreRequest;
2936
use crate::error::Result;
3037
use crate::models::prelude::*;
3138
use crate::models::timetable::Timetable;
3239
use crate::models::timetable::TimetableWithTrains;
3340
use crate::models::train_schedule::TrainSchedule;
3441
use crate::models::train_schedule::TrainScheduleChangeset;
42+
use crate::models::work_schedules::WorkSchedule;
3543
use crate::models::Infra;
3644
use crate::views::train_schedule::train_simulation_batch;
3745
use crate::views::train_schedule::TrainScheduleForm;
@@ -351,6 +359,29 @@ async fn conflicts(
351359
Ok(Json(conflict_detection_response.conflicts))
352360
}
353361

362+
pub fn filter_core_work_schedule(
363+
ws: &WorkSchedule,
364+
start_time: DateTime<Utc>,
365+
) -> crate::core::stdcm::WorkSchedule {
366+
crate::core::stdcm::WorkSchedule {
367+
start_time: elapsed_since_time_ms(&ws.start_date_time, &start_time),
368+
end_time: elapsed_since_time_ms(&ws.end_date_time, &start_time),
369+
track_ranges: ws
370+
.track_ranges
371+
.iter()
372+
.map(|track| UndirectedTrackRange {
373+
track_section: track.track.to_string(),
374+
begin: (track.begin * 1000.0) as u64,
375+
end: (track.end * 1000.0) as u64,
376+
})
377+
.collect(),
378+
}
379+
}
380+
381+
fn elapsed_since_time_ms(time: &NaiveDateTime, zero: &DateTime<Utc>) -> u64 {
382+
max(0, (Utc.from_utc_datetime(time) - zero).num_milliseconds()) as u64
383+
}
384+
354385
#[cfg(test)]
355386
mod tests {
356387
use axum::http::StatusCode;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use std::sync::Arc;
2+
3+
use chrono::DateTime;
4+
use chrono::Utc;
5+
6+
use crate::core::conflict_detection::ConflictDetectionRequest;
7+
use crate::core::conflict_detection::WorkSchedulesRequest;
8+
use crate::core::pathfinding::PathfindingResult;
9+
use crate::core::simulation::SimulationResponse;
10+
use crate::core::stdcm::STDCMResponse;
11+
use crate::core::AsCoreRequest;
12+
use crate::core::CoreClient;
13+
use crate::error::Result;
14+
use crate::models::train_schedule::TrainSchedule;
15+
use crate::models::work_schedules::WorkSchedule;
16+
17+
use super::filter_core_work_schedule;
18+
use super::stdcm::build_train_requirements;
19+
20+
pub struct PathNotFoundHandler {
21+
pub core_client: Arc<CoreClient>,
22+
pub infra_id: i64,
23+
pub infra_version: String,
24+
pub train_schedules: Vec<TrainSchedule>,
25+
pub simulations: Vec<(SimulationResponse, PathfindingResult)>,
26+
pub work_schedules: Vec<WorkSchedule>,
27+
pub virtual_train_schedule: TrainSchedule,
28+
pub virtual_train_sim_result: SimulationResponse,
29+
pub virtual_train_pathfinding_result: PathfindingResult,
30+
pub earliest_departure_time: DateTime<Utc>,
31+
pub maximum_run_time: u64,
32+
pub latest_simulation_end: DateTime<Utc>,
33+
}
34+
35+
impl PathNotFoundHandler {
36+
pub async fn handle(self) -> Result<STDCMResponse> {
37+
let virtual_train_id = self.virtual_train_schedule.id;
38+
39+
// Combine the original train schedules with the virtual train schedule.
40+
let train_schedules = [self.train_schedules, vec![self.virtual_train_schedule]].concat();
41+
42+
// Combine the original simulations with the virtual train's simulation results.
43+
let simulations = [
44+
self.simulations,
45+
vec![(
46+
self.virtual_train_sim_result,
47+
self.virtual_train_pathfinding_result.clone(),
48+
)],
49+
]
50+
.concat();
51+
52+
// Build train requirements based on the combined train schedules and simulations
53+
// This prepares the data structure required for conflict detection.
54+
let trains_requirements = build_train_requirements(
55+
train_schedules,
56+
simulations,
57+
self.earliest_departure_time,
58+
self.latest_simulation_end,
59+
);
60+
61+
// Filter the provided work schedules to find those that conflict with the given parameters
62+
// This identifies any work schedules that may overlap with the earliest departure time and maximum run time.
63+
let conflict_work_schedules = filter_conflict_work_schedules(
64+
&self.work_schedules,
65+
self.earliest_departure_time,
66+
self.maximum_run_time,
67+
);
68+
69+
// Prepare the conflict detection request.
70+
let conflict_detection_request = ConflictDetectionRequest {
71+
infra: self.infra_id,
72+
expected_version: self.infra_version,
73+
trains_requirements,
74+
work_schedules: conflict_work_schedules,
75+
};
76+
77+
// Send the conflict detection request and await the response.
78+
let conflict_detection_response =
79+
conflict_detection_request.fetch(&self.core_client).await?;
80+
81+
// Filter the conflicts to find those specifically related to the virtual train.
82+
let conflicts: Vec<_> = conflict_detection_response
83+
.conflicts
84+
.into_iter()
85+
.filter(|conflict| conflict.train_ids.contains(&virtual_train_id))
86+
.map(|mut conflict| {
87+
conflict.train_ids.retain(|id| id != &virtual_train_id);
88+
conflict
89+
})
90+
.collect();
91+
92+
// Return the conflicts found along with the pathfinding result for the virtual train.
93+
Ok(STDCMResponse::Conflicts {
94+
pathfinding_result: self.virtual_train_pathfinding_result,
95+
conflicts,
96+
})
97+
}
98+
}
99+
100+
fn filter_conflict_work_schedules(
101+
work_schedules: &[WorkSchedule],
102+
start_time: DateTime<Utc>,
103+
maximum_run_time: u64,
104+
) -> Option<WorkSchedulesRequest> {
105+
if work_schedules.is_empty() {
106+
return None;
107+
}
108+
109+
let work_schedule_requirements = work_schedules
110+
.iter()
111+
.map(|ws| (ws.id, filter_core_work_schedule(ws, start_time)))
112+
.filter(|(_, ws)| ws.end_time > 0 && ws.start_time < maximum_run_time)
113+
.collect();
114+
115+
Some(WorkSchedulesRequest {
116+
start_time,
117+
work_schedule_requirements,
118+
})
119+
}

0 commit comments

Comments
 (0)