Skip to content

Commit 2fc0007

Browse files
committed
Adding OperationalPointIdLocation as location for POST /timetable/{id}
1 parent a235b0d commit 2fc0007

File tree

4 files changed

+192
-34
lines changed

4 files changed

+192
-34
lines changed

editoast/openapi.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -3324,13 +3324,18 @@ components:
33243324
- properties:
33253325
OperationalPointNotFound:
33263326
properties:
3327+
missing_ids:
3328+
items:
3329+
type: string
3330+
type: array
33273331
missing_uics:
33283332
items:
33293333
format: int64
33303334
type: integer
33313335
type: array
33323336
required:
33333337
- missing_uics
3338+
- missing_ids
33343339
type: object
33353340
required:
33363341
- OperationalPointNotFound
@@ -3404,6 +3409,17 @@ components:
34043409
- uic
34053410
- type
34063411
type: object
3412+
- properties:
3413+
id:
3414+
type: string
3415+
type:
3416+
enum:
3417+
- operational_point_id
3418+
type: string
3419+
required:
3420+
- id
3421+
- type
3422+
type: object
34073423
TimetableImportPathSchedule:
34083424
properties:
34093425
arrival_time:

editoast/src/models/infra_objects/operational_point.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::error::Result;
44
use crate::{schema::OperationalPoint, tables::infra_object_operational_point};
55
use derivative::Derivative;
6-
use diesel::sql_types::{Array, BigInt};
6+
use diesel::sql_types::{Array, BigInt, Text};
77
use diesel::{result::Error as DieselError, sql_query};
88
use diesel::{ExpressionMethods, QueryDsl};
99
use diesel_async::{AsyncPgConnection as PgConnection, RunQueryDsl};
@@ -38,4 +38,19 @@ impl OperationalPointModel {
3838
.load(conn)
3939
.await?)
4040
}
41+
42+
/// Retrieve a list of operational points from the database
43+
pub async fn retrieve_from_obj_ids(
44+
conn: &mut PgConnection,
45+
infra_id: i64,
46+
ids: &[String],
47+
) -> Result<Vec<Self>> {
48+
let query = "SELECT * FROM infra_object_operational_point
49+
WHERE infra_id = $1 AND infra_object_operational_point.obj_id = ANY($2)".to_string();
50+
Ok(sql_query(query)
51+
.bind::<BigInt, _>(infra_id)
52+
.bind::<Array<Text>, _>(ids)
53+
.load(conn)
54+
.await?)
55+
}
4156
}

editoast/src/views/timetable/import.rs

+155-33
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ pub enum TimetableImportPathLocation {
6767
TrackOffsetLocation { track_section: String, offset: f64 },
6868
#[serde(rename = "operational_point")]
6969
OperationalPointLocation { uic: i64 },
70+
#[serde(rename = "operational_point_id")]
71+
OperationalPointIdLocation { id: String },
7072
}
7173

7274
#[derive(Debug, Serialize, ToSchema)]
@@ -76,10 +78,19 @@ struct TimetableImportReport {
7678

7779
#[derive(Debug, Clone, Serialize, ToSchema)]
7880
enum TimetableImportError {
79-
RollingStockNotFound { name: String },
80-
OperationalPointNotFound { missing_uics: Vec<i64> },
81-
PathfindingError { cause: InternalError },
82-
SimulationError { cause: InternalError },
81+
RollingStockNotFound {
82+
name: String,
83+
},
84+
OperationalPointNotFound {
85+
missing_uics: Vec<i64>,
86+
missing_ids: Vec<String>,
87+
},
88+
PathfindingError {
89+
cause: InternalError,
90+
},
91+
SimulationError {
92+
cause: InternalError,
93+
},
8394
}
8495

8596
#[derive(Debug, Clone, Deserialize, ToSchema)]
@@ -183,19 +194,21 @@ async fn import_item(
183194
pf_request.with_rolling_stocks(&mut vec![rolling_stock.clone()]);
184195
// List operational points uic needed for this import
185196
let ops_uic = ops_uic_from_path(&import_item.path);
197+
let ops_id = ops_id_from_path(&import_item.path);
186198
// Retrieve operational points
187-
let op_id_to_parts = match find_operation_points(&ops_uic, infra_id, conn).await? {
188-
Ok(op_id_to_parts) => op_id_to_parts,
189-
Err(err) => {
190-
return Ok(import_item
191-
.trains
192-
.into_iter()
193-
.map(|train: TimetableImportTrain| (train.name, err.clone()))
194-
.collect());
195-
}
196-
};
199+
let (op_uic_to_parts, op_id_to_parts) =
200+
match find_operation_points(&ops_uic, &ops_id, infra_id, conn).await? {
201+
Ok((op_uic_to_parts, op_id_to_parts)) => (op_uic_to_parts, op_id_to_parts),
202+
Err(err) => {
203+
return Ok(import_item
204+
.trains
205+
.into_iter()
206+
.map(|train: TimetableImportTrain| (train.name, err.clone()))
207+
.collect());
208+
}
209+
};
197210
// Create waypoints
198-
let mut waypoints = waypoints_from_steps(&import_item.path, &op_id_to_parts);
211+
let mut waypoints = waypoints_from_steps(&import_item.path, &op_uic_to_parts, &op_id_to_parts);
199212
pf_request.with_waypoints(&mut waypoints);
200213

201214
// Run pathfinding
@@ -295,30 +308,63 @@ async fn import_item(
295308

296309
async fn find_operation_points(
297310
ops_uic: &[i64],
311+
ops_id: &[String],
298312
infra_id: i64,
299313
conn: &mut AsyncPgConnection,
300-
) -> Result<std::result::Result<HashMap<i64, Vec<OperationalPointPart>>, TimetableImportError>> {
314+
) -> Result<
315+
std::result::Result<
316+
(
317+
HashMap<i64, Vec<OperationalPointPart>>,
318+
HashMap<String, Vec<OperationalPointPart>>,
319+
),
320+
TimetableImportError,
321+
>,
322+
> {
301323
// Retrieve operational points
302-
let ops = OperationalPointModel::retrieve_from_uic(conn, infra_id, ops_uic).await?;
324+
let ops_from_uic: Vec<OperationalPointModel> =
325+
OperationalPointModel::retrieve_from_uic(conn, infra_id, ops_uic).await?;
326+
let mut op_uic_to_parts = HashMap::<_, Vec<_>>::new();
327+
for op in ops_from_uic {
328+
op_uic_to_parts
329+
.entry(op.data.0.extensions.identifier.unwrap().uic)
330+
.or_default()
331+
.extend(op.data.0.parts);
332+
}
333+
let mut missing_uics: Vec<i64> = vec![];
334+
// If we didn't find all the operational points, we can't run the pathfinding
335+
if op_uic_to_parts.len() != ops_uic.len() {
336+
ops_uic.iter().for_each(|uic| {
337+
if !op_uic_to_parts.contains_key(uic) {
338+
missing_uics.push(*uic)
339+
}
340+
});
341+
}
342+
343+
let ops_from_ids = OperationalPointModel::retrieve_from_obj_ids(conn, infra_id, ops_id).await?;
303344
let mut op_id_to_parts = HashMap::<_, Vec<_>>::new();
304-
for op in ops {
345+
for op in ops_from_ids {
305346
op_id_to_parts
306-
.entry(op.data.0.extensions.identifier.unwrap().uic)
347+
.entry(op.obj_id)
307348
.or_default()
308349
.extend(op.data.0.parts);
309350
}
310351
// If we didn't find all the operational points, we can't run the pathfinding
311-
if op_id_to_parts.len() != ops_uic.len() {
312-
let missing_uics = ops_uic
313-
.iter()
314-
.filter(|uic| !op_id_to_parts.contains_key(uic))
315-
.cloned()
316-
.collect();
352+
let mut missing_ids: Vec<String> = vec![];
353+
if op_id_to_parts.len() != ops_id.len() {
354+
ops_id.iter().for_each(|id| {
355+
if !op_id_to_parts.contains_key(id) {
356+
missing_ids.push(id.to_string())
357+
}
358+
});
359+
}
360+
if missing_uics.len() + missing_ids.len() > 0 {
317361
return Ok(Err(TimetableImportError::OperationalPointNotFound {
318362
missing_uics,
363+
missing_ids,
319364
}));
320365
}
321-
Ok(Ok(op_id_to_parts))
366+
367+
Ok(Ok((op_uic_to_parts, op_id_to_parts)))
322368
}
323369

324370
fn ops_uic_from_path(path: &[TimetableImportPathStep]) -> Vec<i64> {
@@ -335,9 +381,24 @@ fn ops_uic_from_path(path: &[TimetableImportPathStep]) -> Vec<i64> {
335381
ops_uic
336382
}
337383

384+
fn ops_id_from_path(path: &[TimetableImportPathStep]) -> Vec<String> {
385+
let mut ops_id = path
386+
.iter()
387+
.filter_map(|step| match &step.location {
388+
TimetableImportPathLocation::OperationalPointIdLocation { id } => Some(id.to_string()),
389+
_ => None,
390+
})
391+
.collect::<Vec<_>>();
392+
// Remove duplicates
393+
ops_id.sort();
394+
ops_id.dedup();
395+
ops_id
396+
}
397+
338398
fn waypoints_from_steps(
339399
path: &Vec<TimetableImportPathStep>,
340-
op_id_to_parts: &HashMap<i64, Vec<OperationalPointPart>>,
400+
op_uic_to_parts: &HashMap<i64, Vec<OperationalPointPart>>,
401+
op_id_to_parts: &HashMap<String, Vec<OperationalPointPart>>,
341402
) -> Vec<Vec<Waypoint>> {
342403
let mut res = PathfindingWaypoints::new();
343404
for step in path {
@@ -346,12 +407,18 @@ fn waypoints_from_steps(
346407
track_section,
347408
offset,
348409
} => Vec::from(Waypoint::bidirectional(track_section, *offset)),
349-
TimetableImportPathLocation::OperationalPointLocation { uic } => op_id_to_parts
410+
TimetableImportPathLocation::OperationalPointLocation { uic } => op_uic_to_parts
350411
.get(uic)
351412
.unwrap()
352413
.iter()
353414
.flat_map(|op_part| Waypoint::bidirectional(&op_part.track, op_part.position))
354415
.collect(),
416+
TimetableImportPathLocation::OperationalPointIdLocation { id } => op_id_to_parts
417+
.get(id)
418+
.unwrap()
419+
.iter()
420+
.flat_map(|op_part| Waypoint::bidirectional(&op_part.track, op_part.position))
421+
.collect(),
355422
});
356423
}
357424
res
@@ -438,8 +505,8 @@ mod tests {
438505

439506
#[test]
440507
fn test_waypoints_from_steps() {
441-
let mut op_id_to_parts = HashMap::new();
442-
op_id_to_parts.insert(
508+
let mut op_uic_to_parts = HashMap::new();
509+
op_uic_to_parts.insert(
443510
1,
444511
vec![
445512
OperationalPointPart {
@@ -455,6 +522,23 @@ mod tests {
455522
],
456523
);
457524

525+
let mut op_id_to_parts = HashMap::new();
526+
op_id_to_parts.insert(
527+
"a1".to_string(),
528+
vec![
529+
OperationalPointPart {
530+
track: Identifier("E".to_string()),
531+
position: 0.,
532+
..Default::default()
533+
},
534+
OperationalPointPart {
535+
track: Identifier("F".to_string()),
536+
position: 100.,
537+
..Default::default()
538+
},
539+
],
540+
);
541+
458542
let path = vec![
459543
TimetableImportPathStep {
460544
location: TimetableImportPathLocation::TrackOffsetLocation {
@@ -467,11 +551,17 @@ mod tests {
467551
location: TimetableImportPathLocation::OperationalPointLocation { uic: 1 },
468552
schedule: HashMap::new(),
469553
},
554+
TimetableImportPathStep {
555+
location: TimetableImportPathLocation::OperationalPointIdLocation {
556+
id: "a1".to_string(),
557+
},
558+
schedule: HashMap::new(),
559+
},
470560
];
471561

472-
let waypoints = waypoints_from_steps(&path, &op_id_to_parts);
562+
let waypoints = waypoints_from_steps(&path, &op_uic_to_parts, &op_id_to_parts);
473563

474-
assert_eq!(waypoints.len(), 2);
564+
assert_eq!(waypoints.len(), 3);
475565
assert_eq!(waypoints[0], Waypoint::bidirectional("C", 50.));
476566
assert_eq!(
477567
waypoints[1],
@@ -481,10 +571,18 @@ mod tests {
481571
]
482572
.concat()
483573
);
574+
assert_eq!(
575+
waypoints[2],
576+
[
577+
Waypoint::bidirectional("E", 0.),
578+
Waypoint::bidirectional("F", 100.),
579+
]
580+
.concat()
581+
);
484582
}
485583

486584
#[test]
487-
fn test_ops_uic_from_path() {
585+
fn test_ops_uic_id_from_path() {
488586
let path = vec![
489587
TimetableImportPathStep {
490588
location: TimetableImportPathLocation::TrackOffsetLocation {
@@ -497,6 +595,12 @@ mod tests {
497595
location: TimetableImportPathLocation::OperationalPointLocation { uic: 1 },
498596
schedule: HashMap::new(),
499597
},
598+
TimetableImportPathStep {
599+
location: TimetableImportPathLocation::OperationalPointIdLocation {
600+
id: "a1".to_string(),
601+
},
602+
schedule: HashMap::new(),
603+
},
500604
TimetableImportPathStep {
501605
location: TimetableImportPathLocation::OperationalPointLocation { uic: 2 },
502606
schedule: HashMap::new(),
@@ -508,6 +612,18 @@ mod tests {
508612
},
509613
schedule: HashMap::new(),
510614
},
615+
TimetableImportPathStep {
616+
location: TimetableImportPathLocation::OperationalPointIdLocation {
617+
id: "a2".to_string(),
618+
},
619+
schedule: HashMap::new(),
620+
},
621+
TimetableImportPathStep {
622+
location: TimetableImportPathLocation::OperationalPointIdLocation {
623+
id: "a1".to_string(),
624+
},
625+
schedule: HashMap::new(),
626+
},
511627
TimetableImportPathStep {
512628
location: TimetableImportPathLocation::OperationalPointLocation { uic: 1 },
513629
schedule: HashMap::new(),
@@ -519,5 +635,11 @@ mod tests {
519635
assert_eq!(ops_uic.len(), 2);
520636
assert_eq!(ops_uic[0], 1);
521637
assert_eq!(ops_uic[1], 2);
638+
639+
let ops_id = ops_id_from_path(&path);
640+
641+
assert_eq!(ops_id.len(), 2);
642+
assert_eq!(ops_id[0], "a1");
643+
assert_eq!(ops_id[1], "a2");
522644
}
523645
}

front/src/common/api/osrdEditoastApi.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2305,6 +2305,7 @@ export type TimetableImportError =
23052305
}
23062306
| {
23072307
OperationalPointNotFound: {
2308+
missing_ids: string[];
23082309
missing_uics: number[];
23092310
};
23102311
}
@@ -2332,6 +2333,10 @@ export type TimetableImportPathLocation =
23322333
| {
23332334
type: 'operational_point';
23342335
uic: number;
2336+
}
2337+
| {
2338+
id: string;
2339+
type: 'operational_point_id';
23352340
};
23362341
export type TimetableImportPathSchedule = {
23372342
arrival_time: string;

0 commit comments

Comments
 (0)