1use std::collections::{HashMap, HashSet};
8
9use super::evaluator::{ConditionResult, ExternalConditionProvider};
10
11const COUNTRIES_WITH_PLZ: &[&str] = &[
13 "DE", "AT", "CH", "BE", "BG", "CZ", "DK", "EE", "FI", "FR", "GR", "HR", "HU", "IE", "IT", "LT",
14 "LU", "LV", "MT", "NL", "PL", "PT", "RO", "SE", "SI", "SK", "ES", "GB", "NO", "CY", "IS", "LI",
15];
16
17const COUNTRY_PLZ_CONDITIONS: &[&str] = &[
18 "country_code_has_plz",
19 "country_has_postal_code",
20 "country_has_postal_code_requirement",
21];
22
23pub struct CountryPostalCodeProvider {
28 country_code: Option<String>,
29}
30
31impl CountryPostalCodeProvider {
32 pub fn new(country_code: Option<String>) -> Self {
34 Self { country_code }
35 }
36}
37
38impl ExternalConditionProvider for CountryPostalCodeProvider {
39 fn evaluate(&self, condition_name: &str) -> ConditionResult {
40 if !COUNTRY_PLZ_CONDITIONS.contains(&condition_name) {
41 return ConditionResult::Unknown;
42 }
43 match &self.country_code {
44 Some(code) => ConditionResult::from(COUNTRIES_WITH_PLZ.contains(&code.as_str())),
45 None => ConditionResult::Unknown,
46 }
47 }
48}
49
50pub struct MapExternalProvider {
71 conditions: HashMap<String, bool>,
72}
73
74impl MapExternalProvider {
75 pub fn new(conditions: HashMap<String, bool>) -> Self {
77 Self { conditions }
78 }
79}
80
81impl ExternalConditionProvider for MapExternalProvider {
82 fn evaluate(&self, condition_name: &str) -> ConditionResult {
83 match self.conditions.get(condition_name) {
84 Some(true) => ConditionResult::True,
85 Some(false) => ConditionResult::False,
86 None => ConditionResult::Unknown,
87 }
88 }
89}
90
91pub struct CompositeExternalProvider {
101 providers: Vec<Box<dyn ExternalConditionProvider>>,
102}
103
104impl CompositeExternalProvider {
105 pub fn new(providers: Vec<Box<dyn ExternalConditionProvider>>) -> Self {
109 Self { providers }
110 }
111
112 pub fn with_defaults(
119 sector: Option<Sector>,
120 roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>,
121 code_list_json: Option<&str>,
122 ) -> Self {
123 Self::builder()
124 .sector(sector)
125 .roles(roles)
126 .code_list_json(code_list_json)
127 .build()
128 }
129
130 pub fn builder() -> CompositeProviderBuilder {
132 CompositeProviderBuilder::default()
133 }
134}
135
136#[derive(Default)]
138pub struct CompositeProviderBuilder {
139 sector: Option<Sector>,
140 roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>,
141 code_list_json: Option<String>,
142 konfigurationen_json: Option<String>,
143 product_code: Option<String>,
144 country_code: Option<String>,
145}
146
147impl CompositeProviderBuilder {
148 pub fn sector(mut self, sector: Option<Sector>) -> Self {
149 self.sector = sector;
150 self
151 }
152
153 pub fn roles(mut self, roles: Option<(Vec<MarketRole>, Vec<MarketRole>)>) -> Self {
154 self.roles = roles;
155 self
156 }
157
158 pub fn code_list_json(mut self, json: Option<&str>) -> Self {
159 self.code_list_json = json.map(String::from);
160 self
161 }
162
163 pub fn konfigurationen_json(mut self, json: &str) -> Self {
164 self.konfigurationen_json = Some(json.to_string());
165 self
166 }
167
168 pub fn product_code(mut self, code: Option<String>) -> Self {
169 self.product_code = code;
170 self
171 }
172
173 pub fn country_code(mut self, code: Option<String>) -> Self {
174 self.country_code = code;
175 self
176 }
177
178 pub fn build(self) -> CompositeExternalProvider {
179 let mut providers: Vec<Box<dyn ExternalConditionProvider>> = Vec::new();
180
181 if let Some(sector) = self.sector {
182 providers.push(Box::new(SectorProvider::new(sector)));
183 }
184 if let Some((sender, recipient)) = self.roles {
185 providers.push(Box::new(MarketRoleProvider::new(sender, recipient)));
186 }
187 if let Some(json) = &self.code_list_json {
188 if let Ok(provider) = CodeListProvider::from_json(json) {
189 providers.push(Box::new(provider));
190 }
191 }
192 if let Some(json) = &self.konfigurationen_json {
193 if let Ok(provider) = KonfigurationenProvider::from_json(json, self.product_code) {
194 providers.push(Box::new(provider));
195 }
196 }
197 if self.country_code.is_some() {
198 providers.push(Box::new(CountryPostalCodeProvider::new(self.country_code)));
199 }
200
201 CompositeExternalProvider::new(providers)
202 }
203}
204
205impl ExternalConditionProvider for CompositeExternalProvider {
206 fn evaluate(&self, condition_name: &str) -> ConditionResult {
207 for provider in &self.providers {
208 let result = provider.evaluate(condition_name);
209 if !result.is_unknown() {
210 return result;
211 }
212 }
213 ConditionResult::Unknown
214 }
215}
216
217pub type Clock = std::sync::Arc<dyn Fn() -> chrono::DateTime<chrono::Utc> + Send + Sync>;
219
220pub struct ClockedProvider<P> {
225 inner: P,
226 clock: Clock,
227}
228
229impl<P: ExternalConditionProvider> ClockedProvider<P> {
230 pub fn new(inner: P, clock: Clock) -> Self {
231 Self { inner, clock }
232 }
233}
234
235impl<P: ExternalConditionProvider> ExternalConditionProvider for ClockedProvider<P> {
236 fn evaluate(&self, condition_name: &str) -> ConditionResult {
237 self.inner.evaluate(condition_name)
238 }
239
240 fn now(&self) -> chrono::DateTime<chrono::Utc> {
241 (self.clock)()
242 }
243}
244
245pub struct CodeListProvider {
250 lists: HashMap<String, HashSet<String>>,
251}
252
253impl CodeListProvider {
254 pub fn new(lists: HashMap<String, Vec<String>>) -> Self {
255 Self {
256 lists: lists
257 .into_iter()
258 .map(|(k, v)| (k, v.into_iter().collect()))
259 .collect(),
260 }
261 }
262
263 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
265 #[derive(serde::Deserialize)]
266 struct Entry {
267 codes: Vec<CodeValue>,
268 }
269 #[derive(serde::Deserialize)]
270 struct CodeValue {
271 value: String,
272 }
273
274 let raw: HashMap<String, Entry> = serde_json::from_str(json)?;
275 let lists = raw
276 .into_iter()
277 .map(|(k, v)| (k, v.codes.into_iter().map(|c| c.value).collect()))
278 .collect();
279 Ok(Self { lists })
280 }
281
282 pub fn from_json_file(
284 path: &std::path::Path,
285 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
286 let json = std::fs::read_to_string(path)?;
287 Ok(Self::from_json(&json)?)
288 }
289}
290
291impl ExternalConditionProvider for CodeListProvider {
292 fn evaluate(&self, condition_name: &str) -> ConditionResult {
293 let rest = match condition_name.strip_prefix("code_in_") {
294 Some(r) => r,
295 None => return ConditionResult::Unknown,
296 };
297 let (de_id, value) = match rest.split_once(':') {
298 Some(pair) => pair,
299 None => return ConditionResult::Unknown,
300 };
301 match self.lists.get(de_id) {
302 Some(set) => ConditionResult::from(set.contains(value)),
303 None => ConditionResult::Unknown,
304 }
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum Sector {
311 Strom,
312 Gas,
313}
314
315pub struct SectorProvider {
317 sector: Sector,
318}
319
320impl SectorProvider {
321 pub fn new(sector: Sector) -> Self {
322 Self { sector }
323 }
324
325 pub fn from_variant(variant: &str) -> Option<Self> {
327 match variant {
328 "Strom" => Some(Self::new(Sector::Strom)),
329 "Gas" => Some(Self::new(Sector::Gas)),
330 _ => None,
331 }
332 }
333}
334
335const STROM_CONDITIONS: &[&str] = &[
336 "recipient_is_strom",
337 "recipient_market_sector_is_strom",
338 "recipient_is_electricity_sector",
339 "sender_is_strom",
340 "mp_id_is_strom",
341 "mp_id_is_strom_sector",
342 "mp_id_is_electricity_sector",
343 "mp_id_from_electricity_sector",
344 "mp_id_only_strom",
345 "mp_id_strom_only",
346 "mp_id_sparte_strom",
347 "market_location_is_electricity",
348 "marktpartner_is_strom",
349 "location_is_strom",
350 "metering_point_is_strom",
351 "network_location_is_strom",
352];
353
354const GAS_CONDITIONS: &[&str] = &[
355 "recipient_is_gas",
356 "recipient_market_sector_is_gas",
357 "recipient_is_gas_sector",
358 "sender_is_gas",
359 "sender_is_gas_sector",
360 "mp_id_is_gas",
361 "mp_id_is_gas_sector",
362 "market_location_is_gas",
363 "marktpartner_is_gas",
364 "location_is_gas",
365 "metering_point_is_gas",
366 "network_location_is_gas",
367 "recipient_is_msb_gas",
368];
369
370impl ExternalConditionProvider for SectorProvider {
371 fn evaluate(&self, condition_name: &str) -> ConditionResult {
372 if STROM_CONDITIONS.contains(&condition_name) {
373 return ConditionResult::from(self.sector == Sector::Strom);
374 }
375 if GAS_CONDITIONS.contains(&condition_name) {
376 return ConditionResult::from(self.sector == Sector::Gas);
377 }
378 ConditionResult::Unknown
379 }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
384pub enum MarketRole {
385 LF,
387 NB,
389 MSB,
391 MDL,
393 UENB,
395 BKV,
397 BIKO,
399 ESA,
401 MGV,
403 KN,
405}
406
407impl MarketRole {
408 fn from_suffix(s: &str) -> Option<Self> {
409 match s {
410 "lf" => Some(Self::LF),
411 "nb" => Some(Self::NB),
412 "msb" => Some(Self::MSB),
413 "mdl" => Some(Self::MDL),
414 "uenb" => Some(Self::UENB),
415 "bkv" => Some(Self::BKV),
416 "biko" => Some(Self::BIKO),
417 "esa" => Some(Self::ESA),
418 "mgv" => Some(Self::MGV),
419 "kn" => Some(Self::KN),
420 _ => None,
421 }
422 }
423
424 fn parse_compound(s: &str) -> Option<Vec<Self>> {
428 if s.contains("_or_") {
430 let parts: Vec<&str> = s.split("_or_").collect();
431 let mut roles = Vec::new();
432 for part in parts {
433 roles.push(Self::from_suffix(part)?);
434 }
435 if !roles.is_empty() {
436 return Some(roles);
437 }
438 }
439 let parts: Vec<&str> = s.split('_').collect();
441 let mut roles = Vec::new();
442 for part in parts {
443 roles.push(Self::from_suffix(part)?);
444 }
445 if roles.is_empty() {
446 None
447 } else {
448 Some(roles)
449 }
450 }
451}
452
453pub struct MarketRoleProvider {
455 sender_roles: HashSet<MarketRole>,
456 recipient_roles: HashSet<MarketRole>,
457}
458
459impl MarketRoleProvider {
460 pub fn new(sender_roles: Vec<MarketRole>, recipient_roles: Vec<MarketRole>) -> Self {
461 Self {
462 sender_roles: sender_roles.into_iter().collect(),
463 recipient_roles: recipient_roles.into_iter().collect(),
464 }
465 }
466}
467
468impl ExternalConditionProvider for MarketRoleProvider {
469 fn evaluate(&self, condition_name: &str) -> ConditionResult {
470 if let Some(role_str) = condition_name.strip_prefix("sender_is_") {
472 if let Some(role) = MarketRole::from_suffix(role_str) {
473 return ConditionResult::from(self.sender_roles.contains(&role));
474 }
475 if let Some(roles) = MarketRole::parse_compound(role_str) {
477 return ConditionResult::from(roles.iter().any(|r| self.sender_roles.contains(r)));
478 }
479 }
480 if let Some(role_str) = condition_name.strip_prefix("recipient_is_") {
481 if let Some(role) = MarketRole::from_suffix(role_str) {
482 return ConditionResult::from(self.recipient_roles.contains(&role));
483 }
484 if let Some(roles) = MarketRole::parse_compound(role_str) {
486 return ConditionResult::from(
487 roles.iter().any(|r| self.recipient_roles.contains(r)),
488 );
489 }
490 }
491 if let Some(role_str) = condition_name.strip_prefix("sender_role_is_not_") {
492 if let Some(role) = MarketRole::from_suffix(role_str) {
493 return ConditionResult::from(!self.sender_roles.contains(&role));
494 }
495 }
496 if let Some(role_str) = condition_name.strip_prefix("recipient_role_is_not_") {
497 if let Some(role) = MarketRole::from_suffix(role_str) {
498 return ConditionResult::from(!self.recipient_roles.contains(&role));
499 }
500 }
501 if let Some(role_str) = condition_name.strip_prefix("recipient_not_") {
503 if let Some(role) = MarketRole::from_suffix(role_str) {
504 return ConditionResult::from(!self.recipient_roles.contains(&role));
505 }
506 if let Some(roles) = MarketRole::parse_compound(role_str) {
507 return ConditionResult::from(
508 !roles.iter().any(|r| self.recipient_roles.contains(r)),
509 );
510 }
511 }
512 if let Some(role_str) = condition_name.strip_prefix("sender_not_") {
514 if let Some(role) = MarketRole::from_suffix(role_str) {
515 return ConditionResult::from(!self.sender_roles.contains(&role));
516 }
517 if let Some(roles) = MarketRole::parse_compound(role_str) {
518 return ConditionResult::from(!roles.iter().any(|r| self.sender_roles.contains(r)));
519 }
520 }
521 if let Some(role_str) = condition_name.strip_prefix("mp_id_role_is_") {
523 if let Some(role) = MarketRole::from_suffix(role_str) {
524 return ConditionResult::from(self.sender_roles.contains(&role));
525 }
526 }
527 ConditionResult::Unknown
528 }
529}
530
531enum KonfigurationenAlias {
544 AllCodes,
546 Category(&'static str),
548 MessproduktUnion,
550}
551
552fn resolve_konfigurationen_alias(condition_name: &str) -> Option<KonfigurationenAlias> {
553 match condition_name {
554 "is_konfigurationsprodukt_code" | "lin_prefix_is_valid_product_code" => {
555 Some(KonfigurationenAlias::AllCodes)
556 }
557 "is_messprodukt_code" => Some(KonfigurationenAlias::MessproduktUnion),
558 "messprodukts_typ2_smgw"
559 | "product_in_typ2_smgw_codelist"
560 | "order_contains_smgw_type2_product" => {
561 Some(KonfigurationenAlias::Category("messprodukt_typ2_smgw"))
562 }
563 "valid_adhoc_steuerkanal_product" => Some(KonfigurationenAlias::Category(
564 "config_product_adhoc_steuerkanal",
565 )),
566 "product_code_level_messlokation" => Some(KonfigurationenAlias::Category(
567 "messprodukt_standard_messlokation",
568 )),
569 "product_code_level_netzlokation" => Some(KonfigurationenAlias::Category(
570 "messprodukt_standard_netzlokation",
571 )),
572 "product_code_abrechnungsdaten_valid" => Some(KonfigurationenAlias::Category(
573 "produkte_aenderung_abrechnungsdaten",
574 )),
575 "lin_product_code_is_lokationsaenderung_strom" => Some(KonfigurationenAlias::Category(
576 "produkte_aenderung_lokation",
577 )),
578 _ => None,
579 }
580}
581
582const MESSPRODUKT_CATEGORY_PREFIXES: &[&str] = &[
584 "messprodukt_standard_",
585 "messprodukt_typ2_smgw",
586 "messprodukt_esa",
587];
588
589pub struct KonfigurationenProvider {
590 categories: HashMap<String, HashSet<String>>,
592 all_codes: HashSet<String>,
594 messprodukt_union: HashSet<String>,
596 product_code: Option<String>,
598}
599
600impl KonfigurationenProvider {
601 pub fn new(categories: HashMap<String, Vec<String>>, product_code: Option<String>) -> Self {
603 let mut all_codes = HashSet::new();
604 let categories: HashMap<String, HashSet<String>> = categories
605 .into_iter()
606 .map(|(k, v)| {
607 for code in &v {
608 all_codes.insert(code.clone());
609 }
610 (k, v.into_iter().collect())
611 })
612 .collect();
613
614 let mut messprodukt_union = HashSet::new();
616 for (name, codes) in &categories {
617 let is_messprodukt = MESSPRODUKT_CATEGORY_PREFIXES
618 .iter()
619 .any(|prefix| name.starts_with(prefix) || name == prefix);
620 if is_messprodukt {
621 messprodukt_union.extend(codes.iter().cloned());
622 }
623 }
624
625 Self {
626 categories,
627 all_codes,
628 messprodukt_union,
629 product_code,
630 }
631 }
632
633 pub fn from_json(json: &str, product_code: Option<String>) -> Result<Self, serde_json::Error> {
635 #[derive(serde::Deserialize)]
636 struct KonfigurationenFile {
637 categories: HashMap<String, Vec<String>>,
638 #[serde(default)]
639 all_product_codes: Vec<String>,
640 }
641 let file: KonfigurationenFile = serde_json::from_str(json)?;
642 let mut provider = Self::new(file.categories, product_code);
643 for code in file.all_product_codes {
645 provider.all_codes.insert(code);
646 }
647 Ok(provider)
648 }
649
650 pub fn from_json_file(
652 path: &std::path::Path,
653 product_code: Option<String>,
654 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
655 let json = std::fs::read_to_string(path)?;
656 Ok(Self::from_json(&json, product_code)?)
657 }
658}
659
660impl ExternalConditionProvider for KonfigurationenProvider {
661 fn evaluate(&self, condition_name: &str) -> ConditionResult {
662 let code = match &self.product_code {
663 Some(c) => c,
664 None => return ConditionResult::Unknown,
665 };
666
667 if condition_name == "code_list_membership_check" {
669 return ConditionResult::from(self.all_codes.contains(code.as_str()));
670 }
671
672 if let Some(alias) = resolve_konfigurationen_alias(condition_name) {
674 return match alias {
675 KonfigurationenAlias::AllCodes => {
676 ConditionResult::from(self.all_codes.contains(code.as_str()))
677 }
678 KonfigurationenAlias::Category(cat) => {
679 if let Some(set) = self.categories.get(cat) {
680 ConditionResult::from(set.contains(code.as_str()))
681 } else {
682 ConditionResult::Unknown
683 }
684 }
685 KonfigurationenAlias::MessproduktUnion => {
686 ConditionResult::from(self.messprodukt_union.contains(code.as_str()))
687 }
688 };
689 }
690
691 if let Some(set) = self.categories.get(condition_name) {
693 return ConditionResult::from(set.contains(code.as_str()));
694 }
695
696 ConditionResult::Unknown
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 #[test]
707 fn map_provider_returns_true_for_true_entry() {
708 let mut conditions = HashMap::new();
709 conditions.insert("DateKnown".to_string(), true);
710 let provider = MapExternalProvider::new(conditions);
711
712 assert_eq!(provider.evaluate("DateKnown"), ConditionResult::True);
713 }
714
715 #[test]
716 fn map_provider_returns_false_for_false_entry() {
717 let mut conditions = HashMap::new();
718 conditions.insert("MessageSplitting".to_string(), false);
719 let provider = MapExternalProvider::new(conditions);
720
721 assert_eq!(
722 provider.evaluate("MessageSplitting"),
723 ConditionResult::False
724 );
725 }
726
727 #[test]
728 fn map_provider_returns_unknown_for_missing_key() {
729 let mut conditions = HashMap::new();
730 conditions.insert("DateKnown".to_string(), true);
731 let provider = MapExternalProvider::new(conditions);
732
733 assert_eq!(provider.evaluate("NonExistent"), ConditionResult::Unknown);
734 }
735
736 #[test]
737 fn map_provider_empty_map_returns_unknown() {
738 let provider = MapExternalProvider::new(HashMap::new());
739
740 assert_eq!(provider.evaluate("Anything"), ConditionResult::Unknown);
741 }
742
743 #[test]
746 fn composite_first_known_wins() {
747 let mut p1_map = HashMap::new();
749 p1_map.insert("A".to_string(), true);
750 let p1 = MapExternalProvider::new(p1_map);
751
752 let mut p2_map = HashMap::new();
754 p2_map.insert("B".to_string(), false);
755 let p2 = MapExternalProvider::new(p2_map);
756
757 let composite = CompositeExternalProvider::new(vec![Box::new(p1), Box::new(p2)]);
758
759 assert_eq!(composite.evaluate("A"), ConditionResult::True);
761 assert_eq!(composite.evaluate("B"), ConditionResult::False);
763 }
764
765 #[test]
766 fn composite_all_unknown_returns_unknown() {
767 let p1 = MapExternalProvider::new(HashMap::new());
769 let p2 = MapExternalProvider::new(HashMap::new());
770
771 let composite = CompositeExternalProvider::new(vec![Box::new(p1), Box::new(p2)]);
772
773 assert_eq!(composite.evaluate("X"), ConditionResult::Unknown);
774 }
775
776 #[test]
777 fn composite_empty_returns_unknown() {
778 let composite = CompositeExternalProvider::new(vec![]);
779
780 assert_eq!(composite.evaluate("Anything"), ConditionResult::Unknown);
781 }
782
783 #[test]
786 fn test_code_list_provider_known_code() {
787 let mut lists = HashMap::new();
788 lists.insert(
789 "7111".to_string(),
790 vec!["Z91".to_string(), "Z90".to_string()],
791 );
792 let provider = CodeListProvider::new(lists);
793 assert_eq!(provider.evaluate("code_in_7111:Z91"), ConditionResult::True);
794 assert_eq!(
795 provider.evaluate("code_in_7111:ZZZ"),
796 ConditionResult::False
797 );
798 assert_eq!(
799 provider.evaluate("code_in_9999:Z91"),
800 ConditionResult::Unknown
801 );
802 }
803
804 #[test]
805 fn test_code_list_provider_loads_json() {
806 let json = r#"{
807 "7111": { "name": "Eigenschaft", "codes": [{"value": "Z91", "name": "MSB"}] },
808 "3225": { "name": "Ort", "codes": [{"value": "Z16", "name": "MaLo"}] }
809 }"#;
810 let provider = CodeListProvider::from_json(json).unwrap();
811 assert_eq!(provider.evaluate("code_in_7111:Z91"), ConditionResult::True);
812 assert_eq!(provider.evaluate("code_in_3225:Z16"), ConditionResult::True);
813 assert_eq!(
814 provider.evaluate("code_in_3225:Z99"),
815 ConditionResult::False
816 );
817 }
818
819 #[test]
820 fn test_code_list_provider_invalid_format() {
821 let mut lists = HashMap::new();
822 lists.insert("7111".to_string(), vec!["Z91".to_string()]);
823 let provider = CodeListProvider::new(lists);
824 assert_eq!(
825 provider.evaluate("not_a_code_check"),
826 ConditionResult::Unknown
827 );
828 assert_eq!(provider.evaluate("code_in_7111"), ConditionResult::Unknown);
829 }
831
832 #[test]
835 fn test_sector_provider_strom() {
836 let provider = SectorProvider::new(Sector::Strom);
837 assert_eq!(
838 provider.evaluate("recipient_is_strom"),
839 ConditionResult::True
840 );
841 assert_eq!(
842 provider.evaluate("recipient_is_gas"),
843 ConditionResult::False
844 );
845 assert_eq!(provider.evaluate("mp_id_is_strom"), ConditionResult::True);
846 assert_eq!(provider.evaluate("mp_id_is_gas"), ConditionResult::False);
847 assert_eq!(
848 provider.evaluate("market_location_is_electricity"),
849 ConditionResult::True
850 );
851 assert_eq!(
852 provider.evaluate("market_location_is_gas"),
853 ConditionResult::False
854 );
855 assert_eq!(
856 provider.evaluate("unrelated_condition"),
857 ConditionResult::Unknown
858 );
859 }
860
861 #[test]
862 fn test_sector_provider_gas() {
863 let provider = SectorProvider::new(Sector::Gas);
864 assert_eq!(
865 provider.evaluate("recipient_is_strom"),
866 ConditionResult::False
867 );
868 assert_eq!(provider.evaluate("recipient_is_gas"), ConditionResult::True);
869 assert_eq!(
870 provider.evaluate("recipient_is_msb_gas"),
871 ConditionResult::True
872 );
873 }
874
875 #[test]
876 fn test_sector_from_variant() {
877 assert!(SectorProvider::from_variant("Strom").is_some());
878 assert!(SectorProvider::from_variant("Gas").is_some());
879 assert!(SectorProvider::from_variant("Water").is_none());
880 }
881
882 #[test]
885 fn test_market_role_provider() {
886 let provider =
887 MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB, MarketRole::MSB]);
888 assert_eq!(provider.evaluate("sender_is_lf"), ConditionResult::True);
889 assert_eq!(provider.evaluate("sender_is_nb"), ConditionResult::False);
890 assert_eq!(provider.evaluate("recipient_is_nb"), ConditionResult::True);
891 assert_eq!(provider.evaluate("recipient_is_msb"), ConditionResult::True);
892 assert_eq!(provider.evaluate("recipient_is_lf"), ConditionResult::False);
893 assert_eq!(provider.evaluate("unrelated"), ConditionResult::Unknown);
894 }
895
896 #[test]
897 fn test_market_role_provider_negated() {
898 let provider = MarketRoleProvider::new(vec![MarketRole::MSB], vec![MarketRole::NB]);
899 assert_eq!(
900 provider.evaluate("sender_role_is_not_msb"),
901 ConditionResult::False
902 );
903 assert_eq!(
904 provider.evaluate("sender_role_is_not_lf"),
905 ConditionResult::True
906 );
907 assert_eq!(
908 provider.evaluate("recipient_role_is_not_nb"),
909 ConditionResult::False
910 );
911 assert_eq!(
912 provider.evaluate("recipient_role_is_not_lf"),
913 ConditionResult::True
914 );
915 }
916
917 #[test]
918 fn test_market_role_unknown_suffix() {
919 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![]);
920 assert_eq!(provider.evaluate("sender_is_xyz"), ConditionResult::Unknown);
922 }
923
924 #[test]
925 fn test_market_role_compound() {
926 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
927 assert_eq!(
929 provider.evaluate("recipient_is_lf_msb"),
930 ConditionResult::False
931 );
932 assert_eq!(
934 provider.evaluate("recipient_is_nb_lf"),
935 ConditionResult::True
936 );
937 assert_eq!(
939 provider.evaluate("recipient_is_lf_nb_msb"),
940 ConditionResult::True
941 );
942 assert_eq!(provider.evaluate("mp_id_role_is_lf"), ConditionResult::True);
944 }
945
946 #[test]
947 fn test_sector_provider_additional_names() {
948 let provider = SectorProvider::new(Sector::Strom);
949 assert_eq!(
950 provider.evaluate("mp_id_is_strom_sector"),
951 ConditionResult::True
952 );
953 assert_eq!(
954 provider.evaluate("marktpartner_is_strom"),
955 ConditionResult::True
956 );
957 assert_eq!(
958 provider.evaluate("recipient_is_gas_sector"),
959 ConditionResult::False
960 );
961 }
962
963 #[test]
964 fn test_composite_with_defaults() {
965 let composite = CompositeExternalProvider::with_defaults(
966 Some(Sector::Strom),
967 Some((vec![MarketRole::LF], vec![MarketRole::NB])),
968 None,
969 );
970 assert_eq!(
972 composite.evaluate("recipient_is_strom"),
973 ConditionResult::True
974 );
975 assert_eq!(composite.evaluate("sender_is_lf"), ConditionResult::True);
977 assert_eq!(composite.evaluate("some_unknown"), ConditionResult::Unknown);
979 }
980
981 fn make_konfigurationen_provider(product_code: Option<&str>) -> KonfigurationenProvider {
984 let mut categories = HashMap::new();
985 categories.insert(
986 "messprodukt_standard_marktlokation".to_string(),
987 vec!["9991000000044".to_string(), "9991000000052".to_string()],
988 );
989 categories.insert(
990 "messprodukt_standard_tranche".to_string(),
991 vec!["9991000000143".to_string()],
992 );
993 categories.insert(
994 "config_product_leistungskurve".to_string(),
995 vec!["9991000000721".to_string()],
996 );
997 KonfigurationenProvider::new(categories, product_code.map(String::from))
998 }
999
1000 #[test]
1001 fn test_konfigurationen_category_match() {
1002 let provider = make_konfigurationen_provider(Some("9991000000044"));
1003 assert_eq!(
1004 provider.evaluate("messprodukt_standard_marktlokation"),
1005 ConditionResult::True
1006 );
1007 assert_eq!(
1008 provider.evaluate("messprodukt_standard_tranche"),
1009 ConditionResult::False
1010 );
1011 assert_eq!(
1012 provider.evaluate("config_product_leistungskurve"),
1013 ConditionResult::False
1014 );
1015 }
1016
1017 #[test]
1018 fn test_konfigurationen_generic_membership() {
1019 let provider = make_konfigurationen_provider(Some("9991000000044"));
1020 assert_eq!(
1021 provider.evaluate("code_list_membership_check"),
1022 ConditionResult::True
1023 );
1024
1025 let provider2 = make_konfigurationen_provider(Some("0000000000000"));
1026 assert_eq!(
1027 provider2.evaluate("code_list_membership_check"),
1028 ConditionResult::False
1029 );
1030 }
1031
1032 #[test]
1033 fn test_konfigurationen_no_product_code() {
1034 let provider = make_konfigurationen_provider(None);
1035 assert_eq!(
1036 provider.evaluate("messprodukt_standard_marktlokation"),
1037 ConditionResult::Unknown
1038 );
1039 assert_eq!(
1040 provider.evaluate("code_list_membership_check"),
1041 ConditionResult::Unknown
1042 );
1043 }
1044
1045 #[test]
1046 fn test_konfigurationen_unknown_condition() {
1047 let provider = make_konfigurationen_provider(Some("9991000000044"));
1048 assert_eq!(
1049 provider.evaluate("completely_unrelated"),
1050 ConditionResult::Unknown
1051 );
1052 }
1053
1054 #[test]
1055 fn test_konfigurationen_from_json() {
1056 let json = r#"{
1057 "source": "Test",
1058 "categories": {
1059 "messprodukt_standard_marktlokation": ["9991000000044", "9991000000052"],
1060 "config_product_leistungskurve": ["9991000000721"]
1061 },
1062 "all_product_codes": ["9991000000044", "9991000000052", "9991000000721"]
1063 }"#;
1064 let provider =
1065 KonfigurationenProvider::from_json(json, Some("9991000000721".to_string())).unwrap();
1066 assert_eq!(
1067 provider.evaluate("messprodukt_standard_marktlokation"),
1068 ConditionResult::False
1069 );
1070 assert_eq!(
1071 provider.evaluate("config_product_leistungskurve"),
1072 ConditionResult::True
1073 );
1074 assert_eq!(
1075 provider.evaluate("code_list_membership_check"),
1076 ConditionResult::True
1077 );
1078 }
1079
1080 #[test]
1081 fn test_konfigurationen_specific_product() {
1082 let provider = make_konfigurationen_provider(Some("9991000000721"));
1084 assert_eq!(
1085 provider.evaluate("config_product_leistungskurve"),
1086 ConditionResult::True
1087 );
1088 assert_eq!(
1089 provider.evaluate("messprodukt_standard_marktlokation"),
1090 ConditionResult::False
1091 );
1092 }
1093
1094 #[test]
1097 fn test_country_plz_provider_de() {
1098 let provider = CountryPostalCodeProvider::new(Some("DE".to_string()));
1099 assert_eq!(
1100 provider.evaluate("country_code_has_plz"),
1101 ConditionResult::True
1102 );
1103 assert_eq!(
1104 provider.evaluate("country_has_postal_code"),
1105 ConditionResult::True
1106 );
1107 assert_eq!(
1108 provider.evaluate("country_has_postal_code_requirement"),
1109 ConditionResult::True
1110 );
1111 }
1112
1113 #[test]
1114 fn test_country_plz_provider_various_countries() {
1115 for code in &["AT", "CH", "FR", "NL", "GB", "NO", "IS", "LI"] {
1116 let provider = CountryPostalCodeProvider::new(Some(code.to_string()));
1117 assert_eq!(
1118 provider.evaluate("country_code_has_plz"),
1119 ConditionResult::True,
1120 "{code} should have PLZ"
1121 );
1122 }
1123 }
1124
1125 #[test]
1126 fn test_country_plz_provider_unknown_country() {
1127 let provider = CountryPostalCodeProvider::new(Some("XX".to_string()));
1128 assert_eq!(
1129 provider.evaluate("country_code_has_plz"),
1130 ConditionResult::False
1131 );
1132 }
1133
1134 #[test]
1135 fn test_country_plz_provider_no_country() {
1136 let provider = CountryPostalCodeProvider::new(None);
1137 assert_eq!(
1138 provider.evaluate("country_code_has_plz"),
1139 ConditionResult::Unknown
1140 );
1141 }
1142
1143 #[test]
1144 fn test_country_plz_provider_unrelated_condition() {
1145 let provider = CountryPostalCodeProvider::new(Some("DE".to_string()));
1146 assert_eq!(
1147 provider.evaluate("unrelated_condition"),
1148 ConditionResult::Unknown
1149 );
1150 }
1151
1152 #[test]
1155 fn test_sector_provider_sender_is_gas_sector() {
1156 let provider = SectorProvider::new(Sector::Gas);
1157 assert_eq!(
1158 provider.evaluate("sender_is_gas_sector"),
1159 ConditionResult::True
1160 );
1161 let provider_strom = SectorProvider::new(Sector::Strom);
1162 assert_eq!(
1163 provider_strom.evaluate("sender_is_gas_sector"),
1164 ConditionResult::False
1165 );
1166 }
1167
1168 #[test]
1171 fn test_market_role_mgv_kn() {
1172 let provider = MarketRoleProvider::new(vec![MarketRole::MGV], vec![MarketRole::KN]);
1173 assert_eq!(provider.evaluate("sender_is_mgv"), ConditionResult::True);
1174 assert_eq!(provider.evaluate("recipient_is_kn"), ConditionResult::True);
1175 assert_eq!(provider.evaluate("sender_is_kn"), ConditionResult::False);
1176 assert_eq!(
1177 provider.evaluate("recipient_is_mgv"),
1178 ConditionResult::False
1179 );
1180 }
1181
1182 #[test]
1185 fn test_market_role_or_compound() {
1186 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
1187 assert_eq!(
1189 provider.evaluate("recipient_is_lf_or_nb"),
1190 ConditionResult::True
1191 );
1192 assert_eq!(
1194 provider.evaluate("recipient_is_msb_or_mdl"),
1195 ConditionResult::False
1196 );
1197 }
1198
1199 #[test]
1202 fn test_market_role_recipient_not() {
1203 let provider = MarketRoleProvider::new(vec![MarketRole::LF], vec![MarketRole::NB]);
1204 assert_eq!(
1206 provider.evaluate("recipient_not_mgv_or_kn"),
1207 ConditionResult::True
1208 );
1209 assert_eq!(
1211 provider.evaluate("recipient_not_nb"),
1212 ConditionResult::False
1213 );
1214 assert_eq!(provider.evaluate("recipient_not_lf"), ConditionResult::True);
1216 assert_eq!(provider.evaluate("sender_not_lf"), ConditionResult::False);
1218 assert_eq!(
1220 provider.evaluate("sender_not_nb_or_msb"),
1221 ConditionResult::True
1222 );
1223 }
1224
1225 fn make_konfigurationen_provider_with_messprodukt(
1228 product_code: Option<&str>,
1229 ) -> KonfigurationenProvider {
1230 let mut categories = HashMap::new();
1231 categories.insert(
1232 "messprodukt_standard_marktlokation".to_string(),
1233 vec!["9991000000044".to_string(), "9991000000052".to_string()],
1234 );
1235 categories.insert(
1236 "messprodukt_standard_tranche".to_string(),
1237 vec!["9991000000143".to_string()],
1238 );
1239 categories.insert(
1240 "messprodukt_standard_messlokation".to_string(),
1241 vec!["9991000000200".to_string()],
1242 );
1243 categories.insert(
1244 "messprodukt_standard_netzlokation".to_string(),
1245 vec!["9991000000300".to_string()],
1246 );
1247 categories.insert(
1248 "messprodukt_typ2_smgw".to_string(),
1249 vec!["9991000000500".to_string()],
1250 );
1251 categories.insert(
1252 "messprodukt_esa".to_string(),
1253 vec!["9991000000600".to_string()],
1254 );
1255 categories.insert(
1256 "config_product_leistungskurve".to_string(),
1257 vec!["9991000000721".to_string()],
1258 );
1259 categories.insert(
1260 "config_product_adhoc_steuerkanal".to_string(),
1261 vec!["9991000000800".to_string()],
1262 );
1263 categories.insert(
1264 "produkte_aenderung_abrechnungsdaten".to_string(),
1265 vec!["9991000000900".to_string()],
1266 );
1267 categories.insert(
1268 "produkte_aenderung_lokation".to_string(),
1269 vec!["9991000001000".to_string()],
1270 );
1271 KonfigurationenProvider::new(categories, product_code.map(String::from))
1272 }
1273
1274 #[test]
1275 fn test_konfigurationen_alias_all_codes() {
1276 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000044"));
1277 assert_eq!(
1279 provider.evaluate("is_konfigurationsprodukt_code"),
1280 ConditionResult::True
1281 );
1282 assert_eq!(
1284 provider.evaluate("lin_prefix_is_valid_product_code"),
1285 ConditionResult::True
1286 );
1287
1288 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("0000000000000"));
1289 assert_eq!(
1290 provider2.evaluate("is_konfigurationsprodukt_code"),
1291 ConditionResult::False
1292 );
1293 }
1294
1295 #[test]
1296 fn test_konfigurationen_alias_messprodukt_union() {
1297 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000044"));
1299 assert_eq!(
1300 provider.evaluate("is_messprodukt_code"),
1301 ConditionResult::True
1302 );
1303
1304 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("9991000000500"));
1306 assert_eq!(
1307 provider2.evaluate("is_messprodukt_code"),
1308 ConditionResult::True
1309 );
1310
1311 let provider3 = make_konfigurationen_provider_with_messprodukt(Some("9991000000600"));
1313 assert_eq!(
1314 provider3.evaluate("is_messprodukt_code"),
1315 ConditionResult::True
1316 );
1317
1318 let provider4 = make_konfigurationen_provider_with_messprodukt(Some("9991000000721"));
1320 assert_eq!(
1321 provider4.evaluate("is_messprodukt_code"),
1322 ConditionResult::False
1323 );
1324 }
1325
1326 #[test]
1327 fn test_konfigurationen_alias_single_category() {
1328 let provider = make_konfigurationen_provider_with_messprodukt(Some("9991000000500"));
1329 assert_eq!(
1331 provider.evaluate("messprodukts_typ2_smgw"),
1332 ConditionResult::True
1333 );
1334 assert_eq!(
1336 provider.evaluate("product_in_typ2_smgw_codelist"),
1337 ConditionResult::True
1338 );
1339 assert_eq!(
1341 provider.evaluate("order_contains_smgw_type2_product"),
1342 ConditionResult::True
1343 );
1344
1345 let provider2 = make_konfigurationen_provider_with_messprodukt(Some("9991000000800"));
1346 assert_eq!(
1348 provider2.evaluate("valid_adhoc_steuerkanal_product"),
1349 ConditionResult::True
1350 );
1351
1352 let provider3 = make_konfigurationen_provider_with_messprodukt(Some("9991000000200"));
1353 assert_eq!(
1355 provider3.evaluate("product_code_level_messlokation"),
1356 ConditionResult::True
1357 );
1358
1359 let provider4 = make_konfigurationen_provider_with_messprodukt(Some("9991000000300"));
1360 assert_eq!(
1362 provider4.evaluate("product_code_level_netzlokation"),
1363 ConditionResult::True
1364 );
1365
1366 let provider5 = make_konfigurationen_provider_with_messprodukt(Some("9991000000900"));
1367 assert_eq!(
1369 provider5.evaluate("product_code_abrechnungsdaten_valid"),
1370 ConditionResult::True
1371 );
1372
1373 let provider6 = make_konfigurationen_provider_with_messprodukt(Some("9991000001000"));
1374 assert_eq!(
1376 provider6.evaluate("lin_product_code_is_lokationsaenderung_strom"),
1377 ConditionResult::True
1378 );
1379 }
1380
1381 #[test]
1384 fn test_builder_with_country_code() {
1385 let composite = CompositeExternalProvider::builder()
1386 .country_code(Some("DE".to_string()))
1387 .build();
1388 assert_eq!(
1389 composite.evaluate("country_code_has_plz"),
1390 ConditionResult::True
1391 );
1392 }
1393}