metering 0.23.0

German energy metering domain library — DST-correct Europe/Berlin calendar arithmetic on both the Liefertag and the 06:00 Gastag, gas m3→kWh_Hs conversion, SigLinDe gas SLP, billing-period aggregation with Benutzungsstundenzahl and Blindmehrarbeit, Ersatzwertbildung in the market's own code list, a robust validation engine, para 14a netzorientierte Steuerung and Modul 3 registers, conservation-checked allocation for para 42b/42c, charging sessions onto the settlement grid, check-digit-validated MaLo-IDs and EICs, and SLP/RLM/iMSys classification. Zero I/O, no async, no clock, exact decimal quantities.
Documentation
# metering — task runner (https://just.systems)
#
# `just` with no arguments lists every recipe.

set shell := ["bash", "-uc"]

# MSRV — keep in sync with `rust-version` in Cargo.toml and rust-toolchain.toml.
msrv := "1.94"

# 📋 List all recipes
default:
    @just --list

# ✅ Everything CI runs, in CI order
ci: fmt-check lint purity test example doc package
    @echo "✅ all checks passed"

# 🧊 Enforce the "zero I/O, no clock" guarantee over non-comment source lines
purity:
    #!/usr/bin/env bash
    set -uo pipefail
    hits="$(grep -rn --include='*.rs' -E \
        'now_utc|SystemTime::now|Instant::now|std::(fs|env|net|process)|\bunsafe\b' \
        src/ | grep -vE ':[[:space:]]*(///|//!|//)' || true)"
    if [ -n "$hits" ]; then
        echo "❌ ambient state or unsafe reached the source:" >&2
        echo "$hits" >&2
        echo "" >&2
        echo "This crate promises equal inputs give equal outputs. Take the" >&2
        echo "timestamp as a parameter instead — see the Determinism section." >&2
        exit 1
    fi
    echo "🧊 pure: no clock, no I/O, no unsafe"

# 🎨 Format the workspace
fmt:
    cargo fmt --all

# 🎨 Fail if anything is unformatted
fmt-check:
    cargo fmt --all -- --check

# 📎 Clippy with warnings denied (default + all features)
lint:
    cargo clippy --all-targets -- -D warnings
    cargo clippy --all-targets --all-features -- -D warnings

# 🔎 Fast type-check, all features
check:
    cargo check --all-targets --all-features

# 🧪 Full test suite (all features, incl. doctests)
test:
    cargo test --all-features

# ▶️  Run the end-to-end pipeline example
example:
    cargo run --all-features --example pipeline

# 🧪 Run tests matching a filter, e.g. `just test-one gas_m3`
test-one filter:
    cargo test --all-features {{ filter }} -- --nocapture

# 🎛️ Build every feature combination
features:
    cargo build --no-default-features
    cargo build --no-default-features --features serde
    cargo build --all-features

# 🦀 Compile on the pinned MSRV
msrv:
    RUSTUP_TOOLCHAIN={{ msrv }} cargo check --all-features --all-targets

# 📚 Build the docs with warnings denied
doc:
    RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features

# 📚 Build and open the docs
doc-open:
    RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features --open

# 📦 Dry-run the crates.io package (catches bad metadata before tagging)
# `--allow-dirty` is local-only convenience; CI runs this on a clean checkout.
package:
    cargo publish --dry-run --all-features --allow-dirty

# 🛡️ Audit dependencies for advisories (needs `cargo install cargo-audit`)
audit:
    cargo audit

# ⬆️ Show outdated dependencies (needs `cargo install cargo-outdated`)
outdated:
    cargo outdated --root-deps-only

# 🏷️ Tag the current Cargo.toml version and push it — triggers release.yml
tag:
    #!/usr/bin/env bash
    set -euo pipefail
    version="$(cargo metadata --no-deps --format-version 1 \
        | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)"
    if [ -n "$(git status --porcelain)" ]; then
        echo "❌ working tree is dirty — commit first" >&2
        exit 1
    fi
    just ci
    git tag -a "v${version}" -m "v${version}"
    echo "🏷️  tagged v${version} — push with: git push origin v${version}"

# Verify every passage the crate presents as verbatim source text.
#
# Needs `specs/` (run `just specs`) and `pdftotext` (poppler). Extra corpora can
# be passed as arguments — the sibling workspaces hold documents this crate
# cites but does not mirror.
#
# Not a CI lane: the corpus is gitignored, so a fresh checkout has nothing to
# check against. Run it every audit round, and after touching a quote.
#
# 📚 Check every German quote in src/, site/ and README.md against the PDFs
quotes *dirs:
    python3 scripts/verify_quotes.py {{ dirs }}

# 🌐 Serve the documentation site locally (needs `zola`)
site:
    zola --root site serve

# 🌐 Build the site and validate every internal link
site-build:
    zola --root site check
    zola --root site build --force

# 🧹 Remove build artifacts
clean:
    cargo clean
    rm -rf site/public

# Third-party publications: gitignored, never committed. Keeps whatever is
# already on disk, so a partial run is safe to repeat. What cannot be fetched
# is reported at the end and indexed in `specs/README.md` with its source.
#
# 📚 Rebuild `specs/` — the primary sources every citation is checked against
specs:
    #!/usr/bin/env bash
    set -uo pipefail
    ua='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36'
    missing=""
    # fetch DIR FILE URL [ALT_URL]
    fetch() {
        mkdir -p "specs/$1"
        if [ -s "specs/$1/$2" ]; then echo "kept     $1/$2"; return 0; fi
        for url in "$3" "${4:-}"; do
            [ -n "$url" ] || continue
            if curl -fsSL -A "$ua" --retry 3 --retry-delay 5 --max-time 900 \
                    -o "specs/$1/$2.part" "$url" \
                    && [ -s "specs/$1/$2.part" ] \
                    && [ "$(file -b --mime-type "specs/$1/$2.part")" != "text/html" ]; then
                mv "specs/$1/$2.part" "specs/$1/$2"; echo "fetched  $1/$2"; return 0
            fi
            rm -f "specs/$1/$2.part"
        done
        echo "MISSING  $1/$2  <- $3" >&2
        missing="$missing  $1/$2  <- $3"$'\n'
        return 0
    }
    # law/ — the statutes, consolidated (gesetze-im-internet.de)
    fetch law enwg.pdf 'https://www.gesetze-im-internet.de/enwg_2005/EnWG.pdf'
    fetch law msbg.pdf 'https://www.gesetze-im-internet.de/messbg/MsbG.pdf'
    fetch law messeg.pdf 'https://www.gesetze-im-internet.de/messeg/MessEG.pdf'
    fetch law messev.pdf 'https://www.gesetze-im-internet.de/messev/MessEV.pdf'
    fetch law stromnev.pdf 'https://www.gesetze-im-internet.de/stromnev/StromNEV.pdf'
    fetch law heizkostenv.pdf 'https://www.gesetze-im-internet.de/heizkostenv/HeizkostenV.pdf'
    fetch law bgbl-2025-i-347-enwg-novelle-20251222.pdf \
        'https://www.recht.bund.de/bgbl/1/2025/347/regelungstext.pdf?__blob=publicationFile&v=2'
    # bnetza/ — the Festlegungen the repealed ordinances left behind
    fetch bnetza bk6-22-300-beschluss-20231127.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2022/BK6-22-300/Beschluss/BK6-22-300_Beschluss_20231127.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk6-22-300-anlage1-20231127.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2022/BK6-22-300/Beschluss/BK6-22-300_Beschluss_Anlage1.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk6-22-300-vde-fnn-empfehlung-tenorziffer-2f.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2022/BK6-22-300/Mitteilung/Mitteilung_3/VDE_FNN_Empfehlung_zu_Tenorziffer_2f.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk6-24-174-beschluss-20241024.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2024/BK6-24-174/Beschluss/BK6-24-174_Beschluss_vom_20241024.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk6-24-174-gpke-teil1-lesefassung.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2024/BK6-24-174/Beschluss/BK6-24-174_GPKE_Teil1_Lesefassung.pdf?__blob=publicationFile&v=1'
    # edi-energy/ — the BDEW catalogue, one file per fileId
    fetch edi-energy codeliste-obis-kennzahlen-und-medien-2.5c.pdf \
        'https://www.bdew-mako.de/api/downloadFile/11918'
    fetch edi-energy allgemeine-festlegungen-6.1c.pdf \
        'https://www.bdew-mako.de/api/downloadFile/11916'
    fetch edi-energy allgemeine-festlegungen-6.1d.pdf \
        'https://www.bdew-mako.de/api/downloadFile/12145'
    fetch edi-energy codeliste-slp-tu-muenchen-1.1.pdf \
        'https://www.bdew-mako.de/api/downloadFile/9092'
    fetch edi-energy mscons-mig-2.4c.pdf \
        'https://www.bdew-mako.de/api/downloadFile/9645'
    fetch edi-energy mscons-mig-2.5.pdf \
        'https://www.bdew-mako.de/api/downloadFile/12175'
    # bdew/ — Anwendungshilfen and the gas-SLP Leitfaden
    fetch bdew bdew-lf-slp-gas-kov-xv-20260327.pdf \
        'https://www.bdew.de/media/documents/260327_LF_SLP_Gas_KoV_XV_CO4f7Rb.pdf'
    fetch bdew bdew-lf-slp-gas-anlage2-pruefroutine-siglinde.xlsm \
        'https://www.bdew.de/media/documents/20240322_KoV_XIV_LF-SLP_Anlage_2-Pr%C3%BCfroutine_Synthetisches_Verfahren_SigLinDe.xlsm'
    fetch bdew bdew-awh-slp-strom-2025-20250317.pdf \
        'https://www.bdew.de/media/documents/2025-03-17_AWH_Aktualisierte_SLP_Strom_2025_Ver%C3%B6ffentlichung.pdf'
    fetch bdew bdew-awh-modul-3-v1.1-20250207.pdf \
        'https://www.bdew.de/media/documents/BDEW-AWH_Modul_3_V1.1_Korrektur070225.pdf'
    fetch bdew bdew-awh-identifikatoren-mako-v1.2.pdf \
        'https://www.bdew.de/media/documents/AWH_Identifikatoren-in-der-Marktkommunikation_Version.1.2.pdf'
    fetch bdew bdew-awh-malo-id-v1.0-20170428.pdf \
        'https://bdew-codes.de/Content/Files/MaLo/2017-04-28-BDEW-Anwendungshilfe-MaLo-ID_Version1.0_FINAL.PDF' \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/_SharedDocs/Mitteilungen_zu_BK6_16_200_BK7_16_142_/Mitteilung_Nr_2/Anlage_1_Anwendungshilfe_MaLo_ID.pdf?__blob=publicationFile&v=1'
    fetch bdew bdew-awh-eic-vergabe-v1.0-20171218.pdf \
        'https://bdew-codes.de/Content/Files/EIC/Awh_20171218_EIC-Vergabe_V1-0.pdf'
    # The EDI@Energy Anwendungshilfe carries the worked § 42b formulas; the BDEW
    # Anwendungshilfe zum Solarpaket I is no longer served as a PDF.
    fetch edi-energy awh-berechnungsformeln-solarpaket-1-v1.1.pdf \
        'https://www.bdew-mako.de/api/downloadFile/11113'
    # vde-fnn/ — what the paywalled Anwendungsregeln are cited through
    fetch vde-fnn vde-fnn-hinweis-bewertung-mindestleistung.pdf \
        'https://www.vde.com/resource/blob/2384818/09cfe30a1ef5a210bebf37ff14953858/vde-fnn-hinweis-bewertung-der-mindestleistung-data.pdf'
    fetch vde-fnn vde-fnn-hinweis-symmetrischer-anschluss-4100.pdf \
        'https://www.vde.com/resource/blob/2243242/931a9d5c1e48cf5c55592bcda7c59e20/fnn-hinweis-anforderungen-fuer-den-symmetrischen-anschluss-und-betrieb-nach-vde-ar-n-4100-data.pdf'
    # eu/ — the Gastag's own definition
    fetch eu vo-eu-312-2014-gasnetzkodex-bilanzierung-de.pdf \
        'https://eur-lex.europa.eu/legal-content/DE/TXT/PDF/?uri=CELEX:32014R0312'
    if [ -n "$missing" ]; then
        echo "" >&2
        echo "⚠️  not fetched (see specs/README.md for the source):" >&2
        printf '%s' "$missing" >&2
    fi
    echo "📚 specs/ rebuilt"