eeg_billing/lib.rs
1//! Pure EEG/KWKG feed-in settlement calculation for German energy markets.
2//!
3//! Implements all settlement models defined in **EEG** (all versions 2000–2023)
4//! and **KWKG**. Zero I/O, zero async — every function is deterministic
5//! and synchronous. No floating-point money: amounts are computed in
6//! `rust_decimal::Decimal`, and every EUR result is rounded and
7//! range-checked through [`crate::EuroAmount`] (`i64 × 10⁻⁵ EUR`).
8//!
9//! # Settlement schemes (10)
10//!
11//! | `SettlementScheme` | Formula | Legal basis |
12//! |---|---|---|
13//! | `FeedInTariff` | `kwh × verguetungssatz_ct / 100` | §21 EEG |
14//! | `TenantElectricity` | Vergütung + `kwh × mieter_zuschlag_ct / 100` | §21 Abs. 3 EEG 2023 |
15//! | `MarketPremium` | `max(0, AW − Marktwert) × kwh / 100` | §23a EEG + Anlage 1 |
16//! | `MarketPremium` + `TariffSource::Auction` | same formula, AW from BNetzA tender | §§22a,28 EEG 2023 |
17//! | `PostEeg` | `kwh × EPEX / 100` (§23b cap: 10 ct; configurable floor) | §21 EEG (post-Förderung) |
18//! | `Eigenverbrauch` | EUR 0 (no feed-in remuneration) | §21 Abs. 3 EEG |
19//! | `KwkSurcharge` | `eligible_kwh × rate / 100` (Vollbenutzungsstunden-Grenze) | §§7, 8 KWKG |
20//! | `FlexibilityPremium` | Vergütung + `kwh × flex_praemie_ct / 100` | §50b EEG 2023 (bestehende Anlagen) |
21//! | `FlexibilitySurcharge` | `kw × rate / 12` (monthly capacity payment) | §50a EEG 2023 (neue Anlagen) |
22//! | `TemporaryFeedInTariff` | `kwh × verguetungssatz_ct / 100` (reduced Ausfallvergütung) | §21 Abs. 1 Satz 1 Nr. 3 EEG 2023 |
23//! | `SonstigeDirektvermarktung` | EUR 0 — revenue on the open market, no NB payment | §21a EEG |
24//!
25//! # One formula — all EEG versions (2000–2023)
26//!
27//! **No separate tariff per EEG version is needed.** The settlement formula is
28//! identical across all EEG versions. What differs between versions:
29//!
30//! 1. **Vergütungssatz (rate)** — fixed at commissioning for 20 years; caller provides it.
31//! Use [`rates::solar_pv_ueberschuss_aw_ct`] or `einsd`'s `lookup_verguetungssatz`.
32//!
33//! 2. **§51 Negativpreisregel** — applied automatically, and keyed on the
34//! **Inbetriebnahmedatum** rather than the law year: the Solarspitzengesetz
35//! rewrote §51 with effect from 25.02.2025, mid-year and inside the EEG 2023
36//! range. [`negativpreis::NegativpreisRegime::fuer_inbetriebnahme`] derives it
37//! — ≥6 h (2016–2020), ≥4 h (2021–2022), the staged 4-3-2-1 h ladder
38//! (2023–24.02.2025), the first negative quarter-hour from 25.02.2025, and
39//! never for a plant commissioned before 2016.
40//!
41//! 3. **§100 EEG 2023 Übergangsregelung** — old plants continue under their EEG version's
42//! rules (§100 Abs. 1). The rate is the only thing that changes per plant.
43//!
44//! # Umsatzsteuer (not Mehrwertsteuer)
45//!
46//! "Umsatzsteuer" (USt) is the legal term; "Mehrwertsteuer" (MwSt) is colloquial.
47//! A feed-in Gutschrift has two VAT treatments — see [`ust`]:
48//! - **§19 UStG Kleinunternehmer**: turnover ≤€25 000/yr → **no USt** (category `E`)
49//! - **Regelbesteuerung**: all others → **19 % USt** (category `S`)
50//!
51//! The status is a declared property of the operator (masterdata), not something
52//! the plant size decides. Use [`ust::ust_tax_layers`] to get the right
53//! `billing::TaxLayer` for a document.
54//!
55//! # Multi-EEG-version support
56//!
57//! Supply `inbetriebnahme` and `leistung_kwp` in [`SettleInput`] for automatic
58//! version-specific rule enforcement:
59//! - §51 Negativpreisregel guard — the size exemption is per regime
60//! ([`negativpreis::NegativpreisRegime::kw_grenze`]): Wind < 3 MW / others
61//! < 500 kW, then < 500 kW, < 400 kW, and < 100 kW until an iMSys is in
62//! (< 2 kW after)
63//! - Automatic `FoerderungBeendet` when `billing_date > foerderendedatum`
64//!
65//! # §24 EEG Anlagenerweiterung
66//!
67//! Plants extended with additional capacity blocks use [`CapacityBlock`].
68//! Settlement is proportionally allocated across all blocks by installed kWp.
69//!
70//! # Quick start
71//!
72//! ```rust
73//! use eeg_billing::{SettleInput, SettlementScheme, calculate_settlement, SettlementStatus};
74//! use rust_decimal::Decimal;
75//! use std::str::FromStr;
76//!
77//! fn d(s: &str) -> Decimal { Decimal::from_str(s).unwrap() }
78//!
79//! // §21 EEG 2023 — 100 kWh × 8.51 ct/kWh (Solarpaket I, ≤10 kWp Überschuss) = 8.51 EUR
80//! let out = calculate_settlement(&SettleInput {
81//! scheme: eeg_billing::SettlementScheme::FeedInTariff { verguetungssatz_ct: d("8.51") },
82//! einspeisemenge_kwh: Some(d("100")),
83//! ..SettleInput::default()
84//! });
85//! assert_eq!(out.status, SettlementStatus::Calculated);
86//! assert_eq!(out.settlement_eur, Some(d("8.51")));
87//! ```
88#![deny(unsafe_code)]
89#![warn(missing_docs)]
90
91pub mod aw_reductions;
92pub mod biomasse;
93pub mod bridge;
94pub mod degression;
95pub mod direktverm;
96mod error;
97pub mod foerderdauer;
98pub mod foerderungsende;
99mod formula;
100#[cfg(feature = "bo4e")]
101pub mod gutschrift;
102pub mod kwkg;
103mod model;
104pub mod negativpreis;
105pub mod rates;
106pub mod reductions;
107pub mod rounding;
108pub mod scheme;
109pub mod seed;
110pub mod settlement_state;
111pub mod tariff;
112pub mod technology;
113pub mod ust;
114pub mod version;
115pub mod wind;
116pub mod zusammenfassung;
117
118/// A monetary amount in euro at 10⁻⁵-EUR resolution.
119///
120/// `billing` 0.12 dropped its own `EuroAmount` alias — the engine is
121/// currency-agnostic and the name asserted a currency the type does not carry.
122/// EEG/KWKG Vergütung is euro-denominated by statute, so the alias is correct
123/// here; it just belongs to the domain crate rather than the engine.
124pub type EuroAmount = billing::Amount<5>;
125
126pub use aw_reductions::{AwReductionApplied, AwReductionContext, Sect54SolarReduction};
127pub use biomasse::{
128 bemessungsleistung_stunden, sect39i_hoechstanteil, sect44b_jahreskontingent_kwh,
129};
130pub use error::SettlementError;
131pub use foerderdauer::{
132 SECT51A_SOLAR_FACTOR_DENOMINATOR, calculate_pflichtzahlung, compute_billing_days_fraction,
133 foerderendedatum_eeg, foerderendedatum_eeg_ausschreibung, foerderendedatum_repowering,
134 kwk_eligible_kwh, kwk_max_kwh, pflichtzahlung_verjaehrt_am, sect52a_netztrennung_erforderlich,
135 verguetungszeitraum_verlaengerung_qh, wind_onshore_korrekturfaktor_corrected_aw,
136};
137pub use formula::calculate_settlement;
138pub use kwkg::{
139 KwkAnlagenart, KwkFoerderdauerInput, KwkLeistungsanteil, KwkVerwendung, KwkZuschlagInput,
140 foerderdauer_vollbenutzungsstunden, jahreshoechstbetrag_vollbenutzungsstunden,
141 jahreskontingent_kwh, zuschlag_ct_kwh, zuschlag_leistungsanteile,
142};
143pub use model::{
144 CapacityBlock, Pflichtverstoss, SanktionAlt, SanktionsTyp, SettleInput, SettleOutput,
145 SettlePosition, SettlementStatus,
146};
147pub use negativpreis::{
148 NegativpreisInterval, NegativpreisRegime, NegativpreisResult, derive_negativpreis,
149};
150pub use rounding::RoundMoney;
151pub use scheme::{
152 ANLAGE1_NR2_STICHTAG, AusschreibungMetadata, CorrectionReason, MarktpreisKategorie,
153 Marktwertserie, Paragraph100Rule, SettlementScheme, SettlementType, TariffSource,
154 marktwertserie,
155};
156pub use seed::{VerguetungssatzRow, verguetungssatz_rows};
157pub use technology::{
158 ErzeugungsArt, InbetriebnahmeTyp, InvalidErzeugungsArt, InvalidInbetriebnahmeTyp,
159 RepoweringScope,
160};
161pub use version::{EegGesetz, InvalidEegGesetz};
162pub use zusammenfassung::{
163 AnlageFuerZusammenfassung, SolarMontage, Steckersolar, ZusammenfassungErgebnis,
164 ZusammenfassungGrund, sind_eine_anlage, zusammenlegung_within_12_months,
165};
166
167// Domain module guide:
168// degression: §49 semi-annual solar AW degression — degressionsstufen, abgesenkter_wert
169// direktverm: §§20–22 Veräußerungsformen — Direktvermarktungspflicht (§21 Abs.1 S.1 Nr.1),
170// Ausschreibungspflicht (§22 Abs.2–5), Zuordnung und Wechsel (§21b/§21c)
171// (Metering topology, Eigenverbrauch/Überschuss split and §42b EnWG GGV allocation live in the
172// external `metering` crate — AggregationRule, compute_virtual_meter, MeasurementPoint, Messtyp.)
173// reductions: §52 Pflichtzahlungen — apply_sect52_netting, ReductionPipeline (euro level)
174// aw_reductions: §§53b–54 — cuts to the anzulegender Wert, applied before the formula
175// zusammenfassung: §24 Abs. 1 — sind_eine_anlage, the full Sätze 1–5 decision
176// settlement_state: Monthly lifecycle state machine — SettlementPeriodState, derive_settlement_state
177// wind: §36h Korrekturfaktor, Standortklasse, reference yield model
178// biomasse: §§42–44 fuel classes, Güllekleinanlage (≤75 kW, ≥80 % Gülle), §39i Abs. 1
179// kwkg: §§7–9 KWKG — Zuschlag je Leistungsanteil, Vollbenutzungsstunden, Jahreshöchstbetrag
180// seed: the §§40–49 net Einspeisevergütung series as flat rows, for a service's reference table
181// foerderungsende: FoerderendeGrund enum, SanktionStatus lifecycle
182// scheme: SettlementScheme, TariffSource, Paragraph100Rule, SettlementType