use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
const BEHG_EUR_PER_T: &[(i32, u32)] = &[
(2021, 25),
(2022, 30),
(2023, 30),
(2024, 45),
(2025, 55),
(2026, 65),
];
pub const BEHG_CO2_FACTOR_H_GAS: Decimal = dec!(0.20160);
pub const BEHG_CO2_FACTOR_L_GAS: Decimal = dec!(0.20140);
const STROMSTEUER_HISTORY: &[(i32, &str)] = &[
(2003, "2.05"),
(2004, "2.05"),
(2005, "2.05"),
(2006, "2.05"),
(2007, "2.05"),
(2008, "2.05"),
(2009, "2.05"),
(2010, "2.05"),
(2011, "2.05"),
(2012, "2.05"),
(2013, "2.05"),
(2014, "2.05"),
(2015, "2.05"),
(2016, "2.05"),
(2017, "2.05"),
(2018, "2.05"),
(2019, "2.05"),
(2020, "2.05"),
(2021, "2.05"),
(2022, "2.05"),
(2023, "2.05"),
(2024, "2.05"),
(2025, "2.05"),
(2026, "2.05"),
];
#[must_use]
pub fn stromsteuer_for_year(year: i32) -> Option<Decimal> {
STROMSTEUER_HISTORY
.iter()
.find(|(y, _)| *y == year)
.map(|(_, rate)| rate.parse().expect("rate is a valid decimal literal"))
}
const ENERGIESTEUER_GAS_HISTORY: &[(i32, &str)] = &[
(2022, "0.00"),
(2023, "0.55"),
(2024, "0.55"),
(2025, "0.55"),
(2026, "0.55"),
];
#[must_use]
pub fn energiesteuer_gas_for_year(year: i32) -> Option<Decimal> {
ENERGIESTEUER_GAS_HISTORY
.iter()
.find(|(y, _)| *y == year)
.map(|(_, rate)| rate.parse().expect("rate is a valid decimal literal"))
}
#[must_use]
pub fn behg_ct_per_kwh_for_year(year: i32) -> Option<Decimal> {
BEHG_EUR_PER_T
.iter()
.find(|(y, _)| *y == year)
.map(|(_, eur_per_t)| {
Decimal::from(*eur_per_t) * BEHG_CO2_FACTOR_H_GAS / dec!(10)
})
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegulatoryRates {
pub stromsteuer_ct_per_kwh: Decimal,
pub energiesteuer_gas_ct_per_kwh: Decimal,
pub behg_gas_ct_per_kwh: Decimal,
pub mwst_rate: Decimal,
}
impl Default for RegulatoryRates {
fn default() -> Self {
Self {
stromsteuer_ct_per_kwh: dec!(2.05),
energiesteuer_gas_ct_per_kwh: dec!(0.55),
behg_gas_ct_per_kwh: dec!(1.310), mwst_rate: dec!(0.19),
}
}
}
impl RegulatoryRates {
#[must_use]
pub fn effective_mwst_electricity(&self, p: &crate::tariff::ElectricityProduct) -> Decimal {
if let Some(r) = p.mwst_rate_override {
return r;
}
if p.anlage_kwp.is_some_and(|kwp| kwp <= dec!(30)) {
return Decimal::ZERO;
}
self.mwst_rate
}
#[must_use]
pub fn effective_mwst_solar(&self, p: &crate::tariff::SolarProduct) -> Decimal {
if let Some(r) = p.mwst_rate_override {
return r;
}
if p.anlage_kwp.is_some_and(|kwp| kwp <= dec!(30)) {
return Decimal::ZERO;
}
self.mwst_rate
}
#[must_use]
pub fn effective_mwst_eeg(&self, p: &crate::tariff::EegProduct) -> Decimal {
if let Some(r) = p.mwst_rate_override {
return r;
}
if p.anlage_kwp.is_some_and(|kwp| kwp <= dec!(30)) {
return Decimal::ZERO;
}
self.mwst_rate
}
#[must_use]
pub fn effective_stromsteuer(&self, override_ct: Option<Decimal>) -> Decimal {
override_ct.unwrap_or(self.stromsteuer_ct_per_kwh)
}
#[must_use]
pub fn effective_energiesteuer_gas(&self, override_ct: Option<Decimal>) -> Decimal {
override_ct.unwrap_or(self.energiesteuer_gas_ct_per_kwh)
}
#[must_use]
pub fn effective_behg_gas(&self, override_ct: Option<Decimal>) -> Decimal {
override_ct.unwrap_or(self.behg_gas_ct_per_kwh)
}
#[must_use]
pub fn effective_behg_gas_for_year(&self, override_ct: Option<Decimal>, year: i32) -> Decimal {
if let Some(o) = override_ct {
return o;
}
behg_ct_per_kwh_for_year(year).unwrap_or(self.behg_gas_ct_per_kwh)
}
#[must_use]
pub fn effective_stromsteuer_for_year(
&self,
override_ct: Option<Decimal>,
year: i32,
) -> Decimal {
if let Some(o) = override_ct {
return o;
}
stromsteuer_for_year(year).unwrap_or(self.stromsteuer_ct_per_kwh)
}
#[must_use]
pub fn effective_energiesteuer_gas_for_year(
&self,
override_ct: Option<Decimal>,
year: i32,
) -> Decimal {
if let Some(o) = override_ct {
return o;
}
energiesteuer_gas_for_year(year).unwrap_or(self.energiesteuer_gas_ct_per_kwh)
}
#[must_use]
pub fn effective_mwst_with_override(
&self,
override_rate: Option<Decimal>,
anlage_kwp: Option<Decimal>,
) -> Decimal {
if let Some(r) = override_rate {
return r;
}
if anlage_kwp.is_some_and(|kwp| kwp <= dec!(30)) {
return Decimal::ZERO;
}
self.mwst_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn behg_year_table_2026_matches_expected() {
let ct = behg_ct_per_kwh_for_year(2026).unwrap();
let expected = dec!(65) * dec!(0.20160) / dec!(10);
assert_eq!(ct, expected);
}
#[test]
fn behg_year_table_2024_matches_expected() {
let ct = behg_ct_per_kwh_for_year(2024).unwrap();
let expected = dec!(45) * dec!(0.20160) / dec!(10);
assert_eq!(ct, expected);
}
#[test]
fn behg_unknown_year_returns_none() {
assert!(behg_ct_per_kwh_for_year(2020).is_none());
assert!(behg_ct_per_kwh_for_year(2030).is_none());
}
#[test]
fn effective_behg_for_year_prefers_override() {
let rates = RegulatoryRates::default();
assert_eq!(
rates.effective_behg_gas_for_year(Some(dec!(0.99)), 2024),
dec!(0.99)
);
let ct = rates.effective_behg_gas_for_year(None, 2024);
assert!(ct > dec!(0.90) && ct < dec!(0.92));
}
}