metering

EEG and EnFG

MiSpeL (BNetzA 618-25-02, adopted 01.10.2026) for storage and charge points, the EEG § 51/§ 51a negative-price quarter-hours, and EnFG § 46 Zeitgleichheit.

ModuleRuleWhat leaves it
eeg::mispelMiSpeL Anlage 1 and 2the Abgrenzungs- and Pauschaloption quantities, formula by formula
eeg::negative_pricesEEG § 51, § 51azero-value quarter-hours, the extension of the Vergütungszeitraum, the § 51 Abs. 3 energy
eeg::zeitgleichheitEnFG § 46 Abs. 3 and 5the per-interval coincident quantity, the worst-case estimate

Quantities only. Where a rule turns on the sign of a price (AW¼ > 0, SP¼ ≥ 0, a negative spot price) you pass which quarter-hours pass, never the price. Inputs are quarter-hourly series on one grid; a mismatch is refused with the first instant where the grids part.

MiSpeL

Marktintegration von Speichern und Ladepunkten (BNetzA, 01.10.2026, Az. 618-25-02): a storage system or bidirectional charge point mixing grid and plant electricity keeps its Umlageprivilegien (§ 21 EnFG) and Marktprämie (§ 19 EEG) for the share these formulas attribute to it.

AnlageModuleScopeFormulasPeriod
1, Abgrenzungsoptionmispel::abgrenzungZ1 at the connection, Z2 before the storage or charge point; cases A1–A11(1)–(40)calendar month: Abgrenzungsoption::monat; abgrenzung::jahr sums months
2, Pauschaloptionmispel::pauschalPV ≤ 30 kWp behind one meter; cases P1–P5; 500 kWh/kWp (P1) and P1 plus an Indifferenzbereich (P4) replace Z2(P1)–(P22)calendar year or Rumpfjahr

Per quarter-hour the Speichervorrang and gates, per month the sums, then every MIN/MAX clamp. Every numbered intermediate is in the result as fN.

use metering::eeg::mispel::pauschal::{Pauschalbasis, Pauschalgrenzen};
use rust_decimal::dec;

// Anlage 2, p. 13: 8 kWp of PV and a 10 kWh storage.
let g = Pauschalgrenzen::new(Pauschalbasis::Stromspeicher { kapazitaet_kwh: dec!(10) }, dec!(8)).unwrap();
assert_eq!((g.p1, g.p3, g.p4), (dec!(4000), dec!(320), dec!(4320)));

Your input: the Formelsatz (binding for a calendar year) and whether the EU state-aid approval of § 19 Abs. 3c EEG, from which the Pauschaloption applies, is granted. Out: the money formulas (41)–(44) of case A9 and the § 22 EnFG heat-pump quantity of the A7 and P5 footnotes. No rounding — the Festlegung states none; its tables are rounded for display.

EEG § 51 and § 51a — negative prices

§ 51 Abs. 1: "Für Zeiträume, in denen der Spotmarktpreis negativ ist, verringert sich der anzulegende Wert auf null." § 51a extends the Vergütungszeitraum by those quarter-hours (year of commissioning plus 19), rounded up to whole days; for a Solaranlage halved and rounded up to Volllastviertelstunden, spent month by month against the statutory table.

use metering::eeg::negative_prices::{extension_days, solar_extension, volllastviertelstunden};
use time::macros::date;

assert_eq!(extension_days(97), 2); // up, never to nearest
assert_eq!(volllastviertelstunden(1001), 501);

// 500 Volllastviertelstunden from an end on 15 April: the rest of April's
// budget, then May's.
let e = solar_extension(date!(2045 - 04 - 15), volllastviertelstunden(1000)).unwrap();
assert_eq!(e.end, date!(2045 - 05 - 31));

negative_span_energy gives the § 51 Abs. 3 energy per uninterrupted span (the 5 % reduction is money). Whether § 51 applies to a plant is your fact.

EnFG § 46 — Zeitgleichheit

Abs. 5: "höchstens bis zu der Höhe der tatsächlichen Netzentnahme, bezogen auf jedes 15-Minuten-Intervall" — the minimum per quarter-hour, summed after: Σ min(a, b). MiSpeL's Speichervorrang is the same function.

use metering::eeg::zeitgleichheit::{worst_case_estimate, zeitgleichheit};
use metering::prelude::*;
use time::{Duration, macros::datetime};

let t = datetime!(2026-06-01 0:00 UTC);
let series = |values: [Decimal; 2]| {
    let slots = values
        .iter()
        .enumerate()
        .map(|(i, v)| MeterInterval::quarter_hour(t + Duration::minutes(15 * i as i64), *v, QualityFlag::Measured))
        .collect::<Result<Vec<_>, _>>().unwrap();
    Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, slots).unwrap()
};
let z = zeitgleichheit(&series([dec!(5), dec!(1)]), &series([dec!(2), dec!(4)])).unwrap();
assert_eq!(z.total, dec!(3)); // min(5, 2) + min(1, 4), not min(6, 6)

// Abs. 3 Satz 4: maximum draw × the full hours of the Berlin calendar year.
let e = worst_case_estimate(dec!(2.5), 2028).unwrap();
assert_eq!(e.hours, 8_784);