1use std::collections::HashMap;
2
3use chrono::DateTime;
4use chrono_tz::Tz;
5
6use crate::{CostPeriod, CostPeriodMatching, CostPeriods, TariffCalculationMethod};
7
8#[derive(Clone, Debug)]
9pub struct PeakPeriods {
10 items: Vec<PeriodDemand>,
11}
12
13impl PeakPeriods {
14 pub fn new(
15 calc_method: TariffCalculationMethod,
16 periods: CostPeriods,
17 mut averages: Vec<AverageDemand>,
18 ) -> Self {
19 let mut periods_averages_map = HashMap::new();
20
21 for (p_idx, period) in periods.iter().enumerate() {
22 let mut averages_for_period = vec![];
23
24 for a_idx in (0..averages.len()).rev() {
25 if period.matches(averages[a_idx].timestamp) {
26 averages_for_period.push(averages[a_idx].clone());
27
28 if periods.match_method() == CostPeriodMatching::First {
29 averages.remove(a_idx);
30 }
31 }
32 }
33
34 periods_averages_map.insert(p_idx, averages_for_period);
35 }
36
37 let items = periods
38 .clone()
39 .iter()
40 .enumerate()
41 .map(|(p_idx, period)| PeriodDemand {
42 period: period.clone(),
43 peaks: PeakDemands::new(calc_method, periods_averages_map.remove(&p_idx).unwrap()),
44 })
45 .collect();
46
47 Self { items }
48 }
49
50 pub fn items(&self) -> &[PeriodDemand] {
51 &self.items
52 }
53}
54
55#[derive(Clone, Debug)]
56pub struct PeriodDemand {
57 period: CostPeriod,
58 peaks: PeakDemands,
59}
60
61impl PeriodDemand {
62 pub fn period(&self) -> &CostPeriod {
63 &self.period
64 }
65 pub fn peaks(&self) -> &PeakDemands {
66 &self.peaks
67 }
68}
69
70#[derive(Clone, Debug, PartialEq)]
71pub struct AverageDemand {
72 timestamp: DateTime<Tz>,
73 value: u32,
74}
75
76impl AverageDemand {
77 pub fn new<Dt: Into<DateTime<Tz>>>(timestamp: Dt, value: u32) -> Self {
78 Self {
79 timestamp: timestamp.into(),
80 value,
81 }
82 }
83
84 pub fn timestamp(&self) -> DateTime<Tz> {
85 self.timestamp
86 }
87
88 pub fn kw(&self) -> f64 {
89 self.value as f64 / 1000.
90 }
91}
92
93#[derive(Default, Clone, Debug)]
94pub struct PeakDemands(Vec<AverageDemand>);
95
96impl PeakDemands {
97 pub fn new(
98 calc_method: TariffCalculationMethod,
99 mut period_averages: Vec<AverageDemand>,
100 ) -> Self {
101 let peak_demands: Vec<AverageDemand> = match calc_method {
102 crate::TariffCalculationMethod::AverageDays(n) => {
103 let mut daily_peaks: HashMap<chrono::NaiveDate, AverageDemand> = HashMap::new();
105
106 for demand in period_averages.clone() {
108 let date = demand.timestamp.date_naive();
109 daily_peaks
110 .entry(date)
111 .and_modify(|existing| {
112 if demand.value > existing.value {
113 *existing = demand.clone();
114 }
115 })
116 .or_insert(demand);
117 }
118
119 let mut daily_peaks_vec: Vec<AverageDemand> = daily_peaks.into_values().collect();
121 daily_peaks_vec.sort_by(|a, b| b.value.cmp(&a.value));
122
123 daily_peaks_vec.into_iter().take(n as usize).collect()
125 }
126 crate::TariffCalculationMethod::AverageHours(n) => {
127 period_averages.sort_by(|a, b| b.value.cmp(&a.value));
129 period_averages.into_iter().take(n as usize).collect()
130 }
131 };
132
133 Self(peak_demands)
134 }
135
136 pub fn values(&self) -> &[AverageDemand] {
137 &self.0
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::{Country, Stockholm, costs::LoadType, months::Month};
145 use chrono::{Datelike, Timelike};
146
147 #[test]
148 fn average_hours_returns_n_highest_values() {
149 let averages = vec![
150 AverageDemand::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100),
151 AverageDemand::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 500),
152 AverageDemand::new(Stockholm.dt(2025, 1, 1, 2, 0, 0), 300),
153 AverageDemand::new(Stockholm.dt(2025, 1, 1, 3, 0, 0), 200),
154 AverageDemand::new(Stockholm.dt(2025, 1, 1, 4, 0, 0), 400),
155 ];
156
157 let peaks = PeakDemands::new(TariffCalculationMethod::AverageHours(3), averages);
158
159 assert_eq!(peaks.values().len(), 3);
160 assert_eq!(peaks.values()[0].value, 500);
161 assert_eq!(peaks.values()[1].value, 400);
162 assert_eq!(peaks.values()[2].value, 300);
163 }
164
165 #[test]
166 fn average_hours_with_equal_values() {
167 let averages = vec![
168 AverageDemand::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 500),
169 AverageDemand::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 500),
170 AverageDemand::new(Stockholm.dt(2025, 1, 1, 2, 0, 0), 300),
171 ];
172
173 let peaks = PeakDemands::new(TariffCalculationMethod::AverageHours(2), averages);
174
175 assert_eq!(peaks.values().len(), 2);
176 assert_eq!(peaks.values()[0].value, 500);
177 assert_eq!(peaks.values()[1].value, 500);
178 }
179
180 #[test]
181 fn average_hours_empty_input() {
182 let averages = vec![];
183
184 let peaks = PeakDemands::new(TariffCalculationMethod::AverageHours(3), averages);
185
186 assert_eq!(peaks.values().len(), 0);
187 }
188
189 #[test]
190 fn average_hours_zero_n() {
191 let averages = vec![AverageDemand::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100)];
192
193 let peaks = PeakDemands::new(TariffCalculationMethod::AverageHours(0), averages);
194
195 assert_eq!(peaks.values().len(), 0);
196 }
197
198 #[test]
199 fn average_hours_n_greater_than_available() {
200 let averages = vec![
201 AverageDemand::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100),
202 AverageDemand::new(Stockholm.dt(2025, 1, 1, 1, 0, 0), 200),
203 ];
204
205 let peaks = PeakDemands::new(TariffCalculationMethod::AverageHours(5), averages);
206
207 assert_eq!(peaks.values().len(), 2);
208 assert_eq!(peaks.values()[0].value, 200);
209 assert_eq!(peaks.values()[1].value, 100);
210 }
211
212 #[test]
213 fn average_days_one_peak_per_day() {
214 let averages = vec![
215 AverageDemand::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
217 AverageDemand::new(Stockholm.dt(2025, 1, 1, 11, 0, 0), 500),
218 AverageDemand::new(Stockholm.dt(2025, 1, 1, 12, 0, 0), 300),
219 AverageDemand::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 200),
221 AverageDemand::new(Stockholm.dt(2025, 1, 2, 11, 0, 0), 600),
222 AverageDemand::new(Stockholm.dt(2025, 1, 3, 10, 0, 0), 150),
224 AverageDemand::new(Stockholm.dt(2025, 1, 3, 11, 0, 0), 250),
225 ];
226
227 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(2), averages);
228
229 assert_eq!(peaks.values().len(), 2);
230 assert_eq!(peaks.values()[0].value, 600);
231 assert_eq!(peaks.values()[0].timestamp.day(), 2);
232 assert_eq!(peaks.values()[1].value, 500);
233 assert_eq!(peaks.values()[1].timestamp.day(), 1);
234 }
235
236 #[test]
237 fn average_days_ensures_different_days() {
238 let averages = vec![
239 AverageDemand::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 500),
240 AverageDemand::new(Stockholm.dt(2025, 1, 1, 11, 0, 0), 450),
241 AverageDemand::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 300),
242 ];
243
244 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(2), averages);
245
246 assert_eq!(peaks.values().len(), 2);
247 let day1 = peaks.values()[0].timestamp.date_naive();
248 let day2 = peaks.values()[1].timestamp.date_naive();
249 assert_ne!(day1, day2);
250 }
251
252 #[test]
253 fn average_days_preserves_peak_hour_timestamp() {
254 let averages = vec![
255 AverageDemand::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
256 AverageDemand::new(Stockholm.dt(2025, 1, 1, 14, 0, 0), 500),
257 AverageDemand::new(Stockholm.dt(2025, 1, 1, 20, 0, 0), 300),
258 ];
259
260 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(1), averages);
261
262 assert_eq!(peaks.values().len(), 1);
263 assert_eq!(peaks.values()[0].timestamp.hour(), 14);
264 assert_eq!(peaks.values()[0].value, 500);
265 }
266
267 #[test]
268 fn average_days_empty_input() {
269 let averages = vec![];
270
271 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(3), averages);
272
273 assert_eq!(peaks.values().len(), 0);
274 }
275
276 #[test]
277 fn average_days_zero_n() {
278 let averages = vec![AverageDemand::new(Stockholm.dt(2025, 1, 1, 0, 0, 0), 100)];
279
280 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(0), averages);
281
282 assert_eq!(peaks.values().len(), 0);
283 }
284
285 #[test]
286 fn average_days_n_greater_than_available_days() {
287 let averages = vec![
288 AverageDemand::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100),
289 AverageDemand::new(Stockholm.dt(2025, 1, 2, 10, 0, 0), 200),
290 ];
291
292 let peaks = PeakDemands::new(TariffCalculationMethod::AverageDays(5), averages);
293
294 assert_eq!(peaks.values().len(), 2);
295 }
296
297 #[test]
298 fn peak_periods_first_matching_splits_values() {
299 static PERIODS_ARRAY: [CostPeriod; 2] = [
300 CostPeriod::builder()
301 .load(LoadType::High)
302 .fixed_cost(10, 0)
303 .hours(6, 22)
304 .months(Month::November, Month::March)
305 .exclude_weekends()
306 .exclude_holidays(Country::SE)
307 .build(),
308 CostPeriod::builder()
309 .load(LoadType::Low)
310 .fixed_cost(5, 0)
311 .build(),
312 ];
313 let periods = CostPeriods::new_first(&PERIODS_ARRAY);
314
315 let averages = vec![
317 AverageDemand::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500),
319 AverageDemand::new(Stockholm.dt(2025, 1, 15, 23, 0, 0), 300),
321 AverageDemand::new(Stockholm.dt(2025, 1, 15, 14, 0, 0), 400),
323 ];
324
325 let result = PeakPeriods::new(TariffCalculationMethod::AverageHours(10), periods, averages);
326
327 assert_eq!(result.items().len(), 2);
328
329 assert_eq!(result.items()[0].peaks().values().len(), 2);
331 assert_eq!(result.items()[0].peaks().values()[0].value, 500);
332 assert_eq!(result.items()[0].peaks().values()[1].value, 400);
333
334 assert_eq!(result.items()[1].peaks().values().len(), 1);
336 assert_eq!(result.items()[1].peaks().values()[0].value, 300);
337 }
338
339 #[test]
340 fn peak_periods_all_matching_duplicates_values() {
341 static PERIODS_ARRAY: [CostPeriod; 2] = [
342 CostPeriod::builder()
343 .load(LoadType::High)
344 .fixed_cost(10, 0)
345 .hours(6, 22)
346 .months(Month::November, Month::March)
347 .exclude_weekends()
348 .exclude_holidays(Country::SE)
349 .build(),
350 CostPeriod::builder()
351 .load(LoadType::Low)
352 .fixed_cost(5, 0)
353 .build(),
354 ];
355 let periods = CostPeriods::new_all(&PERIODS_ARRAY);
356
357 let averages = vec![
359 AverageDemand::new(Stockholm.dt(2025, 1, 15, 10, 0, 0), 500),
360 AverageDemand::new(Stockholm.dt(2025, 1, 15, 23, 0, 0), 300),
361 ];
362
363 let result = PeakPeriods::new(TariffCalculationMethod::AverageHours(10), periods, averages);
364
365 assert_eq!(result.items().len(), 2);
366
367 assert_eq!(result.items()[0].peaks().values().len(), 1);
369 assert_eq!(result.items()[0].peaks().values()[0].value, 500);
370
371 assert_eq!(result.items()[1].peaks().values().len(), 2);
373 assert_eq!(result.items()[1].peaks().values()[0].value, 500);
374 assert_eq!(result.items()[1].peaks().values()[1].value, 300);
375 }
376
377 #[test]
378 fn peak_periods_empty_averages() {
379 static PERIODS_ARRAY: [CostPeriod; 1] = [CostPeriod::builder()
380 .load(LoadType::Low)
381 .fixed_cost(5, 0)
382 .build()];
383 let periods = CostPeriods::new_first(&PERIODS_ARRAY);
384 let averages = vec![];
385
386 let result = PeakPeriods::new(TariffCalculationMethod::AverageHours(3), periods, averages);
387
388 assert_eq!(result.items().len(), 1);
389 assert_eq!(result.items()[0].peaks().values().len(), 0);
390 }
391
392 #[test]
393 fn single_value_both_methods() {
394 let averages = vec![AverageDemand::new(Stockholm.dt(2025, 1, 1, 10, 0, 0), 100)];
395
396 let hours = PeakDemands::new(TariffCalculationMethod::AverageHours(3), averages.clone());
397 assert_eq!(hours.values().len(), 1);
398 assert_eq!(hours.values()[0].value, 100);
399
400 let days = PeakDemands::new(TariffCalculationMethod::AverageDays(3), averages);
401 assert_eq!(days.values().len(), 1);
402 assert_eq!(days.values()[0].value, 100);
403 }
404}