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