grid_tariffs/
transfer_fee.rs

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