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