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