1use crate::errors::ProjectionError;
22use crate::path::DebugInfo;
23use std::fs::File;
24use std::io::Write;
25use std::path::Path;
26
27pub fn export_all_debug_info<P: AsRef<Path>>(
38 debug_info: &DebugInfo,
39 output_dir: P,
40) -> Result<(), ProjectionError> {
41 let dir = output_dir.as_ref();
42 std::fs::create_dir_all(dir)?;
43
44 if !debug_info.position_candidates.is_empty() {
45 export_hmm_emission_probabilities(
46 debug_info,
47 dir.join("01_emission_probabilities.geojson"),
48 )?;
49 }
50
51 if !debug_info.decision_tree.is_empty() {
52 export_hmm_viterbi_trace(debug_info, dir.join("03_viterbi_trace.geojson"))?;
53 }
54
55 if !debug_info.netelement_probabilities.is_empty() {
56 export_hmm_candidate_netelements(debug_info, dir.join("04_candidate_netelements.geojson"))?;
57 export_hmm_selected_path(debug_info, dir.join("07_selected_path.geojson"))?;
58 }
59
60 if !debug_info.sanity_decisions.is_empty() {
61 export_path_sanity_decisions(debug_info, dir.join("05_path_sanity_decisions.geojson"))?;
62 }
63
64 if !debug_info.gap_fills.is_empty() {
65 export_gap_fills(debug_info, dir.join("06_filling_gaps.geojson"))?;
66 }
67
68 if !debug_info.transition_probabilities.is_empty() {
69 export_hmm_transition_probabilities(
70 debug_info,
71 dir.join("02_transition_probabilities.geojson"),
72 )?;
73 }
74
75 Ok(())
76}
77
78pub fn export_hmm_emission_probabilities<P: AsRef<Path>>(
94 debug_info: &DebugInfo,
95 output_path: P,
96) -> Result<(), ProjectionError> {
97 use geojson::{Feature, FeatureCollection, Geometry, Value};
98 use serde_json::{Map, Value as JsonValue};
99
100 let mut features = Vec::new();
101
102 for pos in &debug_info.position_candidates {
103 for candidate in &pos.candidates {
104 let line_geom = Geometry::new(Value::LineString(vec![
105 vec![pos.coordinates.1, pos.coordinates.0],
106 vec![candidate.projected_lon, candidate.projected_lat],
107 ]));
108 let mut props = Map::new();
109 props.insert(
110 "step".to_string(),
111 JsonValue::from(pos.position_index as i64),
112 );
113 props.insert(
114 "netelement_id".to_string(),
115 JsonValue::from(candidate.netelement_id.clone()),
116 );
117 props.insert(
118 "emission_probability".to_string(),
119 JsonValue::from(candidate.combined_probability),
120 );
121 props.insert(
122 "distance_probability".to_string(),
123 JsonValue::from(candidate.distance_probability),
124 );
125 props.insert(
126 "distance_m".to_string(),
127 JsonValue::from(candidate.distance),
128 );
129 if let Some(hp) = candidate.heading_probability {
130 props.insert("heading_probability".to_string(), JsonValue::from(hp));
131 }
132 if let Some(hd) = candidate.heading_difference {
133 props.insert("heading_difference_deg".to_string(), JsonValue::from(hd));
134 }
135 props.insert(
136 "status".to_string(),
137 JsonValue::from(candidate.status.clone()),
138 );
139 features.push(Feature {
140 bbox: None,
141 geometry: Some(line_geom),
142 id: None,
143 properties: Some(props),
144 foreign_members: None,
145 });
146 }
147 }
148
149 let mut fc_members = serde_json::Map::new();
150 fc_members.insert("phase".to_string(), JsonValue::from(1i64));
151 fc_members.insert(
152 "description".to_string(),
153 JsonValue::from(
154 "HMM emission probabilities: links from each GNSS position to its candidate netelements",
155 ),
156 );
157
158 let fc = FeatureCollection {
159 bbox: None,
160 features,
161 foreign_members: Some(fc_members),
162 };
163 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
164 ProjectionError::InvalidGeometry(format!(
165 "Failed to serialize emission probabilities GeoJSON: {}",
166 e
167 ))
168 })?;
169 let mut file = File::create(output_path.as_ref())?;
170 file.write_all(json.as_bytes())?;
171 Ok(())
172}
173
174pub fn export_hmm_viterbi_trace<P: AsRef<Path>>(
190 debug_info: &DebugInfo,
191 output_path: P,
192) -> Result<(), ProjectionError> {
193 use geojson::{Feature, FeatureCollection, Geometry, Value};
194 use serde_json::{Map, Value as JsonValue};
195
196 let pos_lookup: std::collections::HashMap<usize, &crate::path::PositionCandidates> = debug_info
197 .position_candidates
198 .iter()
199 .map(|pc| (pc.position_index, pc))
200 .collect();
201
202 let mut features = Vec::new();
203
204 for decision in &debug_info.decision_tree {
205 let (geometry, selected_probability) = match pos_lookup.get(&decision.step) {
206 Some(pos) => {
207 match pos
208 .candidates
209 .iter()
210 .find(|c| c.netelement_id == decision.chosen_option)
211 {
212 Some(c) => {
213 let geom = Geometry::new(Value::LineString(vec![
214 vec![pos.coordinates.1, pos.coordinates.0],
215 vec![c.projected_lon, c.projected_lat],
216 ]));
217 (Some(geom), Some(c.combined_probability))
218 }
219 None => (None, None),
220 }
221 }
222 None => (None, None),
223 };
224
225 let mut props = Map::new();
226 props.insert("step".to_string(), JsonValue::from(decision.step as i64));
227 props.insert(
228 "netelement_id".to_string(),
229 JsonValue::from(decision.chosen_option.clone()),
230 );
231 props.insert(
232 "decision_type".to_string(),
233 JsonValue::from(decision.decision_type.clone()),
234 );
235 if let Some(prob) = selected_probability {
236 props.insert("selected_probability".to_string(), JsonValue::from(prob));
237 }
238 props.insert(
239 "alternatives_count".to_string(),
240 JsonValue::from(decision.options.len() as i64),
241 );
242 props.insert(
243 "reason".to_string(),
244 JsonValue::from(decision.reason.clone()),
245 );
246
247 features.push(Feature {
248 bbox: None,
249 geometry,
250 id: None,
251 properties: Some(props),
252 foreign_members: None,
253 });
254 }
255
256 let mut fc_members = serde_json::Map::new();
257 fc_members.insert("phase".to_string(), JsonValue::from(3i64));
258 fc_members.insert(
259 "description".to_string(),
260 JsonValue::from(
261 "HMM Viterbi decoding trace: links from each GNSS position to the chosen netelement",
262 ),
263 );
264
265 let fc = FeatureCollection {
266 bbox: None,
267 features,
268 foreign_members: Some(fc_members),
269 };
270 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
271 ProjectionError::InvalidGeometry(format!(
272 "Failed to serialize Viterbi trace GeoJSON: {}",
273 e
274 ))
275 })?;
276 let mut file = File::create(output_path.as_ref())?;
277 file.write_all(json.as_bytes())?;
278 Ok(())
279}
280
281pub fn export_hmm_candidate_netelements<P: AsRef<Path>>(
294 debug_info: &DebugInfo,
295 output_path: P,
296) -> Result<(), ProjectionError> {
297 use geojson::{Feature, FeatureCollection, Geometry, Value};
298 use serde_json::{Map, Value as JsonValue};
299
300 let mut features = Vec::new();
301
302 for ne in &debug_info.netelement_probabilities {
303 if ne.geometry_coords.len() < 2 {
304 continue;
305 }
306 let geom = Geometry::new(Value::LineString(ne.geometry_coords.clone()));
307 let mut props = Map::new();
308 props.insert(
309 "netelement_id".to_string(),
310 JsonValue::from(ne.netelement_id.clone()),
311 );
312 props.insert(
313 "avg_emission_probability".to_string(),
314 JsonValue::from(ne.avg_emission_probability),
315 );
316 props.insert(
317 "position_count".to_string(),
318 JsonValue::from(ne.position_count as i64),
319 );
320 props.insert(
321 "in_viterbi_path".to_string(),
322 JsonValue::from(ne.in_viterbi_path),
323 );
324 props.insert("is_bridge".to_string(), JsonValue::from(ne.is_bridge));
325 features.push(Feature {
326 bbox: None,
327 geometry: Some(geom),
328 id: None,
329 properties: Some(props),
330 foreign_members: None,
331 });
332 }
333
334 let mut fc_members = serde_json::Map::new();
335 fc_members.insert("phase".to_string(), JsonValue::from(4i64));
336 fc_members.insert(
337 "description".to_string(),
338 JsonValue::from("HMM candidate netelements: all states considered during Viterbi decoding"),
339 );
340
341 let fc = FeatureCollection {
342 bbox: None,
343 features,
344 foreign_members: Some(fc_members),
345 };
346 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
347 ProjectionError::InvalidGeometry(format!(
348 "Failed to serialize candidate netelements GeoJSON: {}",
349 e
350 ))
351 })?;
352 let mut file = File::create(output_path.as_ref())?;
353 file.write_all(json.as_bytes())?;
354 Ok(())
355}
356
357pub fn export_hmm_selected_path<P: AsRef<Path>>(
368 debug_info: &DebugInfo,
369 output_path: P,
370) -> Result<(), ProjectionError> {
371 use geojson::{Feature, FeatureCollection, Geometry, Value};
372 use serde_json::{Map, Value as JsonValue};
373
374 let mut features = Vec::new();
375
376 for ne in &debug_info.netelement_probabilities {
377 if !ne.in_viterbi_path {
378 continue;
379 }
380 if ne.geometry_coords.len() < 2 {
381 continue;
382 }
383 let geom = Geometry::new(Value::LineString(ne.geometry_coords.clone()));
384 let mut props = Map::new();
385 props.insert(
386 "netelement_id".to_string(),
387 JsonValue::from(ne.netelement_id.clone()),
388 );
389 props.insert(
390 "avg_emission_probability".to_string(),
391 JsonValue::from(ne.avg_emission_probability),
392 );
393 props.insert(
394 "position_count".to_string(),
395 JsonValue::from(ne.position_count as i64),
396 );
397 props.insert("is_bridge".to_string(), JsonValue::from(ne.is_bridge));
398 features.push(Feature {
399 bbox: None,
400 geometry: Some(geom),
401 id: None,
402 properties: Some(props),
403 foreign_members: None,
404 });
405 }
406
407 let mut fc_members = serde_json::Map::new();
408 fc_members.insert("phase".to_string(), JsonValue::from(6i64));
409 fc_members.insert(
410 "description".to_string(),
411 JsonValue::from("HMM selected path: netelements in the final validated path"),
412 );
413
414 let fc = FeatureCollection {
415 bbox: None,
416 features,
417 foreign_members: Some(fc_members),
418 };
419 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
420 ProjectionError::InvalidGeometry(format!(
421 "Failed to serialize selected path GeoJSON: {}",
422 e
423 ))
424 })?;
425 let mut file = File::create(output_path.as_ref())?;
426 file.write_all(json.as_bytes())?;
427 Ok(())
428}
429
430pub fn export_hmm_transition_probabilities<P: AsRef<Path>>(
450 debug_info: &DebugInfo,
451 output_path: P,
452) -> Result<(), ProjectionError> {
453 use geojson::{Feature, FeatureCollection, Geometry, Value};
454 use serde_json::{Map, Value as JsonValue};
455
456 let mut point_lookup: std::collections::HashMap<(usize, &str), (f64, f64)> =
458 std::collections::HashMap::new();
459 for pos in &debug_info.position_candidates {
460 for c in &pos.candidates {
461 point_lookup.insert(
462 (pos.position_index, c.netelement_id.as_str()),
463 (c.projected_lon, c.projected_lat),
464 );
465 }
466 }
467
468 let mut features = Vec::new();
469
470 for entry in &debug_info.transition_probabilities {
471 let from_pt = point_lookup.get(&(entry.from_step, entry.from_netelement_id.as_str()));
472 let to_pt = point_lookup.get(&(entry.to_step, entry.to_netelement_id.as_str()));
473 let geometry = match (from_pt, to_pt) {
474 (Some(&(from_lon, from_lat)), Some(&(to_lon, to_lat))) => {
475 Some(Geometry::new(Value::LineString(vec![
476 vec![from_lon, from_lat],
477 vec![to_lon, to_lat],
478 ])))
479 }
480 _ => None,
481 };
482
483 let mut props = Map::new();
484 props.insert(
485 "from_step".to_string(),
486 JsonValue::from(entry.from_step as i64),
487 );
488 props.insert("to_step".to_string(), JsonValue::from(entry.to_step as i64));
489 props.insert(
490 "from_netelement_id".to_string(),
491 JsonValue::from(entry.from_netelement_id.clone()),
492 );
493 props.insert(
494 "to_netelement_id".to_string(),
495 JsonValue::from(entry.to_netelement_id.clone()),
496 );
497 props.insert(
498 "transition_probability".to_string(),
499 JsonValue::from(entry.transition_probability),
500 );
501 props.insert(
502 "is_viterbi_chosen".to_string(),
503 JsonValue::from(entry.is_viterbi_chosen),
504 );
505
506 features.push(Feature {
507 bbox: None,
508 geometry,
509 id: None,
510 properties: Some(props),
511 foreign_members: None,
512 });
513 }
514
515 let mut fc_members = serde_json::Map::new();
516 fc_members.insert("phase".to_string(), JsonValue::from(2i64));
517 fc_members.insert(
518 "description".to_string(),
519 JsonValue::from(
520 "HMM transition probabilities: feasible candidate-pair links across consecutive GNSS steps",
521 ),
522 );
523
524 let fc = FeatureCollection {
525 bbox: None,
526 features,
527 foreign_members: Some(fc_members),
528 };
529 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
530 ProjectionError::InvalidGeometry(format!(
531 "Failed to serialize transition probabilities GeoJSON: {}",
532 e
533 ))
534 })?;
535 let mut file = File::create(output_path.as_ref())?;
536 file.write_all(json.as_bytes())?;
537 Ok(())
538}
539
540pub fn export_path_sanity_decisions<P: AsRef<Path>>(
555 debug_info: &DebugInfo,
556 output_path: P,
557) -> Result<(), ProjectionError> {
558 use geojson::{Feature, FeatureCollection, Geometry, Value};
559 use serde_json::{Map, Value as JsonValue};
560 use std::collections::HashMap;
561
562 let ne_geom: HashMap<&str, &Vec<Vec<f64>>> = debug_info
564 .netelement_probabilities
565 .iter()
566 .map(|np| (np.netelement_id.as_str(), &np.geometry_coords))
567 .collect();
568
569 let mut features = Vec::new();
570
571 for decision in &debug_info.sanity_decisions {
572 let coords = ne_geom
574 .get(decision.from_netelement_id.as_str())
575 .and_then(|g| {
576 if g.is_empty() {
577 return None;
578 }
579 let mid = g.len() / 2;
580 Some(vec![g[mid][0], g[mid][1]])
581 })
582 .unwrap_or_else(|| vec![0.0, 0.0]);
583 let geom = Geometry::new(Value::Point(coords));
584 let mut props = Map::new();
585 props.insert(
586 "pair_index".to_string(),
587 JsonValue::from(decision.pair_index as i64),
588 );
589 props.insert(
590 "from_netelement_id".to_string(),
591 JsonValue::from(decision.from_netelement_id.clone()),
592 );
593 props.insert(
594 "to_netelement_id".to_string(),
595 JsonValue::from(decision.to_netelement_id.clone()),
596 );
597 props.insert("reachable".to_string(), JsonValue::from(decision.reachable));
598 props.insert(
599 "action".to_string(),
600 JsonValue::from(decision.action.clone()),
601 );
602 props.insert(
603 "rerouted_via".to_string(),
604 JsonValue::from(decision.rerouted_via.join(",")),
605 );
606 props.insert(
607 "warning".to_string(),
608 JsonValue::from(decision.warning.clone()),
609 );
610
611 features.push(Feature {
612 bbox: None,
613 geometry: Some(geom),
614 id: None,
615 properties: Some(props),
616 foreign_members: None,
617 });
618 }
619
620 let mut fc_members = serde_json::Map::new();
621 fc_members.insert("phase".to_string(), JsonValue::from(5i64));
622 fc_members.insert(
623 "description".to_string(),
624 JsonValue::from(
625 "Path sanity decisions: post-Viterbi navigability validation for each consecutive segment pair",
626 ),
627 );
628
629 let fc = FeatureCollection {
630 bbox: None,
631 features,
632 foreign_members: Some(fc_members),
633 };
634 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
635 ProjectionError::InvalidGeometry(format!(
636 "Failed to serialize path sanity decisions GeoJSON: {}",
637 e
638 ))
639 })?;
640 let mut file = File::create(output_path.as_ref())?;
641 file.write_all(json.as_bytes())?;
642 Ok(())
643}
644
645pub fn export_gap_fills<P: AsRef<Path>>(
651 debug_info: &DebugInfo,
652 output_path: P,
653) -> Result<(), ProjectionError> {
654 use geojson::{Feature, FeatureCollection, Geometry, JsonObject, Value as GeoValue};
655 use serde_json::Value as JsonValue;
656
657 let ne_geom: std::collections::HashMap<&str, &Vec<Vec<f64>>> = debug_info
659 .netelement_probabilities
660 .iter()
661 .map(|np| (np.netelement_id.as_str(), &np.geometry_coords))
662 .collect();
663
664 let mut features = Vec::new();
665
666 for gf in &debug_info.gap_fills {
667 let coords = ne_geom
669 .get(gf.from_netelement_id.as_str())
670 .and_then(|g| {
671 if g.is_empty() {
672 return None;
673 }
674 let mid = g.len() / 2;
675 Some(vec![g[mid][0], g[mid][1]])
676 })
677 .unwrap_or_else(|| vec![0.0, 0.0]);
678 let geom = Geometry::new(GeoValue::Point(coords));
679 let mut props = JsonObject::new();
680 props.insert(
681 "pair_index".to_string(),
682 JsonValue::from(gf.pair_index as u64),
683 );
684 props.insert(
685 "from_netelement_id".to_string(),
686 JsonValue::from(gf.from_netelement_id.as_str()),
687 );
688 props.insert(
689 "to_netelement_id".to_string(),
690 JsonValue::from(gf.to_netelement_id.as_str()),
691 );
692 props.insert("route_found".to_string(), JsonValue::from(gf.route_found));
693 props.insert(
694 "inserted_netelements".to_string(),
695 JsonValue::from(gf.inserted_netelements.join(", ")),
696 );
697 props.insert(
698 "inserted_count".to_string(),
699 JsonValue::from(gf.inserted_netelements.len() as u64),
700 );
701 props.insert("warning".to_string(), JsonValue::from(gf.warning.as_str()));
702
703 features.push(Feature {
704 bbox: None,
705 geometry: Some(geom),
706 id: None,
707 properties: Some(props),
708 foreign_members: None,
709 });
710 }
711
712 let mut fc_members = JsonObject::new();
713 fc_members.insert("phase".to_string(), JsonValue::from(6));
714 fc_members.insert(
715 "description".to_string(),
716 JsonValue::from(
717 "Gap filling: bridge netelements inserted between disconnected consecutive segments after sanity validation",
718 ),
719 );
720
721 let fc = FeatureCollection {
722 bbox: None,
723 features,
724 foreign_members: Some(fc_members),
725 };
726 let json = serde_json::to_string_pretty(&fc).map_err(|e| {
727 ProjectionError::InvalidGeometry(format!("Failed to serialize gap fills GeoJSON: {}", e))
728 })?;
729 let mut file = File::create(output_path.as_ref())?;
730 file.write_all(json.as_bytes())?;
731 Ok(())
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use crate::path::{
738 CandidateInfo, NetelementProbabilityInfo, PathDecision, PositionCandidates,
739 TransitionProbabilityEntry,
740 };
741 use std::io::Read;
742
743 fn make_debug_info() -> DebugInfo {
744 let mut debug_info = DebugInfo::new();
745 debug_info.add_position_candidates(PositionCandidates {
746 position_index: 0,
747 timestamp: "2025-01-09T12:00:00Z".to_string(),
748 coordinates: (50.85, 4.35),
749 candidates: vec![CandidateInfo {
750 netelement_id: "NE_A".to_string(),
751 distance: 5.0,
752 heading_difference: Some(2.0),
753 distance_probability: 0.9,
754 heading_probability: Some(0.8),
755 combined_probability: 0.72,
756 status: "selected".to_string(),
757 projected_lat: 50.851,
758 projected_lon: 4.351,
759 }],
760 selected_netelement: Some("NE_A".to_string()),
761 });
762 debug_info.add_decision(PathDecision {
763 step: 0,
764 decision_type: "viterbi_transition".to_string(),
765 current_segment: "NE_A".to_string(),
766 options: vec!["NE_A".to_string()],
767 option_probabilities: vec![0.72],
768 chosen_option: "NE_A".to_string(),
769 reason: "Only candidate".to_string(),
770 });
771 debug_info
772 .netelement_probabilities
773 .push(NetelementProbabilityInfo {
774 netelement_id: "NE_A".to_string(),
775 avg_emission_probability: 0.72,
776 position_count: 1,
777 geometry_coords: vec![vec![4.35, 50.85], vec![4.36, 50.86]],
778 in_viterbi_path: true,
779 is_bridge: false,
780 });
781 debug_info
782 .transition_probabilities
783 .push(TransitionProbabilityEntry {
784 from_step: 0,
785 to_step: 1,
786 from_netelement_id: "NE_A".to_string(),
787 to_netelement_id: "NE_B".to_string(),
788 transition_probability: 0.65,
789 is_viterbi_chosen: true,
790 });
791 debug_info
792 }
793
794 #[test]
795 fn test_export_hmm_emission_probabilities() {
796 let debug_info = make_debug_info();
797 let temp_dir = std::env::temp_dir();
798 let output_path = temp_dir.join("test_hmm_emission.geojson");
799
800 let result = export_hmm_emission_probabilities(&debug_info, &output_path);
801 assert!(result.is_ok());
802
803 let mut file = File::open(&output_path).unwrap();
804 let mut contents = String::new();
805 file.read_to_string(&mut contents).unwrap();
806 assert!(contents.contains("NE_A"));
807 assert!(contents.contains("emission_probability"));
808 assert!(contents.contains("distance_m"));
809 assert!(!contents.contains("gnss_position"));
811
812 std::fs::remove_file(&output_path).ok();
813 }
814
815 #[test]
816 fn test_export_hmm_viterbi_trace() {
817 let debug_info = make_debug_info();
818 let temp_dir = std::env::temp_dir();
819 let output_path = temp_dir.join("test_hmm_viterbi_trace.geojson");
820
821 let result = export_hmm_viterbi_trace(&debug_info, &output_path);
822 assert!(result.is_ok());
823
824 let mut file = File::open(&output_path).unwrap();
825 let mut contents = String::new();
826 file.read_to_string(&mut contents).unwrap();
827 assert!(contents.contains("NE_A"));
828 assert!(contents.contains("viterbi_transition"));
829 assert!(contents.contains("netelement_id"));
830
831 std::fs::remove_file(&output_path).ok();
832 }
833
834 #[test]
835 fn test_export_hmm_candidate_netelements() {
836 let debug_info = make_debug_info();
837 let temp_dir = std::env::temp_dir();
838 let output_path = temp_dir.join("test_hmm_candidates.geojson");
839
840 let result = export_hmm_candidate_netelements(&debug_info, &output_path);
841 assert!(result.is_ok());
842
843 let mut file = File::open(&output_path).unwrap();
844 let mut contents = String::new();
845 file.read_to_string(&mut contents).unwrap();
846 assert!(contents.contains("NE_A"));
847 assert!(contents.contains("in_viterbi_path"));
848
849 std::fs::remove_file(&output_path).ok();
850 }
851
852 #[test]
853 fn test_export_hmm_selected_path() {
854 let debug_info = make_debug_info();
855 let temp_dir = std::env::temp_dir();
856 let output_path = temp_dir.join("test_hmm_selected_path.geojson");
857
858 let result = export_hmm_selected_path(&debug_info, &output_path);
859 assert!(result.is_ok());
860
861 let mut file = File::open(&output_path).unwrap();
862 let mut contents = String::new();
863 file.read_to_string(&mut contents).unwrap();
864 assert!(contents.contains("NE_A"));
865 assert!(contents.contains("is_bridge"));
866
867 std::fs::remove_file(&output_path).ok();
868 }
869
870 #[test]
871 fn test_export_all_debug_info() {
872 let debug_info = make_debug_info();
873 let temp_dir = std::env::temp_dir().join("tp_hmm_debug_test");
874
875 let result = export_all_debug_info(&debug_info, &temp_dir);
876 assert!(result.is_ok());
877
878 assert!(temp_dir.join("01_emission_probabilities.geojson").exists());
879 assert!(temp_dir.join("03_viterbi_trace.geojson").exists());
880 assert!(temp_dir.join("04_candidate_netelements.geojson").exists());
881 assert!(temp_dir.join("07_selected_path.geojson").exists());
882 assert!(temp_dir
883 .join("02_transition_probabilities.geojson")
884 .exists());
885
886 std::fs::remove_dir_all(&temp_dir).ok();
887 }
888
889 #[test]
890 fn test_export_hmm_transition_probabilities() {
891 let debug_info = make_debug_info();
892 let temp_dir = std::env::temp_dir();
893 let output_path = temp_dir.join("test_hmm_transition_probs.geojson");
894
895 let result = export_hmm_transition_probabilities(&debug_info, &output_path);
896 assert!(result.is_ok());
897
898 let mut file = File::open(&output_path).unwrap();
899 let mut contents = String::new();
900 file.read_to_string(&mut contents).unwrap();
901 assert!(contents.contains("NE_A"));
902 assert!(contents.contains("NE_B"));
903 assert!(contents.contains("transition_probability"));
904 assert!(contents.contains("is_viterbi_chosen"));
905
906 std::fs::remove_file(&output_path).ok();
907 }
908}