grid_billing/lib.rs
1//! Role-neutral **German grid settlement calculation** engine.
2//!
3//! Covers all DSO/TSO-side INVOIC documents:
4//! - **NNE** (Netznutzungsentgelt) — PID 31002 (NN-Rechnung, Strom + Gas)
5//! - **MMM** (Mehr-/Mindermengensaldo) — PIDs 31005/31006 (aggregiert Gas 31007/31008)
6//! - **MSB** (Messstellenbetrieb) — PID 31009
7//!
8//! ## Calculation flow
9//!
10//! ```text
11//! Input → validation → Settlement Engine → SettlementResult → into_rechnung() (service layer)
12//! ```
13//!
14//! [`SettlementResult`] is the canonical output. The service layer (`netzbilanzd`,
15//! `invoicd`) converts it to BO4E `Rechnung`. This keeps `grid-billing`
16//! publishable to crates.io without pulling in the internal `rubo4e` crate.
17//!
18//! ## Explainability
19//!
20//! Every [`SettlementPosition`] carries a [`CalculationTrace`] with:
21//! - input values (quantity, unit price)
22//! - gross intermediate result before rounding
23//! - applicable [`LegalReference`]s (e.g. `StromNEV §17`, `KAV §2`)
24//! - the [`TariffSource`] justifying each rate
25//!
26//! ## No float money
27//!
28//! Quantities, rates, and factors are `rust_decimal::Decimal`; every EUR
29//! result is range-checked through the `crate::EuroAmount` newtype
30//! (`Amount<5>`) before it leaves the crate.
31//!
32//! ## Example
33//!
34//! ```rust,no_run
35//! use grid_billing::{
36//! ArbeitspreisModell, InvoiceDocument, KaKundengruppe, Konzessionsabgabe, MengePreis,
37//! NneInput, SettlementPeriod, settle_nne,
38//! };
39//! use rust_decimal::Decimal;
40//! use time::macros::date;
41//!
42//! fn d(s: &str) -> Decimal { Decimal::from_str_exact(s).unwrap() }
43//!
44//! // The engine is given what was supplied and at what rates — no invoice
45//! // number, no issue date, no Prüfidentifikator.
46//! let settlement = settle_nne(&NneInput {
47//! blindarbeit: None,
48//! malo_id: "51238696012".into(),
49//! nb_mp_id: "9900357000004".into(),
50//! lf_mp_id: "9900012345678".into(),
51//! period: SettlementPeriod::new(date!(2025-01-01), date!(2025-01-31))?,
52//! // One value, not twelve loose fields: flat rate and the three §14a
53//! // modules are mutually exclusive by construction.
54//! arbeitspreis: ArbeitspreisModell::Einheitlich(MengePreis {
55//! menge_kwh: d("1500"),
56//! preis_ct_per_kwh: d("3.5"),
57//! }),
58//! leistungspreis: None,
59//! letztverbrauchergruppe: Default::default(),
60//! sect19_umlage_ct_per_kwh: None,
61//! offshore_umlage_ct_per_kwh: None,
62//! kwkg_umlage_ct_per_kwh: None,
63//! grundpreis: None,
64//! // Recorded so an auditor can check the rate came from the right sheet.
65//! netzebene: Some(grid_billing::netzebene::Netzebene::Niederspannung),
66//! sect19: None,
67//! gas_kapazitaet: None,
68//! jahreshoechstleistung_kw: None,
69//! jahresarbeit_kwh: Some(d("18000")),
70//! // Rate and customer group travel together, so the KAV §2 Höchstbetrag is
71//! // always checked.
72//! konzessionsabgabe: Some(Konzessionsabgabe {
73//! satz_ct_per_kwh: d("0.11"),
74//! klasse: KaKundengruppe::Sondervertragskunde,
75//! }),
76//! tariff_sheet_id: Some("Preisblatt-NNE-2025-Q1".into()),
77//! sparte: grid_billing::Sparte::Strom,
78//! })?;
79//!
80//! // Every position explains itself:
81//! for pos in &settlement.positions {
82//! println!("{}: {}", pos.text, pos.trace.explanation);
83//! }
84//! for r in settlement.all_legal_refs() {
85//! println!(" → {r}");
86//! }
87//!
88//! // Presenting it as an invoice is a separate step, and the only place
89//! // document identity enters.
90//! let document = InvoiceDocument {
91//! settlement,
92//! pid: 31002,
93//! rechnungsnummer: "NNE-2025-001".into(),
94//! correction_of: None,
95//! invoice_date: date!(2025-02-15),
96//! due_date: date!(2025-03-15),
97//! // Cadence is a document fact (`IMD+7081`), and Abschläge are deducted
98//! // from what is owed rather than from what was supplied.
99//! cadence: Some(grid_billing::Rechnungscharakter::Monatsrechnung),
100//! abschlaege: Vec::new(),
101//! };
102//! for (number, pos) in document.numbered_positions() {
103//! println!("{number}. {}", pos.text);
104//! }
105//! # Ok::<(), grid_billing::BillingError>(())
106//! ```
107#![deny(unsafe_code)]
108#![warn(missing_docs)]
109
110pub mod billing;
111/// BO4E bridge (feature `bo4e`): [`crate::InvoiceDocument`] → `rubo4e` Rechnung.
112#[cfg(feature = "bo4e")]
113pub mod bo4e;
114pub mod error;
115pub mod gas;
116pub mod msbg;
117pub mod netzebene;
118pub mod redispatch;
119pub mod regulatory;
120pub mod sect18;
121pub mod sect19;
122pub mod types;
123pub mod umlagen;
124pub mod umsatzsteuer;
125
126/// A monetary amount in euro at 10⁻⁵-EUR resolution.
127///
128/// `billing` 0.12 dropped its own `EuroAmount` alias — the engine is
129/// currency-agnostic and the name asserted a currency the type does not carry.
130/// Netznutzungsentgelte are euro-denominated by statute, so the alias is correct
131/// here; it just belongs to the domain crate rather than the engine.
132pub type EuroAmount = ::billing::Amount<5>;
133
134pub use billing::{
135 correct, reverse, settle_abschlag, settle_gas_awh, settle_mmm, settle_msb, settle_nne,
136};
137pub use error::BillingError;
138pub use gas::GasKapazitaet;
139pub use redispatch::{
140 AusfallarbeitBasis, RedispatchVerguetung, RedispatchVerguetungInput, RedispatchVerguetungsart,
141 bilarem_finanzielle_korrektur, eeg_entgangene_einnahmen, redispatch_verguetung,
142};
143pub use regulatory::{
144 // Which rules were in force for the delivery period — resolved once, at
145 // the edge, and recorded on every SettlementResult.
146 EntgeltRegime,
147 NetzzugangRegime,
148 RegulatoryRegime,
149 SECT19_ABS3_LETZTER_TAG,
150 SECT19_ABS3_UEBERGANG_ENDE,
151};
152pub use types::{
153 // Abschlagsrechnung — a payment on account, and its later deduction.
154 AbschlagGrundlage,
155 AbschlagInput,
156 Abschlagsverrechnung,
157 // The settlement — what is owed and why.
158 ArbeitspreisModell,
159 // AWH positions
160 AwhPositionInput,
161 // BDEW Artikelnummer bridge — maps SettlementPosition.kind → BdewArtikelnummer in service layer
162 BillingPositionKind,
163 // Reactive energy and the terms its excess is charged on.
164 Blindarbeit,
165 // Domain types for explainability + audit
166 CalculationTrace,
167 // Input types
168 GasAwhInput,
169 GemeindeGroesse,
170 Grundpreis,
171 // Presenting a settlement as an invoice: numbers, dates, Prüfidentifikator.
172 InvoiceDocument,
173 KaKundengruppe,
174 Konzessionsabgabe,
175 KorrekturGrund,
176 LegalReference,
177 Leistungspreis,
178 MengePreis,
179 MmmInput,
180 MsbEmpfaengerRolle,
181 MsbInput,
182 MsbRechnungsempfaenger,
183 NneInput,
184 // The pricing formula behind a rate, as a value rather than a document.
185 PriceReference,
186 PriceStep,
187 QuantityUnit,
188 Rechnungscharakter,
189 Reduktionsfaktor,
190 // §14a module type (replaces module: u8 for type safety)
191 Sect14aModule,
192 SettlementPeriod,
193 SettlementPosition,
194 SettlementResult,
195 SettlementStatus,
196 SettlementType,
197 SettlementWarning,
198 Sparte,
199 SpotPriceFormula,
200 SpotpreisInterval,
201 TariffCalculationMethod,
202 TariffSource,
203 ValidationResult,
204 WarningSeverity,
205 // Validation functions
206 validate_gas_awh_input,
207 validate_mmm_input,
208 validate_msb_input,
209};
210pub use umsatzsteuer::{
211 Leistungsart, Steuerausweis, TaxCategory, Wiederverkaeuferstatus, regelsatz_prozent,
212 steuerausweis,
213};