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); Abschlag 31001
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 `billing::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//! malo_id: "51238696780".into(),
48//! nb_mp_id: "9900357000004".into(),
49//! lf_mp_id: "9900012345678".into(),
50//! period: SettlementPeriod::new(date!(2025-01-01), date!(2025-01-31))?,
51//! // One value, not twelve loose fields: flat rate, §14a Modul 1, Modul 2
52//! // HT/NT and Modul 3 are mutually exclusive by construction.
53//! arbeitspreis: ArbeitspreisModell::Einheitlich(MengePreis {
54//! menge_kwh: d("1500"),
55//! preis_ct_per_kwh: d("3.5"),
56//! }),
57//! leistungspreis: None,
58//! letztverbrauchergruppe: Default::default(),
59//! sect19_umlage_ct_per_kwh: None,
60//! offshore_umlage_ct_per_kwh: None,
61//! kwkg_umlage_ct_per_kwh: None,
62//! grundpreis: None,
63//! // Recorded so an auditor can check the rate came from the right sheet.
64//! netzebene: Some(grid_billing::netzebene::Netzebene::Niederspannung),
65//! sect19: None,
66//! gas_kapazitaet: None,
67//! jahreshoechstleistung_kw: None,
68//! jahresarbeit_kwh: Some(d("18000")),
69//! // Rate and customer group travel together, so the KAV §2 Höchstbetrag is
70//! // always checked.
71//! konzessionsabgabe: Some(Konzessionsabgabe {
72//! satz_ct_per_kwh: d("0.11"),
73//! klasse: KaKundengruppe::Sondervertragskunde,
74//! }),
75//! tariff_sheet_id: Some("Preisblatt-NNE-2025-Q1".into()),
76//! sparte: grid_billing::Sparte::Strom,
77//! })?;
78//!
79//! // Every position explains itself:
80//! for pos in &settlement.positions {
81//! println!("{}: {}", pos.text, pos.trace.explanation);
82//! }
83//! for r in settlement.all_legal_refs() {
84//! println!(" → {r}");
85//! }
86//!
87//! // Presenting it as an invoice is a separate step, and the only place
88//! // document identity enters.
89//! let document = InvoiceDocument {
90//! settlement,
91//! pid: 31002,
92//! rechnungsnummer: "NNE-2025-001".into(),
93//! correction_of: None,
94//! invoice_date: date!(2025-02-15),
95//! due_date: date!(2025-03-15),
96//! };
97//! for (number, pos) in document.numbered_positions() {
98//! println!("{number}. {}", pos.text);
99//! }
100//! # Ok::<(), grid_billing::BillingError>(())
101//! ```
102#![deny(unsafe_code)]
103#![warn(missing_docs)]
104
105pub mod billing;
106/// BO4E bridge (feature `bo4e`): [`crate::InvoiceDocument`] → `rubo4e` Rechnung.
107#[cfg(feature = "bo4e")]
108pub mod bo4e;
109pub mod error;
110pub mod gas;
111pub mod msbg;
112pub mod netzebene;
113pub mod redispatch;
114pub mod regulatory;
115pub mod sect18;
116pub mod sect19;
117pub mod types;
118pub mod umlagen;
119
120pub use billing::{correct, reverse, settle_gas_awh, settle_mmm, settle_msb, settle_nne};
121pub use error::BillingError;
122pub use redispatch::{
123 RedispatchVerguetung, RedispatchVerguetungInput, RedispatchVerguetungsart,
124 bilarem_finanzielle_korrektur, eeg_entgangene_einnahmen, redispatch_verguetung,
125};
126pub use regulatory::{
127 // Which rules were in force for the delivery period — resolved once, at
128 // the edge, and recorded on every SettlementResult.
129 EntgeltRegime,
130 NetzzugangRegime,
131 RegulatoryRegime,
132 SECT19_ABS3_LETZTER_TAG,
133 SECT19_ABS3_UEBERGANG_ENDE,
134};
135pub use types::{
136 // The settlement — what is owed and why.
137 ArbeitspreisModell,
138 // AWH positions
139 AwhPositionInput,
140 // BDEW Artikelnummer bridge — maps SettlementPosition.kind → BdewArtikelnummer in service layer
141 BillingPositionKind,
142 // Domain types for explainability + audit
143 CalculationTrace,
144 // Input types
145 GasAwhInput,
146 GemeindeGroesse,
147 Grundpreis,
148 // Presenting a settlement as an invoice: numbers, dates, Prüfidentifikator.
149 InvoiceDocument,
150 KaKundengruppe,
151 Konzessionsabgabe,
152 LegalReference,
153 Leistungspreis,
154 MengePreis,
155 MmmInput,
156 MsbInput,
157 NneInput,
158 // The pricing formula behind a rate, as a value rather than a document.
159 PriceReference,
160 PriceStep,
161 QuantityUnit,
162 Reduktionsfaktor,
163 Sect14aModul3Interval,
164 // §14a module type (replaces module: u8 for type safety)
165 Sect14aModule,
166 SettlementPeriod,
167 SettlementPosition,
168 SettlementResult,
169 SettlementStatus,
170 SettlementType,
171 SettlementWarning,
172 Sparte,
173 SpotPriceFormula,
174 TariffCalculationMethod,
175 TariffSource,
176 ValidationResult,
177 WarningSeverity,
178 // Validation functions
179 validate_gas_awh_input,
180 validate_mmm_input,
181 validate_msb_input,
182};