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