use rust_decimal::Decimal;
#[cfg(feature = "serde")]
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[non_exhaustive]
pub struct ImbalanceSaldo {
#[cfg_attr(feature = "serde", serde(with = "crate::wire::decimal"))]
actual_kwh: Decimal,
#[cfg_attr(feature = "serde", serde(with = "crate::wire::decimal"))]
bilanziert_kwh: Decimal,
}
impl ImbalanceSaldo {
#[must_use]
pub fn new(actual_kwh: Decimal, bilanziert_kwh: Decimal) -> Option<Self> {
actual_kwh.checked_sub(bilanziert_kwh)?;
Some(Self {
actual_kwh,
bilanziert_kwh,
})
}
#[must_use]
pub const fn actual_kwh(&self) -> Decimal {
self.actual_kwh
}
#[must_use]
pub const fn bilanziert_kwh(&self) -> Decimal {
self.bilanziert_kwh
}
#[must_use]
pub fn delta_kwh(&self) -> Decimal {
self.actual_kwh - self.bilanziert_kwh
}
#[must_use]
pub fn mehr_kwh(&self) -> Decimal {
(-self.delta_kwh()).max(Decimal::ZERO)
}
#[must_use]
pub fn minder_kwh(&self) -> Decimal {
self.delta_kwh().max(Decimal::ZERO)
}
#[must_use]
pub fn is_mehr(&self) -> bool {
self.mehr_kwh() > Decimal::ZERO
}
#[must_use]
pub fn is_minder(&self) -> bool {
self.minder_kwh() > Decimal::ZERO
}
#[must_use]
pub fn is_balanced(&self) -> bool {
self.delta_kwh().is_zero()
}
#[must_use]
pub fn delta_pct(&self) -> Option<Decimal> {
if self.bilanziert_kwh.is_zero() {
return None;
}
Some(
self.delta_kwh()
.checked_mul(Decimal::ONE_HUNDRED)?
.checked_div(self.bilanziert_kwh)?
.round_dp_with_strategy(
crate::precision::PERCENT_DP,
crate::precision::PERCENT_STRATEGY,
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::dec;
fn saldo(actual: Decimal, bilanziert: Decimal) -> ImbalanceSaldo {
ImbalanceSaldo::new(actual, bilanziert).unwrap()
}
#[test]
fn over_consumption_is_a_mindermenge() {
let s = saldo(dec!(1050), dec!(1000));
assert_eq!(s.minder_kwh(), dec!(50));
assert_eq!(s.mehr_kwh(), Decimal::ZERO);
assert_eq!(s.delta_kwh(), dec!(50));
assert_eq!(s.delta_pct(), Some(dec!(5)));
}
#[test]
fn under_consumption_is_a_mehrmenge() {
let s = saldo(dec!(950), dec!(1000));
assert_eq!(s.mehr_kwh(), dec!(50));
assert_eq!(s.minder_kwh(), Decimal::ZERO);
assert!(s.is_mehr() && !s.is_minder());
}
#[test]
fn a_balanced_period_and_a_zero_profile() {
let s = saldo(dec!(1000), dec!(1000));
assert!(s.is_balanced());
assert_eq!(s.delta_pct(), Some(Decimal::ZERO));
assert_eq!(saldo(dec!(100), Decimal::ZERO).delta_pct(), None);
}
#[test]
fn the_saldo_reconstructs_its_inputs() {
for (a, b) in [
(dec!(900), dec!(1000)),
(dec!(1100), dec!(1000)),
(dec!(5), dec!(5)),
] {
let s = saldo(a, b);
assert_eq!(
s.bilanziert_kwh() + s.minder_kwh() - s.mehr_kwh(),
s.actual_kwh()
);
assert!(!(s.is_mehr() && s.is_minder()));
}
assert_eq!(ImbalanceSaldo::new(Decimal::MIN, Decimal::MAX), None);
}
}