metering 0.24.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 โ€” 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

# ๐Ÿ”’ What would break for a consumer on the last published version
#    (needs `cargo install cargo-semver-checks`). CI runs it as a report, not a
#    gate, while the crate is pre-1.0; its output is the list a CHANGELOG entry
#    needs.
#
#    `--release-type patch` asks "would this be a legal patch release?", which
#    is the only phrasing that lists every break. Left off, the tool infers the
#    bump from Cargo.toml, and a 0.x minor bump already declares a major change
#    โ€” so it skips every lint and reports a clean run over a hard cut.
semver:
    cargo semver-checks check-release --all-features --release-type patch

# โฌ†๏ธ 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-23-241-beschluss-20260507.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2023/BK6-23-241/BK6-23-241_beschluss_vom_07.05.26.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk6-23-241-anlage-bilarem.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/2023/BK6-23-241/BK6-23-241_bilarem.pdf?__blob=publicationFile&v=1'
    fetch bnetza bk7-24-01-008-gabi-gas-2-1-beschluss.pdf \
        'https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK7-GZ/2024/BK7-24-0008/Anlagen/BK7-24-01-0008_Beschluss_DL_BF.pdf?__blob=publicationFile&v=5'
    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-slp-strom-2025-profile-h25-g25-l25-p25-s25.xlsx \
        'https://www.bdew.de/media/documents/Kopie_von_Repr%C3%A4sentative_Profile_BDEW_H25_G25_L25_P25_S25_Ver%C3%B6ffentlichung.xlsx'
    fetch bdew bdew-lf-ausfallarbeit-redispatch-2-0-202005.pdf \
        'https://www.bdew.de/media/documents/Awh_2020-05_RD_2.0_LF_Ausfallarbeit.pdf'
    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"