1use std::slice::Iter;
2
3use chrono::DateTime;
4use chrono_tz::Tz;
5use serde::{Serialize, Serializer, ser::SerializeSeq};
6
7use crate::{
8 Country,
9 defs::{Hours, Month, Months},
10 money::Money,
11};
12
13#[derive(Debug, Clone, Copy, Serialize)]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15#[serde(tag = "type", content = "value")]
16pub enum Cost {
17 None,
18 Unverified,
20 Fixed(Money),
21 Fuses(&'static [(u16, Money)]),
22 FusesYearlyConsumption(&'static [(u16, Option<u32>, Money)]),
24 FuseRange(&'static [(u16, u16, Money)]),
25}
26
27impl Cost {
28 pub const fn is_unverified(&self) -> bool {
29 matches!(self, Self::Unverified)
30 }
31
32 pub(super) const fn fuses(values: &'static [(u16, Money)]) -> Self {
33 Self::Fuses(values)
34 }
35
36 pub(super) const fn fuse_range(ranges: &'static [(u16, u16, Money)]) -> Self {
37 Self::FuseRange(ranges)
38 }
39
40 pub(super) const fn fuses_with_yearly_consumption(
41 values: &'static [(u16, Option<u32>, Money)],
42 ) -> Cost {
43 Self::FusesYearlyConsumption(values)
44 }
45
46 pub(super) const fn fixed(int: i64, fract: u8) -> Self {
47 Self::Fixed(Money::new(int, fract))
48 }
49
50 pub(super) const fn fixed_yearly(int: i64, fract: u8) -> Self {
51 Self::Fixed(Money::new(int, fract).divide_by(12))
52 }
53
54 pub(super) const fn fixed_subunit(subunit: f64) -> Self {
55 Self::Fixed(Money::new_subunit(subunit))
56 }
57
58 pub(super) const fn divide_by(&self, by: i64) -> Self {
59 match self {
60 Self::None => Self::None,
61 Self::Unverified => Self::Unverified,
62 Self::Fixed(money) => Self::Fixed(money.divide_by(by)),
63 Self::Fuses(items) => panic!(".divide_by() is unsupported on Cost::Fuses"),
64 Self::FusesYearlyConsumption(items) => {
65 panic!(".divide_by() is unsupported on Cost::FuseRangeYearlyConsumption")
66 }
67 Self::FuseRange(items) => panic!(".divide_by() is unsupported on Cost::FuseRange"),
68 }
69 }
70
71 pub const fn cost_for(&self, fuse_size: u16, yearly_consumption: u32) -> Option<Money> {
72 match *self {
73 Cost::None => None,
74 Cost::Unverified => None,
75 Cost::Fixed(money) => Some(money),
76 Cost::Fuses(values) => {
77 let mut i = 0;
78 while i < values.len() {
79 let (fsize, money) = values[i];
80 if fuse_size == fsize {
81 return Some(money);
82 }
83 i += 1;
84 }
85 None
86 }
87 Cost::FusesYearlyConsumption(values) => {
88 let mut i = 0;
89 while i < values.len() {
90 let (fsize, max_consumption, money) = values[i];
91 if fsize == fuse_size {
92 if let Some(max_consumption) = max_consumption {
93 if max_consumption <= yearly_consumption {
94 return Some(money);
95 }
96 } else {
97 return Some(money);
98 }
99 }
100 i += 1;
101 }
102 None
103 }
104 Cost::FuseRange(ranges) => {
105 let mut i = 0;
106 while i < ranges.len() {
107 let (min, max, money) = ranges[i];
108 if fuse_size >= min && fuse_size <= max {
109 return Some(money);
110 }
111 i += 1;
112 }
113 None
114 }
115 }
116 }
117
118 pub(crate) const fn add_vat(&self, country: Country) -> Cost {
119 let rate = match country {
120 Country::SE => 1.25,
121 };
122 match self {
123 Cost::None => Cost::None,
124 Cost::Unverified => Cost::Unverified,
125 Cost::Fixed(money) => Cost::Fixed(money.add_vat(country)),
126 Cost::Fuses(items) => todo!(),
127 Cost::FusesYearlyConsumption(items) => todo!(),
128 Cost::FuseRange(items) => todo!(),
129 }
130 }
131
132 pub(crate) fn is_yearly_consumption_based(&self, fuse_size: u16) -> bool {
133 match self {
134 Cost::FusesYearlyConsumption(items) => items
135 .iter()
136 .filter(|(fsize, _, _)| *fsize == fuse_size)
137 .any(|(_, yearly_consumption, _)| yearly_consumption.is_some()),
138 _ => false,
139 }
140 }
141}
142
143#[derive(Debug, Clone, Copy, Serialize)]
144#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
145pub struct CostPeriods {
146 periods: &'static [CostPeriod],
147}
148
149impl CostPeriods {
150 pub(super) const fn new(periods: &'static [CostPeriod]) -> Self {
151 Self { periods }
152 }
153
154 pub(super) fn iter(&self) -> Iter<'_, CostPeriod> {
155 self.periods.iter()
156 }
157
158 pub(crate) fn is_yearly_consumption_based(&self, fuse_size: u16) -> bool {
159 self.periods
160 .iter()
161 .any(|cp| cp.is_yearly_consumption_based(fuse_size))
162 }
163}
164
165#[derive(Debug, Clone, Serialize)]
167#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
168pub struct CostPeriodsSimple {
169 periods: Vec<CostPeriodSimple>,
170}
171
172impl CostPeriodsSimple {
173 pub(crate) fn new(periods: CostPeriods, fuse_size: u16, yearly_consumption: u32) -> Self {
174 Self {
175 periods: periods
176 .periods
177 .iter()
178 .map(|period| CostPeriodSimple::new(period, fuse_size, yearly_consumption))
179 .flatten()
180 .collect(),
181 }
182 }
183}
184
185#[derive(Debug, Clone, Serialize)]
186#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
187pub(super) struct CostPeriod {
188 cost: Cost,
189 load: LoadType,
190 #[serde(serialize_with = "skip_nones")]
191 include: [Option<PeriodType>; 2],
192 #[serde(serialize_with = "skip_nones")]
193 exclude: [Option<PeriodType>; 2],
194 divide_kw_by: u8,
196}
197
198#[derive(Debug, Clone, Serialize)]
200#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
201pub(super) struct CostPeriodSimple {
202 cost: Money,
203 load: LoadType,
204 include: Vec<PeriodType>,
205 exclude: Vec<PeriodType>,
206 divide_kw_by: u8,
208}
209
210impl CostPeriodSimple {
211 fn new(period: &CostPeriod, fuse_size: u16, yearly_consumption: u32) -> Option<Self> {
212 let Some(cost) = period.cost().cost_for(fuse_size, yearly_consumption) else {
213 return None;
214 };
215 Some(Self {
216 cost,
217 load: period.load,
218 include: period.include.into_iter().flatten().collect(),
219 exclude: period.exclude.into_iter().flatten().collect(),
220 divide_kw_by: period.divide_kw_by,
221 })
222 }
223}
224
225impl CostPeriod {
226 pub(super) const fn builder() -> CostPeriodBuilder {
227 CostPeriodBuilder::new()
228 }
229
230 pub const fn cost(&self) -> Cost {
231 self.cost
232 }
233
234 pub const fn load(&self) -> LoadType {
235 self.load
236 }
237
238 pub fn matches(&self, _timestamp: DateTime<Tz>) -> bool {
239 for _period_type in self.include_period_types() {
240 }
243 todo!()
244 }
245
246 fn include_period_types(&self) -> Vec<PeriodType> {
247 self.include.iter().flatten().copied().collect()
248 }
249
250 fn exclude_period_types(&self) -> Vec<PeriodType> {
251 self.exclude.iter().flatten().copied().collect()
252 }
253
254 fn is_yearly_consumption_based(&self, fuse_size: u16) -> bool {
255 self.cost.is_yearly_consumption_based(fuse_size)
256 }
257}
258
259#[derive(Debug, Clone, Copy, Serialize)]
260#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
261pub enum LoadType {
262 Base,
264 Low,
266 High,
268}
269
270pub(super) use LoadType::*;
271
272#[derive(Clone)]
273pub(super) struct CostPeriodBuilder {
274 cost: Cost,
275 load: Option<LoadType>,
276 include: [Option<PeriodType>; 2],
277 exclude: [Option<PeriodType>; 2],
278 divide_kw_by: u8,
280}
281
282impl CostPeriodBuilder {
283 pub(super) const fn new() -> Self {
284 Self {
285 cost: Cost::None,
286 load: None,
287 include: [None; 2],
288 exclude: [None; 2],
289 divide_kw_by: 1,
290 }
291 }
292
293 pub(super) const fn build(self) -> CostPeriod {
294 CostPeriod {
295 cost: self.cost,
296 load: self.load.expect("`load` must be specified"),
297 include: self.include,
298 exclude: self.exclude,
299 divide_kw_by: self.divide_kw_by,
300 }
301 }
302
303 pub(super) const fn cost(mut self, cost: Cost) -> Self {
304 self.cost = cost;
305 self
306 }
307
308 pub(super) const fn load(mut self, load: LoadType) -> Self {
309 self.load = Some(load);
310 self
311 }
312
313 pub(super) const fn fixed_cost(mut self, int: i64, fract: u8) -> Self {
314 self.cost = Cost::fixed(int, fract);
315 self
316 }
317
318 pub(super) const fn fixed_cost_subunit(mut self, subunit: f64) -> Self {
319 self.cost = Cost::fixed_subunit(subunit);
320 self
321 }
322
323 pub(super) const fn include(mut self, period_type: PeriodType) -> Self {
324 let mut i = 0;
325 while i < self.include.len() {
326 if self.include[i].is_some() {
327 i += 1;
328 } else {
329 self.include[i] = Some(period_type);
330 return self;
331 }
332 }
333 panic!("Too many includes");
334 }
335
336 pub(super) const fn months(self, from: Month, to: Month) -> Self {
337 self.include(PeriodType::Months(Months::new(from, to)))
338 }
339
340 pub(super) const fn month(self, month: Month) -> Self {
341 self.include(PeriodType::Month(month))
342 }
343
344 pub(super) const fn hours(self, from: u8, to_inclusive: u8) -> Self {
345 self.include(PeriodType::Hours(Hours::new(from, to_inclusive)))
346 }
347
348 pub(super) const fn exclude(mut self, period_type: PeriodType) -> Self {
349 let mut i = 0;
350 while i < self.exclude.len() {
351 if self.exclude[i].is_some() {
352 i += 1;
353 } else {
354 self.exclude[i] = Some(period_type);
355 return self;
356 }
357 }
358 panic!("Too many excludes");
359 }
360
361 pub(super) const fn exclude_weekends_and_swedish_holidays(self) -> Self {
362 self.exclude_weekends().exclude(PeriodType::SwedishHolidays)
363 }
364
365 pub(super) const fn exclude_weekends(self) -> Self {
366 self.exclude(PeriodType::Weekends)
367 }
368
369 pub(super) const fn divide_kw_by(mut self, value: u8) -> Self {
370 self.divide_kw_by = value;
371 self
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::Cost;
378 use crate::money::Money;
379
380 #[test]
381 fn fuse_based_cost() {
382 const FUSE_BASED: Cost = Cost::fuse_range(&[
383 (16, 35, Money::new(54, 0)),
384 (35, u16::MAX, Money::new(108, 50)),
385 ]);
386 assert_eq!(FUSE_BASED.cost_for(10, 0), None);
387 assert_eq!(FUSE_BASED.cost_for(25, 0), Some(Money::new(54, 0)));
388 assert_eq!(FUSE_BASED.cost_for(200, 0), Some(Money::new(108, 50)));
389 }
390}
391
392#[derive(Debug, Clone, Copy, Serialize)]
393#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
394#[serde(tag = "type", content = "value")]
395pub(super) enum PeriodType {
396 Months(Months),
397 Month(Month),
398 Hours(Hours),
399 Weekends,
400 SwedishHolidays,
401}
402
403fn skip_nones<S>(items: &[Option<PeriodType>; 2], serializer: S) -> Result<S::Ok, S::Error>
404where
405 S: Serializer,
406{
407 let filtered: Vec<_> = items.iter().filter_map(|x| x.as_ref()).collect();
408 let mut seq = serializer.serialize_seq(Some(filtered.len()))?;
409 for item in filtered {
410 seq.serialize_element(item)?;
411 }
412 seq.end()
413}