Skip to main content

grid_billing/
types.rs

1//! Input/output types for the billing calculation functions.
2
3use rust_decimal::Decimal;
4
5// ── QuantityUnit ──────────────────────────────────────────────────────────────────────────────
6
7/// Unit of measure for an invoice position quantity.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum QuantityUnit {
10    /// Kilowatt-hours (energy).
11    Kwh,
12    /// Kilowatts (demand / peak load).
13    Kw,
14    /// Calendar months.
15    Monat,
16}
17
18// ── InvoicePosition ───────────────────────────────────────────────────────────────────
19
20/// One line item in a grid invoice.
21///
22/// Carries raw numbers for the service layer to map into the required format
23/// (BO4E `Rechnungsposition`, EN16931 UBL, etc.).
24/// Invariant: `net_eur == (quantity × unit_price_eur).round_dp(5)`.
25#[derive(Debug, Clone)]
26pub struct InvoicePosition {
27    /// 1-based sequence number.
28    pub number: u32,
29    /// Human-readable position description.
30    pub text: String,
31    /// Metered or contracted quantity.
32    pub quantity: Decimal,
33    /// Unit of measure.
34    pub unit: QuantityUnit,
35    /// Unit price in EUR (already converted from ct where applicable).
36    pub unit_price_eur: Decimal,
37    /// Net amount in EUR, rounded to 5 decimal places.
38    /// May be negative for credit positions (Mindermengen, Gutschriften).
39    pub net_eur: Decimal,
40}
41
42// ── GridInvoice ──────────────────────────────────────────────────────────────────────────
43
44/// Result of a grid invoice calculation — pure domain type, no BO4E coupling.
45///
46/// Call a local `into_rechnung()` helper in the service layer (netzbilanzd /
47/// invoicd) to produce the `rubo4e::current::Rechnung` required for EDIFACT
48/// serialization and `invoic-checker` validation.
49///
50/// # PID override
51///
52/// `pid` defaults to the primary PID for each function:
53/// - `calculate_nne_invoice` → `31001` (caller sets `31005` for Gas, `31006` for selbstausstellt)
54/// - `calculate_mmm_invoice` → `31002`
55/// - `calculate_msb_invoice` → `31009`
56/// - GeLi Gas AWH: caller overrides to `31011`
57#[derive(Debug, Clone)]
58pub struct GridInvoice {
59    /// BDEW Prüfidentifikator — caller may override after construction.
60    pub pid: u32,
61    /// Unique invoice reference number.
62    pub rechnungsnummer: String,
63    /// Invoice issue date.
64    pub invoice_date: time::Date,
65    /// Payment due date (Zahlungsziel, §271 BGB).
66    pub due_date: time::Date,
67    /// Start of billing period (inclusive).
68    pub period_from: time::Date,
69    /// End of billing period (inclusive).
70    pub period_to: time::Date,
71    /// Sender MP-ID — Netzbetreiber (or MSB for PID 31009).
72    pub nb_mp_id: String,
73    /// Ordered billing positions.
74    pub positions: Vec<InvoicePosition>,
75    /// Net total in EUR, rounded to 2 decimal places.
76    pub total_eur: Decimal,
77}
78
79impl GridInvoice {
80    /// Number of billing positions.
81    #[must_use]
82    pub fn positions_count(&self) -> usize {
83        self.positions.len()
84    }
85}
86
87/// Input for NNE (Netznutzungsentgelt) invoice calculation.
88///
89/// Covers:
90/// - **PID 31001** — NNE Strom (NB → LF, monthly network usage billing)
91/// - **PID 31005** — NNE Gas (NB → LF, monthly gas network usage billing)
92///
93/// For **RLM** (Leistungsmessung) meters:
94/// - Set `spitzenleistung_kw` to the peak demand in kW.
95/// - Set `leistungspreis_eur_per_kw` to the published tariff.
96///
97/// For **SLP** meters:
98/// - Leave both fields as `None` (Arbeitspreisanteil only).
99///
100/// For **§14a Modul 2 time-variable NNE** (BNetzA BK6-22-300):
101/// - Set `arbeitsmenge_ht_kwh` + `arbeitspreis_ht_ct_per_kwh` for Hochlast periods.
102/// - Set `arbeitsmenge_nt_kwh` + `arbeitspreis_nt_ct_per_kwh` for Niedertarif periods.
103/// - Leave `arbeitsmenge_kwh` / `arbeitspreis_ct_per_kwh` as the base fallback.
104///
105/// For Gas:
106/// - The `arbeitsmenge_kwh` should already be converted from m³ using
107///   `brennwert × zustandszahl` before being supplied here.
108///   (`mako-edm` `MeterBillingPeriod.arbeitsmenge_kwh` carries this converted value.)
109#[derive(Debug, Clone)]
110pub struct NneInput {
111    /// 11-digit Marktlokations-ID.
112    pub malo_id: String,
113    /// Invoice sender — Netzbetreiber or Gasnetzbetreiber MP-ID.
114    pub nb_mp_id: String,
115    /// Invoice recipient — Lieferant MP-ID.
116    pub lf_mp_id: String,
117    /// Unique invoice number (operator-generated).
118    pub rechnungsnummer: String,
119    /// Start of billing period (inclusive, German local date).
120    pub period_from: time::Date,
121    /// End of billing period (inclusive, German local date).
122    pub period_to: time::Date,
123    /// Invoice issue date.
124    pub invoice_date: time::Date,
125    /// Payment due date (Zahlungsziel).
126    pub due_date: time::Date,
127    /// Total energy consumption in kWh for the billing period.
128    ///
129    /// For Gas: already converted from m³ (brennwert × zustandszahl × volume).
130    /// Used when HT/NT split is not available (SLP, Gas, or pre-§14a deployments).
131    pub arbeitsmenge_kwh: Decimal,
132    /// Published NNE Arbeitspreis in **ct/kWh** (from `PreisblattNetznutzung`).
133    /// Used as the single Arbeit rate when HT/NT split is absent.
134    pub arbeitspreis_ct_per_kwh: Decimal,
135
136    // ── §14a Modul 2 time-variable (ToU) NNE ─────────────────────────────────
137    // BNetzA BK6-22-300: mandatory for all controllable loads since 01.01.2024.
138    // When both fields below are non-None, the billing engine generates two
139    // separate Arbeit positions (HT + NT) instead of a single blended position.
140    // Source: `edmd` MeterBillingPeriod.arbeitsmenge_ht_kwh / .arbeitsmenge_nt_kwh.
141    /// Hochlast (HT) consumption in kWh — §14a Modul 2 periods (higher-price band).
142    /// `None` when ToU metering is not configured for this MaLo.
143    pub arbeitsmenge_ht_kwh: Option<Decimal>,
144    /// HT Arbeitspreis in ct/kWh (from `PreisblattNetznutzung.zeitvariablePreispositionen`).
145    /// Required when `arbeitsmenge_ht_kwh` is set.
146    pub arbeitspreis_ht_ct_per_kwh: Option<Decimal>,
147    /// Niedertarif (NT) consumption in kWh — §14a Modul 2 off-peak periods.
148    /// `None` when ToU metering is not configured for this MaLo.
149    pub arbeitsmenge_nt_kwh: Option<Decimal>,
150    /// NT Arbeitspreis in ct/kWh (from `PreisblattNetznutzung.zeitvariablePreispositionen`).
151    /// Required when `arbeitsmenge_nt_kwh` is set.
152    pub arbeitspreis_nt_ct_per_kwh: Option<Decimal>,
153
154    // ── RLM demand charge ─────────────────────────────────────────────────────
155    /// Peak demand in **kW** (`spitzenleistung_kw` from `MeterBillingPeriod`).
156    ///
157    /// `None` for SLP meters and Gas MaLos.
158    pub spitzenleistung_kw: Option<Decimal>,
159    /// Published NNE Leistungspreis in **EUR/kW** (from `PreisblattNetznutzung`).
160    ///
161    /// `None` when `spitzenleistung_kw` is `None`.
162    pub leistungspreis_eur_per_kw: Option<Decimal>,
163    /// Published Konzessionsabgabe rate in **ct/kWh** (from `PreisblattKonzessionsabgabe`).
164    ///
165    /// `None` when KA does not apply (Gas or exempt customer class).
166    pub ka_satz_ct_per_kwh: Option<Decimal>,
167}
168
169// ── MmmInput ──────────────────────────────────────────────────────────────────
170
171/// Input for Mehr-/Mindermengen (MMM) settlement invoice calculation.
172///
173/// Covers:
174/// - **PID 31002** — `MMM-Stornorechnung NNE Strom` used for Mehr-/Mindermengen
175///   settlement between NB and LF.
176///
177/// Mehr-/Mindermengen settle the difference between the LF's forecast profile
178/// (SLP standard load profile) and the actual measured consumption.
179///
180/// - **Mehrmengen** (positive deviation): actual > profil → LF owes NB
181/// - **Mindermengen** (negative deviation): actual < profil → NB owes LF
182///
183/// The settlement amount is the algebraic sum of both positions.  It can be
184/// negative (i.e. a credit note from NB to LF) when Mindermengen dominate.
185#[derive(Debug, Clone)]
186pub struct MmmInput {
187    /// 11-digit Marktlokations-ID.
188    pub malo_id: String,
189    /// Invoice sender — Netzbetreiber MP-ID.
190    pub nb_mp_id: String,
191    /// Invoice recipient — Lieferant MP-ID.
192    pub lf_mp_id: String,
193    /// Unique invoice number.
194    pub rechnungsnummer: String,
195    /// Start of billing period.
196    pub period_from: time::Date,
197    /// End of billing period.
198    pub period_to: time::Date,
199    /// Invoice issue date.
200    pub invoice_date: time::Date,
201    /// Payment due date.
202    pub due_date: time::Date,
203    /// Actual measured consumption in kWh (from MSCONS / `MeterBillingPeriod`).
204    pub actual_kwh: Decimal,
205    /// Standard load profile (SLP) forecast consumption in kWh.
206    pub profil_kwh: Decimal,
207    /// Mehrmengen price in **ct/kWh** (from `PreisblattNetznutzung` MMM position).
208    pub mehr_preis_ct_per_kwh: Decimal,
209    /// Mindermengen price in **ct/kWh** (from `PreisblattNetznutzung` MMM position).
210    pub minder_preis_ct_per_kwh: Decimal,
211}
212
213// ── MsbInput ──────────────────────────────────────────────────────────────────
214
215/// Input for MSB (Messstellenbetreiber) invoice calculation.
216///
217/// Covers:
218/// - **PID 31009** — MSB-Rechnung (NB → MSB, monthly metering service settlement)
219///
220/// The NB bills the MSB for the metering service period.  Positions:
221/// 1. Grundgebühr Messstellenbetrieb — flat monthly base fee × billing months.
222/// 2. Messdienstleistung — optional per-period measurement service fee.
223#[derive(Debug, Clone)]
224pub struct MsbInput {
225    /// 11-digit Marktlokations-ID.
226    pub malo_id: String,
227    /// Invoice sender — Netzbetreiber MP-ID.
228    pub nb_mp_id: String,
229    /// Invoice recipient — Messstellenbetreiber MP-ID.
230    pub msb_mp_id: String,
231    /// Unique invoice number.
232    pub rechnungsnummer: String,
233    /// Start of billing period (inclusive, German local date).
234    pub period_from: time::Date,
235    /// End of billing period (inclusive, German local date).
236    pub period_to: time::Date,
237    /// Invoice issue date.
238    pub invoice_date: time::Date,
239    /// Payment due date.
240    pub due_date: time::Date,
241    /// Grundgebühr Messstellenbetrieb in **EUR/month** (from `PreisblattMessung`).
242    pub grundgebuehr_eur_per_month: Decimal,
243    /// Number of full calendar months in the billing period.
244    pub billing_months: u32,
245    /// Optional Messdienstleistung flat fee in **EUR** for the full period.
246    ///
247    /// `None` when the MSB provides only the meter, not a separate measurement service.
248    pub messdienstleistung_eur: Option<Decimal>,
249}