use rust_decimal::Decimal;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct NetworkLosses {
#[cfg_attr(feature = "serde", serde(with = "crate::wire::decimal"))]
einspeisung_kwh: Decimal,
#[cfg_attr(feature = "serde", serde(with = "crate::wire::decimal"))]
entnahme_kwh: Decimal,
}
impl NetworkLosses {
#[must_use]
pub fn new(einspeisung_kwh: Decimal, entnahme_kwh: Decimal) -> Option<Self> {
einspeisung_kwh.checked_sub(entnahme_kwh)?;
Some(Self {
einspeisung_kwh,
entnahme_kwh,
})
}
#[must_use]
pub const fn einspeisung_kwh(&self) -> Decimal {
self.einspeisung_kwh
}
#[must_use]
pub const fn entnahme_kwh(&self) -> Decimal {
self.entnahme_kwh
}
#[must_use]
pub fn verlust_kwh(&self) -> Decimal {
self.einspeisung_kwh - self.entnahme_kwh
}
#[must_use]
pub fn verlust_prozent(&self) -> Option<Decimal> {
if self.einspeisung_kwh <= Decimal::ZERO {
return None;
}
Some(
self.verlust_kwh()
.checked_mul(Decimal::ONE_HUNDRED)?
.checked_div(self.einspeisung_kwh)?
.round_dp_with_strategy(
crate::precision::PERCENT_DP,
crate::precision::PERCENT_STRATEGY,
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::dec;
#[test]
fn a_typical_distribution_grid_shows_single_digit_losses() {
let l = NetworkLosses::new(dec!(1_000_000), dec!(955_000)).unwrap();
assert_eq!(l.verlust_kwh(), dec!(45_000));
assert_eq!(l.verlust_prozent(), Some(dec!(4.50)));
}
#[test]
fn negative_balance_signals_a_metering_coverage_gap() {
let l = NetworkLosses::new(dec!(100), dec!(120)).unwrap();
assert_eq!(l.verlust_kwh(), dec!(-20));
assert_eq!(l.verlust_prozent(), Some(dec!(-20.00)));
}
#[test]
fn zero_infeed_yields_no_percentage() {
assert_eq!(
NetworkLosses::new(Decimal::ZERO, dec!(10))
.unwrap()
.verlust_prozent(),
None
);
assert_eq!(NetworkLosses::new(Decimal::MIN, Decimal::MAX), None);
}
}