automapper-validation 0.50.1

AHB condition expression parsing, evaluation, and EDIFACT validation
Documentation
//! Which OBIS-Kennzahlen and Medien the BDEW Codeliste der OBIS-Kennzahlen und
//! Medien ties to which MSCONS Prüfidentifikatoren: the column
//! "Nutzungseinschränkung in der MSCONS / Prüfidentifikator", which `[501]`
//! reads ("Es sind nur die Werte erlaubt, die in der EDI@Energy Codeliste der
//! OBIS-Kennzahlen und Medien mit dem entsprechenden Prüfidentifikator
//! versehen sind").
//!
//! MSCONS 13013 sends the OBIS-ähnliche Kennzahl `7-b:9.98.0` (PIA+5 DE7143
//! `Z02`), 13020 the Medium `AUA` (Ausfallarbeit, DE7143 `Z08`). An OBIS here
//! keeps the document's placeholders (`b` Kanal, `e` Tarif).
//!
//! Data source: the Codeliste's .docx on bdew-mako.de, extracted with
//! `scripts/extract_obis_medien.py` (version and file id in the JSON).

use serde::Deserialize;
use std::sync::OnceLock;

const EMBEDDED_JSON: &str = include_str!("../../data/obis_medien.json");

/// An OBIS-Kennzahl the list ties to Prüfidentifikatoren.
#[derive(Debug, Clone, Deserialize)]
pub struct Obis {
    /// As the document writes it: `7-b:9.98.0`.
    pub obis: String,
    pub name: String,
    pub chapter: String,
    pub pids: Vec<String>,
}

/// A Medium (chapter 5) and the Prüfidentifikatoren it is for.
#[derive(Debug, Clone, Deserialize)]
pub struct Medium {
    /// `AUA`, `SOL`, …
    pub code: String,
    pub name: String,
    pub pids: Vec<String>,
}

#[derive(Debug, Deserialize)]
struct Table {
    obis: Vec<Obis>,
    medien: Vec<Medium>,
}

fn table() -> &'static Table {
    static TABLE: OnceLock<Table> = OnceLock::new();
    TABLE.get_or_init(|| serde_json::from_str(EMBEDDED_JSON).expect("embedded obis_medien.json"))
}

/// The OBIS-Kennzahlen the list ties to `pid`, in the document's order.
pub fn obis_for_pid(pid: &str) -> impl Iterator<Item = &'static Obis> + '_ {
    table()
        .obis
        .iter()
        .filter(move |o| o.pids.iter().any(|p| p == pid))
}

/// The Medien the list ties to `pid`, in the document's order.
pub fn medien_for_pid(pid: &str) -> impl Iterator<Item = &'static Medium> + '_ {
    table()
        .medien
        .iter()
        .filter(move |m| m.pids.iter().any(|p| p == pid))
}

/// An OBIS of the document with its placeholders filled: Kanal `b` → `0`,
/// Tarif `e` → `0` (`7-b:9.98.0` → `7-0:9.98.0`, `7-b:99.45.e1` →
/// `7-0:99.45.01`).
pub fn concrete(obis: &str) -> String {
    obis.replace(['b', 'e'], "0")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mscons_pids_get_their_values() {
        let allokation: Vec<_> = obis_for_pid("13013").map(|o| o.obis.as_str()).collect();
        assert_eq!(allokation, ["7-b:9.98.0"]);
        let menge: Vec<_> = obis_for_pid("13014").map(|o| o.obis.as_str()).collect();
        assert_eq!(menge, ["1-b:1.98.0", "7-b:9.98.1"]);
        let ausfall: Vec<_> = medien_for_pid("13020").map(|m| m.code.as_str()).collect();
        assert_eq!(ausfall, ["AUA"]);
        let wetter: Vec<_> = medien_for_pid("13021").map(|m| m.code.as_str()).collect();
        assert_eq!(wetter, ["SOL", "WID"]);
        assert_eq!(medien_for_pid("13002").count(), 0);
    }

    #[test]
    fn placeholders_become_digits() {
        assert_eq!(concrete("7-b:9.98.0"), "7-0:9.98.0");
        assert_eq!(concrete("7-b:99.45.e1"), "7-0:99.45.01");
    }
}