tp_lib_core/
detections.rs1pub mod anchor;
6pub mod error;
7pub mod filter;
8pub mod load;
9pub mod resolve;
10pub mod validate;
11
12pub use error::DetectionError;
13
14use std::collections::HashMap;
15use std::path::Path;
16
17use crate::models::{
18 Detection, DetectionKind, DetectionRecord, GnssPosition, Netelement, ResolvedAnchor,
19};
20
21#[derive(Debug, Clone, Default)]
23pub struct PreparedDetections {
24 pub anchors: Vec<ResolvedAnchor>,
27 pub records: Vec<DetectionRecord>,
30 pub warnings: Vec<String>,
32}
33
34pub fn prepare_detections(
38 path: &Path,
39 expected_kind: DetectionKind,
40 gnss: &[GnssPosition],
41 netelements: &[Netelement],
42 cutoff_distance_m: f64,
43) -> Result<PreparedDetections, DetectionError> {
44 let detections = load::load_detections(path, expected_kind)?;
45 prepare_detections_from_loaded(detections, gnss, netelements, cutoff_distance_m)
46}
47
48pub fn prepare_detections_from_loaded(
51 detections: Vec<Detection>,
52 gnss: &[GnssPosition],
53 netelements: &[Netelement],
54 cutoff_distance_m: f64,
55) -> Result<PreparedDetections, DetectionError> {
56 let mut warnings = Vec::new();
57 let mut all_records: Vec<DetectionRecord> = Vec::new();
58 let input_order: Vec<(String, usize)> = detections
59 .iter()
60 .map(|d| (d.source_file().to_owned(), d.source_row()))
61 .collect();
62
63 let validated = validate::validate_detections(detections, netelements)?;
65 let kept_keys: Vec<(String, usize)> = validated
66 .kept
67 .iter()
68 .map(|d| (d.source_file().to_owned(), d.source_row()))
69 .collect();
70 all_records.extend(validated.duplicate_records);
71
72 let filtered = filter::filter_detections_by_time(validated.kept, gnss);
74 all_records.extend(filtered.discard_records);
75 warnings.extend(filtered.warnings);
76
77 let resolution =
79 resolve::resolve_detections(filtered.kept, gnss, netelements, cutoff_distance_m)?;
80 all_records.extend(resolution.records);
81 warnings.extend(resolution.warnings);
82
83 let mut anchors = resolution.anchors;
85 anchors.sort_by_key(|a| a.first_index());
86
87 let mut by_key: HashMap<(String, usize), DetectionRecord> = HashMap::new();
90 for rec in all_records {
91 by_key.insert((rec.source_file.clone(), rec.source_row), rec);
92 }
93 let mut records: Vec<DetectionRecord> = Vec::with_capacity(input_order.len());
94 for key in input_order {
95 if let Some(rec) = by_key.remove(&key) {
96 records.push(rec);
97 }
98 }
99 let mut leftovers: Vec<DetectionRecord> = by_key.into_values().collect();
100 leftovers.sort_by(|a, b| {
101 a.source_file
102 .cmp(&b.source_file)
103 .then(a.source_row.cmp(&b.source_row))
104 });
105 records.extend(leftovers);
106
107 let kept_lookup: HashMap<(String, usize), usize> = kept_keys
108 .into_iter()
109 .enumerate()
110 .map(|(idx, key)| (key, idx))
111 .collect();
112 let mut kept_index_to_provenance_index: Vec<Option<usize>> = vec![None; kept_lookup.len()];
113 for (provenance_idx, rec) in records.iter().enumerate() {
114 if let Some(&kept_idx) = kept_lookup.get(&(rec.source_file.clone(), rec.source_row)) {
115 kept_index_to_provenance_index[kept_idx] = Some(provenance_idx);
116 }
117 }
118 for rec in &mut records {
119 if let crate::models::DetectionStatus::Discarded {
120 reason: crate::models::DiscardReason::DuplicateOfPriorDetection { kept_index },
121 } = &mut rec.status
122 {
123 if let Some(Some(mapped)) = kept_index_to_provenance_index.get(*kept_index) {
124 *kept_index = *mapped;
125 }
126 }
127 }
128
129 Ok(PreparedDetections {
130 anchors,
131 records,
132 warnings,
133 })
134}