diff --git a/editoast/editoast_models/src/tables.rs b/editoast/editoast_models/src/tables.rs index 257666b1310..5d0001c3de7 100644 --- a/editoast/editoast_models/src/tables.rs +++ b/editoast/editoast_models/src/tables.rs @@ -700,6 +700,7 @@ diesel::table! { version -> Int8, #[max_length = 255] label -> Varchar, + max_speed -> Nullable, } } diff --git a/editoast/editoast_schemas/src/rolling_stock/towed_rolling_stock.rs b/editoast/editoast_schemas/src/rolling_stock/towed_rolling_stock.rs index 4f549d59a15..86be48d8250 100644 --- a/editoast/editoast_schemas/src/rolling_stock/towed_rolling_stock.rs +++ b/editoast/editoast_schemas/src/rolling_stock/towed_rolling_stock.rs @@ -18,4 +18,5 @@ pub struct TowedRollingStock { /// The constant gamma braking coefficient used when NOT circulating /// under ETCS/ERTMS signaling system in m/s^2 pub const_gamma: f64, + pub max_speed: Option, } diff --git a/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/down.sql b/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/down.sql new file mode 100644 index 00000000000..24e40014e57 --- /dev/null +++ b/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE towed_rolling_stock +DROP max_speed; diff --git a/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/up.sql b/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/up.sql new file mode 100644 index 00000000000..d83743c350b --- /dev/null +++ b/editoast/migrations/2024-12-03-144258_towed-rolling-stock-add-field-max-speed/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE towed_rolling_stock +ADD max_speed FLOAT8; diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index 29362fc945f..b5609298146 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -10654,6 +10654,10 @@ components: mass: type: number format: double + max_speed: + type: number + format: double + nullable: true name: type: string railjson_version: @@ -10707,6 +10711,10 @@ components: mass: type: number format: double + max_speed: + type: number + format: double + nullable: true name: type: string rolling_resistance: diff --git a/editoast/src/core/simulation.rs b/editoast/src/core/simulation.rs index 2adc8789121..b8db54331af 100644 --- a/editoast/src/core/simulation.rs +++ b/editoast/src/core/simulation.rs @@ -114,10 +114,17 @@ impl PhysicsConsistParameters { } pub fn compute_max_speed(&self) -> f64 { - self.max_speed - .map(|max_speed_parameter| { - f64::min(self.traction_engine.max_speed, max_speed_parameter) - }) + let max_speeds = [ + self.max_speed, + self.towed_rolling_stock + .as_ref() + .and_then(|towed| towed.max_speed), + Some(self.traction_engine.max_speed), + ]; + max_speeds + .into_iter() + .flatten() + .reduce(f64::min) .unwrap_or(self.traction_engine.max_speed) } @@ -555,6 +562,7 @@ mod tests { #[test] fn physics_consist_max_speed() { + // Towed max speed 35 let mut physics_consist = create_physics_consist(); physics_consist.max_speed = Some(20.0); // m/s physics_consist.traction_engine.max_speed = 22.0; // m/s @@ -569,6 +577,12 @@ mod tests { physics_consist.max_speed = None; assert_eq!(physics_consist.compute_max_speed(), 24.0); + + physics_consist.traction_engine.max_speed = 40.0; // m/s + assert_eq!(physics_consist.compute_max_speed(), 35.0); + + physics_consist.towed_rolling_stock = None; + assert_eq!(physics_consist.compute_max_speed(), 40.0); } #[test] diff --git a/editoast/src/models/fixtures.rs b/editoast/src/models/fixtures.rs index 5a93f662aa3..6ceff77b3fc 100644 --- a/editoast/src/models/fixtures.rs +++ b/editoast/src/models/fixtures.rs @@ -239,6 +239,7 @@ pub fn create_towed_rolling_stock() -> TowedRollingStock { C: 0.0002, // In N/(m/s)² }, const_gamma: 1.0, + max_speed: Some(35.0), railjson_version: "3.4".to_string(), } } diff --git a/editoast/src/models/towed_rolling_stock.rs b/editoast/src/models/towed_rolling_stock.rs index 12c458efb2d..669b165aacc 100644 --- a/editoast/src/models/towed_rolling_stock.rs +++ b/editoast/src/models/towed_rolling_stock.rs @@ -25,6 +25,8 @@ pub struct TowedRollingStockModel { pub mass: f64, /// In m pub length: f64, + /// In km/h + pub max_speed: Option, pub comfort_acceleration: f64, pub startup_acceleration: f64, pub inertia_coefficient: f64, @@ -48,6 +50,7 @@ impl From for TowedRollingStock { inertia_coefficient: model.inertia_coefficient, rolling_resistance: model.rolling_resistance, const_gamma: model.const_gamma, + max_speed: model.max_speed, } } } diff --git a/editoast/src/views/rolling_stock/towed.rs b/editoast/src/views/rolling_stock/towed.rs index 43ab27ba5b5..fa4a9eea694 100644 --- a/editoast/src/views/rolling_stock/towed.rs +++ b/editoast/src/views/rolling_stock/towed.rs @@ -60,6 +60,7 @@ struct TowedRollingStock { inertia_coefficient: f64, rolling_resistance: RollingResistancePerWeight, const_gamma: f64, + max_speed: Option, } impl From for TowedRollingStock { @@ -77,6 +78,7 @@ impl From for TowedRollingStock { inertia_coefficient: towed_rolling_stock.inertia_coefficient, rolling_resistance: towed_rolling_stock.rolling_resistance, const_gamma: towed_rolling_stock.const_gamma, + max_speed: towed_rolling_stock.max_speed, } } } @@ -105,6 +107,7 @@ pub struct TowedRollingStockForm { pub inertia_coefficient: f64, pub rolling_resistance: RollingResistancePerWeight, pub const_gamma: f64, + pub max_speed: Option, } impl From for Changeset { @@ -121,6 +124,7 @@ impl From for Changeset { .inertia_coefficient(towed_rolling_stock_form.inertia_coefficient) .rolling_resistance(towed_rolling_stock_form.rolling_resistance) .const_gamma(towed_rolling_stock_form.const_gamma) + .max_speed(towed_rolling_stock_form.max_speed) } } diff --git a/editoast/src/views/timetable/stdcm.rs b/editoast/src/views/timetable/stdcm.rs index 1abf7ef1ae3..1946e7dd7c4 100644 --- a/editoast/src/views/timetable/stdcm.rs +++ b/editoast/src/views/timetable/stdcm.rs @@ -26,6 +26,7 @@ use std::sync::Arc; use thiserror::Error; use utoipa::IntoParams; use utoipa::ToSchema; +use validator::Validate; use crate::core::conflict_detection::Conflict; use crate::core::conflict_detection::TrainRequirements; @@ -139,6 +140,8 @@ async fn stdcm( return Err(AuthorizationError::Unauthorized.into()); } + stdcm_request.validate()?; + let conn = &mut db_pool.get().await?; let timetable_id = id; diff --git a/editoast/src/views/timetable/stdcm/request.rs b/editoast/src/views/timetable/stdcm/request.rs index 5c3ef3bfbe2..f3c682533e3 100644 --- a/editoast/src/views/timetable/stdcm/request.rs +++ b/editoast/src/views/timetable/stdcm/request.rs @@ -11,6 +11,7 @@ use itertools::Itertools; use serde::Deserialize; use serde::Serialize; use utoipa::ToSchema; +use validator::Validate; use crate::core::pathfinding::PathfindingInputError; use crate::error::Result; @@ -65,7 +66,7 @@ struct StepTimingData { } /// An STDCM request -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Validate, ToSchema)] pub(super) struct Request { /// Deprecated, first step arrival time should be used instead pub(super) start_time: Option>, @@ -102,10 +103,13 @@ pub(super) struct Request { #[schema(value_type = Option, example = json!(["5%", "2min/100km"]))] pub(super) margin: Option, /// Total mass of the consist in kg + #[validate(range(exclusive_min = 0.0))] pub(super) total_mass: Option, /// Total length of the consist in meters + #[validate(range(exclusive_min = 0.0))] pub(super) total_length: Option, /// Maximum speed of the consist in km/h + #[validate(range(exclusive_min = 0.0))] pub(super) max_speed: Option, pub(super) loading_gauge_type: Option, } diff --git a/front/src/applications/stdcm/hooks/useStdcmConsist.ts b/front/src/applications/stdcm/hooks/useStdcmConsist.ts index 318651c06da..cf738661b30 100644 --- a/front/src/applications/stdcm/hooks/useStdcmConsist.ts +++ b/front/src/applications/stdcm/hooks/useStdcmConsist.ts @@ -1,5 +1,6 @@ import { useState } from 'react'; +import { min } from 'lodash'; import { useSelector } from 'react-redux'; import type { LightRollingStockWithLiveries, TowedRollingStock } from 'common/api/osrdEditoastApi'; @@ -57,9 +58,8 @@ const useStdcmConsist = () => { } if (!maxSpeedChanged) { - dispatch( - updateMaxSpeed(rollingStock?.max_speed ? Math.floor(rollingStock.max_speed) : undefined) - ); + const consistMaxSpeed = min([rollingStock?.max_speed, towed?.max_speed]); + dispatch(updateMaxSpeed(consistMaxSpeed ? Math.floor(consistMaxSpeed) : undefined)); } }; diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index 48b686e896e..81789320b6f 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -3372,6 +3372,7 @@ export type TowedRollingStock = { length: number; locked: boolean; mass: number; + max_speed?: number | null; name: string; railjson_version: string; rolling_resistance: RollingResistancePerWeight; @@ -3385,6 +3386,7 @@ export type TowedRollingStockForm = { length: number; locked: boolean; mass: number; + max_speed?: number | null; name: string; rolling_resistance: RollingResistancePerWeight; startup_acceleration: number;