use crate::rounding::RoundMoney;
use rust_decimal::Decimal;
use rust_decimal::dec;
use time::{Date, Month};
pub const DEGRESSIONSBEGINN: Date = time::macros::date!(2024 - 02 - 01);
pub const DEGRESSIONSSATZ: Decimal = dec!(0.01);
#[must_use]
pub fn degressionsstufen(inbetriebnahme: Date) -> u32 {
if inbetriebnahme < DEGRESSIONSBEGINN {
return 0;
}
let months = (inbetriebnahme.year() - DEGRESSIONSBEGINN.year()) * 12
+ (inbetriebnahme.month() as i32 - DEGRESSIONSBEGINN.month() as i32);
(months as u32) / 6 + 1
}
#[must_use]
pub fn degressionsfenster(inbetriebnahme: Date) -> Option<Date> {
let stufen = degressionsstufen(inbetriebnahme);
if stufen == 0 {
return None;
}
let months_after_start = (stufen - 1) * 6;
let total = u32::from(DEGRESSIONSBEGINN.month() as u8 - 1) + months_after_start;
let year = DEGRESSIONSBEGINN.year() + (total / 12) as i32;
let month = Month::try_from((total % 12 + 1) as u8).ok()?;
Date::from_calendar_date(year, month, 1).ok()
}
#[must_use]
pub fn abgesenkter_wert(basiswert_ct: Decimal, stufen: u32) -> Decimal {
abgesenkter_wert_ungerundet(basiswert_ct, stufen).round_kfm(2)
}
#[must_use]
pub fn abgesenkter_wert_ungerundet(basiswert_ct: Decimal, stufen: u32) -> Decimal {
let factor = Decimal::ONE - DEGRESSIONSSATZ;
(0..stufen).fold(basiswert_ct, |acc, _| acc * factor)
}
#[must_use]
pub fn anzulegender_wert_bei_inbetriebnahme(
basiswert_ct: Decimal,
inbetriebnahme: Date,
) -> Decimal {
abgesenkter_wert(basiswert_ct, degressionsstufen(inbetriebnahme))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct JaehrlicheAbsenkung {
pub beginn: Date,
pub satz: Decimal,
pub rechtsgrundlage: &'static str,
}
impl JaehrlicheAbsenkung {
pub const WASSERKRAFT: Self = Self {
beginn: time::macros::date!(2024 - 01 - 01),
satz: dec!(0.005),
rechtsgrundlage: "§ 40 Abs. 5 EEG 2023",
};
pub const GASE: Self = Self {
beginn: time::macros::date!(2024 - 01 - 01),
satz: dec!(0.015),
rechtsgrundlage: "§ 41 Abs. 4 EEG 2023",
};
pub const BIOMASSE: Self = Self {
beginn: time::macros::date!(2024 - 07 - 01),
satz: dec!(0.005),
rechtsgrundlage: "§ 44a EEG 2023",
};
pub const GEOTHERMIE: Self = Self {
beginn: time::macros::date!(2024 - 01 - 01),
satz: dec!(0.005),
rechtsgrundlage: "§ 45 Abs. 2 EEG 2023",
};
#[must_use]
pub fn stufen(self, inbetriebnahme: Date) -> u32 {
if inbetriebnahme < self.beginn {
return 0;
}
let mut jahre = u32::try_from(inbetriebnahme.year() - self.beginn.year()).unwrap_or(0);
let anniversary_passed = (inbetriebnahme.month() as u8, inbetriebnahme.day())
>= (self.beginn.month() as u8, self.beginn.day());
if !anniversary_passed {
jahre = jahre.saturating_sub(1);
}
jahre + 1
}
#[must_use]
pub fn anzulegender_wert(self, basiswert_ct: Decimal, inbetriebnahme: Date) -> Decimal {
let faktor = Decimal::ONE - self.satz;
(0..self.stufen(inbetriebnahme))
.fold(basiswert_ct, |acc, _| acc * faktor)
.round_kfm(2)
}
}
#[cfg(test)]
mod jaehrliche_absenkung_tests {
use super::JaehrlicheAbsenkung;
use rust_decimal::dec;
use time::macros::date;
#[test]
fn before_the_first_step_nothing_is_deducted() {
let a = JaehrlicheAbsenkung::WASSERKRAFT;
assert_eq!(a.stufen(date!(2023 - 06 - 01)), 0);
assert_eq!(
a.anzulegender_wert(dec!(12.03), date!(2023 - 06 - 01)),
dec!(12.03)
);
}
#[test]
fn hydro_degresses_half_a_percent_a_year() {
let a = JaehrlicheAbsenkung::WASSERKRAFT;
assert_eq!(a.stufen(date!(2024 - 03 - 01)), 1);
assert_eq!(
a.anzulegender_wert(dec!(12.03), date!(2024 - 03 - 01)),
dec!(11.97)
);
assert_eq!(a.stufen(date!(2026 - 03 - 01)), 3);
assert_eq!(
a.anzulegender_wert(dec!(12.03), date!(2026 - 03 - 01)),
dec!(11.85)
);
}
#[test]
fn biomass_steps_on_the_first_of_july() {
let a = JaehrlicheAbsenkung::BIOMASSE;
assert_eq!(a.stufen(date!(2024 - 05 - 01)), 0);
assert_eq!(a.stufen(date!(2024 - 07 - 01)), 1);
assert_eq!(a.stufen(date!(2025 - 06 - 30)), 1);
assert_eq!(a.stufen(date!(2025 - 07 - 01)), 2);
}
#[test]
fn the_gases_degress_one_and_a_half_percent() {
let a = JaehrlicheAbsenkung::GASE;
assert_eq!(
a.anzulegender_wert(dec!(5.93), date!(2024 - 06 - 01)),
dec!(5.84)
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use time::macros::date;
#[test]
fn sect49_windows_are_semiannual_from_2024_02_01() {
assert_eq!(degressionsfenster(date!(2023 - 12 - 31)), None);
assert_eq!(degressionsfenster(date!(2024 - 01 - 31)), None);
for (ibn, fenster) in [
(date!(2024 - 02 - 01), date!(2024 - 02 - 01)),
(date!(2024 - 07 - 31), date!(2024 - 02 - 01)),
(date!(2024 - 08 - 01), date!(2024 - 08 - 01)),
(date!(2025 - 01 - 31), date!(2024 - 08 - 01)),
(date!(2025 - 02 - 01), date!(2025 - 02 - 01)),
(date!(2025 - 08 - 15), date!(2025 - 08 - 01)),
(date!(2026 - 02 - 01), date!(2026 - 02 - 01)),
(date!(2026 - 08 - 09), date!(2026 - 08 - 01)),
] {
assert_eq!(degressionsfenster(ibn), Some(fenster), "{ibn}");
}
}
#[test]
fn sect49_satz2_compounds_unrounded() {
assert_eq!(abgesenkter_wert(dec!(8.60), 2), dec!(8.43));
assert_ne!(
abgesenkter_wert(dec!(8.51), 1),
abgesenkter_wert(dec!(8.60), 2)
);
}
#[test]
fn sect49_reproduces_the_published_bnetza_series() {
for (ibn, aw) in [
(date!(2023 - 06 - 01), dec!(8.60)),
(date!(2024 - 02 - 01), dec!(8.51)),
(date!(2024 - 08 - 01), dec!(8.43)),
(date!(2025 - 02 - 01), dec!(8.34)),
(date!(2025 - 08 - 01), dec!(8.26)),
(date!(2026 - 02 - 01), dec!(8.18)),
(date!(2026 - 08 - 01), dec!(8.10)),
] {
assert_eq!(
anzulegender_wert_bei_inbetriebnahme(dec!(8.60), ibn),
aw,
"window of {ibn}"
);
}
}
#[test]
fn sect49_published_series_for_the_larger_brackets() {
for (base, expected) in [
(
dec!(7.50),
[dec!(7.43), dec!(7.35), dec!(7.28), dec!(7.20), dec!(7.13)],
),
(
dec!(6.20),
[dec!(6.14), dec!(6.08), dec!(6.02), dec!(5.96), dec!(5.90)],
),
] {
for (stufe, aw) in expected.iter().enumerate() {
assert_eq!(
abgesenkter_wert(base, stufe as u32 + 1),
*aw,
"base {base}, step {}",
stufe + 1
);
}
}
}
#[test]
fn zero_steps_returns_the_base() {
assert_eq!(abgesenkter_wert(dec!(8.60), 0), dec!(8.60));
assert_eq!(degressionsstufen(date!(2023 - 01 - 01)), 0);
}
}