1use super::codelist::ArtikelIdCodelist;
4use super::ebd_cluster::EbdClusterLookup;
5use super::evaluator::{ConditionResult, ExternalConditionProvider};
6use mig_types::navigator::GroupNavigator;
7use mig_types::segment::OwnedSegment;
8use std::collections::HashMap;
9use std::sync::{Arc, OnceLock};
10
11pub struct EvaluationContext<'a> {
16 pub pruefidentifikator: &'a str,
19
20 pub external: &'a dyn ExternalConditionProvider,
23
24 pub segments: &'a [OwnedSegment],
27
28 pub navigator: Option<&'a dyn GroupNavigator>,
31
32 pub resolved_value: Option<&'a str>,
39
40 pub resolved_segment: Option<&'a [Vec<String>]>,
46
47 pub codelist: &'a ArtikelIdCodelist,
51
52 pub ebd_clusters: &'a EbdClusterLookup,
57
58 pub scope: Option<GroupScope<'a>>,
62
63 segment_index: Arc<OnceLock<SegmentIndex>>,
68}
69
70pub struct NoOpGroupNavigator;
72
73#[derive(Debug, Clone, Copy)]
80pub struct GroupScope<'a> {
81 pub path: &'a [(&'a str, usize)],
82}
83
84impl GroupNavigator for NoOpGroupNavigator {
85 fn find_segments_in_group(&self, _: &str, _: &[&str], _: usize) -> Vec<OwnedSegment> {
86 Vec::new()
87 }
88 fn find_segments_with_qualifier_in_group(
89 &self,
90 _: &str,
91 _: usize,
92 _: &str,
93 _: &[&str],
94 _: usize,
95 ) -> Vec<OwnedSegment> {
96 Vec::new()
97 }
98 fn group_instance_count(&self, _: &[&str]) -> usize {
99 0
100 }
101}
102
103impl<'a> EvaluationContext<'a> {
104 pub fn new(
106 pruefidentifikator: &'a str,
107 external: &'a dyn ExternalConditionProvider,
108 segments: &'a [OwnedSegment],
109 ) -> Self {
110 Self {
111 pruefidentifikator,
112 external,
113 segments,
114 navigator: None,
115 resolved_value: None,
116 resolved_segment: None,
117 codelist: ArtikelIdCodelist::embedded(),
118 ebd_clusters: EbdClusterLookup::embedded(),
119 scope: None,
120 segment_index: Arc::default(),
121 }
122 }
123
124 pub fn with_navigator(
126 pruefidentifikator: &'a str,
127 external: &'a dyn ExternalConditionProvider,
128 segments: &'a [OwnedSegment],
129 navigator: &'a dyn GroupNavigator,
130 ) -> Self {
131 Self {
132 pruefidentifikator,
133 external,
134 segments,
135 navigator: Some(navigator),
136 resolved_value: None,
137 resolved_segment: None,
138 codelist: ArtikelIdCodelist::embedded(),
139 ebd_clusters: EbdClusterLookup::embedded(),
140 scope: None,
141 segment_index: Arc::default(),
142 }
143 }
144
145 pub fn with_codelist(mut self, codelist: &'a ArtikelIdCodelist) -> Self {
147 self.codelist = codelist;
148 self
149 }
150
151 pub fn with_ebd_clusters(mut self, ebd_clusters: &'a EbdClusterLookup) -> Self {
153 self.ebd_clusters = ebd_clusters;
154 self
155 }
156
157 pub fn with_scope(&self, scope: GroupScope<'a>) -> Self {
161 Self {
162 scope: Some(scope),
163 segment_index: Arc::clone(&self.segment_index),
164 ..*self
165 }
166 }
167
168 pub fn with_view<'b>(
172 &self,
173 segments: &'b [OwnedSegment],
174 navigator: &'b dyn GroupNavigator,
175 ) -> EvaluationContext<'b>
176 where
177 'a: 'b,
178 {
179 EvaluationContext {
180 pruefidentifikator: self.pruefidentifikator,
181 external: self.external,
182 segments,
183 navigator: Some(navigator),
184 resolved_value: self.resolved_value,
185 resolved_segment: self.resolved_segment,
186 codelist: self.codelist,
187 ebd_clusters: self.ebd_clusters,
188 scope: None,
189 segment_index: Arc::default(),
190 }
191 }
192
193 pub fn with_resolved(
198 &self,
199 value: Option<&'a str>,
200 segment: Option<&'a [Vec<String>]>,
201 ) -> Self {
202 Self {
203 resolved_value: value,
204 resolved_segment: segment,
205 segment_index: Arc::clone(&self.segment_index),
206 ..*self
207 }
208 }
209
210 pub fn navigator(&self) -> Option<&'a dyn GroupNavigator> {
212 self.navigator
213 }
214
215 fn index(&self) -> Option<&SegmentIndex> {
219 let index = self
220 .segment_index
221 .get_or_init(|| SegmentIndex::build(self.segments));
222 index.describes(self.segments).then_some(index)
223 }
224
225 fn lookup<'s>(
229 &'s self,
230 segment_id: &'s str,
231 qualifier: Option<(usize, &'s str)>,
232 ) -> SegmentLookup<'a, 's> {
233 let positions = match self.index() {
234 Some(index) => Positions::Listed(index.positions(segment_id, qualifier).iter()),
235 None => Positions::All(0..self.segments.len()),
236 };
237 SegmentLookup {
238 segments: self.segments,
239 positions,
240 segment_id,
241 qualifier,
242 }
243 }
244
245 pub fn find_segment(&self, segment_id: &str) -> Option<&'a OwnedSegment> {
247 self.lookup(segment_id, None).next()
248 }
249
250 pub fn find_segments(&self, segment_id: &str) -> Vec<&'a OwnedSegment> {
252 self.lookup(segment_id, None).collect()
253 }
254
255 pub fn find_segments_with_qualifier(
257 &self,
258 segment_id: &str,
259 element_index: usize,
260 qualifier: &str,
261 ) -> Vec<&'a OwnedSegment> {
262 self.lookup(segment_id, Some((element_index, qualifier)))
263 .collect()
264 }
265
266 pub fn find_segment_with_qualifier(
271 &self,
272 segment_id: &str,
273 element_index: usize,
274 qualifier: &str,
275 ) -> Option<&'a OwnedSegment> {
276 self.lookup(segment_id, Some((element_index, qualifier)))
277 .next()
278 }
279
280 pub fn scoped_find_segments(&self, tag: &str) -> Vec<OwnedSegment> {
290 match (self.scope, self.navigator) {
291 (Some(scope), Some(nav)) => nav.find_segments_in_subtree(tag, scope.path),
292 _ => self.find_segments(tag).into_iter().cloned().collect(),
293 }
294 }
295
296 pub fn scoped_find_segments_in(&self, group_id: &str, tag: &str) -> Vec<OwnedSegment> {
304 if let (Some(scope), Some(nav)) = (self.scope, self.navigator) {
305 if let Some(level) = scope.path.iter().rposition(|(id, _)| *id == group_id) {
306 return nav.find_segments_in_subtree(tag, &scope.path[..=level]);
307 }
308 }
309 self.find_segments(tag).into_iter().cloned().collect()
310 }
311
312 pub fn sibling_scopes(&self, group_id: &str) -> Option<Vec<Vec<(&'a str, usize)>>> {
317 let (scope, nav) = (self.scope?, self.navigator?);
318 let level = scope.path.iter().rposition(|(id, _)| *id == group_id)?;
319 let (parent, own) = (&scope.path[..level], scope.path[level]);
320 let count = nav.child_instance_count(parent, own.0);
321 Some(
322 (0..count)
323 .filter(|&i| i != own.1)
324 .map(|i| {
325 let mut path = parent.to_vec();
326 path.push((own.0, i));
327 path
328 })
329 .collect(),
330 )
331 }
332
333 pub fn find_segments_at(&self, path: &[(&str, usize)], tag: &str) -> Vec<OwnedSegment> {
336 self.navigator
337 .map(|nav| nav.find_segments_in_subtree(tag, path))
338 .unwrap_or_default()
339 }
340
341 pub fn scoped_find_segments_in_with_qualifier(
344 &self,
345 group_id: &str,
346 tag: &str,
347 element_index: usize,
348 qualifier: &str,
349 ) -> Vec<OwnedSegment> {
350 self.scoped_find_segments_in(group_id, tag)
351 .into_iter()
352 .filter(|s| {
353 s.elements
354 .get(element_index)
355 .and_then(|e| e.first())
356 .is_some_and(|v| v == qualifier)
357 })
358 .collect()
359 }
360
361 pub fn scoped_find_segments_with_qualifier(
364 &self,
365 tag: &str,
366 element_index: usize,
367 qualifier: &str,
368 ) -> Vec<OwnedSegment> {
369 self.scoped_find_segments(tag)
370 .into_iter()
371 .filter(|s| {
372 s.elements
373 .get(element_index)
374 .and_then(|e| e.first())
375 .is_some_and(|v| v == qualifier)
376 })
377 .collect()
378 }
379
380 pub fn format_check(
390 &self,
391 tag: &str,
392 elem: usize,
393 comp: usize,
394 validate: impl FnOnce(&str) -> ConditionResult,
395 ) -> ConditionResult {
396 if let Some(val) = self.resolved_value {
398 return validate(val);
399 }
400 let segs = self.find_segments(tag);
405 if segs.len() != 1 {
406 return ConditionResult::Unknown;
407 }
408 match segs[0]
409 .elements
410 .get(elem)
411 .and_then(|e| e.get(comp))
412 .map(|s| s.as_str())
413 {
414 Some(val) => validate(val),
415 None => ConditionResult::Unknown,
416 }
417 }
418
419 pub fn format_check_qualified(
428 &self,
429 tag: &str,
430 qual_elem: usize,
431 qualifier: &str,
432 val_elem: usize,
433 val_comp: usize,
434 validate: impl FnOnce(&str) -> ConditionResult,
435 ) -> ConditionResult {
436 if let Some(val) = self.resolved_value {
437 return validate(val);
438 }
439 let segs = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
440 if segs.len() != 1 {
441 return ConditionResult::Unknown;
442 }
443 match segs[0]
444 .elements
445 .get(val_elem)
446 .and_then(|e| e.get(val_comp))
447 .map(|s| s.as_str())
448 {
449 Some(val) => validate(val),
450 None => ConditionResult::Unknown,
451 }
452 }
453
454 pub fn format_check_resolved(
461 &self,
462 validate: impl FnOnce(&str) -> ConditionResult,
463 ) -> ConditionResult {
464 match self.resolved_value {
465 Some(val) => validate(val),
466 None => ConditionResult::Unknown,
467 }
468 }
469
470 pub fn self_segment_value(&self, tag: &str, elem: usize, comp: usize) -> Option<&str> {
482 if let Some(elements) = self.resolved_segment {
483 return elements
484 .get(elem)
485 .and_then(|e| e.get(comp))
486 .map(|s| s.as_str());
487 }
488 self.find_segment(tag)
489 .and_then(|s| s.elements.get(elem))
490 .and_then(|e| e.get(comp))
491 .map(|s| s.as_str())
492 }
493
494 pub fn self_segment_value_equals(
498 &self,
499 tag: &str,
500 elem: usize,
501 comp: usize,
502 expected: &str,
503 ) -> bool {
504 self.self_segment_value(tag, elem, comp)
505 .is_some_and(|v| v == expected)
506 }
507
508 pub fn has_segment(&self, segment_id: &str) -> bool {
510 self.find_segment(segment_id).is_some()
511 }
512
513 pub fn count_sg8_obis_matches<F>(&self, obis_pred: F) -> usize
535 where
536 F: Fn(&str) -> bool,
537 {
538 let resolved = match self.resolved_segment {
539 Some(s) => s,
540 None => return 0,
541 };
542 let cur_qual = match resolved.first().and_then(|e| e.first()) {
543 Some(v) => v.as_str(),
544 None => return 0,
545 };
546 if !matches!(cur_qual, "Z02" | "ZA1" | "ZA2") {
547 return 0;
548 }
549 let cur_zid = match resolved.get(1).and_then(|e| e.first()) {
550 Some(v) => v.as_str(),
551 None => return 0,
552 };
553
554 let mut count = 0usize;
555 let mut in_matching_seq = false;
556 for seg in self.segments {
557 if seg.id == "SEQ" {
558 let qual = seg
559 .elements
560 .first()
561 .and_then(|e| e.first())
562 .map(String::as_str);
563 let zid = seg
564 .elements
565 .get(1)
566 .and_then(|e| e.first())
567 .map(String::as_str);
568 in_matching_seq = qual == Some(cur_qual) && zid == Some(cur_zid);
569 } else if seg.id == "PIA" && in_matching_seq {
570 let p_qual = seg
571 .elements
572 .first()
573 .and_then(|e| e.first())
574 .map(String::as_str);
575 if p_qual == Some("5") {
576 if let Some(obis) = seg.elements.get(1).and_then(|e| e.first()) {
577 if obis_pred(obis) {
578 count += 1;
579 }
580 }
581 }
582 }
583 }
584 count
585 }
586
587 pub fn find_segments_in_group(
590 &self,
591 segment_id: &str,
592 group_path: &[&str],
593 instance_index: usize,
594 ) -> Vec<OwnedSegment> {
595 match self.navigator {
596 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
597 None => Vec::new(),
598 }
599 }
600
601 pub fn find_segments_with_qualifier_in_group(
604 &self,
605 segment_id: &str,
606 element_index: usize,
607 qualifier: &str,
608 group_path: &[&str],
609 instance_index: usize,
610 ) -> Vec<OwnedSegment> {
611 match self.navigator {
612 Some(nav) => nav.find_segments_with_qualifier_in_group(
613 segment_id,
614 element_index,
615 qualifier,
616 group_path,
617 instance_index,
618 ),
619 None => Vec::new(),
620 }
621 }
622
623 pub fn has_segment_in_group(
626 &self,
627 segment_id: &str,
628 group_path: &[&str],
629 instance_index: usize,
630 ) -> bool {
631 !self
632 .find_segments_in_group(segment_id, group_path, instance_index)
633 .is_empty()
634 }
635
636 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
639 match self.navigator {
640 Some(nav) => nav.group_instance_count(group_path),
641 None => 0,
642 }
643 }
644
645 pub fn child_group_instance_count(
648 &self,
649 parent_path: &[&str],
650 parent_instance: usize,
651 child_group_id: &str,
652 ) -> usize {
653 match self.navigator {
654 Some(nav) => {
655 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
656 }
657 None => 0,
658 }
659 }
660
661 pub fn find_segments_in_child_group(
664 &self,
665 segment_id: &str,
666 parent_path: &[&str],
667 parent_instance: usize,
668 child_group_id: &str,
669 child_instance: usize,
670 ) -> Vec<OwnedSegment> {
671 match self.navigator {
672 Some(nav) => nav.find_segments_in_child_group(
673 segment_id,
674 parent_path,
675 parent_instance,
676 child_group_id,
677 child_instance,
678 ),
679 None => Vec::new(),
680 }
681 }
682
683 pub fn extract_value_in_group(
686 &self,
687 segment_id: &str,
688 element_index: usize,
689 component_index: usize,
690 group_path: &[&str],
691 instance_index: usize,
692 ) -> Option<String> {
693 self.navigator?.extract_value_in_group(
694 segment_id,
695 element_index,
696 component_index,
697 group_path,
698 instance_index,
699 )
700 }
701
702 pub fn has_qualifier(
708 &self,
709 tag: &str,
710 element_index: usize,
711 qualifier: &str,
712 ) -> ConditionResult {
713 ConditionResult::from(
714 self.find_segment_with_qualifier(tag, element_index, qualifier)
715 .is_some(),
716 )
717 }
718
719 pub fn lacks_qualifier(
722 &self,
723 tag: &str,
724 element_index: usize,
725 qualifier: &str,
726 ) -> ConditionResult {
727 ConditionResult::from(
728 self.find_segment_with_qualifier(tag, element_index, qualifier)
729 .is_none(),
730 )
731 }
732
733 pub fn has_qualified_value(
738 &self,
739 tag: &str,
740 qual_elem: usize,
741 qualifier: &str,
742 value_elem: usize,
743 value_comp: usize,
744 values: &[&str],
745 ) -> ConditionResult {
746 let mut found = false;
747 for seg in self.lookup(tag, Some((qual_elem, qualifier))) {
748 found = true;
749 if let Some(v) = seg
750 .elements
751 .get(value_elem)
752 .and_then(|e| e.get(value_comp))
753 .map(|s| s.as_str())
754 {
755 if values.contains(&v) {
756 return ConditionResult::True;
757 }
758 }
759 }
760 if found {
761 ConditionResult::False
762 } else {
763 ConditionResult::Unknown
764 }
765 }
766
767 pub fn any_group_has_qualifier(
773 &self,
774 tag: &str,
775 element_index: usize,
776 qualifier: &str,
777 group_path: &[&str],
778 ) -> ConditionResult {
779 let instance_count = self.group_instance_count(group_path);
780 if instance_count > 0 {
781 for i in 0..instance_count {
782 if !self
783 .find_segments_with_qualifier_in_group(
784 tag,
785 element_index,
786 qualifier,
787 group_path,
788 i,
789 )
790 .is_empty()
791 {
792 return ConditionResult::True;
793 }
794 }
795 return ConditionResult::False;
796 }
797 self.has_qualifier(tag, element_index, qualifier)
799 }
800
801 pub fn any_group_has_any_qualifier(
807 &self,
808 tag: &str,
809 element_index: usize,
810 qualifiers: &[&str],
811 group_path: &[&str],
812 ) -> ConditionResult {
813 let instance_count = self.group_instance_count(group_path);
814 if instance_count > 0 {
815 for i in 0..instance_count {
816 let segs = self.find_segments_in_group(tag, group_path, i);
817 if segs.iter().any(|seg| {
818 seg.elements
819 .get(element_index)
820 .and_then(|e| e.first())
821 .map(|s| s.as_str())
822 .is_some_and(|v| qualifiers.contains(&v))
823 }) {
824 return ConditionResult::True;
825 }
826 }
827 return ConditionResult::False;
828 }
829 let found = self.find_segments(tag).iter().any(|seg| {
831 seg.elements
832 .get(element_index)
833 .and_then(|e| e.first())
834 .map(|s| s.as_str())
835 .is_some_and(|v| qualifiers.contains(&v))
836 });
837 ConditionResult::from(found)
838 }
839
840 pub fn any_group_has_qualified_value(
846 &self,
847 tag: &str,
848 qual_elem: usize,
849 qualifier: &str,
850 value_elem: usize,
851 value_comp: usize,
852 values: &[&str],
853 group_path: &[&str],
854 ) -> ConditionResult {
855 let instance_count = self.group_instance_count(group_path);
856 if instance_count > 0 {
857 for i in 0..instance_count {
858 let segs = self.find_segments_with_qualifier_in_group(
859 tag, qual_elem, qualifier, group_path, i,
860 );
861 for seg in &segs {
862 if seg
863 .elements
864 .get(value_elem)
865 .and_then(|e| e.get(value_comp))
866 .map(|s| s.as_str())
867 .is_some_and(|v| values.contains(&v))
868 {
869 return ConditionResult::True;
870 }
871 }
872 }
873 return ConditionResult::False;
874 }
875 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
877 }
878
879 #[allow(clippy::too_many_arguments)]
888 pub fn filtered_parent_child_has_qualifier(
889 &self,
890 parent_path: &[&str],
891 parent_tag: &str,
892 parent_elem: usize,
893 parent_qual: &str,
894 child_group_id: &str,
895 child_tag: &str,
896 child_elem: usize,
897 child_qual: &str,
898 ) -> ConditionResult {
899 let parent_count = self.group_instance_count(parent_path);
900 if parent_count > 0 {
901 for pi in 0..parent_count {
902 let parent_segs = self.find_segments_with_qualifier_in_group(
904 parent_tag,
905 parent_elem,
906 parent_qual,
907 parent_path,
908 pi,
909 );
910 if parent_segs.is_empty() {
911 continue;
912 }
913 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
915 for ci in 0..child_count {
916 let child_segs = self.find_segments_in_child_group(
917 child_tag,
918 parent_path,
919 pi,
920 child_group_id,
921 ci,
922 );
923 if child_segs.iter().any(|s| {
924 s.elements
925 .get(child_elem)
926 .and_then(|e| e.first())
927 .is_some_and(|v| v == child_qual)
928 }) {
929 return ConditionResult::True;
930 }
931 }
932 }
933 return ConditionResult::False;
934 }
935 let has_parent = self
937 .find_segment_with_qualifier(parent_tag, parent_elem, parent_qual)
938 .is_some();
939 let has_child = self
940 .find_segment_with_qualifier(child_tag, child_elem, child_qual)
941 .is_some();
942 ConditionResult::from(has_parent && has_child)
943 }
944
945 #[allow(clippy::too_many_arguments)]
951 pub fn any_group_has_qualifier_without(
952 &self,
953 present_tag: &str,
954 present_elem: usize,
955 present_qual: &str,
956 absent_tag: &str,
957 absent_elem: usize,
958 absent_qual: &str,
959 group_path: &[&str],
960 ) -> ConditionResult {
961 let instance_count = self.group_instance_count(group_path);
962 if instance_count > 0 {
963 for i in 0..instance_count {
964 let has_present = !self
965 .find_segments_with_qualifier_in_group(
966 present_tag,
967 present_elem,
968 present_qual,
969 group_path,
970 i,
971 )
972 .is_empty();
973 let has_absent = !self
974 .find_segments_with_qualifier_in_group(
975 absent_tag,
976 absent_elem,
977 absent_qual,
978 group_path,
979 i,
980 )
981 .is_empty();
982 if has_present && !has_absent {
983 return ConditionResult::True;
984 }
985 }
986 return ConditionResult::False;
987 }
988 let has_present = self
990 .find_segment_with_qualifier(present_tag, present_elem, present_qual)
991 .is_some();
992 let has_absent = self
993 .find_segment_with_qualifier(absent_tag, absent_elem, absent_qual)
994 .is_some();
995 ConditionResult::from(has_present && !has_absent)
996 }
997
998 fn enclosing_path(&self, group_path: &[&str]) -> Option<&'a [(&'a str, usize)]> {
1001 let group = group_path.last()?;
1002 let scope = self.scope?;
1003 self.navigator?;
1004 let level = scope.path.iter().rposition(|(id, _)| id == group)?;
1005 Some(&scope.path[..=level])
1006 }
1007
1008 pub fn enclosing_scope(&self, group_id: &str) -> Option<&'a [(&'a str, usize)]> {
1012 self.enclosing_path(&[group_id])
1013 }
1014
1015 pub fn enclosing_child_segments(
1020 &self,
1021 group_id: &str,
1022 child_id: &str,
1023 tags: &[&str],
1024 ) -> Option<Vec<Vec<OwnedSegment>>> {
1025 let path = self.enclosing_scope(group_id)?;
1026 Some(self.child_segments_at(path, child_id, tags))
1027 }
1028
1029 pub fn child_segments_at(
1032 &self,
1033 path: &[(&str, usize)],
1034 child_id: &str,
1035 tags: &[&str],
1036 ) -> Vec<Vec<OwnedSegment>> {
1037 let Some(nav) = self.navigator else {
1038 return Vec::new();
1039 };
1040 (0..nav.child_instance_count(path, child_id))
1041 .map(|j| {
1042 let mut child = path.to_vec();
1043 child.push((child_id, j));
1044 tags.iter()
1045 .flat_map(|tag| nav.find_segments_in_subtree(tag, &child))
1046 .collect()
1047 })
1048 .collect()
1049 }
1050
1051 pub fn scopes_under(
1056 &self,
1057 parent_id: &str,
1058 group_id: &'a str,
1059 ) -> Option<Vec<Vec<(&'a str, usize)>>> {
1060 let parent = self.enclosing_scope(parent_id)?;
1061 let nav = self.navigator?;
1062 Some(
1063 (0..nav.child_instance_count(parent, group_id))
1064 .map(|i| {
1065 let mut path = parent.to_vec();
1066 path.push((group_id, i));
1067 path
1068 })
1069 .collect(),
1070 )
1071 }
1072
1073 #[allow(clippy::too_many_arguments)]
1077 pub fn this_group_has_qualifier_without(
1078 &self,
1079 present_tag: &str,
1080 present_elem: usize,
1081 present_qual: &str,
1082 absent_tag: &str,
1083 absent_elem: usize,
1084 absent_qual: &str,
1085 group_path: &[&str],
1086 ) -> ConditionResult {
1087 let Some(path) = self.enclosing_path(group_path) else {
1088 return self.any_group_has_qualifier_without(
1089 present_tag,
1090 present_elem,
1091 present_qual,
1092 absent_tag,
1093 absent_elem,
1094 absent_qual,
1095 group_path,
1096 );
1097 };
1098 let has = |tag: &str, elem: usize, qual: &str| {
1099 self.find_segments_at(path, tag)
1100 .iter()
1101 .any(|s| first_component(s, elem) == Some(qual))
1102 };
1103 ConditionResult::from(
1104 has(present_tag, present_elem, present_qual)
1105 && !has(absent_tag, absent_elem, absent_qual),
1106 )
1107 }
1108
1109 pub fn this_group_lacks_qualifier(
1119 &self,
1120 tag: &str,
1121 element_index: usize,
1122 qualifier: &str,
1123 group_path: &[&str],
1124 ) -> ConditionResult {
1125 match self.enclosing_path(group_path) {
1126 Some(path) => ConditionResult::from(
1127 !self
1128 .find_segments_at(path, tag)
1129 .iter()
1130 .any(|s| first_component(s, element_index) == Some(qualifier)),
1131 ),
1132 None => ConditionResult::Unknown,
1133 }
1134 }
1135
1136 #[allow(clippy::too_many_arguments)]
1140 pub fn this_group_parent_child_has_qualifier(
1141 &self,
1142 parent_path: &[&str],
1143 parent_tag: &str,
1144 parent_elem: usize,
1145 parent_qual: &str,
1146 child_group_id: &str,
1147 child_tag: &str,
1148 child_elem: usize,
1149 child_qual: &str,
1150 ) -> ConditionResult {
1151 let (Some(path), Some(nav)) = (self.enclosing_path(parent_path), self.navigator) else {
1152 return self.filtered_parent_child_has_qualifier(
1153 parent_path,
1154 parent_tag,
1155 parent_elem,
1156 parent_qual,
1157 child_group_id,
1158 child_tag,
1159 child_elem,
1160 child_qual,
1161 );
1162 };
1163 let parent_matches = nav
1164 .find_segments_in_instance(parent_tag, path)
1165 .iter()
1166 .any(|s| first_component(s, parent_elem) == Some(parent_qual));
1167 if !parent_matches {
1168 return ConditionResult::False;
1169 }
1170 let children = nav.child_instance_count(path, child_group_id);
1171 let found = (0..children).any(|j| {
1172 let mut child = path.to_vec();
1173 child.push((child_group_id, j));
1174 nav.find_segments_in_subtree(child_tag, &child)
1175 .iter()
1176 .any(|s| first_component(s, child_elem) == Some(child_qual))
1177 });
1178 ConditionResult::from(found)
1179 }
1180
1181 pub fn this_group_has_qualifier(
1185 &self,
1186 tag: &str,
1187 element_index: usize,
1188 qualifier: &str,
1189 group_path: &[&str],
1190 ) -> ConditionResult {
1191 match self.enclosing_path(group_path) {
1192 Some(path) => ConditionResult::from(
1193 self.find_segments_at(path, tag)
1194 .iter()
1195 .any(|s| first_component(s, element_index) == Some(qualifier)),
1196 ),
1197 None => self.any_group_has_qualifier(tag, element_index, qualifier, group_path),
1198 }
1199 }
1200
1201 pub fn this_group_has_any_qualifier(
1204 &self,
1205 tag: &str,
1206 element_index: usize,
1207 qualifiers: &[&str],
1208 group_path: &[&str],
1209 ) -> ConditionResult {
1210 match self.enclosing_path(group_path) {
1211 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1212 first_component(s, element_index).is_some_and(|v| qualifiers.contains(&v))
1213 })),
1214 None => self.any_group_has_any_qualifier(tag, element_index, qualifiers, group_path),
1215 }
1216 }
1217
1218 #[allow(clippy::too_many_arguments)]
1221 pub fn this_group_has_qualified_value(
1222 &self,
1223 tag: &str,
1224 qual_elem: usize,
1225 qualifier: &str,
1226 value_elem: usize,
1227 value_comp: usize,
1228 values: &[&str],
1229 group_path: &[&str],
1230 ) -> ConditionResult {
1231 match self.enclosing_path(group_path) {
1232 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1233 first_component(s, qual_elem) == Some(qualifier)
1234 && s.elements
1235 .get(value_elem)
1236 .and_then(|e| e.get(value_comp))
1237 .is_some_and(|v| values.contains(&v.as_str()))
1238 })),
1239 None => self.any_group_has_qualified_value(
1240 tag, qual_elem, qualifier, value_elem, value_comp, values, group_path,
1241 ),
1242 }
1243 }
1244
1245 pub fn this_group_values(
1249 &self,
1250 tag: &str,
1251 elem: usize,
1252 comp: usize,
1253 group_path: &[&str],
1254 ) -> Vec<(usize, String)> {
1255 match self.enclosing_path(group_path) {
1256 Some(path) => self
1257 .find_segments_at(path, tag)
1258 .iter()
1259 .filter_map(|s| s.elements.get(elem).and_then(|e| e.get(comp)).cloned())
1260 .filter(|v| !v.is_empty())
1261 .map(|v| (0, v))
1262 .collect(),
1263 None => self.collect_group_values(tag, elem, comp, group_path),
1264 }
1265 }
1266
1267 pub fn collect_group_values(
1271 &self,
1272 tag: &str,
1273 elem: usize,
1274 comp: usize,
1275 group_path: &[&str],
1276 ) -> Vec<(usize, String)> {
1277 let instance_count = self.group_instance_count(group_path);
1278 let mut results = Vec::new();
1279 for i in 0..instance_count {
1280 if let Some(val) = self
1281 .navigator
1282 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
1283 {
1284 if !val.is_empty() {
1285 results.push((i, val));
1286 }
1287 }
1288 }
1289 results
1290 }
1291
1292 #[allow(clippy::too_many_arguments)]
1301 pub fn groups_share_qualified_value(
1302 &self,
1303 source_tag: &str,
1304 source_qual_elem: usize,
1305 source_qual: &str,
1306 source_value_elem: usize,
1307 source_value_comp: usize,
1308 source_path: &[&str],
1309 target_tag: &str,
1310 target_elem: usize,
1311 target_comp: usize,
1312 target_path: &[&str],
1313 ) -> ConditionResult {
1314 let source_count = self.group_instance_count(source_path);
1315 let target_count = self.group_instance_count(target_path);
1316 if source_count > 0 && target_count > 0 {
1317 let mut source_values = Vec::new();
1319 for si in 0..source_count {
1320 let segs = self.find_segments_with_qualifier_in_group(
1321 source_tag,
1322 source_qual_elem,
1323 source_qual,
1324 source_path,
1325 si,
1326 );
1327 for seg in &segs {
1328 if let Some(val) = seg
1329 .elements
1330 .get(source_value_elem)
1331 .and_then(|e| e.get(source_value_comp))
1332 {
1333 if !val.is_empty() {
1334 source_values.push(val.clone());
1335 }
1336 }
1337 }
1338 }
1339 if source_values.is_empty() {
1340 return ConditionResult::Unknown;
1341 }
1342 let target_values =
1344 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
1345 for (_, tv) in &target_values {
1346 if source_values.iter().any(|sv| sv == tv) {
1347 return ConditionResult::True;
1348 }
1349 }
1350 return ConditionResult::False;
1351 }
1352 let source_segs =
1354 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
1355 let source_vals: Vec<&str> = source_segs
1356 .iter()
1357 .filter_map(|s| {
1358 s.elements
1359 .get(source_value_elem)
1360 .and_then(|e| e.get(source_value_comp))
1361 .map(|v| v.as_str())
1362 })
1363 .filter(|v| !v.is_empty())
1364 .collect();
1365 if source_vals.is_empty() {
1366 return ConditionResult::Unknown;
1367 }
1368 let target_segs = self.find_segments(target_tag);
1369 let has_match = target_segs.iter().any(|s| {
1370 s.elements
1371 .get(target_elem)
1372 .and_then(|e| e.get(target_comp))
1373 .map(|v| v.as_str())
1374 .is_some_and(|v| source_vals.contains(&v))
1375 });
1376 ConditionResult::from(has_match)
1377 }
1378
1379 #[allow(clippy::too_many_arguments)]
1388 pub fn any_group_has_co_occurrence(
1389 &self,
1390 tag_a: &str,
1391 elem_a: usize,
1392 quals_a: &[&str],
1393 tag_b: &str,
1394 elem_b: usize,
1395 comp_b: usize,
1396 vals_b: &[&str],
1397 group_path: &[&str],
1398 ) -> ConditionResult {
1399 let instance_count = self.group_instance_count(group_path);
1400 if instance_count > 0 {
1401 for i in 0..instance_count {
1402 let a_present = self
1403 .find_segments_in_group(tag_a, group_path, i)
1404 .iter()
1405 .any(|seg| {
1406 seg.elements
1407 .get(elem_a)
1408 .and_then(|e| e.first())
1409 .map(|s| s.as_str())
1410 .is_some_and(|v| quals_a.contains(&v))
1411 });
1412 let b_present = self
1413 .find_segments_in_group(tag_b, group_path, i)
1414 .iter()
1415 .any(|seg| {
1416 seg.elements
1417 .get(elem_b)
1418 .and_then(|e| e.get(comp_b))
1419 .map(|s| s.as_str())
1420 .is_some_and(|v| vals_b.contains(&v))
1421 });
1422 if a_present && b_present {
1423 return ConditionResult::True;
1424 }
1425 }
1426 return ConditionResult::False;
1427 }
1428 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1430 seg.elements
1431 .get(elem_a)
1432 .and_then(|e| e.first())
1433 .map(|s| s.as_str())
1434 .is_some_and(|v| quals_a.contains(&v))
1435 });
1436 if !a_found {
1437 return ConditionResult::False;
1438 }
1439 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1440 seg.elements
1441 .get(elem_b)
1442 .and_then(|e| e.get(comp_b))
1443 .map(|s| s.as_str())
1444 .is_some_and(|v| vals_b.contains(&v))
1445 });
1446 ConditionResult::from(b_found)
1447 }
1448
1449 pub fn has_segment_matching(
1459 &self,
1460 tag: &str,
1461 checks: &[(usize, usize, &str)],
1462 ) -> ConditionResult {
1463 let segments = self.find_segments(tag);
1464 if segments.is_empty() {
1465 return ConditionResult::Unknown;
1466 }
1467 let found = segments.iter().any(|seg| {
1468 checks.iter().all(|(elem, comp, val)| {
1469 seg.elements
1470 .get(*elem)
1471 .and_then(|e| e.get(*comp))
1472 .is_some_and(|v| v == val)
1473 })
1474 });
1475 ConditionResult::from(found)
1476 }
1477
1478 pub fn has_segment_matching_in_group(
1483 &self,
1484 tag: &str,
1485 checks: &[(usize, usize, &str)],
1486 group_path: &[&str],
1487 ) -> ConditionResult {
1488 let instance_count = self.group_instance_count(group_path);
1489 if instance_count > 0 {
1490 for i in 0..instance_count {
1491 let segs = self.find_segments_in_group(tag, group_path, i);
1492 if segs.iter().any(|seg| {
1493 checks.iter().all(|(elem, comp, val)| {
1494 seg.elements
1495 .get(*elem)
1496 .and_then(|e| e.get(*comp))
1497 .is_some_and(|v| v == val)
1498 })
1499 }) {
1500 return ConditionResult::True;
1501 }
1502 }
1503 return ConditionResult::False;
1504 }
1505 self.has_segment_matching(tag, checks)
1507 }
1508
1509 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1516 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1517 match segs.first() {
1518 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1519 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1520 None => ConditionResult::Unknown,
1521 },
1522 None => ConditionResult::Unknown,
1523 }
1524 }
1525
1526 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1528 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1529 match segs.first() {
1530 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1531 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1532 None => ConditionResult::Unknown,
1533 },
1534 None => ConditionResult::Unknown,
1535 }
1536 }
1537
1538 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1540 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1541 match segs.first() {
1542 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1543 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1544 None => ConditionResult::Unknown,
1545 },
1546 None => ConditionResult::Unknown,
1547 }
1548 }
1549
1550 pub fn count_qualified_in_group(
1557 &self,
1558 tag: &str,
1559 element_index: usize,
1560 qualifier: &str,
1561 group_path: &[&str],
1562 ) -> usize {
1563 let instance_count = self.group_instance_count(group_path);
1564 if instance_count > 0 {
1565 let mut total = 0;
1566 for i in 0..instance_count {
1567 total += self
1568 .find_segments_with_qualifier_in_group(
1569 tag,
1570 element_index,
1571 qualifier,
1572 group_path,
1573 i,
1574 )
1575 .len();
1576 }
1577 return total;
1578 }
1579 self.find_segments_with_qualifier(tag, element_index, qualifier)
1581 .len()
1582 }
1583
1584 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1586 let instance_count = self.group_instance_count(group_path);
1587 if instance_count > 0 {
1588 let mut total = 0;
1589 for i in 0..instance_count {
1590 total += self.find_segments_in_group(tag, group_path, i).len();
1591 }
1592 return total;
1593 }
1594 self.find_segments(tag).len()
1596 }
1597}
1598
1599fn first_component(segment: &OwnedSegment, element: usize) -> Option<&str> {
1601 segment
1602 .elements
1603 .get(element)
1604 .and_then(|e| e.first())
1605 .map(String::as_str)
1606}
1607
1608#[derive(Debug)]
1611struct SegmentIndex {
1612 slice: (usize, usize),
1615 by_tag: HashMap<String, TagPositions>,
1616}
1617
1618#[derive(Debug, Default)]
1619struct TagPositions {
1620 all: Vec<usize>,
1621 by_qualifier: HashMap<String, Vec<usize>>,
1622}
1623
1624impl SegmentIndex {
1625 fn build(segments: &[OwnedSegment]) -> Self {
1626 let mut by_tag: HashMap<String, TagPositions> = HashMap::new();
1627 for (position, segment) in segments.iter().enumerate() {
1628 let tag = by_tag.entry(segment.id.clone()).or_default();
1629 tag.all.push(position);
1630 if let Some(qualifier) = segment.elements.first().and_then(|e| e.first()) {
1631 tag.by_qualifier
1632 .entry(qualifier.clone())
1633 .or_default()
1634 .push(position);
1635 }
1636 }
1637 Self {
1638 slice: Self::address(segments),
1639 by_tag,
1640 }
1641 }
1642
1643 fn address(segments: &[OwnedSegment]) -> (usize, usize) {
1644 (segments.as_ptr() as usize, segments.len())
1645 }
1646
1647 fn describes(&self, segments: &[OwnedSegment]) -> bool {
1651 self.slice == Self::address(segments)
1652 }
1653
1654 fn positions(&self, tag: &str, qualifier: Option<(usize, &str)>) -> &[usize] {
1657 let Some(tag) = self.by_tag.get(tag) else {
1658 return &[];
1659 };
1660 match qualifier {
1661 Some((0, qualifier)) => tag.by_qualifier.get(qualifier).map_or(&[], Vec::as_slice),
1662 _ => &tag.all,
1663 }
1664 }
1665}
1666
1667struct SegmentLookup<'a, 's> {
1671 segments: &'a [OwnedSegment],
1672 positions: Positions<'s>,
1673 segment_id: &'s str,
1674 qualifier: Option<(usize, &'s str)>,
1675}
1676
1677enum Positions<'s> {
1678 Listed(std::slice::Iter<'s, usize>),
1679 All(std::ops::Range<usize>),
1680}
1681
1682impl<'a> Iterator for SegmentLookup<'a, '_> {
1683 type Item = &'a OwnedSegment;
1684
1685 fn next(&mut self) -> Option<&'a OwnedSegment> {
1686 loop {
1687 let position = match &mut self.positions {
1688 Positions::Listed(positions) => *positions.next()?,
1689 Positions::All(positions) => positions.next()?,
1690 };
1691 let segment = &self.segments[position];
1692 let matches = segment.id == self.segment_id
1693 && match self.qualifier {
1694 None => true,
1695 Some((element_index, qualifier)) => segment
1696 .elements
1697 .get(element_index)
1698 .and_then(|e| e.first())
1699 .is_some_and(|v| v == qualifier),
1700 };
1701 if matches {
1702 return Some(segment);
1703 }
1704 }
1705 }
1706}
1707
1708#[cfg(test)]
1709mod tests {
1710 use super::super::evaluator::NoOpExternalProvider;
1711 use super::*;
1712 use mig_types::navigator::GroupNavigator;
1713
1714 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1715 OwnedSegment {
1716 id: id.to_string(),
1717 elements: elements
1718 .into_iter()
1719 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1720 .collect(),
1721 segment_number: 0,
1722 }
1723 }
1724
1725 struct MockGroupNavigator {
1727 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1728 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1730 }
1731
1732 impl MockGroupNavigator {
1733 fn new() -> Self {
1734 Self {
1735 groups: vec![],
1736 children: vec![],
1737 }
1738 }
1739 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1740 self.groups
1741 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1742 self
1743 }
1744 fn with_child_group(
1745 mut self,
1746 parent_path: &[&str],
1747 parent_instance: usize,
1748 child_id: &str,
1749 child_instance: usize,
1750 segs: Vec<OwnedSegment>,
1751 ) -> Self {
1752 self.children.push((
1753 parent_path.iter().map(|s| s.to_string()).collect(),
1754 parent_instance,
1755 child_id.to_string(),
1756 child_instance,
1757 segs,
1758 ));
1759 self
1760 }
1761 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1762 self.groups
1763 .iter()
1764 .find(|(p, i, _)| {
1765 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1766 ps.as_slice() == group_path && *i == idx
1767 })
1768 .map(|(_, _, segs)| segs.as_slice())
1769 }
1770 }
1771
1772 impl GroupNavigator for MockGroupNavigator {
1773 fn find_segments_in_group(
1774 &self,
1775 segment_id: &str,
1776 group_path: &[&str],
1777 instance_index: usize,
1778 ) -> Vec<OwnedSegment> {
1779 self.find_instance(group_path, instance_index)
1780 .map(|segs| {
1781 segs.iter()
1782 .filter(|s| s.id == segment_id)
1783 .cloned()
1784 .collect()
1785 })
1786 .unwrap_or_default()
1787 }
1788 fn find_segments_with_qualifier_in_group(
1789 &self,
1790 segment_id: &str,
1791 element_index: usize,
1792 qualifier: &str,
1793 group_path: &[&str],
1794 instance_index: usize,
1795 ) -> Vec<OwnedSegment> {
1796 self.find_segments_in_group(segment_id, group_path, instance_index)
1797 .into_iter()
1798 .filter(|s| {
1799 s.elements
1800 .get(element_index)
1801 .and_then(|e| e.first())
1802 .is_some_and(|v| v == qualifier)
1803 })
1804 .collect()
1805 }
1806 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1807 self.groups
1808 .iter()
1809 .filter(|(p, _, _)| {
1810 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1811 ps.as_slice() == group_path
1812 })
1813 .count()
1814 }
1815 fn child_group_instance_count(
1816 &self,
1817 parent_path: &[&str],
1818 parent_instance: usize,
1819 child_group_id: &str,
1820 ) -> usize {
1821 self.children
1822 .iter()
1823 .filter(|(pp, pi, cid, _, _)| {
1824 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1825 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1826 })
1827 .count()
1828 }
1829 fn find_segments_in_child_group(
1830 &self,
1831 segment_id: &str,
1832 parent_path: &[&str],
1833 parent_instance: usize,
1834 child_group_id: &str,
1835 child_instance: usize,
1836 ) -> Vec<OwnedSegment> {
1837 self.children
1838 .iter()
1839 .find(|(pp, pi, cid, ci, _)| {
1840 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1841 ps.as_slice() == parent_path
1842 && *pi == parent_instance
1843 && cid == child_group_id
1844 && *ci == child_instance
1845 })
1846 .map(|(_, _, _, _, segs)| {
1847 segs.iter()
1848 .filter(|s| s.id == segment_id)
1849 .cloned()
1850 .collect()
1851 })
1852 .unwrap_or_default()
1853 }
1854 fn extract_value_in_group(
1855 &self,
1856 segment_id: &str,
1857 element_index: usize,
1858 component_index: usize,
1859 group_path: &[&str],
1860 instance_index: usize,
1861 ) -> Option<String> {
1862 let segs = self.find_instance(group_path, instance_index)?;
1863 let seg = segs.iter().find(|s| s.id == segment_id)?;
1864 seg.elements
1865 .get(element_index)?
1866 .get(component_index)
1867 .cloned()
1868 }
1869 }
1870
1871 #[test]
1872 fn test_find_segment() {
1873 let segments = vec![
1874 make_segment("UNH", vec![vec!["test"]]),
1875 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1876 ];
1877 let external = NoOpExternalProvider;
1878 let ctx = EvaluationContext::new("11001", &external, &segments);
1879
1880 assert!(ctx.find_segment("NAD").is_some());
1881 assert!(ctx.find_segment("DTM").is_none());
1882 }
1883
1884 #[test]
1885 fn test_find_segments_with_qualifier() {
1886 let segments = vec![
1887 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1888 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1889 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1890 ];
1891 let external = NoOpExternalProvider;
1892 let ctx = EvaluationContext::new("11001", &external, &segments);
1893
1894 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1895 assert_eq!(ms_nads.len(), 2);
1896 }
1897
1898 #[test]
1899 fn test_has_segment() {
1900 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1901 let external = NoOpExternalProvider;
1902 let ctx = EvaluationContext::new("11001", &external, &segments);
1903
1904 assert!(ctx.has_segment("UNH"));
1905 assert!(!ctx.has_segment("NAD"));
1906 }
1907
1908 #[test]
1911 fn test_no_navigator_group_find_returns_empty() {
1912 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1913 let external = NoOpExternalProvider;
1914 let ctx = EvaluationContext::new("55001", &external, &segments);
1915 assert!(ctx
1916 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1917 .is_empty());
1918 }
1919
1920 #[test]
1921 fn test_no_navigator_group_instance_count_zero() {
1922 let external = NoOpExternalProvider;
1923 let ctx = EvaluationContext::new("55001", &external, &[]);
1924 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1925 }
1926
1927 #[test]
1928 fn test_with_navigator_finds_segments_in_group() {
1929 let external = NoOpExternalProvider;
1930 let nav = MockGroupNavigator::new().with_group(
1931 &["SG4", "SG8"],
1932 0,
1933 vec![
1934 make_segment("SEQ", vec![vec!["Z98"]]),
1935 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1936 ],
1937 );
1938 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1939 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1940 assert_eq!(result.len(), 1);
1941 assert_eq!(result[0].id, "SEQ");
1942 }
1943
1944 #[test]
1945 fn test_with_navigator_qualifier_in_group() {
1946 let external = NoOpExternalProvider;
1947 let nav = MockGroupNavigator::new().with_group(
1948 &["SG4", "SG8"],
1949 0,
1950 vec![
1951 make_segment("SEQ", vec![vec!["Z98"]]),
1952 make_segment("SEQ", vec![vec!["Z01"]]),
1953 ],
1954 );
1955 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1956 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1957 assert_eq!(result.len(), 1);
1958 }
1959
1960 #[test]
1961 fn test_group_instance_count_with_navigator() {
1962 let external = NoOpExternalProvider;
1963 let nav = MockGroupNavigator::new()
1964 .with_group(
1965 &["SG4", "SG8"],
1966 0,
1967 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1968 )
1969 .with_group(
1970 &["SG4", "SG8"],
1971 1,
1972 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1973 );
1974 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1975 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1976 }
1977
1978 #[test]
1979 fn test_has_segment_in_group() {
1980 let external = NoOpExternalProvider;
1981 let nav = MockGroupNavigator::new().with_group(
1982 &["SG4", "SG8"],
1983 0,
1984 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1985 );
1986 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1987 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1988 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1989 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1990 }
1991
1992 #[test]
1995 fn test_has_qualifier() {
1996 let segments = vec![
1997 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1998 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1999 ];
2000 let external = NoOpExternalProvider;
2001 let ctx = EvaluationContext::new("11001", &external, &segments);
2002
2003 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
2004 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
2005 }
2006
2007 #[test]
2008 fn test_lacks_qualifier() {
2009 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
2010 let external = NoOpExternalProvider;
2011 let ctx = EvaluationContext::new("11001", &external, &segments);
2012
2013 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
2014 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
2015 }
2016
2017 #[test]
2018 fn test_has_qualified_value() {
2019 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
2020 let external = NoOpExternalProvider;
2021 let ctx = EvaluationContext::new("55001", &external, &segments);
2022
2023 assert_eq!(
2024 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
2025 ConditionResult::True,
2026 );
2027 assert_eq!(
2028 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
2029 ConditionResult::False,
2030 );
2031 assert_eq!(
2033 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
2034 ConditionResult::Unknown,
2035 );
2036 }
2037
2038 #[test]
2039 fn test_any_group_has_qualifier() {
2040 let external = NoOpExternalProvider;
2041 let nav = MockGroupNavigator::new()
2042 .with_group(
2043 &["SG4", "SG8"],
2044 0,
2045 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2046 )
2047 .with_group(
2048 &["SG4", "SG8"],
2049 1,
2050 vec![make_segment("SEQ", vec![vec!["Z98"]])],
2051 );
2052 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2053
2054 assert_eq!(
2055 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
2056 ConditionResult::True,
2057 );
2058 assert_eq!(
2059 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
2060 ConditionResult::False,
2061 );
2062 }
2063
2064 #[test]
2065 fn test_any_group_has_qualifier_fallback() {
2066 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
2068 let external = NoOpExternalProvider;
2069 let ctx = EvaluationContext::new("55001", &external, &segments);
2070
2071 assert_eq!(
2072 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
2073 ConditionResult::True,
2074 );
2075 }
2076
2077 #[test]
2078 fn test_any_group_has_any_qualifier() {
2079 let external = NoOpExternalProvider;
2080 let nav = MockGroupNavigator::new().with_group(
2081 &["SG4", "SG8"],
2082 0,
2083 vec![make_segment("SEQ", vec![vec!["Z80"]])],
2084 );
2085 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2086
2087 assert_eq!(
2088 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
2089 ConditionResult::True,
2090 );
2091 assert_eq!(
2092 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
2093 ConditionResult::False,
2094 );
2095 }
2096
2097 #[test]
2098 fn test_any_group_has_co_occurrence() {
2099 let external = NoOpExternalProvider;
2100 let nav = MockGroupNavigator::new().with_group(
2101 &["SG4", "SG8"],
2102 0,
2103 vec![
2104 make_segment("SEQ", vec![vec!["Z01"]]),
2105 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
2106 ],
2107 );
2108 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2109
2110 assert_eq!(
2111 ctx.any_group_has_co_occurrence(
2112 "SEQ",
2113 0,
2114 &["Z01"],
2115 "CCI",
2116 2,
2117 0,
2118 &["Z07"],
2119 &["SG4", "SG8"],
2120 ),
2121 ConditionResult::True,
2122 );
2123 assert_eq!(
2125 ctx.any_group_has_co_occurrence(
2126 "SEQ",
2127 0,
2128 &["Z01"],
2129 "CCI",
2130 2,
2131 0,
2132 &["ZC0"],
2133 &["SG4", "SG8"],
2134 ),
2135 ConditionResult::False,
2136 );
2137 }
2138
2139 #[test]
2142 fn test_filtered_parent_child_has_qualifier() {
2143 let external = NoOpExternalProvider;
2144 let nav = MockGroupNavigator::new()
2147 .with_group(
2148 &["SG4", "SG8"],
2149 0,
2150 vec![make_segment("SEQ", vec![vec!["Z98"]])],
2151 )
2152 .with_group(
2153 &["SG4", "SG8"],
2154 1,
2155 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2156 )
2157 .with_child_group(
2158 &["SG4", "SG8"],
2159 0,
2160 "SG10",
2161 0,
2162 vec![make_segment("CCI", vec![vec!["Z23"]])],
2163 );
2164 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2165
2166 assert_eq!(
2168 ctx.filtered_parent_child_has_qualifier(
2169 &["SG4", "SG8"],
2170 "SEQ",
2171 0,
2172 "Z98",
2173 "SG10",
2174 "CCI",
2175 0,
2176 "Z23",
2177 ),
2178 ConditionResult::True,
2179 );
2180 assert_eq!(
2182 ctx.filtered_parent_child_has_qualifier(
2183 &["SG4", "SG8"],
2184 "SEQ",
2185 0,
2186 "Z01",
2187 "SG10",
2188 "CCI",
2189 0,
2190 "Z23",
2191 ),
2192 ConditionResult::False,
2193 );
2194 assert_eq!(
2196 ctx.filtered_parent_child_has_qualifier(
2197 &["SG4", "SG8"],
2198 "SEQ",
2199 0,
2200 "Z98",
2201 "SG10",
2202 "CCI",
2203 0,
2204 "Z99",
2205 ),
2206 ConditionResult::False,
2207 );
2208 }
2209
2210 #[test]
2211 fn test_filtered_parent_child_fallback() {
2212 let segments = vec![
2214 make_segment("SEQ", vec![vec!["Z98"]]),
2215 make_segment("CCI", vec![vec!["Z23"]]),
2216 ];
2217 let external = NoOpExternalProvider;
2218 let ctx = EvaluationContext::new("55001", &external, &segments);
2219
2220 assert_eq!(
2221 ctx.filtered_parent_child_has_qualifier(
2222 &["SG4", "SG8"],
2223 "SEQ",
2224 0,
2225 "Z98",
2226 "SG10",
2227 "CCI",
2228 0,
2229 "Z23",
2230 ),
2231 ConditionResult::True,
2232 );
2233 assert_eq!(
2235 ctx.filtered_parent_child_has_qualifier(
2236 &["SG4", "SG8"],
2237 "SEQ",
2238 0,
2239 "Z98",
2240 "SG10",
2241 "CCI",
2242 0,
2243 "Z99",
2244 ),
2245 ConditionResult::False,
2246 );
2247 }
2248
2249 #[test]
2250 fn test_any_group_has_qualifier_without() {
2251 let external = NoOpExternalProvider;
2252 let nav = MockGroupNavigator::new()
2255 .with_group(
2256 &["SG4", "SG8"],
2257 0,
2258 vec![make_segment("SEQ", vec![vec!["Z59"]])],
2259 )
2260 .with_group(
2261 &["SG4", "SG8"],
2262 1,
2263 vec![
2264 make_segment("SEQ", vec![vec!["Z01"]]),
2265 make_segment("CCI", vec![vec!["11"]]),
2266 ],
2267 );
2268 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2269
2270 assert_eq!(
2272 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2273 ConditionResult::True,
2274 );
2275 assert_eq!(
2277 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
2278 ConditionResult::False,
2279 );
2280 assert_eq!(
2282 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
2283 ConditionResult::False,
2284 );
2285 }
2286
2287 #[test]
2288 fn test_any_group_has_qualifier_without_fallback() {
2289 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
2290 let external = NoOpExternalProvider;
2291 let ctx = EvaluationContext::new("55001", &external, &segments);
2292
2293 assert_eq!(
2295 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2296 ConditionResult::True,
2297 );
2298 }
2299
2300 #[test]
2301 fn test_collect_group_values() {
2302 let external = NoOpExternalProvider;
2303 let nav = MockGroupNavigator::new()
2304 .with_group(
2305 &["SG4", "SG6"],
2306 0,
2307 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
2308 )
2309 .with_group(
2310 &["SG4", "SG6"],
2311 1,
2312 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
2313 );
2314 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2315
2316 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
2317 assert_eq!(values.len(), 2);
2318 assert_eq!(values[0], (0, "REF001".to_string()));
2319 assert_eq!(values[1], (1, "REF002".to_string()));
2320 }
2321
2322 #[test]
2323 fn test_groups_share_qualified_value() {
2324 let external = NoOpExternalProvider;
2325 let nav = MockGroupNavigator::new()
2329 .with_group(
2330 &["SG4", "SG6"],
2331 0,
2332 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2333 )
2334 .with_group(
2335 &["SG4", "SG8"],
2336 0,
2337 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2338 )
2339 .with_group(
2340 &["SG4", "SG8"],
2341 1,
2342 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
2343 );
2344 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2345
2346 assert_eq!(
2348 ctx.groups_share_qualified_value(
2349 "RFF",
2350 0,
2351 "Z49",
2352 0,
2353 1,
2354 &["SG4", "SG6"],
2355 "SEQ",
2356 1,
2357 0,
2358 &["SG4", "SG8"],
2359 ),
2360 ConditionResult::True,
2361 );
2362 }
2363
2364 #[test]
2365 fn test_groups_share_qualified_value_no_match() {
2366 let external = NoOpExternalProvider;
2367 let nav = MockGroupNavigator::new()
2368 .with_group(
2369 &["SG4", "SG6"],
2370 0,
2371 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2372 )
2373 .with_group(
2374 &["SG4", "SG8"],
2375 0,
2376 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
2377 );
2378 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2379
2380 assert_eq!(
2382 ctx.groups_share_qualified_value(
2383 "RFF",
2384 0,
2385 "Z49",
2386 0,
2387 1,
2388 &["SG4", "SG6"],
2389 "SEQ",
2390 1,
2391 0,
2392 &["SG4", "SG8"],
2393 ),
2394 ConditionResult::False,
2395 );
2396 }
2397
2398 #[test]
2399 fn test_groups_share_qualified_value_no_source() {
2400 let external = NoOpExternalProvider;
2401 let nav = MockGroupNavigator::new()
2403 .with_group(
2404 &["SG4", "SG6"],
2405 0,
2406 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
2407 )
2408 .with_group(
2409 &["SG4", "SG8"],
2410 0,
2411 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2412 );
2413 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2414
2415 assert_eq!(
2417 ctx.groups_share_qualified_value(
2418 "RFF",
2419 0,
2420 "Z49",
2421 0,
2422 1,
2423 &["SG4", "SG6"],
2424 "SEQ",
2425 1,
2426 0,
2427 &["SG4", "SG8"],
2428 ),
2429 ConditionResult::Unknown,
2430 );
2431 }
2432
2433 #[test]
2434 fn test_groups_share_qualified_value_fallback() {
2435 let segments = vec![
2437 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
2438 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
2439 ];
2440 let external = NoOpExternalProvider;
2441 let ctx = EvaluationContext::new("55001", &external, &segments);
2442
2443 assert_eq!(
2444 ctx.groups_share_qualified_value(
2445 "RFF",
2446 0,
2447 "Z49",
2448 0,
2449 1,
2450 &["SG4", "SG6"],
2451 "SEQ",
2452 1,
2453 0,
2454 &["SG4", "SG8"],
2455 ),
2456 ConditionResult::True,
2457 );
2458 }
2459
2460 #[test]
2461 fn test_child_group_pass_throughs_no_navigator() {
2462 let external = NoOpExternalProvider;
2463 let ctx = EvaluationContext::new("55001", &external, &[]);
2464
2465 assert_eq!(
2466 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
2467 0
2468 );
2469 assert!(ctx
2470 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
2471 .is_empty());
2472 assert_eq!(
2473 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
2474 None
2475 );
2476 }
2477
2478 #[test]
2481 fn test_has_segment_matching_found() {
2482 let segments = vec![
2483 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
2484 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
2485 ];
2486 let external = NoOpExternalProvider;
2487 let ctx = EvaluationContext::new("55001", &external, &segments);
2488
2489 assert_eq!(
2490 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
2491 ConditionResult::True,
2492 );
2493 }
2494
2495 #[test]
2496 fn test_has_segment_matching_not_found() {
2497 let segments = vec![make_segment(
2498 "STS",
2499 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2500 )];
2501 let external = NoOpExternalProvider;
2502 let ctx = EvaluationContext::new("55001", &external, &segments);
2503
2504 assert_eq!(
2505 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2506 ConditionResult::False,
2507 );
2508 }
2509
2510 #[test]
2511 fn test_has_segment_matching_no_segments() {
2512 let external = NoOpExternalProvider;
2513 let ctx = EvaluationContext::new("55001", &external, &[]);
2514
2515 assert_eq!(
2516 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2517 ConditionResult::Unknown,
2518 );
2519 }
2520
2521 #[test]
2522 fn test_has_segment_matching_in_group() {
2523 let nav = MockGroupNavigator::new()
2524 .with_group(
2525 &["SG4"],
2526 0,
2527 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2528 )
2529 .with_group(
2530 &["SG4"],
2531 1,
2532 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2533 );
2534 let segments = vec![
2535 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2536 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2537 ];
2538 let external = NoOpExternalProvider;
2539 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2540
2541 assert_eq!(
2542 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2543 ConditionResult::True,
2544 );
2545 }
2546
2547 #[test]
2550 fn test_dtm_ge() {
2551 let segments = vec![make_segment(
2552 "DTM",
2553 vec![vec!["137", "202601010000", "303"]],
2554 )];
2555 let external = NoOpExternalProvider;
2556 let ctx = EvaluationContext::new("55001", &external, &segments);
2557
2558 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2559 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2560 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2561 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2562 }
2563
2564 #[test]
2565 fn test_dtm_lt() {
2566 let segments = vec![make_segment(
2567 "DTM",
2568 vec![vec!["137", "202601010000", "303"]],
2569 )];
2570 let external = NoOpExternalProvider;
2571 let ctx = EvaluationContext::new("55001", &external, &segments);
2572
2573 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2574 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2575 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2576 }
2577
2578 #[test]
2579 fn test_dtm_le() {
2580 let segments = vec![make_segment(
2581 "DTM",
2582 vec![vec!["137", "202601010000", "303"]],
2583 )];
2584 let external = NoOpExternalProvider;
2585 let ctx = EvaluationContext::new("55001", &external, &segments);
2586
2587 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2588 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2589 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2590 }
2591
2592 #[test]
2595 fn test_count_qualified_in_group() {
2596 let nav = MockGroupNavigator::new()
2597 .with_group(
2598 &["SG4", "SG8"],
2599 0,
2600 vec![
2601 make_segment("CCI", vec![vec!["Z23"]]),
2602 make_segment("CCI", vec![vec!["Z30"]]),
2603 ],
2604 )
2605 .with_group(
2606 &["SG4", "SG8"],
2607 1,
2608 vec![make_segment("CCI", vec![vec!["Z23"]])],
2609 );
2610 let segments = vec![
2611 make_segment("CCI", vec![vec!["Z23"]]),
2612 make_segment("CCI", vec![vec!["Z30"]]),
2613 make_segment("CCI", vec![vec!["Z23"]]),
2614 ];
2615 let external = NoOpExternalProvider;
2616 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2617
2618 assert_eq!(
2619 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2620 2
2621 );
2622 assert_eq!(
2623 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2624 1
2625 );
2626 assert_eq!(
2627 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2628 0
2629 );
2630 }
2631
2632 #[test]
2633 fn test_count_in_group() {
2634 let nav = MockGroupNavigator::new()
2635 .with_group(
2636 &["SG4", "SG8"],
2637 0,
2638 vec![
2639 make_segment("SEQ", vec![vec!["Z98"]]),
2640 make_segment("CCI", vec![vec!["Z23"]]),
2641 ],
2642 )
2643 .with_group(
2644 &["SG4", "SG8"],
2645 1,
2646 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2647 );
2648 let segments = vec![
2649 make_segment("SEQ", vec![vec!["Z98"]]),
2650 make_segment("CCI", vec![vec!["Z23"]]),
2651 make_segment("SEQ", vec![vec!["Z01"]]),
2652 ];
2653 let external = NoOpExternalProvider;
2654 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2655
2656 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2657 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2658 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2659 }
2660
2661 #[test]
2662 fn test_count_fallback_no_navigator() {
2663 let segments = vec![
2664 make_segment("CCI", vec![vec!["Z23"]]),
2665 make_segment("CCI", vec![vec!["Z30"]]),
2666 make_segment("CCI", vec![vec!["Z23"]]),
2667 ];
2668 let external = NoOpExternalProvider;
2669 let ctx = EvaluationContext::new("55001", &external, &segments);
2670
2671 assert_eq!(
2673 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2674 2
2675 );
2676 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2677 }
2678
2679 #[test]
2682 fn format_check_prefers_resolved_value() {
2683 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2685 let external = NoOpExternalProvider;
2686 let ctx = EvaluationContext::new("55001", &external, &segments)
2687 .with_resolved(Some("CORRECT"), None);
2688
2689 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2690 assert_eq!(result, ConditionResult::True);
2691 }
2692
2693 #[test]
2694 fn format_check_falls_back_to_segment_search() {
2695 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2697 let external = NoOpExternalProvider;
2698 let ctx = EvaluationContext::new("55001", &external, &segments);
2699
2700 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2701 assert_eq!(result, ConditionResult::True);
2702 }
2703
2704 #[test]
2705 fn format_check_returns_unknown_when_segment_absent() {
2706 let segments: Vec<OwnedSegment> = vec![];
2708 let external = NoOpExternalProvider;
2709 let ctx = EvaluationContext::new("55001", &external, &segments);
2710
2711 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2712 assert_eq!(result, ConditionResult::Unknown);
2713 }
2714
2715 #[test]
2716 fn format_check_returns_unknown_when_multiple_segments() {
2717 let segments = vec![
2721 make_segment("DTM", vec![vec!["92", "OK"]]),
2722 make_segment("DTM", vec![vec!["163", "BAD"]]),
2723 ];
2724 let external = NoOpExternalProvider;
2725 let ctx = EvaluationContext::new("55001", &external, &segments);
2726
2727 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2728 assert_eq!(result, ConditionResult::Unknown);
2729 }
2730
2731 #[test]
2732 fn format_check_qualified_prefers_resolved_value() {
2733 let segments = vec![
2735 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2736 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2737 ];
2738 let external = NoOpExternalProvider;
2739 let ctx = EvaluationContext::new("55001", &external, &segments)
2740 .with_resolved(Some("CORRECT"), None);
2741
2742 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2743 ConditionResult::from(v == "CORRECT")
2744 });
2745 assert_eq!(result, ConditionResult::True);
2746 }
2747
2748 #[test]
2749 fn format_check_resolved_judges_only_the_fields_own_value() {
2750 let segments = vec![make_segment("LOC", vec![vec!["Z16"], vec!["WRONG"]])];
2753 let external = NoOpExternalProvider;
2754 let ctx = EvaluationContext::new("17101", &external, &segments);
2755 let check = |v: &str| ConditionResult::from(v == "CORRECT");
2756
2757 assert_eq!(ctx.format_check_resolved(check), ConditionResult::Unknown);
2758 let field = ctx.with_resolved(Some("CORRECT"), None);
2759 assert_eq!(field.format_check_resolved(check), ConditionResult::True);
2760 }
2761
2762 #[test]
2763 fn format_check_qualified_falls_back_to_qualified_search() {
2764 let segments = vec![
2766 make_segment("DTM", vec![vec!["92", "2200"]]),
2767 make_segment("DTM", vec![vec!["163", "0800"]]),
2768 ];
2769 let external = NoOpExternalProvider;
2770 let ctx = EvaluationContext::new("55001", &external, &segments);
2771
2772 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2774 ConditionResult::from(v == "0800")
2775 });
2776 assert_eq!(result, ConditionResult::True);
2777
2778 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2780 ConditionResult::from(v == "2200")
2781 });
2782 assert_eq!(result2, ConditionResult::False);
2783 }
2784
2785 #[test]
2788 fn self_segment_value_prefers_resolved_segment() {
2789 let segments = vec![
2793 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2794 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2795 ];
2796 let external = NoOpExternalProvider;
2797 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2798 let ctx = EvaluationContext::new("55001", &external, &segments)
2799 .with_resolved(None, Some(&resolved_elements));
2800
2801 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2802 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2803 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2804 }
2805
2806 #[test]
2807 fn self_segment_value_falls_back_when_no_resolved_segment() {
2808 let segments = vec![
2810 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2811 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2812 ];
2813 let external = NoOpExternalProvider;
2814 let ctx = EvaluationContext::new("55001", &external, &segments);
2815
2816 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2817 }
2818
2819 #[test]
2820 fn self_segment_value_missing_returns_none() {
2821 let segments: Vec<OwnedSegment> = vec![];
2822 let external = NoOpExternalProvider;
2823 let ctx = EvaluationContext::new("55001", &external, &segments);
2824
2825 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2826 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2827 }
2828
2829 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2833 let mut segments = Vec::new();
2834 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2835 segments.push(OwnedSegment {
2836 id: "SEQ".to_string(),
2837 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2838 segment_number: (2 * i + 1) as u32,
2839 });
2840 segments.push(OwnedSegment {
2841 id: "PIA".to_string(),
2842 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2843 segment_number: (2 * i + 2) as u32,
2844 });
2845 }
2846 segments
2847 }
2848
2849 #[test]
2850 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2851 let segments = make_sg8_sequence(&[
2853 ("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"), ]);
2858 let external = NoOpExternalProvider;
2859 let seq = &segments[0].elements;
2861 let ctx =
2862 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2863
2864 let n = ctx
2865 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2866 assert_eq!(n, 2);
2867 }
2868
2869 #[test]
2870 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2871 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2872 let external = NoOpExternalProvider;
2873 let ctx = EvaluationContext::new("55001", &external, &segments);
2874 let n = ctx
2875 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2876 assert_eq!(n, 0);
2877 }
2878
2879 #[test]
2880 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2881 let segments = make_sg8_sequence(&[
2882 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2885 let external = NoOpExternalProvider;
2886 let seq = &segments[0].elements;
2887 let ctx =
2888 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2889
2890 assert_eq!(
2891 ctx.count_sg8_obis_matches(
2892 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2893 ),
2894 1
2895 );
2896 assert_eq!(
2897 ctx.count_sg8_obis_matches(
2898 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2899 ),
2900 1
2901 );
2902 }
2903
2904 fn scan<'s>(
2909 segments: &'s [OwnedSegment],
2910 tag: &str,
2911 qualifier: Option<(usize, &str)>,
2912 ) -> Vec<&'s OwnedSegment> {
2913 segments
2914 .iter()
2915 .filter(|s| {
2916 s.id == tag
2917 && qualifier.is_none_or(|(element_index, qualifier)| {
2918 s.elements
2919 .get(element_index)
2920 .and_then(|e| e.first())
2921 .is_some_and(|v| v == qualifier)
2922 })
2923 })
2924 .collect()
2925 }
2926
2927 fn same(found: &[&OwnedSegment], expected: &[&OwnedSegment]) -> bool {
2929 found.len() == expected.len()
2930 && found
2931 .iter()
2932 .zip(expected)
2933 .all(|(f, e)| std::ptr::eq(*f, *e))
2934 }
2935
2936 fn same_one(found: Option<&OwnedSegment>, expected: Option<&&OwnedSegment>) -> bool {
2937 match (found, expected) {
2938 (Some(f), Some(e)) => std::ptr::eq(f, *e),
2939 (None, None) => true,
2940 _ => false,
2941 }
2942 }
2943
2944 fn index_sample() -> Vec<OwnedSegment> {
2948 vec![
2949 make_segment("UNH", vec![vec!["1"], vec!["INVOIC", "D"]]),
2950 make_segment("DTM", vec![vec!["137", "202501010000", "303"]]),
2951 make_segment("RFF", vec![vec!["Z13", "31002"]]),
2952 make_segment("DTM", vec![vec!["9", "202501020000", "303"]]),
2953 make_segment("LIN", vec![vec!["1"], vec![], vec!["ABC", "Z01"]]),
2954 make_segment("QTY", vec![vec!["47", "1", "KWH"]]),
2955 make_segment("ALC", vec![vec!["A"], vec!["", "Z01"]]),
2956 make_segment("ALC", vec![vec!["C"], vec!["", "Z03"]]),
2957 make_segment("LIN", vec![vec!["2"], vec![], vec!["DEF", "Z02"]]),
2958 make_segment("ALC", vec![vec!["A"], vec!["", "Z04"]]),
2959 make_segment("FTX", vec![vec![""], vec![""], vec!["RCH"]]),
2960 make_segment("FTX", vec![vec![], vec!["ACB"]]),
2961 make_segment("UNS", vec![]),
2962 make_segment("MOA", vec![vec!["9", "100"]]),
2963 make_segment("DTM", vec![vec!["137", "202501030000", "303"]]),
2964 make_segment("RFF", vec![vec!["137"]]),
2965 ]
2966 }
2967
2968 fn index_queries() -> Vec<(&'static str, Option<(usize, &'static str)>)> {
2971 let mut queries = vec![];
2972 for tag in [
2973 "UNH", "DTM", "RFF", "LIN", "QTY", "ALC", "FTX", "UNS", "MOA", "NAD",
2974 ] {
2975 queries.push((tag, None));
2976 for element_index in 0..4 {
2977 for qualifier in [
2978 "", "1", "2", "9", "47", "137", "Z13", "A", "C", "ACB", "RCH", "INVOIC", "ABC",
2979 "Z01", "XYZ",
2980 ] {
2981 queries.push((tag, Some((element_index, qualifier))));
2982 }
2983 }
2984 }
2985 queries
2986 }
2987
2988 fn assert_lookups_scan(ctx: &EvaluationContext, expected_over: &[OwnedSegment]) {
2990 for (tag, qualifier) in index_queries() {
2991 let expected = scan(expected_over, tag, qualifier);
2992 let what = format!("{tag} {qualifier:?}");
2993 let Some((element_index, q)) = qualifier else {
2994 assert!(same(&ctx.find_segments(tag), &expected), "{what}");
2995 assert!(same_one(ctx.find_segment(tag), expected.first()), "{what}");
2996 assert_eq!(ctx.has_segment(tag), !expected.is_empty(), "{what}");
2997 continue;
2998 };
2999 let found = ctx.find_segments_with_qualifier(tag, element_index, q);
3000 assert!(same(&found, &expected), "{what}");
3001 assert!(
3002 same_one(
3003 ctx.find_segment_with_qualifier(tag, element_index, q),
3004 expected.first()
3005 ),
3006 "{what}"
3007 );
3008 assert_eq!(
3009 ctx.has_qualifier(tag, element_index, q),
3010 ConditionResult::from(!expected.is_empty()),
3011 "{what}"
3012 );
3013 assert_eq!(
3014 ctx.lacks_qualifier(tag, element_index, q),
3015 ConditionResult::from(expected.is_empty()),
3016 "{what}"
3017 );
3018 for (value_elem, value_comp, values) in [
3019 (1, 1, &["Z01", "Z04"][..]),
3020 (0, 1, &["202501030000"][..]),
3021 (2, 0, &["RCH"][..]),
3022 (5, 0, &["X"][..]),
3023 ] {
3024 let expected_value = if expected.is_empty() {
3025 ConditionResult::Unknown
3026 } else {
3027 ConditionResult::from(expected.iter().any(|s| {
3028 s.elements
3029 .get(value_elem)
3030 .and_then(|e| e.get(value_comp))
3031 .is_some_and(|v| values.contains(&v.as_str()))
3032 }))
3033 };
3034 assert_eq!(
3035 ctx.has_qualified_value(tag, element_index, q, value_elem, value_comp, values),
3036 expected_value,
3037 "{what} {value_elem}/{value_comp}"
3038 );
3039 }
3040 }
3041 }
3042
3043 #[test]
3044 fn indexed_lookups_find_what_scanning_finds() {
3045 let segments = index_sample();
3046 let external = NoOpExternalProvider;
3047 let ctx = EvaluationContext::new("31002", &external, &segments);
3048 assert_lookups_scan(&ctx, &segments);
3049 let path = [("SG26", 0)];
3052 let scoped = ctx
3053 .with_scope(GroupScope { path: &path })
3054 .with_resolved(Some("x"), None);
3055 assert!(Arc::ptr_eq(&ctx.segment_index, &scoped.segment_index));
3056 assert_lookups_scan(&scoped, &segments);
3057 }
3058
3059 #[test]
3060 fn indexed_lookups_on_an_empty_message() {
3061 let external = NoOpExternalProvider;
3062 let ctx = EvaluationContext::new("31002", &external, &[]);
3063 assert_lookups_scan(&ctx, &[]);
3064 }
3065
3066 #[test]
3070 fn a_view_indexes_its_own_segments() {
3071 let segments = index_sample();
3072 let external = NoOpExternalProvider;
3073 let nav = NoOpGroupNavigator;
3074 let ctx = EvaluationContext::with_navigator("31002", &external, &segments, &nav);
3075 assert_lookups_scan(&ctx, &segments);
3076
3077 let mut reversed = segments.clone();
3078 reversed.reverse();
3079 let view = ctx.with_view(&reversed, &nav);
3080 assert!(!Arc::ptr_eq(&ctx.segment_index, &view.segment_index));
3081 assert_lookups_scan(&view, &reversed);
3082 assert_lookups_scan(&ctx, &segments);
3084
3085 let mut buffer: Vec<OwnedSegment> = Vec::with_capacity(segments.len());
3086 for drop_every in [2, 3] {
3087 buffer.clear();
3088 buffer.extend(
3089 segments
3090 .iter()
3091 .enumerate()
3092 .filter(|(i, _)| i % drop_every != 0)
3093 .map(|(_, s)| s.clone()),
3094 );
3095 let view = ctx.with_view(&buffer, &nav);
3096 assert_lookups_scan(&view, &buffer);
3097 }
3098 }
3099
3100 #[test]
3103 fn a_context_given_other_segments_does_not_read_the_old_index() {
3104 let segments = index_sample();
3105 let other: Vec<OwnedSegment> = segments[4..10].to_vec();
3106 let external = NoOpExternalProvider;
3107 let mut ctx = EvaluationContext::new("31002", &external, &segments);
3108 assert_lookups_scan(&ctx, &segments);
3109 ctx.segments = &other;
3110 assert_lookups_scan(&ctx, &other);
3111 ctx.segments = &segments[..5];
3113 assert_lookups_scan(&ctx, &segments[..5]);
3114 }
3115}