Skip to main content

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};