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 format_check_resolved(
415 &self,
416 validate: impl FnOnce(&str) -> ConditionResult,
417 ) -> ConditionResult {
418 match self.resolved_value {
419 Some(val) => validate(val),
420 None => ConditionResult::Unknown,
421 }
422 }
423
424 pub fn self_segment_value(&self, tag: &str, elem: usize, comp: usize) -> Option<&str> {
436 if let Some(elements) = self.resolved_segment {
437 return elements
438 .get(elem)
439 .and_then(|e| e.get(comp))
440 .map(|s| s.as_str());
441 }
442 self.segments
443 .iter()
444 .find(|s| s.id == tag)
445 .and_then(|s| s.elements.get(elem))
446 .and_then(|e| e.get(comp))
447 .map(|s| s.as_str())
448 }
449
450 pub fn self_segment_value_equals(
454 &self,
455 tag: &str,
456 elem: usize,
457 comp: usize,
458 expected: &str,
459 ) -> bool {
460 self.self_segment_value(tag, elem, comp)
461 .is_some_and(|v| v == expected)
462 }
463
464 pub fn has_segment(&self, segment_id: &str) -> bool {
466 self.segments.iter().any(|s| s.id == segment_id)
467 }
468
469 pub fn count_sg8_obis_matches<F>(&self, obis_pred: F) -> usize
491 where
492 F: Fn(&str) -> bool,
493 {
494 let resolved = match self.resolved_segment {
495 Some(s) => s,
496 None => return 0,
497 };
498 let cur_qual = match resolved.first().and_then(|e| e.first()) {
499 Some(v) => v.as_str(),
500 None => return 0,
501 };
502 if !matches!(cur_qual, "Z02" | "ZA1" | "ZA2") {
503 return 0;
504 }
505 let cur_zid = match resolved.get(1).and_then(|e| e.first()) {
506 Some(v) => v.as_str(),
507 None => return 0,
508 };
509
510 let mut count = 0usize;
511 let mut in_matching_seq = false;
512 for seg in self.segments {
513 if seg.id == "SEQ" {
514 let qual = seg
515 .elements
516 .first()
517 .and_then(|e| e.first())
518 .map(String::as_str);
519 let zid = seg
520 .elements
521 .get(1)
522 .and_then(|e| e.first())
523 .map(String::as_str);
524 in_matching_seq = qual == Some(cur_qual) && zid == Some(cur_zid);
525 } else if seg.id == "PIA" && in_matching_seq {
526 let p_qual = seg
527 .elements
528 .first()
529 .and_then(|e| e.first())
530 .map(String::as_str);
531 if p_qual == Some("5") {
532 if let Some(obis) = seg.elements.get(1).and_then(|e| e.first()) {
533 if obis_pred(obis) {
534 count += 1;
535 }
536 }
537 }
538 }
539 }
540 count
541 }
542
543 pub fn find_segments_in_group(
546 &self,
547 segment_id: &str,
548 group_path: &[&str],
549 instance_index: usize,
550 ) -> Vec<OwnedSegment> {
551 match self.navigator {
552 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
553 None => Vec::new(),
554 }
555 }
556
557 pub fn find_segments_with_qualifier_in_group(
560 &self,
561 segment_id: &str,
562 element_index: usize,
563 qualifier: &str,
564 group_path: &[&str],
565 instance_index: usize,
566 ) -> Vec<OwnedSegment> {
567 match self.navigator {
568 Some(nav) => nav.find_segments_with_qualifier_in_group(
569 segment_id,
570 element_index,
571 qualifier,
572 group_path,
573 instance_index,
574 ),
575 None => Vec::new(),
576 }
577 }
578
579 pub fn has_segment_in_group(
582 &self,
583 segment_id: &str,
584 group_path: &[&str],
585 instance_index: usize,
586 ) -> bool {
587 !self
588 .find_segments_in_group(segment_id, group_path, instance_index)
589 .is_empty()
590 }
591
592 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
595 match self.navigator {
596 Some(nav) => nav.group_instance_count(group_path),
597 None => 0,
598 }
599 }
600
601 pub fn child_group_instance_count(
604 &self,
605 parent_path: &[&str],
606 parent_instance: usize,
607 child_group_id: &str,
608 ) -> usize {
609 match self.navigator {
610 Some(nav) => {
611 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
612 }
613 None => 0,
614 }
615 }
616
617 pub fn find_segments_in_child_group(
620 &self,
621 segment_id: &str,
622 parent_path: &[&str],
623 parent_instance: usize,
624 child_group_id: &str,
625 child_instance: usize,
626 ) -> Vec<OwnedSegment> {
627 match self.navigator {
628 Some(nav) => nav.find_segments_in_child_group(
629 segment_id,
630 parent_path,
631 parent_instance,
632 child_group_id,
633 child_instance,
634 ),
635 None => Vec::new(),
636 }
637 }
638
639 pub fn extract_value_in_group(
642 &self,
643 segment_id: &str,
644 element_index: usize,
645 component_index: usize,
646 group_path: &[&str],
647 instance_index: usize,
648 ) -> Option<String> {
649 self.navigator?.extract_value_in_group(
650 segment_id,
651 element_index,
652 component_index,
653 group_path,
654 instance_index,
655 )
656 }
657
658 pub fn has_qualifier(
664 &self,
665 tag: &str,
666 element_index: usize,
667 qualifier: &str,
668 ) -> ConditionResult {
669 ConditionResult::from(
670 !self
671 .find_segments_with_qualifier(tag, element_index, qualifier)
672 .is_empty(),
673 )
674 }
675
676 pub fn lacks_qualifier(
679 &self,
680 tag: &str,
681 element_index: usize,
682 qualifier: &str,
683 ) -> ConditionResult {
684 ConditionResult::from(
685 self.find_segments_with_qualifier(tag, element_index, qualifier)
686 .is_empty(),
687 )
688 }
689
690 pub fn has_qualified_value(
695 &self,
696 tag: &str,
697 qual_elem: usize,
698 qualifier: &str,
699 value_elem: usize,
700 value_comp: usize,
701 values: &[&str],
702 ) -> ConditionResult {
703 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
704 if segments.is_empty() {
705 return ConditionResult::Unknown;
706 }
707 for seg in &segments {
708 if let Some(v) = seg
709 .elements
710 .get(value_elem)
711 .and_then(|e| e.get(value_comp))
712 .map(|s| s.as_str())
713 {
714 if values.contains(&v) {
715 return ConditionResult::True;
716 }
717 }
718 }
719 ConditionResult::False
720 }
721
722 pub fn any_group_has_qualifier(
728 &self,
729 tag: &str,
730 element_index: usize,
731 qualifier: &str,
732 group_path: &[&str],
733 ) -> ConditionResult {
734 let instance_count = self.group_instance_count(group_path);
735 if instance_count > 0 {
736 for i in 0..instance_count {
737 if !self
738 .find_segments_with_qualifier_in_group(
739 tag,
740 element_index,
741 qualifier,
742 group_path,
743 i,
744 )
745 .is_empty()
746 {
747 return ConditionResult::True;
748 }
749 }
750 return ConditionResult::False;
751 }
752 self.has_qualifier(tag, element_index, qualifier)
754 }
755
756 pub fn any_group_has_any_qualifier(
762 &self,
763 tag: &str,
764 element_index: usize,
765 qualifiers: &[&str],
766 group_path: &[&str],
767 ) -> ConditionResult {
768 let instance_count = self.group_instance_count(group_path);
769 if instance_count > 0 {
770 for i in 0..instance_count {
771 let segs = self.find_segments_in_group(tag, group_path, i);
772 if segs.iter().any(|seg| {
773 seg.elements
774 .get(element_index)
775 .and_then(|e| e.first())
776 .map(|s| s.as_str())
777 .is_some_and(|v| qualifiers.contains(&v))
778 }) {
779 return ConditionResult::True;
780 }
781 }
782 return ConditionResult::False;
783 }
784 let found = self.find_segments(tag).iter().any(|seg| {
786 seg.elements
787 .get(element_index)
788 .and_then(|e| e.first())
789 .map(|s| s.as_str())
790 .is_some_and(|v| qualifiers.contains(&v))
791 });
792 ConditionResult::from(found)
793 }
794
795 pub fn any_group_has_qualified_value(
801 &self,
802 tag: &str,
803 qual_elem: usize,
804 qualifier: &str,
805 value_elem: usize,
806 value_comp: usize,
807 values: &[&str],
808 group_path: &[&str],
809 ) -> ConditionResult {
810 let instance_count = self.group_instance_count(group_path);
811 if instance_count > 0 {
812 for i in 0..instance_count {
813 let segs = self.find_segments_with_qualifier_in_group(
814 tag, qual_elem, qualifier, group_path, i,
815 );
816 for seg in &segs {
817 if seg
818 .elements
819 .get(value_elem)
820 .and_then(|e| e.get(value_comp))
821 .map(|s| s.as_str())
822 .is_some_and(|v| values.contains(&v))
823 {
824 return ConditionResult::True;
825 }
826 }
827 }
828 return ConditionResult::False;
829 }
830 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
832 }
833
834 #[allow(clippy::too_many_arguments)]
843 pub fn filtered_parent_child_has_qualifier(
844 &self,
845 parent_path: &[&str],
846 parent_tag: &str,
847 parent_elem: usize,
848 parent_qual: &str,
849 child_group_id: &str,
850 child_tag: &str,
851 child_elem: usize,
852 child_qual: &str,
853 ) -> ConditionResult {
854 let parent_count = self.group_instance_count(parent_path);
855 if parent_count > 0 {
856 for pi in 0..parent_count {
857 let parent_segs = self.find_segments_with_qualifier_in_group(
859 parent_tag,
860 parent_elem,
861 parent_qual,
862 parent_path,
863 pi,
864 );
865 if parent_segs.is_empty() {
866 continue;
867 }
868 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
870 for ci in 0..child_count {
871 let child_segs = self.find_segments_in_child_group(
872 child_tag,
873 parent_path,
874 pi,
875 child_group_id,
876 ci,
877 );
878 if child_segs.iter().any(|s| {
879 s.elements
880 .get(child_elem)
881 .and_then(|e| e.first())
882 .is_some_and(|v| v == child_qual)
883 }) {
884 return ConditionResult::True;
885 }
886 }
887 }
888 return ConditionResult::False;
889 }
890 let has_parent = !self
892 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
893 .is_empty();
894 let has_child = !self
895 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
896 .is_empty();
897 ConditionResult::from(has_parent && has_child)
898 }
899
900 #[allow(clippy::too_many_arguments)]
906 pub fn any_group_has_qualifier_without(
907 &self,
908 present_tag: &str,
909 present_elem: usize,
910 present_qual: &str,
911 absent_tag: &str,
912 absent_elem: usize,
913 absent_qual: &str,
914 group_path: &[&str],
915 ) -> ConditionResult {
916 let instance_count = self.group_instance_count(group_path);
917 if instance_count > 0 {
918 for i in 0..instance_count {
919 let has_present = !self
920 .find_segments_with_qualifier_in_group(
921 present_tag,
922 present_elem,
923 present_qual,
924 group_path,
925 i,
926 )
927 .is_empty();
928 let has_absent = !self
929 .find_segments_with_qualifier_in_group(
930 absent_tag,
931 absent_elem,
932 absent_qual,
933 group_path,
934 i,
935 )
936 .is_empty();
937 if has_present && !has_absent {
938 return ConditionResult::True;
939 }
940 }
941 return ConditionResult::False;
942 }
943 let has_present = !self
945 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
946 .is_empty();
947 let has_absent = !self
948 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
949 .is_empty();
950 ConditionResult::from(has_present && !has_absent)
951 }
952
953 fn enclosing_path(&self, group_path: &[&str]) -> Option<&'a [(&'a str, usize)]> {
956 let group = group_path.last()?;
957 let scope = self.scope?;
958 self.navigator?;
959 let level = scope.path.iter().rposition(|(id, _)| id == group)?;
960 Some(&scope.path[..=level])
961 }
962
963 pub fn enclosing_scope(&self, group_id: &str) -> Option<&'a [(&'a str, usize)]> {
967 self.enclosing_path(&[group_id])
968 }
969
970 pub fn enclosing_child_segments(
975 &self,
976 group_id: &str,
977 child_id: &str,
978 tags: &[&str],
979 ) -> Option<Vec<Vec<OwnedSegment>>> {
980 let path = self.enclosing_scope(group_id)?;
981 Some(self.child_segments_at(path, child_id, tags))
982 }
983
984 pub fn child_segments_at(
987 &self,
988 path: &[(&str, usize)],
989 child_id: &str,
990 tags: &[&str],
991 ) -> Vec<Vec<OwnedSegment>> {
992 let Some(nav) = self.navigator else {
993 return Vec::new();
994 };
995 (0..nav.child_instance_count(path, child_id))
996 .map(|j| {
997 let mut child = path.to_vec();
998 child.push((child_id, j));
999 tags.iter()
1000 .flat_map(|tag| nav.find_segments_in_subtree(tag, &child))
1001 .collect()
1002 })
1003 .collect()
1004 }
1005
1006 pub fn scopes_under(
1011 &self,
1012 parent_id: &str,
1013 group_id: &'a str,
1014 ) -> Option<Vec<Vec<(&'a str, usize)>>> {
1015 let parent = self.enclosing_scope(parent_id)?;
1016 let nav = self.navigator?;
1017 Some(
1018 (0..nav.child_instance_count(parent, group_id))
1019 .map(|i| {
1020 let mut path = parent.to_vec();
1021 path.push((group_id, i));
1022 path
1023 })
1024 .collect(),
1025 )
1026 }
1027
1028 #[allow(clippy::too_many_arguments)]
1032 pub fn this_group_has_qualifier_without(
1033 &self,
1034 present_tag: &str,
1035 present_elem: usize,
1036 present_qual: &str,
1037 absent_tag: &str,
1038 absent_elem: usize,
1039 absent_qual: &str,
1040 group_path: &[&str],
1041 ) -> ConditionResult {
1042 let Some(path) = self.enclosing_path(group_path) else {
1043 return self.any_group_has_qualifier_without(
1044 present_tag,
1045 present_elem,
1046 present_qual,
1047 absent_tag,
1048 absent_elem,
1049 absent_qual,
1050 group_path,
1051 );
1052 };
1053 let has = |tag: &str, elem: usize, qual: &str| {
1054 self.find_segments_at(path, tag)
1055 .iter()
1056 .any(|s| first_component(s, elem) == Some(qual))
1057 };
1058 ConditionResult::from(
1059 has(present_tag, present_elem, present_qual)
1060 && !has(absent_tag, absent_elem, absent_qual),
1061 )
1062 }
1063
1064 pub fn this_group_lacks_qualifier(
1074 &self,
1075 tag: &str,
1076 element_index: usize,
1077 qualifier: &str,
1078 group_path: &[&str],
1079 ) -> ConditionResult {
1080 match self.enclosing_path(group_path) {
1081 Some(path) => ConditionResult::from(
1082 !self
1083 .find_segments_at(path, tag)
1084 .iter()
1085 .any(|s| first_component(s, element_index) == Some(qualifier)),
1086 ),
1087 None => ConditionResult::Unknown,
1088 }
1089 }
1090
1091 #[allow(clippy::too_many_arguments)]
1095 pub fn this_group_parent_child_has_qualifier(
1096 &self,
1097 parent_path: &[&str],
1098 parent_tag: &str,
1099 parent_elem: usize,
1100 parent_qual: &str,
1101 child_group_id: &str,
1102 child_tag: &str,
1103 child_elem: usize,
1104 child_qual: &str,
1105 ) -> ConditionResult {
1106 let (Some(path), Some(nav)) = (self.enclosing_path(parent_path), self.navigator) else {
1107 return self.filtered_parent_child_has_qualifier(
1108 parent_path,
1109 parent_tag,
1110 parent_elem,
1111 parent_qual,
1112 child_group_id,
1113 child_tag,
1114 child_elem,
1115 child_qual,
1116 );
1117 };
1118 let parent_matches = nav
1119 .find_segments_in_instance(parent_tag, path)
1120 .iter()
1121 .any(|s| first_component(s, parent_elem) == Some(parent_qual));
1122 if !parent_matches {
1123 return ConditionResult::False;
1124 }
1125 let children = nav.child_instance_count(path, child_group_id);
1126 let found = (0..children).any(|j| {
1127 let mut child = path.to_vec();
1128 child.push((child_group_id, j));
1129 nav.find_segments_in_subtree(child_tag, &child)
1130 .iter()
1131 .any(|s| first_component(s, child_elem) == Some(child_qual))
1132 });
1133 ConditionResult::from(found)
1134 }
1135
1136 pub fn this_group_has_qualifier(
1140 &self,
1141 tag: &str,
1142 element_index: usize,
1143 qualifier: &str,
1144 group_path: &[&str],
1145 ) -> ConditionResult {
1146 match self.enclosing_path(group_path) {
1147 Some(path) => ConditionResult::from(
1148 self.find_segments_at(path, tag)
1149 .iter()
1150 .any(|s| first_component(s, element_index) == Some(qualifier)),
1151 ),
1152 None => self.any_group_has_qualifier(tag, element_index, qualifier, group_path),
1153 }
1154 }
1155
1156 pub fn this_group_has_any_qualifier(
1159 &self,
1160 tag: &str,
1161 element_index: usize,
1162 qualifiers: &[&str],
1163 group_path: &[&str],
1164 ) -> ConditionResult {
1165 match self.enclosing_path(group_path) {
1166 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1167 first_component(s, element_index).is_some_and(|v| qualifiers.contains(&v))
1168 })),
1169 None => self.any_group_has_any_qualifier(tag, element_index, qualifiers, group_path),
1170 }
1171 }
1172
1173 #[allow(clippy::too_many_arguments)]
1176 pub fn this_group_has_qualified_value(
1177 &self,
1178 tag: &str,
1179 qual_elem: usize,
1180 qualifier: &str,
1181 value_elem: usize,
1182 value_comp: usize,
1183 values: &[&str],
1184 group_path: &[&str],
1185 ) -> ConditionResult {
1186 match self.enclosing_path(group_path) {
1187 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1188 first_component(s, qual_elem) == Some(qualifier)
1189 && s.elements
1190 .get(value_elem)
1191 .and_then(|e| e.get(value_comp))
1192 .is_some_and(|v| values.contains(&v.as_str()))
1193 })),
1194 None => self.any_group_has_qualified_value(
1195 tag, qual_elem, qualifier, value_elem, value_comp, values, group_path,
1196 ),
1197 }
1198 }
1199
1200 pub fn this_group_values(
1204 &self,
1205 tag: &str,
1206 elem: usize,
1207 comp: usize,
1208 group_path: &[&str],
1209 ) -> Vec<(usize, String)> {
1210 match self.enclosing_path(group_path) {
1211 Some(path) => self
1212 .find_segments_at(path, tag)
1213 .iter()
1214 .filter_map(|s| s.elements.get(elem).and_then(|e| e.get(comp)).cloned())
1215 .filter(|v| !v.is_empty())
1216 .map(|v| (0, v))
1217 .collect(),
1218 None => self.collect_group_values(tag, elem, comp, group_path),
1219 }
1220 }
1221
1222 pub fn collect_group_values(
1226 &self,
1227 tag: &str,
1228 elem: usize,
1229 comp: usize,
1230 group_path: &[&str],
1231 ) -> Vec<(usize, String)> {
1232 let instance_count = self.group_instance_count(group_path);
1233 let mut results = Vec::new();
1234 for i in 0..instance_count {
1235 if let Some(val) = self
1236 .navigator
1237 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
1238 {
1239 if !val.is_empty() {
1240 results.push((i, val));
1241 }
1242 }
1243 }
1244 results
1245 }
1246
1247 #[allow(clippy::too_many_arguments)]
1256 pub fn groups_share_qualified_value(
1257 &self,
1258 source_tag: &str,
1259 source_qual_elem: usize,
1260 source_qual: &str,
1261 source_value_elem: usize,
1262 source_value_comp: usize,
1263 source_path: &[&str],
1264 target_tag: &str,
1265 target_elem: usize,
1266 target_comp: usize,
1267 target_path: &[&str],
1268 ) -> ConditionResult {
1269 let source_count = self.group_instance_count(source_path);
1270 let target_count = self.group_instance_count(target_path);
1271 if source_count > 0 && target_count > 0 {
1272 let mut source_values = Vec::new();
1274 for si in 0..source_count {
1275 let segs = self.find_segments_with_qualifier_in_group(
1276 source_tag,
1277 source_qual_elem,
1278 source_qual,
1279 source_path,
1280 si,
1281 );
1282 for seg in &segs {
1283 if let Some(val) = seg
1284 .elements
1285 .get(source_value_elem)
1286 .and_then(|e| e.get(source_value_comp))
1287 {
1288 if !val.is_empty() {
1289 source_values.push(val.clone());
1290 }
1291 }
1292 }
1293 }
1294 if source_values.is_empty() {
1295 return ConditionResult::Unknown;
1296 }
1297 let target_values =
1299 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
1300 for (_, tv) in &target_values {
1301 if source_values.iter().any(|sv| sv == tv) {
1302 return ConditionResult::True;
1303 }
1304 }
1305 return ConditionResult::False;
1306 }
1307 let source_segs =
1309 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
1310 let source_vals: Vec<&str> = source_segs
1311 .iter()
1312 .filter_map(|s| {
1313 s.elements
1314 .get(source_value_elem)
1315 .and_then(|e| e.get(source_value_comp))
1316 .map(|v| v.as_str())
1317 })
1318 .filter(|v| !v.is_empty())
1319 .collect();
1320 if source_vals.is_empty() {
1321 return ConditionResult::Unknown;
1322 }
1323 let target_segs = self.find_segments(target_tag);
1324 let has_match = target_segs.iter().any(|s| {
1325 s.elements
1326 .get(target_elem)
1327 .and_then(|e| e.get(target_comp))
1328 .map(|v| v.as_str())
1329 .is_some_and(|v| source_vals.contains(&v))
1330 });
1331 ConditionResult::from(has_match)
1332 }
1333
1334 #[allow(clippy::too_many_arguments)]
1343 pub fn any_group_has_co_occurrence(
1344 &self,
1345 tag_a: &str,
1346 elem_a: usize,
1347 quals_a: &[&str],
1348 tag_b: &str,
1349 elem_b: usize,
1350 comp_b: usize,
1351 vals_b: &[&str],
1352 group_path: &[&str],
1353 ) -> ConditionResult {
1354 let instance_count = self.group_instance_count(group_path);
1355 if instance_count > 0 {
1356 for i in 0..instance_count {
1357 let a_present = self
1358 .find_segments_in_group(tag_a, group_path, i)
1359 .iter()
1360 .any(|seg| {
1361 seg.elements
1362 .get(elem_a)
1363 .and_then(|e| e.first())
1364 .map(|s| s.as_str())
1365 .is_some_and(|v| quals_a.contains(&v))
1366 });
1367 let b_present = self
1368 .find_segments_in_group(tag_b, group_path, i)
1369 .iter()
1370 .any(|seg| {
1371 seg.elements
1372 .get(elem_b)
1373 .and_then(|e| e.get(comp_b))
1374 .map(|s| s.as_str())
1375 .is_some_and(|v| vals_b.contains(&v))
1376 });
1377 if a_present && b_present {
1378 return ConditionResult::True;
1379 }
1380 }
1381 return ConditionResult::False;
1382 }
1383 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1385 seg.elements
1386 .get(elem_a)
1387 .and_then(|e| e.first())
1388 .map(|s| s.as_str())
1389 .is_some_and(|v| quals_a.contains(&v))
1390 });
1391 if !a_found {
1392 return ConditionResult::False;
1393 }
1394 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1395 seg.elements
1396 .get(elem_b)
1397 .and_then(|e| e.get(comp_b))
1398 .map(|s| s.as_str())
1399 .is_some_and(|v| vals_b.contains(&v))
1400 });
1401 ConditionResult::from(b_found)
1402 }
1403
1404 pub fn has_segment_matching(
1414 &self,
1415 tag: &str,
1416 checks: &[(usize, usize, &str)],
1417 ) -> ConditionResult {
1418 let segments = self.find_segments(tag);
1419 if segments.is_empty() {
1420 return ConditionResult::Unknown;
1421 }
1422 let found = segments.iter().any(|seg| {
1423 checks.iter().all(|(elem, comp, val)| {
1424 seg.elements
1425 .get(*elem)
1426 .and_then(|e| e.get(*comp))
1427 .is_some_and(|v| v == val)
1428 })
1429 });
1430 ConditionResult::from(found)
1431 }
1432
1433 pub fn has_segment_matching_in_group(
1438 &self,
1439 tag: &str,
1440 checks: &[(usize, usize, &str)],
1441 group_path: &[&str],
1442 ) -> ConditionResult {
1443 let instance_count = self.group_instance_count(group_path);
1444 if instance_count > 0 {
1445 for i in 0..instance_count {
1446 let segs = self.find_segments_in_group(tag, group_path, i);
1447 if segs.iter().any(|seg| {
1448 checks.iter().all(|(elem, comp, val)| {
1449 seg.elements
1450 .get(*elem)
1451 .and_then(|e| e.get(*comp))
1452 .is_some_and(|v| v == val)
1453 })
1454 }) {
1455 return ConditionResult::True;
1456 }
1457 }
1458 return ConditionResult::False;
1459 }
1460 self.has_segment_matching(tag, checks)
1462 }
1463
1464 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1471 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1472 match segs.first() {
1473 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1474 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1475 None => ConditionResult::Unknown,
1476 },
1477 None => ConditionResult::Unknown,
1478 }
1479 }
1480
1481 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1483 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1484 match segs.first() {
1485 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1486 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1487 None => ConditionResult::Unknown,
1488 },
1489 None => ConditionResult::Unknown,
1490 }
1491 }
1492
1493 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1495 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1496 match segs.first() {
1497 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1498 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1499 None => ConditionResult::Unknown,
1500 },
1501 None => ConditionResult::Unknown,
1502 }
1503 }
1504
1505 pub fn count_qualified_in_group(
1512 &self,
1513 tag: &str,
1514 element_index: usize,
1515 qualifier: &str,
1516 group_path: &[&str],
1517 ) -> usize {
1518 let instance_count = self.group_instance_count(group_path);
1519 if instance_count > 0 {
1520 let mut total = 0;
1521 for i in 0..instance_count {
1522 total += self
1523 .find_segments_with_qualifier_in_group(
1524 tag,
1525 element_index,
1526 qualifier,
1527 group_path,
1528 i,
1529 )
1530 .len();
1531 }
1532 return total;
1533 }
1534 self.find_segments_with_qualifier(tag, element_index, qualifier)
1536 .len()
1537 }
1538
1539 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1541 let instance_count = self.group_instance_count(group_path);
1542 if instance_count > 0 {
1543 let mut total = 0;
1544 for i in 0..instance_count {
1545 total += self.find_segments_in_group(tag, group_path, i).len();
1546 }
1547 return total;
1548 }
1549 self.find_segments(tag).len()
1551 }
1552}
1553
1554fn first_component(segment: &OwnedSegment, element: usize) -> Option<&str> {
1556 segment
1557 .elements
1558 .get(element)
1559 .and_then(|e| e.first())
1560 .map(String::as_str)
1561}
1562
1563#[cfg(test)]
1564mod tests {
1565 use super::super::evaluator::NoOpExternalProvider;
1566 use super::*;
1567 use mig_types::navigator::GroupNavigator;
1568
1569 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1570 OwnedSegment {
1571 id: id.to_string(),
1572 elements: elements
1573 .into_iter()
1574 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1575 .collect(),
1576 segment_number: 0,
1577 }
1578 }
1579
1580 struct MockGroupNavigator {
1582 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1583 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1585 }
1586
1587 impl MockGroupNavigator {
1588 fn new() -> Self {
1589 Self {
1590 groups: vec![],
1591 children: vec![],
1592 }
1593 }
1594 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1595 self.groups
1596 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1597 self
1598 }
1599 fn with_child_group(
1600 mut self,
1601 parent_path: &[&str],
1602 parent_instance: usize,
1603 child_id: &str,
1604 child_instance: usize,
1605 segs: Vec<OwnedSegment>,
1606 ) -> Self {
1607 self.children.push((
1608 parent_path.iter().map(|s| s.to_string()).collect(),
1609 parent_instance,
1610 child_id.to_string(),
1611 child_instance,
1612 segs,
1613 ));
1614 self
1615 }
1616 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1617 self.groups
1618 .iter()
1619 .find(|(p, i, _)| {
1620 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1621 ps.as_slice() == group_path && *i == idx
1622 })
1623 .map(|(_, _, segs)| segs.as_slice())
1624 }
1625 }
1626
1627 impl GroupNavigator for MockGroupNavigator {
1628 fn find_segments_in_group(
1629 &self,
1630 segment_id: &str,
1631 group_path: &[&str],
1632 instance_index: usize,
1633 ) -> Vec<OwnedSegment> {
1634 self.find_instance(group_path, instance_index)
1635 .map(|segs| {
1636 segs.iter()
1637 .filter(|s| s.id == segment_id)
1638 .cloned()
1639 .collect()
1640 })
1641 .unwrap_or_default()
1642 }
1643 fn find_segments_with_qualifier_in_group(
1644 &self,
1645 segment_id: &str,
1646 element_index: usize,
1647 qualifier: &str,
1648 group_path: &[&str],
1649 instance_index: usize,
1650 ) -> Vec<OwnedSegment> {
1651 self.find_segments_in_group(segment_id, group_path, instance_index)
1652 .into_iter()
1653 .filter(|s| {
1654 s.elements
1655 .get(element_index)
1656 .and_then(|e| e.first())
1657 .is_some_and(|v| v == qualifier)
1658 })
1659 .collect()
1660 }
1661 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1662 self.groups
1663 .iter()
1664 .filter(|(p, _, _)| {
1665 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1666 ps.as_slice() == group_path
1667 })
1668 .count()
1669 }
1670 fn child_group_instance_count(
1671 &self,
1672 parent_path: &[&str],
1673 parent_instance: usize,
1674 child_group_id: &str,
1675 ) -> usize {
1676 self.children
1677 .iter()
1678 .filter(|(pp, pi, cid, _, _)| {
1679 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1680 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1681 })
1682 .count()
1683 }
1684 fn find_segments_in_child_group(
1685 &self,
1686 segment_id: &str,
1687 parent_path: &[&str],
1688 parent_instance: usize,
1689 child_group_id: &str,
1690 child_instance: usize,
1691 ) -> Vec<OwnedSegment> {
1692 self.children
1693 .iter()
1694 .find(|(pp, pi, cid, ci, _)| {
1695 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1696 ps.as_slice() == parent_path
1697 && *pi == parent_instance
1698 && cid == child_group_id
1699 && *ci == child_instance
1700 })
1701 .map(|(_, _, _, _, segs)| {
1702 segs.iter()
1703 .filter(|s| s.id == segment_id)
1704 .cloned()
1705 .collect()
1706 })
1707 .unwrap_or_default()
1708 }
1709 fn extract_value_in_group(
1710 &self,
1711 segment_id: &str,
1712 element_index: usize,
1713 component_index: usize,
1714 group_path: &[&str],
1715 instance_index: usize,
1716 ) -> Option<String> {
1717 let segs = self.find_instance(group_path, instance_index)?;
1718 let seg = segs.iter().find(|s| s.id == segment_id)?;
1719 seg.elements
1720 .get(element_index)?
1721 .get(component_index)
1722 .cloned()
1723 }
1724 }
1725
1726 #[test]
1727 fn test_find_segment() {
1728 let segments = vec![
1729 make_segment("UNH", vec![vec!["test"]]),
1730 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1731 ];
1732 let external = NoOpExternalProvider;
1733 let ctx = EvaluationContext::new("11001", &external, &segments);
1734
1735 assert!(ctx.find_segment("NAD").is_some());
1736 assert!(ctx.find_segment("DTM").is_none());
1737 }
1738
1739 #[test]
1740 fn test_find_segments_with_qualifier() {
1741 let segments = vec![
1742 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1743 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1744 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1745 ];
1746 let external = NoOpExternalProvider;
1747 let ctx = EvaluationContext::new("11001", &external, &segments);
1748
1749 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1750 assert_eq!(ms_nads.len(), 2);
1751 }
1752
1753 #[test]
1754 fn test_has_segment() {
1755 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1756 let external = NoOpExternalProvider;
1757 let ctx = EvaluationContext::new("11001", &external, &segments);
1758
1759 assert!(ctx.has_segment("UNH"));
1760 assert!(!ctx.has_segment("NAD"));
1761 }
1762
1763 #[test]
1766 fn test_no_navigator_group_find_returns_empty() {
1767 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1768 let external = NoOpExternalProvider;
1769 let ctx = EvaluationContext::new("55001", &external, &segments);
1770 assert!(ctx
1771 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1772 .is_empty());
1773 }
1774
1775 #[test]
1776 fn test_no_navigator_group_instance_count_zero() {
1777 let external = NoOpExternalProvider;
1778 let ctx = EvaluationContext::new("55001", &external, &[]);
1779 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1780 }
1781
1782 #[test]
1783 fn test_with_navigator_finds_segments_in_group() {
1784 let external = NoOpExternalProvider;
1785 let nav = MockGroupNavigator::new().with_group(
1786 &["SG4", "SG8"],
1787 0,
1788 vec![
1789 make_segment("SEQ", vec![vec!["Z98"]]),
1790 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1791 ],
1792 );
1793 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1794 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1795 assert_eq!(result.len(), 1);
1796 assert_eq!(result[0].id, "SEQ");
1797 }
1798
1799 #[test]
1800 fn test_with_navigator_qualifier_in_group() {
1801 let external = NoOpExternalProvider;
1802 let nav = MockGroupNavigator::new().with_group(
1803 &["SG4", "SG8"],
1804 0,
1805 vec![
1806 make_segment("SEQ", vec![vec!["Z98"]]),
1807 make_segment("SEQ", vec![vec!["Z01"]]),
1808 ],
1809 );
1810 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1811 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1812 assert_eq!(result.len(), 1);
1813 }
1814
1815 #[test]
1816 fn test_group_instance_count_with_navigator() {
1817 let external = NoOpExternalProvider;
1818 let nav = MockGroupNavigator::new()
1819 .with_group(
1820 &["SG4", "SG8"],
1821 0,
1822 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1823 )
1824 .with_group(
1825 &["SG4", "SG8"],
1826 1,
1827 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1828 );
1829 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1830 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1831 }
1832
1833 #[test]
1834 fn test_has_segment_in_group() {
1835 let external = NoOpExternalProvider;
1836 let nav = MockGroupNavigator::new().with_group(
1837 &["SG4", "SG8"],
1838 0,
1839 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1840 );
1841 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1842 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1843 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1844 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1845 }
1846
1847 #[test]
1850 fn test_has_qualifier() {
1851 let segments = vec![
1852 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1853 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1854 ];
1855 let external = NoOpExternalProvider;
1856 let ctx = EvaluationContext::new("11001", &external, &segments);
1857
1858 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1859 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1860 }
1861
1862 #[test]
1863 fn test_lacks_qualifier() {
1864 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1865 let external = NoOpExternalProvider;
1866 let ctx = EvaluationContext::new("11001", &external, &segments);
1867
1868 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1869 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1870 }
1871
1872 #[test]
1873 fn test_has_qualified_value() {
1874 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1875 let external = NoOpExternalProvider;
1876 let ctx = EvaluationContext::new("55001", &external, &segments);
1877
1878 assert_eq!(
1879 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1880 ConditionResult::True,
1881 );
1882 assert_eq!(
1883 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1884 ConditionResult::False,
1885 );
1886 assert_eq!(
1888 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1889 ConditionResult::Unknown,
1890 );
1891 }
1892
1893 #[test]
1894 fn test_any_group_has_qualifier() {
1895 let external = NoOpExternalProvider;
1896 let nav = MockGroupNavigator::new()
1897 .with_group(
1898 &["SG4", "SG8"],
1899 0,
1900 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1901 )
1902 .with_group(
1903 &["SG4", "SG8"],
1904 1,
1905 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1906 );
1907 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1908
1909 assert_eq!(
1910 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1911 ConditionResult::True,
1912 );
1913 assert_eq!(
1914 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1915 ConditionResult::False,
1916 );
1917 }
1918
1919 #[test]
1920 fn test_any_group_has_qualifier_fallback() {
1921 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1923 let external = NoOpExternalProvider;
1924 let ctx = EvaluationContext::new("55001", &external, &segments);
1925
1926 assert_eq!(
1927 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1928 ConditionResult::True,
1929 );
1930 }
1931
1932 #[test]
1933 fn test_any_group_has_any_qualifier() {
1934 let external = NoOpExternalProvider;
1935 let nav = MockGroupNavigator::new().with_group(
1936 &["SG4", "SG8"],
1937 0,
1938 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1939 );
1940 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1941
1942 assert_eq!(
1943 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1944 ConditionResult::True,
1945 );
1946 assert_eq!(
1947 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1948 ConditionResult::False,
1949 );
1950 }
1951
1952 #[test]
1953 fn test_any_group_has_co_occurrence() {
1954 let external = NoOpExternalProvider;
1955 let nav = MockGroupNavigator::new().with_group(
1956 &["SG4", "SG8"],
1957 0,
1958 vec![
1959 make_segment("SEQ", vec![vec!["Z01"]]),
1960 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1961 ],
1962 );
1963 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1964
1965 assert_eq!(
1966 ctx.any_group_has_co_occurrence(
1967 "SEQ",
1968 0,
1969 &["Z01"],
1970 "CCI",
1971 2,
1972 0,
1973 &["Z07"],
1974 &["SG4", "SG8"],
1975 ),
1976 ConditionResult::True,
1977 );
1978 assert_eq!(
1980 ctx.any_group_has_co_occurrence(
1981 "SEQ",
1982 0,
1983 &["Z01"],
1984 "CCI",
1985 2,
1986 0,
1987 &["ZC0"],
1988 &["SG4", "SG8"],
1989 ),
1990 ConditionResult::False,
1991 );
1992 }
1993
1994 #[test]
1997 fn test_filtered_parent_child_has_qualifier() {
1998 let external = NoOpExternalProvider;
1999 let nav = MockGroupNavigator::new()
2002 .with_group(
2003 &["SG4", "SG8"],
2004 0,
2005 vec![make_segment("SEQ", vec![vec!["Z98"]])],
2006 )
2007 .with_group(
2008 &["SG4", "SG8"],
2009 1,
2010 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2011 )
2012 .with_child_group(
2013 &["SG4", "SG8"],
2014 0,
2015 "SG10",
2016 0,
2017 vec![make_segment("CCI", vec![vec!["Z23"]])],
2018 );
2019 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2020
2021 assert_eq!(
2023 ctx.filtered_parent_child_has_qualifier(
2024 &["SG4", "SG8"],
2025 "SEQ",
2026 0,
2027 "Z98",
2028 "SG10",
2029 "CCI",
2030 0,
2031 "Z23",
2032 ),
2033 ConditionResult::True,
2034 );
2035 assert_eq!(
2037 ctx.filtered_parent_child_has_qualifier(
2038 &["SG4", "SG8"],
2039 "SEQ",
2040 0,
2041 "Z01",
2042 "SG10",
2043 "CCI",
2044 0,
2045 "Z23",
2046 ),
2047 ConditionResult::False,
2048 );
2049 assert_eq!(
2051 ctx.filtered_parent_child_has_qualifier(
2052 &["SG4", "SG8"],
2053 "SEQ",
2054 0,
2055 "Z98",
2056 "SG10",
2057 "CCI",
2058 0,
2059 "Z99",
2060 ),
2061 ConditionResult::False,
2062 );
2063 }
2064
2065 #[test]
2066 fn test_filtered_parent_child_fallback() {
2067 let segments = vec![
2069 make_segment("SEQ", vec![vec!["Z98"]]),
2070 make_segment("CCI", vec![vec!["Z23"]]),
2071 ];
2072 let external = NoOpExternalProvider;
2073 let ctx = EvaluationContext::new("55001", &external, &segments);
2074
2075 assert_eq!(
2076 ctx.filtered_parent_child_has_qualifier(
2077 &["SG4", "SG8"],
2078 "SEQ",
2079 0,
2080 "Z98",
2081 "SG10",
2082 "CCI",
2083 0,
2084 "Z23",
2085 ),
2086 ConditionResult::True,
2087 );
2088 assert_eq!(
2090 ctx.filtered_parent_child_has_qualifier(
2091 &["SG4", "SG8"],
2092 "SEQ",
2093 0,
2094 "Z98",
2095 "SG10",
2096 "CCI",
2097 0,
2098 "Z99",
2099 ),
2100 ConditionResult::False,
2101 );
2102 }
2103
2104 #[test]
2105 fn test_any_group_has_qualifier_without() {
2106 let external = NoOpExternalProvider;
2107 let nav = MockGroupNavigator::new()
2110 .with_group(
2111 &["SG4", "SG8"],
2112 0,
2113 vec![make_segment("SEQ", vec![vec!["Z59"]])],
2114 )
2115 .with_group(
2116 &["SG4", "SG8"],
2117 1,
2118 vec![
2119 make_segment("SEQ", vec![vec!["Z01"]]),
2120 make_segment("CCI", vec![vec!["11"]]),
2121 ],
2122 );
2123 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2124
2125 assert_eq!(
2127 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2128 ConditionResult::True,
2129 );
2130 assert_eq!(
2132 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
2133 ConditionResult::False,
2134 );
2135 assert_eq!(
2137 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
2138 ConditionResult::False,
2139 );
2140 }
2141
2142 #[test]
2143 fn test_any_group_has_qualifier_without_fallback() {
2144 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
2145 let external = NoOpExternalProvider;
2146 let ctx = EvaluationContext::new("55001", &external, &segments);
2147
2148 assert_eq!(
2150 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2151 ConditionResult::True,
2152 );
2153 }
2154
2155 #[test]
2156 fn test_collect_group_values() {
2157 let external = NoOpExternalProvider;
2158 let nav = MockGroupNavigator::new()
2159 .with_group(
2160 &["SG4", "SG6"],
2161 0,
2162 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
2163 )
2164 .with_group(
2165 &["SG4", "SG6"],
2166 1,
2167 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
2168 );
2169 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2170
2171 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
2172 assert_eq!(values.len(), 2);
2173 assert_eq!(values[0], (0, "REF001".to_string()));
2174 assert_eq!(values[1], (1, "REF002".to_string()));
2175 }
2176
2177 #[test]
2178 fn test_groups_share_qualified_value() {
2179 let external = NoOpExternalProvider;
2180 let nav = MockGroupNavigator::new()
2184 .with_group(
2185 &["SG4", "SG6"],
2186 0,
2187 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2188 )
2189 .with_group(
2190 &["SG4", "SG8"],
2191 0,
2192 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2193 )
2194 .with_group(
2195 &["SG4", "SG8"],
2196 1,
2197 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
2198 );
2199 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2200
2201 assert_eq!(
2203 ctx.groups_share_qualified_value(
2204 "RFF",
2205 0,
2206 "Z49",
2207 0,
2208 1,
2209 &["SG4", "SG6"],
2210 "SEQ",
2211 1,
2212 0,
2213 &["SG4", "SG8"],
2214 ),
2215 ConditionResult::True,
2216 );
2217 }
2218
2219 #[test]
2220 fn test_groups_share_qualified_value_no_match() {
2221 let external = NoOpExternalProvider;
2222 let nav = MockGroupNavigator::new()
2223 .with_group(
2224 &["SG4", "SG6"],
2225 0,
2226 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2227 )
2228 .with_group(
2229 &["SG4", "SG8"],
2230 0,
2231 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
2232 );
2233 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2234
2235 assert_eq!(
2237 ctx.groups_share_qualified_value(
2238 "RFF",
2239 0,
2240 "Z49",
2241 0,
2242 1,
2243 &["SG4", "SG6"],
2244 "SEQ",
2245 1,
2246 0,
2247 &["SG4", "SG8"],
2248 ),
2249 ConditionResult::False,
2250 );
2251 }
2252
2253 #[test]
2254 fn test_groups_share_qualified_value_no_source() {
2255 let external = NoOpExternalProvider;
2256 let nav = MockGroupNavigator::new()
2258 .with_group(
2259 &["SG4", "SG6"],
2260 0,
2261 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
2262 )
2263 .with_group(
2264 &["SG4", "SG8"],
2265 0,
2266 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2267 );
2268 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2269
2270 assert_eq!(
2272 ctx.groups_share_qualified_value(
2273 "RFF",
2274 0,
2275 "Z49",
2276 0,
2277 1,
2278 &["SG4", "SG6"],
2279 "SEQ",
2280 1,
2281 0,
2282 &["SG4", "SG8"],
2283 ),
2284 ConditionResult::Unknown,
2285 );
2286 }
2287
2288 #[test]
2289 fn test_groups_share_qualified_value_fallback() {
2290 let segments = vec![
2292 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
2293 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
2294 ];
2295 let external = NoOpExternalProvider;
2296 let ctx = EvaluationContext::new("55001", &external, &segments);
2297
2298 assert_eq!(
2299 ctx.groups_share_qualified_value(
2300 "RFF",
2301 0,
2302 "Z49",
2303 0,
2304 1,
2305 &["SG4", "SG6"],
2306 "SEQ",
2307 1,
2308 0,
2309 &["SG4", "SG8"],
2310 ),
2311 ConditionResult::True,
2312 );
2313 }
2314
2315 #[test]
2316 fn test_child_group_pass_throughs_no_navigator() {
2317 let external = NoOpExternalProvider;
2318 let ctx = EvaluationContext::new("55001", &external, &[]);
2319
2320 assert_eq!(
2321 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
2322 0
2323 );
2324 assert!(ctx
2325 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
2326 .is_empty());
2327 assert_eq!(
2328 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
2329 None
2330 );
2331 }
2332
2333 #[test]
2336 fn test_has_segment_matching_found() {
2337 let segments = vec![
2338 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
2339 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
2340 ];
2341 let external = NoOpExternalProvider;
2342 let ctx = EvaluationContext::new("55001", &external, &segments);
2343
2344 assert_eq!(
2345 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
2346 ConditionResult::True,
2347 );
2348 }
2349
2350 #[test]
2351 fn test_has_segment_matching_not_found() {
2352 let segments = vec![make_segment(
2353 "STS",
2354 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2355 )];
2356 let external = NoOpExternalProvider;
2357 let ctx = EvaluationContext::new("55001", &external, &segments);
2358
2359 assert_eq!(
2360 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2361 ConditionResult::False,
2362 );
2363 }
2364
2365 #[test]
2366 fn test_has_segment_matching_no_segments() {
2367 let external = NoOpExternalProvider;
2368 let ctx = EvaluationContext::new("55001", &external, &[]);
2369
2370 assert_eq!(
2371 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2372 ConditionResult::Unknown,
2373 );
2374 }
2375
2376 #[test]
2377 fn test_has_segment_matching_in_group() {
2378 let nav = MockGroupNavigator::new()
2379 .with_group(
2380 &["SG4"],
2381 0,
2382 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2383 )
2384 .with_group(
2385 &["SG4"],
2386 1,
2387 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2388 );
2389 let segments = vec![
2390 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2391 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2392 ];
2393 let external = NoOpExternalProvider;
2394 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2395
2396 assert_eq!(
2397 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2398 ConditionResult::True,
2399 );
2400 }
2401
2402 #[test]
2405 fn test_dtm_ge() {
2406 let segments = vec![make_segment(
2407 "DTM",
2408 vec![vec!["137", "202601010000", "303"]],
2409 )];
2410 let external = NoOpExternalProvider;
2411 let ctx = EvaluationContext::new("55001", &external, &segments);
2412
2413 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2414 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2415 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2416 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2417 }
2418
2419 #[test]
2420 fn test_dtm_lt() {
2421 let segments = vec![make_segment(
2422 "DTM",
2423 vec![vec!["137", "202601010000", "303"]],
2424 )];
2425 let external = NoOpExternalProvider;
2426 let ctx = EvaluationContext::new("55001", &external, &segments);
2427
2428 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2429 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2430 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2431 }
2432
2433 #[test]
2434 fn test_dtm_le() {
2435 let segments = vec![make_segment(
2436 "DTM",
2437 vec![vec!["137", "202601010000", "303"]],
2438 )];
2439 let external = NoOpExternalProvider;
2440 let ctx = EvaluationContext::new("55001", &external, &segments);
2441
2442 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2443 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2444 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2445 }
2446
2447 #[test]
2450 fn test_count_qualified_in_group() {
2451 let nav = MockGroupNavigator::new()
2452 .with_group(
2453 &["SG4", "SG8"],
2454 0,
2455 vec![
2456 make_segment("CCI", vec![vec!["Z23"]]),
2457 make_segment("CCI", vec![vec!["Z30"]]),
2458 ],
2459 )
2460 .with_group(
2461 &["SG4", "SG8"],
2462 1,
2463 vec![make_segment("CCI", vec![vec!["Z23"]])],
2464 );
2465 let segments = vec![
2466 make_segment("CCI", vec![vec!["Z23"]]),
2467 make_segment("CCI", vec![vec!["Z30"]]),
2468 make_segment("CCI", vec![vec!["Z23"]]),
2469 ];
2470 let external = NoOpExternalProvider;
2471 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2472
2473 assert_eq!(
2474 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2475 2
2476 );
2477 assert_eq!(
2478 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2479 1
2480 );
2481 assert_eq!(
2482 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2483 0
2484 );
2485 }
2486
2487 #[test]
2488 fn test_count_in_group() {
2489 let nav = MockGroupNavigator::new()
2490 .with_group(
2491 &["SG4", "SG8"],
2492 0,
2493 vec![
2494 make_segment("SEQ", vec![vec!["Z98"]]),
2495 make_segment("CCI", vec![vec!["Z23"]]),
2496 ],
2497 )
2498 .with_group(
2499 &["SG4", "SG8"],
2500 1,
2501 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2502 );
2503 let segments = vec![
2504 make_segment("SEQ", vec![vec!["Z98"]]),
2505 make_segment("CCI", vec![vec!["Z23"]]),
2506 make_segment("SEQ", vec![vec!["Z01"]]),
2507 ];
2508 let external = NoOpExternalProvider;
2509 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2510
2511 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2512 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2513 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2514 }
2515
2516 #[test]
2517 fn test_count_fallback_no_navigator() {
2518 let segments = vec![
2519 make_segment("CCI", vec![vec!["Z23"]]),
2520 make_segment("CCI", vec![vec!["Z30"]]),
2521 make_segment("CCI", vec![vec!["Z23"]]),
2522 ];
2523 let external = NoOpExternalProvider;
2524 let ctx = EvaluationContext::new("55001", &external, &segments);
2525
2526 assert_eq!(
2528 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2529 2
2530 );
2531 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2532 }
2533
2534 #[test]
2537 fn format_check_prefers_resolved_value() {
2538 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2540 let external = NoOpExternalProvider;
2541 let ctx = EvaluationContext::new("55001", &external, &segments)
2542 .with_resolved(Some("CORRECT"), None);
2543
2544 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2545 assert_eq!(result, ConditionResult::True);
2546 }
2547
2548 #[test]
2549 fn format_check_falls_back_to_segment_search() {
2550 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2552 let external = NoOpExternalProvider;
2553 let ctx = EvaluationContext::new("55001", &external, &segments);
2554
2555 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2556 assert_eq!(result, ConditionResult::True);
2557 }
2558
2559 #[test]
2560 fn format_check_returns_unknown_when_segment_absent() {
2561 let segments: Vec<OwnedSegment> = vec![];
2563 let external = NoOpExternalProvider;
2564 let ctx = EvaluationContext::new("55001", &external, &segments);
2565
2566 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2567 assert_eq!(result, ConditionResult::Unknown);
2568 }
2569
2570 #[test]
2571 fn format_check_returns_unknown_when_multiple_segments() {
2572 let segments = vec![
2576 make_segment("DTM", vec![vec!["92", "OK"]]),
2577 make_segment("DTM", vec![vec!["163", "BAD"]]),
2578 ];
2579 let external = NoOpExternalProvider;
2580 let ctx = EvaluationContext::new("55001", &external, &segments);
2581
2582 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2583 assert_eq!(result, ConditionResult::Unknown);
2584 }
2585
2586 #[test]
2587 fn format_check_qualified_prefers_resolved_value() {
2588 let segments = vec![
2590 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2591 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2592 ];
2593 let external = NoOpExternalProvider;
2594 let ctx = EvaluationContext::new("55001", &external, &segments)
2595 .with_resolved(Some("CORRECT"), None);
2596
2597 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2598 ConditionResult::from(v == "CORRECT")
2599 });
2600 assert_eq!(result, ConditionResult::True);
2601 }
2602
2603 #[test]
2604 fn format_check_resolved_judges_only_the_fields_own_value() {
2605 let segments = vec![make_segment("LOC", vec![vec!["Z16"], vec!["WRONG"]])];
2608 let external = NoOpExternalProvider;
2609 let ctx = EvaluationContext::new("17101", &external, &segments);
2610 let check = |v: &str| ConditionResult::from(v == "CORRECT");
2611
2612 assert_eq!(ctx.format_check_resolved(check), ConditionResult::Unknown);
2613 let field = ctx.with_resolved(Some("CORRECT"), None);
2614 assert_eq!(field.format_check_resolved(check), ConditionResult::True);
2615 }
2616
2617 #[test]
2618 fn format_check_qualified_falls_back_to_qualified_search() {
2619 let segments = vec![
2621 make_segment("DTM", vec![vec!["92", "2200"]]),
2622 make_segment("DTM", vec![vec!["163", "0800"]]),
2623 ];
2624 let external = NoOpExternalProvider;
2625 let ctx = EvaluationContext::new("55001", &external, &segments);
2626
2627 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2629 ConditionResult::from(v == "0800")
2630 });
2631 assert_eq!(result, ConditionResult::True);
2632
2633 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2635 ConditionResult::from(v == "2200")
2636 });
2637 assert_eq!(result2, ConditionResult::False);
2638 }
2639
2640 #[test]
2643 fn self_segment_value_prefers_resolved_segment() {
2644 let segments = vec![
2648 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2649 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2650 ];
2651 let external = NoOpExternalProvider;
2652 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2653 let ctx = EvaluationContext::new("55001", &external, &segments)
2654 .with_resolved(None, Some(&resolved_elements));
2655
2656 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2657 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2658 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2659 }
2660
2661 #[test]
2662 fn self_segment_value_falls_back_when_no_resolved_segment() {
2663 let segments = vec![
2665 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2666 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2667 ];
2668 let external = NoOpExternalProvider;
2669 let ctx = EvaluationContext::new("55001", &external, &segments);
2670
2671 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2672 }
2673
2674 #[test]
2675 fn self_segment_value_missing_returns_none() {
2676 let segments: Vec<OwnedSegment> = vec![];
2677 let external = NoOpExternalProvider;
2678 let ctx = EvaluationContext::new("55001", &external, &segments);
2679
2680 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2681 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2682 }
2683
2684 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2688 let mut segments = Vec::new();
2689 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2690 segments.push(OwnedSegment {
2691 id: "SEQ".to_string(),
2692 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2693 segment_number: (2 * i + 1) as u32,
2694 });
2695 segments.push(OwnedSegment {
2696 id: "PIA".to_string(),
2697 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2698 segment_number: (2 * i + 2) as u32,
2699 });
2700 }
2701 segments
2702 }
2703
2704 #[test]
2705 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2706 let segments = make_sg8_sequence(&[
2708 ("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"), ]);
2713 let external = NoOpExternalProvider;
2714 let seq = &segments[0].elements;
2716 let ctx =
2717 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2718
2719 let n = ctx
2720 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2721 assert_eq!(n, 2);
2722 }
2723
2724 #[test]
2725 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2726 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2727 let external = NoOpExternalProvider;
2728 let ctx = EvaluationContext::new("55001", &external, &segments);
2729 let n = ctx
2730 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2731 assert_eq!(n, 0);
2732 }
2733
2734 #[test]
2735 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2736 let segments = make_sg8_sequence(&[
2737 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2740 let external = NoOpExternalProvider;
2741 let seq = &segments[0].elements;
2742 let ctx =
2743 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2744
2745 assert_eq!(
2746 ctx.count_sg8_obis_matches(
2747 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2748 ),
2749 1
2750 );
2751 assert_eq!(
2752 ctx.count_sg8_obis_matches(
2753 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2754 ),
2755 1
2756 );
2757 }
2758}