use rust_decimal::Decimal;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum BiomassBrennstoff {
PflanzlicheBiomasse,
BiomethanAusBiomasse,
Guelle,
Festmist,
Holzbiomasse,
Klaegas,
Deponiegas,
Grubengas,
BiogenicWaste,
}
impl BiomassBrennstoff {
#[must_use]
pub fn guellebonusanlage_eligible(self) -> bool {
matches!(self, Self::Guelle | Self::Festmist)
}
#[must_use]
pub fn has_holzbiomasse_restriction(self) -> bool {
self == Self::Holzbiomasse
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BiomassSettlementData {
pub hauptbrennstoff: BiomassBrennstoff,
pub guelle_anteil: Decimal,
pub ist_guellebonusanlage: bool,
pub energiepflanzen_anteil: Decimal,
pub substrate_cap_ok: bool,
}
impl BiomassSettlementData {
#[must_use]
pub fn new(
hauptbrennstoff: BiomassBrennstoff,
guelle_anteil: Decimal,
energiepflanzen_anteil: Decimal,
leistung_kw: Decimal,
) -> Self {
use rust_decimal::dec;
let ist_guellebonusanlage = hauptbrennstoff.guellebonusanlage_eligible()
&& guelle_anteil >= dec!(0.80)
&& leistung_kw <= dec!(75);
let substrate_cap_ok = energiepflanzen_anteil <= dec!(0.40);
Self {
hauptbrennstoff,
guelle_anteil,
ist_guellebonusanlage,
energiepflanzen_anteil,
substrate_cap_ok,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::dec;
#[test]
fn guellebonusanlage_qualifies_when_criteria_met() {
let data = BiomassSettlementData::new(
BiomassBrennstoff::Guelle,
dec!(0.85), dec!(0.05), dec!(50), );
assert!(data.ist_guellebonusanlage);
assert!(data.substrate_cap_ok);
}
#[test]
fn guellebonusanlage_disqualified_by_capacity() {
let data = BiomassSettlementData::new(
BiomassBrennstoff::Guelle,
dec!(0.90), dec!(0.05),
dec!(100), );
assert!(!data.ist_guellebonusanlage, "capacity > 75 kW → no bonus");
}
#[test]
fn substrate_cap_exceeded() {
let data = BiomassSettlementData::new(
BiomassBrennstoff::PflanzlicheBiomasse,
dec!(0.0),
dec!(0.50), dec!(100),
);
assert!(!data.substrate_cap_ok);
}
}
#[must_use]
pub fn bemessungsleistung_stunden(
kalenderjahr: i32,
erstmalige_erzeugung: Option<time::Date>,
) -> Option<rust_decimal::Decimal> {
use rust_decimal::Decimal;
let jahresbeginn =
time::Date::from_calendar_date(kalenderjahr, time::Month::January, 1).ok()?;
let naechstes_jahr =
time::Date::from_calendar_date(kalenderjahr + 1, time::Month::January, 1).ok()?;
let start = match erstmalige_erzeugung {
Some(d) if d >= naechstes_jahr => return Some(Decimal::ZERO),
Some(d) if d > jahresbeginn => d,
_ => jahresbeginn,
};
let tage = (naechstes_jahr - start).whole_days();
Some(Decimal::from(tage) * Decimal::from(24))
}
#[must_use]
pub fn sect44b_jahreskontingent_kwh(
leistung_kw: rust_decimal::Decimal,
kalenderjahr: i32,
erstmalige_erzeugung: Option<time::Date>,
) -> rust_decimal::Decimal {
use rust_decimal::dec;
let stunden =
bemessungsleistung_stunden(kalenderjahr, erstmalige_erzeugung).unwrap_or(dec!(8760));
(leistung_kw * dec!(0.45) * stunden).round_dp(3)
}
#[cfg(test)]
mod bemessungsleistung_tests {
use super::*;
use rust_decimal::dec;
use time::macros::date;
#[test]
fn a_leap_year_has_more_hours() {
assert_eq!(bemessungsleistung_stunden(2027, None), Some(dec!(8760)));
assert_eq!(bemessungsleistung_stunden(2028, None), Some(dec!(8784)));
assert_eq!(
sect44b_jahreskontingent_kwh(dec!(500), 2028, None)
- sect44b_jahreskontingent_kwh(dec!(500), 2027, None),
dec!(5400)
);
}
#[test]
fn hours_before_the_first_generation_are_deducted() {
assert_eq!(
bemessungsleistung_stunden(2027, Some(date!(2027 - 07 - 01))),
Some(dec!(4416))
);
assert_eq!(
bemessungsleistung_stunden(2027, Some(date!(2020 - 03 - 01))),
Some(dec!(8760))
);
assert_eq!(
bemessungsleistung_stunden(2027, Some(date!(2028 - 01 - 01))),
Some(dec!(0))
);
}
}