use rust_decimal::Decimal;
use crate::interval::MeterInterval;
#[derive(Debug, Clone)]
pub struct GasConversionParams {
pub hs_kwh_per_m3: Decimal,
pub zustandszahl: Decimal,
}
impl GasConversionParams {
#[must_use]
pub fn default_erdgas_h() -> Self {
Self {
hs_kwh_per_m3: Decimal::from_str_exact("10.55").unwrap_or(Decimal::from(10u32)),
zustandszahl: Decimal::ONE,
}
}
}
#[must_use]
pub fn gas_m3_to_kwh_hs(
volume_m3: Decimal,
hs_kwh_per_m3: Decimal,
zustandszahl: Decimal,
) -> Decimal {
volume_m3 * hs_kwh_per_m3 * zustandszahl
}
#[must_use]
pub fn normalize_interval_to_kwh(
interval: &MeterInterval,
unit: &str,
gas_params: Option<&GasConversionParams>,
) -> Decimal {
match unit.to_lowercase().as_str() {
"m3" | "m³" => {
let p = gas_params
.map(|p| (p.hs_kwh_per_m3, p.zustandszahl))
.unwrap_or_else(|| {
let d = GasConversionParams::default_erdgas_h();
(d.hs_kwh_per_m3, d.zustandszahl)
});
gas_m3_to_kwh_hs(interval.value_kwh, p.0, p.1)
}
"kw" => {
let dur_h = Decimal::from(interval.duration_secs()) / Decimal::from(3600u32);
interval.value_kwh * dur_h
}
_ => interval.value_kwh, }
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::dec;
#[test]
fn gas_conversion_exact() {
let kwh = gas_m3_to_kwh_hs(dec!(100), dec!(10.55), dec!(1.0));
assert_eq!(kwh, dec!(1055.00));
}
#[test]
fn gas_conversion_with_zustandszahl() {
let kwh = gas_m3_to_kwh_hs(dec!(50), dec!(10.80), dec!(0.9800));
assert_eq!(kwh, dec!(529.2000));
}
#[test]
fn gas_conversion_zero_volume() {
assert_eq!(gas_m3_to_kwh_hs(dec!(0), dec!(10.55), dec!(1.0)), dec!(0));
}
#[test]
fn default_erdgas_h_params() {
let p = GasConversionParams::default_erdgas_h();
assert_eq!(p.hs_kwh_per_m3, dec!(10.55));
assert_eq!(p.zustandszahl, Decimal::ONE);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct WarmWaterAdjustments {
pub brennwert_erdgas: bool,
pub eigenstaendige_gewerbliche_waermelieferung: bool,
pub monovalente_waermepumpe: bool,
}
impl WarmWaterAdjustments {
pub const NONE: Self = Self {
brennwert_erdgas: false,
eigenstaendige_gewerbliche_waermelieferung: false,
monovalente_waermepumpe: false,
};
fn apply(self, base: Decimal) -> Decimal {
let mut q = base;
if self.brennwert_erdgas {
q *= Decimal::from_str_exact("1.11").unwrap_or(Decimal::ONE);
}
if self.eigenstaendige_gewerbliche_waermelieferung {
q /= Decimal::from_str_exact("1.15").unwrap_or(Decimal::ONE);
}
if self.monovalente_waermepumpe {
q *= Decimal::from_str_exact("0.30").unwrap_or(Decimal::ONE);
}
q
}
}
#[must_use]
pub fn warm_water_heat_kwh(
volume_m3: Decimal,
mean_temp_c: Decimal,
adjustments: WarmWaterAdjustments,
) -> Decimal {
let factor = Decimal::from_str_exact("2.5").unwrap_or(Decimal::from(2u32));
let cold_inlet = Decimal::from(10u32);
adjustments.apply(factor * volume_m3 * (mean_temp_c - cold_inlet))
}
#[must_use]
pub fn warm_water_heat_kwh_unmetered(
flaeche_m2: Decimal,
adjustments: WarmWaterAdjustments,
) -> Decimal {
adjustments.apply(Decimal::from(32u32) * flaeche_m2)
}
#[cfg(test)]
mod warm_water_tests {
use super::*;
fn d(s: &str) -> Decimal {
Decimal::from_str_exact(s).unwrap()
}
#[test]
fn metered_warm_water_follows_the_statutory_formula() {
assert_eq!(
warm_water_heat_kwh(
Decimal::from(40u32),
Decimal::from(60u32),
WarmWaterAdjustments::NONE
),
Decimal::from(5000u32)
);
}
#[test]
fn at_and_below_cold_inlet_temperature() {
assert_eq!(
warm_water_heat_kwh(
Decimal::from(40u32),
Decimal::from(10u32),
WarmWaterAdjustments::NONE
),
Decimal::ZERO
);
assert!(
warm_water_heat_kwh(
Decimal::from(40u32),
Decimal::from(5u32),
WarmWaterAdjustments::NONE
) < Decimal::ZERO
);
}
#[test]
fn adjustments_match_the_statutory_factors() {
let v = Decimal::from(40u32);
let t = Decimal::from(60u32);
let base = Decimal::from(5000u32);
let brennwert = WarmWaterAdjustments {
brennwert_erdgas: true,
..WarmWaterAdjustments::NONE
};
assert_eq!(warm_water_heat_kwh(v, t, brennwert), base * d("1.11"));
let wp = WarmWaterAdjustments {
monovalente_waermepumpe: true,
..WarmWaterAdjustments::NONE
};
assert_eq!(warm_water_heat_kwh(v, t, wp), base * d("0.30"));
let gewerblich = WarmWaterAdjustments {
eigenstaendige_gewerbliche_waermelieferung: true,
..WarmWaterAdjustments::NONE
};
assert_eq!(warm_water_heat_kwh(v, t, gewerblich), base / d("1.15"));
}
#[test]
fn adjustments_compose() {
let both = WarmWaterAdjustments {
eigenstaendige_gewerbliche_waermelieferung: true,
monovalente_waermepumpe: true,
..WarmWaterAdjustments::NONE
};
let q = warm_water_heat_kwh(Decimal::from(40u32), Decimal::from(60u32), both);
assert_eq!(q, Decimal::from(5000u32) / d("1.15") * d("0.30"));
}
#[test]
fn unmetered_fallback_uses_floor_area_and_takes_adjustments() {
assert_eq!(
warm_water_heat_kwh_unmetered(Decimal::from(75u32), WarmWaterAdjustments::NONE),
Decimal::from(2400u32)
);
let brennwert = WarmWaterAdjustments {
brennwert_erdgas: true,
..WarmWaterAdjustments::NONE
};
assert_eq!(
warm_water_heat_kwh_unmetered(Decimal::from(75u32), brennwert),
Decimal::from(2400u32) * d("1.11")
);
}
}