grid_tariffs/
power_tariffs.rs

1use chrono::DateTime;
2use serde::Serialize;
3
4use crate::{
5    Actual, GridOperator, Language, PartialPowerAverage, PowerAverage, PowerTariffMatches,
6    costs::{CostPeriods, CostPeriodsSimple},
7    hours::Hours,
8};
9
10#[derive(Debug, Clone, Copy, Serialize)]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
12pub struct PowerDivide {
13    pub(crate) hours: Option<Hours>,
14    divider: u8,
15}
16
17impl PowerDivide {
18    pub const fn new(hours: Option<Hours>, divider: u8) -> Self {
19        Self { hours, divider }
20    }
21
22    pub fn hours(&self) -> Option<Hours> {
23        self.hours
24    }
25
26    pub fn divider<Tz: chrono::TimeZone>(&self, timestamp: DateTime<Tz>) -> u8 {
27        if let Some(hours) = self.hours()
28            && hours.matches(timestamp)
29        {
30            self.divider
31        } else {
32            1
33        }
34    }
35
36    pub fn multiplier<Tz: chrono::TimeZone>(&self, timestamp: DateTime<Tz>) -> f64 {
37        1. / self.divider(timestamp) as f64
38    }
39
40    pub fn divided_percentage(&self) -> u8 {
41        ((1.0 / self.divider as f64) * 100.) as u8
42    }
43}
44
45#[derive(Debug, Clone, Serialize)]
46#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
47pub enum PowerTariff {
48    Unverified,
49    NotImplemented,
50    Periods {
51        method: TariffCalculationMethod,
52        /// Divide kw by this amount during these hours
53        power_divide: Option<PowerDivide>,
54        periods: CostPeriods,
55    },
56}
57
58impl PowerTariff {
59    pub const fn new(method: TariffCalculationMethod, periods: CostPeriods) -> Self {
60        Self::Periods {
61            method,
62            periods,
63            power_divide: None,
64        }
65    }
66
67    pub const fn add_power_divide(self, power_divide: PowerDivide) -> Self {
68        match self {
69            Self::Unverified => unimplemented!(),
70            Self::NotImplemented => unimplemented!(),
71            Self::Periods {
72                method,
73                power_divide: _,
74                periods,
75            } => Self::Periods {
76                method,
77                power_divide: Some(power_divide),
78                periods,
79            },
80        }
81    }
82
83    pub const fn is_unverified(&self) -> bool {
84        matches!(self, Self::Unverified)
85    }
86
87    pub fn simplified(
88        &self,
89        operator: &GridOperator,
90        fuse_size: u16,
91        yearly_consumption: u32,
92        language: Language,
93    ) -> PowerTariffSimplified {
94        PowerTariffSimplified::new(operator, self, fuse_size, yearly_consumption, language)
95    }
96
97    pub fn periods(
98        &self,
99        averages: Vec<PowerAverage<Actual>>,
100        current_power_average: Option<PartialPowerAverage>,
101    ) -> PowerTariffMatches {
102        match self {
103            PowerTariff::Unverified => PowerTariffMatches::new_dummy(),
104            PowerTariff::NotImplemented => PowerTariffMatches::new_dummy(),
105            PowerTariff::Periods {
106                method,
107                periods,
108                power_divide,
109            } => PowerTariffMatches::new(
110                *method,
111                *power_divide,
112                *periods,
113                &averages
114                    .iter()
115                    .map(|a| a.into_virtual(*power_divide))
116                    .collect::<Vec<_>>(),
117                current_power_average,
118            ),
119        }
120    }
121}
122
123/// The method used to calculate power tariffs
124#[derive(Debug, Clone, Copy, Serialize)]
125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
126pub enum TariffCalculationMethod {
127    /// Power peak for top hour of the top x days of the month
128    AverageDays(u8),
129    /// Average of top x hours of the month
130    AverageHours(u8),
131}
132
133/// Like PowerTariff, but with costs being simple Money objects
134#[derive(Debug, Clone, Serialize)]
135#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
136pub enum PowerTariffSimplified {
137    Unverified,
138    NotImplemented,
139    Periods {
140        method: TariffCalculationMethod,
141        /// Divide kw by this amount during these hours
142        power_divide: Option<PowerDivide>,
143        periods: CostPeriodsSimple,
144        info: Option<String>,
145    },
146}
147
148impl PowerTariffSimplified {
149    fn new(
150        operator: &GridOperator,
151        tariff: &PowerTariff,
152        fuse_size: u16,
153        yearly_consumption: u32,
154        language: Language,
155    ) -> Self {
156        match *tariff {
157            PowerTariff::Unverified => PowerTariffSimplified::Unverified,
158            PowerTariff::NotImplemented => PowerTariffSimplified::NotImplemented,
159            PowerTariff::Periods {
160                method,
161                periods,
162                power_divide,
163            } => PowerTariffSimplified::Periods {
164                method,
165                power_divide,
166                periods: CostPeriodsSimple::new(periods, fuse_size, yearly_consumption, language),
167                info: Self::info(operator, tariff, language),
168            },
169        }
170    }
171
172    fn info(operator: &GridOperator, tariff: &PowerTariff, language: Language) -> Option<String> {
173        match language {
174            Language::En => {
175                let (method, power_divide) = match tariff {
176                    PowerTariff::Unverified | PowerTariff::NotImplemented => return None,
177                    PowerTariff::Periods {
178                        method,
179                        power_divide,
180                        ..
181                    } => (method, power_divide),
182                };
183
184                let mut s = format!(
185                    "{} calculates its power tariff based on {} during the month.",
186                    operator.name(),
187                    match method {
188                        TariffCalculationMethod::AverageDays(1)
189                        | TariffCalculationMethod::AverageHours(1) =>
190                            "the highest power peak".to_string(),
191                        TariffCalculationMethod::AverageDays(n) =>
192                            format!("the average of the {n} highest daily power peaks"),
193                        TariffCalculationMethod::AverageHours(n) =>
194                            format!("the average of the {n} highest power peaks"),
195                    }
196                );
197
198                if let Some(power_divide) = power_divide {
199                    let hours = power_divide
200                        .hours()
201                        .expect("hours to be defined on pricelist");
202
203                    s.push_str(&format!(
204                        " Between {}:00 and {}:59, only {}% of the power peak is counted.",
205                        hours.from(),
206                        hours.to_inclusive(),
207                        power_divide.divided_percentage()
208                    ));
209                }
210                Some(s)
211            }
212            Language::Sv => {
213                let (method, power_divide) = match tariff {
214                    PowerTariff::Unverified | PowerTariff::NotImplemented => return None,
215                    PowerTariff::Periods {
216                        method,
217                        power_divide,
218                        ..
219                    } => (method, power_divide),
220                };
221                let mut s = format!(
222                    "{} räknar ut sin effektavgift utifrån {} under månaden.",
223                    operator.name(),
224                    match method {
225                        TariffCalculationMethod::AverageDays(1)
226                        | TariffCalculationMethod::AverageHours(1) =>
227                            "den högsta effekttoppen".to_string(),
228                        TariffCalculationMethod::AverageDays(n) =>
229                            format!("snittet av de {n} högsta dygnseffekttopparna"),
230                        TariffCalculationMethod::AverageHours(n) =>
231                            format!("snittet av de {n} högsta effekttopparna"),
232                    }
233                );
234                if let Some(power_divide) = power_divide {
235                    let hours = power_divide
236                        .hours()
237                        .expect("hours to be defined on pricelist");
238                    s.push_str(&format!(
239                        " Mellan kl {}:00 och {}:59 så räknas bara {}% av effekttoppen.",
240                        hours.from(),
241                        hours.to_inclusive(),
242                        power_divide.divided_percentage()
243                    ));
244                }
245                Some(s)
246            }
247        }
248    }
249}