use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Carrier {
Orders,
Ordrsp,
}
impl Carrier {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Orders => "ORDERS",
Self::Ordrsp => "ORDRSP",
}
}
#[must_use]
pub fn from_unh_code(code: &str) -> Option<Self> {
match code {
"ORDERS" => Some(Self::Orders),
"ORDRSP" => Some(Self::Ordrsp),
_ => None,
}
}
}
impl fmt::Display for Carrier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DvgwMessageType {
Alocat,
Nomint,
Nomres,
}
impl DvgwMessageType {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Alocat => "ALOCAT",
Self::Nomint => "NOMINT",
Self::Nomres => "NOMRES",
}
}
#[must_use]
pub fn carrier(self) -> Carrier {
match self {
Self::Nomint => Carrier::Orders,
Self::Alocat | Self::Nomres => Carrier::Ordrsp,
}
}
#[must_use]
pub fn documents(self) -> &'static [DvgwDocument] {
use DvgwDocument as D;
match self {
Self::Alocat => &[
D::AllokationSlp,
D::KorrigierteMengenmeldungNkp,
D::SlpErsatzwerte,
D::UntertaegigeAllokation,
D::EndgueltigeAllokation,
D::KorrigierteAllokationBilanzierungsbrennwert,
D::KorrigierteAllokationAbrechnungsbrennwert,
D::TaeglicheMengenmeldungNkp,
],
Self::Nomint => &[
D::NominierungTransportkunde,
D::NominierungVirtuellerHandelspunkt,
D::Flexibilitaetsuebertragung,
D::NominierungGebuendelteKapazitaet,
D::NominierungsweitergabeNetzbetreiber,
],
Self::Nomres => &[
D::MatchingBenachrichtigung,
D::Bestaetigung,
D::VhpMatchingBenachrichtigung,
D::VhpBestaetigung,
D::BestaetigungFlexibilitaetsuebertragung,
],
}
}
pub const ALL: [Self; 3] = [Self::Alocat, Self::Nomint, Self::Nomres];
}
impl fmt::Display for DvgwMessageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum DvgwDocument {
AllokationSlp,
KorrigierteMengenmeldungNkp,
SlpErsatzwerte,
UntertaegigeAllokation,
EndgueltigeAllokation,
KorrigierteAllokationBilanzierungsbrennwert,
KorrigierteAllokationAbrechnungsbrennwert,
TaeglicheMengenmeldungNkp,
NominierungTransportkunde,
NominierungVirtuellerHandelspunkt,
Flexibilitaetsuebertragung,
NominierungGebuendelteKapazitaet,
NominierungsweitergabeNetzbetreiber,
MatchingBenachrichtigung,
Bestaetigung,
VhpMatchingBenachrichtigung,
VhpBestaetigung,
BestaetigungFlexibilitaetsuebertragung,
}
const CATALOGUE: &[(&str, DvgwDocument, &str)] = {
use DvgwDocument as D;
&[
(
"X1G",
D::AllokationSlp,
"Allokation anhand von Standardlastprofilen (SLP)",
),
(
"X2G",
D::KorrigierteMengenmeldungNkp,
"Korrigierte Mengenmeldung NKP je Netzkonto",
),
("X3G", D::SlpErsatzwerte, "SLP-Ersatzwerte"),
(
"X4G",
D::UntertaegigeAllokation,
"Untertägige Allokation (Intraday)",
),
(
"X5G",
D::EndgueltigeAllokation,
"Endgültige Allokation (Bilanzierungsbrennwert)",
),
(
"X6G",
D::KorrigierteAllokationBilanzierungsbrennwert,
"Korrigierte Allokation (Bilanzierungsbrennwert)",
),
(
"X7G",
D::KorrigierteAllokationAbrechnungsbrennwert,
"Korrigierte Allokation (Abrechnungsbrennwert)",
),
(
"XBG",
D::TaeglicheMengenmeldungNkp,
"Tägliche Mengenmeldung NKP je Netzkonto",
),
(
"01G",
D::NominierungTransportkunde,
"Nominierung von einem Transportkunden",
),
(
"55G",
D::NominierungVirtuellerHandelspunkt,
"Nominierung an einem Virtuellen Handelspunkt",
),
(
"Y1G",
D::Flexibilitaetsuebertragung,
"Flexibilitätsübertragung",
),
(
"Y6G",
D::NominierungGebuendelteKapazitaet,
"Nominierung gebündelter Kapazität an MÜP und GÜP",
),
(
"Y7G",
D::NominierungsweitergabeNetzbetreiber,
"Nominierungsweitergabe zwischen Netzbetreibern",
),
(
"07G",
D::MatchingBenachrichtigung,
"Matching-Benachrichtigung",
),
("08G", D::Bestaetigung, "Bestätigung"),
(
"19G",
D::VhpMatchingBenachrichtigung,
"Virtueller Handelspunkt: Matching-Benachrichtigung",
),
(
"20G",
D::VhpBestaetigung,
"Virtueller Handelspunkt: Bestätigung",
),
(
"Y2G",
D::BestaetigungFlexibilitaetsuebertragung,
"Bestätigung Flexibilitätsübertragung",
),
]
};
impl DvgwDocument {
#[must_use]
pub fn code(self) -> &'static str {
CATALOGUE
.iter()
.find(|(_, d, _)| *d == self)
.map_or("", |(c, _, _)| *c)
}
#[must_use]
pub fn from_code(code: &str) -> Option<Self> {
CATALOGUE
.iter()
.find(|(c, _, _)| *c == code)
.map(|(_, d, _)| *d)
}
#[must_use]
pub fn description(self) -> &'static str {
CATALOGUE
.iter()
.find(|(_, d, _)| *d == self)
.map_or("", |(_, _, t)| *t)
}
#[must_use]
pub fn message_type(self) -> DvgwMessageType {
for mt in DvgwMessageType::ALL {
if mt.documents().contains(&self) {
return mt;
}
}
unreachable!("every DvgwDocument is listed in exactly one DvgwMessageType::documents()")
}
#[must_use]
pub fn carrier(self) -> Carrier {
self.message_type().carrier()
}
pub fn all() -> impl Iterator<Item = Self> {
CATALOGUE.iter().map(|(_, d, _)| *d)
}
}
pub const DVGW_AGENCY_CODE: &str = "332";
impl fmt::Display for DvgwDocument {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.code())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_document_round_trips_through_its_code() {
for doc in DvgwDocument::all() {
assert!(!doc.code().is_empty(), "{doc:?} has no wire code");
assert!(!doc.description().is_empty(), "{doc:?} has no description");
assert_eq!(DvgwDocument::from_code(doc.code()), Some(doc));
}
}
#[test]
fn the_catalogue_has_no_duplicate_codes() {
let mut codes: Vec<&str> = CATALOGUE.iter().map(|(c, _, _)| *c).collect();
let total = codes.len();
codes.sort_unstable();
codes.dedup();
assert_eq!(
codes.len(),
total,
"duplicate BGM 1001 code in the catalogue"
);
}
#[test]
fn message_type_documents_partition_the_catalogue() {
let listed: usize = DvgwMessageType::ALL
.iter()
.map(|m| m.documents().len())
.sum();
assert_eq!(
listed,
CATALOGUE.len(),
"a document code is unreachable from its family"
);
}
#[test]
fn nomint_rides_orders_and_the_rest_ride_ordrsp() {
assert_eq!(DvgwMessageType::Nomint.carrier(), Carrier::Orders);
assert_eq!(DvgwMessageType::Alocat.carrier(), Carrier::Ordrsp);
assert_eq!(DvgwMessageType::Nomres.carrier(), Carrier::Ordrsp);
assert_eq!(
Carrier::from_unh_code("ALOCAT"),
None,
"ALOCAT is not a wire carrier"
);
}
}