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 scoped_find_segments_in_with_qualifier(
245 &self,
246 group_id: &str,
247 tag: &str,
248 element_index: usize,
249 qualifier: &str,
250 ) -> Vec<OwnedSegment> {
251 self.scoped_find_segments_in(group_id, tag)
252 .into_iter()
253 .filter(|s| {
254 s.elements
255 .get(element_index)
256 .and_then(|e| e.first())
257 .is_some_and(|v| v == qualifier)
258 })
259 .collect()
260 }
261
262 pub fn scoped_find_segments_with_qualifier(
265 &self,
266 tag: &str,
267 element_index: usize,
268 qualifier: &str,
269 ) -> Vec<OwnedSegment> {
270 self.scoped_find_segments(tag)
271 .into_iter()
272 .filter(|s| {
273 s.elements
274 .get(element_index)
275 .and_then(|e| e.first())
276 .is_some_and(|v| v == qualifier)
277 })
278 .collect()
279 }
280
281 pub fn format_check(
291 &self,
292 tag: &str,
293 elem: usize,
294 comp: usize,
295 validate: impl FnOnce(&str) -> ConditionResult,
296 ) -> ConditionResult {
297 if let Some(val) = self.resolved_value {
299 return validate(val);
300 }
301 let segs = self.find_segments(tag);
306 if segs.len() != 1 {
307 return ConditionResult::Unknown;
308 }
309 match segs[0]
310 .elements
311 .get(elem)
312 .and_then(|e| e.get(comp))
313 .map(|s| s.as_str())
314 {
315 Some(val) => validate(val),
316 None => ConditionResult::Unknown,
317 }
318 }
319
320 pub fn format_check_qualified(
329 &self,
330 tag: &str,
331 qual_elem: usize,
332 qualifier: &str,
333 val_elem: usize,
334 val_comp: usize,
335 validate: impl FnOnce(&str) -> ConditionResult,
336 ) -> ConditionResult {
337 if let Some(val) = self.resolved_value {
338 return validate(val);
339 }
340 let segs = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
341 if segs.len() != 1 {
342 return ConditionResult::Unknown;
343 }
344 match segs[0]
345 .elements
346 .get(val_elem)
347 .and_then(|e| e.get(val_comp))
348 .map(|s| s.as_str())
349 {
350 Some(val) => validate(val),
351 None => ConditionResult::Unknown,
352 }
353 }
354
355 pub fn self_segment_value(&self, tag: &str, elem: usize, comp: usize) -> Option<&str> {
367 if let Some(elements) = self.resolved_segment {
368 return elements
369 .get(elem)
370 .and_then(|e| e.get(comp))
371 .map(|s| s.as_str());
372 }
373 self.segments
374 .iter()
375 .find(|s| s.id == tag)
376 .and_then(|s| s.elements.get(elem))
377 .and_then(|e| e.get(comp))
378 .map(|s| s.as_str())
379 }
380
381 pub fn self_segment_value_equals(
385 &self,
386 tag: &str,
387 elem: usize,
388 comp: usize,
389 expected: &str,
390 ) -> bool {
391 self.self_segment_value(tag, elem, comp)
392 .is_some_and(|v| v == expected)
393 }
394
395 pub fn has_segment(&self, segment_id: &str) -> bool {
397 self.segments.iter().any(|s| s.id == segment_id)
398 }
399
400 pub fn count_sg8_obis_matches<F>(&self, obis_pred: F) -> usize
422 where
423 F: Fn(&str) -> bool,
424 {
425 let resolved = match self.resolved_segment {
426 Some(s) => s,
427 None => return 0,
428 };
429 let cur_qual = match resolved.first().and_then(|e| e.first()) {
430 Some(v) => v.as_str(),
431 None => return 0,
432 };
433 if !matches!(cur_qual, "Z02" | "ZA1" | "ZA2") {
434 return 0;
435 }
436 let cur_zid = match resolved.get(1).and_then(|e| e.first()) {
437 Some(v) => v.as_str(),
438 None => return 0,
439 };
440
441 let mut count = 0usize;
442 let mut in_matching_seq = false;
443 for seg in self.segments {
444 if seg.id == "SEQ" {
445 let qual = seg
446 .elements
447 .first()
448 .and_then(|e| e.first())
449 .map(String::as_str);
450 let zid = seg
451 .elements
452 .get(1)
453 .and_then(|e| e.first())
454 .map(String::as_str);
455 in_matching_seq = qual == Some(cur_qual) && zid == Some(cur_zid);
456 } else if seg.id == "PIA" && in_matching_seq {
457 let p_qual = seg
458 .elements
459 .first()
460 .and_then(|e| e.first())
461 .map(String::as_str);
462 if p_qual == Some("5") {
463 if let Some(obis) = seg.elements.get(1).and_then(|e| e.first()) {
464 if obis_pred(obis) {
465 count += 1;
466 }
467 }
468 }
469 }
470 }
471 count
472 }
473
474 pub fn find_segments_in_group(
477 &self,
478 segment_id: &str,
479 group_path: &[&str],
480 instance_index: usize,
481 ) -> Vec<OwnedSegment> {
482 match self.navigator {
483 Some(nav) => nav.find_segments_in_group(segment_id, group_path, instance_index),
484 None => Vec::new(),
485 }
486 }
487
488 pub fn find_segments_with_qualifier_in_group(
491 &self,
492 segment_id: &str,
493 element_index: usize,
494 qualifier: &str,
495 group_path: &[&str],
496 instance_index: usize,
497 ) -> Vec<OwnedSegment> {
498 match self.navigator {
499 Some(nav) => nav.find_segments_with_qualifier_in_group(
500 segment_id,
501 element_index,
502 qualifier,
503 group_path,
504 instance_index,
505 ),
506 None => Vec::new(),
507 }
508 }
509
510 pub fn has_segment_in_group(
513 &self,
514 segment_id: &str,
515 group_path: &[&str],
516 instance_index: usize,
517 ) -> bool {
518 !self
519 .find_segments_in_group(segment_id, group_path, instance_index)
520 .is_empty()
521 }
522
523 pub fn group_instance_count(&self, group_path: &[&str]) -> usize {
526 match self.navigator {
527 Some(nav) => nav.group_instance_count(group_path),
528 None => 0,
529 }
530 }
531
532 pub fn child_group_instance_count(
535 &self,
536 parent_path: &[&str],
537 parent_instance: usize,
538 child_group_id: &str,
539 ) -> usize {
540 match self.navigator {
541 Some(nav) => {
542 nav.child_group_instance_count(parent_path, parent_instance, child_group_id)
543 }
544 None => 0,
545 }
546 }
547
548 pub fn find_segments_in_child_group(
551 &self,
552 segment_id: &str,
553 parent_path: &[&str],
554 parent_instance: usize,
555 child_group_id: &str,
556 child_instance: usize,
557 ) -> Vec<OwnedSegment> {
558 match self.navigator {
559 Some(nav) => nav.find_segments_in_child_group(
560 segment_id,
561 parent_path,
562 parent_instance,
563 child_group_id,
564 child_instance,
565 ),
566 None => Vec::new(),
567 }
568 }
569
570 pub fn extract_value_in_group(
573 &self,
574 segment_id: &str,
575 element_index: usize,
576 component_index: usize,
577 group_path: &[&str],
578 instance_index: usize,
579 ) -> Option<String> {
580 self.navigator?.extract_value_in_group(
581 segment_id,
582 element_index,
583 component_index,
584 group_path,
585 instance_index,
586 )
587 }
588
589 pub fn has_qualifier(
595 &self,
596 tag: &str,
597 element_index: usize,
598 qualifier: &str,
599 ) -> ConditionResult {
600 ConditionResult::from(
601 !self
602 .find_segments_with_qualifier(tag, element_index, qualifier)
603 .is_empty(),
604 )
605 }
606
607 pub fn lacks_qualifier(
610 &self,
611 tag: &str,
612 element_index: usize,
613 qualifier: &str,
614 ) -> ConditionResult {
615 ConditionResult::from(
616 self.find_segments_with_qualifier(tag, element_index, qualifier)
617 .is_empty(),
618 )
619 }
620
621 pub fn has_qualified_value(
626 &self,
627 tag: &str,
628 qual_elem: usize,
629 qualifier: &str,
630 value_elem: usize,
631 value_comp: usize,
632 values: &[&str],
633 ) -> ConditionResult {
634 let segments = self.find_segments_with_qualifier(tag, qual_elem, qualifier);
635 if segments.is_empty() {
636 return ConditionResult::Unknown;
637 }
638 for seg in &segments {
639 if let Some(v) = seg
640 .elements
641 .get(value_elem)
642 .and_then(|e| e.get(value_comp))
643 .map(|s| s.as_str())
644 {
645 if values.contains(&v) {
646 return ConditionResult::True;
647 }
648 }
649 }
650 ConditionResult::False
651 }
652
653 pub fn any_group_has_qualifier(
659 &self,
660 tag: &str,
661 element_index: usize,
662 qualifier: &str,
663 group_path: &[&str],
664 ) -> ConditionResult {
665 let instance_count = self.group_instance_count(group_path);
666 if instance_count > 0 {
667 for i in 0..instance_count {
668 if !self
669 .find_segments_with_qualifier_in_group(
670 tag,
671 element_index,
672 qualifier,
673 group_path,
674 i,
675 )
676 .is_empty()
677 {
678 return ConditionResult::True;
679 }
680 }
681 return ConditionResult::False;
682 }
683 self.has_qualifier(tag, element_index, qualifier)
685 }
686
687 pub fn any_group_has_any_qualifier(
693 &self,
694 tag: &str,
695 element_index: usize,
696 qualifiers: &[&str],
697 group_path: &[&str],
698 ) -> ConditionResult {
699 let instance_count = self.group_instance_count(group_path);
700 if instance_count > 0 {
701 for i in 0..instance_count {
702 let segs = self.find_segments_in_group(tag, group_path, i);
703 if segs.iter().any(|seg| {
704 seg.elements
705 .get(element_index)
706 .and_then(|e| e.first())
707 .map(|s| s.as_str())
708 .is_some_and(|v| qualifiers.contains(&v))
709 }) {
710 return ConditionResult::True;
711 }
712 }
713 return ConditionResult::False;
714 }
715 let found = self.find_segments(tag).iter().any(|seg| {
717 seg.elements
718 .get(element_index)
719 .and_then(|e| e.first())
720 .map(|s| s.as_str())
721 .is_some_and(|v| qualifiers.contains(&v))
722 });
723 ConditionResult::from(found)
724 }
725
726 pub fn any_group_has_qualified_value(
732 &self,
733 tag: &str,
734 qual_elem: usize,
735 qualifier: &str,
736 value_elem: usize,
737 value_comp: usize,
738 values: &[&str],
739 group_path: &[&str],
740 ) -> ConditionResult {
741 let instance_count = self.group_instance_count(group_path);
742 if instance_count > 0 {
743 for i in 0..instance_count {
744 let segs = self.find_segments_with_qualifier_in_group(
745 tag, qual_elem, qualifier, group_path, i,
746 );
747 for seg in &segs {
748 if seg
749 .elements
750 .get(value_elem)
751 .and_then(|e| e.get(value_comp))
752 .map(|s| s.as_str())
753 .is_some_and(|v| values.contains(&v))
754 {
755 return ConditionResult::True;
756 }
757 }
758 }
759 return ConditionResult::False;
760 }
761 self.has_qualified_value(tag, qual_elem, qualifier, value_elem, value_comp, values)
763 }
764
765 #[allow(clippy::too_many_arguments)]
774 pub fn filtered_parent_child_has_qualifier(
775 &self,
776 parent_path: &[&str],
777 parent_tag: &str,
778 parent_elem: usize,
779 parent_qual: &str,
780 child_group_id: &str,
781 child_tag: &str,
782 child_elem: usize,
783 child_qual: &str,
784 ) -> ConditionResult {
785 let parent_count = self.group_instance_count(parent_path);
786 if parent_count > 0 {
787 for pi in 0..parent_count {
788 let parent_segs = self.find_segments_with_qualifier_in_group(
790 parent_tag,
791 parent_elem,
792 parent_qual,
793 parent_path,
794 pi,
795 );
796 if parent_segs.is_empty() {
797 continue;
798 }
799 let child_count = self.child_group_instance_count(parent_path, pi, child_group_id);
801 for ci in 0..child_count {
802 let child_segs = self.find_segments_in_child_group(
803 child_tag,
804 parent_path,
805 pi,
806 child_group_id,
807 ci,
808 );
809 if child_segs.iter().any(|s| {
810 s.elements
811 .get(child_elem)
812 .and_then(|e| e.first())
813 .is_some_and(|v| v == child_qual)
814 }) {
815 return ConditionResult::True;
816 }
817 }
818 }
819 return ConditionResult::False;
820 }
821 let has_parent = !self
823 .find_segments_with_qualifier(parent_tag, parent_elem, parent_qual)
824 .is_empty();
825 let has_child = !self
826 .find_segments_with_qualifier(child_tag, child_elem, child_qual)
827 .is_empty();
828 ConditionResult::from(has_parent && has_child)
829 }
830
831 #[allow(clippy::too_many_arguments)]
837 pub fn any_group_has_qualifier_without(
838 &self,
839 present_tag: &str,
840 present_elem: usize,
841 present_qual: &str,
842 absent_tag: &str,
843 absent_elem: usize,
844 absent_qual: &str,
845 group_path: &[&str],
846 ) -> ConditionResult {
847 let instance_count = self.group_instance_count(group_path);
848 if instance_count > 0 {
849 for i in 0..instance_count {
850 let has_present = !self
851 .find_segments_with_qualifier_in_group(
852 present_tag,
853 present_elem,
854 present_qual,
855 group_path,
856 i,
857 )
858 .is_empty();
859 let has_absent = !self
860 .find_segments_with_qualifier_in_group(
861 absent_tag,
862 absent_elem,
863 absent_qual,
864 group_path,
865 i,
866 )
867 .is_empty();
868 if has_present && !has_absent {
869 return ConditionResult::True;
870 }
871 }
872 return ConditionResult::False;
873 }
874 let has_present = !self
876 .find_segments_with_qualifier(present_tag, present_elem, present_qual)
877 .is_empty();
878 let has_absent = !self
879 .find_segments_with_qualifier(absent_tag, absent_elem, absent_qual)
880 .is_empty();
881 ConditionResult::from(has_present && !has_absent)
882 }
883
884 pub fn collect_group_values(
888 &self,
889 tag: &str,
890 elem: usize,
891 comp: usize,
892 group_path: &[&str],
893 ) -> Vec<(usize, String)> {
894 let instance_count = self.group_instance_count(group_path);
895 let mut results = Vec::new();
896 for i in 0..instance_count {
897 if let Some(val) = self
898 .navigator
899 .and_then(|nav| nav.extract_value_in_group(tag, elem, comp, group_path, i))
900 {
901 if !val.is_empty() {
902 results.push((i, val));
903 }
904 }
905 }
906 results
907 }
908
909 #[allow(clippy::too_many_arguments)]
918 pub fn groups_share_qualified_value(
919 &self,
920 source_tag: &str,
921 source_qual_elem: usize,
922 source_qual: &str,
923 source_value_elem: usize,
924 source_value_comp: usize,
925 source_path: &[&str],
926 target_tag: &str,
927 target_elem: usize,
928 target_comp: usize,
929 target_path: &[&str],
930 ) -> ConditionResult {
931 let source_count = self.group_instance_count(source_path);
932 let target_count = self.group_instance_count(target_path);
933 if source_count > 0 && target_count > 0 {
934 let mut source_values = Vec::new();
936 for si in 0..source_count {
937 let segs = self.find_segments_with_qualifier_in_group(
938 source_tag,
939 source_qual_elem,
940 source_qual,
941 source_path,
942 si,
943 );
944 for seg in &segs {
945 if let Some(val) = seg
946 .elements
947 .get(source_value_elem)
948 .and_then(|e| e.get(source_value_comp))
949 {
950 if !val.is_empty() {
951 source_values.push(val.clone());
952 }
953 }
954 }
955 }
956 if source_values.is_empty() {
957 return ConditionResult::Unknown;
958 }
959 let target_values =
961 self.collect_group_values(target_tag, target_elem, target_comp, target_path);
962 for (_, tv) in &target_values {
963 if source_values.iter().any(|sv| sv == tv) {
964 return ConditionResult::True;
965 }
966 }
967 return ConditionResult::False;
968 }
969 let source_segs =
971 self.find_segments_with_qualifier(source_tag, source_qual_elem, source_qual);
972 let source_vals: Vec<&str> = source_segs
973 .iter()
974 .filter_map(|s| {
975 s.elements
976 .get(source_value_elem)
977 .and_then(|e| e.get(source_value_comp))
978 .map(|v| v.as_str())
979 })
980 .filter(|v| !v.is_empty())
981 .collect();
982 if source_vals.is_empty() {
983 return ConditionResult::Unknown;
984 }
985 let target_segs = self.find_segments(target_tag);
986 let has_match = target_segs.iter().any(|s| {
987 s.elements
988 .get(target_elem)
989 .and_then(|e| e.get(target_comp))
990 .map(|v| v.as_str())
991 .is_some_and(|v| source_vals.contains(&v))
992 });
993 ConditionResult::from(has_match)
994 }
995
996 #[allow(clippy::too_many_arguments)]
1005 pub fn any_group_has_co_occurrence(
1006 &self,
1007 tag_a: &str,
1008 elem_a: usize,
1009 quals_a: &[&str],
1010 tag_b: &str,
1011 elem_b: usize,
1012 comp_b: usize,
1013 vals_b: &[&str],
1014 group_path: &[&str],
1015 ) -> ConditionResult {
1016 let instance_count = self.group_instance_count(group_path);
1017 if instance_count > 0 {
1018 for i in 0..instance_count {
1019 let a_present = self
1020 .find_segments_in_group(tag_a, group_path, i)
1021 .iter()
1022 .any(|seg| {
1023 seg.elements
1024 .get(elem_a)
1025 .and_then(|e| e.first())
1026 .map(|s| s.as_str())
1027 .is_some_and(|v| quals_a.contains(&v))
1028 });
1029 let b_present = self
1030 .find_segments_in_group(tag_b, group_path, i)
1031 .iter()
1032 .any(|seg| {
1033 seg.elements
1034 .get(elem_b)
1035 .and_then(|e| e.get(comp_b))
1036 .map(|s| s.as_str())
1037 .is_some_and(|v| vals_b.contains(&v))
1038 });
1039 if a_present && b_present {
1040 return ConditionResult::True;
1041 }
1042 }
1043 return ConditionResult::False;
1044 }
1045 let a_found = self.find_segments(tag_a).iter().any(|seg| {
1047 seg.elements
1048 .get(elem_a)
1049 .and_then(|e| e.first())
1050 .map(|s| s.as_str())
1051 .is_some_and(|v| quals_a.contains(&v))
1052 });
1053 if !a_found {
1054 return ConditionResult::False;
1055 }
1056 let b_found = self.find_segments(tag_b).iter().any(|seg| {
1057 seg.elements
1058 .get(elem_b)
1059 .and_then(|e| e.get(comp_b))
1060 .map(|s| s.as_str())
1061 .is_some_and(|v| vals_b.contains(&v))
1062 });
1063 ConditionResult::from(b_found)
1064 }
1065
1066 pub fn has_segment_matching(
1076 &self,
1077 tag: &str,
1078 checks: &[(usize, usize, &str)],
1079 ) -> ConditionResult {
1080 let segments = self.find_segments(tag);
1081 if segments.is_empty() {
1082 return ConditionResult::Unknown;
1083 }
1084 let found = segments.iter().any(|seg| {
1085 checks.iter().all(|(elem, comp, val)| {
1086 seg.elements
1087 .get(*elem)
1088 .and_then(|e| e.get(*comp))
1089 .is_some_and(|v| v == val)
1090 })
1091 });
1092 ConditionResult::from(found)
1093 }
1094
1095 pub fn has_segment_matching_in_group(
1100 &self,
1101 tag: &str,
1102 checks: &[(usize, usize, &str)],
1103 group_path: &[&str],
1104 ) -> ConditionResult {
1105 let instance_count = self.group_instance_count(group_path);
1106 if instance_count > 0 {
1107 for i in 0..instance_count {
1108 let segs = self.find_segments_in_group(tag, group_path, i);
1109 if segs.iter().any(|seg| {
1110 checks.iter().all(|(elem, comp, val)| {
1111 seg.elements
1112 .get(*elem)
1113 .and_then(|e| e.get(*comp))
1114 .is_some_and(|v| v == val)
1115 })
1116 }) {
1117 return ConditionResult::True;
1118 }
1119 }
1120 return ConditionResult::False;
1121 }
1122 self.has_segment_matching(tag, checks)
1124 }
1125
1126 pub fn dtm_ge(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1133 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1134 match segs.first() {
1135 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1136 Some(val) => ConditionResult::from(val.as_str() >= threshold),
1137 None => ConditionResult::Unknown,
1138 },
1139 None => ConditionResult::Unknown,
1140 }
1141 }
1142
1143 pub fn dtm_lt(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1145 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1146 match segs.first() {
1147 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1148 Some(val) => ConditionResult::from((val.as_str()) < threshold),
1149 None => ConditionResult::Unknown,
1150 },
1151 None => ConditionResult::Unknown,
1152 }
1153 }
1154
1155 pub fn dtm_le(&self, qualifier: &str, threshold: &str) -> ConditionResult {
1157 let segs = self.find_segments_with_qualifier("DTM", 0, qualifier);
1158 match segs.first() {
1159 Some(dtm) => match dtm.elements.first().and_then(|e| e.get(1)) {
1160 Some(val) => ConditionResult::from(val.as_str() <= threshold),
1161 None => ConditionResult::Unknown,
1162 },
1163 None => ConditionResult::Unknown,
1164 }
1165 }
1166
1167 pub fn count_qualified_in_group(
1174 &self,
1175 tag: &str,
1176 element_index: usize,
1177 qualifier: &str,
1178 group_path: &[&str],
1179 ) -> usize {
1180 let instance_count = self.group_instance_count(group_path);
1181 if instance_count > 0 {
1182 let mut total = 0;
1183 for i in 0..instance_count {
1184 total += self
1185 .find_segments_with_qualifier_in_group(
1186 tag,
1187 element_index,
1188 qualifier,
1189 group_path,
1190 i,
1191 )
1192 .len();
1193 }
1194 return total;
1195 }
1196 self.find_segments_with_qualifier(tag, element_index, qualifier)
1198 .len()
1199 }
1200
1201 pub fn count_in_group(&self, tag: &str, group_path: &[&str]) -> usize {
1203 let instance_count = self.group_instance_count(group_path);
1204 if instance_count > 0 {
1205 let mut total = 0;
1206 for i in 0..instance_count {
1207 total += self.find_segments_in_group(tag, group_path, i).len();
1208 }
1209 return total;
1210 }
1211 self.find_segments(tag).len()
1213 }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218 use super::super::evaluator::NoOpExternalProvider;
1219 use super::*;
1220 use mig_types::navigator::GroupNavigator;
1221
1222 fn make_segment(id: &str, elements: Vec<Vec<&str>>) -> OwnedSegment {
1223 OwnedSegment {
1224 id: id.to_string(),
1225 elements: elements
1226 .into_iter()
1227 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
1228 .collect(),
1229 segment_number: 0,
1230 }
1231 }
1232
1233 struct MockGroupNavigator {
1235 groups: Vec<(Vec<String>, usize, Vec<OwnedSegment>)>,
1236 children: Vec<(Vec<String>, usize, String, usize, Vec<OwnedSegment>)>,
1238 }
1239
1240 impl MockGroupNavigator {
1241 fn new() -> Self {
1242 Self {
1243 groups: vec![],
1244 children: vec![],
1245 }
1246 }
1247 fn with_group(mut self, path: &[&str], instance: usize, segs: Vec<OwnedSegment>) -> Self {
1248 self.groups
1249 .push((path.iter().map(|s| s.to_string()).collect(), instance, segs));
1250 self
1251 }
1252 fn with_child_group(
1253 mut self,
1254 parent_path: &[&str],
1255 parent_instance: usize,
1256 child_id: &str,
1257 child_instance: usize,
1258 segs: Vec<OwnedSegment>,
1259 ) -> Self {
1260 self.children.push((
1261 parent_path.iter().map(|s| s.to_string()).collect(),
1262 parent_instance,
1263 child_id.to_string(),
1264 child_instance,
1265 segs,
1266 ));
1267 self
1268 }
1269 fn find_instance(&self, group_path: &[&str], idx: usize) -> Option<&[OwnedSegment]> {
1270 self.groups
1271 .iter()
1272 .find(|(p, i, _)| {
1273 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1274 ps.as_slice() == group_path && *i == idx
1275 })
1276 .map(|(_, _, segs)| segs.as_slice())
1277 }
1278 }
1279
1280 impl GroupNavigator for MockGroupNavigator {
1281 fn find_segments_in_group(
1282 &self,
1283 segment_id: &str,
1284 group_path: &[&str],
1285 instance_index: usize,
1286 ) -> Vec<OwnedSegment> {
1287 self.find_instance(group_path, instance_index)
1288 .map(|segs| {
1289 segs.iter()
1290 .filter(|s| s.id == segment_id)
1291 .cloned()
1292 .collect()
1293 })
1294 .unwrap_or_default()
1295 }
1296 fn find_segments_with_qualifier_in_group(
1297 &self,
1298 segment_id: &str,
1299 element_index: usize,
1300 qualifier: &str,
1301 group_path: &[&str],
1302 instance_index: usize,
1303 ) -> Vec<OwnedSegment> {
1304 self.find_segments_in_group(segment_id, group_path, instance_index)
1305 .into_iter()
1306 .filter(|s| {
1307 s.elements
1308 .get(element_index)
1309 .and_then(|e| e.first())
1310 .is_some_and(|v| v == qualifier)
1311 })
1312 .collect()
1313 }
1314 fn group_instance_count(&self, group_path: &[&str]) -> usize {
1315 self.groups
1316 .iter()
1317 .filter(|(p, _, _)| {
1318 let ps: Vec<&str> = p.iter().map(|s| s.as_str()).collect();
1319 ps.as_slice() == group_path
1320 })
1321 .count()
1322 }
1323 fn child_group_instance_count(
1324 &self,
1325 parent_path: &[&str],
1326 parent_instance: usize,
1327 child_group_id: &str,
1328 ) -> usize {
1329 self.children
1330 .iter()
1331 .filter(|(pp, pi, cid, _, _)| {
1332 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1333 ps.as_slice() == parent_path && *pi == parent_instance && cid == child_group_id
1334 })
1335 .count()
1336 }
1337 fn find_segments_in_child_group(
1338 &self,
1339 segment_id: &str,
1340 parent_path: &[&str],
1341 parent_instance: usize,
1342 child_group_id: &str,
1343 child_instance: usize,
1344 ) -> Vec<OwnedSegment> {
1345 self.children
1346 .iter()
1347 .find(|(pp, pi, cid, ci, _)| {
1348 let ps: Vec<&str> = pp.iter().map(|s| s.as_str()).collect();
1349 ps.as_slice() == parent_path
1350 && *pi == parent_instance
1351 && cid == child_group_id
1352 && *ci == child_instance
1353 })
1354 .map(|(_, _, _, _, segs)| {
1355 segs.iter()
1356 .filter(|s| s.id == segment_id)
1357 .cloned()
1358 .collect()
1359 })
1360 .unwrap_or_default()
1361 }
1362 fn extract_value_in_group(
1363 &self,
1364 segment_id: &str,
1365 element_index: usize,
1366 component_index: usize,
1367 group_path: &[&str],
1368 instance_index: usize,
1369 ) -> Option<String> {
1370 let segs = self.find_instance(group_path, instance_index)?;
1371 let seg = segs.iter().find(|s| s.id == segment_id)?;
1372 seg.elements
1373 .get(element_index)?
1374 .get(component_index)
1375 .cloned()
1376 }
1377 }
1378
1379 #[test]
1380 fn test_find_segment() {
1381 let segments = vec![
1382 make_segment("UNH", vec![vec!["test"]]),
1383 make_segment("NAD", vec![vec!["MS"], vec!["123456789", "", "293"]]),
1384 ];
1385 let external = NoOpExternalProvider;
1386 let ctx = EvaluationContext::new("11001", &external, &segments);
1387
1388 assert!(ctx.find_segment("NAD").is_some());
1389 assert!(ctx.find_segment("DTM").is_none());
1390 }
1391
1392 #[test]
1393 fn test_find_segments_with_qualifier() {
1394 let segments = vec![
1395 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1396 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1397 make_segment("NAD", vec![vec!["MS"], vec!["333"]]),
1398 ];
1399 let external = NoOpExternalProvider;
1400 let ctx = EvaluationContext::new("11001", &external, &segments);
1401
1402 let ms_nads = ctx.find_segments_with_qualifier("NAD", 0, "MS");
1403 assert_eq!(ms_nads.len(), 2);
1404 }
1405
1406 #[test]
1407 fn test_has_segment() {
1408 let segments = vec![make_segment("UNH", vec![vec!["test"]])];
1409 let external = NoOpExternalProvider;
1410 let ctx = EvaluationContext::new("11001", &external, &segments);
1411
1412 assert!(ctx.has_segment("UNH"));
1413 assert!(!ctx.has_segment("NAD"));
1414 }
1415
1416 #[test]
1419 fn test_no_navigator_group_find_returns_empty() {
1420 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1421 let external = NoOpExternalProvider;
1422 let ctx = EvaluationContext::new("55001", &external, &segments);
1423 assert!(ctx
1424 .find_segments_in_group("SEQ", &["SG4", "SG8"], 0)
1425 .is_empty());
1426 }
1427
1428 #[test]
1429 fn test_no_navigator_group_instance_count_zero() {
1430 let external = NoOpExternalProvider;
1431 let ctx = EvaluationContext::new("55001", &external, &[]);
1432 assert_eq!(ctx.group_instance_count(&["SG4"]), 0);
1433 }
1434
1435 #[test]
1436 fn test_with_navigator_finds_segments_in_group() {
1437 let external = NoOpExternalProvider;
1438 let nav = MockGroupNavigator::new().with_group(
1439 &["SG4", "SG8"],
1440 0,
1441 vec![
1442 make_segment("SEQ", vec![vec!["Z98"]]),
1443 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1444 ],
1445 );
1446 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1447 let result = ctx.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
1448 assert_eq!(result.len(), 1);
1449 assert_eq!(result[0].id, "SEQ");
1450 }
1451
1452 #[test]
1453 fn test_with_navigator_qualifier_in_group() {
1454 let external = NoOpExternalProvider;
1455 let nav = MockGroupNavigator::new().with_group(
1456 &["SG4", "SG8"],
1457 0,
1458 vec![
1459 make_segment("SEQ", vec![vec!["Z98"]]),
1460 make_segment("SEQ", vec![vec!["Z01"]]),
1461 ],
1462 );
1463 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1464 let result = ctx.find_segments_with_qualifier_in_group("SEQ", 0, "Z98", &["SG4", "SG8"], 0);
1465 assert_eq!(result.len(), 1);
1466 }
1467
1468 #[test]
1469 fn test_group_instance_count_with_navigator() {
1470 let external = NoOpExternalProvider;
1471 let nav = MockGroupNavigator::new()
1472 .with_group(
1473 &["SG4", "SG8"],
1474 0,
1475 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1476 )
1477 .with_group(
1478 &["SG4", "SG8"],
1479 1,
1480 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1481 );
1482 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1483 assert_eq!(ctx.group_instance_count(&["SG4", "SG8"]), 2);
1484 }
1485
1486 #[test]
1487 fn test_has_segment_in_group() {
1488 let external = NoOpExternalProvider;
1489 let nav = MockGroupNavigator::new().with_group(
1490 &["SG4", "SG8"],
1491 0,
1492 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1493 );
1494 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1495 assert!(ctx.has_segment_in_group("SEQ", &["SG4", "SG8"], 0));
1496 assert!(!ctx.has_segment_in_group("CCI", &["SG4", "SG8"], 0));
1497 assert!(!ctx.has_segment_in_group("SEQ", &["SG4", "SG5"], 0));
1498 }
1499
1500 #[test]
1503 fn test_has_qualifier() {
1504 let segments = vec![
1505 make_segment("NAD", vec![vec!["MS"], vec!["111"]]),
1506 make_segment("NAD", vec![vec!["MR"], vec!["222"]]),
1507 ];
1508 let external = NoOpExternalProvider;
1509 let ctx = EvaluationContext::new("11001", &external, &segments);
1510
1511 assert_eq!(ctx.has_qualifier("NAD", 0, "MS"), ConditionResult::True);
1512 assert_eq!(ctx.has_qualifier("NAD", 0, "DP"), ConditionResult::False);
1513 }
1514
1515 #[test]
1516 fn test_lacks_qualifier() {
1517 let segments = vec![make_segment("DTM", vec![vec!["92", "2025"]])];
1518 let external = NoOpExternalProvider;
1519 let ctx = EvaluationContext::new("11001", &external, &segments);
1520
1521 assert_eq!(ctx.lacks_qualifier("DTM", 0, "93"), ConditionResult::True);
1522 assert_eq!(ctx.lacks_qualifier("DTM", 0, "92"), ConditionResult::False);
1523 }
1524
1525 #[test]
1526 fn test_has_qualified_value() {
1527 let segments = vec![make_segment("STS", vec![vec!["7"], vec![], vec!["ZG9"]])];
1528 let external = NoOpExternalProvider;
1529 let ctx = EvaluationContext::new("55001", &external, &segments);
1530
1531 assert_eq!(
1532 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["ZG9", "ZH1", "ZH2"]),
1533 ConditionResult::True,
1534 );
1535 assert_eq!(
1536 ctx.has_qualified_value("STS", 0, "7", 2, 0, &["E01"]),
1537 ConditionResult::False,
1538 );
1539 assert_eq!(
1541 ctx.has_qualified_value("STS", 0, "E01", 2, 0, &["Z01"]),
1542 ConditionResult::Unknown,
1543 );
1544 }
1545
1546 #[test]
1547 fn test_any_group_has_qualifier() {
1548 let external = NoOpExternalProvider;
1549 let nav = MockGroupNavigator::new()
1550 .with_group(
1551 &["SG4", "SG8"],
1552 0,
1553 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1554 )
1555 .with_group(
1556 &["SG4", "SG8"],
1557 1,
1558 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1559 );
1560 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1561
1562 assert_eq!(
1563 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1564 ConditionResult::True,
1565 );
1566 assert_eq!(
1567 ctx.any_group_has_qualifier("SEQ", 0, "Z99", &["SG4", "SG8"]),
1568 ConditionResult::False,
1569 );
1570 }
1571
1572 #[test]
1573 fn test_any_group_has_qualifier_fallback() {
1574 let segments = vec![make_segment("SEQ", vec![vec!["Z98"]])];
1576 let external = NoOpExternalProvider;
1577 let ctx = EvaluationContext::new("55001", &external, &segments);
1578
1579 assert_eq!(
1580 ctx.any_group_has_qualifier("SEQ", 0, "Z98", &["SG4", "SG8"]),
1581 ConditionResult::True,
1582 );
1583 }
1584
1585 #[test]
1586 fn test_any_group_has_any_qualifier() {
1587 let external = NoOpExternalProvider;
1588 let nav = MockGroupNavigator::new().with_group(
1589 &["SG4", "SG8"],
1590 0,
1591 vec![make_segment("SEQ", vec![vec!["Z80"]])],
1592 );
1593 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1594
1595 assert_eq!(
1596 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z01", "Z80", "Z81"], &["SG4", "SG8"]),
1597 ConditionResult::True,
1598 );
1599 assert_eq!(
1600 ctx.any_group_has_any_qualifier("SEQ", 0, &["Z98"], &["SG4", "SG8"]),
1601 ConditionResult::False,
1602 );
1603 }
1604
1605 #[test]
1606 fn test_any_group_has_co_occurrence() {
1607 let external = NoOpExternalProvider;
1608 let nav = MockGroupNavigator::new().with_group(
1609 &["SG4", "SG8"],
1610 0,
1611 vec![
1612 make_segment("SEQ", vec![vec!["Z01"]]),
1613 make_segment("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
1614 ],
1615 );
1616 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1617
1618 assert_eq!(
1619 ctx.any_group_has_co_occurrence(
1620 "SEQ",
1621 0,
1622 &["Z01"],
1623 "CCI",
1624 2,
1625 0,
1626 &["Z07"],
1627 &["SG4", "SG8"],
1628 ),
1629 ConditionResult::True,
1630 );
1631 assert_eq!(
1633 ctx.any_group_has_co_occurrence(
1634 "SEQ",
1635 0,
1636 &["Z01"],
1637 "CCI",
1638 2,
1639 0,
1640 &["ZC0"],
1641 &["SG4", "SG8"],
1642 ),
1643 ConditionResult::False,
1644 );
1645 }
1646
1647 #[test]
1650 fn test_filtered_parent_child_has_qualifier() {
1651 let external = NoOpExternalProvider;
1652 let nav = MockGroupNavigator::new()
1655 .with_group(
1656 &["SG4", "SG8"],
1657 0,
1658 vec![make_segment("SEQ", vec![vec!["Z98"]])],
1659 )
1660 .with_group(
1661 &["SG4", "SG8"],
1662 1,
1663 vec![make_segment("SEQ", vec![vec!["Z01"]])],
1664 )
1665 .with_child_group(
1666 &["SG4", "SG8"],
1667 0,
1668 "SG10",
1669 0,
1670 vec![make_segment("CCI", vec![vec!["Z23"]])],
1671 );
1672 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1673
1674 assert_eq!(
1676 ctx.filtered_parent_child_has_qualifier(
1677 &["SG4", "SG8"],
1678 "SEQ",
1679 0,
1680 "Z98",
1681 "SG10",
1682 "CCI",
1683 0,
1684 "Z23",
1685 ),
1686 ConditionResult::True,
1687 );
1688 assert_eq!(
1690 ctx.filtered_parent_child_has_qualifier(
1691 &["SG4", "SG8"],
1692 "SEQ",
1693 0,
1694 "Z01",
1695 "SG10",
1696 "CCI",
1697 0,
1698 "Z23",
1699 ),
1700 ConditionResult::False,
1701 );
1702 assert_eq!(
1704 ctx.filtered_parent_child_has_qualifier(
1705 &["SG4", "SG8"],
1706 "SEQ",
1707 0,
1708 "Z98",
1709 "SG10",
1710 "CCI",
1711 0,
1712 "Z99",
1713 ),
1714 ConditionResult::False,
1715 );
1716 }
1717
1718 #[test]
1719 fn test_filtered_parent_child_fallback() {
1720 let segments = vec![
1722 make_segment("SEQ", vec![vec!["Z98"]]),
1723 make_segment("CCI", vec![vec!["Z23"]]),
1724 ];
1725 let external = NoOpExternalProvider;
1726 let ctx = EvaluationContext::new("55001", &external, &segments);
1727
1728 assert_eq!(
1729 ctx.filtered_parent_child_has_qualifier(
1730 &["SG4", "SG8"],
1731 "SEQ",
1732 0,
1733 "Z98",
1734 "SG10",
1735 "CCI",
1736 0,
1737 "Z23",
1738 ),
1739 ConditionResult::True,
1740 );
1741 assert_eq!(
1743 ctx.filtered_parent_child_has_qualifier(
1744 &["SG4", "SG8"],
1745 "SEQ",
1746 0,
1747 "Z98",
1748 "SG10",
1749 "CCI",
1750 0,
1751 "Z99",
1752 ),
1753 ConditionResult::False,
1754 );
1755 }
1756
1757 #[test]
1758 fn test_any_group_has_qualifier_without() {
1759 let external = NoOpExternalProvider;
1760 let nav = MockGroupNavigator::new()
1763 .with_group(
1764 &["SG4", "SG8"],
1765 0,
1766 vec![make_segment("SEQ", vec![vec!["Z59"]])],
1767 )
1768 .with_group(
1769 &["SG4", "SG8"],
1770 1,
1771 vec![
1772 make_segment("SEQ", vec![vec!["Z01"]]),
1773 make_segment("CCI", vec![vec!["11"]]),
1774 ],
1775 );
1776 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1777
1778 assert_eq!(
1780 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1781 ConditionResult::True,
1782 );
1783 assert_eq!(
1785 ctx.any_group_has_qualifier_without("SEQ", 0, "Z01", "CCI", 0, "11", &["SG4", "SG8"]),
1786 ConditionResult::False,
1787 );
1788 assert_eq!(
1790 ctx.any_group_has_qualifier_without("SEQ", 0, "Z99", "CCI", 0, "11", &["SG4", "SG8"]),
1791 ConditionResult::False,
1792 );
1793 }
1794
1795 #[test]
1796 fn test_any_group_has_qualifier_without_fallback() {
1797 let segments = vec![make_segment("SEQ", vec![vec!["Z59"]])];
1798 let external = NoOpExternalProvider;
1799 let ctx = EvaluationContext::new("55001", &external, &segments);
1800
1801 assert_eq!(
1803 ctx.any_group_has_qualifier_without("SEQ", 0, "Z59", "CCI", 0, "11", &["SG4", "SG8"]),
1804 ConditionResult::True,
1805 );
1806 }
1807
1808 #[test]
1809 fn test_collect_group_values() {
1810 let external = NoOpExternalProvider;
1811 let nav = MockGroupNavigator::new()
1812 .with_group(
1813 &["SG4", "SG6"],
1814 0,
1815 vec![make_segment("RFF", vec![vec!["Z49", "REF001"]])],
1816 )
1817 .with_group(
1818 &["SG4", "SG6"],
1819 1,
1820 vec![make_segment("RFF", vec![vec!["Z49", "REF002"]])],
1821 );
1822 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1823
1824 let values = ctx.collect_group_values("RFF", 0, 1, &["SG4", "SG6"]);
1825 assert_eq!(values.len(), 2);
1826 assert_eq!(values[0], (0, "REF001".to_string()));
1827 assert_eq!(values[1], (1, "REF002".to_string()));
1828 }
1829
1830 #[test]
1831 fn test_groups_share_qualified_value() {
1832 let external = NoOpExternalProvider;
1833 let nav = MockGroupNavigator::new()
1837 .with_group(
1838 &["SG4", "SG6"],
1839 0,
1840 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1841 )
1842 .with_group(
1843 &["SG4", "SG8"],
1844 0,
1845 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1846 )
1847 .with_group(
1848 &["SG4", "SG8"],
1849 1,
1850 vec![make_segment("SEQ", vec![vec!["Z01"], vec!["TS999"]])],
1851 );
1852 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1853
1854 assert_eq!(
1856 ctx.groups_share_qualified_value(
1857 "RFF",
1858 0,
1859 "Z49",
1860 0,
1861 1,
1862 &["SG4", "SG6"],
1863 "SEQ",
1864 1,
1865 0,
1866 &["SG4", "SG8"],
1867 ),
1868 ConditionResult::True,
1869 );
1870 }
1871
1872 #[test]
1873 fn test_groups_share_qualified_value_no_match() {
1874 let external = NoOpExternalProvider;
1875 let nav = MockGroupNavigator::new()
1876 .with_group(
1877 &["SG4", "SG6"],
1878 0,
1879 vec![make_segment("RFF", vec![vec!["Z49", "TS001"]])],
1880 )
1881 .with_group(
1882 &["SG4", "SG8"],
1883 0,
1884 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS999"]])],
1885 );
1886 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1887
1888 assert_eq!(
1890 ctx.groups_share_qualified_value(
1891 "RFF",
1892 0,
1893 "Z49",
1894 0,
1895 1,
1896 &["SG4", "SG6"],
1897 "SEQ",
1898 1,
1899 0,
1900 &["SG4", "SG8"],
1901 ),
1902 ConditionResult::False,
1903 );
1904 }
1905
1906 #[test]
1907 fn test_groups_share_qualified_value_no_source() {
1908 let external = NoOpExternalProvider;
1909 let nav = MockGroupNavigator::new()
1911 .with_group(
1912 &["SG4", "SG6"],
1913 0,
1914 vec![make_segment("RFF", vec![vec!["Z13", "55001"]])],
1915 )
1916 .with_group(
1917 &["SG4", "SG8"],
1918 0,
1919 vec![make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]])],
1920 );
1921 let ctx = EvaluationContext::with_navigator("55001", &external, &[], &nav);
1922
1923 assert_eq!(
1925 ctx.groups_share_qualified_value(
1926 "RFF",
1927 0,
1928 "Z49",
1929 0,
1930 1,
1931 &["SG4", "SG6"],
1932 "SEQ",
1933 1,
1934 0,
1935 &["SG4", "SG8"],
1936 ),
1937 ConditionResult::Unknown,
1938 );
1939 }
1940
1941 #[test]
1942 fn test_groups_share_qualified_value_fallback() {
1943 let segments = vec![
1945 make_segment("RFF", vec![vec!["Z49", "TS001"]]),
1946 make_segment("SEQ", vec![vec!["Z98"], vec!["TS001"]]),
1947 ];
1948 let external = NoOpExternalProvider;
1949 let ctx = EvaluationContext::new("55001", &external, &segments);
1950
1951 assert_eq!(
1952 ctx.groups_share_qualified_value(
1953 "RFF",
1954 0,
1955 "Z49",
1956 0,
1957 1,
1958 &["SG4", "SG6"],
1959 "SEQ",
1960 1,
1961 0,
1962 &["SG4", "SG8"],
1963 ),
1964 ConditionResult::True,
1965 );
1966 }
1967
1968 #[test]
1969 fn test_child_group_pass_throughs_no_navigator() {
1970 let external = NoOpExternalProvider;
1971 let ctx = EvaluationContext::new("55001", &external, &[]);
1972
1973 assert_eq!(
1974 ctx.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
1975 0
1976 );
1977 assert!(ctx
1978 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0)
1979 .is_empty());
1980 assert_eq!(
1981 ctx.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
1982 None
1983 );
1984 }
1985
1986 #[test]
1989 fn test_has_segment_matching_found() {
1990 let segments = vec![
1991 make_segment("STS", vec![vec!["7"], vec!["E01"], vec!["ZW4"]]),
1992 make_segment("STS", vec![vec!["Z20"], vec!["Z32"], vec!["A99"]]),
1993 ];
1994 let external = NoOpExternalProvider;
1995 let ctx = EvaluationContext::new("55001", &external, &segments);
1996
1997 assert_eq!(
1998 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32"), (2, 0, "A99")]),
1999 ConditionResult::True,
2000 );
2001 }
2002
2003 #[test]
2004 fn test_has_segment_matching_not_found() {
2005 let segments = vec![make_segment(
2006 "STS",
2007 vec![vec!["7"], vec!["E01"], vec!["ZW4"]],
2008 )];
2009 let external = NoOpExternalProvider;
2010 let ctx = EvaluationContext::new("55001", &external, &segments);
2011
2012 assert_eq!(
2013 ctx.has_segment_matching("STS", &[(0, 0, "Z20"), (1, 0, "Z32")]),
2014 ConditionResult::False,
2015 );
2016 }
2017
2018 #[test]
2019 fn test_has_segment_matching_no_segments() {
2020 let external = NoOpExternalProvider;
2021 let ctx = EvaluationContext::new("55001", &external, &[]);
2022
2023 assert_eq!(
2024 ctx.has_segment_matching("STS", &[(0, 0, "Z20")]),
2025 ConditionResult::Unknown,
2026 );
2027 }
2028
2029 #[test]
2030 fn test_has_segment_matching_in_group() {
2031 let nav = MockGroupNavigator::new()
2032 .with_group(
2033 &["SG4"],
2034 0,
2035 vec![make_segment("STS", vec![vec!["7"], vec!["E01"]])],
2036 )
2037 .with_group(
2038 &["SG4"],
2039 1,
2040 vec![make_segment("STS", vec![vec!["Z20"], vec!["Z32"]])],
2041 );
2042 let segments = vec![
2043 make_segment("STS", vec![vec!["7"], vec!["E01"]]),
2044 make_segment("STS", vec![vec!["Z20"], vec!["Z32"]]),
2045 ];
2046 let external = NoOpExternalProvider;
2047 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2048
2049 assert_eq!(
2050 ctx.has_segment_matching_in_group("STS", &[(0, 0, "Z20"), (1, 0, "Z32")], &["SG4"]),
2051 ConditionResult::True,
2052 );
2053 }
2054
2055 #[test]
2058 fn test_dtm_ge() {
2059 let segments = vec![make_segment(
2060 "DTM",
2061 vec![vec!["137", "202601010000", "303"]],
2062 )];
2063 let external = NoOpExternalProvider;
2064 let ctx = EvaluationContext::new("55001", &external, &segments);
2065
2066 assert_eq!(ctx.dtm_ge("137", "202601010000"), ConditionResult::True);
2067 assert_eq!(ctx.dtm_ge("137", "202501010000"), ConditionResult::True);
2068 assert_eq!(ctx.dtm_ge("137", "202701010000"), ConditionResult::False);
2069 assert_eq!(ctx.dtm_ge("999", "202601010000"), ConditionResult::Unknown);
2070 }
2071
2072 #[test]
2073 fn test_dtm_lt() {
2074 let segments = vec![make_segment(
2075 "DTM",
2076 vec![vec!["137", "202601010000", "303"]],
2077 )];
2078 let external = NoOpExternalProvider;
2079 let ctx = EvaluationContext::new("55001", &external, &segments);
2080
2081 assert_eq!(ctx.dtm_lt("137", "202701010000"), ConditionResult::True);
2082 assert_eq!(ctx.dtm_lt("137", "202601010000"), ConditionResult::False);
2083 assert_eq!(ctx.dtm_lt("137", "202501010000"), ConditionResult::False);
2084 }
2085
2086 #[test]
2087 fn test_dtm_le() {
2088 let segments = vec![make_segment(
2089 "DTM",
2090 vec![vec!["137", "202601010000", "303"]],
2091 )];
2092 let external = NoOpExternalProvider;
2093 let ctx = EvaluationContext::new("55001", &external, &segments);
2094
2095 assert_eq!(ctx.dtm_le("137", "202601010000"), ConditionResult::True);
2096 assert_eq!(ctx.dtm_le("137", "202701010000"), ConditionResult::True);
2097 assert_eq!(ctx.dtm_le("137", "202501010000"), ConditionResult::False);
2098 }
2099
2100 #[test]
2103 fn test_count_qualified_in_group() {
2104 let nav = MockGroupNavigator::new()
2105 .with_group(
2106 &["SG4", "SG8"],
2107 0,
2108 vec![
2109 make_segment("CCI", vec![vec!["Z23"]]),
2110 make_segment("CCI", vec![vec!["Z30"]]),
2111 ],
2112 )
2113 .with_group(
2114 &["SG4", "SG8"],
2115 1,
2116 vec![make_segment("CCI", vec![vec!["Z23"]])],
2117 );
2118 let segments = vec![
2119 make_segment("CCI", vec![vec!["Z23"]]),
2120 make_segment("CCI", vec![vec!["Z30"]]),
2121 make_segment("CCI", vec![vec!["Z23"]]),
2122 ];
2123 let external = NoOpExternalProvider;
2124 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2125
2126 assert_eq!(
2127 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2128 2
2129 );
2130 assert_eq!(
2131 ctx.count_qualified_in_group("CCI", 0, "Z30", &["SG4", "SG8"]),
2132 1
2133 );
2134 assert_eq!(
2135 ctx.count_qualified_in_group("CCI", 0, "Z99", &["SG4", "SG8"]),
2136 0
2137 );
2138 }
2139
2140 #[test]
2141 fn test_count_in_group() {
2142 let nav = MockGroupNavigator::new()
2143 .with_group(
2144 &["SG4", "SG8"],
2145 0,
2146 vec![
2147 make_segment("SEQ", vec![vec!["Z98"]]),
2148 make_segment("CCI", vec![vec!["Z23"]]),
2149 ],
2150 )
2151 .with_group(
2152 &["SG4", "SG8"],
2153 1,
2154 vec![make_segment("SEQ", vec![vec!["Z01"]])],
2155 );
2156 let segments = vec![
2157 make_segment("SEQ", vec![vec!["Z98"]]),
2158 make_segment("CCI", vec![vec!["Z23"]]),
2159 make_segment("SEQ", vec![vec!["Z01"]]),
2160 ];
2161 let external = NoOpExternalProvider;
2162 let ctx = EvaluationContext::with_navigator("55001", &external, &segments, &nav);
2163
2164 assert_eq!(ctx.count_in_group("SEQ", &["SG4", "SG8"]), 2);
2165 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 1);
2166 assert_eq!(ctx.count_in_group("DTM", &["SG4", "SG8"]), 0);
2167 }
2168
2169 #[test]
2170 fn test_count_fallback_no_navigator() {
2171 let segments = vec![
2172 make_segment("CCI", vec![vec!["Z23"]]),
2173 make_segment("CCI", vec![vec!["Z30"]]),
2174 make_segment("CCI", vec![vec!["Z23"]]),
2175 ];
2176 let external = NoOpExternalProvider;
2177 let ctx = EvaluationContext::new("55001", &external, &segments);
2178
2179 assert_eq!(
2181 ctx.count_qualified_in_group("CCI", 0, "Z23", &["SG4", "SG8"]),
2182 2
2183 );
2184 assert_eq!(ctx.count_in_group("CCI", &["SG4", "SG8"]), 3);
2185 }
2186
2187 #[test]
2190 fn format_check_prefers_resolved_value() {
2191 let segments = vec![make_segment("DTM", vec![vec!["92", "WRONG"]])];
2193 let external = NoOpExternalProvider;
2194 let ctx = EvaluationContext::new("55001", &external, &segments)
2195 .with_resolved(Some("CORRECT"), None);
2196
2197 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "CORRECT"));
2198 assert_eq!(result, ConditionResult::True);
2199 }
2200
2201 #[test]
2202 fn format_check_falls_back_to_segment_search() {
2203 let segments = vec![make_segment("DTM", vec![vec!["92", "202501011200"]])];
2205 let external = NoOpExternalProvider;
2206 let ctx = EvaluationContext::new("55001", &external, &segments);
2207
2208 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "202501011200"));
2209 assert_eq!(result, ConditionResult::True);
2210 }
2211
2212 #[test]
2213 fn format_check_returns_unknown_when_segment_absent() {
2214 let segments: Vec<OwnedSegment> = vec![];
2216 let external = NoOpExternalProvider;
2217 let ctx = EvaluationContext::new("55001", &external, &segments);
2218
2219 let result = ctx.format_check("DTM", 0, 1, |_| ConditionResult::True);
2220 assert_eq!(result, ConditionResult::Unknown);
2221 }
2222
2223 #[test]
2224 fn format_check_returns_unknown_when_multiple_segments() {
2225 let segments = vec![
2229 make_segment("DTM", vec![vec!["92", "OK"]]),
2230 make_segment("DTM", vec![vec!["163", "BAD"]]),
2231 ];
2232 let external = NoOpExternalProvider;
2233 let ctx = EvaluationContext::new("55001", &external, &segments);
2234
2235 let result = ctx.format_check("DTM", 0, 1, |v| ConditionResult::from(v == "OK"));
2236 assert_eq!(result, ConditionResult::Unknown);
2237 }
2238
2239 #[test]
2240 fn format_check_qualified_prefers_resolved_value() {
2241 let segments = vec![
2243 make_segment("DTM", vec![vec!["92", "WRONG"]]),
2244 make_segment("DTM", vec![vec!["163", "ALSO_WRONG"]]),
2245 ];
2246 let external = NoOpExternalProvider;
2247 let ctx = EvaluationContext::new("55001", &external, &segments)
2248 .with_resolved(Some("CORRECT"), None);
2249
2250 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2251 ConditionResult::from(v == "CORRECT")
2252 });
2253 assert_eq!(result, ConditionResult::True);
2254 }
2255
2256 #[test]
2257 fn format_check_qualified_falls_back_to_qualified_search() {
2258 let segments = vec![
2260 make_segment("DTM", vec![vec!["92", "2200"]]),
2261 make_segment("DTM", vec![vec!["163", "0800"]]),
2262 ];
2263 let external = NoOpExternalProvider;
2264 let ctx = EvaluationContext::new("55001", &external, &segments);
2265
2266 let result = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2268 ConditionResult::from(v == "0800")
2269 });
2270 assert_eq!(result, ConditionResult::True);
2271
2272 let result2 = ctx.format_check_qualified("DTM", 0, "163", 0, 1, |v| {
2274 ConditionResult::from(v == "2200")
2275 });
2276 assert_eq!(result2, ConditionResult::False);
2277 }
2278
2279 #[test]
2282 fn self_segment_value_prefers_resolved_segment() {
2283 let segments = vec![
2287 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2288 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2289 ];
2290 let external = NoOpExternalProvider;
2291 let resolved_elements = vec![vec!["Z25".to_string(), String::new(), "303".to_string()]];
2292 let ctx = EvaluationContext::new("55001", &external, &segments)
2293 .with_resolved(None, Some(&resolved_elements));
2294
2295 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2296 assert!(ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2297 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "OTHER"));
2298 }
2299
2300 #[test]
2301 fn self_segment_value_falls_back_when_no_resolved_segment() {
2302 let segments = vec![
2304 make_segment("DTM", vec![vec!["Z25", "", "303"]]),
2305 make_segment("DTM", vec![vec!["472", "", "OTHER"]]),
2306 ];
2307 let external = NoOpExternalProvider;
2308 let ctx = EvaluationContext::new("55001", &external, &segments);
2309
2310 assert_eq!(ctx.self_segment_value("DTM", 0, 2), Some("303"));
2311 }
2312
2313 #[test]
2314 fn self_segment_value_missing_returns_none() {
2315 let segments: Vec<OwnedSegment> = vec![];
2316 let external = NoOpExternalProvider;
2317 let ctx = EvaluationContext::new("55001", &external, &segments);
2318
2319 assert_eq!(ctx.self_segment_value("DTM", 0, 2), None);
2320 assert!(!ctx.self_segment_value_equals("DTM", 0, 2, "303"));
2321 }
2322
2323 fn make_sg8_sequence(sg8s: &[(&str, &str, &str)]) -> Vec<OwnedSegment> {
2327 let mut segments = Vec::new();
2328 for (i, (qual, zid, obis)) in sg8s.iter().enumerate() {
2329 segments.push(OwnedSegment {
2330 id: "SEQ".to_string(),
2331 elements: vec![vec![qual.to_string()], vec![zid.to_string()]],
2332 segment_number: (2 * i + 1) as u32,
2333 });
2334 segments.push(OwnedSegment {
2335 id: "PIA".to_string(),
2336 elements: vec![vec!["5".to_string()], vec![obis.to_string()]],
2337 segment_number: (2 * i + 2) as u32,
2338 });
2339 }
2340 segments
2341 }
2342
2343 #[test]
2344 fn count_sg8_obis_matches_counts_matching_kumuliert() {
2345 let segments = make_sg8_sequence(&[
2347 ("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"), ]);
2352 let external = NoOpExternalProvider;
2353 let seq = &segments[0].elements;
2355 let ctx =
2356 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2357
2358 let n = ctx
2359 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2360 assert_eq!(n, 2);
2361 }
2362
2363 #[test]
2364 fn count_sg8_obis_matches_returns_zero_without_resolved_segment() {
2365 let segments = make_sg8_sequence(&[("Z02", "ZR1", "1-1:1.8.0")]);
2366 let external = NoOpExternalProvider;
2367 let ctx = EvaluationContext::new("55001", &external, &segments);
2368 let n = ctx
2369 .count_sg8_obis_matches(super::super::format_validators::is_obis_wirkarbeit_kumuliert);
2370 assert_eq!(n, 0);
2371 }
2372
2373 #[test]
2374 fn count_sg8_obis_matches_quarter_hour_and_kumuliert_are_disjoint() {
2375 let segments = make_sg8_sequence(&[
2376 ("Z02", "ZR1", "1-1:1.8.0"), ("Z02", "ZR1", "1-1:1.29.0"), ]);
2379 let external = NoOpExternalProvider;
2380 let seq = &segments[0].elements;
2381 let ctx =
2382 EvaluationContext::new("55001", &external, &segments).with_resolved(None, Some(seq));
2383
2384 assert_eq!(
2385 ctx.count_sg8_obis_matches(
2386 super::super::format_validators::is_obis_wirkarbeit_kumuliert
2387 ),
2388 1
2389 );
2390 assert_eq!(
2391 ctx.count_sg8_obis_matches(
2392 super::super::format_validators::is_obis_wirkarbeit_quarter_hour
2393 ),
2394 1
2395 );
2396 }
2397}