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 Simple(Cost),
11 SpotPriceVariable {
14 base_cost: Money,
15 spot_price_multiplier: f64,
16 approximated: bool,
17 },
18 Periods(CostPeriods),
19}
20
21impl TransferFee {
22 pub const fn new_periods(periods: CostPeriods) -> Self {
23 Self::Periods(periods)
24 }
25
26 pub const fn simple_cost(&self) -> Option<Cost> {
27 match self {
28 Self::Simple(cost) => Some(*cost),
29 _ => None,
30 }
31 }
32
33 pub const fn none() -> Self {
34 Self::Simple(Cost::None)
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 if spot_price_multiplier < 0.0 || spot_price_multiplier > 1.0 {
43 panic!("`spot_price_multiplier` too low/high");
44 }
45 Self::SpotPriceVariable {
46 base_cost: Money::new_subunit(base_cost_subunit),
47 spot_price_multiplier,
48 approximated,
49 }
50 }
51
52 pub fn simplified(
53 &self,
54 fuse_size: u16,
55 yearly_consumption: u32,
56 language: Language,
57 ) -> TransferFeeSimplified {
58 TransferFeeSimplified::new(self, fuse_size, yearly_consumption, language)
59 }
60
61 pub(crate) const fn fixed(int: i64, fract: u8) -> Self {
62 Self::Simple(Cost::fixed(int, fract))
63 }
64
65 pub(crate) const fn fixed_subunit(subunit: f64) -> Self {
66 Self::Simple(Cost::fixed_subunit(subunit))
67 }
68
69 pub fn kwh_cost<Tz: TimeZone>(
70 &self,
71 timestamp: DateTime<Tz>,
72 spotprice: Money,
73 fuse_size: u16,
74 yearly_consumption: u32,
75 ) -> Money
76 where
77 DateTime<Tz>: Copy,
78 {
79 match *self {
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(crate) fn is_yearly_consumption_based(&self, fuse_size: u16) -> bool {
97 match self {
98 TransferFee::SpotPriceVariable { .. } => false,
99 TransferFee::Simple(cost) => cost.is_yearly_consumption_based(fuse_size),
100 TransferFee::Periods(periods) => periods.is_yearly_consumption_based(fuse_size),
101 }
102 }
103
104 pub(crate) const fn spot_price_variable_placeholder() -> TransferFee {
106 Self::spot_price_variable(2.0, 0.06, true)
107 }
108}
109
110#[derive(Debug, Clone, Serialize)]
112#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
113pub enum TransferFeeSimplified {
114 Unlisted,
116 Simple(Money),
118 SpotPriceVariable {
121 base_cost: Money,
122 spot_price_multiplier: f64,
123 approximated: bool,
124 info: String,
125 },
126 Periods(CostPeriodsSimple),
127}
128
129impl TransferFeeSimplified {
130 fn new(fee: &TransferFee, fuse_size: u16, yearly_consumption: u32, language: Language) -> Self {
131 match *fee {
132 TransferFee::Simple(cost) => TransferFeeSimplified::Simple(
133 cost.cost_for(fuse_size, yearly_consumption)
134 .unwrap_or(Money::ZERO),
135 ),
136 TransferFee::SpotPriceVariable {
137 base_cost,
138 spot_price_multiplier,
139 approximated,
140 } => TransferFeeSimplified::SpotPriceVariable {
141 base_cost,
142 spot_price_multiplier,
143 approximated,
144 info: Default::default(),
145 },
146 TransferFee::Periods(periods) => TransferFeeSimplified::Periods(
147 CostPeriodsSimple::new(periods, fuse_size, yearly_consumption, language),
148 ),
149 }
150 .add_info(language)
151 }
152
153 fn add_info(self, language: Language) -> Self {
154 match self {
155 TransferFeeSimplified::Unlisted => self,
156 TransferFeeSimplified::Simple(_) => self,
157 TransferFeeSimplified::SpotPriceVariable {
158 base_cost,
159 spot_price_multiplier,
160 approximated,
161 info: _,
162 } => {
163 let percentage = spot_price_multiplier * 100.;
164 let mut info = match language {
165 Language::En => format!(
166 "The grid operator bases its transfer fee on a fixed part of {} and {:.2}% of the current spot price.",
167 base_cost.display(Currency::SEK).subunit_display(),
168 percentage
169 ),
170 Language::Sv => format!(
171 "Nätbolaget baserar sin överföringsavgift på en fast del om {} samt {:.2}% av spotpriset.",
172 base_cost.display(Currency::SEK).subunit_display(),
173 percentage
174 ),
175 };
176 if approximated {
177 info.push_str(match language {
178 Language::En => " The base fee and percentage are estimated, as the grid operator doesn't list them on their website.",
179 Language::Sv => " Basavgift och procentsats är uppskattade, eftersom nätbolaget inte skriver ut exakt vad de är på sin webbplats.",
180 })
181 }
182 TransferFeeSimplified::SpotPriceVariable {
183 base_cost,
184 spot_price_multiplier,
185 approximated,
186 info,
187 }
188 }
189 TransferFeeSimplified::Periods(_) => self,
190 }
191 }
192}