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 pub resolved_value: Option<&'a str>,
35
36 pub resolved_segment: Option<&'a [Vec<String>]>,
42}
43
44pub struct NoOpGroupNavigator;
46
47impl GroupNavigator for NoOpGroupNavigator {
48 fn find_segments_in_group(&self, _: &str, _: &[&str], _: usize) -> Vec<OwnedSegment> {
49 Vec::new()
50 }
51 fn find_segments_with_qualifier_in_group(
52 &self,
53 _: &str,
54 _: usize,
55 _: &str,
56 _: &[&str],
57 _: usize,
58 ) -> Vec<OwnedSegment> {
59 Vec::new()
60 }
61 fn group_instance_count(&self, _: &[&str]) -> usize {
62 0
63 }
64}
65
66impl<'a> EvaluationContext<'a> {
67 pub fn new(
69 pruefidentifikator: &'a str,
70 external: &'a dyn ExternalConditionProvider,
71 segments: &'a [OwnedSegment],
72 ) -> Self {
73 Self {
74 pruefidentifikator,
75 external,
76 segments,
77 navigator: None,
78 resolved_value: None,
79 resolved_segment: None,
80 }
81 }
82
83 pub fn with_navigator(
85 pruefidentifikator: &'a str,
86 external: &'a dyn ExternalConditionProvider,
87 segments: &'a [OwnedSegment],
88 navigator: &'a dyn GroupNavigator,
89 ) -> Self {
90 Self {
91 pruefidentifikator,
92 external,
93 segments,
94 navigator: Some(navigator),
95 resolved_value: None,
96 resolved_segment: None,
97 }
98 }
99
100 pub fn with_resolved(
105 &self,
106 value: Option<&'a str>,
107 segment: Option<&'a [Vec<String>]>,
108 ) -> Self {
109 Self {
110 resolved_value: value,
111 resolved_segment: segment,
112 ..*self
113 }
114 }
115
116 pub fn navigator(&self) -> Option<&'a dyn GroupNavigator> {
118 self.navigator
119 }
120
121 pub fn find_segment(&self, segment_id: &str) -> Option<&'a OwnedSegment> {
123 self.segments.iter().find(|s| s.id == segment_id)
124 }
125
126 pub fn find_segments(&self, segment_id: &str) -> Vec<&'a OwnedSegment> {
128 self.segments
129 .iter()
130 .filter(|s| s.id == segment_id)
131 .collect()
132 }
133
134 pub fn find_segments_with_qualifier(
136 &self,
137 segment_id: &str,
138 element_index: usize,
139 qualifier: &str,
140 ) -> Vec<&'a OwnedSegment> {
141 self.segments
142 .iter()
143 .filter(|s| {
144 s.id == segment_id
145 && s.elements
146 .get(element_index)
147 .and_then(|e| e.first())
148 .is_some_and(|v| v == qualifier)
149 })
150 .collect()
151 }
152
153 pub fn format_check(
163 &self,
164 tag: &str,
165 elem: usize,
166 comp: usize,
167 validate: impl FnOnce(&str) -> ConditionResult,
168 ) -> ConditionResult {
169 if let Some(val) = self.resolved_value {
171 return validate(val);
172 }
173 let segs = self.find_segments(tag);
178 if segs.len() != 1 {
179 return ConditionResult::Unknown;
180 }
181 match segs[0]
182 .elements
183 .get(elem)
184 .and_then(|e| e.get(comp))
185 .map(|s| s.as_str())
186 {
187 Some(val) => validate(val),
188 None => ConditionResult::Unknown,
189 }
190 }
191
192 pub fn format_check_qualified(
201 &self,
202 tag: &str,
203 qual_elem: usize,
204 qualifier: &str,
205 val_elem: usize,
206 val_comp: usize,
207 validate: impl FnOnce(&str) -> ConditionResult,
208 ) -> ConditionResult {
209 if let Some(val) = self.resolved_value {
210 return validate(val);
211 }
212 let segs = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
213 if segs.len() != 1 {
214 return ConditionResult::Unknown;
215 }
216 match segs[0]
217 .elements
218 .get(val_elem)
219 .and_then(|e| e.get(val_comp))
220 .map(|s| s.as_str())
221 {
222 Some(val) => validate(val),
223 None => ConditionResult::Unknown,
224 }
225 }
226
227 pub fn self_segment_value(
239 &self,
240 tag: &str,
241 elem: usize,
242 comp: usize,
243 ) -> Option<&str> {
244 if let Some(elements) = self.resolved_segment {
245 return elements
246 .get(elem)
247 .and_then(|e| e.get(comp))
248 .map(|s| s.as_str());
249 }
250 self.segments
251 .iter()
252 .find(|s| s.id == tag)
253 .and_then(|s| s.elements.get(elem))
254 .and_then(|e| e.get(comp))
255 .map(|s| s.as_str())
256 }
257
258 pub fn self_segment_value_equals(
262 &self,
263 tag: &str,
264 elem: usize,
265 comp: usize,
266 expected: &str,
267 ) -> bool {
268 self.self_segment_value(tag, elem, comp)
269 .is_some_and(|v| v == expected)
270 }
271
272 pub fn has_segment(&self, segment_id: &str) -> bool {
274 self.segments.iter().any(|s| s.id == segment_id)
275 }
276
277 pub fn find_segments_in_group(
280 &self,
281 segment_id: &str,
282 group_path: &[&str],
283 instance_index: usize,
284 ) -> Vec<OwnedSegment> {
285 match self.navigator {
286 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
287 None => Vec::new(),
288 }
289 }
290
291 pub fn find_segments_with_qualifier_in_group(
294 &self,
295 segment_id: &str,
296 element_index: usize,
297 qualifier: &str,
298 group_path: &[&str],
299 instance_index: usize,
300 ) -> Vec<OwnedSegment> {
301 match self.navigator {
302 Some(nav) => nav.find_segments_with_qualifier_in_group(
303 segment_id,
304 element_index,
305 qualifier,
306 group_path,
307 instance_index,
308 ),
309 None => Vec::new(),
310 }
311 }
312
313 pub fn has_segment_in_group(
316 &self,
317 segment_id: &str,
318 group_path: &[&str],
319 instance_index: usize,
320 ) -> bool {
321 !self
322 .find_segments_in_group(segment_id, group_path, instance_index)
323 .is_empty()
324 }
325
326 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
329 match self.navigator {
330 Some(nav) => nav.group_instance_count(group_path),
331 None => 0,
332 }
333 }
334
335 pub fn child_group_instance_count(
338 &self,
339 parent_path: &[&str],
340 parent_instance: usize,
341 child_group_id: &str,
342 ) -> usize {
343 match self.navigator {
344 Some(nav) => {
345 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
346 }
347 None => 0,
348 }
349 }
350
351 pub fn find_segments_in_child_group(
354 &self,
355 segment_id: &str,
356 parent_path: &[&str],
357 parent_instance: usize,
358 child_group_id: &str,
359 child_instance: usize,
360 ) -> Vec<OwnedSegment> {
361 match self.navigator {
362 Some(nav) => nav.find_segments_in_child_group(
363 segment_id,
364 parent_path,
365 parent_instance,
366 child_group_id,
367 child_instance,
368 ),
369 None => Vec::new(),
370 }
371 }
372
373 pub fn extract_value_in_group(
376 &self,
377 segment_id: &str,
378 element_index: usize,
379 component_index: usize,
380 group_path: &[&str],
381 instance_index: usize,
382 ) -> Option<String> {
383 self.navigator?.extract_value_in_group(
384 segment_id,
385 element_index,
386 component_index,
387 group_path,
388 instance_index,
389 )
390 }
391
392 pub fn has_qualifier(
398 &self,
399 tag: &str,
400 element_index: usize,
401 qualifier: &str,
402 ) -> ConditionResult {
403 ConditionResult::from(
404 !self
405 .find_segments_with_qualifier(tag, element_index, qualifier)
406 .is_empty(),
407 )
408 }
409
410 pub fn lacks_qualifier(
413 &self,
414 tag: &str,
415 element_index: usize,
416 qualifier: &str,
417 ) -> ConditionResult {
418 ConditionResult::from(
419 self.find_segments_with_qualifier(tag, element_index, qualifier)
420 .is_empty(),
421 )
422 }
423
424 pub fn has_qualified_value(
429 &self,
430 tag: &str,
431 qual_elem: usize,
432 qualifier: &str,
433 value_elem: usize,
434 value_comp: usize,
435 values: &[&str],
436 ) -> ConditionResult {
437 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
438 if segments.is_empty() {
439 return ConditionResult::Unknown;
440 }
441 for seg in &segments {
442 if let Some(v) = seg
443 .elements
444 .get(value_elem)
445 .and_then(|e| e.get(value_comp))
446 .map(|s| s.as_str())
447 {
448 if values.contains(&v) {
449 return ConditionResult::True;
450 }
451 }
452 }
453 ConditionResult::False
454 }
455
456 pub fn any_group_has_qualifier(
462 &self,
463 tag: &str,
464 element_index: usize,
465 qualifier: &str,
466 group_path: &[&str],
467 ) -> ConditionResult {
468 let instance_count = self.group_instance_count(group_path);
469 if instance_count > 0 {
470 for i in 0..instance_count {
471 if !self
472 .find_segments_with_qualifier_in_group(
473 tag,
474 element_index,
475 qualifier,
476 group_path,
477 i,
478 )
479 .is_empty()
480 {
481 return ConditionResult::True;
482 }
483 }
484 return ConditionResult::False;
485 }
486 self.has_qualifier(tag, element_index, qualifier)
488 }
489
490 pub fn any_group_has_any_qualifier(
496 &self,
497 tag: &str,
498 element_index: usize,
499 qualifiers: &[&str],
500 group_path: &[&str],
501 ) -> ConditionResult {
502 let instance_count = self.group_instance_count(group_path);
503 if instance_count > 0 {
504 for i in 0..instance_count {
505 let segs = self.find_segments_in_group(tag, group_path, i);
506 if segs.iter().any(|seg| {
507 seg.elements
508 .get(element_index)
509 .and_then(|e| e.first())
510 .map(|s| s.as_str())
511 .is_some_and(|v| qualifiers.contains(&v))
512 }) {
513 return ConditionResult::True;
514 }
515 }
516 return ConditionResult::False;
517 }
518 let found = self.find_segments(tag).iter().any(|seg| {
520 seg.elements
521 .get(element_index)
522 .and_then(|e| e.first())
523 .map(|s| s.as_str())
524 .is_some_and(|v| qualifiers.contains(&v))
525 });
526 ConditionResult::from(found)
527 }
528
529 pub fn any_group_has_qualified_value(
535 &self,
536 tag: &str,
537 qual_elem: usize,
538 qualifier: &str,
539 value_elem: usize,
540 value_comp: usize,
541 values: &[&str],
542 group_path: &[&str],
543 ) -> ConditionResult {
544 let instance_count = self.group_instance_count(group_path);
545 if instance_count > 0 {
546 for i in 0..instance_count {
547 let segs = self.find_segments_with_qualifier_in_group(
548 tag, qual_elem, qualifier, group_path, i,
549 );
550 for seg in &segs {
551 if seg
552 .elements
553 .get(value_elem)
554 .and_then(|e| e.get(value_comp))
555 .map(|s| s.as_str())
556 .is_some_and(|v| values.contains(&v))
557 {
558 return ConditionResult::True;
559 }
560 }
561 }
562 return ConditionResult::False;
563 }
564 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
566 }
567
568 #[allow(clippy::too_many_arguments)]
577 pub fn filtered_parent_child_has_qualifier(
578 &self,
579 parent_path: &[&str],
580 parent_tag: &str,
581 parent_elem: usize,
582 parent_qual: &str,
583 child_group_id: &str,
584 child_tag: &str,
585 child_elem: usize,
586 child_qual: &str,
587 ) -> ConditionResult {
588 let parent_count = self.group_instance_count(parent_path);
589 if parent_count > 0 {
590 for pi in 0..parent_count {
591 let parent_segs = self.find_segments_with_qualifier_in_group(
593 parent_tag,
594 parent_elem,
595 parent_qual,
596 parent_path,
597 pi,
598 );
599 if parent_segs.is_empty() {
600 continue;
601 }
602 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
604 for ci in 0..child_count {
605 let child_segs = self.find_segments_in_child_group(
606 child_tag,
607 parent_path,
608 pi,
609 child_group_id,
610 ci,
611 );
612 if child_segs.iter().any(|s| {
613 s.elements
614 .get(child_elem)
615 .and_then(|e| e.first())
616 .is_some_and(|v| v == child_qual)
617 }) {
618 return ConditionResult::True;
619 }
620 }
621 }
622 return ConditionResult::False;
623 }
624 let has_parent = !self
626 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
627 .is_empty();
628 let has_child = !self
629 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
630 .is_empty();
631 ConditionResult::from(has_parent && has_child)
632 }
633
634 #[allow(clippy::too_many_arguments)]
640 pub fn any_group_has_qualifier_without(
641 &self,
642 present_tag: &str,
643 present_elem: usize,
644 present_qual: &str,
645 absent_tag: &str,
646 absent_elem: usize,
647 absent_qual: &str,
648 group_path: &[&str],
649 ) -> ConditionResult {
650 let instance_count = self.group_instance_count(group_path);
651 if instance_count > 0 {
652 for i in 0..instance_count {
653 let has_present = !self
654 .find_segments_with_qualifier_in_group(
655 present_tag,
656 present_elem,
657 present_qual,
658 group_path,
659 i,
660 )
661 .is_empty();
662 let has_absent = !self
663 .find_segments_with_qualifier_in_group(
664 absent_tag,
665 absent_elem,
666 absent_qual,
667 group_path,
668 i,
669 )
670 .is_empty();
671 if has_present && !has_absent {
672 return ConditionResult::True;
673 }
674 }
675 return ConditionResult::False;
676 }
677 let has_present = !self
679 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
680 .is_empty();
681 let has_absent = !self
682 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
683 .is_empty();
684 ConditionResult::from(has_present && !has_absent)
685 }
686
687 pub fn collect_group_values(
691 &self,
692 tag: &str,
693 elem: usize,
694 comp: usize,
695 group_path: &[&str],
696 ) -> Vec<(usize, String)> {
697 let instance_count = self.group_instance_count(group_path);
698 let mut results = Vec::new();
699 for i in 0..instance_count {
700 if let Some(val) = self
701 .navigator
702 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
703 {
704 if !val.is_empty() {
705 results.push((i, val));
706 }
707 }
708 }
709 results
710 }
711
712 #[allow(clippy::too_many_arguments)]
721 pub fn groups_share_qualified_value(
722 &self,
723 source_tag: &str,
724 source_qual_elem: usize,
725 source_qual: &str,
726 source_value_elem: usize,
727 source_value_comp: usize,
728 source_path: &[&str],
729 target_tag: &str,
730 target_elem: usize,
731 target_comp: usize,
732 target_path: &[&str],
733 ) -> ConditionResult {
734 let source_count = self.group_instance_count(source_path);
735 let target_count = self.group_instance_count(target_path);
736 if source_count > 0 && target_count > 0 {
737 let mut source_values = Vec::new();
739 for si in 0..source_count {
740 let segs = self.find_segments_with_qualifier_in_group(
741 source_tag,
742 source_qual_elem,
743 source_qual,
744 source_path,
745 si,
746 );
747 for seg in &segs {
748 if let Some(val) = seg
749 .elements
750 .get(source_value_elem)
751 .and_then(|e| e.get(source_value_comp))
752 {
753 if !val.is_empty() {
754 source_values.push(val.clone());
755 }
756 }
757 }
758 }
759 if source_values.is_empty() {
760 return ConditionResult::Unknown;
761 }
762 let target_values =
764 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
765 for (_, tv) in &target_values {
766 if source_values.iter().any(|sv| sv == tv) {
767 return ConditionResult::True;
768 }
769 }
770 return ConditionResult::False;
771 }
772 let source_segs =
774 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
775 let source_vals: Vec<&str> = source_segs
776 .iter()
777 .filter_map(|s| {
778 s.elements
779 .get(source_value_elem)
780 .and_then(|e| e.get(source_value_comp))
781 .map(|v| v.as_str())
782 })
783 .filter(|v| !v.is_empty())
784 .collect();
785 if source_vals.is_empty() {
786 return ConditionResult::Unknown;
787 }
788 let target_segs = self.find_segments(target_tag);
789 let has_match = target_segs.iter().any(|s| {
790 s.elements
791 .get(target_elem)
792 .and_then(|e| e.get(target_comp))
793 .map(|v| v.as_str())
794 .is_some_and(|v| source_vals.contains(&v))
795 });
796 ConditionResult::from(has_match)
797 }
798
799 #[allow(clippy::too_many_arguments)]
808 pub fn any_group_has_co_occurrence(
809 &self,
810 tag_a: &str,
811 elem_a: usize,
812 quals_a: &[&str],
813 tag_b: &str,
814 elem_b: usize,
815 comp_b: usize,
816 vals_b: &[&str],
817 group_path: &[&str],
818 ) -> ConditionResult {
819 let instance_count = self.group_instance_count(group_path);
820 if instance_count > 0 {
821 for i in 0..instance_count {
822 let a_present = self
823 .find_segments_in_group(tag_a, group_path, i)
824 .iter()
825 .any(|seg| {
826 seg.elements
827 .get(elem_a)
828 .and_then(|e| e.first())
829 .map(|s| s.as_str())
830 .is_some_and(|v| quals_a.contains(&v))
831 });
832 let b_present = self
833 .find_segments_in_group(tag_b, group_path, i)
834 .iter()
835 .any(|seg| {
836 seg.elements
837 .get(elem_b)
838 .and_then(|e| e.get(comp_b))
839 .map(|s| s.as_str())
840 .is_some_and(|v| vals_b.contains(&v))
841 });
842 if a_present && b_present {
843 return ConditionResult::True;
844 }
845 }
846 return ConditionResult::False;
847 }
848 let a_found = self.find_segments(tag_a).iter().any(|seg| {
850 seg.elements
851 .get(elem_a)
852 .and_then(|e| e.first())
853 .map(|s| s.as_str())
854 .is_some_and(|v| quals_a.contains(&v))
855 });
856 if !a_found {
857 return ConditionResult::False;
858 }
859 let b_found = self.find_segments(tag_b).iter().any(|seg| {
860 seg.elements
861 .get(elem_b)
862 .and_then(|e| e.get(comp_b))
863 .map(|s| s.as_str())
864 .is_some_and(|v| vals_b.contains(&v))
865 });
866 ConditionResult::from(b_found)
867 }
868
869 pub fn has_segment_matching(
879 &self,
880 tag: &str,
881 checks: &[(usize, usize, &str)],
882 ) -> ConditionResult {
883 let segments = self.find_segments(tag);
884 if segments.is_empty() {
885 return ConditionResult::Unknown;
886 }
887 let found = segments.iter().any(|seg| {
888 checks.iter().all(|(elem, comp, val)| {
889 seg.elements
890 .get(*elem)
891 .and_then(|e| e.get(*comp))
892 .is_some_and(|v| v == val)
893 })
894 });
895 ConditionResult::from(found)
896 }
897
898 pub fn has_segment_matching_in_group(
903 &self,
904 tag: &str,
905 checks: &[(usize, usize, &str)],
906 group_path: &[&str],
907 ) -> ConditionResult {
908 let instance_count = self.group_instance_count(group_path);
909 if instance_count > 0 {
910 for i in 0..instance_count {
911 let segs = self.find_segments_in_group(tag, group_path, i);
912 if segs.iter().any(|seg| {
913 checks.iter().all(|(elem, comp, val)| {
914 seg.elements
915 .get(*elem)
916 .and_then(|e| e.get(*comp))
917 .is_some_and(|v| v == val)
918 })
919 }) {
920 return ConditionResult::True;
921 }
922 }
923 return ConditionResult::False;
924 }
925 self.has_segment_matching(tag, checks)
927 }
928
929 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
936 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
937 match segs.first() {
938 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
939 Some(val) => ConditionResult::from(val.as_str() >= threshold),
940 None => ConditionResult::Unknown,
941 },
942 None => ConditionResult::Unknown,
943 }
944 }
945
946 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
948 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
949 match segs.first() {
950 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
951 Some(val) => ConditionResult::from((val.as_str()) < threshold),
952 None => ConditionResult::Unknown,
953 },
954 None => ConditionResult::Unknown,
955 }
956 }
957
958 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
960 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
961 match segs.first() {
962 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
963 Some(val) => ConditionResult::from(val.as_str() <= threshold),
964 None => ConditionResult::Unknown,
965 },
966 None => ConditionResult::Unknown,
967 }
968 }
969
970 pub fn count_qualified_in_group(
977 &self,
978 tag: &str,
979 element_index: usize,
980 qualifier: &str,
981 group_path: &[&str],
982 ) -> usize {
983 let instance_count = self.group_instance_count(group_path);
984 if instance_count > 0 {
985 let mut total = 0;
986 for i in 0..instance_count {
987 total += self
988 .find_segments_with_qualifier_in_group(
989 tag,
990 element_index,
991 qualifier,
992 group_path,
993 i,
994 )
995 .len();
996 }
997 return total;
998 }
999 self.find_segments_with_qualifier(tag, element_index, qualifier)
1001 .len()
1002 }
1003
1004 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1006 let instance_count = self.group_instance_count(group_path);
1007 if instance_count > 0 {
1008 let mut total = 0;
1009 for i in 0..instance_count {
1010 total += self.find_segments_in_group(tag, group_path, i).len();
1011 }
1012 return total;
1013 }
1014 self.find_segments(tag).len()
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::super::evaluator::NoOpExternalProvider;
1022 use super::*;
1023 use mig_types::navigator::GroupNavigator;
1024
1025 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1026 OwnedSegment {
1027 id: id.to_string(),
1028 elements: elements
1029 .into_iter()
1030 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1031 .collect(),
1032 segment_number: 0,
1033 }
1034 }
1035
1036 struct MockGroupNavigator {
1038 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1039 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1041 }
1042
1043 impl MockGroupNavigator {
1044 fn new() -> Self {
1045 Self {
1046 groups: vec![],
1047 children: vec![],
1048 }
1049 }
1050 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1051 self.groups
1052 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1053 self
1054 }
1055 fn with_child_group(
1056 mut self,
1057 parent_path: &[&str],
1058 parent_instance: usize,
1059 child_id: &str,
1060 child_instance: usize,
1061 segs: Vec<OwnedSegment>,
1062 ) -> Self {
1063 self.children.push((
1064 parent_path.iter().map(|s| s.to_string()).collect(),
1065 parent_instance,
1066 child_id.to_string(),
1067 child_instance,
1068 segs,
1069 ));
1070 self
1071 }
1072 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1073 self.groups
1074 .iter()
1075 .find(|(p, i, _)| {
1076 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1077 ps.as_slice() == group_path && *i == idx
1078 })
1079 .map(|(_, _, segs)| segs.as_slice())
1080 }
1081 }
1082
1083 impl GroupNavigator for MockGroupNavigator {
1084 fn find_segments_in_group(
1085 &self,
1086 segment_id: &str,
1087 group_path: &[&str],
1088 instance_index: usize,
1089 ) -> Vec<OwnedSegment> {
1090 self.find_instance(group_path, instance_index)
1091 .map(|segs| {
1092 segs.iter()
1093 .filter(|s| s.id == segment_id)
1094 .cloned()
1095 .collect()
1096 })
1097 .unwrap_or_default()
1098 }
1099 fn find_segments_with_qualifier_in_group(
1100 &self,
1101 segment_id: &str,
1102 element_index: usize,
1103 qualifier: &str,
1104 group_path: &[&str],
1105 instance_index: usize,
1106 ) -> Vec<OwnedSegment> {
1107 self.find_segments_in_group(segment_id, group_path, instance_index)
1108 .into_iter()
1109 .filter(|s| {
1110 s.elements
1111 .get(element_index)
1112 .and_then(|e| e.first())
1113 .is_some_and(|v| v == qualifier)
1114 })
1115 .collect()
1116 }
1117 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1118 self.groups
1119 .iter()
1120 .filter(|(p, _, _)| {
1121 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1122 ps.as_slice() == group_path
1123 })
1124 .count()
1125 }
1126 fn child_group_instance_count(
1127 &self,
1128 parent_path: &[&str],
1129 parent_instance: usize,
1130 child_group_id: &str,
1131 ) -> usize {
1132 self.children
1133 .iter()
1134 .filter(|(pp, pi, cid, _, _)| {
1135 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1136 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1137 })
1138 .count()
1139 }
1140 fn find_segments_in_child_group(
1141 &self,
1142 segment_id: &str,
1143 parent_path: &[&str],
1144 parent_instance: usize,
1145 child_group_id: &str,
1146 child_instance: usize,
1147 ) -> Vec<OwnedSegment> {
1148 self.children
1149 .iter()
1150 .find(|(pp, pi, cid, ci, _)| {
1151 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1152 ps.as_slice() == parent_path
1153 && *pi == parent_instance
1154 && cid == child_group_id
1155 && *ci == child_instance
1156 })
1157 .map(|(_, _, _, _, segs)| {
1158 segs.iter()
1159 .filter(|s| s.id == segment_id)
1160 .cloned()
1161 .collect()
1162 })
1163 .unwrap_or_default()
1164 }
1165 fn extract_value_in_group(
1166 &self,
1167 segment_id: &str,
1168 element_index: usize,
1169 component_index: usize,
1170 group_path: &[&str],
1171 instance_index: usize,
1172 ) -> Option<String> {
1173 let segs = self.find_instance(group_path, instance_index)?;
1174 let seg = segs.iter().find(|s| s.id == segment_id)?;
1175 seg.elements
1176 .get(element_index)?
1177 .get(component_index)
1178 .cloned()
1179 }
1180 }
1181
1182 #[test]
1183 fn test_find_segment() {
1184 let segments = vec![
1185 make_segment("UNH", vec![vec!["test"]]),
1186 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1187 ];
1188 let external = NoOpExternalProvider;
1189 let ctx = EvaluationContext::new("11001", &external, &segments);
1190
1191 assert!(ctx.find_segment("NAD").is_some());
1192 assert!(ctx.find_segment("DTM").is_none());
1193 }
1194
1195 #[test]
1196 fn test_find_segments_with_qualifier() {
1197 let segments = vec![
1198 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1199 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1200 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1201 ];
1202 let external = NoOpExternalProvider;
1203 let ctx = EvaluationContext::new("11001", &external, &segments);
1204
1205 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1206 assert_eq!(ms_nads.len(), 2);
1207 }
1208
1209 #[test]
1210 fn test_has_segment() {
1211 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1212 let external = NoOpExternalProvider;
1213 let ctx = EvaluationContext::new("11001", &external, &segments);
1214
1215 assert!(ctx.has_segment("UNH"));
1216 assert!(!ctx.has_segment("NAD"));
1217 }
1218
1219 #[test]
1222 fn test_no_navigator_group_find_returns_empty() {
1223 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1224 let external = NoOpExternalProvider;
1225 let ctx = EvaluationContext::new("55001", &external, &segments);
1226 assert!(ctx
1227 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1228 .is_empty());
1229 }
1230
1231 #[test]
1232 fn test_no_navigator_group_instance_count_zero() {
1233 let external = NoOpExternalProvider;
1234 let ctx = EvaluationContext::new("55001", &external, &[]);
1235 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1236 }
1237
1238 #[test]
1239 fn test_with_navigator_finds_segments_in_group() {
1240 let external = NoOpExternalProvider;
1241 let nav = MockGroupNavigator::new().with_group(
1242 &["SG4", "SG8"],
1243 0,
1244 vec![
1245 make_segment("SEQ", vec![vec!["Z98"]]),
1246 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1247 ],
1248 );
1249 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1250 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1251 assert_eq!(result.len(), 1);
1252 assert_eq!(result[0].id, "SEQ");
1253 }
1254
1255 #[test]
1256 fn test_with_navigator_qualifier_in_group() {
1257 let external = NoOpExternalProvider;
1258 let nav = MockGroupNavigator::new().with_group(
1259 &["SG4", "SG8"],
1260 0,
1261 vec![
1262 make_segment("SEQ", vec![vec!["Z98"]]),
1263 make_segment("SEQ", vec![vec!["Z01"]]),
1264 ],
1265 );
1266 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1267 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1268 assert_eq!(result.len(), 1);
1269 }
1270
1271 #[test]
1272 fn test_group_instance_count_with_navigator() {
1273 let external = NoOpExternalProvider;
1274 let nav = MockGroupNavigator::new()
1275 .with_group(
1276 &["SG4", "SG8"],
1277 0,
1278 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1279 )
1280 .with_group(
1281 &["SG4", "SG8"],
1282 1,
1283 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1284 );
1285 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1286 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1287 }
1288
1289 #[test]
1290 fn test_has_segment_in_group() {
1291 let external = NoOpExternalProvider;
1292 let nav = MockGroupNavigator::new().with_group(
1293 &["SG4", "SG8"],
1294 0,
1295 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1296 );
1297 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1298 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1299 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1300 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1301 }
1302
1303 #[test]
1306 fn test_has_qualifier() {
1307 let segments = vec![
1308 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1309 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1310 ];
1311 let external = NoOpExternalProvider;
1312 let ctx = EvaluationContext::new("11001", &external, &segments);
1313
1314 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1315 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1316 }
1317
1318 #[test]
1319 fn test_lacks_qualifier() {
1320 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1321 let external = NoOpExternalProvider;
1322 let ctx = EvaluationContext::new("11001", &external, &segments);
1323
1324 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1325 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1326 }
1327
1328 #[test]
1329 fn test_has_qualified_value() {
1330 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1331 let external = NoOpExternalProvider;
1332 let ctx = EvaluationContext::new("55001", &external, &segments);
1333
1334 assert_eq!(
1335 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1336 ConditionResult::True,
1337 );
1338 assert_eq!(
1339 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1340 ConditionResult::False,
1341 );
1342 assert_eq!(
1344 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1345 ConditionResult::Unknown,
1346 );
1347 }
1348
1349 #[test]
1350 fn test_any_group_has_qualifier() {
1351 let external = NoOpExternalProvider;
1352 let nav = MockGroupNavigator::new()
1353 .with_group(
1354 &["SG4", "SG8"],
1355 0,
1356 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1357 )
1358 .with_group(
1359 &["SG4", "SG8"],
1360 1,
1361 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1362 );
1363 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1364
1365 assert_eq!(
1366 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1367 ConditionResult::True,
1368 );
1369 assert_eq!(
1370 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1371 ConditionResult::False,
1372 );
1373 }
1374
1375 #[test]
1376 fn test_any_group_has_qualifier_fallback() {
1377 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1379 let external = NoOpExternalProvider;
1380 let ctx = EvaluationContext::new("55001", &external, &segments);
1381
1382 assert_eq!(
1383 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1384 ConditionResult::True,
1385 );
1386 }
1387
1388 #[test]
1389 fn test_any_group_has_any_qualifier() {
1390 let external = NoOpExternalProvider;
1391 let nav = MockGroupNavigator::new().with_group(
1392 &["SG4", "SG8"],
1393 0,
1394 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1395 );
1396 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1397
1398 assert_eq!(
1399 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1400 ConditionResult::True,
1401 );
1402 assert_eq!(
1403 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1404 ConditionResult::False,
1405 );
1406 }
1407
1408 #[test]
1409 fn test_any_group_has_co_occurrence() {
1410 let external = NoOpExternalProvider;
1411 let nav = MockGroupNavigator::new().with_group(
1412 &["SG4", "SG8"],
1413 0,
1414 vec![
1415 make_segment("SEQ", vec![vec!["Z01"]]),
1416 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1417 ],
1418 );
1419 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1420
1421 assert_eq!(
1422 ctx.any_group_has_co_occurrence(
1423 "SEQ",
1424 0,
1425 &["Z01"],
1426 "CCI",
1427 2,
1428 0,
1429 &["Z07"],
1430 &["SG4", "SG8"],
1431 ),
1432 ConditionResult::True,
1433 );
1434 assert_eq!(
1436 ctx.any_group_has_co_occurrence(
1437 "SEQ",
1438 0,
1439 &["Z01"],
1440 "CCI",
1441 2,
1442 0,
1443 &["ZC0"],
1444 &["SG4", "SG8"],
1445 ),
1446 ConditionResult::False,
1447 );
1448 }
1449
1450 #[test]
1453 fn test_filtered_parent_child_has_qualifier() {
1454 let external = NoOpExternalProvider;
1455 let nav = MockGroupNavigator::new()
1458 .with_group(
1459 &["SG4", "SG8"],
1460 0,
1461 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1462 )
1463 .with_group(
1464 &["SG4", "SG8"],
1465 1,
1466 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1467 )
1468 .with_child_group(
1469 &["SG4", "SG8"],
1470 0,
1471 "SG10",
1472 0,
1473 vec![make_segment("CCI", vec![vec!["Z23"]])],
1474 );
1475 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1476
1477 assert_eq!(
1479 ctx.filtered_parent_child_has_qualifier(
1480 &["SG4", "SG8"],
1481 "SEQ",
1482 0,
1483 "Z98",
1484 "SG10",
1485 "CCI",
1486 0,
1487 "Z23",
1488 ),
1489 ConditionResult::True,
1490 );
1491 assert_eq!(
1493 ctx.filtered_parent_child_has_qualifier(
1494 &["SG4", "SG8"],
1495 "SEQ",
1496 0,
1497 "Z01",
1498 "SG10",
1499 "CCI",
1500 0,
1501 "Z23",
1502 ),
1503 ConditionResult::False,
1504 );
1505 assert_eq!(
1507 ctx.filtered_parent_child_has_qualifier(
1508 &["SG4", "SG8"],
1509 "SEQ",
1510 0,
1511 "Z98",
1512 "SG10",
1513 "CCI",
1514 0,
1515 "Z99",
1516 ),
1517 ConditionResult::False,
1518 );
1519 }
1520
1521 #[test]
1522 fn test_filtered_parent_child_fallback() {
1523 let segments = vec![
1525 make_segment("SEQ", vec![vec!["Z98"]]),
1526 make_segment("CCI", vec![vec!["Z23"]]),
1527 ];
1528 let external = NoOpExternalProvider;
1529 let ctx = EvaluationContext::new("55001", &external, &segments);
1530
1531 assert_eq!(
1532 ctx.filtered_parent_child_has_qualifier(
1533 &["SG4", "SG8"],
1534 "SEQ",
1535 0,
1536 "Z98",
1537 "SG10",
1538 "CCI",
1539 0,
1540 "Z23",
1541 ),
1542 ConditionResult::True,
1543 );
1544 assert_eq!(
1546 ctx.filtered_parent_child_has_qualifier(
1547 &["SG4", "SG8"],
1548 "SEQ",
1549 0,
1550 "Z98",
1551 "SG10",
1552 "CCI",
1553 0,
1554 "Z99",
1555 ),
1556 ConditionResult::False,
1557 );
1558 }
1559
1560 #[test]
1561 fn test_any_group_has_qualifier_without() {
1562 let external = NoOpExternalProvider;
1563 let nav = MockGroupNavigator::new()
1566 .with_group(
1567 &["SG4", "SG8"],
1568 0,
1569 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1570 )
1571 .with_group(
1572 &["SG4", "SG8"],
1573 1,
1574 vec![
1575 make_segment("SEQ", vec![vec!["Z01"]]),
1576 make_segment("CCI", vec![vec!["11"]]),
1577 ],
1578 );
1579 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1580
1581 assert_eq!(
1583 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1584 ConditionResult::True,
1585 );
1586 assert_eq!(
1588 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
1589 ConditionResult::False,
1590 );
1591 assert_eq!(
1593 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
1594 ConditionResult::False,
1595 );
1596 }
1597
1598 #[test]
1599 fn test_any_group_has_qualifier_without_fallback() {
1600 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
1601 let external = NoOpExternalProvider;
1602 let ctx = EvaluationContext::new("55001", &external, &segments);
1603
1604 assert_eq!(
1606 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1607 ConditionResult::True,
1608 );
1609 }
1610
1611 #[test]
1612 fn test_collect_group_values() {
1613 let external = NoOpExternalProvider;
1614 let nav = MockGroupNavigator::new()
1615 .with_group(
1616 &["SG4", "SG6"],
1617 0,
1618 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
1619 )
1620 .with_group(
1621 &["SG4", "SG6"],
1622 1,
1623 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
1624 );
1625 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1626
1627 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
1628 assert_eq!(values.len(), 2);
1629 assert_eq!(values[0], (0, "REF001".to_string()));
1630 assert_eq!(values[1], (1, "REF002".to_string()));
1631 }
1632
1633 #[test]
1634 fn test_groups_share_qualified_value() {
1635 let external = NoOpExternalProvider;
1636 let nav = MockGroupNavigator::new()
1640 .with_group(
1641 &["SG4", "SG6"],
1642 0,
1643 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1644 )
1645 .with_group(
1646 &["SG4", "SG8"],
1647 0,
1648 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1649 )
1650 .with_group(
1651 &["SG4", "SG8"],
1652 1,
1653 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
1654 );
1655 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1656
1657 assert_eq!(
1659 ctx.groups_share_qualified_value(
1660 "RFF",
1661 0,
1662 "Z49",
1663 0,
1664 1,
1665 &["SG4", "SG6"],
1666 "SEQ",
1667 1,
1668 0,
1669 &["SG4", "SG8"],
1670 ),
1671 ConditionResult::True,
1672 );
1673 }
1674
1675 #[test]
1676 fn test_groups_share_qualified_value_no_match() {
1677 let external = NoOpExternalProvider;
1678 let nav = MockGroupNavigator::new()
1679 .with_group(
1680 &["SG4", "SG6"],
1681 0,
1682 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1683 )
1684 .with_group(
1685 &["SG4", "SG8"],
1686 0,
1687 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
1688 );
1689 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1690
1691 assert_eq!(
1693 ctx.groups_share_qualified_value(
1694 "RFF",
1695 0,
1696 "Z49",
1697 0,
1698 1,
1699 &["SG4", "SG6"],
1700 "SEQ",
1701 1,
1702 0,
1703 &["SG4", "SG8"],
1704 ),
1705 ConditionResult::False,
1706 );
1707 }
1708
1709 #[test]
1710 fn test_groups_share_qualified_value_no_source() {
1711 let external = NoOpExternalProvider;
1712 let nav = MockGroupNavigator::new()
1714 .with_group(
1715 &["SG4", "SG6"],
1716 0,
1717 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
1718 )
1719 .with_group(
1720 &["SG4", "SG8"],
1721 0,
1722 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1723 );
1724 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1725
1726 assert_eq!(
1728 ctx.groups_share_qualified_value(
1729 "RFF",
1730 0,
1731 "Z49",
1732 0,
1733 1,
1734 &["SG4", "SG6"],
1735 "SEQ",
1736 1,
1737 0,
1738 &["SG4", "SG8"],
1739 ),
1740 ConditionResult::Unknown,
1741 );
1742 }
1743
1744 #[test]
1745 fn test_groups_share_qualified_value_fallback() {
1746 let segments = vec![
1748 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
1749 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
1750 ];
1751 let external = NoOpExternalProvider;
1752 let ctx = EvaluationContext::new("55001", &external, &segments);
1753
1754 assert_eq!(
1755 ctx.groups_share_qualified_value(
1756 "RFF",
1757 0,
1758 "Z49",
1759 0,
1760 1,
1761 &["SG4", "SG6"],
1762 "SEQ",
1763 1,
1764 0,
1765 &["SG4", "SG8"],
1766 ),
1767 ConditionResult::True,
1768 );
1769 }
1770
1771 #[test]
1772 fn test_child_group_pass_throughs_no_navigator() {
1773 let external = NoOpExternalProvider;
1774 let ctx = EvaluationContext::new("55001", &external, &[]);
1775
1776 assert_eq!(
1777 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
1778 0
1779 );
1780 assert!(ctx
1781 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
1782 .is_empty());
1783 assert_eq!(
1784 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
1785 None
1786 );
1787 }
1788
1789 #[test]
1792 fn test_has_segment_matching_found() {
1793 let segments = vec![
1794 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
1795 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
1796 ];
1797 let external = NoOpExternalProvider;
1798 let ctx = EvaluationContext::new("55001", &external, &segments);
1799
1800 assert_eq!(
1801 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
1802 ConditionResult::True,
1803 );
1804 }
1805
1806 #[test]
1807 fn test_has_segment_matching_not_found() {
1808 let segments = vec![make_segment(
1809 "STS",
1810 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
1811 )];
1812 let external = NoOpExternalProvider;
1813 let ctx = EvaluationContext::new("55001", &external, &segments);
1814
1815 assert_eq!(
1816 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
1817 ConditionResult::False,
1818 );
1819 }
1820
1821 #[test]
1822 fn test_has_segment_matching_no_segments() {
1823 let external = NoOpExternalProvider;
1824 let ctx = EvaluationContext::new("55001", &external, &[]);
1825
1826 assert_eq!(
1827 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
1828 ConditionResult::Unknown,
1829 );
1830 }
1831
1832 #[test]
1833 fn test_has_segment_matching_in_group() {
1834 let nav = MockGroupNavigator::new()
1835 .with_group(
1836 &["SG4"],
1837 0,
1838 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
1839 )
1840 .with_group(
1841 &["SG4"],
1842 1,
1843 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
1844 );
1845 let segments = vec![
1846 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
1847 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
1848 ];
1849 let external = NoOpExternalProvider;
1850 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1851
1852 assert_eq!(
1853 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
1854 ConditionResult::True,
1855 );
1856 }
1857
1858 #[test]
1861 fn test_dtm_ge() {
1862 let segments = vec![make_segment(
1863 "DTM",
1864 vec![vec!["137", "202601010000", "303"]],
1865 )];
1866 let external = NoOpExternalProvider;
1867 let ctx = EvaluationContext::new("55001", &external, &segments);
1868
1869 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
1870 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
1871 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
1872 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
1873 }
1874
1875 #[test]
1876 fn test_dtm_lt() {
1877 let segments = vec![make_segment(
1878 "DTM",
1879 vec![vec!["137", "202601010000", "303"]],
1880 )];
1881 let external = NoOpExternalProvider;
1882 let ctx = EvaluationContext::new("55001", &external, &segments);
1883
1884 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
1885 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
1886 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
1887 }
1888
1889 #[test]
1890 fn test_dtm_le() {
1891 let segments = vec![make_segment(
1892 "DTM",
1893 vec![vec!["137", "202601010000", "303"]],
1894 )];
1895 let external = NoOpExternalProvider;
1896 let ctx = EvaluationContext::new("55001", &external, &segments);
1897
1898 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
1899 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
1900 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
1901 }
1902
1903 #[test]
1906 fn test_count_qualified_in_group() {
1907 let nav = MockGroupNavigator::new()
1908 .with_group(
1909 &["SG4", "SG8"],
1910 0,
1911 vec![
1912 make_segment("CCI", vec![vec!["Z23"]]),
1913 make_segment("CCI", vec![vec!["Z30"]]),
1914 ],
1915 )
1916 .with_group(
1917 &["SG4", "SG8"],
1918 1,
1919 vec![make_segment("CCI", vec![vec!["Z23"]])],
1920 );
1921 let segments = vec![
1922 make_segment("CCI", vec![vec!["Z23"]]),
1923 make_segment("CCI", vec![vec!["Z30"]]),
1924 make_segment("CCI", vec![vec!["Z23"]]),
1925 ];
1926 let external = NoOpExternalProvider;
1927 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1928
1929 assert_eq!(
1930 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1931 2
1932 );
1933 assert_eq!(
1934 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
1935 1
1936 );
1937 assert_eq!(
1938 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
1939 0
1940 );
1941 }
1942
1943 #[test]
1944 fn test_count_in_group() {
1945 let nav = MockGroupNavigator::new()
1946 .with_group(
1947 &["SG4", "SG8"],
1948 0,
1949 vec![
1950 make_segment("SEQ", vec![vec!["Z98"]]),
1951 make_segment("CCI", vec![vec!["Z23"]]),
1952 ],
1953 )
1954 .with_group(
1955 &["SG4", "SG8"],
1956 1,
1957 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1958 );
1959 let segments = vec![
1960 make_segment("SEQ", vec![vec!["Z98"]]),
1961 make_segment("CCI", vec![vec!["Z23"]]),
1962 make_segment("SEQ", vec![vec!["Z01"]]),
1963 ];
1964 let external = NoOpExternalProvider;
1965 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1966
1967 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
1968 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
1969 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
1970 }
1971
1972 #[test]
1973 fn test_count_fallback_no_navigator() {
1974 let segments = vec![
1975 make_segment("CCI", vec![vec!["Z23"]]),
1976 make_segment("CCI", vec![vec!["Z30"]]),
1977 make_segment("CCI", vec![vec!["Z23"]]),
1978 ];
1979 let external = NoOpExternalProvider;
1980 let ctx = EvaluationContext::new("55001", &external, &segments);
1981
1982 assert_eq!(
1984 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1985 2
1986 );
1987 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
1988 }
1989
1990 #[test]
1993 fn format_check_prefers_resolved_value() {
1994 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
1996 let external = NoOpExternalProvider;
1997 let ctx = EvaluationContext::new("55001", &external, &segments)
1998 .with_resolved(Some("CORRECT"), None);
1999
2000 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2001 assert_eq!(result, ConditionResult::True);
2002 }
2003
2004 #[test]
2005 fn format_check_falls_back_to_segment_search() {
2006 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2008 let external = NoOpExternalProvider;
2009 let ctx = EvaluationContext::new("55001", &external, &segments);
2010
2011 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2012 assert_eq!(result, ConditionResult::True);
2013 }
2014
2015 #[test]
2016 fn format_check_returns_unknown_when_segment_absent() {
2017 let segments: Vec<OwnedSegment> = vec![];
2019 let external = NoOpExternalProvider;
2020 let ctx = EvaluationContext::new("55001", &external, &segments);
2021
2022 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2023 assert_eq!(result, ConditionResult::Unknown);
2024 }
2025
2026 #[test]
2027 fn format_check_returns_unknown_when_multiple_segments() {
2028 let segments = vec![
2032 make_segment("DTM", vec![vec!["92", "OK"]]),
2033 make_segment("DTM", vec![vec!["163", "BAD"]]),
2034 ];
2035 let external = NoOpExternalProvider;
2036 let ctx = EvaluationContext::new("55001", &external, &segments);
2037
2038 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2039 assert_eq!(result, ConditionResult::Unknown);
2040 }
2041
2042 #[test]
2043 fn format_check_qualified_prefers_resolved_value() {
2044 let segments = vec![
2046 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2047 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2048 ];
2049 let external = NoOpExternalProvider;
2050 let ctx = EvaluationContext::new("55001", &external, &segments)
2051 .with_resolved(Some("CORRECT"), None);
2052
2053 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2054 ConditionResult::from(v == "CORRECT")
2055 });
2056 assert_eq!(result, ConditionResult::True);
2057 }
2058
2059 #[test]
2060 fn format_check_qualified_falls_back_to_qualified_search() {
2061 let segments = vec![
2063 make_segment("DTM", vec![vec!["92", "2200"]]),
2064 make_segment("DTM", vec![vec!["163", "0800"]]),
2065 ];
2066 let external = NoOpExternalProvider;
2067 let ctx = EvaluationContext::new("55001", &external, &segments);
2068
2069 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2071 ConditionResult::from(v == "0800")
2072 });
2073 assert_eq!(result, ConditionResult::True);
2074
2075 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2077 ConditionResult::from(v == "2200")
2078 });
2079 assert_eq!(result2, ConditionResult::False);
2080 }
2081
2082 #[test]
2085 fn self_segment_value_prefers_resolved_segment() {
2086 let segments = vec![
2090 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2091 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2092 ];
2093 let external = NoOpExternalProvider;
2094 let resolved_elements = vec![vec![
2095 "Z25".to_string(),
2096 String::new(),
2097 "303".to_string(),
2098 ]];
2099 let ctx = EvaluationContext::new("55001", &external, &segments)
2100 .with_resolved(None, Some(&resolved_elements));
2101
2102 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2103 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2104 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2105 }
2106
2107 #[test]
2108 fn self_segment_value_falls_back_when_no_resolved_segment() {
2109 let segments = vec![
2111 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2112 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2113 ];
2114 let external = NoOpExternalProvider;
2115 let ctx = EvaluationContext::new("55001", &external, &segments);
2116
2117 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2118 }
2119
2120 #[test]
2121 fn self_segment_value_missing_returns_none() {
2122 let segments: Vec<OwnedSegment> = vec![];
2123 let external = NoOpExternalProvider;
2124 let ctx = EvaluationContext::new("55001", &external, &segments);
2125
2126 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2127 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2128 }
2129}