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
//! Pure multi-product retail energy billing for German markets.
//!
//! ## Architecture
//!
//! This crate is the **commercial billing engine for the Lieferant (LF)**. It
//! answers: *"What does the customer's invoice look like?"*
//!
//! ```text
//! metering — "What quantities are billable?"
//! ↓
//! eeg-billing — "What EEG remuneration applies?" (NB-side)
//! ↓
//! energy-billing — "What does the customer's invoice look like?" (LF-side)
//! ↓
//! accountingd — Payments, Ledger, Dunning
//! ```
//!
//! ## Primary API — `Product::build_engine`
//!
//! ```rust
//! use energy_billing::{BillingContext, BillingPeriod, GridInput, InvoiceType, MeterInput, Product, Quantities, RegulatoryRates};
//! use rust_decimal::dec;
//! use time::macros::date;
//!
//! let json = r#"{"category":"STROM","arbeitspreis_ct_per_kwh":"30.0","grundpreis_ct_per_day":"8.0"}"#;
//! let product: Product = serde_json::from_str(json).unwrap();
//! let ctx = BillingContext {
//! malo_id: "51238696012".to_owned(),
//! lf_mp_id: "9900000000001".to_owned(),
//! rechnungsnummer: "R2026-001".to_owned(),
//! period: BillingPeriod::new(date!(2026-01-01), date!(2026-01-31)).unwrap(),
//! invoice_type: InvoiceType::Initial,
//! contract_id: None,
//! regulatory_rates: RegulatoryRates::default(),
//! ..Default::default()
//! };
//! let quantities = Quantities {
//! electricity: Some(MeterInput { arbeitsmenge_kwh: dec!(500), ..Default::default() }),
//! ..Default::default()
//! };
//! let invoice = product.build_engine(&GridInput::default(), &RegulatoryRates::default())
//! .bill(ctx, &quantities).unwrap();
//! assert!(invoice.brutto_eur > invoice.netto_eur);
//! ```
//!
//! ## Product categories
//!
//! | Category | Provider | Legal basis |
//! |---|---|---|
//! | `STROM` | `ElectricityProvider` | §41 EnWG |
//! | `WAERMEPUMPE` | `ControllableLoadProvider` (§14a) | §14a EnWG |
//! | `WALLBOX` | `ControllableLoadProvider` (§14a) | §14a EnWG |
//! | `GAS` | `GasProvider` | §41 EnWG |
//! | `WAERME` | `HeatProvider` | §41 EnWG; AVBFernwärmeV §24; CO2KostAufG §3; §14 WPG |
//! | `WASSER` | `WaterProvider` | AVBWasserV; §12 Abs. 2 Nr. 1 UStG (7 %); gesplittete Abwassergebühr |
//! | `SOLAR` | `SolarProvider` | §42a Abs. 4 EnWG (Mieterstrom-Preisdeckel) / §42b EnWG (GGV) |
//! | `EEG` | `EegProvider` (→ eeg-billing) | §§20–21 EEG 2023 |
//! | `EINSPEISUNG` | `EinspeisungProvider` | §20 EEG 2023 |
//! | `HEMS` | `HemsProvider` | — |
//! | `EMOBILITY` | `EmobilityProvider` | §41a EnWG |
//! | `ENERGIEDIENSTLEISTUNG` | `ServiceProvider` | — |
//! | `STROM` + `dynamic_epex=true` | `DynamicElectricityProvider` | §41a EnWG |
//! | `SHARING` | `ElectricityProvider` + `EnergyShareProvider` | §42c EnWG |
// ── Modules ───────────────────────────────────────────────────────────────────
/// EN 16931 semantic-model bridge (`Invoice::to_en16931`), behind `en16931`.
/// Verbrauchsteuerliche Begünstigungen — Befreiung, Ermäßigung, Entlastung.
///
/// Only the first two change what a supplier invoices; the third is the
/// customer's own claim at the Hauptzollamt. Keeping them apart is what stops a
/// § 9b StromStG relief from being billed as a § 9 Abs. 1 exemption.
// ── Primary API re-exports ────────────────────────────────────────────────────
// Core billing types
pub use ;
pub use BillingEngine;
pub use EngineError;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
// Typed Product enum + per-category product structs
pub use ;
// Concrete providers
pub use ;
// The arithmetic core — `Amount<P>` fixed-point money, the canonical
// `RoundingStrategy` (kaufmännisch by convention in this workspace), and the
// error reachable through [`EngineError::Arithmetic`]. `round_money` /
// `RoundMoney` delegate their mode to this crate; use `Amount` directly
// where the precision is statutory (cents, 10⁻⁵-EUR unit prices).
pub use ;
/// 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.
/// German retail energy billing *is* euro-denominated, so the alias is correct
/// here; it just belongs to the domain crate rather than the engine.
pub type EuroAmount = ;