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_resolved(
162 &self,
163 value: Option<&'a str>,
164 segment: Option<&'a [Vec<String>]>,
165 ) -> Self {
166 Self {
167 resolved_value: value,
168 resolved_segment: segment,
169 ..*self
170 }
171 }
172
173 pub fn navigator(&self) -> Option<&'a dyn GroupNavigator> {
175 self.navigator
176 }
177
178 pub fn find_segment(&self, segment_id: &str) -> Option<&'a OwnedSegment> {
180 self.segments.iter().find(|s| s.id == segment_id)
181 }
182
183 pub fn find_segments(&self, segment_id: &str) -> Vec<&'a OwnedSegment> {
185 self.segments
186 .iter()
187 .filter(|s| s.id == segment_id)
188 .collect()
189 }
190
191 pub fn find_segments_with_qualifier(
193 &self,
194 segment_id: &str,
195 element_index: usize,
196 qualifier: &str,
197 ) -> Vec<&'a OwnedSegment> {
198 self.segments
199 .iter()
200 .filter(|s| {
201 s.id == segment_id
202 && s.elements
203 .get(element_index)
204 .and_then(|e| e.first())
205 .is_some_and(|v| v == qualifier)
206 })
207 .collect()
208 }
209
210 pub fn scoped_find_segments(&self, tag: &str) -> Vec<OwnedSegment> {
220 match (self.scope, self.navigator) {
221 (Some(scope), Some(nav)) => nav.find_segments_in_subtree(tag, scope.path),
222 _ => self.find_segments(tag).into_iter().cloned().collect(),
223 }
224 }
225
226 pub fn scoped_find_segments_in(&self, group_id: &str, tag: &str) -> Vec<OwnedSegment> {
234 if let (Some(scope), Some(nav)) = (self.scope, self.navigator) {
235 if let Some(level) = scope.path.iter().rposition(|(id, _)| *id == group_id) {
236 return nav.find_segments_in_subtree(tag, &scope.path[..=level]);
237 }
238 }
239 self.find_segments(tag).into_iter().cloned().collect()
240 }
241
242 pub fn sibling_scopes(&self, group_id: &str) -> Option<Vec<Vec<(&'a str, usize)>>> {
247 let (scope, nav) = (self.scope?, self.navigator?);
248 let level = scope.path.iter().rposition(|(id, _)| *id == group_id)?;
249 let (parent, own) = (&scope.path[..level], scope.path[level]);
250 let count = nav.child_instance_count(parent, own.0);
251 Some(
252 (0..count)
253 .filter(|&i| i != own.1)
254 .map(|i| {
255 let mut path = parent.to_vec();
256 path.push((own.0, i));
257 path
258 })
259 .collect(),
260 )
261 }
262
263 pub fn find_segments_at(&self, path: &[(&str, usize)], tag: &str) -> Vec<OwnedSegment> {
266 self.navigator
267 .map(|nav| nav.find_segments_in_subtree(tag, path))
268 .unwrap_or_default()
269 }
270
271 pub fn scoped_find_segments_in_with_qualifier(
274 &self,
275 group_id: &str,
276 tag: &str,
277 element_index: usize,
278 qualifier: &str,
279 ) -> Vec<OwnedSegment> {
280 self.scoped_find_segments_in(group_id, tag)
281 .into_iter()
282 .filter(|s| {
283 s.elements
284 .get(element_index)
285 .and_then(|e| e.first())
286 .is_some_and(|v| v == qualifier)
287 })
288 .collect()
289 }
290
291 pub fn scoped_find_segments_with_qualifier(
294 &self,
295 tag: &str,
296 element_index: usize,
297 qualifier: &str,
298 ) -> Vec<OwnedSegment> {
299 self.scoped_find_segments(tag)
300 .into_iter()
301 .filter(|s| {
302 s.elements
303 .get(element_index)
304 .and_then(|e| e.first())
305 .is_some_and(|v| v == qualifier)
306 })
307 .collect()
308 }
309
310 pub fn format_check(
320 &self,
321 tag: &str,
322 elem: usize,
323 comp: usize,
324 validate: impl FnOnce(&str) -> ConditionResult,
325 ) -> ConditionResult {
326 if let Some(val) = self.resolved_value {
328 return validate(val);
329 }
330 let segs = self.find_segments(tag);
335 if segs.len() != 1 {
336 return ConditionResult::Unknown;
337 }
338 match segs[0]
339 .elements
340 .get(elem)
341 .and_then(|e| e.get(comp))
342 .map(|s| s.as_str())
343 {
344 Some(val) => validate(val),
345 None => ConditionResult::Unknown,
346 }
347 }
348
349 pub fn format_check_qualified(
358 &self,
359 tag: &str,
360 qual_elem: usize,
361 qualifier: &str,
362 val_elem: usize,
363 val_comp: usize,
364 validate: impl FnOnce(&str) -> ConditionResult,
365 ) -> ConditionResult {
366 if let Some(val) = self.resolved_value {
367 return validate(val);
368 }
369 let segs = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
370 if segs.len() != 1 {
371 return ConditionResult::Unknown;
372 }
373 match segs[0]
374 .elements
375 .get(val_elem)
376 .and_then(|e| e.get(val_comp))
377 .map(|s| s.as_str())
378 {
379 Some(val) => validate(val),
380 None => ConditionResult::Unknown,
381 }
382 }
383
384 pub fn self_segment_value(&self, tag: &str, elem: usize, comp: usize) -> Option<&str> {
396 if let Some(elements) = self.resolved_segment {
397 return elements
398 .get(elem)
399 .and_then(|e| e.get(comp))
400 .map(|s| s.as_str());
401 }
402 self.segments
403 .iter()
404 .find(|s| s.id == tag)
405 .and_then(|s| s.elements.get(elem))
406 .and_then(|e| e.get(comp))
407 .map(|s| s.as_str())
408 }
409
410 pub fn self_segment_value_equals(
414 &self,
415 tag: &str,
416 elem: usize,
417 comp: usize,
418 expected: &str,
419 ) -> bool {
420 self.self_segment_value(tag, elem, comp)
421 .is_some_and(|v| v == expected)
422 }
423
424 pub fn has_segment(&self, segment_id: &str) -> bool {
426 self.segments.iter().any(|s| s.id == segment_id)
427 }
428
429 pub fn count_sg8_obis_matches<F>(&self, obis_pred: F) -> usize
451 where
452 F: Fn(&str) -> bool,
453 {
454 let resolved = match self.resolved_segment {
455 Some(s) => s,
456 None => return 0,
457 };
458 let cur_qual = match resolved.first().and_then(|e| e.first()) {
459 Some(v) => v.as_str(),
460 None => return 0,
461 };
462 if !matches!(cur_qual, "Z02" | "ZA1" | "ZA2") {
463 return 0;
464 }
465 let cur_zid = match resolved.get(1).and_then(|e| e.first()) {
466 Some(v) => v.as_str(),
467 None => return 0,
468 };
469
470 let mut count = 0usize;
471 let mut in_matching_seq = false;
472 for seg in self.segments {
473 if seg.id == "SEQ" {
474 let qual = seg
475 .elements
476 .first()
477 .and_then(|e| e.first())
478 .map(String::as_str);
479 let zid = seg
480 .elements
481 .get(1)
482 .and_then(|e| e.first())
483 .map(String::as_str);
484 in_matching_seq = qual == Some(cur_qual) && zid == Some(cur_zid);
485 } else if seg.id == "PIA" && in_matching_seq {
486 let p_qual = seg
487 .elements
488 .first()
489 .and_then(|e| e.first())
490 .map(String::as_str);
491 if p_qual == Some("5") {
492 if let Some(obis) = seg.elements.get(1).and_then(|e| e.first()) {
493 if obis_pred(obis) {
494 count += 1;
495 }
496 }
497 }
498 }
499 }
500 count
501 }
502
503 pub fn find_segments_in_group(
506 &self,
507 segment_id: &str,
508 group_path: &[&str],
509 instance_index: usize,
510 ) -> Vec<OwnedSegment> {
511 match self.navigator {
512 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
513 None => Vec::new(),
514 }
515 }
516
517 pub fn find_segments_with_qualifier_in_group(
520 &self,
521 segment_id: &str,
522 element_index: usize,
523 qualifier: &str,
524 group_path: &[&str],
525 instance_index: usize,
526 ) -> Vec<OwnedSegment> {
527 match self.navigator {
528 Some(nav) => nav.find_segments_with_qualifier_in_group(
529 segment_id,
530 element_index,
531 qualifier,
532 group_path,
533 instance_index,
534 ),
535 None => Vec::new(),
536 }
537 }
538
539 pub fn has_segment_in_group(
542 &self,
543 segment_id: &str,
544 group_path: &[&str],
545 instance_index: usize,
546 ) -> bool {
547 !self
548 .find_segments_in_group(segment_id, group_path, instance_index)
549 .is_empty()
550 }
551
552 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
555 match self.navigator {
556 Some(nav) => nav.group_instance_count(group_path),
557 None => 0,
558 }
559 }
560
561 pub fn child_group_instance_count(
564 &self,
565 parent_path: &[&str],
566 parent_instance: usize,
567 child_group_id: &str,
568 ) -> usize {
569 match self.navigator {
570 Some(nav) => {
571 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
572 }
573 None => 0,
574 }
575 }
576
577 pub fn find_segments_in_child_group(
580 &self,
581 segment_id: &str,
582 parent_path: &[&str],
583 parent_instance: usize,
584 child_group_id: &str,
585 child_instance: usize,
586 ) -> Vec<OwnedSegment> {
587 match self.navigator {
588 Some(nav) => nav.find_segments_in_child_group(
589 segment_id,
590 parent_path,
591 parent_instance,
592 child_group_id,
593 child_instance,
594 ),
595 None => Vec::new(),
596 }
597 }
598
599 pub fn extract_value_in_group(
602 &self,
603 segment_id: &str,
604 element_index: usize,
605 component_index: usize,
606 group_path: &[&str],
607 instance_index: usize,
608 ) -> Option<String> {
609 self.navigator?.extract_value_in_group(
610 segment_id,
611 element_index,
612 component_index,
613 group_path,
614 instance_index,
615 )
616 }
617
618 pub fn has_qualifier(
624 &self,
625 tag: &str,
626 element_index: usize,
627 qualifier: &str,
628 ) -> ConditionResult {
629 ConditionResult::from(
630 !self
631 .find_segments_with_qualifier(tag, element_index, qualifier)
632 .is_empty(),
633 )
634 }
635
636 pub fn lacks_qualifier(
639 &self,
640 tag: &str,
641 element_index: usize,
642 qualifier: &str,
643 ) -> ConditionResult {
644 ConditionResult::from(
645 self.find_segments_with_qualifier(tag, element_index, qualifier)
646 .is_empty(),
647 )
648 }
649
650 pub fn has_qualified_value(
655 &self,
656 tag: &str,
657 qual_elem: usize,
658 qualifier: &str,
659 value_elem: usize,
660 value_comp: usize,
661 values: &[&str],
662 ) -> ConditionResult {
663 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
664 if segments.is_empty() {
665 return ConditionResult::Unknown;
666 }
667 for seg in &segments {
668 if let Some(v) = seg
669 .elements
670 .get(value_elem)
671 .and_then(|e| e.get(value_comp))
672 .map(|s| s.as_str())
673 {
674 if values.contains(&v) {
675 return ConditionResult::True;
676 }
677 }
678 }
679 ConditionResult::False
680 }
681
682 pub fn any_group_has_qualifier(
688 &self,
689 tag: &str,
690 element_index: usize,
691 qualifier: &str,
692 group_path: &[&str],
693 ) -> ConditionResult {
694 let instance_count = self.group_instance_count(group_path);
695 if instance_count > 0 {
696 for i in 0..instance_count {
697 if !self
698 .find_segments_with_qualifier_in_group(
699 tag,
700 element_index,
701 qualifier,
702 group_path,
703 i,
704 )
705 .is_empty()
706 {
707 return ConditionResult::True;
708 }
709 }
710 return ConditionResult::False;
711 }
712 self.has_qualifier(tag, element_index, qualifier)
714 }
715
716 pub fn any_group_has_any_qualifier(
722 &self,
723 tag: &str,
724 element_index: usize,
725 qualifiers: &[&str],
726 group_path: &[&str],
727 ) -> ConditionResult {
728 let instance_count = self.group_instance_count(group_path);
729 if instance_count > 0 {
730 for i in 0..instance_count {
731 let segs = self.find_segments_in_group(tag, group_path, i);
732 if segs.iter().any(|seg| {
733 seg.elements
734 .get(element_index)
735 .and_then(|e| e.first())
736 .map(|s| s.as_str())
737 .is_some_and(|v| qualifiers.contains(&v))
738 }) {
739 return ConditionResult::True;
740 }
741 }
742 return ConditionResult::False;
743 }
744 let found = self.find_segments(tag).iter().any(|seg| {
746 seg.elements
747 .get(element_index)
748 .and_then(|e| e.first())
749 .map(|s| s.as_str())
750 .is_some_and(|v| qualifiers.contains(&v))
751 });
752 ConditionResult::from(found)
753 }
754
755 pub fn any_group_has_qualified_value(
761 &self,
762 tag: &str,
763 qual_elem: usize,
764 qualifier: &str,
765 value_elem: usize,
766 value_comp: usize,
767 values: &[&str],
768 group_path: &[&str],
769 ) -> ConditionResult {
770 let instance_count = self.group_instance_count(group_path);
771 if instance_count > 0 {
772 for i in 0..instance_count {
773 let segs = self.find_segments_with_qualifier_in_group(
774 tag, qual_elem, qualifier, group_path, i,
775 );
776 for seg in &segs {
777 if seg
778 .elements
779 .get(value_elem)
780 .and_then(|e| e.get(value_comp))
781 .map(|s| s.as_str())
782 .is_some_and(|v| values.contains(&v))
783 {
784 return ConditionResult::True;
785 }
786 }
787 }
788 return ConditionResult::False;
789 }
790 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
792 }
793
794 #[allow(clippy::too_many_arguments)]
803 pub fn filtered_parent_child_has_qualifier(
804 &self,
805 parent_path: &[&str],
806 parent_tag: &str,
807 parent_elem: usize,
808 parent_qual: &str,
809 child_group_id: &str,
810 child_tag: &str,
811 child_elem: usize,
812 child_qual: &str,
813 ) -> ConditionResult {
814 let parent_count = self.group_instance_count(parent_path);
815 if parent_count > 0 {
816 for pi in 0..parent_count {
817 let parent_segs = self.find_segments_with_qualifier_in_group(
819 parent_tag,
820 parent_elem,
821 parent_qual,
822 parent_path,
823 pi,
824 );
825 if parent_segs.is_empty() {
826 continue;
827 }
828 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
830 for ci in 0..child_count {
831 let child_segs = self.find_segments_in_child_group(
832 child_tag,
833 parent_path,
834 pi,
835 child_group_id,
836 ci,
837 );
838 if child_segs.iter().any(|s| {
839 s.elements
840 .get(child_elem)
841 .and_then(|e| e.first())
842 .is_some_and(|v| v == child_qual)
843 }) {
844 return ConditionResult::True;
845 }
846 }
847 }
848 return ConditionResult::False;
849 }
850 let has_parent = !self
852 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
853 .is_empty();
854 let has_child = !self
855 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
856 .is_empty();
857 ConditionResult::from(has_parent && has_child)
858 }
859
860 #[allow(clippy::too_many_arguments)]
866 pub fn any_group_has_qualifier_without(
867 &self,
868 present_tag: &str,
869 present_elem: usize,
870 present_qual: &str,
871 absent_tag: &str,
872 absent_elem: usize,
873 absent_qual: &str,
874 group_path: &[&str],
875 ) -> ConditionResult {
876 let instance_count = self.group_instance_count(group_path);
877 if instance_count > 0 {
878 for i in 0..instance_count {
879 let has_present = !self
880 .find_segments_with_qualifier_in_group(
881 present_tag,
882 present_elem,
883 present_qual,
884 group_path,
885 i,
886 )
887 .is_empty();
888 let has_absent = !self
889 .find_segments_with_qualifier_in_group(
890 absent_tag,
891 absent_elem,
892 absent_qual,
893 group_path,
894 i,
895 )
896 .is_empty();
897 if has_present && !has_absent {
898 return ConditionResult::True;
899 }
900 }
901 return ConditionResult::False;
902 }
903 let has_present = !self
905 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
906 .is_empty();
907 let has_absent = !self
908 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
909 .is_empty();
910 ConditionResult::from(has_present && !has_absent)
911 }
912
913 fn enclosing_path(&self, group_path: &[&str]) -> Option<&'a [(&'a str, usize)]> {
916 let group = group_path.last()?;
917 let scope = self.scope?;
918 self.navigator?;
919 let level = scope.path.iter().rposition(|(id, _)| id == group)?;
920 Some(&scope.path[..=level])
921 }
922
923 pub fn enclosing_scope(&self, group_id: &str) -> Option<&'a [(&'a str, usize)]> {
927 self.enclosing_path(&[group_id])
928 }
929
930 #[allow(clippy::too_many_arguments)]
934 pub fn this_group_has_qualifier_without(
935 &self,
936 present_tag: &str,
937 present_elem: usize,
938 present_qual: &str,
939 absent_tag: &str,
940 absent_elem: usize,
941 absent_qual: &str,
942 group_path: &[&str],
943 ) -> ConditionResult {
944 let Some(path) = self.enclosing_path(group_path) else {
945 return self.any_group_has_qualifier_without(
946 present_tag,
947 present_elem,
948 present_qual,
949 absent_tag,
950 absent_elem,
951 absent_qual,
952 group_path,
953 );
954 };
955 let has = |tag: &str, elem: usize, qual: &str| {
956 self.find_segments_at(path, tag)
957 .iter()
958 .any(|s| first_component(s, elem) == Some(qual))
959 };
960 ConditionResult::from(
961 has(present_tag, present_elem, present_qual)
962 && !has(absent_tag, absent_elem, absent_qual),
963 )
964 }
965
966 #[allow(clippy::too_many_arguments)]
970 pub fn this_group_parent_child_has_qualifier(
971 &self,
972 parent_path: &[&str],
973 parent_tag: &str,
974 parent_elem: usize,
975 parent_qual: &str,
976 child_group_id: &str,
977 child_tag: &str,
978 child_elem: usize,
979 child_qual: &str,
980 ) -> ConditionResult {
981 let (Some(path), Some(nav)) = (self.enclosing_path(parent_path), self.navigator) else {
982 return self.filtered_parent_child_has_qualifier(
983 parent_path,
984 parent_tag,
985 parent_elem,
986 parent_qual,
987 child_group_id,
988 child_tag,
989 child_elem,
990 child_qual,
991 );
992 };
993 let parent_matches = nav
994 .find_segments_in_instance(parent_tag, path)
995 .iter()
996 .any(|s| first_component(s, parent_elem) == Some(parent_qual));
997 if !parent_matches {
998 return ConditionResult::False;
999 }
1000 let children = nav.child_instance_count(path, child_group_id);
1001 let found = (0..children).any(|j| {
1002 let mut child = path.to_vec();
1003 child.push((child_group_id, j));
1004 nav.find_segments_in_subtree(child_tag, &child)
1005 .iter()
1006 .any(|s| first_component(s, child_elem) == Some(child_qual))
1007 });
1008 ConditionResult::from(found)
1009 }
1010
1011 pub fn this_group_has_qualifier(
1015 &self,
1016 tag: &str,
1017 element_index: usize,
1018 qualifier: &str,
1019 group_path: &[&str],
1020 ) -> ConditionResult {
1021 match self.enclosing_path(group_path) {
1022 Some(path) => ConditionResult::from(
1023 self.find_segments_at(path, tag)
1024 .iter()
1025 .any(|s| first_component(s, element_index) == Some(qualifier)),
1026 ),
1027 None => self.any_group_has_qualifier(tag, element_index, qualifier, group_path),
1028 }
1029 }
1030
1031 pub fn this_group_has_any_qualifier(
1034 &self,
1035 tag: &str,
1036 element_index: usize,
1037 qualifiers: &[&str],
1038 group_path: &[&str],
1039 ) -> ConditionResult {
1040 match self.enclosing_path(group_path) {
1041 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1042 first_component(s, element_index).is_some_and(|v| qualifiers.contains(&v))
1043 })),
1044 None => self.any_group_has_any_qualifier(tag, element_index, qualifiers, group_path),
1045 }
1046 }
1047
1048 #[allow(clippy::too_many_arguments)]
1051 pub fn this_group_has_qualified_value(
1052 &self,
1053 tag: &str,
1054 qual_elem: usize,
1055 qualifier: &str,
1056 value_elem: usize,
1057 value_comp: usize,
1058 values: &[&str],
1059 group_path: &[&str],
1060 ) -> ConditionResult {
1061 match self.enclosing_path(group_path) {
1062 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
1063 first_component(s, qual_elem) == Some(qualifier)
1064 && s.elements
1065 .get(value_elem)
1066 .and_then(|e| e.get(value_comp))
1067 .is_some_and(|v| values.contains(&v.as_str()))
1068 })),
1069 None => self.any_group_has_qualified_value(
1070 tag, qual_elem, qualifier, value_elem, value_comp, values, group_path,
1071 ),
1072 }
1073 }
1074
1075 pub fn this_group_values(
1079 &self,
1080 tag: &str,
1081 elem: usize,
1082 comp: usize,
1083 group_path: &[&str],
1084 ) -> Vec<(usize, String)> {
1085 match self.enclosing_path(group_path) {
1086 Some(path) => self
1087 .find_segments_at(path, tag)
1088 .iter()
1089 .filter_map(|s| s.elements.get(elem).and_then(|e| e.get(comp)).cloned())
1090 .filter(|v| !v.is_empty())
1091 .map(|v| (0, v))
1092 .collect(),
1093 None => self.collect_group_values(tag, elem, comp, group_path),
1094 }
1095 }
1096
1097 pub fn collect_group_values(
1101 &self,
1102 tag: &str,
1103 elem: usize,
1104 comp: usize,
1105 group_path: &[&str],
1106 ) -> Vec<(usize, String)> {
1107 let instance_count = self.group_instance_count(group_path);
1108 let mut results = Vec::new();
1109 for i in 0..instance_count {
1110 if let Some(val) = self
1111 .navigator
1112 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
1113 {
1114 if !val.is_empty() {
1115 results.push((i, val));
1116 }
1117 }
1118 }
1119 results
1120 }
1121
1122 #[allow(clippy::too_many_arguments)]
1131 pub fn groups_share_qualified_value(
1132 &self,
1133 source_tag: &str,
1134 source_qual_elem: usize,
1135 source_qual: &str,
1136 source_value_elem: usize,
1137 source_value_comp: usize,
1138 source_path: &[&str],
1139 target_tag: &str,
1140 target_elem: usize,
1141 target_comp: usize,
1142 target_path: &[&str],
1143 ) -> ConditionResult {
1144 let source_count = self.group_instance_count(source_path);
1145 let target_count = self.group_instance_count(target_path);
1146 if source_count > 0 && target_count > 0 {
1147 let mut source_values = Vec::new();
1149 for si in 0..source_count {
1150 let segs = self.find_segments_with_qualifier_in_group(
1151 source_tag,
1152 source_qual_elem,
1153 source_qual,
1154 source_path,
1155 si,
1156 );
1157 for seg in &segs {
1158 if let Some(val) = seg
1159 .elements
1160 .get(source_value_elem)
1161 .and_then(|e| e.get(source_value_comp))
1162 {
1163 if !val.is_empty() {
1164 source_values.push(val.clone());
1165 }
1166 }
1167 }
1168 }
1169 if source_values.is_empty() {
1170 return ConditionResult::Unknown;
1171 }
1172 let target_values =
1174 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
1175 for (_, tv) in &target_values {
1176 if source_values.iter().any(|sv| sv == tv) {
1177 return ConditionResult::True;
1178 }
1179 }
1180 return ConditionResult::False;
1181 }
1182 let source_segs =
1184 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
1185 let source_vals: Vec<&str> = source_segs
1186 .iter()
1187 .filter_map(|s| {
1188 s.elements
1189 .get(source_value_elem)
1190 .and_then(|e| e.get(source_value_comp))
1191 .map(|v| v.as_str())
1192 })
1193 .filter(|v| !v.is_empty())
1194 .collect();
1195 if source_vals.is_empty() {
1196 return ConditionResult::Unknown;
1197 }
1198 let target_segs = self.find_segments(target_tag);
1199 let has_match = target_segs.iter().any(|s| {
1200 s.elements
1201 .get(target_elem)
1202 .and_then(|e| e.get(target_comp))
1203 .map(|v| v.as_str())
1204 .is_some_and(|v| source_vals.contains(&v))
1205 });
1206 ConditionResult::from(has_match)
1207 }
1208
1209 #[allow(clippy::too_many_arguments)]
1218 pub fn any_group_has_co_occurrence(
1219 &self,
1220 tag_a: &str,
1221 elem_a: usize,
1222 quals_a: &[&str],
1223 tag_b: &str,
1224 elem_b: usize,
1225 comp_b: usize,
1226 vals_b: &[&str],
1227 group_path: &[&str],
1228 ) -> ConditionResult {
1229 let instance_count = self.group_instance_count(group_path);
1230 if instance_count > 0 {
1231 for i in 0..instance_count {
1232 let a_present = self
1233 .find_segments_in_group(tag_a, group_path, i)
1234 .iter()
1235 .any(|seg| {
1236 seg.elements
1237 .get(elem_a)
1238 .and_then(|e| e.first())
1239 .map(|s| s.as_str())
1240 .is_some_and(|v| quals_a.contains(&v))
1241 });
1242 let b_present = self
1243 .find_segments_in_group(tag_b, group_path, i)
1244 .iter()
1245 .any(|seg| {
1246 seg.elements
1247 .get(elem_b)
1248 .and_then(|e| e.get(comp_b))
1249 .map(|s| s.as_str())
1250 .is_some_and(|v| vals_b.contains(&v))
1251 });
1252 if a_present && b_present {
1253 return ConditionResult::True;
1254 }
1255 }
1256 return ConditionResult::False;
1257 }
1258 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1260 seg.elements
1261 .get(elem_a)
1262 .and_then(|e| e.first())
1263 .map(|s| s.as_str())
1264 .is_some_and(|v| quals_a.contains(&v))
1265 });
1266 if !a_found {
1267 return ConditionResult::False;
1268 }
1269 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1270 seg.elements
1271 .get(elem_b)
1272 .and_then(|e| e.get(comp_b))
1273 .map(|s| s.as_str())
1274 .is_some_and(|v| vals_b.contains(&v))
1275 });
1276 ConditionResult::from(b_found)
1277 }
1278
1279 pub fn has_segment_matching(
1289 &self,
1290 tag: &str,
1291 checks: &[(usize, usize, &str)],
1292 ) -> ConditionResult {
1293 let segments = self.find_segments(tag);
1294 if segments.is_empty() {
1295 return ConditionResult::Unknown;
1296 }
1297 let found = segments.iter().any(|seg| {
1298 checks.iter().all(|(elem, comp, val)| {
1299 seg.elements
1300 .get(*elem)
1301 .and_then(|e| e.get(*comp))
1302 .is_some_and(|v| v == val)
1303 })
1304 });
1305 ConditionResult::from(found)
1306 }
1307
1308 pub fn has_segment_matching_in_group(
1313 &self,
1314 tag: &str,
1315 checks: &[(usize, usize, &str)],
1316 group_path: &[&str],
1317 ) -> ConditionResult {
1318 let instance_count = self.group_instance_count(group_path);
1319 if instance_count > 0 {
1320 for i in 0..instance_count {
1321 let segs = self.find_segments_in_group(tag, group_path, i);
1322 if segs.iter().any(|seg| {
1323 checks.iter().all(|(elem, comp, val)| {
1324 seg.elements
1325 .get(*elem)
1326 .and_then(|e| e.get(*comp))
1327 .is_some_and(|v| v == val)
1328 })
1329 }) {
1330 return ConditionResult::True;
1331 }
1332 }
1333 return ConditionResult::False;
1334 }
1335 self.has_segment_matching(tag, checks)
1337 }
1338
1339 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1346 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1347 match segs.first() {
1348 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1349 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1350 None => ConditionResult::Unknown,
1351 },
1352 None => ConditionResult::Unknown,
1353 }
1354 }
1355
1356 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1358 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1359 match segs.first() {
1360 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1361 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1362 None => ConditionResult::Unknown,
1363 },
1364 None => ConditionResult::Unknown,
1365 }
1366 }
1367
1368 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1370 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1371 match segs.first() {
1372 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1373 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1374 None => ConditionResult::Unknown,
1375 },
1376 None => ConditionResult::Unknown,
1377 }
1378 }
1379
1380 pub fn count_qualified_in_group(
1387 &self,
1388 tag: &str,
1389 element_index: usize,
1390 qualifier: &str,
1391 group_path: &[&str],
1392 ) -> usize {
1393 let instance_count = self.group_instance_count(group_path);
1394 if instance_count > 0 {
1395 let mut total = 0;
1396 for i in 0..instance_count {
1397 total += self
1398 .find_segments_with_qualifier_in_group(
1399 tag,
1400 element_index,
1401 qualifier,
1402 group_path,
1403 i,
1404 )
1405 .len();
1406 }
1407 return total;
1408 }
1409 self.find_segments_with_qualifier(tag, element_index, qualifier)
1411 .len()
1412 }
1413
1414 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1416 let instance_count = self.group_instance_count(group_path);
1417 if instance_count > 0 {
1418 let mut total = 0;
1419 for i in 0..instance_count {
1420 total += self.find_segments_in_group(tag, group_path, i).len();
1421 }
1422 return total;
1423 }
1424 self.find_segments(tag).len()
1426 }
1427}
1428
1429fn first_component(segment: &OwnedSegment, element: usize) -> Option<&str> {
1431 segment
1432 .elements
1433 .get(element)
1434 .and_then(|e| e.first())
1435 .map(String::as_str)
1436}
1437
1438#[cfg(test)]
1439mod tests {
1440 use super::super::evaluator::NoOpExternalProvider;
1441 use super::*;
1442 use mig_types::navigator::GroupNavigator;
1443
1444 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1445 OwnedSegment {
1446 id: id.to_string(),
1447 elements: elements
1448 .into_iter()
1449 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1450 .collect(),
1451 segment_number: 0,
1452 }
1453 }
1454
1455 struct MockGroupNavigator {
1457 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1458 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1460 }
1461
1462 impl MockGroupNavigator {
1463 fn new() -> Self {
1464 Self {
1465 groups: vec![],
1466 children: vec![],
1467 }
1468 }
1469 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1470 self.groups
1471 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1472 self
1473 }
1474 fn with_child_group(
1475 mut self,
1476 parent_path: &[&str],
1477 parent_instance: usize,
1478 child_id: &str,
1479 child_instance: usize,
1480 segs: Vec<OwnedSegment>,
1481 ) -> Self {
1482 self.children.push((
1483 parent_path.iter().map(|s| s.to_string()).collect(),
1484 parent_instance,
1485 child_id.to_string(),
1486 child_instance,
1487 segs,
1488 ));
1489 self
1490 }
1491 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1492 self.groups
1493 .iter()
1494 .find(|(p, i, _)| {
1495 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1496 ps.as_slice() == group_path && *i == idx
1497 })
1498 .map(|(_, _, segs)| segs.as_slice())
1499 }
1500 }
1501
1502 impl GroupNavigator for MockGroupNavigator {
1503 fn find_segments_in_group(
1504 &self,
1505 segment_id: &str,
1506 group_path: &[&str],
1507 instance_index: usize,
1508 ) -> Vec<OwnedSegment> {
1509 self.find_instance(group_path, instance_index)
1510 .map(|segs| {
1511 segs.iter()
1512 .filter(|s| s.id == segment_id)
1513 .cloned()
1514 .collect()
1515 })
1516 .unwrap_or_default()
1517 }
1518 fn find_segments_with_qualifier_in_group(
1519 &self,
1520 segment_id: &str,
1521 element_index: usize,
1522 qualifier: &str,
1523 group_path: &[&str],
1524 instance_index: usize,
1525 ) -> Vec<OwnedSegment> {
1526 self.find_segments_in_group(segment_id, group_path, instance_index)
1527 .into_iter()
1528 .filter(|s| {
1529 s.elements
1530 .get(element_index)
1531 .and_then(|e| e.first())
1532 .is_some_and(|v| v == qualifier)
1533 })
1534 .collect()
1535 }
1536 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1537 self.groups
1538 .iter()
1539 .filter(|(p, _, _)| {
1540 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1541 ps.as_slice() == group_path
1542 })
1543 .count()
1544 }
1545 fn child_group_instance_count(
1546 &self,
1547 parent_path: &[&str],
1548 parent_instance: usize,
1549 child_group_id: &str,
1550 ) -> usize {
1551 self.children
1552 .iter()
1553 .filter(|(pp, pi, cid, _, _)| {
1554 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1555 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1556 })
1557 .count()
1558 }
1559 fn find_segments_in_child_group(
1560 &self,
1561 segment_id: &str,
1562 parent_path: &[&str],
1563 parent_instance: usize,
1564 child_group_id: &str,
1565 child_instance: usize,
1566 ) -> Vec<OwnedSegment> {
1567 self.children
1568 .iter()
1569 .find(|(pp, pi, cid, ci, _)| {
1570 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1571 ps.as_slice() == parent_path
1572 && *pi == parent_instance
1573 && cid == child_group_id
1574 && *ci == child_instance
1575 })
1576 .map(|(_, _, _, _, segs)| {
1577 segs.iter()
1578 .filter(|s| s.id == segment_id)
1579 .cloned()
1580 .collect()
1581 })
1582 .unwrap_or_default()
1583 }
1584 fn extract_value_in_group(
1585 &self,
1586 segment_id: &str,
1587 element_index: usize,
1588 component_index: usize,
1589 group_path: &[&str],
1590 instance_index: usize,
1591 ) -> Option<String> {
1592 let segs = self.find_instance(group_path, instance_index)?;
1593 let seg = segs.iter().find(|s| s.id == segment_id)?;
1594 seg.elements
1595 .get(element_index)?
1596 .get(component_index)
1597 .cloned()
1598 }
1599 }
1600
1601 #[test]
1602 fn test_find_segment() {
1603 let segments = vec![
1604 make_segment("UNH", vec![vec!["test"]]),
1605 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1606 ];
1607 let external = NoOpExternalProvider;
1608 let ctx = EvaluationContext::new("11001", &external, &segments);
1609
1610 assert!(ctx.find_segment("NAD").is_some());
1611 assert!(ctx.find_segment("DTM").is_none());
1612 }
1613
1614 #[test]
1615 fn test_find_segments_with_qualifier() {
1616 let segments = vec![
1617 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1618 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1619 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1620 ];
1621 let external = NoOpExternalProvider;
1622 let ctx = EvaluationContext::new("11001", &external, &segments);
1623
1624 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1625 assert_eq!(ms_nads.len(), 2);
1626 }
1627
1628 #[test]
1629 fn test_has_segment() {
1630 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1631 let external = NoOpExternalProvider;
1632 let ctx = EvaluationContext::new("11001", &external, &segments);
1633
1634 assert!(ctx.has_segment("UNH"));
1635 assert!(!ctx.has_segment("NAD"));
1636 }
1637
1638 #[test]
1641 fn test_no_navigator_group_find_returns_empty() {
1642 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1643 let external = NoOpExternalProvider;
1644 let ctx = EvaluationContext::new("55001", &external, &segments);
1645 assert!(ctx
1646 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1647 .is_empty());
1648 }
1649
1650 #[test]
1651 fn test_no_navigator_group_instance_count_zero() {
1652 let external = NoOpExternalProvider;
1653 let ctx = EvaluationContext::new("55001", &external, &[]);
1654 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1655 }
1656
1657 #[test]
1658 fn test_with_navigator_finds_segments_in_group() {
1659 let external = NoOpExternalProvider;
1660 let nav = MockGroupNavigator::new().with_group(
1661 &["SG4", "SG8"],
1662 0,
1663 vec![
1664 make_segment("SEQ", vec![vec!["Z98"]]),
1665 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1666 ],
1667 );
1668 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1669 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1670 assert_eq!(result.len(), 1);
1671 assert_eq!(result[0].id, "SEQ");
1672 }
1673
1674 #[test]
1675 fn test_with_navigator_qualifier_in_group() {
1676 let external = NoOpExternalProvider;
1677 let nav = MockGroupNavigator::new().with_group(
1678 &["SG4", "SG8"],
1679 0,
1680 vec![
1681 make_segment("SEQ", vec![vec!["Z98"]]),
1682 make_segment("SEQ", vec![vec!["Z01"]]),
1683 ],
1684 );
1685 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1686 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1687 assert_eq!(result.len(), 1);
1688 }
1689
1690 #[test]
1691 fn test_group_instance_count_with_navigator() {
1692 let external = NoOpExternalProvider;
1693 let nav = MockGroupNavigator::new()
1694 .with_group(
1695 &["SG4", "SG8"],
1696 0,
1697 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1698 )
1699 .with_group(
1700 &["SG4", "SG8"],
1701 1,
1702 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1703 );
1704 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1705 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1706 }
1707
1708 #[test]
1709 fn test_has_segment_in_group() {
1710 let external = NoOpExternalProvider;
1711 let nav = MockGroupNavigator::new().with_group(
1712 &["SG4", "SG8"],
1713 0,
1714 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1715 );
1716 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1717 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1718 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1719 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1720 }
1721
1722 #[test]
1725 fn test_has_qualifier() {
1726 let segments = vec![
1727 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1728 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1729 ];
1730 let external = NoOpExternalProvider;
1731 let ctx = EvaluationContext::new("11001", &external, &segments);
1732
1733 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1734 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1735 }
1736
1737 #[test]
1738 fn test_lacks_qualifier() {
1739 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1740 let external = NoOpExternalProvider;
1741 let ctx = EvaluationContext::new("11001", &external, &segments);
1742
1743 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1744 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1745 }
1746
1747 #[test]
1748 fn test_has_qualified_value() {
1749 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1750 let external = NoOpExternalProvider;
1751 let ctx = EvaluationContext::new("55001", &external, &segments);
1752
1753 assert_eq!(
1754 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1755 ConditionResult::True,
1756 );
1757 assert_eq!(
1758 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1759 ConditionResult::False,
1760 );
1761 assert_eq!(
1763 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1764 ConditionResult::Unknown,
1765 );
1766 }
1767
1768 #[test]
1769 fn test_any_group_has_qualifier() {
1770 let external = NoOpExternalProvider;
1771 let nav = MockGroupNavigator::new()
1772 .with_group(
1773 &["SG4", "SG8"],
1774 0,
1775 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1776 )
1777 .with_group(
1778 &["SG4", "SG8"],
1779 1,
1780 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1781 );
1782 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1783
1784 assert_eq!(
1785 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1786 ConditionResult::True,
1787 );
1788 assert_eq!(
1789 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1790 ConditionResult::False,
1791 );
1792 }
1793
1794 #[test]
1795 fn test_any_group_has_qualifier_fallback() {
1796 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1798 let external = NoOpExternalProvider;
1799 let ctx = EvaluationContext::new("55001", &external, &segments);
1800
1801 assert_eq!(
1802 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1803 ConditionResult::True,
1804 );
1805 }
1806
1807 #[test]
1808 fn test_any_group_has_any_qualifier() {
1809 let external = NoOpExternalProvider;
1810 let nav = MockGroupNavigator::new().with_group(
1811 &["SG4", "SG8"],
1812 0,
1813 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1814 );
1815 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1816
1817 assert_eq!(
1818 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1819 ConditionResult::True,
1820 );
1821 assert_eq!(
1822 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1823 ConditionResult::False,
1824 );
1825 }
1826
1827 #[test]
1828 fn test_any_group_has_co_occurrence() {
1829 let external = NoOpExternalProvider;
1830 let nav = MockGroupNavigator::new().with_group(
1831 &["SG4", "SG8"],
1832 0,
1833 vec![
1834 make_segment("SEQ", vec![vec!["Z01"]]),
1835 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1836 ],
1837 );
1838 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1839
1840 assert_eq!(
1841 ctx.any_group_has_co_occurrence(
1842 "SEQ",
1843 0,
1844 &["Z01"],
1845 "CCI",
1846 2,
1847 0,
1848 &["Z07"],
1849 &["SG4", "SG8"],
1850 ),
1851 ConditionResult::True,
1852 );
1853 assert_eq!(
1855 ctx.any_group_has_co_occurrence(
1856 "SEQ",
1857 0,
1858 &["Z01"],
1859 "CCI",
1860 2,
1861 0,
1862 &["ZC0"],
1863 &["SG4", "SG8"],
1864 ),
1865 ConditionResult::False,
1866 );
1867 }
1868
1869 #[test]
1872 fn test_filtered_parent_child_has_qualifier() {
1873 let external = NoOpExternalProvider;
1874 let nav = MockGroupNavigator::new()
1877 .with_group(
1878 &["SG4", "SG8"],
1879 0,
1880 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1881 )
1882 .with_group(
1883 &["SG4", "SG8"],
1884 1,
1885 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1886 )
1887 .with_child_group(
1888 &["SG4", "SG8"],
1889 0,
1890 "SG10",
1891 0,
1892 vec![make_segment("CCI", vec![vec!["Z23"]])],
1893 );
1894 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1895
1896 assert_eq!(
1898 ctx.filtered_parent_child_has_qualifier(
1899 &["SG4", "SG8"],
1900 "SEQ",
1901 0,
1902 "Z98",
1903 "SG10",
1904 "CCI",
1905 0,
1906 "Z23",
1907 ),
1908 ConditionResult::True,
1909 );
1910 assert_eq!(
1912 ctx.filtered_parent_child_has_qualifier(
1913 &["SG4", "SG8"],
1914 "SEQ",
1915 0,
1916 "Z01",
1917 "SG10",
1918 "CCI",
1919 0,
1920 "Z23",
1921 ),
1922 ConditionResult::False,
1923 );
1924 assert_eq!(
1926 ctx.filtered_parent_child_has_qualifier(
1927 &["SG4", "SG8"],
1928 "SEQ",
1929 0,
1930 "Z98",
1931 "SG10",
1932 "CCI",
1933 0,
1934 "Z99",
1935 ),
1936 ConditionResult::False,
1937 );
1938 }
1939
1940 #[test]
1941 fn test_filtered_parent_child_fallback() {
1942 let segments = vec![
1944 make_segment("SEQ", vec![vec!["Z98"]]),
1945 make_segment("CCI", vec![vec!["Z23"]]),
1946 ];
1947 let external = NoOpExternalProvider;
1948 let ctx = EvaluationContext::new("55001", &external, &segments);
1949
1950 assert_eq!(
1951 ctx.filtered_parent_child_has_qualifier(
1952 &["SG4", "SG8"],
1953 "SEQ",
1954 0,
1955 "Z98",
1956 "SG10",
1957 "CCI",
1958 0,
1959 "Z23",
1960 ),
1961 ConditionResult::True,
1962 );
1963 assert_eq!(
1965 ctx.filtered_parent_child_has_qualifier(
1966 &["SG4", "SG8"],
1967 "SEQ",
1968 0,
1969 "Z98",
1970 "SG10",
1971 "CCI",
1972 0,
1973 "Z99",
1974 ),
1975 ConditionResult::False,
1976 );
1977 }
1978
1979 #[test]
1980 fn test_any_group_has_qualifier_without() {
1981 let external = NoOpExternalProvider;
1982 let nav = MockGroupNavigator::new()
1985 .with_group(
1986 &["SG4", "SG8"],
1987 0,
1988 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1989 )
1990 .with_group(
1991 &["SG4", "SG8"],
1992 1,
1993 vec![
1994 make_segment("SEQ", vec![vec!["Z01"]]),
1995 make_segment("CCI", vec![vec!["11"]]),
1996 ],
1997 );
1998 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1999
2000 assert_eq!(
2002 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2003 ConditionResult::True,
2004 );
2005 assert_eq!(
2007 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
2008 ConditionResult::False,
2009 );
2010 assert_eq!(
2012 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
2013 ConditionResult::False,
2014 );
2015 }
2016
2017 #[test]
2018 fn test_any_group_has_qualifier_without_fallback() {
2019 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
2020 let external = NoOpExternalProvider;
2021 let ctx = EvaluationContext::new("55001", &external, &segments);
2022
2023 assert_eq!(
2025 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
2026 ConditionResult::True,
2027 );
2028 }
2029
2030 #[test]
2031 fn test_collect_group_values() {
2032 let external = NoOpExternalProvider;
2033 let nav = MockGroupNavigator::new()
2034 .with_group(
2035 &["SG4", "SG6"],
2036 0,
2037 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
2038 )
2039 .with_group(
2040 &["SG4", "SG6"],
2041 1,
2042 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
2043 );
2044 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2045
2046 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
2047 assert_eq!(values.len(), 2);
2048 assert_eq!(values[0], (0, "REF001".to_string()));
2049 assert_eq!(values[1], (1, "REF002".to_string()));
2050 }
2051
2052 #[test]
2053 fn test_groups_share_qualified_value() {
2054 let external = NoOpExternalProvider;
2055 let nav = MockGroupNavigator::new()
2059 .with_group(
2060 &["SG4", "SG6"],
2061 0,
2062 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2063 )
2064 .with_group(
2065 &["SG4", "SG8"],
2066 0,
2067 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2068 )
2069 .with_group(
2070 &["SG4", "SG8"],
2071 1,
2072 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
2073 );
2074 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2075
2076 assert_eq!(
2078 ctx.groups_share_qualified_value(
2079 "RFF",
2080 0,
2081 "Z49",
2082 0,
2083 1,
2084 &["SG4", "SG6"],
2085 "SEQ",
2086 1,
2087 0,
2088 &["SG4", "SG8"],
2089 ),
2090 ConditionResult::True,
2091 );
2092 }
2093
2094 #[test]
2095 fn test_groups_share_qualified_value_no_match() {
2096 let external = NoOpExternalProvider;
2097 let nav = MockGroupNavigator::new()
2098 .with_group(
2099 &["SG4", "SG6"],
2100 0,
2101 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2102 )
2103 .with_group(
2104 &["SG4", "SG8"],
2105 0,
2106 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
2107 );
2108 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2109
2110 assert_eq!(
2112 ctx.groups_share_qualified_value(
2113 "RFF",
2114 0,
2115 "Z49",
2116 0,
2117 1,
2118 &["SG4", "SG6"],
2119 "SEQ",
2120 1,
2121 0,
2122 &["SG4", "SG8"],
2123 ),
2124 ConditionResult::False,
2125 );
2126 }
2127
2128 #[test]
2129 fn test_groups_share_qualified_value_no_source() {
2130 let external = NoOpExternalProvider;
2131 let nav = MockGroupNavigator::new()
2133 .with_group(
2134 &["SG4", "SG6"],
2135 0,
2136 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
2137 )
2138 .with_group(
2139 &["SG4", "SG8"],
2140 0,
2141 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2142 );
2143 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2144
2145 assert_eq!(
2147 ctx.groups_share_qualified_value(
2148 "RFF",
2149 0,
2150 "Z49",
2151 0,
2152 1,
2153 &["SG4", "SG6"],
2154 "SEQ",
2155 1,
2156 0,
2157 &["SG4", "SG8"],
2158 ),
2159 ConditionResult::Unknown,
2160 );
2161 }
2162
2163 #[test]
2164 fn test_groups_share_qualified_value_fallback() {
2165 let segments = vec![
2167 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
2168 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
2169 ];
2170 let external = NoOpExternalProvider;
2171 let ctx = EvaluationContext::new("55001", &external, &segments);
2172
2173 assert_eq!(
2174 ctx.groups_share_qualified_value(
2175 "RFF",
2176 0,
2177 "Z49",
2178 0,
2179 1,
2180 &["SG4", "SG6"],
2181 "SEQ",
2182 1,
2183 0,
2184 &["SG4", "SG8"],
2185 ),
2186 ConditionResult::True,
2187 );
2188 }
2189
2190 #[test]
2191 fn test_child_group_pass_throughs_no_navigator() {
2192 let external = NoOpExternalProvider;
2193 let ctx = EvaluationContext::new("55001", &external, &[]);
2194
2195 assert_eq!(
2196 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
2197 0
2198 );
2199 assert!(ctx
2200 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
2201 .is_empty());
2202 assert_eq!(
2203 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
2204 None
2205 );
2206 }
2207
2208 #[test]
2211 fn test_has_segment_matching_found() {
2212 let segments = vec![
2213 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
2214 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
2215 ];
2216 let external = NoOpExternalProvider;
2217 let ctx = EvaluationContext::new("55001", &external, &segments);
2218
2219 assert_eq!(
2220 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
2221 ConditionResult::True,
2222 );
2223 }
2224
2225 #[test]
2226 fn test_has_segment_matching_not_found() {
2227 let segments = vec![make_segment(
2228 "STS",
2229 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2230 )];
2231 let external = NoOpExternalProvider;
2232 let ctx = EvaluationContext::new("55001", &external, &segments);
2233
2234 assert_eq!(
2235 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2236 ConditionResult::False,
2237 );
2238 }
2239
2240 #[test]
2241 fn test_has_segment_matching_no_segments() {
2242 let external = NoOpExternalProvider;
2243 let ctx = EvaluationContext::new("55001", &external, &[]);
2244
2245 assert_eq!(
2246 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2247 ConditionResult::Unknown,
2248 );
2249 }
2250
2251 #[test]
2252 fn test_has_segment_matching_in_group() {
2253 let nav = MockGroupNavigator::new()
2254 .with_group(
2255 &["SG4"],
2256 0,
2257 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2258 )
2259 .with_group(
2260 &["SG4"],
2261 1,
2262 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2263 );
2264 let segments = vec![
2265 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2266 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2267 ];
2268 let external = NoOpExternalProvider;
2269 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2270
2271 assert_eq!(
2272 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2273 ConditionResult::True,
2274 );
2275 }
2276
2277 #[test]
2280 fn test_dtm_ge() {
2281 let segments = vec![make_segment(
2282 "DTM",
2283 vec![vec!["137", "202601010000", "303"]],
2284 )];
2285 let external = NoOpExternalProvider;
2286 let ctx = EvaluationContext::new("55001", &external, &segments);
2287
2288 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2289 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2290 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2291 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2292 }
2293
2294 #[test]
2295 fn test_dtm_lt() {
2296 let segments = vec![make_segment(
2297 "DTM",
2298 vec![vec!["137", "202601010000", "303"]],
2299 )];
2300 let external = NoOpExternalProvider;
2301 let ctx = EvaluationContext::new("55001", &external, &segments);
2302
2303 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2304 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2305 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2306 }
2307
2308 #[test]
2309 fn test_dtm_le() {
2310 let segments = vec![make_segment(
2311 "DTM",
2312 vec![vec!["137", "202601010000", "303"]],
2313 )];
2314 let external = NoOpExternalProvider;
2315 let ctx = EvaluationContext::new("55001", &external, &segments);
2316
2317 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2318 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2319 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2320 }
2321
2322 #[test]
2325 fn test_count_qualified_in_group() {
2326 let nav = MockGroupNavigator::new()
2327 .with_group(
2328 &["SG4", "SG8"],
2329 0,
2330 vec![
2331 make_segment("CCI", vec![vec!["Z23"]]),
2332 make_segment("CCI", vec![vec!["Z30"]]),
2333 ],
2334 )
2335 .with_group(
2336 &["SG4", "SG8"],
2337 1,
2338 vec![make_segment("CCI", vec![vec!["Z23"]])],
2339 );
2340 let segments = vec![
2341 make_segment("CCI", vec![vec!["Z23"]]),
2342 make_segment("CCI", vec![vec!["Z30"]]),
2343 make_segment("CCI", vec![vec!["Z23"]]),
2344 ];
2345 let external = NoOpExternalProvider;
2346 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2347
2348 assert_eq!(
2349 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2350 2
2351 );
2352 assert_eq!(
2353 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2354 1
2355 );
2356 assert_eq!(
2357 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2358 0
2359 );
2360 }
2361
2362 #[test]
2363 fn test_count_in_group() {
2364 let nav = MockGroupNavigator::new()
2365 .with_group(
2366 &["SG4", "SG8"],
2367 0,
2368 vec![
2369 make_segment("SEQ", vec![vec!["Z98"]]),
2370 make_segment("CCI", vec![vec!["Z23"]]),
2371 ],
2372 )
2373 .with_group(
2374 &["SG4", "SG8"],
2375 1,
2376 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2377 );
2378 let segments = vec![
2379 make_segment("SEQ", vec![vec!["Z98"]]),
2380 make_segment("CCI", vec![vec!["Z23"]]),
2381 make_segment("SEQ", vec![vec!["Z01"]]),
2382 ];
2383 let external = NoOpExternalProvider;
2384 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2385
2386 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2387 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2388 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2389 }
2390
2391 #[test]
2392 fn test_count_fallback_no_navigator() {
2393 let segments = vec![
2394 make_segment("CCI", vec![vec!["Z23"]]),
2395 make_segment("CCI", vec![vec!["Z30"]]),
2396 make_segment("CCI", vec![vec!["Z23"]]),
2397 ];
2398 let external = NoOpExternalProvider;
2399 let ctx = EvaluationContext::new("55001", &external, &segments);
2400
2401 assert_eq!(
2403 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2404 2
2405 );
2406 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2407 }
2408
2409 #[test]
2412 fn format_check_prefers_resolved_value() {
2413 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2415 let external = NoOpExternalProvider;
2416 let ctx = EvaluationContext::new("55001", &external, &segments)
2417 .with_resolved(Some("CORRECT"), None);
2418
2419 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2420 assert_eq!(result, ConditionResult::True);
2421 }
2422
2423 #[test]
2424 fn format_check_falls_back_to_segment_search() {
2425 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2427 let external = NoOpExternalProvider;
2428 let ctx = EvaluationContext::new("55001", &external, &segments);
2429
2430 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2431 assert_eq!(result, ConditionResult::True);
2432 }
2433
2434 #[test]
2435 fn format_check_returns_unknown_when_segment_absent() {
2436 let segments: Vec<OwnedSegment> = vec![];
2438 let external = NoOpExternalProvider;
2439 let ctx = EvaluationContext::new("55001", &external, &segments);
2440
2441 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2442 assert_eq!(result, ConditionResult::Unknown);
2443 }
2444
2445 #[test]
2446 fn format_check_returns_unknown_when_multiple_segments() {
2447 let segments = vec![
2451 make_segment("DTM", vec![vec!["92", "OK"]]),
2452 make_segment("DTM", vec![vec!["163", "BAD"]]),
2453 ];
2454 let external = NoOpExternalProvider;
2455 let ctx = EvaluationContext::new("55001", &external, &segments);
2456
2457 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2458 assert_eq!(result, ConditionResult::Unknown);
2459 }
2460
2461 #[test]
2462 fn format_check_qualified_prefers_resolved_value() {
2463 let segments = vec![
2465 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2466 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2467 ];
2468 let external = NoOpExternalProvider;
2469 let ctx = EvaluationContext::new("55001", &external, &segments)
2470 .with_resolved(Some("CORRECT"), None);
2471
2472 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2473 ConditionResult::from(v == "CORRECT")
2474 });
2475 assert_eq!(result, ConditionResult::True);
2476 }
2477
2478 #[test]
2479 fn format_check_qualified_falls_back_to_qualified_search() {
2480 let segments = vec![
2482 make_segment("DTM", vec![vec!["92", "2200"]]),
2483 make_segment("DTM", vec![vec!["163", "0800"]]),
2484 ];
2485 let external = NoOpExternalProvider;
2486 let ctx = EvaluationContext::new("55001", &external, &segments);
2487
2488 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2490 ConditionResult::from(v == "0800")
2491 });
2492 assert_eq!(result, ConditionResult::True);
2493
2494 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2496 ConditionResult::from(v == "2200")
2497 });
2498 assert_eq!(result2, ConditionResult::False);
2499 }
2500
2501 #[test]
2504 fn self_segment_value_prefers_resolved_segment() {
2505 let segments = vec![
2509 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2510 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2511 ];
2512 let external = NoOpExternalProvider;
2513 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2514 let ctx = EvaluationContext::new("55001", &external, &segments)
2515 .with_resolved(None, Some(&resolved_elements));
2516
2517 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2518 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2519 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2520 }
2521
2522 #[test]
2523 fn self_segment_value_falls_back_when_no_resolved_segment() {
2524 let segments = vec![
2526 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2527 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2528 ];
2529 let external = NoOpExternalProvider;
2530 let ctx = EvaluationContext::new("55001", &external, &segments);
2531
2532 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2533 }
2534
2535 #[test]
2536 fn self_segment_value_missing_returns_none() {
2537 let segments: Vec<OwnedSegment> = vec![];
2538 let external = NoOpExternalProvider;
2539 let ctx = EvaluationContext::new("55001", &external, &segments);
2540
2541 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2542 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2543 }
2544
2545 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2549 let mut segments = Vec::new();
2550 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2551 segments.push(OwnedSegment {
2552 id: "SEQ".to_string(),
2553 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2554 segment_number: (2 * i + 1) as u32,
2555 });
2556 segments.push(OwnedSegment {
2557 id: "PIA".to_string(),
2558 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2559 segment_number: (2 * i + 2) as u32,
2560 });
2561 }
2562 segments
2563 }
2564
2565 #[test]
2566 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2567 let segments = make_sg8_sequence(&[
2569 ("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"), ]);
2574 let external = NoOpExternalProvider;
2575 let seq = &segments[0].elements;
2577 let ctx =
2578 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2579
2580 let n = ctx
2581 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2582 assert_eq!(n, 2);
2583 }
2584
2585 #[test]
2586 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2587 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2588 let external = NoOpExternalProvider;
2589 let ctx = EvaluationContext::new("55001", &external, &segments);
2590 let n = ctx
2591 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2592 assert_eq!(n, 0);
2593 }
2594
2595 #[test]
2596 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2597 let segments = make_sg8_sequence(&[
2598 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2601 let external = NoOpExternalProvider;
2602 let seq = &segments[0].elements;
2603 let ctx =
2604 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2605
2606 assert_eq!(
2607 ctx.count_sg8_obis_matches(
2608 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2609 ),
2610 1
2611 );
2612 assert_eq!(
2613 ctx.count_sg8_obis_matches(
2614 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2615 ),
2616 1
2617 );
2618 }
2619}