use edifact_rs::{ProfileRulePack, Segment, ValidationIssue, ValidationSeverity};
#[inline]
fn has_segment(segs: &[Segment<'_>], tag: &str) -> bool {
segs.iter().any(|s| s.tag == tag)
}
#[inline]
fn has_nad_role(segs: &[Segment<'_>], qualifier: &str) -> bool {
segs.iter()
.any(|s| s.tag == "NAD" && s.element_str(0) == Some(qualifier))
}
#[inline]
fn has_dtm_qualifier(segs: &[Segment<'_>], qualifier: &str) -> bool {
segs.iter()
.any(|s| s.tag == "DTM" && s.component_str(0, 0).is_some_and(|q| q == qualifier))
}
#[inline]
fn has_rff_qualifier(segs: &[Segment<'_>], qualifier: &str) -> bool {
segs.iter()
.any(|s| s.tag == "RFF" && s.component_str(0, 0).is_some_and(|q| q == qualifier))
}
fn require_bgm(segs: &[Segment<'_>], issues: &mut Vec<ValidationIssue>) {
if !has_segment(segs, "BGM") {
issues.push(
ValidationIssue::new(
ValidationSeverity::Error,
"BGM segment is missing — every DVGW message must carry a document \
reference in the Business Group Message segment",
)
.with_rule_id("SEM-DVGW-BGM-REQUIRED")
.with_segment("BGM")
.with_suggestion(
"Add BGM+<function-code>+<document-number>' to identify the message; \
the document number (element 1 component 0, DE 1004) is used for \
correlation (NOMRES RFF+Z13) and duplicate detection",
),
);
}
}
fn require_nad_ms(segs: &[Segment<'_>], issues: &mut Vec<ValidationIssue>) {
if !has_nad_role(segs, "MS") {
issues.push(
ValidationIssue::new(
ValidationSeverity::Error,
"NAD+MS (Marktteilnehmer Absender / sending market participant) is missing",
)
.with_rule_id("SEM-DVGW-NAD-MS-REQUIRED")
.with_segment("NAD")
.with_suggestion(
"Add NAD+MS+<EIC-code>::293' to identify the sending market participant; \
qualifier 293 (ENTSOE) is the standard code list for EIC codes in DVGW messages",
),
);
}
}
fn require_nad_mr(segs: &[Segment<'_>], issues: &mut Vec<ValidationIssue>) {
if !has_nad_role(segs, "MR") {
issues.push(
ValidationIssue::new(
ValidationSeverity::Error,
"NAD+MR (Marktteilnehmer Empfänger / receiving market participant) is missing",
)
.with_rule_id("SEM-DVGW-NAD-MR-REQUIRED")
.with_segment("NAD")
.with_suggestion(
"Add NAD+MR+<EIC-code>::293' to identify the receiving market participant",
),
);
}
}
fn require_dtm(
segs: &[Segment<'_>],
qualifier: &'static str,
rule_id: &'static str,
description: &'static str,
issues: &mut Vec<ValidationIssue>,
) {
if !has_dtm_qualifier(segs, qualifier) {
issues.push(
ValidationIssue::new(
ValidationSeverity::Error,
format!("DTM+{qualifier} ({description}) is missing"),
)
.with_rule_id(rule_id)
.with_segment("DTM")
.with_suggestion(format!(
"Add DTM+{qualifier}:<timestamp>:203' to specify the {description}; \
format 203 = CCYYMMDDHHmm (ISO 8601 combined date/time)",
)),
);
}
}
#[cfg(feature = "alocat")]
pub(crate) fn alocat_pack() -> ProfileRulePack {
ProfileRulePack::new("ALOCAT-SEM")
.for_message_type("ALOCAT")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-ALOCAT-DTM-137-REQUIRED",
"Gasdatum / Abrechnungstag (gas accounting day)",
issues,
);
if !has_segment(segs, "LOC") {
issues.push(
ValidationIssue::new(
ValidationSeverity::Warning,
"ALOCAT contains no LOC segment — at least one allocation \
quantity line item (LOC + QTY) is expected per DVGW ALOCAT specification",
)
.with_rule_id("SEM-ALOCAT-LOC-EXPECTED")
.with_segment("LOC")
.with_suggestion(
"Add at least one LOC+<qualifier>+<location-code>::ZZZ' segment \
followed by a QTY segment for each allocation entry",
),
);
}
})
}
#[cfg(feature = "nomint")]
pub(crate) fn nomint_pack() -> ProfileRulePack {
ProfileRulePack::new("NOMINT-SEM")
.for_message_type("NOMINT")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-NOMINT-DTM-137-REQUIRED",
"Gasdatum (gas day for nomination)",
issues,
);
})
}
#[cfg(feature = "nomres")]
pub(crate) fn nomres_pack() -> ProfileRulePack {
ProfileRulePack::new("NOMRES-SEM")
.for_message_type("NOMRES")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-NOMRES-DTM-137-REQUIRED",
"Gasdatum (gas day for nomination response)",
issues,
);
if !has_rff_qualifier(segs, "Z13") {
issues.push(
ValidationIssue::new(
ValidationSeverity::Warning,
"NOMRES has no RFF+Z13 (Nominierungsreferenz) — the document number \
of the corresponding NOMINT should be cited here for correlation",
)
.with_rule_id("SEM-NOMRES-RFF-Z13-EXPECTED")
.with_segment("RFF")
.with_suggestion(
"Add RFF+Z13:<nomination_ref>' where <nomination_ref> is the BGM \
element-1 document number from the originating NOMINT; this field \
is used by the ProcessRegistry to route the response to the correct \
outbound nomination workflow",
),
);
}
})
}
#[cfg(feature = "schedl")]
pub(crate) fn schedl_pack() -> ProfileRulePack {
ProfileRulePack::new("SCHEDL-SEM")
.for_message_type("SCHEDL")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-SCHEDL-DTM-137-REQUIRED",
"Gasdatum / Transporttag (gas transport day)",
issues,
);
})
}
#[cfg(feature = "imbnot")]
pub(crate) fn imbnot_pack() -> ProfileRulePack {
ProfileRulePack::new("IMBNOT-SEM")
.for_message_type("IMBNOT")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-IMBNOT-DTM-137-REQUIRED",
"Gasdatum (imbalance gas day)",
issues,
);
})
}
#[cfg(feature = "tranot")]
pub(crate) fn tranot_pack() -> ProfileRulePack {
ProfileRulePack::new("TRANOT-SEM")
.for_message_type("TRANOT")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-TRANOT-DTM-137-REQUIRED",
"Gasdatum / Transportdatum (gas transport notification date)",
issues,
);
})
}
#[cfg(feature = "delord")]
pub(crate) fn delord_pack() -> ProfileRulePack {
ProfileRulePack::new("DELORD-SEM")
.for_message_type("DELORD")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-DELORD-DTM-137-REQUIRED",
"Gasdatum / Lieferdatum (gas delivery order date)",
issues,
);
})
}
#[cfg(feature = "delres")]
pub(crate) fn delres_pack() -> ProfileRulePack {
ProfileRulePack::new("DELRES-SEM")
.for_message_type("DELRES")
.with_stateless_rule_fn(|segs, issues| {
require_bgm(segs, issues);
require_nad_ms(segs, issues);
require_nad_mr(segs, issues);
require_dtm(
segs,
"137",
"SEM-DELRES-DTM-137-REQUIRED",
"Gasdatum / Lieferdatum (gas delivery response date)",
issues,
);
})
}