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}