grid_tariffs/
feed_in_revenue.rs

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