use time::Date;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
pub enum NetzzugangRegime {
Nzv,
EnwgFestlegung,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
pub enum EntgeltRegime {
Verordnung,
AgNeS,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub struct RegulatoryRegime {
netzzugang: NetzzugangRegime,
entgelt: EntgeltRegime,
tarifjahr: i32,
}
const NZV_LETZTER_TAG: Date = time::macros::date!(2025 - 12 - 31);
const VERORDNUNG_LETZTER_TAG: Date = time::macros::date!(2028 - 12 - 31);
pub const SECT19_ABS3_LETZTER_TAG: Date = time::macros::date!(2025 - 12 - 31);
pub const SECT19_ABS3_UEBERGANG_ENDE: Date = time::macros::date!(2028 - 12 - 31);
impl RegulatoryRegime {
#[must_use]
pub fn for_period(period_from: Date, period_to: Date) -> Self {
let _ = period_from;
Self {
netzzugang: if period_to <= NZV_LETZTER_TAG {
NetzzugangRegime::Nzv
} else {
NetzzugangRegime::EnwgFestlegung
},
entgelt: if period_to <= VERORDNUNG_LETZTER_TAG {
EntgeltRegime::Verordnung
} else {
EntgeltRegime::AgNeS
},
tarifjahr: period_from.year(),
}
}
#[must_use]
pub const fn new(netzzugang: NetzzugangRegime, entgelt: EntgeltRegime, tarifjahr: i32) -> Self {
Self {
netzzugang,
entgelt,
tarifjahr,
}
}
#[must_use]
pub const fn netzzugang(&self) -> NetzzugangRegime {
self.netzzugang
}
#[must_use]
pub const fn entgelt(&self) -> EntgeltRegime {
self.entgelt
}
#[must_use]
pub const fn tarifjahr(&self) -> i32 {
self.tarifjahr
}
pub fn ensure_berechenbar(&self) -> Result<(), crate::error::BillingError> {
match self.entgelt {
EntgeltRegime::Verordnung => Ok(()),
EntgeltRegime::AgNeS => Err(crate::error::BillingError::UnsupportedEntgeltRegime {
tarifjahr: self.tarifjahr,
}),
}
}
#[must_use]
pub fn straddles_turnover(period_from: Date, period_to: Date) -> bool {
let start = Self::for_period(period_from, period_from);
let end = Self::for_period(period_to, period_to);
start.netzzugang != end.netzzugang || start.entgelt != end.entgelt
}
}
#[cfg(test)]
mod tests {
use super::*;
use time::macros::date;
#[test]
fn the_nzv_regime_ends_with_2025() {
let last = RegulatoryRegime::for_period(date!(2025 - 12 - 01), date!(2025 - 12 - 31));
assert_eq!(last.netzzugang(), NetzzugangRegime::Nzv);
let first_after =
RegulatoryRegime::for_period(date!(2026 - 01 - 01), date!(2026 - 01 - 31));
assert_eq!(first_after.netzzugang(), NetzzugangRegime::EnwgFestlegung);
}
#[test]
fn the_verordnung_regime_ends_with_2028() {
let last = RegulatoryRegime::for_period(date!(2028 - 12 - 01), date!(2028 - 12 - 31));
assert_eq!(last.entgelt(), EntgeltRegime::Verordnung);
let first_after =
RegulatoryRegime::for_period(date!(2029 - 01 - 01), date!(2029 - 01 - 31));
assert_eq!(first_after.entgelt(), EntgeltRegime::AgNeS);
}
#[test]
fn a_period_is_governed_by_the_rules_at_its_end() {
let straddling = RegulatoryRegime::for_period(date!(2025 - 12 - 15), date!(2026 - 01 - 15));
assert_eq!(straddling.netzzugang(), NetzzugangRegime::EnwgFestlegung);
}
#[test]
fn a_straddling_period_is_detected() {
assert!(RegulatoryRegime::straddles_turnover(
date!(2025 - 12 - 15),
date!(2026 - 01 - 15)
));
assert!(RegulatoryRegime::straddles_turnover(
date!(2028 - 12 - 15),
date!(2029 - 01 - 15)
));
assert!(!RegulatoryRegime::straddles_turnover(
date!(2026 - 01 - 01),
date!(2026 - 12 - 31)
));
}
#[test]
fn an_explicit_regime_overrides_the_calendar() {
let historical =
RegulatoryRegime::new(NetzzugangRegime::Nzv, EntgeltRegime::Verordnung, 2024);
assert_eq!(historical.netzzugang(), NetzzugangRegime::Nzv);
assert_eq!(historical.tarifjahr(), 2024);
}
#[test]
fn the_tarifjahr_follows_the_period_start() {
let r = RegulatoryRegime::for_period(date!(2026 - 03 - 01), date!(2026 - 03 - 31));
assert_eq!(r.tarifjahr(), 2026);
}
#[test]
fn an_agnes_regime_is_not_berechenbar() {
let last_verordnung =
RegulatoryRegime::for_period(date!(2028 - 12 - 01), date!(2028 - 12 - 31));
assert!(last_verordnung.ensure_berechenbar().is_ok());
let agnes = RegulatoryRegime::for_period(date!(2029 - 01 - 01), date!(2029 - 01 - 31));
let err = agnes
.ensure_berechenbar()
.expect_err("an AgNeS period must be refused");
assert!(matches!(
err,
crate::error::BillingError::UnsupportedEntgeltRegime { tarifjahr: 2029 }
));
let msg = err.to_string();
assert!(msg.contains("GBK-25-01"), "must name the Festlegung: {msg}");
assert!(
msg.contains("nicht") || msg.contains("not yet festgelegt"),
"must say the parameter tables are not yet festgelegt: {msg}"
);
}
#[test]
fn the_regulatory_surface_is_re_exported_at_the_crate_root() {
use crate::{
EntgeltRegime as ReexportedEntgelt, NetzzugangRegime as ReexportedNetzzugang,
RegulatoryRegime as ReexportedRegime, SECT19_ABS3_LETZTER_TAG,
SECT19_ABS3_UEBERGANG_ENDE,
};
let r = ReexportedRegime::new(
ReexportedNetzzugang::EnwgFestlegung,
ReexportedEntgelt::Verordnung,
2026,
);
assert_eq!(r.tarifjahr(), 2026);
assert!(SECT19_ABS3_LETZTER_TAG < SECT19_ABS3_UEBERGANG_ENDE);
}
}