1use crate::assembler::{AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree};
4use mig_types::navigator::GroupNavigator;
5use mig_types::segment::OwnedSegment;
6
7pub struct AssembledTreeNavigator<'a> {
9 tree: &'a AssembledTree,
10}
11
12impl<'a> AssembledTreeNavigator<'a> {
13 pub fn new(tree: &'a AssembledTree) -> Self {
14 Self { tree }
15 }
16}
17
18impl GroupNavigator for AssembledTreeNavigator<'_> {
19 fn find_segments_in_group(
20 &self,
21 segment_id: &str,
22 group_path: &[&str],
23 instance_index: usize,
24 ) -> Vec<OwnedSegment> {
25 let Some(instance) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
26 return Vec::new();
27 };
28 instance
29 .segments
30 .iter()
31 .enumerate()
32 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
33 .map(|(i, s)| to_owned(s, i as u32))
34 .collect()
35 }
36
37 fn find_segments_with_qualifier_in_group(
38 &self,
39 segment_id: &str,
40 element_index: usize,
41 qualifier: &str,
42 group_path: &[&str],
43 instance_index: usize,
44 ) -> Vec<OwnedSegment> {
45 self.find_segments_in_group(segment_id, group_path, instance_index)
46 .into_iter()
47 .filter(|s| {
48 s.elements
49 .get(element_index)
50 .and_then(|e| e.first())
51 .is_some_and(|v| v == qualifier)
52 })
53 .collect()
54 }
55
56 fn group_instance_count(&self, group_path: &[&str]) -> usize {
57 resolve_group(&self.tree.groups, group_path)
58 .map(|g| g.repetitions.len())
59 .unwrap_or(0)
60 }
61
62 fn find_segments_in_instance(
63 &self,
64 segment_id: &str,
65 path: &[(&str, usize)],
66 ) -> Vec<OwnedSegment> {
67 let Some(instance) = resolve_instance_path(&self.tree.groups, path) else {
68 return Vec::new();
69 };
70 instance
71 .segments
72 .iter()
73 .enumerate()
74 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
75 .map(|(i, s)| to_owned(s, i as u32))
76 .collect()
77 }
78
79 fn child_instance_count(&self, parent: &[(&str, usize)], child_group_id: &str) -> usize {
80 let groups = if parent.is_empty() {
81 &self.tree.groups
82 } else {
83 match resolve_instance_path(&self.tree.groups, parent) {
84 Some(instance) => &instance.child_groups,
85 None => return 0,
86 }
87 };
88 groups
89 .iter()
90 .find(|g| g.group_id == child_group_id)
91 .map_or(0, |g| g.repetitions.len())
92 }
93
94 fn find_segments_in_subtree(
95 &self,
96 segment_id: &str,
97 path: &[(&str, usize)],
98 ) -> Vec<OwnedSegment> {
99 let Some(instance) = resolve_instance_path(&self.tree.groups, path) else {
100 return Vec::new();
101 };
102 let mut out = Vec::new();
103 collect_subtree(instance, segment_id, &mut out);
104 out
105 }
106
107 fn has_any_segment_in_group(&self, group_path: &[&str], instance_index: usize) -> bool {
108 resolve_instance(&self.tree.groups, group_path, instance_index)
109 .is_some_and(|inst| !inst.segments.is_empty())
110 }
111
112 fn instance_has_mig_number(
113 &self,
114 group_path: &[&str],
115 instance_index: usize,
116 mig_number: &str,
117 ) -> bool {
118 let Some(inst) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
119 return true;
120 };
121 if !inst.variant_mig_numbers.is_empty() {
122 return inst.variant_mig_numbers.iter().any(|n| n == mig_number);
123 }
124 has_mig_in_instance(inst, mig_number)
125 }
126
127 fn instance_at_has_mig_number(&self, path: &[(&str, usize)], mig_number: &str) -> bool {
128 let Some(inst) = resolve_instance_path(&self.tree.groups, path) else {
129 return true;
130 };
131 if !inst.variant_mig_numbers.is_empty() {
132 return inst.variant_mig_numbers.iter().any(|n| n == mig_number);
133 }
134 has_mig_in_instance(inst, mig_number)
135 }
136
137 fn child_group_instance_count(
138 &self,
139 parent_path: &[&str],
140 parent_instance: usize,
141 child_group_id: &str,
142 ) -> usize {
143 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
144 return 0;
145 };
146 parent
147 .child_groups
148 .iter()
149 .find(|g| g.group_id == child_group_id)
150 .map(|g| g.repetitions.len())
151 .unwrap_or(0)
152 }
153
154 fn find_segments_in_child_group(
155 &self,
156 segment_id: &str,
157 parent_path: &[&str],
158 parent_instance: usize,
159 child_group_id: &str,
160 child_instance: usize,
161 ) -> Vec<OwnedSegment> {
162 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
163 return Vec::new();
164 };
165 let Some(child_group) = parent
166 .child_groups
167 .iter()
168 .find(|g| g.group_id == child_group_id)
169 else {
170 return Vec::new();
171 };
172 let Some(instance) = child_group.repetitions.get(child_instance) else {
173 return Vec::new();
174 };
175 instance
176 .segments
177 .iter()
178 .enumerate()
179 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
180 .map(|(i, s)| to_owned(s, i as u32))
181 .collect()
182 }
183
184 fn extract_value_in_group(
185 &self,
186 segment_id: &str,
187 element_index: usize,
188 component_index: usize,
189 group_path: &[&str],
190 instance_index: usize,
191 ) -> Option<String> {
192 let instance = resolve_instance(&self.tree.groups, group_path, instance_index)?;
193 let seg = instance
194 .segments
195 .iter()
196 .find(|s| s.tag.eq_ignore_ascii_case(segment_id))?;
197 seg.elements
198 .get(element_index)?
199 .get(component_index)
200 .cloned()
201 }
202}
203
204fn resolve_group<'a>(groups: &'a [AssembledGroup], path: &[&str]) -> Option<&'a AssembledGroup> {
206 if path.is_empty() {
207 return None;
208 }
209 let group = groups.iter().find(|g| g.group_id == path[0])?;
210 if path.len() == 1 {
211 return Some(group);
212 }
213 let instance = group.repetitions.first()?;
215 resolve_group(&instance.child_groups, &path[1..])
216}
217
218fn resolve_instance_path<'a>(
220 groups: &'a [AssembledGroup],
221 path: &[(&str, usize)],
222) -> Option<&'a AssembledGroupInstance> {
223 let (&(group_id, index), rest) = path.split_first()?;
224 let instance = groups
225 .iter()
226 .find(|g| g.group_id == group_id)?
227 .repetitions
228 .get(index)?;
229 if rest.is_empty() {
230 Some(instance)
231 } else {
232 resolve_instance_path(&instance.child_groups, rest)
233 }
234}
235
236fn collect_subtree(
239 instance: &AssembledGroupInstance,
240 segment_id: &str,
241 out: &mut Vec<OwnedSegment>,
242) {
243 for (i, s) in instance.segments.iter().enumerate() {
244 if s.tag.eq_ignore_ascii_case(segment_id) {
245 out.push(to_owned(s, i as u32));
246 }
247 }
248 for group in &instance.child_groups {
249 for repetition in &group.repetitions {
250 collect_subtree(repetition, segment_id, out);
251 }
252 }
253}
254
255fn resolve_instance<'a>(
257 groups: &'a [AssembledGroup],
258 path: &[&str],
259 instance_index: usize,
260) -> Option<&'a AssembledGroupInstance> {
261 let group = resolve_group(groups, path)?;
262 group.repetitions.get(instance_index)
263}
264
265fn has_mig_in_instance(inst: &AssembledGroupInstance, mig_number: &str) -> bool {
268 if inst
269 .segments
270 .iter()
271 .any(|s| s.mig_number.as_deref() == Some(mig_number))
272 {
273 return true;
274 }
275 inst.child_groups.iter().any(|g| {
276 g.repetitions
277 .iter()
278 .any(|r| has_mig_in_instance(r, mig_number))
279 })
280}
281
282fn to_owned(seg: &AssembledSegment, segment_number: u32) -> OwnedSegment {
283 OwnedSegment {
284 id: seg.tag.clone(),
285 elements: seg.elements.clone(),
286 segment_number,
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::assembler::{
294 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
295 };
296 use mig_types::navigator::GroupNavigator;
297
298 fn make_seg(tag: &str, elements: Vec<Vec<&str>>) -> AssembledSegment {
299 AssembledSegment {
300 tag: tag.to_string(),
301 elements: elements
302 .into_iter()
303 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
304 .collect(),
305 mig_number: None,
306 segment_number: None,
307 }
308 }
309
310 fn tree_with_sg4_sg8() -> AssembledTree {
311 AssembledTree {
318 segments: vec![make_seg("UNH", vec![vec!["001"]])],
319 groups: vec![AssembledGroup {
320 group_id: "SG4".to_string(),
321 repetitions: vec![AssembledGroupInstance {
322 segments: vec![
323 make_seg("IDE", vec![vec!["24", "TX001"]]),
324 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
325 ],
326 child_groups: vec![AssembledGroup {
327 group_id: "SG8".to_string(),
328 repetitions: vec![
329 AssembledGroupInstance {
330 segments: vec![
331 make_seg("SEQ", vec![vec!["Z98"]]),
332 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
333 ],
334 child_groups: vec![AssembledGroup {
335 group_id: "SG10".to_string(),
336 repetitions: vec![
337 AssembledGroupInstance {
338 segments: vec![
339 make_seg("CCI", vec![vec!["Z23"]]),
340 make_seg("CAV", vec![vec!["Z91", "value1"]]),
341 ],
342 child_groups: vec![],
343 entry_mig_number: None,
344 variant_mig_numbers: vec![],
345 skipped_segments: vec![],
346 skipped_positions: Vec::new(),
347 },
348 AssembledGroupInstance {
349 segments: vec![
350 make_seg("CCI", vec![vec![""]]),
351 make_seg("CAV", vec![vec!["", "value2"]]),
352 ],
353 child_groups: vec![],
354 entry_mig_number: None,
355 variant_mig_numbers: vec![],
356 skipped_segments: vec![],
357 skipped_positions: Vec::new(),
358 },
359 ],
360 }],
361 entry_mig_number: None,
362 variant_mig_numbers: vec![],
363 skipped_segments: vec![],
364 skipped_positions: Vec::new(),
365 },
366 AssembledGroupInstance {
367 segments: vec![
368 make_seg("SEQ", vec![vec!["Z01"]]),
369 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
370 ],
371 child_groups: vec![],
372 entry_mig_number: None,
373 variant_mig_numbers: vec![],
374 skipped_segments: vec![],
375 skipped_positions: Vec::new(),
376 },
377 ],
378 }],
379 entry_mig_number: None,
380 variant_mig_numbers: vec![],
381 skipped_segments: vec![],
382 skipped_positions: Vec::new(),
383 }],
384 }],
385 post_group_start: 1,
386 inter_group_segments: std::collections::BTreeMap::new(),
387 }
388 }
389
390 fn tree_with_two_sg4() -> AssembledTree {
393 fn instance(
394 segments: Vec<AssembledSegment>,
395 child_groups: Vec<AssembledGroup>,
396 ) -> AssembledGroupInstance {
397 AssembledGroupInstance {
398 segments,
399 child_groups,
400 entry_mig_number: None,
401 variant_mig_numbers: vec![],
402 skipped_segments: vec![],
403 skipped_positions: Vec::new(),
404 }
405 }
406 fn group(id: &str, repetitions: Vec<AssembledGroupInstance>) -> AssembledGroup {
407 AssembledGroup {
408 group_id: id.to_string(),
409 repetitions,
410 }
411 }
412 let sg10 = |code: &str| {
413 group(
414 "SG10",
415 vec![instance(
416 vec![make_seg("CCI", vec![vec![""], vec![], vec![code]])],
417 vec![],
418 )],
419 )
420 };
421 AssembledTree {
422 segments: vec![],
423 groups: vec![group(
424 "SG4",
425 vec![
426 instance(
427 vec![make_seg("IDE", vec![vec!["24", "TX0"]])],
428 vec![group(
429 "SG8",
430 vec![instance(
431 vec![make_seg("SEQ", vec![vec!["Z98"]])],
432 vec![sg10("Z23")],
433 )],
434 )],
435 ),
436 instance(
437 vec![make_seg("IDE", vec![vec!["24", "TX1"]])],
438 vec![group(
439 "SG8",
440 vec![
441 instance(vec![make_seg("SEQ", vec![vec!["Z01"]])], vec![]),
442 instance(
443 vec![make_seg("SEQ", vec![vec!["Z03"]])],
444 vec![sg10("E13")],
445 ),
446 ],
447 )],
448 ),
449 ],
450 )],
451 post_group_start: 0,
452 inter_group_segments: std::collections::BTreeMap::new(),
453 }
454 }
455
456 #[test]
457 fn an_instance_is_addressed_at_every_level() {
458 let tree = tree_with_two_sg4();
459 let nav = AssembledTreeNavigator::new(&tree);
460 let seq = |path: &[(&str, usize)]| -> Vec<String> {
461 nav.find_segments_in_instance("SEQ", path)
462 .iter()
463 .map(|s| s.get_element(0).to_string())
464 .collect()
465 };
466 assert_eq!(seq(&[("SG4", 0), ("SG8", 0)]), ["Z98"]);
467 assert_eq!(seq(&[("SG4", 1), ("SG8", 0)]), ["Z01"]);
468 assert_eq!(seq(&[("SG4", 1), ("SG8", 1)]), ["Z03"]);
469 assert!(seq(&[("SG4", 0), ("SG8", 1)]).is_empty());
470 }
471
472 #[test]
473 fn a_subtree_includes_the_nested_groups() {
474 let tree = tree_with_two_sg4();
475 let nav = AssembledTreeNavigator::new(&tree);
476 let cci = |path: &[(&str, usize)]| -> Vec<String> {
477 nav.find_segments_in_subtree("CCI", path)
478 .iter()
479 .map(|s| s.get_component(2, 0).to_string())
480 .collect()
481 };
482 assert_eq!(cci(&[("SG4", 1), ("SG8", 1)]), ["E13"]);
483 assert!(cci(&[("SG4", 1), ("SG8", 0)]).is_empty());
484 assert_eq!(cci(&[("SG4", 1)]), ["E13"], "the whole SG4");
485 assert!(
486 nav.find_segments_in_instance("CCI", &[("SG4", 1), ("SG8", 1)])
487 .is_empty(),
488 "the instance alone holds no CCI"
489 );
490 }
491
492 #[test]
493 fn test_find_in_sg8_instance_0() {
494 let tree = tree_with_sg4_sg8();
495 let nav = AssembledTreeNavigator::new(&tree);
496 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
497 assert_eq!(segs.len(), 1);
498 assert_eq!(segs[0].get_element(0), "Z98");
499 }
500
501 #[test]
502 fn test_find_in_sg8_instance_1() {
503 let tree = tree_with_sg4_sg8();
504 let nav = AssembledTreeNavigator::new(&tree);
505 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
506 assert_eq!(segs.len(), 1);
507 assert_eq!(segs[0].get_element(0), "Z01");
508 }
509
510 #[test]
511 fn test_qualifier_in_group_scoped() {
512 let tree = tree_with_sg4_sg8();
513 let nav = AssembledTreeNavigator::new(&tree);
514 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
515 assert_eq!(segs.len(), 1);
516 assert!(nav
518 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
519 .is_empty());
520 }
521
522 #[test]
523 fn test_group_instance_count() {
524 let tree = tree_with_sg4_sg8();
525 let nav = AssembledTreeNavigator::new(&tree);
526 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
527 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
528 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
529 }
530
531 #[test]
532 fn test_find_in_sg4_directly() {
533 let tree = tree_with_sg4_sg8();
534 let nav = AssembledTreeNavigator::new(&tree);
535 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
536 assert_eq!(segs.len(), 1);
537 }
538
539 #[test]
540 fn test_invalid_path_returns_empty() {
541 let tree = tree_with_sg4_sg8();
542 let nav = AssembledTreeNavigator::new(&tree);
543 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
544 assert!(nav
545 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
546 .is_empty());
547 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
548 }
549
550 #[test]
551 fn test_child_group_instance_count() {
552 let tree = tree_with_sg4_sg8();
553 let nav = AssembledTreeNavigator::new(&tree);
554 assert_eq!(
556 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
557 2
558 );
559 assert_eq!(
561 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
562 0
563 );
564 assert_eq!(
566 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
567 0
568 );
569 }
570
571 #[test]
572 fn test_find_segments_in_child_group() {
573 let tree = tree_with_sg4_sg8();
574 let nav = AssembledTreeNavigator::new(&tree);
575 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
577 assert_eq!(segs.len(), 1);
578 assert_eq!(segs[0].get_element(0), "Z23");
579 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
581 assert_eq!(segs.len(), 1);
582 assert_eq!(segs[0].get_element(0), "");
583 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
585 assert_eq!(segs.len(), 1);
586 }
587
588 #[test]
589 fn test_child_group_invalid_path() {
590 let tree = tree_with_sg4_sg8();
591 let nav = AssembledTreeNavigator::new(&tree);
592 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
594 assert!(nav
596 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
597 .is_empty());
598 assert!(nav
600 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
601 .is_empty());
602 }
603
604 #[test]
605 fn test_extract_value_in_group() {
606 let tree = tree_with_sg4_sg8();
607 let nav = AssembledTreeNavigator::new(&tree);
608 assert_eq!(
610 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
611 Some("Z98".to_string()),
612 );
613 assert_eq!(
615 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
616 Some("Z01".to_string()),
617 );
618 }
619
620 #[test]
621 fn test_extract_value_missing() {
622 let tree = tree_with_sg4_sg8();
623 let nav = AssembledTreeNavigator::new(&tree);
624 assert_eq!(
626 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
627 None
628 );
629 assert_eq!(
631 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
632 None
633 );
634 assert_eq!(
636 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
637 None
638 );
639 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
641 }
642}