metering 0.25.0

German energy metering quantities: DST-correct Europe/Berlin and 06:00-Gastag calendar arithmetic, exact decimal values, and the market's own code lists. Zero I/O, no async, no clock.
Documentation

metering

Crates.io Docs.rs CI MSRV License

The open, checkable reference for German energy-metering quantities, in Rust. Every number traces to a published clause — MsbG, EnWG, EEG, HeizkostenV, the BNetzA Festlegungen, the EDI@Energy documents — and every quoted passage is checked against the PDF it comes from.

A pure library — no I/O, no async, no clock. Quantities are exact Decimals, every rounding names its places and mode, and time is Europe/Berlin: a day of 23, 24 or 25 hours, a Gastag from 06:00. It computes kWh, m³ and kW, not money.

Guides · API reference · Changelog

Install

cargo add metering
cargo add metering --features serde   # wire formats for the inputs, codes and results

The serde feature enables serde and time/serde; see Design for the representation. MSRV 1.88 (edition 2024), tested in CI; raising it is a minor-version bump while 0.x.

Quick start

A day of quarter-hours with one value missing: validate, substitute, aggregate.

use metering::prelude::*;
use metering::time::holiday::Bundesland;
use metering::vee::substitute::SubstitutionReason;
use metering::vee::validation::Grade;
use time::{Duration, macros::{date, datetime}};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The Liefertag 1 June 2026, 00:00 to 00:00 Europe/Berlin, as a UTC period.
    let day = DayBoundary::Strom.day(date!(2026 - 06 - 01)).unwrap();

    // 96 quarter-hours of 0.5 kWh, except slot 50, which never arrived.
    let slots = (0..96)
        .filter(|i| *i != 50)
        .map(|i| {
            let from = day.start() + Duration::minutes(15 * i);
            MeterInterval::quarter_hour(from, dec!(0.5), QualityFlag::Measured)
        })
        .collect::<Result<Vec<_>, _>>()?;
    let series = Series::new(Resolution::QUARTER_HOUR, DayBoundary::Strom, slots)?;

    // Validate: the gap is an error finding, so the grade is C.
    let rules = Rules::strom(day, datetime!(2026-07-01 0:00 UTC), None);
    let report = validate(&series, &rules);
    assert_eq!(report.grade(), Grade::C);

    // Substitute: a gap up to 2 h is interpolated (STS+Z32 code Z92).
    let policy = Policy::strom(Bundesland::Be, SubstitutionReason::CommunicationFailure);
    let filled = substitute(&series, &report, &policy)?;
    assert_eq!(filled.substitutes[0].code, Some("Z92"));

    // Aggregate: Arbeitsmenge and Spitzenleistung of the billed day.
    let period = aggregate(&filled.series, day)?;
    assert_eq!(period.arbeitsmenge, dec!(48));
    assert_eq!(period.spitzenleistung_kw, Some(dec!(2)));
    Ok(())
}

examples/pipeline.rs runs the whole chain — readings, validation, substitution, aggregation, tariff registers — on the 25-hour autumn day: cargo run --example pipeline.

What's in it

Group Modules Guide
Series, time, IDs series (intervals, Series, readings, resampling), time (DayBoundary, Period, holidays), ids (MaLo, MeLo, EIC, BDEW codes, OBIS) Time · Series and readings · Identifiers
VEE vee — validation, grading, Ersatzwertbildung with MSCONS codes Validation and substitution
Billing billing — Arbeitsmenge, Spitzenleistung, tariff registers and § 14a Modul 3, Blindmehrarbeit, Mehr-/Mindermengen, losses, forecast Billing quantities
SLP slp — BDEW SLP Strom, SigLinDe SLP Gas Gas and units
Gas gas — m³ → kWh_Hs, Zustandszahl (DVGW G 685) Gas and units
Heat heat — HeizkostenV § 6a, § 9, § 9a Heat
Grid grid — § 14a EnWG, Redispatch Ausfallarbeit, EN 50160, rollout Grid · Power quality
Allocation allocation — UTILTS Berechnungsformel, § 42b community, § 42c sharing, sessions Allocation
EEG eeg — MiSpeL (adopted 01.10.2026), EEG § 51/§ 51a, EnFG § 46 EEG and EnFG

Scope

Not here Why Where instead
Money — prices, Netzentgelte, Umlagen, invoices the output is kWh, m³ and kW your billing layer
EDIFACT / XML market messages parsing a MSCONS or UTILTS is not arithmetic mako
Fristen — counting Werktage to a deadline a process-engine concern; the holiday calendar here serves SLP day types and tariff registers your process engine
SMGW certificates, device inventory PKI and asset tracking, not quantities your device management

The regulatory basis lists every source and the version in force. Nothing here is legal advice.

Testing

just ci          # the CI gate: fmt, clippy, purity, tests, docs, example, package, deny
just references  # fetch the primary sources the citations are checked against
just quotes      # check every quoted German passage against those PDFs

The Rust blocks in this README and on the guide pages run as doctests. just quotes needs pdftotext and the fetched corpus; it is not a CI job.

Contributing and license

Issues and pull requests welcome; a change to a regulated calculation cites the provision it implements. Licensed under Apache-2.0 or MIT at your option.