1use super::codelist::ArtikelIdCodelist;
4use super::ebd_cluster::EbdClusterLookup;
5use super::evaluator::{ConditionResult, ExternalConditionProvider};
6use mig_types::navigator::GroupNavigator;
7use mig_types::segment::OwnedSegment;
8
9pub struct EvaluationContext<'a> {
14 pub pruefidentifikator: &'a str,
17
18 pub external: &'a dyn ExternalConditionProvider,
21
22 pub segments: &'a [OwnedSegment],
25
26 pub navigator: Option<&'a dyn GroupNavigator>,
29
30 pub resolved_value: Option<&'a str>,
37
38 pub resolved_segment: Option<&'a [Vec<String>]>,
44
45 pub codelist: &'a ArtikelIdCodelist,
49
50 pub ebd_clusters: &'a EbdClusterLookup,
55
56 pub scope: Option<GroupScope<'a>>,
60}
61
62pub struct NoOpGroupNavigator;
64
65#[derive(Debug, Clone, Copy)]
72pub struct GroupScope<'a> {
73 pub path: &'a [(&'a str, usize)],
74}
75
76impl GroupNavigator for NoOpGroupNavigator {
77 fn find_segments_in_group(&self, _: &str, _: &[&str], _: usize) -> Vec<OwnedSegment> {
78 Vec::new()
79 }
80 fn find_segments_with_qualifier_in_group(
81 &self,
82 _: &str,
83 _: usize,
84 _: &str,
85 _: &[&str],
86 _: usize,
87 ) -> Vec<OwnedSegment> {
88 Vec::new()
89 }
90 fn group_instance_count(&self, _: &[&str]) -> usize {
91 0
92 }
93}
94
95impl<'a> EvaluationContext<'a> {
96 pub fn new(
98 pruefidentifikator: &'a str,
99 external: &'a dyn ExternalConditionProvider,
100 segments: &'a [OwnedSegment],
101 ) -> Self {
102 Self {
103 pruefidentifikator,
104 external,
105 segments,
106 navigator: None,
107 resolved_value: None,
108 resolved_segment: None,
109 codelist: ArtikelIdCodelist::embedded(),
110 ebd_clusters: EbdClusterLookup::embedded(),
111 scope: None,
112 }
113 }
114
115 pub fn with_navigator(
117 pruefidentifikator: &'a str,
118 external: &'a dyn ExternalConditionProvider,
119 segments: &'a [OwnedSegment],
120 navigator: &'a dyn GroupNavigator,
121 ) -> Self {
122 Self {
123 pruefidentifikator,
124 external,
125 segments,
126 navigator: Some(navigator),
127 resolved_value: None,
128 resolved_segment: None,
129 codelist: ArtikelIdCodelist::embedded(),
130 ebd_clusters: EbdClusterLookup::embedded(),
131 scope: None,
132 }
133 }
134
135 pub fn with_codelist(mut self, codelist: &'a ArtikelIdCodelist) -> Self {
137 self.codelist = codelist;
138 self
139 }
140
141 pub fn with_ebd_clusters(mut self, ebd_clusters: &'a EbdClusterLookup) -> Self {
143 self.ebd_clusters = ebd_clusters;
144 self
145 }
146
147 pub fn with_scope(&self, scope: GroupScope<'a>) -> Self {
151 Self {
152 scope: Some(scope),
153 ..*self
154 }
155 }
156
157 pub fn with_view<'b>(
161 &self,
162 segments: &'b [OwnedSegment],
163 navigator: &'b dyn GroupNavigator,
164 ) -> EvaluationContext<'b>
165 where
166 'a: 'b,
167 {
168 EvaluationContext {
169 pruefidentifikator: self.pruefidentifikator,
170 external: self.external,
171 segments,
172 navigator: Some(navigator),
173 resolved_value: self.resolved_value,
174 resolved_segment: self.resolved_segment,
175 codelist: self.codelist,
176 ebd_clusters: self.ebd_clusters,
177 scope: None,
178 }
179 }
180
181 pub fn with_resolved(
186 &self,
187 value: Option<&'a str>,
188 segment: Option<&'a [Vec<String>]>,
189 ) -> Self {
190 Self {
191 resolved_value: value,
192 resolved_segment: segment,
193 ..*self
194 }
195 }
196
197 pub fn navigator(&self) -> Option<&'a dyn GroupNavigator> {
199 self.navigator
200 }
201
202 pub fn find_segment(&self, segment_id: &str) -> Option<&'a OwnedSegment> {
204 self.segments.iter().find(|s| s.id == segment_id)
205 }
206
207 pub fn find_segments(&self, segment_id: &str) -> Vec<&'a OwnedSegment> {
209 self.segments
210 .iter()
211 .filter(|s| s.id == segment_id)
212 .collect()
213 }
214
215 pub fn find_segments_with_qualifier(
217 &self,
218 segment_id: &str,
219 element_index: usize,
220 qualifier: &str,
221 ) -> Vec<&'a OwnedSegment> {
222 self.segments
223 .iter()
224 .filter(|s| {
225 s.id == segment_id
226 && s.elements
227 .get(element_index)
228 .and_then(|e| e.first())
229 .is_some_and(|v| v == qualifier)
230 })
231 .collect()
232 }
233
234 pub fn scoped_find_segments(&self, tag: &str) -> Vec<OwnedSegment> {
244 match (self.scope, self.navigator) {
245 (Some(scope), Some(nav)) => nav.find_segments_in_subtree(tag, scope.path),
246 _ => self.find_segments(tag).into_iter().cloned().collect(),
247 }
248 }
249
250 pub fn scoped_find_segments_in(&self, group_id: &str, tag: &str) -> Vec<OwnedSegment> {
258 if let (Some(scope), Some(nav)) = (self.scope, self.navigator) {
259 if let Some(level) = scope.path.iter().rposition(|(id, _)| *id == group_id) {
260 return nav.find_segments_in_subtree(tag, &scope.path[..=level]);
261 }
262 }
263 self.find_segments(tag).into_iter().cloned().collect()
264 }
265
266 pub fn sibling_scopes(&self, group_id: &str) -> Option<Vec<Vec<(&'a str, usize)>>> {
271 let (scope, nav) = (self.scope?, self.navigator?);
272 let level = scope.path.iter().rposition(|(id, _)| *id == group_id)?;
273 let (parent, own) = (&scope.path[..level], scope.path[level]);
274 let count = nav.child_instance_count(parent, own.0);
275 Some(
276 (0..count)
277 .filter(|&i| i != own.1)
278 .map(|i| {
279 let mut path = parent.to_vec();
280 path.push((own.0, i));
281 path
282 })
283 .collect(),
284 )
285 }
286
287 pub fn find_segments_at(&self, path: &[(&str, usize)], tag: &str) -> Vec<OwnedSegment> {
290 self.navigator
291 .map(|nav| nav.find_segments_in_subtree(tag, path))
292 .unwrap_or_default()
293 }
294
295 pub fn scoped_find_segments_in_with_qualifier(
298 &self,
299 group_id: &str,
300 tag: &str,
301 element_index: usize,
302 qualifier: &str,
303 ) -> Vec<OwnedSegment> {
304 self.scoped_find_segments_in(group_id, tag)
305 .into_iter()
306 .filter(|s| {
307 s.elements
308 .get(element_index)
309 .and_then(|e| e.first())
310 .is_some_and(|v| v == qualifier)
311 })
312 .collect()
313 }
314
315 pub fn scoped_find_segments_with_qualifier(
318 &self,
319 tag: &str,
320 element_index: usize,
321 qualifier: &str,
322 ) -> Vec<OwnedSegment> {
323 self.scoped_find_segments(tag)
324 .into_iter()
325 .filter(|s| {
326 s.elements
327 .get(element_index)
328 .and_then(|e| e.first())
329 .is_some_and(|v| v == qualifier)
330 })
331 .collect()
332 }
333
334 pub fn format_check(
344 &self,
345 tag: &str,
346 elem: usize,
347 comp: usize,
348 validate: impl FnOnce(&str) -> ConditionResult,
349 ) -> ConditionResult {
350 if let Some(val) = self.resolved_value {
352 return validate(val);
353 }
354 let segs = self.find_segments(tag);
359 if segs.len() != 1 {
360 return ConditionResult::Unknown;
361 }
362 match segs[0]
363 .elements
364 .get(elem)
365 .and_then(|e| e.get(comp))
366 .map(|s| s.as_str())
367 {
368 Some(val) => validate(val),
369 None => ConditionResult::Unknown,
370 }
371 }
372
373 pub fn format_check_qualified(
382 &self,
383 tag: &str,
384 qual_elem: usize,
385 qualifier: &str,
386 val_elem: usize,
387 val_comp: usize,
388 validate: impl FnOnce(&str) -> ConditionResult,
389 ) -> ConditionResult {
390 if let Some(val) = self.resolved_value {
391 return validate(val);
392 }
393 let segs = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
394 if segs.len() != 1 {
395 return ConditionResult::Unknown;
396 }
397 match segs[0]
398 .elements
399 .get(val_elem)
400 .and_then(|e| e.get(val_comp))
401 .map(|s| s.as_str())
402 {
403 Some(val) => validate(val),
404 None => ConditionResult::Unknown,
405 }
406 }
407
408 pub fn self_segment_value(&self, tag: &str, elem: usize, comp: usize) -> Option<&str> {
420 if let Some(elements) = self.resolved_segment {
421 return elements
422 .get(elem)
423 .and_then(|e| e.get(comp))
424 .map(|s| s.as_str());
425 }
426 self.segments
427 .iter()
428 .find(|s| s.id == tag)
429 .and_then(|s| s.elements.get(elem))
430 .and_then(|e| e.get(comp))
431 .map(|s| s.as_str())
432 }
433
434 pub fn self_segment_value_equals(
438 &self,
439 tag: &str,
440 elem: usize,
441 comp: usize,
442 expected: &str,
443 ) -> bool {
444 self.self_segment_value(tag, elem, comp)
445 .is_some_and(|v| v == expected)
446 }
447
448 pub fn has_segment(&self, segment_id: &str) -> bool {
450 self.segments.iter().any(|s| s.id == segment_id)
451 }
452
453 pub fn count_sg8_obis_matches<F>(&self, obis_pred: F) -> usize
475 where
476 F: Fn(&str) -> bool,
477 {
478 let resolved = match self.resolved_segment {
479 Some(s) => s,
480 None => return 0,
481 };
482 let cur_qual = match resolved.first().and_then(|e| e.first()) {
483 Some(v) => v.as_str(),
484 None => return 0,
485 };
486 if !matches!(cur_qual, "Z02" | "ZA1" | "ZA2") {
487 return 0;
488 }
489 let cur_zid = match resolved.get(1).and_then(|e| e.first()) {
490 Some(v) => v.as_str(),
491 None => return 0,
492 };
493
494 let mut count = 0usize;
495 let mut in_matching_seq = false;
496 for seg in self.segments {
497 if seg.id == "SEQ" {
498 let qual = seg
499 .elements
500 .first()
501 .and_then(|e| e.first())
502 .map(String::as_str);
503 let zid = seg
504 .elements
505 .get(1)
506 .and_then(|e| e.first())
507 .map(String::as_str);
508 in_matching_seq = qual == Some(cur_qual) && zid == Some(cur_zid);
509 } else if seg.id == "PIA" && in_matching_seq {
510 let p_qual = seg
511 .elements
512 .first()
513 .and_then(|e| e.first())
514 .map(String::as_str);
515 if p_qual == Some("5") {
516 if let Some(obis) = seg.elements.get(1).and_then(|e| e.first()) {
517 if obis_pred(obis) {
518 count += 1;
519 }
520 }
521 }
522 }
523 }
524 count
525 }
526
527 pub fn find_segments_in_group(
530 &self,
531 segment_id: &str,
532 group_path: &[&str],
533 instance_index: usize,
534 ) -> Vec<OwnedSegment> {
535 match self.navigator {
536 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
537 None => Vec::new(),
538 }
539 }
540
541 pub fn find_segments_with_qualifier_in_group(
544 &self,
545 segment_id: &str,
546 element_index: usize,
547 qualifier: &str,
548 group_path: &[&str],
549 instance_index: usize,
550 ) -> Vec<OwnedSegment> {
551 match self.navigator {
552 Some(nav) => nav.find_segments_with_qualifier_in_group(
553 segment_id,
554 element_index,
555 qualifier,
556 group_path,
557 instance_index,
558 ),
559 None => Vec::new(),
560 }
561 }
562
563 pub fn has_segment_in_group(
566 &self,
567 segment_id: &str,
568 group_path: &[&str],
569 instance_index: usize,
570 ) -> bool {
571 !self
572 .find_segments_in_group(segment_id, group_path, instance_index)
573 .is_empty()
574 }
575
576 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
579 match self.navigator {
580 Some(nav) => nav.group_instance_count(group_path),
581 None => 0,
582 }
583 }
584
585 pub fn child_group_instance_count(
588 &self,
589 parent_path: &[&str],
590 parent_instance: usize,
591 child_group_id: &str,
592 ) -> usize {
593 match self.navigator {
594 Some(nav) => {
595 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
596 }
597 None => 0,
598 }
599 }
600
601 pub fn find_segments_in_child_group(
604 &self,
605 segment_id: &str,
606 parent_path: &[&str],
607 parent_instance: usize,
608 child_group_id: &str,
609 child_instance: usize,
610 ) -> Vec<OwnedSegment> {
611 match self.navigator {
612 Some(nav) => nav.find_segments_in_child_group(
613 segment_id,
614 parent_path,
615 parent_instance,
616 child_group_id,
617 child_instance,
618 ),
619 None => Vec::new(),
620 }
621 }
622
623 pub fn extract_value_in_group(
626 &self,
627 segment_id: &str,
628 element_index: usize,
629 component_index: usize,
630 group_path: &[&str],
631 instance_index: usize,
632 ) -> Option<String> {
633 self.navigator?.extract_value_in_group(
634 segment_id,
635 element_index,
636 component_index,
637 group_path,
638 instance_index,
639 )
640 }
641
642 pub fn has_qualifier(
648 &self,
649 tag: &str,
650 element_index: usize,
651 qualifier: &str,
652 ) -> ConditionResult {
653 ConditionResult::from(
654 !self
655 .find_segments_with_qualifier(tag, element_index, qualifier)
656 .is_empty(),
657 )
658 }
659
660 pub fn lacks_qualifier(
663 &self,
664 tag: &str,
665 element_index: usize,
666 qualifier: &str,
667 ) -> ConditionResult {
668 ConditionResult::from(
669 self.find_segments_with_qualifier(tag, element_index, qualifier)
670 .is_empty(),
671 )
672 }
673
674 pub fn has_qualified_value(
679 &self,
680 tag: &str,
681 qual_elem: usize,
682 qualifier: &str,
683 value_elem: usize,
684 value_comp: usize,
685 values: &[&str],
686 ) -> ConditionResult {
687 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
688 if segments.is_empty() {
689 return ConditionResult::Unknown;
690 }
691 for seg in &segments {
692 if let Some(v) = seg
693 .elements
694 .get(value_elem)
695 .and_then(|e| e.get(value_comp))
696 .map(|s| s.as_str())
697 {
698 if values.contains(&v) {
699 return ConditionResult::True;
700 }
701 }
702 }
703 ConditionResult::False
704 }
705
706 pub fn any_group_has_qualifier(
712 &self,
713 tag: &str,
714 element_index: usize,
715 qualifier: &str,
716 group_path: &[&str],
717 ) -> ConditionResult {
718 let instance_count = self.group_instance_count(group_path);
719 if instance_count > 0 {
720 for i in 0..instance_count {
721 if !self
722 .find_segments_with_qualifier_in_group(
723 tag,
724 element_index,
725 qualifier,
726 group_path,
727 i,
728 )
729 .is_empty()
730 {
731 return ConditionResult::True;
732 }
733 }
734 return ConditionResult::False;
735 }
736 self.has_qualifier(tag, element_index, qualifier)
738 }
739
740 pub fn any_group_has_any_qualifier(
746 &self,
747 tag: &str,
748 element_index: usize,
749 qualifiers: &[&str],
750 group_path: &[&str],
751 ) -> ConditionResult {
752 let instance_count = self.group_instance_count(group_path);
753 if instance_count > 0 {
754 for i in 0..instance_count {
755 let segs = self.find_segments_in_group(tag, group_path, i);
756 if segs.iter().any(|seg| {
757 seg.elements
758 .get(element_index)
759 .and_then(|e| e.first())
760 .map(|s| s.as_str())
761 .is_some_and(|v| qualifiers.contains(&v))
762 }) {
763 return ConditionResult::True;
764 }
765 }
766 return ConditionResult::False;
767 }
768 let found = self.find_segments(tag).iter().any(|seg| {
770 seg.elements
771 .get(element_index)
772 .and_then(|e| e.first())
773 .map(|s| s.as_str())
774 .is_some_and(|v| qualifiers.contains(&v))
775 });
776 ConditionResult::from(found)
777 }
778
779 pub fn any_group_has_qualified_value(
785 &self,
786 tag: &str,
787 qual_elem: usize,
788 qualifier: &str,
789 value_elem: usize,
790 value_comp: usize,
791 values: &[&str],
792 group_path: &[&str],
793 ) -> ConditionResult {
794 let instance_count = self.group_instance_count(group_path);
795 if instance_count > 0 {
796 for i in 0..instance_count {
797 let segs = self.find_segments_with_qualifier_in_group(
798 tag, qual_elem, qualifier, group_path, i,
799 );
800 for seg in &segs {
801 if seg
802 .elements
803 .get(value_elem)
804 .and_then(|e| e.get(value_comp))
805 .map(|s| s.as_str())
806 .is_some_and(|v| values.contains(&v))
807 {
808 return ConditionResult::True;
809 }
810 }
811 }
812 return ConditionResult::False;
813 }
814 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
816 }
817
818 #[allow(clippy::too_many_arguments)]
827 pub fn filtered_parent_child_has_qualifier(
828 &self,
829 parent_path: &[&str],
830 parent_tag: &str,
831 parent_elem: usize,
832 parent_qual: &str,
833 child_group_id: &str,
834 child_tag: &str,
835 child_elem: usize,
836 child_qual: &str,
837 ) -> ConditionResult {
838 let parent_count = self.group_instance_count(parent_path);
839 if parent_count > 0 {
840 for pi in 0..parent_count {
841 let parent_segs = self.find_segments_with_qualifier_in_group(
843 parent_tag,
844 parent_elem,
845 parent_qual,
846 parent_path,
847 pi,
848 );
849 if parent_segs.is_empty() {
850 continue;
851 }
852 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
854 for ci in 0..child_count {
855 let child_segs = self.find_segments_in_child_group(
856 child_tag,
857 parent_path,
858 pi,
859 child_group_id,
860 ci,
861 );
862 if child_segs.iter().any(|s| {
863 s.elements
864 .get(child_elem)
865 .and_then(|e| e.first())
866 .is_some_and(|v| v == child_qual)
867 }) {
868 return ConditionResult::True;
869 }
870 }
871 }
872 return ConditionResult::False;
873 }
874 let has_parent = !self
876 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
877 .is_empty();
878 let has_child = !self
879 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
880 .is_empty();
881 ConditionResult::from(has_parent && has_child)
882 }
883
884 #[allow(clippy::too_many_arguments)]
890 pub fn any_group_has_qualifier_without(
891 &self,
892 present_tag: &str,
893 present_elem: usize,
894 present_qual: &str,
895 absent_tag: &str,
896 absent_elem: usize,
897 absent_qual: &str,
898 group_path: &[&str],
899 ) -> ConditionResult {
900 let instance_count = self.group_instance_count(group_path);
901 if instance_count > 0 {
902 for i in 0..instance_count {
903 let has_present = !self
904 .find_segments_with_qualifier_in_group(
905 present_tag,
906 present_elem,
907 present_qual,
908 group_path,
909 i,
910 )
911 .is_empty();
912 let has_absent = !self
913 .find_segments_with_qualifier_in_group(
914 absent_tag,
915 absent_elem,
916 absent_qual,
917 group_path,
918 i,
919 )
920 .is_empty();
921 if has_present && !has_absent {
922 return ConditionResult::True;
923 }
924 }
925 return ConditionResult::False;
926 }
927 let has_present = !self
929 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
930 .is_empty();
931 let has_absent = !self
932 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
933 .is_empty();
934 ConditionResult::from(has_present && !has_absent)
935 }
936
937 fn enclosing_path(&self, group_path: &[&str]) -> Option<&'a [(&'a str, usize)]> {
940 let group = group_path.last()?;
941 let scope = self.scope?;
942 self.navigator?;
943 let level = scope.path.iter().rposition(|(id, _)| id == group)?;
944 Some(&scope.path[..=level])
945 }
946
947 pub fn enclosing_scope(&self, group_id: &str) -> Option<&'a [(&'a str, usize)]> {
951 self.enclosing_path(&[group_id])
952 }
953
954 pub fn enclosing_child_segments(
959 &self,
960 group_id: &str,
961 child_id: &str,
962 tags: &[&str],
963 ) -> Option<Vec<Vec<OwnedSegment>>> {
964 let path = self.enclosing_scope(group_id)?;
965 Some(self.child_segments_at(path, child_id, tags))
966 }
967
968 pub fn child_segments_at(
971 &self,
972 path: &[(&str, usize)],
973 child_id: &str,
974 tags: &[&str],
975 ) -> Vec<Vec<OwnedSegment>> {
976 let Some(nav) = self.navigator else {
977 return Vec::new();
978 };
979 (0..nav.child_instance_count(path, child_id))
980 .map(|j| {
981 let mut child = path.to_vec();
982 child.push((child_id, j));
983 tags.iter()
984 .flat_map(|tag| nav.find_segments_in_subtree(tag, &child))
985 .collect()
986 })
987 .collect()
988 }
989
990 pub fn scopes_under(
995 &self,
996 parent_id: &str,
997 group_id: &'a str,
998 ) -> Option<Vec<Vec<(&'a str, usize)>>> {
999 let parent = self.enclosing_scope(parent_id)?;
1000 let nav = self.navigator?;
1001 Some(
1002 (0..nav.child_instance_count(parent, group_id))
1003 .map(|i| {
1004 let mut path = parent.to_vec();
1005 path.push((group_id, i));
1006 path
1007 })
1008 .collect(),
1009 )
1010 }
1011
1012 #[allow(clippy::too_many_arguments)]
1016 pub fn this_group_has_qualifier_without(
1017 &self,
1018 present_tag: &str,
1019 present_elem: usize,
1020 present_qual: &str,
1021 absent_tag: &str,
1022 absent_elem: usize,
1023 absent_qual: &str,
1024 group_path: &[&str],
1025 ) -> ConditionResult {
1026 let Some(path) = self.enclosing_path(group_path) else {
1027 return self.any_group_has_qualifier_without(
1028 present_tag,
1029 present_elem,
1030 present_qual,
1031 absent_tag,
1032 absent_elem,
1033 absent_qual,
1034 group_path,
1035 );
1036 };
1037 let has = |tag: &str, elem: usize, qual: &str| {
1038 self.find_segments_at(path, tag)
1039 .iter()
1040 .any(|s| first_component(s, elem) == Some(qual))
1041 };
1042 ConditionResult::from(
1043 has(present_tag, present_elem, present_qual)
1044 && !has(absent_tag, absent_elem, absent_qual),
1045 )
1046 }
1047
1048 pub fn this_group_lacks_qualifier(
1058 &self,
1059 tag: &str,
1060 element_index: usize,
1061 qualifier: &str,
1062 group_path: &[&str],
1063 ) -> ConditionResult {
1064 match self.enclosing_path(group_path) {
1065 Some(path) => ConditionResult::from(
1066 !self
1067 .find_segments_at(path, tag)
1068 .iter()
1069 .any(|s| first_component(s, element_index) == Some(qualifier)),
1070 ),
1071 None => ConditionResult::Unknown,
1072 }
1073 }
1074
1075 #[allow(clippy::too_many_arguments)]
1079 pub fn this_group_parent_child_has_qualifier(
1080 &self,
1081 parent_path: &[&str],
1082 parent_tag: &str,
1083 parent_elem: usize,
1084 parent_qual: &str,
1085 child_group_id: &str,
1086 child_tag: &str,
1087 child_elem: usize,
1088 child_qual: &str,
1089 ) -> ConditionResult {
1090 let (Some(path), Some(nav)) = (self.enclosing_path(parent_path), self.navigator) else {
1091 return self.filtered_parent_child_has_qualifier(
1092 parent_path,
1093 parent_tag,
1094 parent_elem,
1095 parent_qual,
1096 child_group_id,
1097 child_tag,
1098 child_elem,
1099 child_qual,
1100 );
1101 };
1102 let parent_matches = nav
1103 .find_segments_in_instance(parent_tag, path)
1104 .iter()
1105 .any(|s| first_component(s, parent_elem) == Some(parent_qual));
1106 if !parent_matches {
1107 return ConditionResult::False;
1108 }
1109 let children = nav.child_instance_count(path, child_group_id);
1110 let found = (0..children).any(|j| {
1111 let mut child = path.to_vec();
1112 child.push((child_group_id, j));
1113 nav.find_segments_in_subtree(child_tag, &child)
1114 .iter()
1115 .any(|s| first_component(s, child_elem) == Some(child_qual))
1116 });
1117 ConditionResult::from(found)
1118 }
1119
1120 pub fn this_group_has_qualifier(
1124 &self,
1125 tag: &str,
1126 element_index: usize,
1127 qualifier: &str,
1128 group_path: &[&str],
1129 ) -> ConditionResult {
1130 match self.enclosing_path(group_path) {
1131 Some(path) => ConditionResult::from(
1132 self.find_segments_at(path, tag)
1133 .iter()
1134 .any(|s| first_component(s, element_index) == Some(qualifier)),
1135 ),
1136 None => self.any_group_has_qualifier(tag, element_index, qualifier, group_path),
1137 }
1138 }
1139
1140 pub fn this_group_has_any_qualifier(
1143 &self,
1144 tag: &str,
1145 element_index: usize,
1146 qualifiers: &[&str],
1147 group_path: &[&str],
1148 ) -> ConditionResult {
1149 match self.enclosing_path(group_path) {
1150 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1151 first_component(s, element_index).is_some_and(|v| qualifiers.contains(&v))
1152 })),
1153 None => self.any_group_has_any_qualifier(tag, element_index, qualifiers, group_path),
1154 }
1155 }
1156
1157 #[allow(clippy::too_many_arguments)]
1160 pub fn this_group_has_qualified_value(
1161 &self,
1162 tag: &str,
1163 qual_elem: usize,
1164 qualifier: &str,
1165 value_elem: usize,
1166 value_comp: usize,
1167 values: &[&str],
1168 group_path: &[&str],
1169 ) -> ConditionResult {
1170 match self.enclosing_path(group_path) {
1171 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1172 first_component(s, qual_elem) == Some(qualifier)
1173 && s.elements
1174 .get(value_elem)
1175 .and_then(|e| e.get(value_comp))
1176 .is_some_and(|v| values.contains(&v.as_str()))
1177 })),
1178 None => self.any_group_has_qualified_value(
1179 tag, qual_elem, qualifier, value_elem, value_comp, values, group_path,
1180 ),
1181 }
1182 }
1183
1184 pub fn this_group_values(
1188 &self,
1189 tag: &str,
1190 elem: usize,
1191 comp: usize,
1192 group_path: &[&str],
1193 ) -> Vec<(usize, String)> {
1194 match self.enclosing_path(group_path) {
1195 Some(path) => self
1196 .find_segments_at(path, tag)
1197 .iter()
1198 .filter_map(|s| s.elements.get(elem).and_then(|e| e.get(comp)).cloned())
1199 .filter(|v| !v.is_empty())
1200 .map(|v| (0, v))
1201 .collect(),
1202 None => self.collect_group_values(tag, elem, comp, group_path),
1203 }
1204 }
1205
1206 pub fn collect_group_values(
1210 &self,
1211 tag: &str,
1212 elem: usize,
1213 comp: usize,
1214 group_path: &[&str],
1215 ) -> Vec<(usize, String)> {
1216 let instance_count = self.group_instance_count(group_path);
1217 let mut results = Vec::new();
1218 for i in 0..instance_count {
1219 if let Some(val) = self
1220 .navigator
1221 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
1222 {
1223 if !val.is_empty() {
1224 results.push((i, val));
1225 }
1226 }
1227 }
1228 results
1229 }
1230
1231 #[allow(clippy::too_many_arguments)]
1240 pub fn groups_share_qualified_value(
1241 &self,
1242 source_tag: &str,
1243 source_qual_elem: usize,
1244 source_qual: &str,
1245 source_value_elem: usize,
1246 source_value_comp: usize,
1247 source_path: &[&str],
1248 target_tag: &str,
1249 target_elem: usize,
1250 target_comp: usize,
1251 target_path: &[&str],
1252 ) -> ConditionResult {
1253 let source_count = self.group_instance_count(source_path);
1254 let target_count = self.group_instance_count(target_path);
1255 if source_count > 0 && target_count > 0 {
1256 let mut source_values = Vec::new();
1258 for si in 0..source_count {
1259 let segs = self.find_segments_with_qualifier_in_group(
1260 source_tag,
1261 source_qual_elem,
1262 source_qual,
1263 source_path,
1264 si,
1265 );
1266 for seg in &segs {
1267 if let Some(val) = seg
1268 .elements
1269 .get(source_value_elem)
1270 .and_then(|e| e.get(source_value_comp))
1271 {
1272 if !val.is_empty() {
1273 source_values.push(val.clone());
1274 }
1275 }
1276 }
1277 }
1278 if source_values.is_empty() {
1279 return ConditionResult::Unknown;
1280 }
1281 let target_values =
1283 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
1284 for (_, tv) in &target_values {
1285 if source_values.iter().any(|sv| sv == tv) {
1286 return ConditionResult::True;
1287 }
1288 }
1289 return ConditionResult::False;
1290 }
1291 let source_segs =
1293 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
1294 let source_vals: Vec<&str> = source_segs
1295 .iter()
1296 .filter_map(|s| {
1297 s.elements
1298 .get(source_value_elem)
1299 .and_then(|e| e.get(source_value_comp))
1300 .map(|v| v.as_str())
1301 })
1302 .filter(|v| !v.is_empty())
1303 .collect();
1304 if source_vals.is_empty() {
1305 return ConditionResult::Unknown;
1306 }
1307 let target_segs = self.find_segments(target_tag);
1308 let has_match = target_segs.iter().any(|s| {
1309 s.elements
1310 .get(target_elem)
1311 .and_then(|e| e.get(target_comp))
1312 .map(|v| v.as_str())
1313 .is_some_and(|v| source_vals.contains(&v))
1314 });
1315 ConditionResult::from(has_match)
1316 }
1317
1318 #[allow(clippy::too_many_arguments)]
1327 pub fn any_group_has_co_occurrence(
1328 &self,
1329 tag_a: &str,
1330 elem_a: usize,
1331 quals_a: &[&str],
1332 tag_b: &str,
1333 elem_b: usize,
1334 comp_b: usize,
1335 vals_b: &[&str],
1336 group_path: &[&str],
1337 ) -> ConditionResult {
1338 let instance_count = self.group_instance_count(group_path);
1339 if instance_count > 0 {
1340 for i in 0..instance_count {
1341 let a_present = self
1342 .find_segments_in_group(tag_a, group_path, i)
1343 .iter()
1344 .any(|seg| {
1345 seg.elements
1346 .get(elem_a)
1347 .and_then(|e| e.first())
1348 .map(|s| s.as_str())
1349 .is_some_and(|v| quals_a.contains(&v))
1350 });
1351 let b_present = self
1352 .find_segments_in_group(tag_b, group_path, i)
1353 .iter()
1354 .any(|seg| {
1355 seg.elements
1356 .get(elem_b)
1357 .and_then(|e| e.get(comp_b))
1358 .map(|s| s.as_str())
1359 .is_some_and(|v| vals_b.contains(&v))
1360 });
1361 if a_present && b_present {
1362 return ConditionResult::True;
1363 }
1364 }
1365 return ConditionResult::False;
1366 }
1367 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1369 seg.elements
1370 .get(elem_a)
1371 .and_then(|e| e.first())
1372 .map(|s| s.as_str())
1373 .is_some_and(|v| quals_a.contains(&v))
1374 });
1375 if !a_found {
1376 return ConditionResult::False;
1377 }
1378 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1379 seg.elements
1380 .get(elem_b)
1381 .and_then(|e| e.get(comp_b))
1382 .map(|s| s.as_str())
1383 .is_some_and(|v| vals_b.contains(&v))
1384 });
1385 ConditionResult::from(b_found)
1386 }
1387
1388 pub fn has_segment_matching(
1398 &self,
1399 tag: &str,
1400 checks: &[(usize, usize, &str)],
1401 ) -> ConditionResult {
1402 let segments = self.find_segments(tag);
1403 if segments.is_empty() {
1404 return ConditionResult::Unknown;
1405 }
1406 let found = segments.iter().any(|seg| {
1407 checks.iter().all(|(elem, comp, val)| {
1408 seg.elements
1409 .get(*elem)
1410 .and_then(|e| e.get(*comp))
1411 .is_some_and(|v| v == val)
1412 })
1413 });
1414 ConditionResult::from(found)
1415 }
1416
1417 pub fn has_segment_matching_in_group(
1422 &self,
1423 tag: &str,
1424 checks: &[(usize, usize, &str)],
1425 group_path: &[&str],
1426 ) -> ConditionResult {
1427 let instance_count = self.group_instance_count(group_path);
1428 if instance_count > 0 {
1429 for i in 0..instance_count {
1430 let segs = self.find_segments_in_group(tag, group_path, i);
1431 if segs.iter().any(|seg| {
1432 checks.iter().all(|(elem, comp, val)| {
1433 seg.elements
1434 .get(*elem)
1435 .and_then(|e| e.get(*comp))
1436 .is_some_and(|v| v == val)
1437 })
1438 }) {
1439 return ConditionResult::True;
1440 }
1441 }
1442 return ConditionResult::False;
1443 }
1444 self.has_segment_matching(tag, checks)
1446 }
1447
1448 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1455 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1456 match segs.first() {
1457 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1458 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1459 None => ConditionResult::Unknown,
1460 },
1461 None => ConditionResult::Unknown,
1462 }
1463 }
1464
1465 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1467 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1468 match segs.first() {
1469 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1470 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1471 None => ConditionResult::Unknown,
1472 },
1473 None => ConditionResult::Unknown,
1474 }
1475 }
1476
1477 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1479 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1480 match segs.first() {
1481 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1482 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1483 None => ConditionResult::Unknown,
1484 },
1485 None => ConditionResult::Unknown,
1486 }
1487 }
1488
1489 pub fn count_qualified_in_group(
1496 &self,
1497 tag: &str,
1498 element_index: usize,
1499 qualifier: &str,
1500 group_path: &[&str],
1501 ) -> usize {
1502 let instance_count = self.group_instance_count(group_path);
1503 if instance_count > 0 {
1504 let mut total = 0;
1505 for i in 0..instance_count {
1506 total += self
1507 .find_segments_with_qualifier_in_group(
1508 tag,
1509 element_index,
1510 qualifier,
1511 group_path,
1512 i,
1513 )
1514 .len();
1515 }
1516 return total;
1517 }
1518 self.find_segments_with_qualifier(tag, element_index, qualifier)
1520 .len()
1521 }
1522
1523 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1525 let instance_count = self.group_instance_count(group_path);
1526 if instance_count > 0 {
1527 let mut total = 0;
1528 for i in 0..instance_count {
1529 total += self.find_segments_in_group(tag, group_path, i).len();
1530 }
1531 return total;
1532 }
1533 self.find_segments(tag).len()
1535 }
1536}
1537
1538fn first_component(segment: &OwnedSegment, element: usize) -> Option<&str> {
1540 segment
1541 .elements
1542 .get(element)
1543 .and_then(|e| e.first())
1544 .map(String::as_str)
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549 use super::super::evaluator::NoOpExternalProvider;
1550 use super::*;
1551 use mig_types::navigator::GroupNavigator;
1552
1553 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1554 OwnedSegment {
1555 id: id.to_string(),
1556 elements: elements
1557 .into_iter()
1558 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1559 .collect(),
1560 segment_number: 0,
1561 }
1562 }
1563
1564 struct MockGroupNavigator {
1566 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1567 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1569 }
1570
1571 impl MockGroupNavigator {
1572 fn new() -> Self {
1573 Self {
1574 groups: vec![],
1575 children: vec![],
1576 }
1577 }
1578 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1579 self.groups
1580 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1581 self
1582 }
1583 fn with_child_group(
1584 mut self,
1585 parent_path: &[&str],
1586 parent_instance: usize,
1587 child_id: &str,
1588 child_instance: usize,
1589 segs: Vec<OwnedSegment>,
1590 ) -> Self {
1591 self.children.push((
1592 parent_path.iter().map(|s| s.to_string()).collect(),
1593 parent_instance,
1594 child_id.to_string(),
1595 child_instance,
1596 segs,
1597 ));
1598 self
1599 }
1600 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1601 self.groups
1602 .iter()
1603 .find(|(p, i, _)| {
1604 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1605 ps.as_slice() == group_path && *i == idx
1606 })
1607 .map(|(_, _, segs)| segs.as_slice())
1608 }
1609 }
1610
1611 impl GroupNavigator for MockGroupNavigator {
1612 fn find_segments_in_group(
1613 &self,
1614 segment_id: &str,
1615 group_path: &[&str],
1616 instance_index: usize,
1617 ) -> Vec<OwnedSegment> {
1618 self.find_instance(group_path, instance_index)
1619 .map(|segs| {
1620 segs.iter()
1621 .filter(|s| s.id == segment_id)
1622 .cloned()
1623 .collect()
1624 })
1625 .unwrap_or_default()
1626 }
1627 fn find_segments_with_qualifier_in_group(
1628 &self,
1629 segment_id: &str,
1630 element_index: usize,
1631 qualifier: &str,
1632 group_path: &[&str],
1633 instance_index: usize,
1634 ) -> Vec<OwnedSegment> {
1635 self.find_segments_in_group(segment_id, group_path, instance_index)
1636 .into_iter()
1637 .filter(|s| {
1638 s.elements
1639 .get(element_index)
1640 .and_then(|e| e.first())
1641 .is_some_and(|v| v == qualifier)
1642 })
1643 .collect()
1644 }
1645 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1646 self.groups
1647 .iter()
1648 .filter(|(p, _, _)| {
1649 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1650 ps.as_slice() == group_path
1651 })
1652 .count()
1653 }
1654 fn child_group_instance_count(
1655 &self,
1656 parent_path: &[&str],
1657 parent_instance: usize,
1658 child_group_id: &str,
1659 ) -> usize {
1660 self.children
1661 .iter()
1662 .filter(|(pp, pi, cid, _, _)| {
1663 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1664 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1665 })
1666 .count()
1667 }
1668 fn find_segments_in_child_group(
1669 &self,
1670 segment_id: &str,
1671 parent_path: &[&str],
1672 parent_instance: usize,
1673 child_group_id: &str,
1674 child_instance: usize,
1675 ) -> Vec<OwnedSegment> {
1676 self.children
1677 .iter()
1678 .find(|(pp, pi, cid, ci, _)| {
1679 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1680 ps.as_slice() == parent_path
1681 && *pi == parent_instance
1682 && cid == child_group_id
1683 && *ci == child_instance
1684 })
1685 .map(|(_, _, _, _, segs)| {
1686 segs.iter()
1687 .filter(|s| s.id == segment_id)
1688 .cloned()
1689 .collect()
1690 })
1691 .unwrap_or_default()
1692 }
1693 fn extract_value_in_group(
1694 &self,
1695 segment_id: &str,
1696 element_index: usize,
1697 component_index: usize,
1698 group_path: &[&str],
1699 instance_index: usize,
1700 ) -> Option<String> {
1701 let segs = self.find_instance(group_path, instance_index)?;
1702 let seg = segs.iter().find(|s| s.id == segment_id)?;
1703 seg.elements
1704 .get(element_index)?
1705 .get(component_index)
1706 .cloned()
1707 }
1708 }
1709
1710 #[test]
1711 fn test_find_segment() {
1712 let segments = vec![
1713 make_segment("UNH", vec![vec!["test"]]),
1714 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1715 ];
1716 let external = NoOpExternalProvider;
1717 let ctx = EvaluationContext::new("11001", &external, &segments);
1718
1719 assert!(ctx.find_segment("NAD").is_some());
1720 assert!(ctx.find_segment("DTM").is_none());
1721 }
1722
1723 #[test]
1724 fn test_find_segments_with_qualifier() {
1725 let segments = vec![
1726 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1727 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1728 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1729 ];
1730 let external = NoOpExternalProvider;
1731 let ctx = EvaluationContext::new("11001", &external, &segments);
1732
1733 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1734 assert_eq!(ms_nads.len(), 2);
1735 }
1736
1737 #[test]
1738 fn test_has_segment() {
1739 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1740 let external = NoOpExternalProvider;
1741 let ctx = EvaluationContext::new("11001", &external, &segments);
1742
1743 assert!(ctx.has_segment("UNH"));
1744 assert!(!ctx.has_segment("NAD"));
1745 }
1746
1747 #[test]
1750 fn test_no_navigator_group_find_returns_empty() {
1751 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1752 let external = NoOpExternalProvider;
1753 let ctx = EvaluationContext::new("55001", &external, &segments);
1754 assert!(ctx
1755 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1756 .is_empty());
1757 }
1758
1759 #[test]
1760 fn test_no_navigator_group_instance_count_zero() {
1761 let external = NoOpExternalProvider;
1762 let ctx = EvaluationContext::new("55001", &external, &[]);
1763 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1764 }
1765
1766 #[test]
1767 fn test_with_navigator_finds_segments_in_group() {
1768 let external = NoOpExternalProvider;
1769 let nav = MockGroupNavigator::new().with_group(
1770 &["SG4", "SG8"],
1771 0,
1772 vec![
1773 make_segment("SEQ", vec![vec!["Z98"]]),
1774 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1775 ],
1776 );
1777 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1778 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1779 assert_eq!(result.len(), 1);
1780 assert_eq!(result[0].id, "SEQ");
1781 }
1782
1783 #[test]
1784 fn test_with_navigator_qualifier_in_group() {
1785 let external = NoOpExternalProvider;
1786 let nav = MockGroupNavigator::new().with_group(
1787 &["SG4", "SG8"],
1788 0,
1789 vec![
1790 make_segment("SEQ", vec![vec!["Z98"]]),
1791 make_segment("SEQ", vec![vec!["Z01"]]),
1792 ],
1793 );
1794 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1795 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1796 assert_eq!(result.len(), 1);
1797 }
1798
1799 #[test]
1800 fn test_group_instance_count_with_navigator() {
1801 let external = NoOpExternalProvider;
1802 let nav = MockGroupNavigator::new()
1803 .with_group(
1804 &["SG4", "SG8"],
1805 0,
1806 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1807 )
1808 .with_group(
1809 &["SG4", "SG8"],
1810 1,
1811 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1812 );
1813 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1814 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1815 }
1816
1817 #[test]
1818 fn test_has_segment_in_group() {
1819 let external = NoOpExternalProvider;
1820 let nav = MockGroupNavigator::new().with_group(
1821 &["SG4", "SG8"],
1822 0,
1823 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1824 );
1825 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1826 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1827 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1828 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1829 }
1830
1831 #[test]
1834 fn test_has_qualifier() {
1835 let segments = vec![
1836 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1837 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1838 ];
1839 let external = NoOpExternalProvider;
1840 let ctx = EvaluationContext::new("11001", &external, &segments);
1841
1842 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1843 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1844 }
1845
1846 #[test]
1847 fn test_lacks_qualifier() {
1848 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1849 let external = NoOpExternalProvider;
1850 let ctx = EvaluationContext::new("11001", &external, &segments);
1851
1852 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1853 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1854 }
1855
1856 #[test]
1857 fn test_has_qualified_value() {
1858 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1859 let external = NoOpExternalProvider;
1860 let ctx = EvaluationContext::new("55001", &external, &segments);
1861
1862 assert_eq!(
1863 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1864 ConditionResult::True,
1865 );
1866 assert_eq!(
1867 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1868 ConditionResult::False,
1869 );
1870 assert_eq!(
1872 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1873 ConditionResult::Unknown,
1874 );
1875 }
1876
1877 #[test]
1878 fn test_any_group_has_qualifier() {
1879 let external = NoOpExternalProvider;
1880 let nav = MockGroupNavigator::new()
1881 .with_group(
1882 &["SG4", "SG8"],
1883 0,
1884 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1885 )
1886 .with_group(
1887 &["SG4", "SG8"],
1888 1,
1889 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1890 );
1891 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1892
1893 assert_eq!(
1894 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1895 ConditionResult::True,
1896 );
1897 assert_eq!(
1898 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1899 ConditionResult::False,
1900 );
1901 }
1902
1903 #[test]
1904 fn test_any_group_has_qualifier_fallback() {
1905 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1907 let external = NoOpExternalProvider;
1908 let ctx = EvaluationContext::new("55001", &external, &segments);
1909
1910 assert_eq!(
1911 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1912 ConditionResult::True,
1913 );
1914 }
1915
1916 #[test]
1917 fn test_any_group_has_any_qualifier() {
1918 let external = NoOpExternalProvider;
1919 let nav = MockGroupNavigator::new().with_group(
1920 &["SG4", "SG8"],
1921 0,
1922 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1923 );
1924 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1925
1926 assert_eq!(
1927 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1928 ConditionResult::True,
1929 );
1930 assert_eq!(
1931 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1932 ConditionResult::False,
1933 );
1934 }
1935
1936 #[test]
1937 fn test_any_group_has_co_occurrence() {
1938 let external = NoOpExternalProvider;
1939 let nav = MockGroupNavigator::new().with_group(
1940 &["SG4", "SG8"],
1941 0,
1942 vec![
1943 make_segment("SEQ", vec![vec!["Z01"]]),
1944 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1945 ],
1946 );
1947 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1948
1949 assert_eq!(
1950 ctx.any_group_has_co_occurrence(
1951 "SEQ",
1952 0,
1953 &["Z01"],
1954 "CCI",
1955 2,
1956 0,
1957 &["Z07"],
1958 &["SG4", "SG8"],
1959 ),
1960 ConditionResult::True,
1961 );
1962 assert_eq!(
1964 ctx.any_group_has_co_occurrence(
1965 "SEQ",
1966 0,
1967 &["Z01"],
1968 "CCI",
1969 2,
1970 0,
1971 &["ZC0"],
1972 &["SG4", "SG8"],
1973 ),
1974 ConditionResult::False,
1975 );
1976 }
1977
1978 #[test]
1981 fn test_filtered_parent_child_has_qualifier() {
1982 let external = NoOpExternalProvider;
1983 let nav = MockGroupNavigator::new()
1986 .with_group(
1987 &["SG4", "SG8"],
1988 0,
1989 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1990 )
1991 .with_group(
1992 &["SG4", "SG8"],
1993 1,
1994 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1995 )
1996 .with_child_group(
1997 &["SG4", "SG8"],
1998 0,
1999 "SG10",
2000 0,
2001 vec![make_segment("CCI", vec![vec!["Z23"]])],
2002 );
2003 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2004
2005 assert_eq!(
2007 ctx.filtered_parent_child_has_qualifier(
2008 &["SG4", "SG8"],
2009 "SEQ",
2010 0,
2011 "Z98",
2012 "SG10",
2013 "CCI",
2014 0,
2015 "Z23",
2016 ),
2017 ConditionResult::True,
2018 );
2019 assert_eq!(
2021 ctx.filtered_parent_child_has_qualifier(
2022 &["SG4", "SG8"],
2023 "SEQ",
2024 0,
2025 "Z01",
2026 "SG10",
2027 "CCI",
2028 0,
2029 "Z23",
2030 ),
2031 ConditionResult::False,
2032 );
2033 assert_eq!(
2035 ctx.filtered_parent_child_has_qualifier(
2036 &["SG4", "SG8"],
2037 "SEQ",
2038 0,
2039 "Z98",
2040 "SG10",
2041 "CCI",
2042 0,
2043 "Z99",
2044 ),
2045 ConditionResult::False,
2046 );
2047 }
2048
2049 #[test]
2050 fn test_filtered_parent_child_fallback() {
2051 let segments = vec![
2053 make_segment("SEQ", vec![vec!["Z98"]]),
2054 make_segment("CCI", vec![vec!["Z23"]]),
2055 ];
2056 let external = NoOpExternalProvider;
2057 let ctx = EvaluationContext::new("55001", &external, &segments);
2058
2059 assert_eq!(
2060 ctx.filtered_parent_child_has_qualifier(
2061 &["SG4", "SG8"],
2062 "SEQ",
2063 0,
2064 "Z98",
2065 "SG10",
2066 "CCI",
2067 0,
2068 "Z23",
2069 ),
2070 ConditionResult::True,
2071 );
2072 assert_eq!(
2074 ctx.filtered_parent_child_has_qualifier(
2075 &["SG4", "SG8"],
2076 "SEQ",
2077 0,
2078 "Z98",
2079 "SG10",
2080 "CCI",
2081 0,
2082 "Z99",
2083 ),
2084 ConditionResult::False,
2085 );
2086 }
2087
2088 #[test]
2089 fn test_any_group_has_qualifier_without() {
2090 let external = NoOpExternalProvider;
2091 let nav = MockGroupNavigator::new()
2094 .with_group(
2095 &["SG4", "SG8"],
2096 0,
2097 vec![make_segment("SEQ", vec![vec!["Z59"]])],
2098 )
2099 .with_group(
2100 &["SG4", "SG8"],
2101 1,
2102 vec![
2103 make_segment("SEQ", vec![vec!["Z01"]]),
2104 make_segment("CCI", vec![vec!["11"]]),
2105 ],
2106 );
2107 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2108
2109 assert_eq!(
2111 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2112 ConditionResult::True,
2113 );
2114 assert_eq!(
2116 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
2117 ConditionResult::False,
2118 );
2119 assert_eq!(
2121 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
2122 ConditionResult::False,
2123 );
2124 }
2125
2126 #[test]
2127 fn test_any_group_has_qualifier_without_fallback() {
2128 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
2129 let external = NoOpExternalProvider;
2130 let ctx = EvaluationContext::new("55001", &external, &segments);
2131
2132 assert_eq!(
2134 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2135 ConditionResult::True,
2136 );
2137 }
2138
2139 #[test]
2140 fn test_collect_group_values() {
2141 let external = NoOpExternalProvider;
2142 let nav = MockGroupNavigator::new()
2143 .with_group(
2144 &["SG4", "SG6"],
2145 0,
2146 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
2147 )
2148 .with_group(
2149 &["SG4", "SG6"],
2150 1,
2151 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
2152 );
2153 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2154
2155 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
2156 assert_eq!(values.len(), 2);
2157 assert_eq!(values[0], (0, "REF001".to_string()));
2158 assert_eq!(values[1], (1, "REF002".to_string()));
2159 }
2160
2161 #[test]
2162 fn test_groups_share_qualified_value() {
2163 let external = NoOpExternalProvider;
2164 let nav = MockGroupNavigator::new()
2168 .with_group(
2169 &["SG4", "SG6"],
2170 0,
2171 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2172 )
2173 .with_group(
2174 &["SG4", "SG8"],
2175 0,
2176 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2177 )
2178 .with_group(
2179 &["SG4", "SG8"],
2180 1,
2181 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
2182 );
2183 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2184
2185 assert_eq!(
2187 ctx.groups_share_qualified_value(
2188 "RFF",
2189 0,
2190 "Z49",
2191 0,
2192 1,
2193 &["SG4", "SG6"],
2194 "SEQ",
2195 1,
2196 0,
2197 &["SG4", "SG8"],
2198 ),
2199 ConditionResult::True,
2200 );
2201 }
2202
2203 #[test]
2204 fn test_groups_share_qualified_value_no_match() {
2205 let external = NoOpExternalProvider;
2206 let nav = MockGroupNavigator::new()
2207 .with_group(
2208 &["SG4", "SG6"],
2209 0,
2210 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2211 )
2212 .with_group(
2213 &["SG4", "SG8"],
2214 0,
2215 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
2216 );
2217 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2218
2219 assert_eq!(
2221 ctx.groups_share_qualified_value(
2222 "RFF",
2223 0,
2224 "Z49",
2225 0,
2226 1,
2227 &["SG4", "SG6"],
2228 "SEQ",
2229 1,
2230 0,
2231 &["SG4", "SG8"],
2232 ),
2233 ConditionResult::False,
2234 );
2235 }
2236
2237 #[test]
2238 fn test_groups_share_qualified_value_no_source() {
2239 let external = NoOpExternalProvider;
2240 let nav = MockGroupNavigator::new()
2242 .with_group(
2243 &["SG4", "SG6"],
2244 0,
2245 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
2246 )
2247 .with_group(
2248 &["SG4", "SG8"],
2249 0,
2250 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2251 );
2252 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2253
2254 assert_eq!(
2256 ctx.groups_share_qualified_value(
2257 "RFF",
2258 0,
2259 "Z49",
2260 0,
2261 1,
2262 &["SG4", "SG6"],
2263 "SEQ",
2264 1,
2265 0,
2266 &["SG4", "SG8"],
2267 ),
2268 ConditionResult::Unknown,
2269 );
2270 }
2271
2272 #[test]
2273 fn test_groups_share_qualified_value_fallback() {
2274 let segments = vec![
2276 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
2277 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
2278 ];
2279 let external = NoOpExternalProvider;
2280 let ctx = EvaluationContext::new("55001", &external, &segments);
2281
2282 assert_eq!(
2283 ctx.groups_share_qualified_value(
2284 "RFF",
2285 0,
2286 "Z49",
2287 0,
2288 1,
2289 &["SG4", "SG6"],
2290 "SEQ",
2291 1,
2292 0,
2293 &["SG4", "SG8"],
2294 ),
2295 ConditionResult::True,
2296 );
2297 }
2298
2299 #[test]
2300 fn test_child_group_pass_throughs_no_navigator() {
2301 let external = NoOpExternalProvider;
2302 let ctx = EvaluationContext::new("55001", &external, &[]);
2303
2304 assert_eq!(
2305 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
2306 0
2307 );
2308 assert!(ctx
2309 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
2310 .is_empty());
2311 assert_eq!(
2312 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
2313 None
2314 );
2315 }
2316
2317 #[test]
2320 fn test_has_segment_matching_found() {
2321 let segments = vec![
2322 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
2323 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
2324 ];
2325 let external = NoOpExternalProvider;
2326 let ctx = EvaluationContext::new("55001", &external, &segments);
2327
2328 assert_eq!(
2329 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
2330 ConditionResult::True,
2331 );
2332 }
2333
2334 #[test]
2335 fn test_has_segment_matching_not_found() {
2336 let segments = vec![make_segment(
2337 "STS",
2338 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2339 )];
2340 let external = NoOpExternalProvider;
2341 let ctx = EvaluationContext::new("55001", &external, &segments);
2342
2343 assert_eq!(
2344 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2345 ConditionResult::False,
2346 );
2347 }
2348
2349 #[test]
2350 fn test_has_segment_matching_no_segments() {
2351 let external = NoOpExternalProvider;
2352 let ctx = EvaluationContext::new("55001", &external, &[]);
2353
2354 assert_eq!(
2355 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2356 ConditionResult::Unknown,
2357 );
2358 }
2359
2360 #[test]
2361 fn test_has_segment_matching_in_group() {
2362 let nav = MockGroupNavigator::new()
2363 .with_group(
2364 &["SG4"],
2365 0,
2366 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2367 )
2368 .with_group(
2369 &["SG4"],
2370 1,
2371 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2372 );
2373 let segments = vec![
2374 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2375 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2376 ];
2377 let external = NoOpExternalProvider;
2378 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2379
2380 assert_eq!(
2381 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2382 ConditionResult::True,
2383 );
2384 }
2385
2386 #[test]
2389 fn test_dtm_ge() {
2390 let segments = vec![make_segment(
2391 "DTM",
2392 vec![vec!["137", "202601010000", "303"]],
2393 )];
2394 let external = NoOpExternalProvider;
2395 let ctx = EvaluationContext::new("55001", &external, &segments);
2396
2397 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2398 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2399 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2400 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2401 }
2402
2403 #[test]
2404 fn test_dtm_lt() {
2405 let segments = vec![make_segment(
2406 "DTM",
2407 vec![vec!["137", "202601010000", "303"]],
2408 )];
2409 let external = NoOpExternalProvider;
2410 let ctx = EvaluationContext::new("55001", &external, &segments);
2411
2412 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2413 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2414 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2415 }
2416
2417 #[test]
2418 fn test_dtm_le() {
2419 let segments = vec![make_segment(
2420 "DTM",
2421 vec![vec!["137", "202601010000", "303"]],
2422 )];
2423 let external = NoOpExternalProvider;
2424 let ctx = EvaluationContext::new("55001", &external, &segments);
2425
2426 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2427 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2428 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2429 }
2430
2431 #[test]
2434 fn test_count_qualified_in_group() {
2435 let nav = MockGroupNavigator::new()
2436 .with_group(
2437 &["SG4", "SG8"],
2438 0,
2439 vec![
2440 make_segment("CCI", vec![vec!["Z23"]]),
2441 make_segment("CCI", vec![vec!["Z30"]]),
2442 ],
2443 )
2444 .with_group(
2445 &["SG4", "SG8"],
2446 1,
2447 vec![make_segment("CCI", vec![vec!["Z23"]])],
2448 );
2449 let segments = vec![
2450 make_segment("CCI", vec![vec!["Z23"]]),
2451 make_segment("CCI", vec![vec!["Z30"]]),
2452 make_segment("CCI", vec![vec!["Z23"]]),
2453 ];
2454 let external = NoOpExternalProvider;
2455 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2456
2457 assert_eq!(
2458 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2459 2
2460 );
2461 assert_eq!(
2462 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2463 1
2464 );
2465 assert_eq!(
2466 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2467 0
2468 );
2469 }
2470
2471 #[test]
2472 fn test_count_in_group() {
2473 let nav = MockGroupNavigator::new()
2474 .with_group(
2475 &["SG4", "SG8"],
2476 0,
2477 vec![
2478 make_segment("SEQ", vec![vec!["Z98"]]),
2479 make_segment("CCI", vec![vec!["Z23"]]),
2480 ],
2481 )
2482 .with_group(
2483 &["SG4", "SG8"],
2484 1,
2485 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2486 );
2487 let segments = vec![
2488 make_segment("SEQ", vec![vec!["Z98"]]),
2489 make_segment("CCI", vec![vec!["Z23"]]),
2490 make_segment("SEQ", vec![vec!["Z01"]]),
2491 ];
2492 let external = NoOpExternalProvider;
2493 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2494
2495 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2496 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2497 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2498 }
2499
2500 #[test]
2501 fn test_count_fallback_no_navigator() {
2502 let segments = vec![
2503 make_segment("CCI", vec![vec!["Z23"]]),
2504 make_segment("CCI", vec![vec!["Z30"]]),
2505 make_segment("CCI", vec![vec!["Z23"]]),
2506 ];
2507 let external = NoOpExternalProvider;
2508 let ctx = EvaluationContext::new("55001", &external, &segments);
2509
2510 assert_eq!(
2512 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2513 2
2514 );
2515 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2516 }
2517
2518 #[test]
2521 fn format_check_prefers_resolved_value() {
2522 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2524 let external = NoOpExternalProvider;
2525 let ctx = EvaluationContext::new("55001", &external, &segments)
2526 .with_resolved(Some("CORRECT"), None);
2527
2528 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2529 assert_eq!(result, ConditionResult::True);
2530 }
2531
2532 #[test]
2533 fn format_check_falls_back_to_segment_search() {
2534 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2536 let external = NoOpExternalProvider;
2537 let ctx = EvaluationContext::new("55001", &external, &segments);
2538
2539 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2540 assert_eq!(result, ConditionResult::True);
2541 }
2542
2543 #[test]
2544 fn format_check_returns_unknown_when_segment_absent() {
2545 let segments: Vec<OwnedSegment> = vec![];
2547 let external = NoOpExternalProvider;
2548 let ctx = EvaluationContext::new("55001", &external, &segments);
2549
2550 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2551 assert_eq!(result, ConditionResult::Unknown);
2552 }
2553
2554 #[test]
2555 fn format_check_returns_unknown_when_multiple_segments() {
2556 let segments = vec![
2560 make_segment("DTM", vec![vec!["92", "OK"]]),
2561 make_segment("DTM", vec![vec!["163", "BAD"]]),
2562 ];
2563 let external = NoOpExternalProvider;
2564 let ctx = EvaluationContext::new("55001", &external, &segments);
2565
2566 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2567 assert_eq!(result, ConditionResult::Unknown);
2568 }
2569
2570 #[test]
2571 fn format_check_qualified_prefers_resolved_value() {
2572 let segments = vec![
2574 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2575 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2576 ];
2577 let external = NoOpExternalProvider;
2578 let ctx = EvaluationContext::new("55001", &external, &segments)
2579 .with_resolved(Some("CORRECT"), None);
2580
2581 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2582 ConditionResult::from(v == "CORRECT")
2583 });
2584 assert_eq!(result, ConditionResult::True);
2585 }
2586
2587 #[test]
2588 fn format_check_qualified_falls_back_to_qualified_search() {
2589 let segments = vec![
2591 make_segment("DTM", vec![vec!["92", "2200"]]),
2592 make_segment("DTM", vec![vec!["163", "0800"]]),
2593 ];
2594 let external = NoOpExternalProvider;
2595 let ctx = EvaluationContext::new("55001", &external, &segments);
2596
2597 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2599 ConditionResult::from(v == "0800")
2600 });
2601 assert_eq!(result, ConditionResult::True);
2602
2603 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2605 ConditionResult::from(v == "2200")
2606 });
2607 assert_eq!(result2, ConditionResult::False);
2608 }
2609
2610 #[test]
2613 fn self_segment_value_prefers_resolved_segment() {
2614 let segments = vec![
2618 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2619 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2620 ];
2621 let external = NoOpExternalProvider;
2622 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2623 let ctx = EvaluationContext::new("55001", &external, &segments)
2624 .with_resolved(None, Some(&resolved_elements));
2625
2626 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2627 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2628 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2629 }
2630
2631 #[test]
2632 fn self_segment_value_falls_back_when_no_resolved_segment() {
2633 let segments = vec![
2635 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2636 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2637 ];
2638 let external = NoOpExternalProvider;
2639 let ctx = EvaluationContext::new("55001", &external, &segments);
2640
2641 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2642 }
2643
2644 #[test]
2645 fn self_segment_value_missing_returns_none() {
2646 let segments: Vec<OwnedSegment> = vec![];
2647 let external = NoOpExternalProvider;
2648 let ctx = EvaluationContext::new("55001", &external, &segments);
2649
2650 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2651 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2652 }
2653
2654 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2658 let mut segments = Vec::new();
2659 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2660 segments.push(OwnedSegment {
2661 id: "SEQ".to_string(),
2662 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2663 segment_number: (2 * i + 1) as u32,
2664 });
2665 segments.push(OwnedSegment {
2666 id: "PIA".to_string(),
2667 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2668 segment_number: (2 * i + 2) as u32,
2669 });
2670 }
2671 segments
2672 }
2673
2674 #[test]
2675 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2676 let segments = make_sg8_sequence(&[
2678 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:2.8.0"), ("Z02", "ZR2", "1-1:1.8.0"), ("ZA1", "ZR1", "1-1:1.8.0"), ]);
2683 let external = NoOpExternalProvider;
2684 let seq = &segments[0].elements;
2686 let ctx =
2687 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2688
2689 let n = ctx
2690 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2691 assert_eq!(n, 2);
2692 }
2693
2694 #[test]
2695 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2696 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2697 let external = NoOpExternalProvider;
2698 let ctx = EvaluationContext::new("55001", &external, &segments);
2699 let n = ctx
2700 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2701 assert_eq!(n, 0);
2702 }
2703
2704 #[test]
2705 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2706 let segments = make_sg8_sequence(&[
2707 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2710 let external = NoOpExternalProvider;
2711 let seq = &segments[0].elements;
2712 let ctx =
2713 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2714
2715 assert_eq!(
2716 ctx.count_sg8_obis_matches(
2717 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2718 ),
2719 1
2720 );
2721 assert_eq!(
2722 ctx.count_sg8_obis_matches(
2723 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2724 ),
2725 1
2726 );
2727 }
2728}