1use std::fmt;
2
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5
6use crate::{json, money::Cost, number, Money, Number};
7
8#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)]
10#[serde(transparent)]
11pub struct Kwh(Number);
12
13impl json::FromJson<'_, '_> for Kwh {
14 type WarningKind = number::WarningKind;
15
16 fn from_json(elem: &'_ json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
17 Ok(Number::from_json(elem)?.map(Kwh))
18 }
19}
20
21impl Cost for Kwh {
22 fn cost(&self, price: Money) -> Money {
23 let cost = self.0.saturating_mul(price.into());
24 Money::from_number(cost)
25 }
26}
27
28impl Kwh {
29 #[expect(dead_code, reason = "Soon to be used in generate feature")]
30 pub(crate) fn from_decimal(d: Decimal) -> Self {
31 Self(Number::from_decimal(d))
32 }
33
34 pub fn zero() -> Self {
35 Self(Number::default())
36 }
37
38 #[must_use]
40 pub fn saturating_add(self, other: Self) -> Self {
41 Self(self.0.saturating_add(other.0))
42 }
43
44 #[must_use]
46 pub fn saturating_sub(self, other: Self) -> Self {
47 Self(self.0.saturating_sub(other.0))
48 }
49
50 pub fn watt_hours(self) -> Number {
51 self.0.saturating_mul(Number::from(1000))
52 }
53
54 #[expect(clippy::missing_panics_doc, reason = "divisor is non-zero")]
55 #[expect(clippy::unwrap_used, reason = "divisor is non-zero")]
56 pub fn from_watt_hours(num: Number) -> Self {
57 Self(num.checked_div(Number::from(1000)).unwrap())
58 }
59
60 #[must_use]
62 pub fn rescale(self) -> Self {
63 Self(self.0.rescale())
64 }
65
66 #[must_use]
67 pub fn round_dp(self, digits: u32) -> Self {
68 Self(self.0.round_dp(digits))
69 }
70}
71
72impl From<Kwh> for Decimal {
73 fn from(value: Kwh) -> Self {
74 value.0.into()
75 }
76}
77
78impl From<Kwh> for Number {
79 fn from(value: Kwh) -> Self {
80 value.0
81 }
82}
83
84impl fmt::Display for Kwh {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "{:.4}", self.0)
87 }
88}
89
90#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
92#[serde(transparent)]
93pub struct Kw(Number);
94
95impl json::FromJson<'_, '_> for Kw {
96 type WarningKind = number::WarningKind;
97
98 fn from_json(elem: &'_ json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
99 Ok(Number::from_json(elem)?.map(Kw))
100 }
101}
102
103impl From<Kw> for Decimal {
104 fn from(value: Kw) -> Self {
105 value.0.into()
106 }
107}
108
109#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
111#[serde(transparent)]
112pub struct Ampere(Number);
113
114impl json::FromJson<'_, '_> for Ampere {
115 type WarningKind = number::WarningKind;
116
117 fn from_json(elem: &'_ json::Element<'_>) -> crate::Verdict<Self, Self::WarningKind> {
118 Ok(Number::from_json(elem)?.map(Ampere))
119 }
120}
121
122impl From<Ampere> for Decimal {
123 fn from(value: Ampere) -> Self {
124 value.0.into()
125 }
126}
127
128#[cfg(test)]
129mod test {
130 use crate::Number;
131
132 use super::{Ampere, Kw, Kwh};
133
134 impl From<u64> for Kw {
135 fn from(value: u64) -> Self {
136 Self(value.into())
137 }
138 }
139
140 impl From<u64> for Kwh {
141 fn from(value: u64) -> Self {
142 Self(value.into())
143 }
144 }
145
146 impl From<Number> for Ampere {
147 fn from(value: Number) -> Self {
148 Self(value)
149 }
150 }
151}