edifact-mapper 0.20.0

EDIFACT to BO4E bidirectional conversion for the German energy market
Documentation
//! A condition naming one group instance ("in dieser SG8", "in diesem CCI")
//! judges a nested-list element by its own instance, not by any in the message.
//!
//! Reported by mako-twin against 0.17.0 (UTILMD Strom 55043): each element
//! alone raised nothing, and the same element beside another group raised a
//! finding. The inputs are the entity maps their gate passed (synthetic data):
//! `*__clean` is the element alone, `*__fp` the element beside the other group,
//! `rundlauf_*` a full round trip.

use std::path::PathBuf;

use edifact_mapper::{DataDir, Mapper};
use serde_json::Value;

const VARIANT: &str = "UTILMD_Strom";
const PID: &str = "55043";

fn data(name: &str) -> Value {
    let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests/data/mako_twin_55043_scope")
        .join(name);
    serde_json::from_str(&std::fs::read_to_string(&path).expect("dump")).expect("json")
}

fn mapper(fv: &str) -> Option<Mapper> {
    let dist = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../dist");
    if !dist.join(format!("edifact-data-{fv}.bin")).exists() {
        eprintln!("skipping: build the {fv} bundle first");
        return None;
    }
    Some(Mapper::from_data_dir(DataDir::path(dist).eager(&[fv])).expect("bundle"))
}

/// The conditional validator's errors on `json` that mention `needle`. (A
/// warning — a part required under a condition that cannot be decided, like
/// [248] — is not the false finding reported.)
fn findings(mapper: &Mapper, fv: &str, json: &Value, needle: &str) -> Vec<String> {
    mapper
        .validate_pid_with_conditions(json, fv, VARIANT, PID)
        .expect("validate")
        .iter()
        .map(|e| e.to_string())
        .filter(|e| e.starts_with("ERROR") && e.contains(needle))
        .collect()
}

/// (condition, the element alone, the element beside the other group, the
/// finding the other group used to cause)
const PAIRS: &[(&str, &str, &str, &str)] = &[
    (
        "[182]/[183] TLP beside SLP",
        "182_183__malo1_alone__clean.json",
        "182_183__malo1_plus_tlp_malo2__fp.json",
        "MarktlokationDaten.mengen[265]",
    ),
    (
        "[106] Erzeugung beside SLP",
        "106__erzeugung_malo3_alone__clean.json",
        "106__erzeugung_malo3_plus_slp_malo1__fp.json",
        "MarktlokationDaten.mengen[31]",
    ),
    (
        "[316] SMGW meter beside a plain one",
        "316__smgw_meter_alone__clean.json",
        "316__smgw_meter_plus_plain_meter__fp.json",
        "ZaehlerDaten.zuordnungen[messwerterfassungAnDerZaehleinrichtung]",
    ),
    (
        "[258] PV MaLo beside a Verbrauch MaLo",
        "258__erzeugung_pv_malo_alone__clean.json",
        "258__pv_malo_plus_verbrauch_malo__fp.json",
        "MarktlokationDaten.zuordnungen[abwicklungsmodell]",
    ),
    (
        "[106] PV MaLo beside a Verbrauch MaLo",
        "258__erzeugung_pv_malo_alone__clean.json",
        "258__pv_malo_plus_verbrauch_malo__fp.json",
        "MarktlokationDaten.mengen[31]",
    ),
    (
        "[58] Klimazone beside a CCI with 3055 = 293",
        "58__klimazone_alone__clean.json",
        "rundlauf_FV2604_entities.json",
        "ProfilscharDaten.zuordnungen[].merkmal.codeliste",
    ),
    (
        "[438] Z41 Messprodukt beside a CCI+Z39",
        "438__no_cci_z39_in_message__clean.json",
        "rundlauf_FV2604_entities.json",
        "MengenDatenErw.zuordnungen[].merkmal.code",
    ),
];

#[test]
fn a_nested_element_is_not_judged_by_another_groups_instance() {
    let Some(mapper) = mapper("FV2604") else {
        return;
    };
    let mut wrong = Vec::new();
    for (condition, clean, fp, needle) in PAIRS {
        let alone = findings(&mapper, "FV2604", &data(clean), needle);
        assert!(alone.is_empty(), "{condition}: {clean} {alone:#?}");
        let beside = findings(&mapper, "FV2604", &data(fp), needle);
        if !beside.is_empty() {
            wrong.push(format!("{condition}: {beside:?}"));
        }
    }
    assert!(wrong.is_empty(), "{wrong:#?}");
}

#[test]
fn condition_438_in_fv2610() {
    let Some(mapper) = mapper("FV2610") else {
        return;
    };
    let json = data("rundlauf_FV2610_entities.json");
    let wrong: Vec<String> = [
        "MengenDatenErw.zuordnungen[].merkmal.code",
        "MarktlokationMessproduktDaten.zuordnungen[].merkmal.code",
    ]
    .iter()
    .flat_map(|needle| findings(&mapper, "FV2610", &json, needle))
    .collect();
    assert!(wrong.is_empty(), "{wrong:#?}");
}