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