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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! Role-neutral **German grid settlement calculation** engine.
//!
//! Covers all DSO/TSO-side INVOIC documents:
//! - **NNE** (Netznutzungsentgelt) — PID 31002 (NN-Rechnung, Strom + Gas)
//! - **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 `crate::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 {
//! blindarbeit: None,
//! malo_id: "51238696012".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 and the three §14a
//! // modules 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),
//! // Cadence is a document fact (`IMD+7081`), and Abschläge are deducted
//! // from what is owed rather than from what was supplied.
//! cadence: Some(grid_billing::Rechnungscharakter::Monatsrechnung),
//! abschlaege: Vec::new(),
//! };
//! for (number, pos) in document.numbered_positions() {
//! println!("{number}. {}", pos.text);
//! }
//! # Ok::<(), grid_billing::BillingError>(())
//! ```
/// BO4E bridge (feature `bo4e`): [`crate::InvoiceDocument`] → `rubo4e` Rechnung.
/// A monetary amount in euro at 10⁻⁵-EUR resolution.
///
/// `billing` 0.12 dropped its own `EuroAmount` alias — the engine is
/// currency-agnostic and the name asserted a currency the type does not carry.
/// Netznutzungsentgelte are euro-denominated by statute, so the alias is correct
/// here; it just belongs to the domain crate rather than the engine.
pub type EuroAmount = Amount;
pub use ;
pub use BillingError;
pub use GasKapazitaet;
pub use ;
pub use ;
pub use ;
pub use ;