use rust_decimal::Decimal;
use time::OffsetDateTime;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum Sparte {
#[default]
Strom,
Gas,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "SCREAMING_SNAKE_CASE"))]
pub enum QualityFlag {
Measured,
Estimated,
Substituted,
Calculated,
#[default]
Unknown,
}
impl QualityFlag {
#[must_use]
pub fn is_billable(&self) -> bool {
matches!(
self,
QualityFlag::Measured | QualityFlag::Calculated | QualityFlag::Substituted
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct MeterInterval {
pub from: OffsetDateTime,
pub to: OffsetDateTime,
pub value_kwh: Decimal,
pub quality: QualityFlag,
pub obis_code: Option<String>,
}
impl MeterInterval {
#[must_use]
pub fn duration_secs(&self) -> i64 {
(self.to - self.from).whole_seconds()
}
#[must_use]
pub fn duration_minutes(&self) -> i64 {
(self.to - self.from).whole_minutes()
}
#[must_use]
pub fn demand_kw(&self) -> Option<Decimal> {
let h = Decimal::from(self.duration_secs()) / Decimal::from(3600u32);
if h.is_zero() {
None
} else {
Some(self.value_kwh / h)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
use time::macros::datetime;
#[test]
fn demand_kw_15min_interval() {
let iv = MeterInterval {
from: datetime!(2026-01-01 0:00 UTC),
to: datetime!(2026-01-01 0:15 UTC),
value_kwh: dec!(2.5),
quality: QualityFlag::Measured,
obis_code: None,
};
assert_eq!(iv.demand_kw(), Some(dec!(10)));
}
#[test]
fn demand_kw_hourly() {
let iv = MeterInterval {
from: datetime!(2026-01-01 0:00 UTC),
to: datetime!(2026-01-01 1:00 UTC),
value_kwh: dec!(5.0),
quality: QualityFlag::Measured,
obis_code: None,
};
assert_eq!(iv.demand_kw(), Some(dec!(5)));
}
#[test]
fn quality_flag_billable() {
assert!(QualityFlag::Measured.is_billable());
assert!(QualityFlag::Substituted.is_billable());
assert!(!QualityFlag::Estimated.is_billable());
assert!(!QualityFlag::Unknown.is_billable());
}
}