1use super::evaluator::{ConditionResult, ExternalConditionProvider};
4use mig_types::navigator::GroupNavigator;
5use mig_types::segment::OwnedSegment;
6
7pub struct EvaluationContext<'a> {
12 pub pruefidentifikator: &'a str,
15
16 pub external: &'a dyn ExternalConditionProvider,
19
20 pub segments: &'a [OwnedSegment],
23
24 pub navigator: Option<&'a dyn GroupNavigator>,
27}
28
29pub struct NoOpGroupNavigator;
31
32impl GroupNavigator for NoOpGroupNavigator {
33 fn find_segments_in_group(&self, _: &str, _: &[&str], _: usize) -> Vec<OwnedSegment> {
34 Vec::new()
35 }
36 fn find_segments_with_qualifier_in_group(
37 &self,
38 _: &str,
39 _: usize,
40 _: &str,
41 _: &[&str],
42 _: usize,
43 ) -> Vec<OwnedSegment> {
44 Vec::new()
45 }
46 fn group_instance_count(&self, _: &[&str]) -> usize {
47 0
48 }
49}
50
51impl<'a> EvaluationContext<'a> {
52 pub fn new(
54 pruefidentifikator: &'a str,
55 external: &'a dyn ExternalConditionProvider,
56 segments: &'a [OwnedSegment],
57 ) -> Self {
58 Self {
59 pruefidentifikator,
60 external,
61 segments,
62 navigator: None,
63 }
64 }
65
66 pub fn with_navigator(
68 pruefidentifikator: &'a str,
69 external: &'a dyn ExternalConditionProvider,
70 segments: &'a [OwnedSegment],
71 navigator: &'a dyn GroupNavigator,
72 ) -> Self {
73 Self {
74 pruefidentifikator,
75 external,
76 segments,
77 navigator: Some(navigator),
78 }
79 }
80
81 pub fn navigator(&self) -> Option<&'a dyn GroupNavigator> {
83 self.navigator
84 }
85
86 pub fn find_segment(&self, segment_id: &str) -> Option<&'a OwnedSegment> {
88 self.segments.iter().find(|s| s.id == segment_id)
89 }
90
91 pub fn find_segments(&self, segment_id: &str) -> Vec<&'a OwnedSegment> {
93 self.segments
94 .iter()
95 .filter(|s| s.id == segment_id)
96 .collect()
97 }
98
99 pub fn find_segments_with_qualifier(
101 &self,
102 segment_id: &str,
103 element_index: usize,
104 qualifier: &str,
105 ) -> Vec<&'a OwnedSegment> {
106 self.segments
107 .iter()
108 .filter(|s| {
109 s.id == segment_id
110 && s.elements
111 .get(element_index)
112 .and_then(|e| e.first())
113 .is_some_and(|v| v == qualifier)
114 })
115 .collect()
116 }
117
118 pub fn has_segment(&self, segment_id: &str) -> bool {
120 self.segments.iter().any(|s| s.id == segment_id)
121 }
122
123 pub fn find_segments_in_group(
126 &self,
127 segment_id: &str,
128 group_path: &[&str],
129 instance_index: usize,
130 ) -> Vec<OwnedSegment> {
131 match self.navigator {
132 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
133 None => Vec::new(),
134 }
135 }
136
137 pub fn find_segments_with_qualifier_in_group(
140 &self,
141 segment_id: &str,
142 element_index: usize,
143 qualifier: &str,
144 group_path: &[&str],
145 instance_index: usize,
146 ) -> Vec<OwnedSegment> {
147 match self.navigator {
148 Some(nav) => nav.find_segments_with_qualifier_in_group(
149 segment_id,
150 element_index,
151 qualifier,
152 group_path,
153 instance_index,
154 ),
155 None => Vec::new(),
156 }
157 }
158
159 pub fn has_segment_in_group(
162 &self,
163 segment_id: &str,
164 group_path: &[&str],
165 instance_index: usize,
166 ) -> bool {
167 !self
168 .find_segments_in_group(segment_id, group_path, instance_index)
169 .is_empty()
170 }
171
172 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
175 match self.navigator {
176 Some(nav) => nav.group_instance_count(group_path),
177 None => 0,
178 }
179 }
180
181 pub fn child_group_instance_count(
184 &self,
185 parent_path: &[&str],
186 parent_instance: usize,
187 child_group_id: &str,
188 ) -> usize {
189 match self.navigator {
190 Some(nav) => {
191 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
192 }
193 None => 0,
194 }
195 }
196
197 pub fn find_segments_in_child_group(
200 &self,
201 segment_id: &str,
202 parent_path: &[&str],
203 parent_instance: usize,
204 child_group_id: &str,
205 child_instance: usize,
206 ) -> Vec<OwnedSegment> {
207 match self.navigator {
208 Some(nav) => nav.find_segments_in_child_group(
209 segment_id,
210 parent_path,
211 parent_instance,
212 child_group_id,
213 child_instance,
214 ),
215 None => Vec::new(),
216 }
217 }
218
219 pub fn extract_value_in_group(
222 &self,
223 segment_id: &str,
224 element_index: usize,
225 component_index: usize,
226 group_path: &[&str],
227 instance_index: usize,
228 ) -> Option<String> {
229 self.navigator?.extract_value_in_group(
230 segment_id,
231 element_index,
232 component_index,
233 group_path,
234 instance_index,
235 )
236 }
237
238 pub fn has_qualifier(
244 &self,
245 tag: &str,
246 element_index: usize,
247 qualifier: &str,
248 ) -> ConditionResult {
249 ConditionResult::from(
250 !self
251 .find_segments_with_qualifier(tag, element_index, qualifier)
252 .is_empty(),
253 )
254 }
255
256 pub fn lacks_qualifier(
259 &self,
260 tag: &str,
261 element_index: usize,
262 qualifier: &str,
263 ) -> ConditionResult {
264 ConditionResult::from(
265 self.find_segments_with_qualifier(tag, element_index, qualifier)
266 .is_empty(),
267 )
268 }
269
270 pub fn has_qualified_value(
275 &self,
276 tag: &str,
277 qual_elem: usize,
278 qualifier: &str,
279 value_elem: usize,
280 value_comp: usize,
281 values: &[&str],
282 ) -> ConditionResult {
283 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
284 if segments.is_empty() {
285 return ConditionResult::Unknown;
286 }
287 for seg in &segments {
288 if let Some(v) = seg
289 .elements
290 .get(value_elem)
291 .and_then(|e| e.get(value_comp))
292 .map(|s| s.as_str())
293 {
294 if values.contains(&v) {
295 return ConditionResult::True;
296 }
297 }
298 }
299 ConditionResult::False
300 }
301
302 pub fn any_group_has_qualifier(
308 &self,
309 tag: &str,
310 element_index: usize,
311 qualifier: &str,
312 group_path: &[&str],
313 ) -> ConditionResult {
314 let instance_count = self.group_instance_count(group_path);
315 if instance_count > 0 {
316 for i in 0..instance_count {
317 if !self
318 .find_segments_with_qualifier_in_group(
319 tag,
320 element_index,
321 qualifier,
322 group_path,
323 i,
324 )
325 .is_empty()
326 {
327 return ConditionResult::True;
328 }
329 }
330 return ConditionResult::False;
331 }
332 self.has_qualifier(tag, element_index, qualifier)
334 }
335
336 pub fn any_group_has_any_qualifier(
342 &self,
343 tag: &str,
344 element_index: usize,
345 qualifiers: &[&str],
346 group_path: &[&str],
347 ) -> ConditionResult {
348 let instance_count = self.group_instance_count(group_path);
349 if instance_count > 0 {
350 for i in 0..instance_count {
351 let segs = self.find_segments_in_group(tag, group_path, i);
352 if segs.iter().any(|seg| {
353 seg.elements
354 .get(element_index)
355 .and_then(|e| e.first())
356 .map(|s| s.as_str())
357 .is_some_and(|v| qualifiers.contains(&v))
358 }) {
359 return ConditionResult::True;
360 }
361 }
362 return ConditionResult::False;
363 }
364 let found = self.find_segments(tag).iter().any(|seg| {
366 seg.elements
367 .get(element_index)
368 .and_then(|e| e.first())
369 .map(|s| s.as_str())
370 .is_some_and(|v| qualifiers.contains(&v))
371 });
372 ConditionResult::from(found)
373 }
374
375 pub fn any_group_has_qualified_value(
381 &self,
382 tag: &str,
383 qual_elem: usize,
384 qualifier: &str,
385 value_elem: usize,
386 value_comp: usize,
387 values: &[&str],
388 group_path: &[&str],
389 ) -> ConditionResult {
390 let instance_count = self.group_instance_count(group_path);
391 if instance_count > 0 {
392 for i in 0..instance_count {
393 let segs = self.find_segments_with_qualifier_in_group(
394 tag, qual_elem, qualifier, group_path, i,
395 );
396 for seg in &segs {
397 if seg
398 .elements
399 .get(value_elem)
400 .and_then(|e| e.get(value_comp))
401 .map(|s| s.as_str())
402 .is_some_and(|v| values.contains(&v))
403 {
404 return ConditionResult::True;
405 }
406 }
407 }
408 return ConditionResult::False;
409 }
410 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
412 }
413
414 #[allow(clippy::too_many_arguments)]
423 pub fn filtered_parent_child_has_qualifier(
424 &self,
425 parent_path: &[&str],
426 parent_tag: &str,
427 parent_elem: usize,
428 parent_qual: &str,
429 child_group_id: &str,
430 child_tag: &str,
431 child_elem: usize,
432 child_qual: &str,
433 ) -> ConditionResult {
434 let parent_count = self.group_instance_count(parent_path);
435 if parent_count > 0 {
436 for pi in 0..parent_count {
437 let parent_segs = self.find_segments_with_qualifier_in_group(
439 parent_tag,
440 parent_elem,
441 parent_qual,
442 parent_path,
443 pi,
444 );
445 if parent_segs.is_empty() {
446 continue;
447 }
448 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
450 for ci in 0..child_count {
451 let child_segs = self.find_segments_in_child_group(
452 child_tag,
453 parent_path,
454 pi,
455 child_group_id,
456 ci,
457 );
458 if child_segs.iter().any(|s| {
459 s.elements
460 .get(child_elem)
461 .and_then(|e| e.first())
462 .is_some_and(|v| v == child_qual)
463 }) {
464 return ConditionResult::True;
465 }
466 }
467 }
468 return ConditionResult::False;
469 }
470 let has_parent = !self
472 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
473 .is_empty();
474 let has_child = !self
475 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
476 .is_empty();
477 ConditionResult::from(has_parent && has_child)
478 }
479
480 #[allow(clippy::too_many_arguments)]
486 pub fn any_group_has_qualifier_without(
487 &self,
488 present_tag: &str,
489 present_elem: usize,
490 present_qual: &str,
491 absent_tag: &str,
492 absent_elem: usize,
493 absent_qual: &str,
494 group_path: &[&str],
495 ) -> ConditionResult {
496 let instance_count = self.group_instance_count(group_path);
497 if instance_count > 0 {
498 for i in 0..instance_count {
499 let has_present = !self
500 .find_segments_with_qualifier_in_group(
501 present_tag,
502 present_elem,
503 present_qual,
504 group_path,
505 i,
506 )
507 .is_empty();
508 let has_absent = !self
509 .find_segments_with_qualifier_in_group(
510 absent_tag,
511 absent_elem,
512 absent_qual,
513 group_path,
514 i,
515 )
516 .is_empty();
517 if has_present && !has_absent {
518 return ConditionResult::True;
519 }
520 }
521 return ConditionResult::False;
522 }
523 let has_present = !self
525 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
526 .is_empty();
527 let has_absent = !self
528 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
529 .is_empty();
530 ConditionResult::from(has_present && !has_absent)
531 }
532
533 pub fn collect_group_values(
537 &self,
538 tag: &str,
539 elem: usize,
540 comp: usize,
541 group_path: &[&str],
542 ) -> Vec<(usize, String)> {
543 let instance_count = self.group_instance_count(group_path);
544 let mut results = Vec::new();
545 for i in 0..instance_count {
546 if let Some(val) = self
547 .navigator
548 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
549 {
550 if !val.is_empty() {
551 results.push((i, val));
552 }
553 }
554 }
555 results
556 }
557
558 #[allow(clippy::too_many_arguments)]
567 pub fn groups_share_qualified_value(
568 &self,
569 source_tag: &str,
570 source_qual_elem: usize,
571 source_qual: &str,
572 source_value_elem: usize,
573 source_value_comp: usize,
574 source_path: &[&str],
575 target_tag: &str,
576 target_elem: usize,
577 target_comp: usize,
578 target_path: &[&str],
579 ) -> ConditionResult {
580 let source_count = self.group_instance_count(source_path);
581 let target_count = self.group_instance_count(target_path);
582 if source_count > 0 && target_count > 0 {
583 let mut source_values = Vec::new();
585 for si in 0..source_count {
586 let segs = self.find_segments_with_qualifier_in_group(
587 source_tag,
588 source_qual_elem,
589 source_qual,
590 source_path,
591 si,
592 );
593 for seg in &segs {
594 if let Some(val) = seg
595 .elements
596 .get(source_value_elem)
597 .and_then(|e| e.get(source_value_comp))
598 {
599 if !val.is_empty() {
600 source_values.push(val.clone());
601 }
602 }
603 }
604 }
605 if source_values.is_empty() {
606 return ConditionResult::Unknown;
607 }
608 let target_values =
610 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
611 for (_, tv) in &target_values {
612 if source_values.iter().any(|sv| sv == tv) {
613 return ConditionResult::True;
614 }
615 }
616 return ConditionResult::False;
617 }
618 let source_segs =
620 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
621 let source_vals: Vec<&str> = source_segs
622 .iter()
623 .filter_map(|s| {
624 s.elements
625 .get(source_value_elem)
626 .and_then(|e| e.get(source_value_comp))
627 .map(|v| v.as_str())
628 })
629 .filter(|v| !v.is_empty())
630 .collect();
631 if source_vals.is_empty() {
632 return ConditionResult::Unknown;
633 }
634 let target_segs = self.find_segments(target_tag);
635 let has_match = target_segs.iter().any(|s| {
636 s.elements
637 .get(target_elem)
638 .and_then(|e| e.get(target_comp))
639 .map(|v| v.as_str())
640 .is_some_and(|v| source_vals.contains(&v))
641 });
642 ConditionResult::from(has_match)
643 }
644
645 #[allow(clippy::too_many_arguments)]
654 pub fn any_group_has_co_occurrence(
655 &self,
656 tag_a: &str,
657 elem_a: usize,
658 quals_a: &[&str],
659 tag_b: &str,
660 elem_b: usize,
661 comp_b: usize,
662 vals_b: &[&str],
663 group_path: &[&str],
664 ) -> ConditionResult {
665 let instance_count = self.group_instance_count(group_path);
666 if instance_count > 0 {
667 for i in 0..instance_count {
668 let a_present = self
669 .find_segments_in_group(tag_a, group_path, i)
670 .iter()
671 .any(|seg| {
672 seg.elements
673 .get(elem_a)
674 .and_then(|e| e.first())
675 .map(|s| s.as_str())
676 .is_some_and(|v| quals_a.contains(&v))
677 });
678 let b_present = self
679 .find_segments_in_group(tag_b, group_path, i)
680 .iter()
681 .any(|seg| {
682 seg.elements
683 .get(elem_b)
684 .and_then(|e| e.get(comp_b))
685 .map(|s| s.as_str())
686 .is_some_and(|v| vals_b.contains(&v))
687 });
688 if a_present && b_present {
689 return ConditionResult::True;
690 }
691 }
692 return ConditionResult::False;
693 }
694 let a_found = self.find_segments(tag_a).iter().any(|seg| {
696 seg.elements
697 .get(elem_a)
698 .and_then(|e| e.first())
699 .map(|s| s.as_str())
700 .is_some_and(|v| quals_a.contains(&v))
701 });
702 if !a_found {
703 return ConditionResult::False;
704 }
705 let b_found = self.find_segments(tag_b).iter().any(|seg| {
706 seg.elements
707 .get(elem_b)
708 .and_then(|e| e.get(comp_b))
709 .map(|s| s.as_str())
710 .is_some_and(|v| vals_b.contains(&v))
711 });
712 ConditionResult::from(b_found)
713 }
714
715 pub fn has_segment_matching(
725 &self,
726 tag: &str,
727 checks: &[(usize, usize, &str)],
728 ) -> ConditionResult {
729 let segments = self.find_segments(tag);
730 if segments.is_empty() {
731 return ConditionResult::Unknown;
732 }
733 let found = segments.iter().any(|seg| {
734 checks.iter().all(|(elem, comp, val)| {
735 seg.elements
736 .get(*elem)
737 .and_then(|e| e.get(*comp))
738 .is_some_and(|v| v == val)
739 })
740 });
741 ConditionResult::from(found)
742 }
743
744 pub fn has_segment_matching_in_group(
749 &self,
750 tag: &str,
751 checks: &[(usize, usize, &str)],
752 group_path: &[&str],
753 ) -> ConditionResult {
754 let instance_count = self.group_instance_count(group_path);
755 if instance_count > 0 {
756 for i in 0..instance_count {
757 let segs = self.find_segments_in_group(tag, group_path, i);
758 if segs.iter().any(|seg| {
759 checks.iter().all(|(elem, comp, val)| {
760 seg.elements
761 .get(*elem)
762 .and_then(|e| e.get(*comp))
763 .is_some_and(|v| v == val)
764 })
765 }) {
766 return ConditionResult::True;
767 }
768 }
769 return ConditionResult::False;
770 }
771 self.has_segment_matching(tag, checks)
773 }
774
775 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
782 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
783 match segs.first() {
784 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
785 Some(val) => ConditionResult::from(val.as_str() >= threshold),
786 None => ConditionResult::Unknown,
787 },
788 None => ConditionResult::Unknown,
789 }
790 }
791
792 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
794 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
795 match segs.first() {
796 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
797 Some(val) => ConditionResult::from((val.as_str()) < threshold),
798 None => ConditionResult::Unknown,
799 },
800 None => ConditionResult::Unknown,
801 }
802 }
803
804 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
806 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
807 match segs.first() {
808 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
809 Some(val) => ConditionResult::from(val.as_str() <= threshold),
810 None => ConditionResult::Unknown,
811 },
812 None => ConditionResult::Unknown,
813 }
814 }
815
816 pub fn count_qualified_in_group(
823 &self,
824 tag: &str,
825 element_index: usize,
826 qualifier: &str,
827 group_path: &[&str],
828 ) -> usize {
829 let instance_count = self.group_instance_count(group_path);
830 if instance_count > 0 {
831 let mut total = 0;
832 for i in 0..instance_count {
833 total += self
834 .find_segments_with_qualifier_in_group(
835 tag,
836 element_index,
837 qualifier,
838 group_path,
839 i,
840 )
841 .len();
842 }
843 return total;
844 }
845 self.find_segments_with_qualifier(tag, element_index, qualifier)
847 .len()
848 }
849
850 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
852 let instance_count = self.group_instance_count(group_path);
853 if instance_count > 0 {
854 let mut total = 0;
855 for i in 0..instance_count {
856 total += self.find_segments_in_group(tag, group_path, i).len();
857 }
858 return total;
859 }
860 self.find_segments(tag).len()
862 }
863}
864
865#[cfg(test)]
866mod tests {
867 use super::super::evaluator::NoOpExternalProvider;
868 use super::*;
869 use mig_types::navigator::GroupNavigator;
870
871 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
872 OwnedSegment {
873 id: id.to_string(),
874 elements: elements
875 .into_iter()
876 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
877 .collect(),
878 segment_number: 0,
879 }
880 }
881
882 struct MockGroupNavigator {
884 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
885 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
887 }
888
889 impl MockGroupNavigator {
890 fn new() -> Self {
891 Self {
892 groups: vec![],
893 children: vec![],
894 }
895 }
896 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
897 self.groups
898 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
899 self
900 }
901 fn with_child_group(
902 mut self,
903 parent_path: &[&str],
904 parent_instance: usize,
905 child_id: &str,
906 child_instance: usize,
907 segs: Vec<OwnedSegment>,
908 ) -> Self {
909 self.children.push((
910 parent_path.iter().map(|s| s.to_string()).collect(),
911 parent_instance,
912 child_id.to_string(),
913 child_instance,
914 segs,
915 ));
916 self
917 }
918 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
919 self.groups
920 .iter()
921 .find(|(p, i, _)| {
922 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
923 ps.as_slice() == group_path && *i == idx
924 })
925 .map(|(_, _, segs)| segs.as_slice())
926 }
927 }
928
929 impl GroupNavigator for MockGroupNavigator {
930 fn find_segments_in_group(
931 &self,
932 segment_id: &str,
933 group_path: &[&str],
934 instance_index: usize,
935 ) -> Vec<OwnedSegment> {
936 self.find_instance(group_path, instance_index)
937 .map(|segs| {
938 segs.iter()
939 .filter(|s| s.id == segment_id)
940 .cloned()
941 .collect()
942 })
943 .unwrap_or_default()
944 }
945 fn find_segments_with_qualifier_in_group(
946 &self,
947 segment_id: &str,
948 element_index: usize,
949 qualifier: &str,
950 group_path: &[&str],
951 instance_index: usize,
952 ) -> Vec<OwnedSegment> {
953 self.find_segments_in_group(segment_id, group_path, instance_index)
954 .into_iter()
955 .filter(|s| {
956 s.elements
957 .get(element_index)
958 .and_then(|e| e.first())
959 .is_some_and(|v| v == qualifier)
960 })
961 .collect()
962 }
963 fn group_instance_count(&self, group_path: &[&str]) -> usize {
964 self.groups
965 .iter()
966 .filter(|(p, _, _)| {
967 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
968 ps.as_slice() == group_path
969 })
970 .count()
971 }
972 fn child_group_instance_count(
973 &self,
974 parent_path: &[&str],
975 parent_instance: usize,
976 child_group_id: &str,
977 ) -> usize {
978 self.children
979 .iter()
980 .filter(|(pp, pi, cid, _, _)| {
981 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
982 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
983 })
984 .count()
985 }
986 fn find_segments_in_child_group(
987 &self,
988 segment_id: &str,
989 parent_path: &[&str],
990 parent_instance: usize,
991 child_group_id: &str,
992 child_instance: usize,
993 ) -> Vec<OwnedSegment> {
994 self.children
995 .iter()
996 .find(|(pp, pi, cid, ci, _)| {
997 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
998 ps.as_slice() == parent_path
999 && *pi == parent_instance
1000 && cid == child_group_id
1001 && *ci == child_instance
1002 })
1003 .map(|(_, _, _, _, segs)| {
1004 segs.iter()
1005 .filter(|s| s.id == segment_id)
1006 .cloned()
1007 .collect()
1008 })
1009 .unwrap_or_default()
1010 }
1011 fn extract_value_in_group(
1012 &self,
1013 segment_id: &str,
1014 element_index: usize,
1015 component_index: usize,
1016 group_path: &[&str],
1017 instance_index: usize,
1018 ) -> Option<String> {
1019 let segs = self.find_instance(group_path, instance_index)?;
1020 let seg = segs.iter().find(|s| s.id == segment_id)?;
1021 seg.elements
1022 .get(element_index)?
1023 .get(component_index)
1024 .cloned()
1025 }
1026 }
1027
1028 #[test]
1029 fn test_find_segment() {
1030 let segments = vec![
1031 make_segment("UNH", vec![vec!["test"]]),
1032 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1033 ];
1034 let external = NoOpExternalProvider;
1035 let ctx = EvaluationContext::new("11001", &external, &segments);
1036
1037 assert!(ctx.find_segment("NAD").is_some());
1038 assert!(ctx.find_segment("DTM").is_none());
1039 }
1040
1041 #[test]
1042 fn test_find_segments_with_qualifier() {
1043 let segments = vec![
1044 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1045 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1046 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1047 ];
1048 let external = NoOpExternalProvider;
1049 let ctx = EvaluationContext::new("11001", &external, &segments);
1050
1051 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1052 assert_eq!(ms_nads.len(), 2);
1053 }
1054
1055 #[test]
1056 fn test_has_segment() {
1057 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1058 let external = NoOpExternalProvider;
1059 let ctx = EvaluationContext::new("11001", &external, &segments);
1060
1061 assert!(ctx.has_segment("UNH"));
1062 assert!(!ctx.has_segment("NAD"));
1063 }
1064
1065 #[test]
1068 fn test_no_navigator_group_find_returns_empty() {
1069 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1070 let external = NoOpExternalProvider;
1071 let ctx = EvaluationContext::new("55001", &external, &segments);
1072 assert!(ctx
1073 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1074 .is_empty());
1075 }
1076
1077 #[test]
1078 fn test_no_navigator_group_instance_count_zero() {
1079 let external = NoOpExternalProvider;
1080 let ctx = EvaluationContext::new("55001", &external, &[]);
1081 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1082 }
1083
1084 #[test]
1085 fn test_with_navigator_finds_segments_in_group() {
1086 let external = NoOpExternalProvider;
1087 let nav = MockGroupNavigator::new().with_group(
1088 &["SG4", "SG8"],
1089 0,
1090 vec![
1091 make_segment("SEQ", vec![vec!["Z98"]]),
1092 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1093 ],
1094 );
1095 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1096 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1097 assert_eq!(result.len(), 1);
1098 assert_eq!(result[0].id, "SEQ");
1099 }
1100
1101 #[test]
1102 fn test_with_navigator_qualifier_in_group() {
1103 let external = NoOpExternalProvider;
1104 let nav = MockGroupNavigator::new().with_group(
1105 &["SG4", "SG8"],
1106 0,
1107 vec![
1108 make_segment("SEQ", vec![vec!["Z98"]]),
1109 make_segment("SEQ", vec![vec!["Z01"]]),
1110 ],
1111 );
1112 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1113 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1114 assert_eq!(result.len(), 1);
1115 }
1116
1117 #[test]
1118 fn test_group_instance_count_with_navigator() {
1119 let external = NoOpExternalProvider;
1120 let nav = MockGroupNavigator::new()
1121 .with_group(
1122 &["SG4", "SG8"],
1123 0,
1124 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1125 )
1126 .with_group(
1127 &["SG4", "SG8"],
1128 1,
1129 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1130 );
1131 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1132 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1133 }
1134
1135 #[test]
1136 fn test_has_segment_in_group() {
1137 let external = NoOpExternalProvider;
1138 let nav = MockGroupNavigator::new().with_group(
1139 &["SG4", "SG8"],
1140 0,
1141 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1142 );
1143 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1144 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1145 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1146 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1147 }
1148
1149 #[test]
1152 fn test_has_qualifier() {
1153 let segments = vec![
1154 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1155 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1156 ];
1157 let external = NoOpExternalProvider;
1158 let ctx = EvaluationContext::new("11001", &external, &segments);
1159
1160 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1161 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1162 }
1163
1164 #[test]
1165 fn test_lacks_qualifier() {
1166 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1167 let external = NoOpExternalProvider;
1168 let ctx = EvaluationContext::new("11001", &external, &segments);
1169
1170 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1171 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1172 }
1173
1174 #[test]
1175 fn test_has_qualified_value() {
1176 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1177 let external = NoOpExternalProvider;
1178 let ctx = EvaluationContext::new("55001", &external, &segments);
1179
1180 assert_eq!(
1181 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1182 ConditionResult::True,
1183 );
1184 assert_eq!(
1185 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1186 ConditionResult::False,
1187 );
1188 assert_eq!(
1190 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1191 ConditionResult::Unknown,
1192 );
1193 }
1194
1195 #[test]
1196 fn test_any_group_has_qualifier() {
1197 let external = NoOpExternalProvider;
1198 let nav = MockGroupNavigator::new()
1199 .with_group(
1200 &["SG4", "SG8"],
1201 0,
1202 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1203 )
1204 .with_group(
1205 &["SG4", "SG8"],
1206 1,
1207 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1208 );
1209 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1210
1211 assert_eq!(
1212 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1213 ConditionResult::True,
1214 );
1215 assert_eq!(
1216 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1217 ConditionResult::False,
1218 );
1219 }
1220
1221 #[test]
1222 fn test_any_group_has_qualifier_fallback() {
1223 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1225 let external = NoOpExternalProvider;
1226 let ctx = EvaluationContext::new("55001", &external, &segments);
1227
1228 assert_eq!(
1229 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1230 ConditionResult::True,
1231 );
1232 }
1233
1234 #[test]
1235 fn test_any_group_has_any_qualifier() {
1236 let external = NoOpExternalProvider;
1237 let nav = MockGroupNavigator::new().with_group(
1238 &["SG4", "SG8"],
1239 0,
1240 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1241 );
1242 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1243
1244 assert_eq!(
1245 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1246 ConditionResult::True,
1247 );
1248 assert_eq!(
1249 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1250 ConditionResult::False,
1251 );
1252 }
1253
1254 #[test]
1255 fn test_any_group_has_co_occurrence() {
1256 let external = NoOpExternalProvider;
1257 let nav = MockGroupNavigator::new().with_group(
1258 &["SG4", "SG8"],
1259 0,
1260 vec![
1261 make_segment("SEQ", vec![vec!["Z01"]]),
1262 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1263 ],
1264 );
1265 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1266
1267 assert_eq!(
1268 ctx.any_group_has_co_occurrence(
1269 "SEQ",
1270 0,
1271 &["Z01"],
1272 "CCI",
1273 2,
1274 0,
1275 &["Z07"],
1276 &["SG4", "SG8"],
1277 ),
1278 ConditionResult::True,
1279 );
1280 assert_eq!(
1282 ctx.any_group_has_co_occurrence(
1283 "SEQ",
1284 0,
1285 &["Z01"],
1286 "CCI",
1287 2,
1288 0,
1289 &["ZC0"],
1290 &["SG4", "SG8"],
1291 ),
1292 ConditionResult::False,
1293 );
1294 }
1295
1296 #[test]
1299 fn test_filtered_parent_child_has_qualifier() {
1300 let external = NoOpExternalProvider;
1301 let nav = MockGroupNavigator::new()
1304 .with_group(
1305 &["SG4", "SG8"],
1306 0,
1307 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1308 )
1309 .with_group(
1310 &["SG4", "SG8"],
1311 1,
1312 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1313 )
1314 .with_child_group(
1315 &["SG4", "SG8"],
1316 0,
1317 "SG10",
1318 0,
1319 vec![make_segment("CCI", vec![vec!["Z23"]])],
1320 );
1321 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1322
1323 assert_eq!(
1325 ctx.filtered_parent_child_has_qualifier(
1326 &["SG4", "SG8"],
1327 "SEQ",
1328 0,
1329 "Z98",
1330 "SG10",
1331 "CCI",
1332 0,
1333 "Z23",
1334 ),
1335 ConditionResult::True,
1336 );
1337 assert_eq!(
1339 ctx.filtered_parent_child_has_qualifier(
1340 &["SG4", "SG8"],
1341 "SEQ",
1342 0,
1343 "Z01",
1344 "SG10",
1345 "CCI",
1346 0,
1347 "Z23",
1348 ),
1349 ConditionResult::False,
1350 );
1351 assert_eq!(
1353 ctx.filtered_parent_child_has_qualifier(
1354 &["SG4", "SG8"],
1355 "SEQ",
1356 0,
1357 "Z98",
1358 "SG10",
1359 "CCI",
1360 0,
1361 "Z99",
1362 ),
1363 ConditionResult::False,
1364 );
1365 }
1366
1367 #[test]
1368 fn test_filtered_parent_child_fallback() {
1369 let segments = vec![
1371 make_segment("SEQ", vec![vec!["Z98"]]),
1372 make_segment("CCI", vec![vec!["Z23"]]),
1373 ];
1374 let external = NoOpExternalProvider;
1375 let ctx = EvaluationContext::new("55001", &external, &segments);
1376
1377 assert_eq!(
1378 ctx.filtered_parent_child_has_qualifier(
1379 &["SG4", "SG8"],
1380 "SEQ",
1381 0,
1382 "Z98",
1383 "SG10",
1384 "CCI",
1385 0,
1386 "Z23",
1387 ),
1388 ConditionResult::True,
1389 );
1390 assert_eq!(
1392 ctx.filtered_parent_child_has_qualifier(
1393 &["SG4", "SG8"],
1394 "SEQ",
1395 0,
1396 "Z98",
1397 "SG10",
1398 "CCI",
1399 0,
1400 "Z99",
1401 ),
1402 ConditionResult::False,
1403 );
1404 }
1405
1406 #[test]
1407 fn test_any_group_has_qualifier_without() {
1408 let external = NoOpExternalProvider;
1409 let nav = MockGroupNavigator::new()
1412 .with_group(
1413 &["SG4", "SG8"],
1414 0,
1415 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1416 )
1417 .with_group(
1418 &["SG4", "SG8"],
1419 1,
1420 vec![
1421 make_segment("SEQ", vec![vec!["Z01"]]),
1422 make_segment("CCI", vec![vec!["11"]]),
1423 ],
1424 );
1425 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1426
1427 assert_eq!(
1429 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1430 ConditionResult::True,
1431 );
1432 assert_eq!(
1434 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
1435 ConditionResult::False,
1436 );
1437 assert_eq!(
1439 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
1440 ConditionResult::False,
1441 );
1442 }
1443
1444 #[test]
1445 fn test_any_group_has_qualifier_without_fallback() {
1446 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
1447 let external = NoOpExternalProvider;
1448 let ctx = EvaluationContext::new("55001", &external, &segments);
1449
1450 assert_eq!(
1452 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1453 ConditionResult::True,
1454 );
1455 }
1456
1457 #[test]
1458 fn test_collect_group_values() {
1459 let external = NoOpExternalProvider;
1460 let nav = MockGroupNavigator::new()
1461 .with_group(
1462 &["SG4", "SG6"],
1463 0,
1464 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
1465 )
1466 .with_group(
1467 &["SG4", "SG6"],
1468 1,
1469 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
1470 );
1471 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1472
1473 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
1474 assert_eq!(values.len(), 2);
1475 assert_eq!(values[0], (0, "REF001".to_string()));
1476 assert_eq!(values[1], (1, "REF002".to_string()));
1477 }
1478
1479 #[test]
1480 fn test_groups_share_qualified_value() {
1481 let external = NoOpExternalProvider;
1482 let nav = MockGroupNavigator::new()
1486 .with_group(
1487 &["SG4", "SG6"],
1488 0,
1489 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1490 )
1491 .with_group(
1492 &["SG4", "SG8"],
1493 0,
1494 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1495 )
1496 .with_group(
1497 &["SG4", "SG8"],
1498 1,
1499 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
1500 );
1501 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1502
1503 assert_eq!(
1505 ctx.groups_share_qualified_value(
1506 "RFF",
1507 0,
1508 "Z49",
1509 0,
1510 1,
1511 &["SG4", "SG6"],
1512 "SEQ",
1513 1,
1514 0,
1515 &["SG4", "SG8"],
1516 ),
1517 ConditionResult::True,
1518 );
1519 }
1520
1521 #[test]
1522 fn test_groups_share_qualified_value_no_match() {
1523 let external = NoOpExternalProvider;
1524 let nav = MockGroupNavigator::new()
1525 .with_group(
1526 &["SG4", "SG6"],
1527 0,
1528 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1529 )
1530 .with_group(
1531 &["SG4", "SG8"],
1532 0,
1533 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
1534 );
1535 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1536
1537 assert_eq!(
1539 ctx.groups_share_qualified_value(
1540 "RFF",
1541 0,
1542 "Z49",
1543 0,
1544 1,
1545 &["SG4", "SG6"],
1546 "SEQ",
1547 1,
1548 0,
1549 &["SG4", "SG8"],
1550 ),
1551 ConditionResult::False,
1552 );
1553 }
1554
1555 #[test]
1556 fn test_groups_share_qualified_value_no_source() {
1557 let external = NoOpExternalProvider;
1558 let nav = MockGroupNavigator::new()
1560 .with_group(
1561 &["SG4", "SG6"],
1562 0,
1563 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
1564 )
1565 .with_group(
1566 &["SG4", "SG8"],
1567 0,
1568 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1569 );
1570 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1571
1572 assert_eq!(
1574 ctx.groups_share_qualified_value(
1575 "RFF",
1576 0,
1577 "Z49",
1578 0,
1579 1,
1580 &["SG4", "SG6"],
1581 "SEQ",
1582 1,
1583 0,
1584 &["SG4", "SG8"],
1585 ),
1586 ConditionResult::Unknown,
1587 );
1588 }
1589
1590 #[test]
1591 fn test_groups_share_qualified_value_fallback() {
1592 let segments = vec![
1594 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
1595 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
1596 ];
1597 let external = NoOpExternalProvider;
1598 let ctx = EvaluationContext::new("55001", &external, &segments);
1599
1600 assert_eq!(
1601 ctx.groups_share_qualified_value(
1602 "RFF",
1603 0,
1604 "Z49",
1605 0,
1606 1,
1607 &["SG4", "SG6"],
1608 "SEQ",
1609 1,
1610 0,
1611 &["SG4", "SG8"],
1612 ),
1613 ConditionResult::True,
1614 );
1615 }
1616
1617 #[test]
1618 fn test_child_group_pass_throughs_no_navigator() {
1619 let external = NoOpExternalProvider;
1620 let ctx = EvaluationContext::new("55001", &external, &[]);
1621
1622 assert_eq!(
1623 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
1624 0
1625 );
1626 assert!(ctx
1627 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
1628 .is_empty());
1629 assert_eq!(
1630 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
1631 None
1632 );
1633 }
1634
1635 #[test]
1638 fn test_has_segment_matching_found() {
1639 let segments = vec![
1640 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
1641 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
1642 ];
1643 let external = NoOpExternalProvider;
1644 let ctx = EvaluationContext::new("55001", &external, &segments);
1645
1646 assert_eq!(
1647 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
1648 ConditionResult::True,
1649 );
1650 }
1651
1652 #[test]
1653 fn test_has_segment_matching_not_found() {
1654 let segments = vec![make_segment(
1655 "STS",
1656 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
1657 )];
1658 let external = NoOpExternalProvider;
1659 let ctx = EvaluationContext::new("55001", &external, &segments);
1660
1661 assert_eq!(
1662 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
1663 ConditionResult::False,
1664 );
1665 }
1666
1667 #[test]
1668 fn test_has_segment_matching_no_segments() {
1669 let external = NoOpExternalProvider;
1670 let ctx = EvaluationContext::new("55001", &external, &[]);
1671
1672 assert_eq!(
1673 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
1674 ConditionResult::Unknown,
1675 );
1676 }
1677
1678 #[test]
1679 fn test_has_segment_matching_in_group() {
1680 let nav = MockGroupNavigator::new()
1681 .with_group(
1682 &["SG4"],
1683 0,
1684 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
1685 )
1686 .with_group(
1687 &["SG4"],
1688 1,
1689 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
1690 );
1691 let segments = vec![
1692 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
1693 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
1694 ];
1695 let external = NoOpExternalProvider;
1696 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1697
1698 assert_eq!(
1699 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
1700 ConditionResult::True,
1701 );
1702 }
1703
1704 #[test]
1707 fn test_dtm_ge() {
1708 let segments = vec![make_segment(
1709 "DTM",
1710 vec![vec!["137", "202601010000", "303"]],
1711 )];
1712 let external = NoOpExternalProvider;
1713 let ctx = EvaluationContext::new("55001", &external, &segments);
1714
1715 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
1716 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
1717 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
1718 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
1719 }
1720
1721 #[test]
1722 fn test_dtm_lt() {
1723 let segments = vec![make_segment(
1724 "DTM",
1725 vec![vec!["137", "202601010000", "303"]],
1726 )];
1727 let external = NoOpExternalProvider;
1728 let ctx = EvaluationContext::new("55001", &external, &segments);
1729
1730 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
1731 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
1732 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
1733 }
1734
1735 #[test]
1736 fn test_dtm_le() {
1737 let segments = vec![make_segment(
1738 "DTM",
1739 vec![vec!["137", "202601010000", "303"]],
1740 )];
1741 let external = NoOpExternalProvider;
1742 let ctx = EvaluationContext::new("55001", &external, &segments);
1743
1744 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
1745 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
1746 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
1747 }
1748
1749 #[test]
1752 fn test_count_qualified_in_group() {
1753 let nav = MockGroupNavigator::new()
1754 .with_group(
1755 &["SG4", "SG8"],
1756 0,
1757 vec![
1758 make_segment("CCI", vec![vec!["Z23"]]),
1759 make_segment("CCI", vec![vec!["Z30"]]),
1760 ],
1761 )
1762 .with_group(
1763 &["SG4", "SG8"],
1764 1,
1765 vec![make_segment("CCI", vec![vec!["Z23"]])],
1766 );
1767 let segments = vec![
1768 make_segment("CCI", vec![vec!["Z23"]]),
1769 make_segment("CCI", vec![vec!["Z30"]]),
1770 make_segment("CCI", vec![vec!["Z23"]]),
1771 ];
1772 let external = NoOpExternalProvider;
1773 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1774
1775 assert_eq!(
1776 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1777 2
1778 );
1779 assert_eq!(
1780 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
1781 1
1782 );
1783 assert_eq!(
1784 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
1785 0
1786 );
1787 }
1788
1789 #[test]
1790 fn test_count_in_group() {
1791 let nav = MockGroupNavigator::new()
1792 .with_group(
1793 &["SG4", "SG8"],
1794 0,
1795 vec![
1796 make_segment("SEQ", vec![vec!["Z98"]]),
1797 make_segment("CCI", vec![vec!["Z23"]]),
1798 ],
1799 )
1800 .with_group(
1801 &["SG4", "SG8"],
1802 1,
1803 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1804 );
1805 let segments = vec![
1806 make_segment("SEQ", vec![vec!["Z98"]]),
1807 make_segment("CCI", vec![vec!["Z23"]]),
1808 make_segment("SEQ", vec![vec!["Z01"]]),
1809 ];
1810 let external = NoOpExternalProvider;
1811 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1812
1813 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
1814 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
1815 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
1816 }
1817
1818 #[test]
1819 fn test_count_fallback_no_navigator() {
1820 let segments = vec![
1821 make_segment("CCI", vec![vec!["Z23"]]),
1822 make_segment("CCI", vec![vec!["Z30"]]),
1823 make_segment("CCI", vec![vec!["Z23"]]),
1824 ];
1825 let external = NoOpExternalProvider;
1826 let ctx = EvaluationContext::new("55001", &external, &segments);
1827
1828 assert_eq!(
1830 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1831 2
1832 );
1833 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
1834 }
1835}