Skip to main content

grid_billing/
msbg.rs

1//! Preisobergrenzen für den Messstellenbetrieb — §30 MsbG.
2//!
3//! What a Messstellenbetreiber may charge for an intelligentes Messsystem is
4//! capped, and the cap is split: part falls to the Netzbetreiber, the remainder
5//! to the Letztverbraucher. The bands are set by annual consumption **or** by
6//! installed generating capacity, whichever puts the metering point in the
7//! higher band.
8//!
9//! ## Why this is checked rather than assumed
10//!
11//! These are Höchstbeträge in the same sense as the KAV §2 ceilings, and the
12//! crate already refuses to let a Konzessionsabgabe exceed its ceiling silently.
13//! A metering charge above the POG is the same class of defect — an amount the
14//! customer is entitled to have refunded — so the settlement checks the ceiling
15//! and not merely that the fee is non-negative.
16
17use rust_decimal::Decimal;
18use rust_decimal::dec;
19
20/// Which §30 MsbG case a metering point falls under.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
22pub enum MessstellenKategorie {
23    /// **§30 Abs. 1** — Pflichteinbaufall.
24    Pflichteinbau(PflichtBand),
25    /// **§30 Abs. 3** — optionaler Einbau, at the Anschlussnutzer's request.
26    ///
27    /// A single ceiling regardless of consumption.
28    OptionalerEinbau,
29}
30
31/// The §30 Abs. 1 bands.
32///
33/// A metering point falls in a band by annual consumption **or** by installed
34/// capacity — whichever is higher. `Ueber100000` has no fixed total: §30 Abs. 1
35/// allows an "angemessenes jährliches Entgelt", so only the Netzbetreiber's
36/// share is capped.
37#[derive(
38    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
39)]
40pub enum PflichtBand {
41    /// > 6 000 – ≤ 10 000 kWh.
42    Bis10000,
43    /// > 10 000 – ≤ 20 000 kWh, a steuerbare Verbrauchseinrichtung, or > 7 – ≤ 15 kW.
44    Bis20000,
45    /// > 20 000 – ≤ 50 000 kWh, or > 15 – ≤ 25 kW.
46    Bis50000,
47    /// > 50 000 – ≤ 100 000 kWh, or > 25 – ≤ 100 kW.
48    Bis100000,
49    /// > 100 000 kWh or > 100 kW.
50    Ueber100000,
51}
52
53/// Who owes the charge.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
55pub enum Entgeltschuldner {
56    /// The Netzbetreiber's share.
57    Netzbetreiber,
58    /// The Letztverbraucher's share.
59    Letztverbraucher,
60}
61
62/// The §30 MsbG ceiling in EUR per year, or `None` where the statute sets none.
63///
64/// `None` means "no fixed ceiling" — the >100 000 kWh band, where §30 Abs. 1
65/// allows an angemessenes Entgelt for the Letztverbraucher's share. It does not
66/// mean "unchecked": the Netzbetreiber's share is capped in every band.
67#[must_use]
68pub fn preisobergrenze_eur_per_jahr(
69    kategorie: MessstellenKategorie,
70    schuldner: Entgeltschuldner,
71) -> Option<Decimal> {
72    use Entgeltschuldner as E;
73    use MessstellenKategorie as K;
74    use PflichtBand as B;
75
76    match (kategorie, schuldner) {
77        // §30 Abs. 1: the Netzbetreiber's share is 80 EUR in every band.
78        (K::Pflichteinbau(_), E::Netzbetreiber) => Some(dec!(80)),
79        (K::Pflichteinbau(B::Bis10000), E::Letztverbraucher) => Some(dec!(40)),
80        (K::Pflichteinbau(B::Bis20000), E::Letztverbraucher) => Some(dec!(50)),
81        (K::Pflichteinbau(B::Bis50000), E::Letztverbraucher) => Some(dec!(110)),
82        (K::Pflichteinbau(B::Bis100000), E::Letztverbraucher) => Some(dec!(140)),
83        // "angemessenes jährliches Entgelt" — no fixed figure.
84        (K::Pflichteinbau(B::Ueber100000), E::Letztverbraucher) => None,
85        // §30 Abs. 3: 60 EUR in total, 30 EUR each.
86        (K::OptionalerEinbau, _) => Some(dec!(30)),
87    }
88}
89
90/// The combined §30 Abs. 1 ceiling across both parties, where one is fixed.
91#[must_use]
92pub fn gesamtobergrenze_eur_per_jahr(kategorie: MessstellenKategorie) -> Option<Decimal> {
93    let nb = preisobergrenze_eur_per_jahr(kategorie, Entgeltschuldner::Netzbetreiber)?;
94    let lv = preisobergrenze_eur_per_jahr(kategorie, Entgeltschuldner::Letztverbraucher)?;
95    Some(nb + lv)
96}
97
98/// **§30 Abs. 2** — the additional yearly ceiling per party for installing and
99/// operating a Steuereinrichtung at the Netzanschlusspunkt.
100pub const STEUEREINRICHTUNG_OBERGRENZE_EUR_PER_JAHR: Decimal = dec!(50);
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use Entgeltschuldner as E;
106    use MessstellenKategorie as K;
107    use PflichtBand as B;
108
109    /// The §30 Abs. 1 schedule, as published.
110    #[test]
111    fn the_pflichteinbau_schedule() {
112        for (band, lv, total) in [
113            (B::Bis10000, dec!(40), dec!(120)),
114            (B::Bis20000, dec!(50), dec!(130)),
115            (B::Bis50000, dec!(110), dec!(190)),
116            (B::Bis100000, dec!(140), dec!(220)),
117        ] {
118            let k = K::Pflichteinbau(band);
119            assert_eq!(
120                preisobergrenze_eur_per_jahr(k, E::Netzbetreiber),
121                Some(dec!(80))
122            );
123            assert_eq!(
124                preisobergrenze_eur_per_jahr(k, E::Letztverbraucher),
125                Some(lv)
126            );
127            assert_eq!(gesamtobergrenze_eur_per_jahr(k), Some(total));
128        }
129    }
130
131    /// Above 100 000 kWh the Letztverbraucher's share is an angemessenes
132    /// Entgelt, but the Netzbetreiber's share is capped like every other band.
133    #[test]
134    fn the_top_band_caps_only_the_grid_operators_share() {
135        let k = K::Pflichteinbau(B::Ueber100000);
136        assert_eq!(
137            preisobergrenze_eur_per_jahr(k, E::Netzbetreiber),
138            Some(dec!(80))
139        );
140        assert_eq!(preisobergrenze_eur_per_jahr(k, E::Letztverbraucher), None);
141        assert_eq!(
142            gesamtobergrenze_eur_per_jahr(k),
143            None,
144            "no total where one share is open"
145        );
146    }
147
148    /// §30 Abs. 3 is one ceiling regardless of consumption.
149    #[test]
150    fn an_optional_installation_is_capped_at_thirty_each() {
151        for schuldner in [E::Netzbetreiber, E::Letztverbraucher] {
152            assert_eq!(
153                preisobergrenze_eur_per_jahr(K::OptionalerEinbau, schuldner),
154                Some(dec!(30))
155            );
156        }
157        assert_eq!(
158            gesamtobergrenze_eur_per_jahr(K::OptionalerEinbau),
159            Some(dec!(60))
160        );
161    }
162
163    /// The bands rise monotonically — a higher band never caps lower.
164    #[test]
165    fn the_bands_rise_monotonically() {
166        let bands = [B::Bis10000, B::Bis20000, B::Bis50000, B::Bis100000];
167        let mut previous = Decimal::ZERO;
168        for band in bands {
169            let ceiling = preisobergrenze_eur_per_jahr(K::Pflichteinbau(band), E::Letztverbraucher)
170                .expect("a fixed ceiling");
171            assert!(ceiling > previous, "{band:?} must exceed the band below it");
172            previous = ceiling;
173        }
174    }
175}