use super::{Flussrichtung, Lokationsbuendelstruktur, Objektrolle, Objekttyp};
use crate::current::{
Energierichtung, Lokationszuordnung, Marktlokation, Messlokation, Netzlokation,
SteuerbareRessource, TechnischeRessource,
};
use crate::error::IdentifierError;
use crate::identifiers::{LokationsbuendelObjektcode, Lokationsbuendelcode};
impl Flussrichtung {
#[must_use]
pub const fn as_energierichtung(self) -> Option<Energierichtung> {
match self {
Self::Verbrauch => Some(Energierichtung::Aussp),
Self::Erzeugung => Some(Energierichtung::Einsp),
Self::VerbrauchUndErzeugung => None,
}
}
}
pub trait LokationsbuendelObjekt {
fn lokationsbuendel_objektcode_raw(&self) -> Option<&str>;
fn codelist_objekttyp() -> Option<Objekttyp>
where
Self: Sized;
fn lokationsbuendel_objektcode(
&self,
) -> Option<Result<LokationsbuendelObjektcode, IdentifierError>> {
self.lokationsbuendel_objektcode_raw()
.map(LokationsbuendelObjektcode::new)
}
fn objektrolle(&self) -> Option<&'static Objektrolle> {
Objektrolle::from_wire(self.lokationsbuendel_objektcode_raw()?)
}
fn ebene(&self) -> Option<u8> {
self.objektrolle().map(|r| r.ebene)
}
fn flussrichtung(&self) -> Option<Flussrichtung> {
self.objektrolle()?.richtung
}
}
macro_rules! impl_lokationsbuendel_objekt {
($ty:ty, $typ:expr) => {
impl LokationsbuendelObjekt for $ty {
fn lokationsbuendel_objektcode_raw(&self) -> Option<&str> {
self.lokationsbuendel_objektcode.as_deref()
}
fn codelist_objekttyp() -> Option<Objekttyp> {
$typ
}
}
};
}
impl_lokationsbuendel_objekt!(Marktlokation, Some(Objekttyp::Marktlokation));
impl_lokationsbuendel_objekt!(Messlokation, Some(Objekttyp::Messlokation));
impl_lokationsbuendel_objekt!(Netzlokation, Some(Objekttyp::Netzlokation));
impl_lokationsbuendel_objekt!(TechnischeRessource, Some(Objekttyp::TechnischeRessource));
impl_lokationsbuendel_objekt!(SteuerbareRessource, None);
#[derive(Debug, Clone, Copy)]
pub struct Lokationsbuendel<'a> {
zuordnung: &'a Lokationszuordnung,
}
impl<'a> Lokationsbuendel<'a> {
#[must_use]
pub const fn zuordnung(&self) -> &'a Lokationszuordnung {
self.zuordnung
}
#[must_use]
pub fn code(&self) -> Option<Result<Lokationsbuendelcode, IdentifierError>> {
self.zuordnung
.lokationsbuendelcode
.as_deref()
.map(Lokationsbuendelcode::new)
}
#[must_use]
pub fn struktur(&self) -> Option<&'static Lokationsbuendelstruktur> {
Lokationsbuendelstruktur::from_wire(self.zuordnung.lokationsbuendelcode.as_deref()?)
}
pub fn marktlokationen(&self) -> impl Iterator<Item = &'a Marktlokation> {
flatten(self.zuordnung.marktlokationen.as_deref())
}
pub fn messlokationen(&self) -> impl Iterator<Item = &'a Messlokation> {
flatten(self.zuordnung.messlokationen.as_deref())
}
pub fn netzlokationen(&self) -> impl Iterator<Item = &'a Netzlokation> {
flatten(self.zuordnung.netzlokationen.as_deref())
}
pub fn technische_ressourcen(&self) -> impl Iterator<Item = &'a TechnischeRessource> {
flatten(self.zuordnung.technische_ressourcen.as_deref())
}
pub fn steuerbare_ressourcen(&self) -> impl Iterator<Item = &'a SteuerbareRessource> {
flatten(self.zuordnung.steuerbare_ressourcen.as_deref())
}
#[must_use]
pub fn len(&self) -> usize {
count(self.zuordnung.marktlokationen.as_deref())
+ count(self.zuordnung.messlokationen.as_deref())
+ count(self.zuordnung.netzlokationen.as_deref())
+ count(self.zuordnung.technische_ressourcen.as_deref())
+ count(self.zuordnung.steuerbare_ressourcen.as_deref())
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn verbrauchs_ressourcen(&self) -> impl Iterator<Item = &'a TechnischeRessource> {
self.technische_ressourcen()
.filter(|tr| tr.objektrolle().is_some_and(Objektrolle::is_verbrauchs_tr))
}
pub fn objekte_auf_ebene(&self, ebene: u8) -> Vec<(Objekttyp, &'a str)> {
let mut out = Vec::new();
collect_ebene(self.marktlokationen(), ebene, &mut out);
collect_ebene(self.messlokationen(), ebene, &mut out);
collect_ebene(self.netzlokationen(), ebene, &mut out);
collect_ebene(self.technische_ressourcen(), ebene, &mut out);
out
}
}
fn collect_ebene<'a, T: LokationsbuendelObjekt + 'a>(
items: impl Iterator<Item = &'a T>,
ebene: u8,
out: &mut Vec<(Objekttyp, &'a str)>,
) {
for item in items {
if let (Some(rolle), Some(code)) =
(item.objektrolle(), item.lokationsbuendel_objektcode_raw())
{
if rolle.ebene == ebene {
out.push((rolle.objekttyp, code));
}
}
}
}
fn flatten<T>(items: Option<&[Box<T>]>) -> impl Iterator<Item = &T> {
items.unwrap_or(&[]).iter().map(std::convert::AsRef::as_ref)
}
fn count<T>(items: Option<&[Box<T>]>) -> usize {
items.map_or(0, <[Box<T>]>::len)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Befund {
StrukturcodeFehlt,
StrukturcodeUngueltig {
code: String,
fehler: IdentifierError,
},
StrukturUnbekannt {
code: String,
},
ObjektcodeFehlt {
objekttyp: Objekttyp,
index: usize,
},
ObjektcodeUngueltig {
objekttyp: Objekttyp,
index: usize,
code: String,
fehler: IdentifierError,
},
ObjektcodeUnbekannt {
objekttyp: Objekttyp,
index: usize,
code: String,
},
ObjekttypWiderspruch {
gefunden: Objekttyp,
index: usize,
erwartet: Objekttyp,
code: String,
},
ObjektcodeNichtInStruktur {
objekttyp: Objekttyp,
index: usize,
code: String,
},
AnzahlVerletzt {
code: String,
objekttyp: Objekttyp,
gefunden: u32,
erwartet: String,
},
}
impl std::fmt::Display for Befund {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StrukturcodeFehlt => f.write_str("lokationsbuendelcode is absent"),
Self::StrukturcodeUngueltig { code, fehler } => {
write!(f, "lokationsbuendelcode \"{code}\" is invalid: {fehler}")
}
Self::StrukturUnbekannt { code } => {
write!(
f,
"lokationsbuendelcode {code} is not a published structure"
)
}
Self::ObjektcodeFehlt { objekttyp, index } => {
write!(
f,
"{objekttyp}[{index}] carries no lokationsbuendelObjektcode"
)
}
Self::ObjektcodeUngueltig {
objekttyp,
index,
code,
fehler,
} => write!(
f,
"{objekttyp}[{index}] object code \"{code}\" is invalid: {fehler}"
),
Self::ObjektcodeUnbekannt {
objekttyp,
index,
code,
} => write!(
f,
"{objekttyp}[{index}] object code {code} is not catalogued"
),
Self::ObjekttypWiderspruch {
gefunden,
index,
erwartet,
code,
} => write!(
f,
"{gefunden}[{index}] carries {code}, which is a {erwartet} object code"
),
Self::ObjektcodeNichtInStruktur {
objekttyp,
index,
code,
} => write!(
f,
"{objekttyp}[{index}] object code {code} is not part of the declared structure"
),
Self::AnzahlVerletzt {
code,
objekttyp,
gefunden,
erwartet,
} => write!(
f,
"{objekttyp} {code}: found {gefunden}, structure states {erwartet}"
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct Buendelaudit {
pub struktur: Option<&'static Lokationsbuendelstruktur>,
pub befunde: Vec<Befund>,
}
impl Buendelaudit {
#[must_use]
pub fn is_conformant(&self) -> bool {
self.befunde.is_empty()
}
}
impl std::fmt::Display for Buendelaudit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.struktur {
Some(s) => writeln!(f, "{s}")?,
None => writeln!(f, "<no published structure>")?,
}
if self.befunde.is_empty() {
return f.write_str("conformant");
}
for (i, b) in self.befunde.iter().enumerate() {
if i > 0 {
writeln!(f)?;
}
write!(f, "- {b}")?;
}
Ok(())
}
}
pub trait LokationsbuendelExt {
fn buendel(&self) -> Lokationsbuendel<'_>;
fn lokationsbuendelcode(&self) -> Option<Result<Lokationsbuendelcode, IdentifierError>>;
fn lokationsbuendelstruktur(&self) -> Option<&'static Lokationsbuendelstruktur>;
fn audit_buendel(&self) -> Buendelaudit;
}
impl LokationsbuendelExt for Lokationszuordnung {
fn buendel(&self) -> Lokationsbuendel<'_> {
Lokationsbuendel { zuordnung: self }
}
fn lokationsbuendelcode(&self) -> Option<Result<Lokationsbuendelcode, IdentifierError>> {
self.buendel().code()
}
fn lokationsbuendelstruktur(&self) -> Option<&'static Lokationsbuendelstruktur> {
self.buendel().struktur()
}
fn audit_buendel(&self) -> Buendelaudit {
let mut befunde = Vec::new();
let struktur = match self.lokationsbuendelcode.as_deref() {
None => {
befunde.push(Befund::StrukturcodeFehlt);
None
}
Some(raw) => match Lokationsbuendelcode::new(raw) {
Err(fehler) => {
befunde.push(Befund::StrukturcodeUngueltig {
code: raw.to_owned(),
fehler,
});
None
}
Ok(code) => match Lokationsbuendelstruktur::from_code(&code) {
None => {
befunde.push(Befund::StrukturUnbekannt {
code: raw.to_owned(),
});
None
}
some => some,
},
},
};
let mut counts: Vec<(&'static str, u32)> = Vec::new();
macro_rules! walk {
($items:expr, $typ:expr) => {
for (index, item) in $items.enumerate() {
audit_objekt(item, $typ, index, struktur, &mut counts, &mut befunde);
}
};
}
let b = self.buendel();
walk!(b.marktlokationen(), Objekttyp::Marktlokation);
walk!(b.messlokationen(), Objekttyp::Messlokation);
walk!(b.netzlokationen(), Objekttyp::Netzlokation);
walk!(b.technische_ressourcen(), Objekttyp::TechnischeRessource);
if let Some(s) = struktur {
for row in s.objekte {
let found = counts
.iter()
.find(|(c, _)| *c == row.code)
.map_or(0, |(_, n)| *n);
if !row.permits(found) {
befunde.push(Befund::AnzahlVerletzt {
code: row.code.to_owned(),
objekttyp: row.rolle().objekttyp,
gefunden: found,
erwartet: row.cardinality(),
});
}
}
}
Buendelaudit { struktur, befunde }
}
}
fn audit_objekt<T: LokationsbuendelObjekt>(
item: &T,
objekttyp: Objekttyp,
index: usize,
struktur: Option<&'static Lokationsbuendelstruktur>,
counts: &mut Vec<(&'static str, u32)>,
befunde: &mut Vec<Befund>,
) {
let Some(raw) = item.lokationsbuendel_objektcode_raw() else {
befunde.push(Befund::ObjektcodeFehlt { objekttyp, index });
return;
};
if let Err(fehler) = LokationsbuendelObjektcode::new(raw) {
befunde.push(Befund::ObjektcodeUngueltig {
objekttyp,
index,
code: raw.to_owned(),
fehler,
});
return;
}
let Some(rolle) = Objektrolle::from_wire(raw) else {
befunde.push(Befund::ObjektcodeUnbekannt {
objekttyp,
index,
code: raw.to_owned(),
});
return;
};
if rolle.objekttyp != objekttyp {
befunde.push(Befund::ObjekttypWiderspruch {
gefunden: objekttyp,
index,
erwartet: rolle.objekttyp,
code: raw.to_owned(),
});
return;
}
if let Some(s) = struktur {
if s.objekt(raw).is_none() {
befunde.push(Befund::ObjektcodeNichtInStruktur {
objekttyp,
index,
code: raw.to_owned(),
});
return;
}
}
match counts.iter_mut().find(|(c, _)| *c == rolle.code) {
Some((_, n)) => *n += 1,
None => counts.push((rolle.code, 1)),
}
}