grid_tariffs/
transfer_fee.rs

1use chrono::{DateTime, TimeZone};
2use serde::Serialize;
3
4use crate::{Cost, CostPeriods, CostPeriodsSimple, Language, Money, currency::Currency};
5
6#[derive(Debug, Clone, Copy, Serialize)]
7#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
8pub enum TransferFee {
9    /// Price was not listed on their website
10    Unlisted,
11    /// Transfer fee has not been verified by us
12    Unverified,
13    /// Fee does not change except possibly by fuse size
14    Simple(Cost),
15    /// Transfer fee that varies by the current spot price
16    /// We have currently only observed that Växjö Energi uses this variant
17    SpotPriceVariable {
18        base_cost: Money,
19        spot_price_multiplier: f64,
20        approximated: bool,
21    },
22    Periods(CostPeriods),
23}
24
25impl TransferFee {
26    pub const fn is_unverified(&self) -> bool {
27        matches!(self, Self::Unverified)
28    }
29
30    pub const fn simple_cost(&self) -> Option<Cost> {
31        match self {
32            Self::Simple(cost) => Some(*cost),
33            _ => None,
34        }
35    }
36
37    pub const fn spot_price_variable(
38        base_cost_subunit: f64,
39        spot_price_multiplier: f64,
40        approximated: bool,
41    ) -> Self {
42        Self::SpotPriceVariable {
43            base_cost: Money::new_subunit(base_cost_subunit),
44            spot_price_multiplier,
45            approximated,
46        }
47    }
48
49    pub fn simplified(
50        &self,
51        fuse_size: u16,
52        yearly_consumption: u32,
53        language: Language,
54    ) -> TransferFeeSimplified {
55        TransferFeeSimplified::new(self, fuse_size, yearly_consumption, language)
56    }
57
58    pub(crate) const fn new_periods(periods: CostPeriods) -> Self {
59        Self::Periods(periods)
60    }
61
62    pub(crate) const fn fixed(int: i64, fract: u8) -> Self {
63        Self::Simple(Cost::fixed(int, fract))
64    }
65
66    pub(crate) const fn fixed_subunit(subunit: f64) -> Self {
67        Self::Simple(Cost::fixed_subunit(subunit))
68    }
69
70    pub fn kwh_cost<Tz: TimeZone>(
71        &self,
72        timestamp: DateTime<Tz>,
73        spotprice: Money,
74        fuse_size: u16,
75        yearly_consumption: u32,
76    ) -> Money
77    where
78        DateTime<Tz>: Copy,
79    {
80        match *self {
81            TransferFee::Unlisted | TransferFee::Unverified => Money::ZERO,
82            TransferFee::Simple(cost) => cost
83                .cost_for(fuse_size, yearly_consumption)
84                .unwrap_or_default(),
85            TransferFee::SpotPriceVariable {
86                base_cost,
87                spot_price_multiplier,
88                approximated: _,
89            } => base_cost + (spotprice * spot_price_multiplier),
90            TransferFee::Periods(cost_periods) => cost_periods
91                .matching_periods(timestamp)
92                .into_iter()
93                .flat_map(|p| p.cost().cost_for(fuse_size, yearly_consumption))
94                .sum(),
95        }
96    }
97
98    pub(crate) fn is_yearly_consumption_based(&self, fuse_size: u16) -> bool {
99        match self {
100            TransferFee::Unlisted
101            | TransferFee::Unverified
102            | TransferFee::SpotPriceVariable { .. } => false,
103            TransferFee::Simple(cost) => cost.is_yearly_consumption_based(fuse_size),
104            TransferFee::Periods(periods) => periods.is_yearly_consumption_based(fuse_size),
105        }
106    }
107
108    /// Use when the operator states that they use spot price variable pricing, but don't specify the actual multipliers
109    pub(crate) const fn spot_price_variable_placeholder() -> TransferFee {
110        Self::spot_price_variable(2.0, 0.06, true)
111    }
112}
113
114/// Like TransferFee, but with costs being simple Money objects
115#[derive(Debug, Clone, Serialize)]
116#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
117pub enum TransferFeeSimplified {
118    /// Price was not listed on their website
119    Unlisted,
120    /// Transfer fee has not been verified by us
121    Unverified,
122    /// Fee does not change except possibly by fuse size
123    Simple(Money),
124    /// Transfer fee that varies by the current spot price
125    /// We have currently only observed that Växjö Energi uses this variant
126    SpotPriceVariable {
127        base_cost: Money,
128        spot_price_multiplier: f64,
129        approximated: bool,
130        info: String,
131    },
132    Periods(CostPeriodsSimple),
133}
134
135impl TransferFeeSimplified {
136    fn new(fee: &TransferFee, fuse_size: u16, yearly_consumption: u32, language: Language) -> Self {
137        match *fee {
138            TransferFee::Unlisted => TransferFeeSimplified::Unlisted,
139            TransferFee::Unverified => TransferFeeSimplified::Unverified,
140            TransferFee::Simple(cost) => TransferFeeSimplified::Simple(
141                cost.cost_for(fuse_size, yearly_consumption)
142                    .unwrap_or(Money::ZERO),
143            ),
144            TransferFee::SpotPriceVariable {
145                base_cost,
146                spot_price_multiplier,
147                approximated,
148            } => TransferFeeSimplified::SpotPriceVariable {
149                base_cost,
150                spot_price_multiplier,
151                approximated,
152                info: Default::default(),
153            },
154            TransferFee::Periods(periods) => TransferFeeSimplified::Periods(
155                CostPeriodsSimple::new(periods, fuse_size, yearly_consumption, language),
156            ),
157        }
158        .add_info(language)
159    }
160
161    fn add_info(self, language: Language) -> Self {
162        match self {
163            TransferFeeSimplified::Unlisted => self,
164            TransferFeeSimplified::Unverified => self,
165            TransferFeeSimplified::Simple(_) => self,
166            TransferFeeSimplified::SpotPriceVariable {
167                base_cost,
168                spot_price_multiplier,
169                approximated,
170                info: _,
171            } => {
172                let percentage = spot_price_multiplier * 100.;
173                let mut info = match language {
174                    Language::En => format!(
175                        "The grid operator bases its transfer fee on a fixed part of {} and {}% of the current spot price.",
176                        base_cost.display(Currency::SEK),
177                        percentage
178                    ),
179                    Language::Sv => format!(
180                        "Nätbolaget baserar sin överföringsavgift på en fast del om {} samt {}% av spotpriset.",
181                        base_cost.display(Currency::SEK),
182                        percentage
183                    ),
184                };
185                if approximated {
186                    info.push_str(match language {
187                        Language::En => " The base fee and percentage are estimated, as the grid operator doesn't list them on their website.",
188                        Language::Sv => " Basavgift och procentsats är uppskattade, eftersom nätbolaget inte skriver ut exakt vad de är på sin webbplats.",
189                    })
190                }
191                TransferFeeSimplified::SpotPriceVariable {
192                    base_cost,
193                    spot_price_multiplier,
194                    approximated,
195                    info,
196                }
197            }
198            TransferFeeSimplified::Periods(_) => self,
199        }
200    }
201}