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