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(
161 &self,
162 tag: &str,
163 elem: usize,
164 comp: usize,
165 validate: impl FnOnce(&str) -> ConditionResult,
166 ) -> ConditionResult {
167 let value = self.resolved_value.or_else(|| {
168 self.find_segments(tag)
169 .into_iter()
170 .next()
171 .and_then(|s| s.elements.get(elem))
172 .and_then(|e| e.get(comp))
173 .map(|s| s.as_str())
174 });
175 match value {
176 Some(val) => validate(val),
177 None => ConditionResult::False,
178 }
179 }
180
181 pub fn format_check_qualified(
189 &self,
190 tag: &str,
191 qual_elem: usize,
192 qualifier: &str,
193 val_elem: usize,
194 val_comp: usize,
195 validate: impl FnOnce(&str) -> ConditionResult,
196 ) -> ConditionResult {
197 let value = self.resolved_value.or_else(|| {
198 self.find_segments_with_qualifier(tag, qual_elem, qualifier)
199 .into_iter()
200 .next()
201 .and_then(|s| s.elements.get(val_elem))
202 .and_then(|e| e.get(val_comp))
203 .map(|s| s.as_str())
204 });
205 match value {
206 Some(val) => validate(val),
207 None => ConditionResult::False,
208 }
209 }
210
211 pub fn has_segment(&self, segment_id: &str) -> bool {
213 self.segments.iter().any(|s| s.id == segment_id)
214 }
215
216 pub fn find_segments_in_group(
219 &self,
220 segment_id: &str,
221 group_path: &[&str],
222 instance_index: usize,
223 ) -> Vec<OwnedSegment> {
224 match self.navigator {
225 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
226 None => Vec::new(),
227 }
228 }
229
230 pub fn find_segments_with_qualifier_in_group(
233 &self,
234 segment_id: &str,
235 element_index: usize,
236 qualifier: &str,
237 group_path: &[&str],
238 instance_index: usize,
239 ) -> Vec<OwnedSegment> {
240 match self.navigator {
241 Some(nav) => nav.find_segments_with_qualifier_in_group(
242 segment_id,
243 element_index,
244 qualifier,
245 group_path,
246 instance_index,
247 ),
248 None => Vec::new(),
249 }
250 }
251
252 pub fn has_segment_in_group(
255 &self,
256 segment_id: &str,
257 group_path: &[&str],
258 instance_index: usize,
259 ) -> bool {
260 !self
261 .find_segments_in_group(segment_id, group_path, instance_index)
262 .is_empty()
263 }
264
265 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
268 match self.navigator {
269 Some(nav) => nav.group_instance_count(group_path),
270 None => 0,
271 }
272 }
273
274 pub fn child_group_instance_count(
277 &self,
278 parent_path: &[&str],
279 parent_instance: usize,
280 child_group_id: &str,
281 ) -> usize {
282 match self.navigator {
283 Some(nav) => {
284 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
285 }
286 None => 0,
287 }
288 }
289
290 pub fn find_segments_in_child_group(
293 &self,
294 segment_id: &str,
295 parent_path: &[&str],
296 parent_instance: usize,
297 child_group_id: &str,
298 child_instance: usize,
299 ) -> Vec<OwnedSegment> {
300 match self.navigator {
301 Some(nav) => nav.find_segments_in_child_group(
302 segment_id,
303 parent_path,
304 parent_instance,
305 child_group_id,
306 child_instance,
307 ),
308 None => Vec::new(),
309 }
310 }
311
312 pub fn extract_value_in_group(
315 &self,
316 segment_id: &str,
317 element_index: usize,
318 component_index: usize,
319 group_path: &[&str],
320 instance_index: usize,
321 ) -> Option<String> {
322 self.navigator?.extract_value_in_group(
323 segment_id,
324 element_index,
325 component_index,
326 group_path,
327 instance_index,
328 )
329 }
330
331 pub fn has_qualifier(
337 &self,
338 tag: &str,
339 element_index: usize,
340 qualifier: &str,
341 ) -> ConditionResult {
342 ConditionResult::from(
343 !self
344 .find_segments_with_qualifier(tag, element_index, qualifier)
345 .is_empty(),
346 )
347 }
348
349 pub fn lacks_qualifier(
352 &self,
353 tag: &str,
354 element_index: usize,
355 qualifier: &str,
356 ) -> ConditionResult {
357 ConditionResult::from(
358 self.find_segments_with_qualifier(tag, element_index, qualifier)
359 .is_empty(),
360 )
361 }
362
363 pub fn has_qualified_value(
368 &self,
369 tag: &str,
370 qual_elem: usize,
371 qualifier: &str,
372 value_elem: usize,
373 value_comp: usize,
374 values: &[&str],
375 ) -> ConditionResult {
376 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
377 if segments.is_empty() {
378 return ConditionResult::Unknown;
379 }
380 for seg in &segments {
381 if let Some(v) = seg
382 .elements
383 .get(value_elem)
384 .and_then(|e| e.get(value_comp))
385 .map(|s| s.as_str())
386 {
387 if values.contains(&v) {
388 return ConditionResult::True;
389 }
390 }
391 }
392 ConditionResult::False
393 }
394
395 pub fn any_group_has_qualifier(
401 &self,
402 tag: &str,
403 element_index: usize,
404 qualifier: &str,
405 group_path: &[&str],
406 ) -> ConditionResult {
407 let instance_count = self.group_instance_count(group_path);
408 if instance_count > 0 {
409 for i in 0..instance_count {
410 if !self
411 .find_segments_with_qualifier_in_group(
412 tag,
413 element_index,
414 qualifier,
415 group_path,
416 i,
417 )
418 .is_empty()
419 {
420 return ConditionResult::True;
421 }
422 }
423 return ConditionResult::False;
424 }
425 self.has_qualifier(tag, element_index, qualifier)
427 }
428
429 pub fn any_group_has_any_qualifier(
435 &self,
436 tag: &str,
437 element_index: usize,
438 qualifiers: &[&str],
439 group_path: &[&str],
440 ) -> ConditionResult {
441 let instance_count = self.group_instance_count(group_path);
442 if instance_count > 0 {
443 for i in 0..instance_count {
444 let segs = self.find_segments_in_group(tag, group_path, i);
445 if segs.iter().any(|seg| {
446 seg.elements
447 .get(element_index)
448 .and_then(|e| e.first())
449 .map(|s| s.as_str())
450 .is_some_and(|v| qualifiers.contains(&v))
451 }) {
452 return ConditionResult::True;
453 }
454 }
455 return ConditionResult::False;
456 }
457 let found = self.find_segments(tag).iter().any(|seg| {
459 seg.elements
460 .get(element_index)
461 .and_then(|e| e.first())
462 .map(|s| s.as_str())
463 .is_some_and(|v| qualifiers.contains(&v))
464 });
465 ConditionResult::from(found)
466 }
467
468 pub fn any_group_has_qualified_value(
474 &self,
475 tag: &str,
476 qual_elem: usize,
477 qualifier: &str,
478 value_elem: usize,
479 value_comp: usize,
480 values: &[&str],
481 group_path: &[&str],
482 ) -> ConditionResult {
483 let instance_count = self.group_instance_count(group_path);
484 if instance_count > 0 {
485 for i in 0..instance_count {
486 let segs = self.find_segments_with_qualifier_in_group(
487 tag, qual_elem, qualifier, group_path, i,
488 );
489 for seg in &segs {
490 if seg
491 .elements
492 .get(value_elem)
493 .and_then(|e| e.get(value_comp))
494 .map(|s| s.as_str())
495 .is_some_and(|v| values.contains(&v))
496 {
497 return ConditionResult::True;
498 }
499 }
500 }
501 return ConditionResult::False;
502 }
503 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
505 }
506
507 #[allow(clippy::too_many_arguments)]
516 pub fn filtered_parent_child_has_qualifier(
517 &self,
518 parent_path: &[&str],
519 parent_tag: &str,
520 parent_elem: usize,
521 parent_qual: &str,
522 child_group_id: &str,
523 child_tag: &str,
524 child_elem: usize,
525 child_qual: &str,
526 ) -> ConditionResult {
527 let parent_count = self.group_instance_count(parent_path);
528 if parent_count > 0 {
529 for pi in 0..parent_count {
530 let parent_segs = self.find_segments_with_qualifier_in_group(
532 parent_tag,
533 parent_elem,
534 parent_qual,
535 parent_path,
536 pi,
537 );
538 if parent_segs.is_empty() {
539 continue;
540 }
541 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
543 for ci in 0..child_count {
544 let child_segs = self.find_segments_in_child_group(
545 child_tag,
546 parent_path,
547 pi,
548 child_group_id,
549 ci,
550 );
551 if child_segs.iter().any(|s| {
552 s.elements
553 .get(child_elem)
554 .and_then(|e| e.first())
555 .is_some_and(|v| v == child_qual)
556 }) {
557 return ConditionResult::True;
558 }
559 }
560 }
561 return ConditionResult::False;
562 }
563 let has_parent = !self
565 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
566 .is_empty();
567 let has_child = !self
568 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
569 .is_empty();
570 ConditionResult::from(has_parent && has_child)
571 }
572
573 #[allow(clippy::too_many_arguments)]
579 pub fn any_group_has_qualifier_without(
580 &self,
581 present_tag: &str,
582 present_elem: usize,
583 present_qual: &str,
584 absent_tag: &str,
585 absent_elem: usize,
586 absent_qual: &str,
587 group_path: &[&str],
588 ) -> ConditionResult {
589 let instance_count = self.group_instance_count(group_path);
590 if instance_count > 0 {
591 for i in 0..instance_count {
592 let has_present = !self
593 .find_segments_with_qualifier_in_group(
594 present_tag,
595 present_elem,
596 present_qual,
597 group_path,
598 i,
599 )
600 .is_empty();
601 let has_absent = !self
602 .find_segments_with_qualifier_in_group(
603 absent_tag,
604 absent_elem,
605 absent_qual,
606 group_path,
607 i,
608 )
609 .is_empty();
610 if has_present && !has_absent {
611 return ConditionResult::True;
612 }
613 }
614 return ConditionResult::False;
615 }
616 let has_present = !self
618 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
619 .is_empty();
620 let has_absent = !self
621 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
622 .is_empty();
623 ConditionResult::from(has_present && !has_absent)
624 }
625
626 pub fn collect_group_values(
630 &self,
631 tag: &str,
632 elem: usize,
633 comp: usize,
634 group_path: &[&str],
635 ) -> Vec<(usize, String)> {
636 let instance_count = self.group_instance_count(group_path);
637 let mut results = Vec::new();
638 for i in 0..instance_count {
639 if let Some(val) = self
640 .navigator
641 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
642 {
643 if !val.is_empty() {
644 results.push((i, val));
645 }
646 }
647 }
648 results
649 }
650
651 #[allow(clippy::too_many_arguments)]
660 pub fn groups_share_qualified_value(
661 &self,
662 source_tag: &str,
663 source_qual_elem: usize,
664 source_qual: &str,
665 source_value_elem: usize,
666 source_value_comp: usize,
667 source_path: &[&str],
668 target_tag: &str,
669 target_elem: usize,
670 target_comp: usize,
671 target_path: &[&str],
672 ) -> ConditionResult {
673 let source_count = self.group_instance_count(source_path);
674 let target_count = self.group_instance_count(target_path);
675 if source_count > 0 && target_count > 0 {
676 let mut source_values = Vec::new();
678 for si in 0..source_count {
679 let segs = self.find_segments_with_qualifier_in_group(
680 source_tag,
681 source_qual_elem,
682 source_qual,
683 source_path,
684 si,
685 );
686 for seg in &segs {
687 if let Some(val) = seg
688 .elements
689 .get(source_value_elem)
690 .and_then(|e| e.get(source_value_comp))
691 {
692 if !val.is_empty() {
693 source_values.push(val.clone());
694 }
695 }
696 }
697 }
698 if source_values.is_empty() {
699 return ConditionResult::Unknown;
700 }
701 let target_values =
703 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
704 for (_, tv) in &target_values {
705 if source_values.iter().any(|sv| sv == tv) {
706 return ConditionResult::True;
707 }
708 }
709 return ConditionResult::False;
710 }
711 let source_segs =
713 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
714 let source_vals: Vec<&str> = source_segs
715 .iter()
716 .filter_map(|s| {
717 s.elements
718 .get(source_value_elem)
719 .and_then(|e| e.get(source_value_comp))
720 .map(|v| v.as_str())
721 })
722 .filter(|v| !v.is_empty())
723 .collect();
724 if source_vals.is_empty() {
725 return ConditionResult::Unknown;
726 }
727 let target_segs = self.find_segments(target_tag);
728 let has_match = target_segs.iter().any(|s| {
729 s.elements
730 .get(target_elem)
731 .and_then(|e| e.get(target_comp))
732 .map(|v| v.as_str())
733 .is_some_and(|v| source_vals.contains(&v))
734 });
735 ConditionResult::from(has_match)
736 }
737
738 #[allow(clippy::too_many_arguments)]
747 pub fn any_group_has_co_occurrence(
748 &self,
749 tag_a: &str,
750 elem_a: usize,
751 quals_a: &[&str],
752 tag_b: &str,
753 elem_b: usize,
754 comp_b: usize,
755 vals_b: &[&str],
756 group_path: &[&str],
757 ) -> ConditionResult {
758 let instance_count = self.group_instance_count(group_path);
759 if instance_count > 0 {
760 for i in 0..instance_count {
761 let a_present = self
762 .find_segments_in_group(tag_a, group_path, i)
763 .iter()
764 .any(|seg| {
765 seg.elements
766 .get(elem_a)
767 .and_then(|e| e.first())
768 .map(|s| s.as_str())
769 .is_some_and(|v| quals_a.contains(&v))
770 });
771 let b_present = self
772 .find_segments_in_group(tag_b, group_path, i)
773 .iter()
774 .any(|seg| {
775 seg.elements
776 .get(elem_b)
777 .and_then(|e| e.get(comp_b))
778 .map(|s| s.as_str())
779 .is_some_and(|v| vals_b.contains(&v))
780 });
781 if a_present && b_present {
782 return ConditionResult::True;
783 }
784 }
785 return ConditionResult::False;
786 }
787 let a_found = self.find_segments(tag_a).iter().any(|seg| {
789 seg.elements
790 .get(elem_a)
791 .and_then(|e| e.first())
792 .map(|s| s.as_str())
793 .is_some_and(|v| quals_a.contains(&v))
794 });
795 if !a_found {
796 return ConditionResult::False;
797 }
798 let b_found = self.find_segments(tag_b).iter().any(|seg| {
799 seg.elements
800 .get(elem_b)
801 .and_then(|e| e.get(comp_b))
802 .map(|s| s.as_str())
803 .is_some_and(|v| vals_b.contains(&v))
804 });
805 ConditionResult::from(b_found)
806 }
807
808 pub fn has_segment_matching(
818 &self,
819 tag: &str,
820 checks: &[(usize, usize, &str)],
821 ) -> ConditionResult {
822 let segments = self.find_segments(tag);
823 if segments.is_empty() {
824 return ConditionResult::Unknown;
825 }
826 let found = segments.iter().any(|seg| {
827 checks.iter().all(|(elem, comp, val)| {
828 seg.elements
829 .get(*elem)
830 .and_then(|e| e.get(*comp))
831 .is_some_and(|v| v == val)
832 })
833 });
834 ConditionResult::from(found)
835 }
836
837 pub fn has_segment_matching_in_group(
842 &self,
843 tag: &str,
844 checks: &[(usize, usize, &str)],
845 group_path: &[&str],
846 ) -> ConditionResult {
847 let instance_count = self.group_instance_count(group_path);
848 if instance_count > 0 {
849 for i in 0..instance_count {
850 let segs = self.find_segments_in_group(tag, group_path, i);
851 if segs.iter().any(|seg| {
852 checks.iter().all(|(elem, comp, val)| {
853 seg.elements
854 .get(*elem)
855 .and_then(|e| e.get(*comp))
856 .is_some_and(|v| v == val)
857 })
858 }) {
859 return ConditionResult::True;
860 }
861 }
862 return ConditionResult::False;
863 }
864 self.has_segment_matching(tag, checks)
866 }
867
868 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
875 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
876 match segs.first() {
877 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
878 Some(val) => ConditionResult::from(val.as_str() >= threshold),
879 None => ConditionResult::Unknown,
880 },
881 None => ConditionResult::Unknown,
882 }
883 }
884
885 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
887 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
888 match segs.first() {
889 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
890 Some(val) => ConditionResult::from((val.as_str()) < threshold),
891 None => ConditionResult::Unknown,
892 },
893 None => ConditionResult::Unknown,
894 }
895 }
896
897 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
899 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
900 match segs.first() {
901 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
902 Some(val) => ConditionResult::from(val.as_str() <= threshold),
903 None => ConditionResult::Unknown,
904 },
905 None => ConditionResult::Unknown,
906 }
907 }
908
909 pub fn count_qualified_in_group(
916 &self,
917 tag: &str,
918 element_index: usize,
919 qualifier: &str,
920 group_path: &[&str],
921 ) -> usize {
922 let instance_count = self.group_instance_count(group_path);
923 if instance_count > 0 {
924 let mut total = 0;
925 for i in 0..instance_count {
926 total += self
927 .find_segments_with_qualifier_in_group(
928 tag,
929 element_index,
930 qualifier,
931 group_path,
932 i,
933 )
934 .len();
935 }
936 return total;
937 }
938 self.find_segments_with_qualifier(tag, element_index, qualifier)
940 .len()
941 }
942
943 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
945 let instance_count = self.group_instance_count(group_path);
946 if instance_count > 0 {
947 let mut total = 0;
948 for i in 0..instance_count {
949 total += self.find_segments_in_group(tag, group_path, i).len();
950 }
951 return total;
952 }
953 self.find_segments(tag).len()
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use super::super::evaluator::NoOpExternalProvider;
961 use super::*;
962 use mig_types::navigator::GroupNavigator;
963
964 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
965 OwnedSegment {
966 id: id.to_string(),
967 elements: elements
968 .into_iter()
969 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
970 .collect(),
971 segment_number: 0,
972 }
973 }
974
975 struct MockGroupNavigator {
977 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
978 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
980 }
981
982 impl MockGroupNavigator {
983 fn new() -> Self {
984 Self {
985 groups: vec![],
986 children: vec![],
987 }
988 }
989 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
990 self.groups
991 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
992 self
993 }
994 fn with_child_group(
995 mut self,
996 parent_path: &[&str],
997 parent_instance: usize,
998 child_id: &str,
999 child_instance: usize,
1000 segs: Vec<OwnedSegment>,
1001 ) -> Self {
1002 self.children.push((
1003 parent_path.iter().map(|s| s.to_string()).collect(),
1004 parent_instance,
1005 child_id.to_string(),
1006 child_instance,
1007 segs,
1008 ));
1009 self
1010 }
1011 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1012 self.groups
1013 .iter()
1014 .find(|(p, i, _)| {
1015 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1016 ps.as_slice() == group_path && *i == idx
1017 })
1018 .map(|(_, _, segs)| segs.as_slice())
1019 }
1020 }
1021
1022 impl GroupNavigator for MockGroupNavigator {
1023 fn find_segments_in_group(
1024 &self,
1025 segment_id: &str,
1026 group_path: &[&str],
1027 instance_index: usize,
1028 ) -> Vec<OwnedSegment> {
1029 self.find_instance(group_path, instance_index)
1030 .map(|segs| {
1031 segs.iter()
1032 .filter(|s| s.id == segment_id)
1033 .cloned()
1034 .collect()
1035 })
1036 .unwrap_or_default()
1037 }
1038 fn find_segments_with_qualifier_in_group(
1039 &self,
1040 segment_id: &str,
1041 element_index: usize,
1042 qualifier: &str,
1043 group_path: &[&str],
1044 instance_index: usize,
1045 ) -> Vec<OwnedSegment> {
1046 self.find_segments_in_group(segment_id, group_path, instance_index)
1047 .into_iter()
1048 .filter(|s| {
1049 s.elements
1050 .get(element_index)
1051 .and_then(|e| e.first())
1052 .is_some_and(|v| v == qualifier)
1053 })
1054 .collect()
1055 }
1056 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1057 self.groups
1058 .iter()
1059 .filter(|(p, _, _)| {
1060 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1061 ps.as_slice() == group_path
1062 })
1063 .count()
1064 }
1065 fn child_group_instance_count(
1066 &self,
1067 parent_path: &[&str],
1068 parent_instance: usize,
1069 child_group_id: &str,
1070 ) -> usize {
1071 self.children
1072 .iter()
1073 .filter(|(pp, pi, cid, _, _)| {
1074 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1075 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1076 })
1077 .count()
1078 }
1079 fn find_segments_in_child_group(
1080 &self,
1081 segment_id: &str,
1082 parent_path: &[&str],
1083 parent_instance: usize,
1084 child_group_id: &str,
1085 child_instance: usize,
1086 ) -> Vec<OwnedSegment> {
1087 self.children
1088 .iter()
1089 .find(|(pp, pi, cid, ci, _)| {
1090 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1091 ps.as_slice() == parent_path
1092 && *pi == parent_instance
1093 && cid == child_group_id
1094 && *ci == child_instance
1095 })
1096 .map(|(_, _, _, _, segs)| {
1097 segs.iter()
1098 .filter(|s| s.id == segment_id)
1099 .cloned()
1100 .collect()
1101 })
1102 .unwrap_or_default()
1103 }
1104 fn extract_value_in_group(
1105 &self,
1106 segment_id: &str,
1107 element_index: usize,
1108 component_index: usize,
1109 group_path: &[&str],
1110 instance_index: usize,
1111 ) -> Option<String> {
1112 let segs = self.find_instance(group_path, instance_index)?;
1113 let seg = segs.iter().find(|s| s.id == segment_id)?;
1114 seg.elements
1115 .get(element_index)?
1116 .get(component_index)
1117 .cloned()
1118 }
1119 }
1120
1121 #[test]
1122 fn test_find_segment() {
1123 let segments = vec![
1124 make_segment("UNH", vec![vec!["test"]]),
1125 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1126 ];
1127 let external = NoOpExternalProvider;
1128 let ctx = EvaluationContext::new("11001", &external, &segments);
1129
1130 assert!(ctx.find_segment("NAD").is_some());
1131 assert!(ctx.find_segment("DTM").is_none());
1132 }
1133
1134 #[test]
1135 fn test_find_segments_with_qualifier() {
1136 let segments = vec![
1137 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1138 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1139 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1140 ];
1141 let external = NoOpExternalProvider;
1142 let ctx = EvaluationContext::new("11001", &external, &segments);
1143
1144 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1145 assert_eq!(ms_nads.len(), 2);
1146 }
1147
1148 #[test]
1149 fn test_has_segment() {
1150 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1151 let external = NoOpExternalProvider;
1152 let ctx = EvaluationContext::new("11001", &external, &segments);
1153
1154 assert!(ctx.has_segment("UNH"));
1155 assert!(!ctx.has_segment("NAD"));
1156 }
1157
1158 #[test]
1161 fn test_no_navigator_group_find_returns_empty() {
1162 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1163 let external = NoOpExternalProvider;
1164 let ctx = EvaluationContext::new("55001", &external, &segments);
1165 assert!(ctx
1166 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1167 .is_empty());
1168 }
1169
1170 #[test]
1171 fn test_no_navigator_group_instance_count_zero() {
1172 let external = NoOpExternalProvider;
1173 let ctx = EvaluationContext::new("55001", &external, &[]);
1174 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1175 }
1176
1177 #[test]
1178 fn test_with_navigator_finds_segments_in_group() {
1179 let external = NoOpExternalProvider;
1180 let nav = MockGroupNavigator::new().with_group(
1181 &["SG4", "SG8"],
1182 0,
1183 vec![
1184 make_segment("SEQ", vec![vec!["Z98"]]),
1185 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1186 ],
1187 );
1188 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1189 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1190 assert_eq!(result.len(), 1);
1191 assert_eq!(result[0].id, "SEQ");
1192 }
1193
1194 #[test]
1195 fn test_with_navigator_qualifier_in_group() {
1196 let external = NoOpExternalProvider;
1197 let nav = MockGroupNavigator::new().with_group(
1198 &["SG4", "SG8"],
1199 0,
1200 vec![
1201 make_segment("SEQ", vec![vec!["Z98"]]),
1202 make_segment("SEQ", vec![vec!["Z01"]]),
1203 ],
1204 );
1205 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1206 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1207 assert_eq!(result.len(), 1);
1208 }
1209
1210 #[test]
1211 fn test_group_instance_count_with_navigator() {
1212 let external = NoOpExternalProvider;
1213 let nav = MockGroupNavigator::new()
1214 .with_group(
1215 &["SG4", "SG8"],
1216 0,
1217 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1218 )
1219 .with_group(
1220 &["SG4", "SG8"],
1221 1,
1222 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1223 );
1224 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1225 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1226 }
1227
1228 #[test]
1229 fn test_has_segment_in_group() {
1230 let external = NoOpExternalProvider;
1231 let nav = MockGroupNavigator::new().with_group(
1232 &["SG4", "SG8"],
1233 0,
1234 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1235 );
1236 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1237 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1238 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1239 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1240 }
1241
1242 #[test]
1245 fn test_has_qualifier() {
1246 let segments = vec![
1247 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1248 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1249 ];
1250 let external = NoOpExternalProvider;
1251 let ctx = EvaluationContext::new("11001", &external, &segments);
1252
1253 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1254 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1255 }
1256
1257 #[test]
1258 fn test_lacks_qualifier() {
1259 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1260 let external = NoOpExternalProvider;
1261 let ctx = EvaluationContext::new("11001", &external, &segments);
1262
1263 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1264 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1265 }
1266
1267 #[test]
1268 fn test_has_qualified_value() {
1269 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1270 let external = NoOpExternalProvider;
1271 let ctx = EvaluationContext::new("55001", &external, &segments);
1272
1273 assert_eq!(
1274 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1275 ConditionResult::True,
1276 );
1277 assert_eq!(
1278 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1279 ConditionResult::False,
1280 );
1281 assert_eq!(
1283 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1284 ConditionResult::Unknown,
1285 );
1286 }
1287
1288 #[test]
1289 fn test_any_group_has_qualifier() {
1290 let external = NoOpExternalProvider;
1291 let nav = MockGroupNavigator::new()
1292 .with_group(
1293 &["SG4", "SG8"],
1294 0,
1295 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1296 )
1297 .with_group(
1298 &["SG4", "SG8"],
1299 1,
1300 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1301 );
1302 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1303
1304 assert_eq!(
1305 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1306 ConditionResult::True,
1307 );
1308 assert_eq!(
1309 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1310 ConditionResult::False,
1311 );
1312 }
1313
1314 #[test]
1315 fn test_any_group_has_qualifier_fallback() {
1316 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1318 let external = NoOpExternalProvider;
1319 let ctx = EvaluationContext::new("55001", &external, &segments);
1320
1321 assert_eq!(
1322 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1323 ConditionResult::True,
1324 );
1325 }
1326
1327 #[test]
1328 fn test_any_group_has_any_qualifier() {
1329 let external = NoOpExternalProvider;
1330 let nav = MockGroupNavigator::new().with_group(
1331 &["SG4", "SG8"],
1332 0,
1333 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1334 );
1335 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1336
1337 assert_eq!(
1338 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1339 ConditionResult::True,
1340 );
1341 assert_eq!(
1342 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1343 ConditionResult::False,
1344 );
1345 }
1346
1347 #[test]
1348 fn test_any_group_has_co_occurrence() {
1349 let external = NoOpExternalProvider;
1350 let nav = MockGroupNavigator::new().with_group(
1351 &["SG4", "SG8"],
1352 0,
1353 vec![
1354 make_segment("SEQ", vec![vec!["Z01"]]),
1355 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1356 ],
1357 );
1358 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1359
1360 assert_eq!(
1361 ctx.any_group_has_co_occurrence(
1362 "SEQ",
1363 0,
1364 &["Z01"],
1365 "CCI",
1366 2,
1367 0,
1368 &["Z07"],
1369 &["SG4", "SG8"],
1370 ),
1371 ConditionResult::True,
1372 );
1373 assert_eq!(
1375 ctx.any_group_has_co_occurrence(
1376 "SEQ",
1377 0,
1378 &["Z01"],
1379 "CCI",
1380 2,
1381 0,
1382 &["ZC0"],
1383 &["SG4", "SG8"],
1384 ),
1385 ConditionResult::False,
1386 );
1387 }
1388
1389 #[test]
1392 fn test_filtered_parent_child_has_qualifier() {
1393 let external = NoOpExternalProvider;
1394 let nav = MockGroupNavigator::new()
1397 .with_group(
1398 &["SG4", "SG8"],
1399 0,
1400 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1401 )
1402 .with_group(
1403 &["SG4", "SG8"],
1404 1,
1405 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1406 )
1407 .with_child_group(
1408 &["SG4", "SG8"],
1409 0,
1410 "SG10",
1411 0,
1412 vec![make_segment("CCI", vec![vec!["Z23"]])],
1413 );
1414 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1415
1416 assert_eq!(
1418 ctx.filtered_parent_child_has_qualifier(
1419 &["SG4", "SG8"],
1420 "SEQ",
1421 0,
1422 "Z98",
1423 "SG10",
1424 "CCI",
1425 0,
1426 "Z23",
1427 ),
1428 ConditionResult::True,
1429 );
1430 assert_eq!(
1432 ctx.filtered_parent_child_has_qualifier(
1433 &["SG4", "SG8"],
1434 "SEQ",
1435 0,
1436 "Z01",
1437 "SG10",
1438 "CCI",
1439 0,
1440 "Z23",
1441 ),
1442 ConditionResult::False,
1443 );
1444 assert_eq!(
1446 ctx.filtered_parent_child_has_qualifier(
1447 &["SG4", "SG8"],
1448 "SEQ",
1449 0,
1450 "Z98",
1451 "SG10",
1452 "CCI",
1453 0,
1454 "Z99",
1455 ),
1456 ConditionResult::False,
1457 );
1458 }
1459
1460 #[test]
1461 fn test_filtered_parent_child_fallback() {
1462 let segments = vec![
1464 make_segment("SEQ", vec![vec!["Z98"]]),
1465 make_segment("CCI", vec![vec!["Z23"]]),
1466 ];
1467 let external = NoOpExternalProvider;
1468 let ctx = EvaluationContext::new("55001", &external, &segments);
1469
1470 assert_eq!(
1471 ctx.filtered_parent_child_has_qualifier(
1472 &["SG4", "SG8"],
1473 "SEQ",
1474 0,
1475 "Z98",
1476 "SG10",
1477 "CCI",
1478 0,
1479 "Z23",
1480 ),
1481 ConditionResult::True,
1482 );
1483 assert_eq!(
1485 ctx.filtered_parent_child_has_qualifier(
1486 &["SG4", "SG8"],
1487 "SEQ",
1488 0,
1489 "Z98",
1490 "SG10",
1491 "CCI",
1492 0,
1493 "Z99",
1494 ),
1495 ConditionResult::False,
1496 );
1497 }
1498
1499 #[test]
1500 fn test_any_group_has_qualifier_without() {
1501 let external = NoOpExternalProvider;
1502 let nav = MockGroupNavigator::new()
1505 .with_group(
1506 &["SG4", "SG8"],
1507 0,
1508 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1509 )
1510 .with_group(
1511 &["SG4", "SG8"],
1512 1,
1513 vec![
1514 make_segment("SEQ", vec![vec!["Z01"]]),
1515 make_segment("CCI", vec![vec!["11"]]),
1516 ],
1517 );
1518 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1519
1520 assert_eq!(
1522 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1523 ConditionResult::True,
1524 );
1525 assert_eq!(
1527 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
1528 ConditionResult::False,
1529 );
1530 assert_eq!(
1532 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
1533 ConditionResult::False,
1534 );
1535 }
1536
1537 #[test]
1538 fn test_any_group_has_qualifier_without_fallback() {
1539 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
1540 let external = NoOpExternalProvider;
1541 let ctx = EvaluationContext::new("55001", &external, &segments);
1542
1543 assert_eq!(
1545 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1546 ConditionResult::True,
1547 );
1548 }
1549
1550 #[test]
1551 fn test_collect_group_values() {
1552 let external = NoOpExternalProvider;
1553 let nav = MockGroupNavigator::new()
1554 .with_group(
1555 &["SG4", "SG6"],
1556 0,
1557 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
1558 )
1559 .with_group(
1560 &["SG4", "SG6"],
1561 1,
1562 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
1563 );
1564 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1565
1566 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
1567 assert_eq!(values.len(), 2);
1568 assert_eq!(values[0], (0, "REF001".to_string()));
1569 assert_eq!(values[1], (1, "REF002".to_string()));
1570 }
1571
1572 #[test]
1573 fn test_groups_share_qualified_value() {
1574 let external = NoOpExternalProvider;
1575 let nav = MockGroupNavigator::new()
1579 .with_group(
1580 &["SG4", "SG6"],
1581 0,
1582 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1583 )
1584 .with_group(
1585 &["SG4", "SG8"],
1586 0,
1587 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1588 )
1589 .with_group(
1590 &["SG4", "SG8"],
1591 1,
1592 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
1593 );
1594 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1595
1596 assert_eq!(
1598 ctx.groups_share_qualified_value(
1599 "RFF",
1600 0,
1601 "Z49",
1602 0,
1603 1,
1604 &["SG4", "SG6"],
1605 "SEQ",
1606 1,
1607 0,
1608 &["SG4", "SG8"],
1609 ),
1610 ConditionResult::True,
1611 );
1612 }
1613
1614 #[test]
1615 fn test_groups_share_qualified_value_no_match() {
1616 let external = NoOpExternalProvider;
1617 let nav = MockGroupNavigator::new()
1618 .with_group(
1619 &["SG4", "SG6"],
1620 0,
1621 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1622 )
1623 .with_group(
1624 &["SG4", "SG8"],
1625 0,
1626 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
1627 );
1628 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1629
1630 assert_eq!(
1632 ctx.groups_share_qualified_value(
1633 "RFF",
1634 0,
1635 "Z49",
1636 0,
1637 1,
1638 &["SG4", "SG6"],
1639 "SEQ",
1640 1,
1641 0,
1642 &["SG4", "SG8"],
1643 ),
1644 ConditionResult::False,
1645 );
1646 }
1647
1648 #[test]
1649 fn test_groups_share_qualified_value_no_source() {
1650 let external = NoOpExternalProvider;
1651 let nav = MockGroupNavigator::new()
1653 .with_group(
1654 &["SG4", "SG6"],
1655 0,
1656 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
1657 )
1658 .with_group(
1659 &["SG4", "SG8"],
1660 0,
1661 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1662 );
1663 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1664
1665 assert_eq!(
1667 ctx.groups_share_qualified_value(
1668 "RFF",
1669 0,
1670 "Z49",
1671 0,
1672 1,
1673 &["SG4", "SG6"],
1674 "SEQ",
1675 1,
1676 0,
1677 &["SG4", "SG8"],
1678 ),
1679 ConditionResult::Unknown,
1680 );
1681 }
1682
1683 #[test]
1684 fn test_groups_share_qualified_value_fallback() {
1685 let segments = vec![
1687 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
1688 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
1689 ];
1690 let external = NoOpExternalProvider;
1691 let ctx = EvaluationContext::new("55001", &external, &segments);
1692
1693 assert_eq!(
1694 ctx.groups_share_qualified_value(
1695 "RFF",
1696 0,
1697 "Z49",
1698 0,
1699 1,
1700 &["SG4", "SG6"],
1701 "SEQ",
1702 1,
1703 0,
1704 &["SG4", "SG8"],
1705 ),
1706 ConditionResult::True,
1707 );
1708 }
1709
1710 #[test]
1711 fn test_child_group_pass_throughs_no_navigator() {
1712 let external = NoOpExternalProvider;
1713 let ctx = EvaluationContext::new("55001", &external, &[]);
1714
1715 assert_eq!(
1716 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
1717 0
1718 );
1719 assert!(ctx
1720 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
1721 .is_empty());
1722 assert_eq!(
1723 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
1724 None
1725 );
1726 }
1727
1728 #[test]
1731 fn test_has_segment_matching_found() {
1732 let segments = vec![
1733 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
1734 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
1735 ];
1736 let external = NoOpExternalProvider;
1737 let ctx = EvaluationContext::new("55001", &external, &segments);
1738
1739 assert_eq!(
1740 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
1741 ConditionResult::True,
1742 );
1743 }
1744
1745 #[test]
1746 fn test_has_segment_matching_not_found() {
1747 let segments = vec![make_segment(
1748 "STS",
1749 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
1750 )];
1751 let external = NoOpExternalProvider;
1752 let ctx = EvaluationContext::new("55001", &external, &segments);
1753
1754 assert_eq!(
1755 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
1756 ConditionResult::False,
1757 );
1758 }
1759
1760 #[test]
1761 fn test_has_segment_matching_no_segments() {
1762 let external = NoOpExternalProvider;
1763 let ctx = EvaluationContext::new("55001", &external, &[]);
1764
1765 assert_eq!(
1766 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
1767 ConditionResult::Unknown,
1768 );
1769 }
1770
1771 #[test]
1772 fn test_has_segment_matching_in_group() {
1773 let nav = MockGroupNavigator::new()
1774 .with_group(
1775 &["SG4"],
1776 0,
1777 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
1778 )
1779 .with_group(
1780 &["SG4"],
1781 1,
1782 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
1783 );
1784 let segments = vec![
1785 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
1786 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
1787 ];
1788 let external = NoOpExternalProvider;
1789 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1790
1791 assert_eq!(
1792 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
1793 ConditionResult::True,
1794 );
1795 }
1796
1797 #[test]
1800 fn test_dtm_ge() {
1801 let segments = vec![make_segment(
1802 "DTM",
1803 vec![vec!["137", "202601010000", "303"]],
1804 )];
1805 let external = NoOpExternalProvider;
1806 let ctx = EvaluationContext::new("55001", &external, &segments);
1807
1808 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
1809 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
1810 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
1811 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
1812 }
1813
1814 #[test]
1815 fn test_dtm_lt() {
1816 let segments = vec![make_segment(
1817 "DTM",
1818 vec![vec!["137", "202601010000", "303"]],
1819 )];
1820 let external = NoOpExternalProvider;
1821 let ctx = EvaluationContext::new("55001", &external, &segments);
1822
1823 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
1824 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
1825 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
1826 }
1827
1828 #[test]
1829 fn test_dtm_le() {
1830 let segments = vec![make_segment(
1831 "DTM",
1832 vec![vec!["137", "202601010000", "303"]],
1833 )];
1834 let external = NoOpExternalProvider;
1835 let ctx = EvaluationContext::new("55001", &external, &segments);
1836
1837 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
1838 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
1839 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
1840 }
1841
1842 #[test]
1845 fn test_count_qualified_in_group() {
1846 let nav = MockGroupNavigator::new()
1847 .with_group(
1848 &["SG4", "SG8"],
1849 0,
1850 vec![
1851 make_segment("CCI", vec![vec!["Z23"]]),
1852 make_segment("CCI", vec![vec!["Z30"]]),
1853 ],
1854 )
1855 .with_group(
1856 &["SG4", "SG8"],
1857 1,
1858 vec![make_segment("CCI", vec![vec!["Z23"]])],
1859 );
1860 let segments = vec![
1861 make_segment("CCI", vec![vec!["Z23"]]),
1862 make_segment("CCI", vec![vec!["Z30"]]),
1863 make_segment("CCI", vec![vec!["Z23"]]),
1864 ];
1865 let external = NoOpExternalProvider;
1866 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1867
1868 assert_eq!(
1869 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1870 2
1871 );
1872 assert_eq!(
1873 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
1874 1
1875 );
1876 assert_eq!(
1877 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
1878 0
1879 );
1880 }
1881
1882 #[test]
1883 fn test_count_in_group() {
1884 let nav = MockGroupNavigator::new()
1885 .with_group(
1886 &["SG4", "SG8"],
1887 0,
1888 vec![
1889 make_segment("SEQ", vec![vec!["Z98"]]),
1890 make_segment("CCI", vec![vec!["Z23"]]),
1891 ],
1892 )
1893 .with_group(
1894 &["SG4", "SG8"],
1895 1,
1896 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1897 );
1898 let segments = vec![
1899 make_segment("SEQ", vec![vec!["Z98"]]),
1900 make_segment("CCI", vec![vec!["Z23"]]),
1901 make_segment("SEQ", vec![vec!["Z01"]]),
1902 ];
1903 let external = NoOpExternalProvider;
1904 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
1905
1906 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
1907 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
1908 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
1909 }
1910
1911 #[test]
1912 fn test_count_fallback_no_navigator() {
1913 let segments = vec![
1914 make_segment("CCI", vec![vec!["Z23"]]),
1915 make_segment("CCI", vec![vec!["Z30"]]),
1916 make_segment("CCI", vec![vec!["Z23"]]),
1917 ];
1918 let external = NoOpExternalProvider;
1919 let ctx = EvaluationContext::new("55001", &external, &segments);
1920
1921 assert_eq!(
1923 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
1924 2
1925 );
1926 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
1927 }
1928
1929 #[test]
1932 fn format_check_prefers_resolved_value() {
1933 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
1935 let external = NoOpExternalProvider;
1936 let ctx = EvaluationContext::new("55001", &external, &segments)
1937 .with_resolved(Some("CORRECT"), None);
1938
1939 let result = ctx.format_check("DTM", 0, 1, |v| {
1940 ConditionResult::from(v == "CORRECT")
1941 });
1942 assert_eq!(result, ConditionResult::True);
1943 }
1944
1945 #[test]
1946 fn format_check_falls_back_to_segment_search() {
1947 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
1949 let external = NoOpExternalProvider;
1950 let ctx = EvaluationContext::new("55001", &external, &segments);
1951
1952 let result = ctx.format_check("DTM", 0, 1, |v| {
1953 ConditionResult::from(v == "202501011200")
1954 });
1955 assert_eq!(result, ConditionResult::True);
1956 }
1957
1958 #[test]
1959 fn format_check_returns_false_when_segment_absent() {
1960 let segments: Vec<OwnedSegment> = vec![];
1962 let external = NoOpExternalProvider;
1963 let ctx = EvaluationContext::new("55001", &external, &segments);
1964
1965 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
1966 assert_eq!(result, ConditionResult::False);
1967 }
1968
1969 #[test]
1970 fn format_check_qualified_prefers_resolved_value() {
1971 let segments = vec![
1973 make_segment("DTM", vec![vec!["92", "WRONG"]]),
1974 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
1975 ];
1976 let external = NoOpExternalProvider;
1977 let ctx = EvaluationContext::new("55001", &external, &segments)
1978 .with_resolved(Some("CORRECT"), None);
1979
1980 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
1981 ConditionResult::from(v == "CORRECT")
1982 });
1983 assert_eq!(result, ConditionResult::True);
1984 }
1985
1986 #[test]
1987 fn format_check_qualified_falls_back_to_qualified_search() {
1988 let segments = vec![
1990 make_segment("DTM", vec![vec!["92", "2200"]]),
1991 make_segment("DTM", vec![vec!["163", "0800"]]),
1992 ];
1993 let external = NoOpExternalProvider;
1994 let ctx = EvaluationContext::new("55001", &external, &segments);
1995
1996 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
1998 ConditionResult::from(v == "0800")
1999 });
2000 assert_eq!(result, ConditionResult::True);
2001
2002 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2004 ConditionResult::from(v == "2200")
2005 });
2006 assert_eq!(result2, ConditionResult::False);
2007 }
2008}