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 this_group_has_qualifier(
927 &self,
928 tag: &str,
929 element_index: usize,
930 qualifier: &str,
931 group_path: &[&str],
932 ) -> ConditionResult {
933 match self.enclosing_path(group_path) {
934 Some(path) => ConditionResult::from(
935 self.find_segments_at(path, tag)
936 .iter()
937 .any(|s| first_component(s, element_index) == Some(qualifier)),
938 ),
939 None => self.any_group_has_qualifier(tag, element_index, qualifier, group_path),
940 }
941 }
942
943 pub fn this_group_has_any_qualifier(
946 &self,
947 tag: &str,
948 element_index: usize,
949 qualifiers: &[&str],
950 group_path: &[&str],
951 ) -> ConditionResult {
952 match self.enclosing_path(group_path) {
953 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
954 first_component(s, element_index).is_some_and(|v| qualifiers.contains(&v))
955 })),
956 None => self.any_group_has_any_qualifier(tag, element_index, qualifiers, group_path),
957 }
958 }
959
960 #[allow(clippy::too_many_arguments)]
963 pub fn this_group_has_qualified_value(
964 &self,
965 tag: &str,
966 qual_elem: usize,
967 qualifier: &str,
968 value_elem: usize,
969 value_comp: usize,
970 values: &[&str],
971 group_path: &[&str],
972 ) -> ConditionResult {
973 match self.enclosing_path(group_path) {
974 Some(path) => ConditionResult::from(self.find_segments_at(path, tag).iter().any(|s| {
975 first_component(s, qual_elem) == Some(qualifier)
976 && s.elements
977 .get(value_elem)
978 .and_then(|e| e.get(value_comp))
979 .is_some_and(|v| values.contains(&v.as_str()))
980 })),
981 None => self.any_group_has_qualified_value(
982 tag, qual_elem, qualifier, value_elem, value_comp, values, group_path,
983 ),
984 }
985 }
986
987 pub fn this_group_values(
991 &self,
992 tag: &str,
993 elem: usize,
994 comp: usize,
995 group_path: &[&str],
996 ) -> Vec<(usize, String)> {
997 match self.enclosing_path(group_path) {
998 Some(path) => self
999 .find_segments_at(path, tag)
1000 .iter()
1001 .filter_map(|s| s.elements.get(elem).and_then(|e| e.get(comp)).cloned())
1002 .filter(|v| !v.is_empty())
1003 .map(|v| (0, v))
1004 .collect(),
1005 None => self.collect_group_values(tag, elem, comp, group_path),
1006 }
1007 }
1008
1009 pub fn collect_group_values(
1013 &self,
1014 tag: &str,
1015 elem: usize,
1016 comp: usize,
1017 group_path: &[&str],
1018 ) -> Vec<(usize, String)> {
1019 let instance_count = self.group_instance_count(group_path);
1020 let mut results = Vec::new();
1021 for i in 0..instance_count {
1022 if let Some(val) = self
1023 .navigator
1024 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
1025 {
1026 if !val.is_empty() {
1027 results.push((i, val));
1028 }
1029 }
1030 }
1031 results
1032 }
1033
1034 #[allow(clippy::too_many_arguments)]
1043 pub fn groups_share_qualified_value(
1044 &self,
1045 source_tag: &str,
1046 source_qual_elem: usize,
1047 source_qual: &str,
1048 source_value_elem: usize,
1049 source_value_comp: usize,
1050 source_path: &[&str],
1051 target_tag: &str,
1052 target_elem: usize,
1053 target_comp: usize,
1054 target_path: &[&str],
1055 ) -> ConditionResult {
1056 let source_count = self.group_instance_count(source_path);
1057 let target_count = self.group_instance_count(target_path);
1058 if source_count > 0 && target_count > 0 {
1059 let mut source_values = Vec::new();
1061 for si in 0..source_count {
1062 let segs = self.find_segments_with_qualifier_in_group(
1063 source_tag,
1064 source_qual_elem,
1065 source_qual,
1066 source_path,
1067 si,
1068 );
1069 for seg in &segs {
1070 if let Some(val) = seg
1071 .elements
1072 .get(source_value_elem)
1073 .and_then(|e| e.get(source_value_comp))
1074 {
1075 if !val.is_empty() {
1076 source_values.push(val.clone());
1077 }
1078 }
1079 }
1080 }
1081 if source_values.is_empty() {
1082 return ConditionResult::Unknown;
1083 }
1084 let target_values =
1086 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
1087 for (_, tv) in &target_values {
1088 if source_values.iter().any(|sv| sv == tv) {
1089 return ConditionResult::True;
1090 }
1091 }
1092 return ConditionResult::False;
1093 }
1094 let source_segs =
1096 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
1097 let source_vals: Vec<&str> = source_segs
1098 .iter()
1099 .filter_map(|s| {
1100 s.elements
1101 .get(source_value_elem)
1102 .and_then(|e| e.get(source_value_comp))
1103 .map(|v| v.as_str())
1104 })
1105 .filter(|v| !v.is_empty())
1106 .collect();
1107 if source_vals.is_empty() {
1108 return ConditionResult::Unknown;
1109 }
1110 let target_segs = self.find_segments(target_tag);
1111 let has_match = target_segs.iter().any(|s| {
1112 s.elements
1113 .get(target_elem)
1114 .and_then(|e| e.get(target_comp))
1115 .map(|v| v.as_str())
1116 .is_some_and(|v| source_vals.contains(&v))
1117 });
1118 ConditionResult::from(has_match)
1119 }
1120
1121 #[allow(clippy::too_many_arguments)]
1130 pub fn any_group_has_co_occurrence(
1131 &self,
1132 tag_a: &str,
1133 elem_a: usize,
1134 quals_a: &[&str],
1135 tag_b: &str,
1136 elem_b: usize,
1137 comp_b: usize,
1138 vals_b: &[&str],
1139 group_path: &[&str],
1140 ) -> ConditionResult {
1141 let instance_count = self.group_instance_count(group_path);
1142 if instance_count > 0 {
1143 for i in 0..instance_count {
1144 let a_present = self
1145 .find_segments_in_group(tag_a, group_path, i)
1146 .iter()
1147 .any(|seg| {
1148 seg.elements
1149 .get(elem_a)
1150 .and_then(|e| e.first())
1151 .map(|s| s.as_str())
1152 .is_some_and(|v| quals_a.contains(&v))
1153 });
1154 let b_present = self
1155 .find_segments_in_group(tag_b, group_path, i)
1156 .iter()
1157 .any(|seg| {
1158 seg.elements
1159 .get(elem_b)
1160 .and_then(|e| e.get(comp_b))
1161 .map(|s| s.as_str())
1162 .is_some_and(|v| vals_b.contains(&v))
1163 });
1164 if a_present && b_present {
1165 return ConditionResult::True;
1166 }
1167 }
1168 return ConditionResult::False;
1169 }
1170 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1172 seg.elements
1173 .get(elem_a)
1174 .and_then(|e| e.first())
1175 .map(|s| s.as_str())
1176 .is_some_and(|v| quals_a.contains(&v))
1177 });
1178 if !a_found {
1179 return ConditionResult::False;
1180 }
1181 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1182 seg.elements
1183 .get(elem_b)
1184 .and_then(|e| e.get(comp_b))
1185 .map(|s| s.as_str())
1186 .is_some_and(|v| vals_b.contains(&v))
1187 });
1188 ConditionResult::from(b_found)
1189 }
1190
1191 pub fn has_segment_matching(
1201 &self,
1202 tag: &str,
1203 checks: &[(usize, usize, &str)],
1204 ) -> ConditionResult {
1205 let segments = self.find_segments(tag);
1206 if segments.is_empty() {
1207 return ConditionResult::Unknown;
1208 }
1209 let found = segments.iter().any(|seg| {
1210 checks.iter().all(|(elem, comp, val)| {
1211 seg.elements
1212 .get(*elem)
1213 .and_then(|e| e.get(*comp))
1214 .is_some_and(|v| v == val)
1215 })
1216 });
1217 ConditionResult::from(found)
1218 }
1219
1220 pub fn has_segment_matching_in_group(
1225 &self,
1226 tag: &str,
1227 checks: &[(usize, usize, &str)],
1228 group_path: &[&str],
1229 ) -> ConditionResult {
1230 let instance_count = self.group_instance_count(group_path);
1231 if instance_count > 0 {
1232 for i in 0..instance_count {
1233 let segs = self.find_segments_in_group(tag, group_path, i);
1234 if segs.iter().any(|seg| {
1235 checks.iter().all(|(elem, comp, val)| {
1236 seg.elements
1237 .get(*elem)
1238 .and_then(|e| e.get(*comp))
1239 .is_some_and(|v| v == val)
1240 })
1241 }) {
1242 return ConditionResult::True;
1243 }
1244 }
1245 return ConditionResult::False;
1246 }
1247 self.has_segment_matching(tag, checks)
1249 }
1250
1251 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1258 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1259 match segs.first() {
1260 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1261 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1262 None => ConditionResult::Unknown,
1263 },
1264 None => ConditionResult::Unknown,
1265 }
1266 }
1267
1268 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1270 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1271 match segs.first() {
1272 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1273 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1274 None => ConditionResult::Unknown,
1275 },
1276 None => ConditionResult::Unknown,
1277 }
1278 }
1279
1280 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1282 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1283 match segs.first() {
1284 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1285 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1286 None => ConditionResult::Unknown,
1287 },
1288 None => ConditionResult::Unknown,
1289 }
1290 }
1291
1292 pub fn count_qualified_in_group(
1299 &self,
1300 tag: &str,
1301 element_index: usize,
1302 qualifier: &str,
1303 group_path: &[&str],
1304 ) -> usize {
1305 let instance_count = self.group_instance_count(group_path);
1306 if instance_count > 0 {
1307 let mut total = 0;
1308 for i in 0..instance_count {
1309 total += self
1310 .find_segments_with_qualifier_in_group(
1311 tag,
1312 element_index,
1313 qualifier,
1314 group_path,
1315 i,
1316 )
1317 .len();
1318 }
1319 return total;
1320 }
1321 self.find_segments_with_qualifier(tag, element_index, qualifier)
1323 .len()
1324 }
1325
1326 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1328 let instance_count = self.group_instance_count(group_path);
1329 if instance_count > 0 {
1330 let mut total = 0;
1331 for i in 0..instance_count {
1332 total += self.find_segments_in_group(tag, group_path, i).len();
1333 }
1334 return total;
1335 }
1336 self.find_segments(tag).len()
1338 }
1339}
1340
1341fn first_component(segment: &OwnedSegment, element: usize) -> Option<&str> {
1343 segment
1344 .elements
1345 .get(element)
1346 .and_then(|e| e.first())
1347 .map(String::as_str)
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352 use super::super::evaluator::NoOpExternalProvider;
1353 use super::*;
1354 use mig_types::navigator::GroupNavigator;
1355
1356 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1357 OwnedSegment {
1358 id: id.to_string(),
1359 elements: elements
1360 .into_iter()
1361 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1362 .collect(),
1363 segment_number: 0,
1364 }
1365 }
1366
1367 struct MockGroupNavigator {
1369 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1370 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1372 }
1373
1374 impl MockGroupNavigator {
1375 fn new() -> Self {
1376 Self {
1377 groups: vec![],
1378 children: vec![],
1379 }
1380 }
1381 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1382 self.groups
1383 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1384 self
1385 }
1386 fn with_child_group(
1387 mut self,
1388 parent_path: &[&str],
1389 parent_instance: usize,
1390 child_id: &str,
1391 child_instance: usize,
1392 segs: Vec<OwnedSegment>,
1393 ) -> Self {
1394 self.children.push((
1395 parent_path.iter().map(|s| s.to_string()).collect(),
1396 parent_instance,
1397 child_id.to_string(),
1398 child_instance,
1399 segs,
1400 ));
1401 self
1402 }
1403 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1404 self.groups
1405 .iter()
1406 .find(|(p, i, _)| {
1407 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1408 ps.as_slice() == group_path && *i == idx
1409 })
1410 .map(|(_, _, segs)| segs.as_slice())
1411 }
1412 }
1413
1414 impl GroupNavigator for MockGroupNavigator {
1415 fn find_segments_in_group(
1416 &self,
1417 segment_id: &str,
1418 group_path: &[&str],
1419 instance_index: usize,
1420 ) -> Vec<OwnedSegment> {
1421 self.find_instance(group_path, instance_index)
1422 .map(|segs| {
1423 segs.iter()
1424 .filter(|s| s.id == segment_id)
1425 .cloned()
1426 .collect()
1427 })
1428 .unwrap_or_default()
1429 }
1430 fn find_segments_with_qualifier_in_group(
1431 &self,
1432 segment_id: &str,
1433 element_index: usize,
1434 qualifier: &str,
1435 group_path: &[&str],
1436 instance_index: usize,
1437 ) -> Vec<OwnedSegment> {
1438 self.find_segments_in_group(segment_id, group_path, instance_index)
1439 .into_iter()
1440 .filter(|s| {
1441 s.elements
1442 .get(element_index)
1443 .and_then(|e| e.first())
1444 .is_some_and(|v| v == qualifier)
1445 })
1446 .collect()
1447 }
1448 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1449 self.groups
1450 .iter()
1451 .filter(|(p, _, _)| {
1452 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1453 ps.as_slice() == group_path
1454 })
1455 .count()
1456 }
1457 fn child_group_instance_count(
1458 &self,
1459 parent_path: &[&str],
1460 parent_instance: usize,
1461 child_group_id: &str,
1462 ) -> usize {
1463 self.children
1464 .iter()
1465 .filter(|(pp, pi, cid, _, _)| {
1466 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1467 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1468 })
1469 .count()
1470 }
1471 fn find_segments_in_child_group(
1472 &self,
1473 segment_id: &str,
1474 parent_path: &[&str],
1475 parent_instance: usize,
1476 child_group_id: &str,
1477 child_instance: usize,
1478 ) -> Vec<OwnedSegment> {
1479 self.children
1480 .iter()
1481 .find(|(pp, pi, cid, ci, _)| {
1482 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1483 ps.as_slice() == parent_path
1484 && *pi == parent_instance
1485 && cid == child_group_id
1486 && *ci == child_instance
1487 })
1488 .map(|(_, _, _, _, segs)| {
1489 segs.iter()
1490 .filter(|s| s.id == segment_id)
1491 .cloned()
1492 .collect()
1493 })
1494 .unwrap_or_default()
1495 }
1496 fn extract_value_in_group(
1497 &self,
1498 segment_id: &str,
1499 element_index: usize,
1500 component_index: usize,
1501 group_path: &[&str],
1502 instance_index: usize,
1503 ) -> Option<String> {
1504 let segs = self.find_instance(group_path, instance_index)?;
1505 let seg = segs.iter().find(|s| s.id == segment_id)?;
1506 seg.elements
1507 .get(element_index)?
1508 .get(component_index)
1509 .cloned()
1510 }
1511 }
1512
1513 #[test]
1514 fn test_find_segment() {
1515 let segments = vec![
1516 make_segment("UNH", vec![vec!["test"]]),
1517 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1518 ];
1519 let external = NoOpExternalProvider;
1520 let ctx = EvaluationContext::new("11001", &external, &segments);
1521
1522 assert!(ctx.find_segment("NAD").is_some());
1523 assert!(ctx.find_segment("DTM").is_none());
1524 }
1525
1526 #[test]
1527 fn test_find_segments_with_qualifier() {
1528 let segments = vec![
1529 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1530 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1531 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1532 ];
1533 let external = NoOpExternalProvider;
1534 let ctx = EvaluationContext::new("11001", &external, &segments);
1535
1536 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1537 assert_eq!(ms_nads.len(), 2);
1538 }
1539
1540 #[test]
1541 fn test_has_segment() {
1542 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1543 let external = NoOpExternalProvider;
1544 let ctx = EvaluationContext::new("11001", &external, &segments);
1545
1546 assert!(ctx.has_segment("UNH"));
1547 assert!(!ctx.has_segment("NAD"));
1548 }
1549
1550 #[test]
1553 fn test_no_navigator_group_find_returns_empty() {
1554 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1555 let external = NoOpExternalProvider;
1556 let ctx = EvaluationContext::new("55001", &external, &segments);
1557 assert!(ctx
1558 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1559 .is_empty());
1560 }
1561
1562 #[test]
1563 fn test_no_navigator_group_instance_count_zero() {
1564 let external = NoOpExternalProvider;
1565 let ctx = EvaluationContext::new("55001", &external, &[]);
1566 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1567 }
1568
1569 #[test]
1570 fn test_with_navigator_finds_segments_in_group() {
1571 let external = NoOpExternalProvider;
1572 let nav = MockGroupNavigator::new().with_group(
1573 &["SG4", "SG8"],
1574 0,
1575 vec![
1576 make_segment("SEQ", vec![vec!["Z98"]]),
1577 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1578 ],
1579 );
1580 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1581 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1582 assert_eq!(result.len(), 1);
1583 assert_eq!(result[0].id, "SEQ");
1584 }
1585
1586 #[test]
1587 fn test_with_navigator_qualifier_in_group() {
1588 let external = NoOpExternalProvider;
1589 let nav = MockGroupNavigator::new().with_group(
1590 &["SG4", "SG8"],
1591 0,
1592 vec![
1593 make_segment("SEQ", vec![vec!["Z98"]]),
1594 make_segment("SEQ", vec![vec!["Z01"]]),
1595 ],
1596 );
1597 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1598 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1599 assert_eq!(result.len(), 1);
1600 }
1601
1602 #[test]
1603 fn test_group_instance_count_with_navigator() {
1604 let external = NoOpExternalProvider;
1605 let nav = MockGroupNavigator::new()
1606 .with_group(
1607 &["SG4", "SG8"],
1608 0,
1609 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1610 )
1611 .with_group(
1612 &["SG4", "SG8"],
1613 1,
1614 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1615 );
1616 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1617 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1618 }
1619
1620 #[test]
1621 fn test_has_segment_in_group() {
1622 let external = NoOpExternalProvider;
1623 let nav = MockGroupNavigator::new().with_group(
1624 &["SG4", "SG8"],
1625 0,
1626 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1627 );
1628 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1629 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1630 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1631 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1632 }
1633
1634 #[test]
1637 fn test_has_qualifier() {
1638 let segments = vec![
1639 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1640 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1641 ];
1642 let external = NoOpExternalProvider;
1643 let ctx = EvaluationContext::new("11001", &external, &segments);
1644
1645 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1646 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1647 }
1648
1649 #[test]
1650 fn test_lacks_qualifier() {
1651 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1652 let external = NoOpExternalProvider;
1653 let ctx = EvaluationContext::new("11001", &external, &segments);
1654
1655 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1656 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1657 }
1658
1659 #[test]
1660 fn test_has_qualified_value() {
1661 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1662 let external = NoOpExternalProvider;
1663 let ctx = EvaluationContext::new("55001", &external, &segments);
1664
1665 assert_eq!(
1666 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1667 ConditionResult::True,
1668 );
1669 assert_eq!(
1670 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1671 ConditionResult::False,
1672 );
1673 assert_eq!(
1675 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1676 ConditionResult::Unknown,
1677 );
1678 }
1679
1680 #[test]
1681 fn test_any_group_has_qualifier() {
1682 let external = NoOpExternalProvider;
1683 let nav = MockGroupNavigator::new()
1684 .with_group(
1685 &["SG4", "SG8"],
1686 0,
1687 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1688 )
1689 .with_group(
1690 &["SG4", "SG8"],
1691 1,
1692 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1693 );
1694 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1695
1696 assert_eq!(
1697 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1698 ConditionResult::True,
1699 );
1700 assert_eq!(
1701 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1702 ConditionResult::False,
1703 );
1704 }
1705
1706 #[test]
1707 fn test_any_group_has_qualifier_fallback() {
1708 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1710 let external = NoOpExternalProvider;
1711 let ctx = EvaluationContext::new("55001", &external, &segments);
1712
1713 assert_eq!(
1714 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1715 ConditionResult::True,
1716 );
1717 }
1718
1719 #[test]
1720 fn test_any_group_has_any_qualifier() {
1721 let external = NoOpExternalProvider;
1722 let nav = MockGroupNavigator::new().with_group(
1723 &["SG4", "SG8"],
1724 0,
1725 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1726 );
1727 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1728
1729 assert_eq!(
1730 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1731 ConditionResult::True,
1732 );
1733 assert_eq!(
1734 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1735 ConditionResult::False,
1736 );
1737 }
1738
1739 #[test]
1740 fn test_any_group_has_co_occurrence() {
1741 let external = NoOpExternalProvider;
1742 let nav = MockGroupNavigator::new().with_group(
1743 &["SG4", "SG8"],
1744 0,
1745 vec![
1746 make_segment("SEQ", vec![vec!["Z01"]]),
1747 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1748 ],
1749 );
1750 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1751
1752 assert_eq!(
1753 ctx.any_group_has_co_occurrence(
1754 "SEQ",
1755 0,
1756 &["Z01"],
1757 "CCI",
1758 2,
1759 0,
1760 &["Z07"],
1761 &["SG4", "SG8"],
1762 ),
1763 ConditionResult::True,
1764 );
1765 assert_eq!(
1767 ctx.any_group_has_co_occurrence(
1768 "SEQ",
1769 0,
1770 &["Z01"],
1771 "CCI",
1772 2,
1773 0,
1774 &["ZC0"],
1775 &["SG4", "SG8"],
1776 ),
1777 ConditionResult::False,
1778 );
1779 }
1780
1781 #[test]
1784 fn test_filtered_parent_child_has_qualifier() {
1785 let external = NoOpExternalProvider;
1786 let nav = MockGroupNavigator::new()
1789 .with_group(
1790 &["SG4", "SG8"],
1791 0,
1792 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1793 )
1794 .with_group(
1795 &["SG4", "SG8"],
1796 1,
1797 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1798 )
1799 .with_child_group(
1800 &["SG4", "SG8"],
1801 0,
1802 "SG10",
1803 0,
1804 vec![make_segment("CCI", vec![vec!["Z23"]])],
1805 );
1806 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1807
1808 assert_eq!(
1810 ctx.filtered_parent_child_has_qualifier(
1811 &["SG4", "SG8"],
1812 "SEQ",
1813 0,
1814 "Z98",
1815 "SG10",
1816 "CCI",
1817 0,
1818 "Z23",
1819 ),
1820 ConditionResult::True,
1821 );
1822 assert_eq!(
1824 ctx.filtered_parent_child_has_qualifier(
1825 &["SG4", "SG8"],
1826 "SEQ",
1827 0,
1828 "Z01",
1829 "SG10",
1830 "CCI",
1831 0,
1832 "Z23",
1833 ),
1834 ConditionResult::False,
1835 );
1836 assert_eq!(
1838 ctx.filtered_parent_child_has_qualifier(
1839 &["SG4", "SG8"],
1840 "SEQ",
1841 0,
1842 "Z98",
1843 "SG10",
1844 "CCI",
1845 0,
1846 "Z99",
1847 ),
1848 ConditionResult::False,
1849 );
1850 }
1851
1852 #[test]
1853 fn test_filtered_parent_child_fallback() {
1854 let segments = vec![
1856 make_segment("SEQ", vec![vec!["Z98"]]),
1857 make_segment("CCI", vec![vec!["Z23"]]),
1858 ];
1859 let external = NoOpExternalProvider;
1860 let ctx = EvaluationContext::new("55001", &external, &segments);
1861
1862 assert_eq!(
1863 ctx.filtered_parent_child_has_qualifier(
1864 &["SG4", "SG8"],
1865 "SEQ",
1866 0,
1867 "Z98",
1868 "SG10",
1869 "CCI",
1870 0,
1871 "Z23",
1872 ),
1873 ConditionResult::True,
1874 );
1875 assert_eq!(
1877 ctx.filtered_parent_child_has_qualifier(
1878 &["SG4", "SG8"],
1879 "SEQ",
1880 0,
1881 "Z98",
1882 "SG10",
1883 "CCI",
1884 0,
1885 "Z99",
1886 ),
1887 ConditionResult::False,
1888 );
1889 }
1890
1891 #[test]
1892 fn test_any_group_has_qualifier_without() {
1893 let external = NoOpExternalProvider;
1894 let nav = MockGroupNavigator::new()
1897 .with_group(
1898 &["SG4", "SG8"],
1899 0,
1900 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1901 )
1902 .with_group(
1903 &["SG4", "SG8"],
1904 1,
1905 vec![
1906 make_segment("SEQ", vec![vec!["Z01"]]),
1907 make_segment("CCI", vec![vec!["11"]]),
1908 ],
1909 );
1910 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1911
1912 assert_eq!(
1914 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1915 ConditionResult::True,
1916 );
1917 assert_eq!(
1919 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
1920 ConditionResult::False,
1921 );
1922 assert_eq!(
1924 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
1925 ConditionResult::False,
1926 );
1927 }
1928
1929 #[test]
1930 fn test_any_group_has_qualifier_without_fallback() {
1931 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
1932 let external = NoOpExternalProvider;
1933 let ctx = EvaluationContext::new("55001", &external, &segments);
1934
1935 assert_eq!(
1937 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1938 ConditionResult::True,
1939 );
1940 }
1941
1942 #[test]
1943 fn test_collect_group_values() {
1944 let external = NoOpExternalProvider;
1945 let nav = MockGroupNavigator::new()
1946 .with_group(
1947 &["SG4", "SG6"],
1948 0,
1949 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
1950 )
1951 .with_group(
1952 &["SG4", "SG6"],
1953 1,
1954 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
1955 );
1956 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1957
1958 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
1959 assert_eq!(values.len(), 2);
1960 assert_eq!(values[0], (0, "REF001".to_string()));
1961 assert_eq!(values[1], (1, "REF002".to_string()));
1962 }
1963
1964 #[test]
1965 fn test_groups_share_qualified_value() {
1966 let external = NoOpExternalProvider;
1967 let nav = MockGroupNavigator::new()
1971 .with_group(
1972 &["SG4", "SG6"],
1973 0,
1974 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1975 )
1976 .with_group(
1977 &["SG4", "SG8"],
1978 0,
1979 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1980 )
1981 .with_group(
1982 &["SG4", "SG8"],
1983 1,
1984 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
1985 );
1986 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1987
1988 assert_eq!(
1990 ctx.groups_share_qualified_value(
1991 "RFF",
1992 0,
1993 "Z49",
1994 0,
1995 1,
1996 &["SG4", "SG6"],
1997 "SEQ",
1998 1,
1999 0,
2000 &["SG4", "SG8"],
2001 ),
2002 ConditionResult::True,
2003 );
2004 }
2005
2006 #[test]
2007 fn test_groups_share_qualified_value_no_match() {
2008 let external = NoOpExternalProvider;
2009 let nav = MockGroupNavigator::new()
2010 .with_group(
2011 &["SG4", "SG6"],
2012 0,
2013 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
2014 )
2015 .with_group(
2016 &["SG4", "SG8"],
2017 0,
2018 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
2019 );
2020 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2021
2022 assert_eq!(
2024 ctx.groups_share_qualified_value(
2025 "RFF",
2026 0,
2027 "Z49",
2028 0,
2029 1,
2030 &["SG4", "SG6"],
2031 "SEQ",
2032 1,
2033 0,
2034 &["SG4", "SG8"],
2035 ),
2036 ConditionResult::False,
2037 );
2038 }
2039
2040 #[test]
2041 fn test_groups_share_qualified_value_no_source() {
2042 let external = NoOpExternalProvider;
2043 let nav = MockGroupNavigator::new()
2045 .with_group(
2046 &["SG4", "SG6"],
2047 0,
2048 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
2049 )
2050 .with_group(
2051 &["SG4", "SG8"],
2052 0,
2053 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
2054 );
2055 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
2056
2057 assert_eq!(
2059 ctx.groups_share_qualified_value(
2060 "RFF",
2061 0,
2062 "Z49",
2063 0,
2064 1,
2065 &["SG4", "SG6"],
2066 "SEQ",
2067 1,
2068 0,
2069 &["SG4", "SG8"],
2070 ),
2071 ConditionResult::Unknown,
2072 );
2073 }
2074
2075 #[test]
2076 fn test_groups_share_qualified_value_fallback() {
2077 let segments = vec![
2079 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
2080 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
2081 ];
2082 let external = NoOpExternalProvider;
2083 let ctx = EvaluationContext::new("55001", &external, &segments);
2084
2085 assert_eq!(
2086 ctx.groups_share_qualified_value(
2087 "RFF",
2088 0,
2089 "Z49",
2090 0,
2091 1,
2092 &["SG4", "SG6"],
2093 "SEQ",
2094 1,
2095 0,
2096 &["SG4", "SG8"],
2097 ),
2098 ConditionResult::True,
2099 );
2100 }
2101
2102 #[test]
2103 fn test_child_group_pass_throughs_no_navigator() {
2104 let external = NoOpExternalProvider;
2105 let ctx = EvaluationContext::new("55001", &external, &[]);
2106
2107 assert_eq!(
2108 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
2109 0
2110 );
2111 assert!(ctx
2112 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
2113 .is_empty());
2114 assert_eq!(
2115 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
2116 None
2117 );
2118 }
2119
2120 #[test]
2123 fn test_has_segment_matching_found() {
2124 let segments = vec![
2125 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
2126 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
2127 ];
2128 let external = NoOpExternalProvider;
2129 let ctx = EvaluationContext::new("55001", &external, &segments);
2130
2131 assert_eq!(
2132 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
2133 ConditionResult::True,
2134 );
2135 }
2136
2137 #[test]
2138 fn test_has_segment_matching_not_found() {
2139 let segments = vec![make_segment(
2140 "STS",
2141 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2142 )];
2143 let external = NoOpExternalProvider;
2144 let ctx = EvaluationContext::new("55001", &external, &segments);
2145
2146 assert_eq!(
2147 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2148 ConditionResult::False,
2149 );
2150 }
2151
2152 #[test]
2153 fn test_has_segment_matching_no_segments() {
2154 let external = NoOpExternalProvider;
2155 let ctx = EvaluationContext::new("55001", &external, &[]);
2156
2157 assert_eq!(
2158 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2159 ConditionResult::Unknown,
2160 );
2161 }
2162
2163 #[test]
2164 fn test_has_segment_matching_in_group() {
2165 let nav = MockGroupNavigator::new()
2166 .with_group(
2167 &["SG4"],
2168 0,
2169 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2170 )
2171 .with_group(
2172 &["SG4"],
2173 1,
2174 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2175 );
2176 let segments = vec![
2177 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2178 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2179 ];
2180 let external = NoOpExternalProvider;
2181 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2182
2183 assert_eq!(
2184 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2185 ConditionResult::True,
2186 );
2187 }
2188
2189 #[test]
2192 fn test_dtm_ge() {
2193 let segments = vec![make_segment(
2194 "DTM",
2195 vec![vec!["137", "202601010000", "303"]],
2196 )];
2197 let external = NoOpExternalProvider;
2198 let ctx = EvaluationContext::new("55001", &external, &segments);
2199
2200 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2201 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2202 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2203 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2204 }
2205
2206 #[test]
2207 fn test_dtm_lt() {
2208 let segments = vec![make_segment(
2209 "DTM",
2210 vec![vec!["137", "202601010000", "303"]],
2211 )];
2212 let external = NoOpExternalProvider;
2213 let ctx = EvaluationContext::new("55001", &external, &segments);
2214
2215 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2216 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2217 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2218 }
2219
2220 #[test]
2221 fn test_dtm_le() {
2222 let segments = vec![make_segment(
2223 "DTM",
2224 vec![vec!["137", "202601010000", "303"]],
2225 )];
2226 let external = NoOpExternalProvider;
2227 let ctx = EvaluationContext::new("55001", &external, &segments);
2228
2229 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2230 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2231 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2232 }
2233
2234 #[test]
2237 fn test_count_qualified_in_group() {
2238 let nav = MockGroupNavigator::new()
2239 .with_group(
2240 &["SG4", "SG8"],
2241 0,
2242 vec![
2243 make_segment("CCI", vec![vec!["Z23"]]),
2244 make_segment("CCI", vec![vec!["Z30"]]),
2245 ],
2246 )
2247 .with_group(
2248 &["SG4", "SG8"],
2249 1,
2250 vec![make_segment("CCI", vec![vec!["Z23"]])],
2251 );
2252 let segments = vec![
2253 make_segment("CCI", vec![vec!["Z23"]]),
2254 make_segment("CCI", vec![vec!["Z30"]]),
2255 make_segment("CCI", vec![vec!["Z23"]]),
2256 ];
2257 let external = NoOpExternalProvider;
2258 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2259
2260 assert_eq!(
2261 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2262 2
2263 );
2264 assert_eq!(
2265 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2266 1
2267 );
2268 assert_eq!(
2269 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2270 0
2271 );
2272 }
2273
2274 #[test]
2275 fn test_count_in_group() {
2276 let nav = MockGroupNavigator::new()
2277 .with_group(
2278 &["SG4", "SG8"],
2279 0,
2280 vec![
2281 make_segment("SEQ", vec![vec!["Z98"]]),
2282 make_segment("CCI", vec![vec!["Z23"]]),
2283 ],
2284 )
2285 .with_group(
2286 &["SG4", "SG8"],
2287 1,
2288 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2289 );
2290 let segments = vec![
2291 make_segment("SEQ", vec![vec!["Z98"]]),
2292 make_segment("CCI", vec![vec!["Z23"]]),
2293 make_segment("SEQ", vec![vec!["Z01"]]),
2294 ];
2295 let external = NoOpExternalProvider;
2296 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2297
2298 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2299 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2300 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2301 }
2302
2303 #[test]
2304 fn test_count_fallback_no_navigator() {
2305 let segments = vec![
2306 make_segment("CCI", vec![vec!["Z23"]]),
2307 make_segment("CCI", vec![vec!["Z30"]]),
2308 make_segment("CCI", vec![vec!["Z23"]]),
2309 ];
2310 let external = NoOpExternalProvider;
2311 let ctx = EvaluationContext::new("55001", &external, &segments);
2312
2313 assert_eq!(
2315 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2316 2
2317 );
2318 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2319 }
2320
2321 #[test]
2324 fn format_check_prefers_resolved_value() {
2325 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2327 let external = NoOpExternalProvider;
2328 let ctx = EvaluationContext::new("55001", &external, &segments)
2329 .with_resolved(Some("CORRECT"), None);
2330
2331 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2332 assert_eq!(result, ConditionResult::True);
2333 }
2334
2335 #[test]
2336 fn format_check_falls_back_to_segment_search() {
2337 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2339 let external = NoOpExternalProvider;
2340 let ctx = EvaluationContext::new("55001", &external, &segments);
2341
2342 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2343 assert_eq!(result, ConditionResult::True);
2344 }
2345
2346 #[test]
2347 fn format_check_returns_unknown_when_segment_absent() {
2348 let segments: Vec<OwnedSegment> = vec![];
2350 let external = NoOpExternalProvider;
2351 let ctx = EvaluationContext::new("55001", &external, &segments);
2352
2353 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2354 assert_eq!(result, ConditionResult::Unknown);
2355 }
2356
2357 #[test]
2358 fn format_check_returns_unknown_when_multiple_segments() {
2359 let segments = vec![
2363 make_segment("DTM", vec![vec!["92", "OK"]]),
2364 make_segment("DTM", vec![vec!["163", "BAD"]]),
2365 ];
2366 let external = NoOpExternalProvider;
2367 let ctx = EvaluationContext::new("55001", &external, &segments);
2368
2369 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2370 assert_eq!(result, ConditionResult::Unknown);
2371 }
2372
2373 #[test]
2374 fn format_check_qualified_prefers_resolved_value() {
2375 let segments = vec![
2377 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2378 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2379 ];
2380 let external = NoOpExternalProvider;
2381 let ctx = EvaluationContext::new("55001", &external, &segments)
2382 .with_resolved(Some("CORRECT"), None);
2383
2384 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2385 ConditionResult::from(v == "CORRECT")
2386 });
2387 assert_eq!(result, ConditionResult::True);
2388 }
2389
2390 #[test]
2391 fn format_check_qualified_falls_back_to_qualified_search() {
2392 let segments = vec![
2394 make_segment("DTM", vec![vec!["92", "2200"]]),
2395 make_segment("DTM", vec![vec!["163", "0800"]]),
2396 ];
2397 let external = NoOpExternalProvider;
2398 let ctx = EvaluationContext::new("55001", &external, &segments);
2399
2400 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2402 ConditionResult::from(v == "0800")
2403 });
2404 assert_eq!(result, ConditionResult::True);
2405
2406 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2408 ConditionResult::from(v == "2200")
2409 });
2410 assert_eq!(result2, ConditionResult::False);
2411 }
2412
2413 #[test]
2416 fn self_segment_value_prefers_resolved_segment() {
2417 let segments = vec![
2421 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2422 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2423 ];
2424 let external = NoOpExternalProvider;
2425 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2426 let ctx = EvaluationContext::new("55001", &external, &segments)
2427 .with_resolved(None, Some(&resolved_elements));
2428
2429 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2430 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2431 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2432 }
2433
2434 #[test]
2435 fn self_segment_value_falls_back_when_no_resolved_segment() {
2436 let segments = vec![
2438 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2439 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2440 ];
2441 let external = NoOpExternalProvider;
2442 let ctx = EvaluationContext::new("55001", &external, &segments);
2443
2444 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2445 }
2446
2447 #[test]
2448 fn self_segment_value_missing_returns_none() {
2449 let segments: Vec<OwnedSegment> = vec![];
2450 let external = NoOpExternalProvider;
2451 let ctx = EvaluationContext::new("55001", &external, &segments);
2452
2453 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2454 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2455 }
2456
2457 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2461 let mut segments = Vec::new();
2462 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2463 segments.push(OwnedSegment {
2464 id: "SEQ".to_string(),
2465 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2466 segment_number: (2 * i + 1) as u32,
2467 });
2468 segments.push(OwnedSegment {
2469 id: "PIA".to_string(),
2470 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2471 segment_number: (2 * i + 2) as u32,
2472 });
2473 }
2474 segments
2475 }
2476
2477 #[test]
2478 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2479 let segments = make_sg8_sequence(&[
2481 ("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"), ]);
2486 let external = NoOpExternalProvider;
2487 let seq = &segments[0].elements;
2489 let ctx =
2490 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2491
2492 let n = ctx
2493 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2494 assert_eq!(n, 2);
2495 }
2496
2497 #[test]
2498 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2499 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2500 let external = NoOpExternalProvider;
2501 let ctx = EvaluationContext::new("55001", &external, &segments);
2502 let n = ctx
2503 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2504 assert_eq!(n, 0);
2505 }
2506
2507 #[test]
2508 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2509 let segments = make_sg8_sequence(&[
2510 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2513 let external = NoOpExternalProvider;
2514 let seq = &segments[0].elements;
2515 let ctx =
2516 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2517
2518 assert_eq!(
2519 ctx.count_sg8_obis_matches(
2520 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2521 ),
2522 1
2523 );
2524 assert_eq!(
2525 ctx.count_sg8_obis_matches(
2526 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2527 ),
2528 1
2529 );
2530 }
2531}