1use rust_decimal::Decimal;
2use rust_decimal_macros::dec;
3
4use crate::{
5 impl_dec_newtype, json,
6 money::Cost,
7 number::{self, approx_eq_dec, FromDecimal as _, IsZero},
8 Money,
9};
10
11impl_dec_newtype!(Ampere, "A");
12impl_dec_newtype!(Kw, "kW");
13impl_dec_newtype!(Kwh, "kWh");
14
15#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)]
17#[cfg_attr(test, derive(serde::Deserialize))]
18pub struct Kwh(Decimal);
19
20impl IsZero for Kwh {
21 fn is_zero(&self) -> bool {
22 const TOLERANCE: Decimal = dec!(0.001);
23
24 approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
25 }
26}
27
28impl Cost for Kwh {
29 fn cost(&self, money: Money) -> Money {
30 let cost = self.0.saturating_mul(money.into());
31 Money::from_decimal(cost)
32 }
33}
34
35const KILO: Decimal = dec!(1000);
36
37impl Kwh {
38 #[must_use]
39 pub(crate) const fn zero() -> Self {
40 Self(Decimal::ZERO)
41 }
42
43 pub fn watt_hours(self) -> Decimal {
44 self.0.saturating_mul(KILO)
45 }
46
47 #[expect(clippy::missing_panics_doc, reason = "divisor is non-zero")]
48 #[expect(clippy::unwrap_used, reason = "divisor is non-zero")]
49 pub fn from_watt_hours(num: Decimal) -> Self {
50 Self(num.checked_div(KILO).unwrap())
51 }
52}
53
54#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
56pub struct Kw(Decimal);
57
58impl IsZero for Kw {
59 fn is_zero(&self) -> bool {
60 const TOLERANCE: Decimal = dec!(0.001);
61
62 approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
63 }
64}
65
66#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
68pub struct Ampere(Decimal);
69
70impl IsZero for Ampere {
71 fn is_zero(&self) -> bool {
72 const TOLERANCE: Decimal = dec!(0.001);
73
74 approx_eq_dec(&self.0, &Decimal::ZERO, TOLERANCE)
75 }
76}