Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add infra editor autofix for operational points parts #7571

Merged
merged 1 commit into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions editoast/src/views/infra/auto_fixes/electrifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ fn invalid_reference_to_ordered_operation(
electrification: &Electrification,
object_ref: &ObjectRef,
) -> Option<OrderedOperation> {
let (track_range_idx, _) = electrification
let (track_refs, _) = electrification
.track_ranges
.iter()
.enumerate()
.find(|(_idx, track_range)| track_range.track.as_str() == object_ref.obj_id)?;
Some(OrderedOperation::RemoveTrackRange { track_range_idx })
Some(OrderedOperation::RemoveTrackRef { track_refs })
}

pub fn fix_electrification(
Expand All @@ -47,18 +47,17 @@ pub fn fix_electrification(
None
}
})
.unique()
// Need to invert the ordering because removing from the front would invalidate other indexes
.sorted_by_key(|ordered_operation| std::cmp::Reverse(ordered_operation.clone()))
.map(|ordered_operation| match ordered_operation {
OrderedOperation::RemoveTrackRange { track_range_idx } => {
Operation::Update(UpdateOperation {
obj_id: electrification.get_id().clone(),
obj_type: electrification.get_type(),
railjson_patch: Patch(vec![PatchOperation::Remove(RemoveOperation {
path: format!("/track_ranges/{track_range_idx}").parse().unwrap(),
})]),
})
}
OrderedOperation::RemoveTrackRef { track_refs } => Operation::Update(UpdateOperation {
obj_id: electrification.get_id().clone(),
obj_type: electrification.get_type(),
railjson_patch: Patch(vec![PatchOperation::Remove(RemoveOperation {
path: format!("/track_ranges/{track_refs}").parse().unwrap(),
})]),
}),
OrderedOperation::Delete => {
Operation::Delete(DeleteOperation::from(electrification.get_ref()))
}
Expand Down
4 changes: 2 additions & 2 deletions editoast/src/views/infra/auto_fixes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ fn new_ref_fix_create_pair(object: RailjsonObject) -> (ObjectRef, Fix) {
(object_ref, (operation, cache_operation))
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OrderedOperation {
RemoveTrackRange { track_range_idx: usize },
RemoveTrackRef { track_refs: usize },
Delete,
}

Expand Down
188 changes: 184 additions & 4 deletions editoast/src/views/infra/auto_fixes/operational_point.rs
woshilapin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,26 +1,206 @@
use editoast_schemas::primitives::OSRDIdentified;
use editoast_schemas::primitives::OSRDObject as _;
use editoast_schemas::primitives::ObjectRef;
use editoast_schemas::primitives::ObjectType;
use itertools::Itertools;
use json_patch::Patch;
use json_patch::PatchOperation;
use json_patch::RemoveOperation;
use std::collections::HashMap;

use tracing::debug;

use super::new_ref_fix_delete_pair;
use super::Fix;
use super::OrderedOperation;
use crate::generated_data::infra_error::InfraError;
use crate::generated_data::infra_error::InfraErrorType;
use crate::infra_cache::object_cache::OperationalPointCache;
use crate::infra_cache::operation::CacheOperation;
use crate::infra_cache::operation::DeleteOperation;
use crate::infra_cache::operation::Operation;
use crate::infra_cache::operation::UpdateOperation;
use crate::infra_cache::ObjectCache;

fn invalid_reference_to_ordered_operation(
operational_point: &OperationalPointCache,
object_ref: &ObjectRef,
) -> Option<OrderedOperation> {
let (track_refs, _) = operational_point
.parts
.iter()
.enumerate()
.find(|(_idx, part)| part.track.as_str() == object_ref.obj_id)?;
bougue-pe marked this conversation as resolved.
Show resolved Hide resolved
Some(OrderedOperation::RemoveTrackRef { track_refs })
}

pub fn fix_operational_point(
operational_point: &OperationalPointCache,
errors: impl Iterator<Item = InfraError>,
) -> HashMap<ObjectRef, Fix> {
errors
let mut new_op = operational_point.clone();
let operation = errors
.filter_map(|infra_error| match infra_error.get_sub_type() {
flomonster marked this conversation as resolved.
Show resolved Hide resolved
InfraErrorType::EmptyObject => Some(new_ref_fix_delete_pair(operational_point)),
InfraErrorType::EmptyObject => Some(OrderedOperation::Delete),
InfraErrorType::InvalidReference { reference }
if reference.obj_type == ObjectType::TrackSection =>
{
new_op
.parts
.retain(|part| part.track.as_str() != reference.obj_id);
flomonster marked this conversation as resolved.
Show resolved Hide resolved
invalid_reference_to_ordered_operation(operational_point, reference)
}
_ => {
debug!("error not (yet) fixable for '{}'", infra_error.get_type());
None
}
})
.unique()
// Need to invert the ordering because removing from the front would invalidate other indexes
.sorted_by_key(|ordered_operation| std::cmp::Reverse(ordered_operation.clone()))
.map(|ordered_operation| match ordered_operation {
OrderedOperation::RemoveTrackRef { track_refs } => Operation::Update(UpdateOperation {
obj_id: operational_point.get_id().clone(),
obj_type: operational_point.get_type(),
railjson_patch: Patch(vec![PatchOperation::Remove(RemoveOperation {
path: format!("/parts/{track_refs}").parse().unwrap(),
})]),
}),
OrderedOperation::Delete => {
Operation::Delete(DeleteOperation::from(operational_point.get_ref()))
}
})
.map(Some)
.reduce(super::reduce_operation)
.flatten();
operation
.map(|operation| {
let cache_operation = match operation {
Operation::Update(_) => CacheOperation::Update(ObjectCache::from(new_op)),
Operation::Delete(_) => CacheOperation::Delete(operational_point.get_ref()),
Operation::Create(_) => panic!("We should not create new operational points"),
};
(operational_point.get_ref(), (operation, cache_operation))
})
.into_iter()
.collect()
}

#[cfg(test)]
mod tests {
use json_patch::Patch;

use crate::generated_data::infra_error::InfraError;
use crate::infra_cache::object_cache::OperationalPointCache;
use crate::infra_cache::object_cache::OperationalPointPartCache;
use crate::infra_cache::operation::CacheOperation;
use crate::infra_cache::operation::Operation;
use crate::infra_cache::ObjectCache;
use editoast_schemas::primitives::Identifier;
use editoast_schemas::primitives::OSRDObject as _;
use editoast_schemas::primitives::ObjectRef;
use editoast_schemas::primitives::ObjectType;

#[test]
fn invalid_refs_ordered_operational_point() {
let op_cache = OperationalPointCache {
obj_id: "operational_point_id".into(),
parts: vec![
OperationalPointPartCache {
track: Identifier::from("unknown_track_section_1"),
position: 0.,
},
OperationalPointPartCache {
track: Identifier::from("track_section_id"),
position: 0.,
},
OperationalPointPartCache {
track: Identifier::from("unknown_track_section_2"),
position: 0.,
},
],
};
let error_op_1 = InfraError::new_invalid_reference(
&op_cache,
"parts.0",
ObjectRef::new(ObjectType::TrackSection, "unknown_track_section_1"),
);
let error_op_2 = InfraError::new_invalid_reference(
&op_cache,
"parts.2",
ObjectRef::new(ObjectType::TrackSection, "unknown_track_section_2"),
);

let operations =
super::fix_operational_point(&op_cache, vec![error_op_1, error_op_2].into_iter());

assert_eq!(operations.len(), 1);

let (operation, cache_operation) = operations.get(&op_cache.get_ref()).unwrap();
let Operation::Update(update_operation) = operation else {
panic!("not an `Operation::Update`");
};
assert_eq!(update_operation.obj_id, "operational_point_id");
assert!(matches!(
update_operation.obj_type,
ObjectType::OperationalPoint
));
assert_eq!(
update_operation.railjson_patch,
serde_json::from_str::<Patch>(
r#"[
{"op":"remove","path":"/parts/2"},
{"op":"remove","path":"/parts/0"}
]"#
)
.unwrap()
);
let CacheOperation::Update(ObjectCache::OperationalPoint(op)) = cache_operation else {
panic!("not a `CacheOperation::Update(ObjectCache::OperationalPoint())`");
};
assert_eq!(op.parts.len(), 1);
assert_eq!(op.parts[0].track.0, "track_section_id");
}

#[test]
fn empty_object_and_invalid_ref_operational_points() {
let op_cache = OperationalPointCache {
obj_id: "operational_point_id".into(),
parts: vec![
OperationalPointPartCache {
track: Identifier::from("unknown_track_section_1"),
position: 0.,
},
OperationalPointPartCache {
track: Identifier::from("track_section_id"),
position: 0.,
},
],
};

let error_op_1 = InfraError::new_invalid_reference(
&op_cache,
"parts.0",
ObjectRef::new(ObjectType::TrackSection, "unknown_track_section_1"),
);
let error_op_2 = InfraError::new_empty_object(&op_cache, "parts");

let operations =
super::fix_operational_point(&op_cache, vec![error_op_1, error_op_2].into_iter());

assert_eq!(operations.len(), 1);

let (operation, cache_operation) = operations.get(&op_cache.get_ref()).unwrap();
let Operation::Delete(delete_operation) = operation else {
panic!("not an `Operation::Delete`");
};
assert_eq!(delete_operation.obj_id, "operational_point_id");
assert!(matches!(
delete_operation.obj_type,
ObjectType::OperationalPoint
));
let CacheOperation::Delete(object_ref) = cache_operation else {
panic!("not a `CacheOperation::Delete()`");
};
assert_eq!(object_ref.obj_id, "operational_point_id");
assert_eq!(object_ref.obj_type, ObjectType::OperationalPoint);
}
}
21 changes: 10 additions & 11 deletions editoast/src/views/infra/auto_fixes/speed_section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ fn invalid_reference_to_ordered_operation(
speed_section: &SpeedSection,
object_ref: &ObjectRef,
) -> Option<OrderedOperation> {
let (track_range_idx, _) = speed_section
let (track_refs, _) = speed_section
.track_ranges
.iter()
.enumerate()
.find(|(_idx, track_range)| track_range.track.as_str() == object_ref.obj_id)?;
Some(OrderedOperation::RemoveTrackRange { track_range_idx })
Some(OrderedOperation::RemoveTrackRef { track_refs })
}

pub fn fix_speed_section(
Expand All @@ -47,18 +47,17 @@ pub fn fix_speed_section(
None
}
})
.unique()
// Need to invert the ordering because removing from the front would invalidate other indexes
.sorted_by_key(|ordered_operation| std::cmp::Reverse(ordered_operation.clone()))
.map(|ordered_operation| match ordered_operation {
OrderedOperation::RemoveTrackRange { track_range_idx } => {
Operation::Update(UpdateOperation {
obj_id: speed_section.get_id().clone(),
obj_type: speed_section.get_type(),
railjson_patch: Patch(vec![PatchOperation::Remove(RemoveOperation {
path: format!("/track_ranges/{track_range_idx}").parse().unwrap(),
})]),
})
}
OrderedOperation::RemoveTrackRef { track_refs } => Operation::Update(UpdateOperation {
obj_id: speed_section.get_id().clone(),
obj_type: speed_section.get_type(),
railjson_patch: Patch(vec![PatchOperation::Remove(RemoveOperation {
path: format!("/track_ranges/{track_refs}").parse().unwrap(),
})]),
}),
OrderedOperation::Delete => {
Operation::Delete(DeleteOperation::from(speed_section.get_ref()))
}
Expand Down
Loading