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) — PIDs 31001, 31005, 31006, 31011
5//! - **MMM** (Mehr-/Mindermengensaldo) — PID 31002
6//! - **MSB** (Messstellenbetrieb) — PID 31009
7//!
8//! ## Calculation flow
9//!
10//! ```text
11//! Input → validation → Settlement Engine → GridSettlement → into_rechnung() (service layer)
12//! ```
13//!
14//! [`GridSettlement`] 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 [`InvoicePosition`] 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//! All amounts use `rust_decimal::Decimal` and the `billing::EuroAmount` newtype.
29//!
30//! ## Example
31//!
32//! ```rust,no_run
33//! use grid_billing::{NneInput, calculate_nne_invoice};
34//! use rust_decimal::Decimal;
35//! use time::macros::date;
36//!
37//! fn d(s: &str) -> Decimal { Decimal::from_str_exact(s).unwrap() }
38//!
39//! let result = calculate_nne_invoice(&NneInput {
40//!     malo_id: "51238696780".into(),
41//!     nb_mp_id: "9900357000004".into(),
42//!     lf_mp_id: "9900012345678".into(),
43//!     rechnungsnummer: "NNE-2025-001".into(),
44//!     period_from: date!(2025-01-01),
45//!     period_to:   date!(2025-01-31),
46//!     invoice_date: date!(2025-02-15),
47//!     due_date: date!(2025-03-15),
48//!     arbeitsmenge_kwh: d("1500"),
49//!     arbeitspreis_ct_per_kwh: d("3.5"),
50//!     arbeitsmenge_ht_kwh: None,
51//!     arbeitspreis_ht_ct_per_kwh: None,
52//!     arbeitsmenge_nt_kwh: None,
53//!     arbeitspreis_nt_ct_per_kwh: None,
54//!     spitzenleistung_kw: None,
55//!     leistungspreis_eur_per_kw: None,
56//!     ka_satz_ct_per_kwh: Some(d("0.11")),
57//!     sect14a_modul1_reduction_factor: None,
58//!     nne_grundpreis_eur_per_month: None,
59//!     nne_grundpreis_months: None,
60//!     tariff_sheet_id: Some("Preisblatt-NNE-2025-Q1".into()),
61//!     sparte: grid_billing::Sparte::Strom,
62//!     ka_klasse: Some(grid_billing::KaKlasse::TarifkundeLow),
63//!     sect14a_modul3_intervals: vec![],
64//! }).expect("valid billing input");
65//!
66//! // Every position explains itself:
67//! for pos in &result.positions {
68//!     println!("{}: {}", pos.text, pos.trace.explanation);
69//! }
70//!
71//! // Legal references used:
72//! for r in result.all_legal_refs() {
73//!     println!("  → {r}");
74//! }
75//! ```
76#![deny(unsafe_code)]
77#![warn(missing_docs)]
78
79pub mod billing;
80pub mod error;
81pub mod types;
82
83pub use billing::{
84    calculate_correction, calculate_gas_awh_invoice, calculate_mmm_invoice, calculate_msb_invoice,
85    calculate_nne_invoice, calculate_reversal,
86};
87pub use error::BillingError;
88pub use types::{
89    // AWH positions
90    AwhPositionInput,
91    // BDEW Artikelnummer bridge — maps InvoicePosition.kind → BdewArtikelnummer in service layer
92    BillingPositionKind,
93    // Domain types for explainability + audit
94    CalculationTrace,
95    // Input types
96    GasAwhInput,
97    // Backward-compatible alias for GridSettlement — same type, kept for call-site stability
98    GridInvoice,
99    // Core settlement output
100    GridSettlement,
101    InvoicePosition,
102    KaKlasse,
103    LegalReference,
104    MmmInput,
105    MsbInput,
106    NneInput,
107    QuantityUnit,
108    // §14a module type (replaces module: u8 for type safety)
109    Sect14aModule,
110    SettlementStatus,
111    SettlementType,
112    SettlementWarning,
113    Sparte,
114    TariffSource,
115    ValidationResult,
116    WarningSeverity,
117    // Validation functions
118    validate_gas_awh_input,
119    validate_mmm_input,
120    validate_msb_input,
121    validate_nne_input,
122};