1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! 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
//!
//! ```text
//! 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 [`LegalReference`]s (e.g. `StromNEV §17`, `KAV §2`)
//! - the [`TariffSource`] justifying 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
//!
//! ```rust,no_run
//! 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);
//! }
//! # Ok::<(), grid_billing::BillingError>(())
//! ```
/// BO4E bridge (feature `bo4e`): [`crate::InvoiceDocument`] → `rubo4e` Rechnung.
pub use ;
pub use BillingError;
pub use ;
pub use ;
pub use ;