Expand description
Role-neutral German grid settlement calculation engine.
Covers all DSO/TSO-side INVOIC documents:
- NNE (Netznutzungsentgelt) — PID 31002 (NN-Rechnung, Strom + Gas); Abschlag 31001
- MMM (Mehr-/Mindermengensaldo) — PIDs 31005/31006 (aggregiert Gas 31007/31008)
- MSB (Messstellenbetrieb) — PID 31009
§Calculation flow
Input → validation → Settlement Engine → SettlementResult → into_rechnung() (service layer)SettlementResult is the canonical output. The service layer (netzbilanzd,
invoicd) converts it to BO4E Rechnung. This keeps grid-billing
publishable to crates.io without pulling in the internal rubo4e crate.
§Explainability
Every SettlementPosition carries a CalculationTrace with:
- input values (quantity, unit price)
- gross intermediate result before rounding
- applicable
LegalReferences (e.g.StromNEV §17,KAV §2) - the
TariffSourcejustifying each rate
§No float money
Quantities, rates, and factors are rust_decimal::Decimal; every EUR
result is range-checked through the billing::EuroAmount newtype
(Amount<5>) before it leaves the crate.
§Example
use grid_billing::{
ArbeitspreisModell, InvoiceDocument, KaKundengruppe, Konzessionsabgabe, MengePreis,
NneInput, SettlementPeriod, settle_nne,
};
use rust_decimal::Decimal;
use time::macros::date;
fn d(s: &str) -> Decimal { Decimal::from_str_exact(s).unwrap() }
// The engine is given what was supplied and at what rates — no invoice
// number, no issue date, no Prüfidentifikator.
let settlement = settle_nne(&NneInput {
malo_id: "51238696780".into(),
nb_mp_id: "9900357000004".into(),
lf_mp_id: "9900012345678".into(),
period: SettlementPeriod::new(date!(2025-01-01), date!(2025-01-31))?,
// One value, not twelve loose fields: flat rate, §14a Modul 1, Modul 2
// HT/NT and Modul 3 are mutually exclusive by construction.
arbeitspreis: ArbeitspreisModell::Einheitlich(MengePreis {
menge_kwh: d("1500"),
preis_ct_per_kwh: d("3.5"),
}),
leistungspreis: None,
letztverbrauchergruppe: Default::default(),
sect19_umlage_ct_per_kwh: None,
offshore_umlage_ct_per_kwh: None,
kwkg_umlage_ct_per_kwh: None,
grundpreis: None,
// Recorded so an auditor can check the rate came from the right sheet.
netzebene: Some(grid_billing::netzebene::Netzebene::Niederspannung),
sect19: None,
gas_kapazitaet: None,
jahreshoechstleistung_kw: None,
jahresarbeit_kwh: Some(d("18000")),
// Rate and customer group travel together, so the KAV §2 Höchstbetrag is
// always checked.
konzessionsabgabe: Some(Konzessionsabgabe {
satz_ct_per_kwh: d("0.11"),
klasse: KaKundengruppe::Sondervertragskunde,
}),
tariff_sheet_id: Some("Preisblatt-NNE-2025-Q1".into()),
sparte: grid_billing::Sparte::Strom,
})?;
// Every position explains itself:
for pos in &settlement.positions {
println!("{}: {}", pos.text, pos.trace.explanation);
}
for r in settlement.all_legal_refs() {
println!(" → {r}");
}
// Presenting it as an invoice is a separate step, and the only place
// document identity enters.
let document = InvoiceDocument {
settlement,
pid: 31002,
rechnungsnummer: "NNE-2025-001".into(),
correction_of: None,
invoice_date: date!(2025-02-15),
due_date: date!(2025-03-15),
};
for (number, pos) in document.numbered_positions() {
println!("{number}. {}", pos.text);
}Re-exports§
pub use billing::correct;pub use billing::reverse;pub use billing::settle_gas_awh;pub use billing::settle_mmm;pub use billing::settle_msb;pub use billing::settle_nne;pub use error::BillingError;pub use redispatch::RedispatchVerguetung;pub use redispatch::RedispatchVerguetungInput;pub use redispatch::RedispatchVerguetungsart;pub use redispatch::bilarem_finanzielle_korrektur;pub use redispatch::eeg_entgangene_einnahmen;pub use redispatch::redispatch_verguetung;pub use regulatory::EntgeltRegime;pub use regulatory::NetzzugangRegime;pub use regulatory::RegulatoryRegime;pub use regulatory::SECT19_ABS3_LETZTER_TAG;pub use regulatory::SECT19_ABS3_UEBERGANG_ENDE;pub use types::ArbeitspreisModell;pub use types::AwhPositionInput;pub use types::BillingPositionKind;pub use types::CalculationTrace;pub use types::GasAwhInput;pub use types::GemeindeGroesse;pub use types::Grundpreis;pub use types::InvoiceDocument;pub use types::KaKundengruppe;pub use types::Konzessionsabgabe;pub use types::LegalReference;pub use types::Leistungspreis;pub use types::MengePreis;pub use types::MmmInput;pub use types::MsbInput;pub use types::NneInput;pub use types::PriceReference;pub use types::PriceStep;pub use types::QuantityUnit;pub use types::Reduktionsfaktor;pub use types::Sect14aModul3Interval;pub use types::Sect14aModule;pub use types::SettlementPeriod;pub use types::SettlementPosition;pub use types::SettlementResult;pub use types::SettlementStatus;pub use types::SettlementType;pub use types::SettlementWarning;pub use types::Sparte;pub use types::SpotPriceFormula;pub use types::TariffCalculationMethod;pub use types::TariffSource;pub use types::ValidationResult;pub use types::WarningSeverity;pub use types::validate_gas_awh_input;pub use types::validate_mmm_input;pub use types::validate_msb_input;
Modules§
- billing
- NNE, MMM, and MSB settlement calculation logic.
- error
- Error types for
grid-billing. - gas
- Gas network structure — Druckstufen and Kapazitätsprodukte, §15 GasNEV.
- msbg
- Preisobergrenzen für den Messstellenbetrieb — §30 MsbG.
- netzebene
- Netzebenen und Entgeltsystematik — §17 StromNEV.
- redispatch
- §13a EnWG Redispatch 2.0 compensation (angemessene Vergütung).
- regulatory
- Which rules were in force for a delivery period.
- sect18
- Entgelte für dezentrale Erzeugung — §18 StromNEV, under Abschmelzung.
- sect19
- Individuelle Netzentgelte — §19 Abs. 2 StromNEV.
- types
- Input/output types for the settlement calculation engine.
- umlagen
- Netzseitige Umlagen — the levies the ÜNB collect through the Netzentgelt.