energy_billing/lib.rs
1//! Pure multi-product retail energy billing for German markets.
2//!
3//! ## Architecture
4//!
5//! This crate is the **commercial billing engine for the Lieferant (LF)**. It
6//! answers: *"What does the customer's invoice look like?"*
7//!
8//! ```text
9//! metering — "What quantities are billable?"
10//! ↓
11//! eeg-billing — "What EEG remuneration applies?" (NB-side)
12//! ↓
13//! energy-billing — "What does the customer's invoice look like?" (LF-side)
14//! ↓
15//! accountingd — Payments, Ledger, Dunning
16//! ```
17//!
18//! ## Primary API — `Product::build_engine`
19//!
20//! ```rust
21//! use energy_billing::{BillingContext, BillingPeriod, GridInput, InvoiceType, MeterInput, Product, Quantities, RegulatoryRates};
22//! use rust_decimal::dec;
23//! use time::macros::date;
24//!
25//! let json = r#"{"category":"STROM","arbeitspreis_ct_per_kwh":"30.0","grundpreis_ct_per_day":"8.0"}"#;
26//! let product: Product = serde_json::from_str(json).unwrap();
27//! let ctx = BillingContext {
28//! malo_id: "51238696012".to_owned(),
29//! lf_mp_id: "9900000000001".to_owned(),
30//! rechnungsnummer: "R2026-001".to_owned(),
31//! period: BillingPeriod::new(date!(2026-01-01), date!(2026-01-31)).unwrap(),
32//! invoice_type: InvoiceType::Initial,
33//! contract_id: None,
34//! regulatory_rates: RegulatoryRates::default(),
35//! ..Default::default()
36//! };
37//! let quantities = Quantities {
38//! electricity: Some(MeterInput { arbeitsmenge_kwh: dec!(500), ..Default::default() }),
39//! ..Default::default()
40//! };
41//! let invoice = product.build_engine(&GridInput::default(), &RegulatoryRates::default())
42//! .bill(ctx, &quantities).unwrap();
43//! assert!(invoice.brutto_eur > invoice.netto_eur);
44//! ```
45//!
46//! ## Product categories
47//!
48//! | Category | Provider | Legal basis |
49//! |---|---|---|
50//! | `STROM` | `ElectricityProvider` | §41 EnWG |
51//! | `WAERMEPUMPE` | `ControllableLoadProvider` (§14a) | §14a EnWG |
52//! | `WALLBOX` | `ControllableLoadProvider` (§14a) | §14a EnWG |
53//! | `GAS` | `GasProvider` | §41 EnWG |
54//! | `WAERME` | `HeatProvider` | §41 EnWG; AVBFernwärmeV §24; CO2KostAufG §3; §14 WPG |
55//! | `WASSER` | `WaterProvider` | AVBWasserV; §12 Abs. 2 Nr. 1 UStG (7 %); gesplittete Abwassergebühr |
56//! | `SOLAR` | `SolarProvider` | §42a Abs. 4 EnWG (Mieterstrom-Preisdeckel) / §42b EnWG (GGV) |
57//! | `EEG` | `EegProvider` (→ eeg-billing) | §§20–21 EEG 2023 |
58//! | `EINSPEISUNG` | `EinspeisungProvider` | §20 EEG 2023 |
59//! | `HEMS` | `HemsProvider` | — |
60//! | `EMOBILITY` | `EmobilityProvider` | §41a EnWG |
61//! | `ENERGIEDIENSTLEISTUNG` | `ServiceProvider` | — |
62//! | `STROM` + `dynamic_epex=true` | `DynamicElectricityProvider` | §41a EnWG |
63//! | `SHARING` | `ElectricityProvider` + `EnergyShareProvider` | §42c EnWG |
64
65#![deny(unsafe_code)]
66
67// ── Modules ───────────────────────────────────────────────────────────────────
68
69pub mod context;
70/// EN 16931 semantic-model bridge (`Invoice::to_en16931`), behind `en16931`.
71#[cfg(feature = "en16931")]
72pub mod en16931_map;
73pub mod engine;
74pub mod error;
75pub mod invoice;
76pub mod position;
77pub mod provider;
78pub mod providers;
79pub mod quantities;
80pub mod rates;
81/// Verbrauchsteuerliche Begünstigungen — Befreiung, Ermäßigung, Entlastung.
82///
83/// Only the first two change what a supplier invoices; the third is the
84/// customer's own claim at the Hauptzollamt. Keeping them apart is what stops a
85/// § 9b StromStG relief from being billed as a § 9 Abs. 1 exemption.
86pub mod steuer;
87pub mod tariff;
88
89// ── Primary API re-exports ────────────────────────────────────────────────────
90
91// Core billing types
92pub use context::{
93 AbschlagDeduction, BillingContext, BillingPeriod, CustomerKategorie, InvoiceType,
94 Rechnungsempfaenger, SettlementForm, Verbraucherinformationen, Verbrauchshistorie, Vertragsart,
95 Vertragsinformationen,
96};
97pub use engine::BillingEngine;
98pub use error::EngineError;
99pub use invoice::{
100 Invoice, TaxSubtotal, VatCategory, negate_rechnung_json_for_correction, tax_subtotals_of,
101};
102pub use position::{
103 BillingPosition, BillingWarning, PositionCategory, PositionTrace, WarningSeverity,
104};
105pub use provider::{BillingProvider, MTU_MINUTES, mtu_start};
106pub use quantities::{
107 Ablesungsart, Abschlagsplan, AbschlagsplanEntry, Absetzung, AbsetzungsGrund, DayApportionment,
108 DynamicInterval, EegMeterInput, EmobilityMeterInput, EnergyShareMeterInput, GasMeterInput,
109 GgvNutzungsplan, GgvNutzungsplanEntry, GgvSolarInput, GridInput, HemsMeterInput, MeterInput,
110 MeteringMode, ProsumerMeterInput, Quantities, Sect14aModul3Verbrauch, Sect41aAnnualComparison,
111 ServiceMeterInput, SolarMeterInput, WaermeMeterInput, WasserMeterInput,
112};
113pub use rates::{
114 ERDGAS_UMRECHNUNGSFAKTOR_GJ_PER_MWH, RegulatoryRates, RoundMoney, behg_ct_per_kwh_for_year,
115 behg_ct_per_kwh_from_price, energiesteuer_gas_for_year, erdgas_emissionsfaktor_kg_per_kwh,
116 mwst_rate_for_gas_waerme_period, mwst_rate_for_period, round_money,
117 steuer_stichtage_im_zeitraum, stromsteuer_for_year,
118};
119pub use steuer::{
120 EnergiesteuerBefreiung, EnergiesteuerTarif, Steuerentlastung, StromsteuerBefreiung,
121 StromsteuerErmaessigung, StromsteuerTarif,
122};
123
124// Typed Product enum + per-category product structs
125pub use tariff::{
126 AbwasserRegime, BlockTierInput, ControllableLoadProduct, EegProduct, EinspeisungProduct,
127 ElectricityProduct, EmobilityProduct, EnergieQuellen, GasProduct, HeatProduct, HemsProduct,
128 IndexedPriceConfig, Product, SeasonalPriceOverride, ServiceProduct, SharingProduct,
129 SolarProduct, WaterProduct,
130};
131
132// Concrete providers
133pub use providers::{
134 ControllableLoadProvider, DynamicElectricityProvider, EegProvider, EinspeisungProvider,
135 ElectricityProvider, EmobilityProvider, EnergyShareProvider, GasProvider, HeatProvider,
136 HemsProvider, MwStProvider, ServiceProvider, SolarProvider, WaterProvider,
137};
138
139// The arithmetic core — `Amount<P>` fixed-point money, the canonical
140// `RoundingStrategy` (kaufmännisch by convention in this workspace), and the
141// error reachable through [`EngineError::Arithmetic`]. `round_money` /
142// `RoundMoney` delegate their mode to this crate; use `Amount` directly
143// where the precision is statutory (cents, 10⁻⁵-EUR unit prices).
144pub use billing::{Amount, BillingError, RoundingStrategy};
145
146/// A monetary amount in euro at 10⁻⁵-EUR resolution.
147///
148/// `billing` 0.12 dropped its own `EuroAmount` alias — the engine is
149/// currency-agnostic and the name asserted a currency the type does not carry.
150/// German retail energy billing *is* euro-denominated, so the alias is correct
151/// here; it just belongs to the domain crate rather than the engine.
152pub type EuroAmount = Amount<5>;