grid_tariffs/
feed_in_revenue.rs

1use chrono::DateTime;
2use chrono_tz::Tz;
3use serde::Serialize;
4
5use crate::{Cost, CostPeriods, CostPeriodsSimple, Language, Money, currency::Currency};
6
7/// Feed-in revenue, per kWh (usually from solar production)
8/// A Swedish concept for "thanking" micro producers (<=43,5 kW) for reducing losses in the grid
9#[derive(Debug, Clone, Copy, Serialize)]
10#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
11pub enum FeedInRevenue {
12    Simple(Cost),
13    /// Not yet checked
14    Unverified,
15    /// Could not be located on their website or elsewhere
16    Unlisted,
17    /// Varies by the current spot price
18    SpotPriceVariable {
19        base_cost: Money,
20        spot_price_multiplier: f64,
21        /// If this is approximated from actual data, or if it's based on documented pricing
22        approximated: bool,
23    },
24    Periods(CostPeriods),
25}
26
27impl FeedInRevenue {
28    pub const fn is_unverified(&self) -> bool {
29        matches!(self, Self::Unverified)
30    }
31
32    pub(super) const fn new_periods(periods: CostPeriods) -> Self {
33        Self::Periods(periods)
34    }
35
36    pub(super) const fn fixed_subunit(subunit: f64) -> Self {
37        Self::Simple(Cost::fixed_subunit(subunit))
38    }
39
40    pub const fn spot_price_variable(
41        base_cost_subunit: f64,
42        spot_price_multiplier: f64,
43        approximated: bool,
44    ) -> Self {
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    ) -> FeedInRevenueSimplified {
58        FeedInRevenueSimplified::new(self, fuse_size, yearly_consumption, language)
59    }
60
61    pub fn kwh_revenue(
62        &self,
63        timestamp: DateTime<Tz>,
64        spotprice: Money,
65        fuse_size: u16,
66        yearly_consumption: u32,
67    ) -> Money {
68        match self {
69            FeedInRevenue::Unverified | FeedInRevenue::Unlisted => Money::ZERO,
70            FeedInRevenue::Simple(cost) => cost
71                .cost_for(fuse_size, yearly_consumption)
72                .unwrap_or_default(),
73            FeedInRevenue::SpotPriceVariable {
74                base_cost,
75                spot_price_multiplier,
76                approximated,
77            } => todo!(),
78            FeedInRevenue::Periods(cost_periods) => cost_periods
79                .matching_periods(timestamp)
80                .into_iter()
81                .flat_map(|period| period.cost().cost_for(fuse_size, yearly_consumption))
82                .sum(),
83        }
84    }
85}
86
87/// Feed-in revenue, per kWh (usually from solar production)
88/// Like FeedInRevenue, but with costs being simple Money objects
89#[derive(Debug, Clone, Serialize)]
90#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
91pub enum FeedInRevenueSimplified {
92    Simple(Option<Money>),
93    /// Not yet checked
94    Unverified,
95    /// Could not be located on their website or elsewhere
96    Unlisted,
97    /// Varies by the current spot price
98    SpotPriceVariable {
99        base_cost: Money,
100        spot_price_multiplier: f64,
101        /// If this is approximated from actual data, or if it's based on documented pricing
102        approximated: bool,
103        info: String,
104    },
105    Periods(CostPeriodsSimple),
106}
107
108impl FeedInRevenueSimplified {
109    fn new(
110        revenue: &FeedInRevenue,
111        fuse_size: u16,
112        yearly_consumption: u32,
113        language: Language,
114    ) -> Self {
115        match *revenue {
116            FeedInRevenue::Unlisted => Self::Unlisted,
117            FeedInRevenue::Unverified => Self::Unverified,
118            FeedInRevenue::Simple(cost) => {
119                Self::Simple(cost.cost_for(fuse_size, yearly_consumption))
120            }
121            FeedInRevenue::SpotPriceVariable {
122                base_cost,
123                spot_price_multiplier,
124                approximated,
125            } => Self::SpotPriceVariable {
126                base_cost,
127                spot_price_multiplier,
128                approximated,
129                info: Default::default(),
130            },
131            FeedInRevenue::Periods(periods) => Self::Periods(CostPeriodsSimple::new(
132                periods,
133                fuse_size,
134                yearly_consumption,
135                language,
136            )),
137        }
138        .add_info(language)
139    }
140
141    fn add_info(mut self, language: Language) -> Self {
142        match self {
143            FeedInRevenueSimplified::SpotPriceVariable {
144                base_cost,
145                spot_price_multiplier,
146                approximated,
147                info,
148            } => {
149                let percentage = spot_price_multiplier * 100.;
150                let mut info = match language {
151                    Language::En => format!(
152                        "The grid operator bases its feed-in revenue on a fixed part of {} and {}% of the current spot price.",
153                        base_cost.display(Currency::SEK),
154                        percentage
155                    ),
156                    Language::Sv => format!(
157                        "Nätbolaget baserar sin nätnytta på en fast del om {} samt {}% av spotpriset.",
158                        base_cost.display(Currency::SEK),
159                        percentage
160                    ),
161                };
162                if approximated {
163                    info.push_str(match language {
164                        Language::En => " The base fee and percentage are estimated, as the grid operator doesn't list them on their website.",
165                        Language::Sv => " Basavgift och procentsats är uppskattade, eftersom nätbolaget inte skriver ut exakt vad de är på sin webbplats.",
166                    })
167                }
168                FeedInRevenueSimplified::SpotPriceVariable {
169                    base_cost,
170                    spot_price_multiplier,
171                    approximated,
172                    info,
173                }
174            }
175            _ => self,
176        }
177    }
178}