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