Skip to main content

grid_billing/
lib.rs

1//! Role-neutral **grid invoice calculation** for German energy market billing.
2//!
3//! Covers all DSO/TSO-side INVOIC documents:
4//! - **NNE** (Netznutzungsentgelt) — PIDs 31001, 31005, 31011
5//! - **MMM** (Mehr-/Mindermengensaldo) — PID 31002
6//! - **MSB** (Messstellenbetrieb) — PID 31009
7//! - **Selbst ausgestellte Rechnung** — PID 31006 (LF acting as NB)
8//!
9//! Used by both the **NB** (`netzbilanzd`) to generate invoices and the **LF**
10//! (`invoicd`) to self-issue invoices under §20 MessZV.  The formula is identical
11//! for both roles — only who initiates differs.
12//!
13//! Generates [`GridInvoice`] domain objects from meter readings and
14//! tariff data.  The service layer (`netzbilanzd`, `invoicd`) converts
15//! `GridInvoice` to `rubo4e::current::Rechnung` via a local `into_rechnung()`
16//! helper — keeping BO4E as a service-layer concern.  The generated invoices are:
17//! - **PID 31001** — `MMM-Rechnung NNE Strom` (NB → LF, monthly network usage)
18//! - **PID 31002** — `MMM-Stornorechnung NNE Strom` (NB → LF, correction/reversal)
19//! - **PID 31005** — `MMM-Rechnung NNE Gas` (NB → LF, monthly gas NNE)
20//! - **PID 31006** — `MMM-selbst ausgestellte Rechnung` (LF selbstausstellt, same formula)
21//!
22//! # Design
23//!
24//! - **Pure library** — zero I/O, zero async.  All calculations are deterministic.
25//! - **No floating-point money** — uses `EuroAmount` (`i64 × 10⁻⁵ EUR`) for all
26//!   monetary arithmetic to avoid rounding errors.
27//! - **Self-validating** — all generated invoices satisfy `invoic-checker` checks 1–3
28//!   (period validity, position arithmetic, document total) by construction.
29//!   Check 4–5 (tariff deviation) depends on the `PreisblattStore` supplied by the caller.
30//! - **BO4E-free output** — returns [`GridInvoice`] (pure domain type).  The service
31//!   layer (netzbilanzd, invoicd) owns the `rubo4e::current::Rechnung` conversion via a
32//!   local `into_rechnung()` helper.  This keeps grid-billing publishable to crates.io
33//!   without pulling in the internal `rubo4e` crate.
34//!
35//! # Example
36//!
37//! ```rust,no_run
38//! use grid_billing::{NneInput, calculate_nne_invoice};
39//! use rust_decimal::Decimal;
40//! use time::macros::date;
41//!
42//! fn d(s: &str) -> Decimal { Decimal::from_str_exact(s).unwrap() }
43//!
44//! let result = calculate_nne_invoice(&NneInput {
45//!     malo_id: "51238696780".into(),
46//!     nb_mp_id: "9900357000004".into(),
47//!     lf_mp_id: "9900012345678".into(),
48//!     rechnungsnummer: "NNE-2025-001".into(),
49//!     period_from: date!(2025-01-01),
50//!     period_to:   date!(2025-01-31),
51//!     invoice_date: date!(2025-02-15),
52//!     due_date: date!(2025-03-15),
53//!     arbeitsmenge_kwh: d("1500"),
54//!     arbeitspreis_ct_per_kwh: d("3.5"),
55//!     arbeitsmenge_ht_kwh: None,
56//!     arbeitspreis_ht_ct_per_kwh: None,
57//!     arbeitsmenge_nt_kwh: None,
58//!     arbeitspreis_nt_ct_per_kwh: None,
59//!     spitzenleistung_kw: None,
60//!     leistungspreis_eur_per_kw: None,
61//!     ka_satz_ct_per_kwh: Some(d("0.11")),
62//! }).expect("valid billing input");
63//! ```
64#![deny(unsafe_code)]
65
66pub mod billing;
67pub mod error;
68pub mod types;
69
70pub use billing::{calculate_mmm_invoice, calculate_msb_invoice, calculate_nne_invoice};
71pub use error::BillingError;
72pub use types::{GridInvoice, InvoicePosition, MmmInput, MsbInput, NneInput, QuantityUnit};