Skip to content

Commit

Permalink
editoast: refactor path not found handling
Browse files Browse the repository at this point in the history
Signed-off-by: hamz2a <atrari.hamza@gmail.com>
  • Loading branch information
hamz2a committed Oct 23, 2024
1 parent 3ef7876 commit 3fcc0f0
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 131 deletions.
4 changes: 2 additions & 2 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2479,9 +2479,9 @@ paths:
The result contains the simulation output based on the train schedules
and infrastructure provided.
If the simulation fails, the function uses a fake train to detect conflicts
If the simulation fails, the function uses a virtual train to detect conflicts
with existing train schedules. It then returns both the conflict information
and the pathfinding result from the fake train's simulation.
and the pathfinding result from the virtual train's simulation.
parameters:
- name: infra
in: query
Expand Down
31 changes: 31 additions & 0 deletions editoast/src/views/timetable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod path_not_found_handler;
pub mod stdcm;
pub mod stdcm_request_payload;

use std::cmp::max;
use std::collections::HashMap;

use axum::extract::Json;
Expand All @@ -10,6 +12,10 @@ use axum::extract::State;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Extension;
use chrono::DateTime;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono::Utc;
use derivative::Derivative;
use editoast_authz::BuiltinRole;
use editoast_derive::EditoastError;
Expand All @@ -25,13 +31,15 @@ use crate::core::conflict_detection::Conflict;
use crate::core::conflict_detection::ConflictDetectionRequest;
use crate::core::conflict_detection::TrainRequirements;
use crate::core::simulation::SimulationResponse;
use crate::core::stdcm::UndirectedTrackRange;
use crate::core::AsCoreRequest;
use crate::error::Result;
use crate::models::prelude::*;
use crate::models::timetable::Timetable;
use crate::models::timetable::TimetableWithTrains;
use crate::models::train_schedule::TrainSchedule;
use crate::models::train_schedule::TrainScheduleChangeset;
use crate::models::work_schedules::WorkSchedule;
use crate::models::Infra;
use crate::views::train_schedule::train_simulation_batch;
use crate::views::train_schedule::TrainScheduleForm;
Expand Down Expand Up @@ -351,6 +359,29 @@ async fn conflicts(
Ok(Json(conflict_detection_response.conflicts))
}

pub fn filter_core_work_schedule(
ws: &WorkSchedule,
start_time: DateTime<Utc>,
) -> crate::core::stdcm::WorkSchedule {
crate::core::stdcm::WorkSchedule {
start_time: elapsed_since_time_ms(&ws.start_date_time, &start_time),
end_time: elapsed_since_time_ms(&ws.end_date_time, &start_time),
track_ranges: ws
.track_ranges
.iter()
.map(|track| UndirectedTrackRange {
track_section: track.track.to_string(),
begin: (track.begin * 1000.0) as u64,
end: (track.end * 1000.0) as u64,
})
.collect(),
}
}

fn elapsed_since_time_ms(time: &NaiveDateTime, zero: &DateTime<Utc>) -> u64 {
max(0, (Utc.from_utc_datetime(time) - zero).num_milliseconds()) as u64
}

#[cfg(test)]
mod tests {
use axum::http::StatusCode;
Expand Down
119 changes: 119 additions & 0 deletions editoast/src/views/timetable/path_not_found_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::sync::Arc;

use chrono::DateTime;
use chrono::Utc;

use crate::core::conflict_detection::ConflictDetectionRequest;
use crate::core::conflict_detection::WorkSchedulesRequest;
use crate::core::pathfinding::PathfindingResult;
use crate::core::simulation::SimulationResponse;
use crate::core::stdcm::STDCMResponse;
use crate::core::AsCoreRequest;
use crate::core::CoreClient;
use crate::error::Result;
use crate::models::train_schedule::TrainSchedule;
use crate::models::work_schedules::WorkSchedule;

use super::filter_core_work_schedule;
use super::stdcm::build_train_requirements;

pub struct PathNotFoundHandler {
pub core_client: Arc<CoreClient>,
pub infra_id: i64,
pub infra_version: String,
pub train_schedules: Vec<TrainSchedule>,
pub simulations: Vec<(SimulationResponse, PathfindingResult)>,
pub work_schedules: Vec<WorkSchedule>,
pub virtual_train_schedule: TrainSchedule,
pub virtual_train_sim_result: SimulationResponse,
pub virtual_train_pathfinding_result: PathfindingResult,
pub earliest_departure_time: DateTime<Utc>,
pub maximum_run_time: u64,
pub latest_simulation_end: DateTime<Utc>,
}

impl PathNotFoundHandler {
pub async fn handle(self) -> Result<STDCMResponse> {
let virtual_train_id = self.virtual_train_schedule.id;

// Combine the original train schedules with the virtual train schedule.
let train_schedules = [self.train_schedules, vec![self.virtual_train_schedule]].concat();

// Combine the original simulations with the virtual train's simulation results.
let simulations = [
self.simulations,
vec![(
self.virtual_train_sim_result,
self.virtual_train_pathfinding_result.clone(),
)],
]
.concat();

// Build train requirements based on the combined train schedules and simulations
// This prepares the data structure required for conflict detection.
let trains_requirements = build_train_requirements(
train_schedules,
simulations,
self.earliest_departure_time,
self.latest_simulation_end,
);

// Filter the provided work schedules to find those that conflict with the given parameters
// This identifies any work schedules that may overlap with the earliest departure time and maximum run time.
let conflict_work_schedules = filter_conflict_work_schedules(
&self.work_schedules,
self.earliest_departure_time,
self.maximum_run_time,
);

// Prepare the conflict detection request.
let conflict_detection_request = ConflictDetectionRequest {
infra: self.infra_id,
expected_version: self.infra_version,
trains_requirements,
work_schedules: conflict_work_schedules,
};

// Send the conflict detection request and await the response.
let conflict_detection_response =
conflict_detection_request.fetch(&self.core_client).await?;

// Filter the conflicts to find those specifically related to the virtual train.
let conflicts: Vec<_> = conflict_detection_response
.conflicts
.into_iter()
.filter(|conflict| conflict.train_ids.contains(&virtual_train_id))
.map(|mut conflict| {
conflict.train_ids.retain(|id| id != &virtual_train_id);
conflict
})
.collect();

// Return the conflicts found along with the pathfinding result for the virtual train.
Ok(STDCMResponse::Conflicts {
pathfinding_result: self.virtual_train_pathfinding_result,
conflicts,
})
}
}

fn filter_conflict_work_schedules(
work_schedules: &[WorkSchedule],
start_time: DateTime<Utc>,
maximum_run_time: u64,
) -> Option<WorkSchedulesRequest> {
if work_schedules.is_empty() {
return None;
}

let work_schedule_requirements = work_schedules
.iter()
.map(|ws| (ws.id, filter_core_work_schedule(ws, start_time)))
.filter(|(_, ws)| ws.end_time > 0 && ws.start_time < maximum_run_time)
.collect();

Some(WorkSchedulesRequest {
start_time,
work_schedule_requirements,
})
}
Loading

0 comments on commit 3fcc0f0

Please sign in to comment.