EDIFACT-Nachrichten in der Energiewirtschaft: Entwickler-Einführung
Ein praktischer Leitfaden für die deutsche Marktkommunikation
🎯 Für wen ist diese Einführung?
Du bist Entwickler:in und stehst vor deiner ersten EDIFACT-Nachricht aus der deutschen Energiewirtschaft? Du möchtest verstehen, was all diese kryptischen Zeichen bedeuten und wie du effizient damit arbeiten kannst? Dann bist du hier richtig!
Diese Einführung:
- ✅ Erklärt EDIFACT von Grund auf – kein Vorwissen nötig
- ✅ Zeigt praktische Beispiele mit dem
edifact-json-transformer
- ✅ Vermittelt regulatorisches Wissen (GPKE, GeLi Gas, WiM, MaBiS)
- ✅ Gibt Best Practices für die tägliche Arbeit
Powered by: Willi Mako – die intelligente MaKo-Plattform
Open Source Tool: edifact-json-transformer
📖 Inhaltsverzeichnis
- Was ist EDIFACT und warum brauchen wir das?
- Die vier Säulen der Marktkommunikation
- Anatomie einer EDIFACT-Nachricht
- Schnellstart mit dem Transformer
- Die wichtigsten Nachrichtentypen
- Prüfidentifikatoren verstehen
- Praktische Tipps für den Alltag
- Häufige Fehler und Lösungen
- Best Practices
- Weiterführende Ressourcen
📚 Was ist EDIFACT und warum brauchen wir das?
Die Grundidee
EDIFACT (Electronic Data Interchange For Administration, Commerce and Transport) ist ein UN-Standard für den elektronischen Datenaustausch. In der deutschen Energiewirtschaft ist dieser Standard gesetzlich vorgeschrieben und bildet das Rückgrat der Kommunikation zwischen allen Marktteilnehmern.
Rechtliche Grundlagen
Die Basis bildet das Energiewirtschaftsgesetz (EnWG). Darauf aufbauend hat die Bundesnetzagentur (BNetzA) detaillierte Festlegungen erlassen, die jeden Aspekt der Marktkommunikation regeln:
Festlegung | Rechtsgrundlage | Was wird geregelt? |
---|---|---|
GPKE | Geschäftsprozesse Kundenbelieferung Elektrizität | Strom-Lieferantenwechsel, An-/Abmeldungen, Stammdaten |
GeLi Gas | Geschäftsprozesse Lieferantenwechsel Gas | Gas-Lieferantenwechsel, Gasbelieferung |
WiM | Wechselprozesse im Messwesen | Zählerwechsel, Messstellenbetrieb, Messdaten |
MaBiS | Marktregeln Bilanzierung Strom | Bilanzkreisabrechnung, Ausgleichsenergie |
Die Marktteilnehmer
┌─────────────────┐
│ Lieferant │ ──┐
│ (LF) │ │
└─────────────────┘ │
│ EDIFACT
┌─────────────────┐ │ Nachrichten
│ Netzbetreiber │ ──┤
│ (NB) │ │
└─────────────────┘ │
│
┌─────────────────┐ │
│Messstellenbetr. │ ──┘
│ (MSB) │
└─────────────────┘
Rollen:
- Lieferant (LF): Versorgt Endkunden mit Energie, verantwortlich für Abrechnung
- Netzbetreiber (NB): Betreibt das Netz, zentrale Kommunikationsstelle
- Messstellenbetreiber (MSB): Betreibt Zähler, liefert Messdaten
🏛️ Die vier Säulen der Marktkommunikation
1. GPKE – Strom-Lieferantenwechsel
Kernprozesse:
- Anmeldung zur Netznutzung (Prüf-ID 44001)
- Abmeldung (Prüf-ID 44004)
- Kündigung beim bisherigen Lieferanten (Prüf-ID 44016)
- Stammdatenänderungen (Prüf-ID 44112, 44123)
Typische Nachrichten: UTILMD, MSCONS, INVOIC, APERAK
Beispiel-Workflow:
Neuer Lieferant ──[UTILMD Anmeldung]──> Netzbetreiber
│
├─[UTILMD Kündigung]─> Alter Lieferant
│
Neuer Lieferant <─[APERAK Bestätigung]──────┘
2. GeLi Gas – Gas-Lieferantenwechsel
Besonderheiten:
- Zusätzliche Brennwert- und Zustandszahl-Informationen
- Spezielle Gasbeschaffenheitsdaten in MSCONS
- ORDERS für Anfragen (z.B. nach Brennwerten)
Typische Nachrichten: UTILMD, MSCONS, ORDERS, ORDRSP, APERAK
3. WiM – Wechselprozesse im Messwesen
Kernprozesse:
- Gerätewechsel (Prüf-ID 17xxx-Serie)
- Messkonzeptwechsel (Prüf-ID 21xxx-Serie)
- Störungsmeldungen (INSRPT)
- Auftragsbestätigungen (IFTSTA)
Typische Nachrichten: ORDERS, ORDRSP, IFTSTA, INSRPT, MSCONS
Beispiel-Workflow:
Lieferant ──[ORDERS Zählerwechsel]──> MSB
│
MSB ──────[IFTSTA Auftragsbestätigung]─┘
└─[MSCONS neue Messwerte]──────> Lieferant
4. MaBiS – Bilanzkreisabrechnung
Kernprozesse:
- Fahrplanmeldungen
- Bilanzkreiszuordnungen
- Mehr-/Mindermengenabrechnung
- Ausgleichsenergieabrechnung
Typische Nachrichten: MSCONS, PRICAT, PARTIN, COMDIS
🔬 Anatomie einer EDIFACT-Nachricht
Die Grundstruktur
Eine EDIFACT-Nachricht sieht zunächst kryptisch aus:
UNH+1+UTILMD:D:11A:UN:2.6'BGM+E01+12345+9'DTM+137:20241019:102'NAD+MS+9900123456789::293'RFF+Z13:44001'IDE+24+12345678901'UNT+6+1'
Zerlegt ergibt sich:
UNH+1+UTILMD:D:11A:UN:2.6' ← Nachrichtenkopf
BGM+E01+12345+9' ← Dokumentkopf
DTM+137:20241019:102' ← Datum
NAD+MS+9900123456789::293' ← Absender
RFF+Z13:44001' ← Prüfidentifikator
IDE+24+12345678901' ← Marktlokation
UNT+6+1' ← Nachrichtenende
Die Trennzeichen
Zeichen | Bedeutung | Beispiel |
---|---|---|
' |
Segment-Ende | NAD+MS+123' |
+ |
Datenelemente-Trenner | NAD+MS+123 |
: |
Komponenten-Trenner | RFF+Z13:44001 |
? |
Escape-Character | ?+ für echtes + |
, |
Dezimaltrennzeichen | 1234,56 |
Wichtige Segmente im Überblick
Segment | Vollständiger Name | Inhalt | Beispiel |
---|---|---|---|
UNH | Message Header | Nachrichtentyp, Version, Referenz | UNH+1+UTILMD:D:11A:UN:2.6' |
BGM | Beginning of Message | Dokumentart, Nummer, Funktion | BGM+E01+12345+9' |
DTM | Date/Time/Period | Datum/Zeit mit Qualifier | DTM+137:20241019:102' |
NAD | Name and Address | Partei-Informationen | NAD+MS+9900123456789::293' |
RFF | Reference | Referenzen (z.B. Prüf-ID) | RFF+Z13:44001' |
IDE | Identity | Objekt-Identifikation | IDE+24+12345678901' |
LOC | Place/Location | Ortsangaben | LOC+172+DE' |
QTY | Quantity | Mengenangaben | QTY+220:1234.5:KWH' |
MOA | Monetary Amount | Geldbeträge | MOA+77:1234.56:EUR' |
CCI | Characteristic | Eigenschaften/Klassen | CCI+Z19++++Bilanzkreis' |
UNT | Message Trailer | Segmentanzahl, Referenz | UNT+6+1' |
Kardinalitäten verstehen
In den BDEW-Anwendungshandbüchern (AHB) findest du Kardinalitäten:
Code | Bedeutung | Beschreibung |
---|---|---|
M | Mandatory | Pflichtfeld, muss immer vorhanden sein |
C | Conditional | Bedingt, abhängig vom Kontext |
R | Required (BDEW) | BDEW-spezifische Pflichtfelder |
D | Dependent | Abhängig von anderen Feldern |
N | Not used | Nicht verwendet in diesem Kontext |
🚀 Schnellstart mit dem Transformer
Installation
npm install edifact-json-transformer
Deine erste Transformation
const { EdifactTransformer } = require('edifact-json-transformer');
// Transformer mit Validierung erstellen
const transformer = new EdifactTransformer({
enableAHBValidation: true, // AHB-Regeln prüfen
parseTimestamps: true, // Datumswerte formatieren
generateGraphRelations: true // Graph-Beziehungen erstellen
});
// EDIFACT-String (z.B. aus Datei gelesen)
const edifactString = `
UNH+1+UTILMD:D:11A:UN:2.6'
BGM+E01+12345+9'
DTM+137:20241019:102'
NAD+MS+9900123456789::293'
NAD+MR+9900987654321::293'
RFF+Z13:44001'
IDE+24+12345678901'
UNT+7+1'
`;
// Transformation durchführen
const json = transformer.transform(edifactString);
// Ergebnis verwenden
console.log('Nachrichtentyp:', json.metadata.message_type);
console.log('Prüf-ID:', json.metadata.pruefidentifikator);
console.log('Marktlokationen:', json.body.stammdaten.marktlokationen);
Ausgabe:
{
metadata: {
message_type: 'UTILMD',
message_name: 'Stammdaten',
category: 'master_data',
applicable_processes: ['GPKE', 'GeLi Gas', 'WiM', 'MaBiS'],
version: '2.6',
pruefidentifikator: {
id: '44001',
description: 'Anmeldung NN'
}
},
body: {
stammdaten: {
marktlokationen: [
{ id: '12345678901', valid: true }
]
}
},
parties: {
sender: {
id: '9900123456789',
role: 'MS',
valid_mp_id: true
},
receiver: {
id: '9900987654321',
role: 'MR',
valid_mp_id: true
}
}
}
📨 Die wichtigsten Nachrichtentypen im Detail
1. UTILMD – Utility Master Data (Stammdaten)
Zweck: Austausch von Stammdaten zu Netzanschlüssen, Messlokationen und Lieferantenzuordnungen
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Zeitangaben (Lieferbeginn/-ende)
- RFF+Z13: Prüfidentifikator (Geschäftsvorfall)
- NAD: Beteiligte Marktpartner
- IDE+24: Marktlokations-ID (11-stellig)
- IDE+25: Messlokations-ID (33-stellig)
- LOC: Lokationsangaben
- CCI: Eigenschaften (z.B. Bilanzkreis)
Code-Beispiel:
const json = transformer.transform(utilmdString);
// Stammdaten auslesen
const malo = json.body.stammdaten.marktlokationen[0];
console.log(`MaLo-ID: ${malo.id}, gültig: ${malo.valid}`);
// Prüfidentifikator prüfen
if (json.metadata.pruefidentifikator.id === '44001') {
console.log('Anmeldung zur Netznutzung');
}
// Bilanzkreis ermitteln
const bk = json.body.stammdaten.bilanzkreise[0];
console.log(`Bilanzkreis: ${bk.id}`);
Typische Anwendungsfälle:
- Lieferantenwechsel (GPKE/GeLi Gas)
- Stammdatenpflege
- Messlokations-Anmeldung (WiM)
2. MSCONS – Meter Reading Schedule Consumption (Messwerte)
Zweck: Übermittlung von Verbrauchsdaten zwischen Marktpartnern
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Messperiode (Start/Ende)
- RFF: Referenzen (Marktlokation, Geschäftsvorfall)
- NAD: Beteiligte Partner
- LOC: Messlokation
- QTY: Messwerte mit Einheit
- SEQ: Zeitreihen-Sequenzen
- MEA: Detaillierte Messwerte (oft Viertelstundenwerte)
Code-Beispiel:
const json = transformer.transform(msconsString);
// Messwerte auslesen
json.body.messwerte.messwerte.forEach(messwert => {
console.log(`
Qualifier: ${messwert.qualifier}
Wert: ${messwert.value} ${messwert.unit}
Gültig: ${messwert.valid_unit}
`);
});
// Zeitreihen-Daten
console.log('Anzahl Zeitscheiben:', json.body.messwerte.zeitreihen.length);
// Messperiode
const periode = json.body.messwerte.messperioden;
console.log(`Von ${periode[0].datetime} bis ${periode[1].datetime}`);
Wichtige Qualifier:
-
220
: Gelieferte Energie -
66
: Zählerstand -
74
: Bezogene Energie
Einheiten:
-
KWH
: Kilowattstunde -
MWH
: Megawattstunde -
W
: Watt (Leistung)
Typische Anwendungsfälle:
- Bilanzierungsrelevante Messwerte (MaBiS)
- Abrechnungsdaten für Lieferanten
- Austausch zwischen MSB und NB/LF
3. ORDERS – Purchase Order (Bestellung)
Zweck: Elektronische Übermittlung von Bestellungen und Anfragen
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Bestelldatum, Liefertermin
- RFF: Bestellnummer, Referenzen
- NAD: Besteller und Empfänger
- LIN: Bestellpositionen
- PIA: Produktidentifikation
- IMD: Artikelbeschreibung
- QTY: Bestellmengen
Code-Beispiel:
const json = transformer.transform(ordersString);
// Bestellpositionen auslesen
json.body.bestellung.order_lines.forEach(line => {
console.log(`
Position: ${line.line_number}
Artikel-ID: ${line.item_id}
Typ: ${line.item_type}
`);
});
Typische Anwendungsfälle:
- Beauftragung Messstellenbetrieb (WiM)
- Anfrage Gasbeschaffenheit (GeLi Gas)
- Materialbestellungen
4. ORDRSP – Purchase Order Response (Bestellantwort)
Zweck: Antwort auf ORDERS-Nachricht
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Antwortdatum
- RFF: Referenz auf ORDERS
- NAD: Absender/Empfänger
- STS: Bestellstatus
- LIN, PIA, IMD: Positionsdaten
Statuswerte:
-
7
: Bestätigt -
27
: Abgelehnt -
4
: Teilweise bestätigt
5. INVOIC – Invoice (Rechnung)
Zweck: Übermittlung von Rechnungen und Gutschriften
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Rechnungs-/Leistungsdatum
- RFF: Rechnungs-/Bestellnummer
- NAD: Rechnungssteller/-empfänger
- LIN, PIA, IMD: Rechnungspositionen
- MOA: Geldbeträge
- TAX: Steuerdetails
Code-Beispiel:
const json = transformer.transform(invoicString);
// Rechnungssumme
const summe = json.body.rechnung.totals['77'];
console.log(`Gesamt: ${summe.amount} ${summe.currency}`);
// Steuern
json.body.rechnung.tax_amounts.forEach(tax => {
console.log(`Steuer: ${tax.type}, Rate: ${tax.rate}%`);
});
MOA-Qualifier:
-
77
: Rechnungssumme -
79
: Gesamtbetrag inkl. MwSt -
125
: Steuerbetrag
6. APERAK – Application Error and Acknowledgement
Zweck: Bestätigung oder Fehlermeldung für empfangene Nachrichten
Zentrale Segmente:
- UNH, BGM: Nachrichtenkopf
- DTM: Erstellungsdatum
- RFF: Referenz auf Original-Nachricht
- ERC: Fehlerinformationen
- FTX: Freitext-Erklärungen
Code-Beispiel:
const json = transformer.transform(aperakString);
// Status prüfen
if (json.body.quittierung.status === 'positive') {
console.log('Nachricht erfolgreich verarbeitet');
} else {
// Fehler ausgeben
json.body.quittierung.errors.forEach(error => {
console.error(`Fehler ${error.code}: ${error.description}`);
});
}
Häufige Fehlercodes:
-
1
: Syntax-Fehler -
2
: Semantischer Fehler -
3
: Geschäftsprozess-Fehler -
7
: Nachricht akzeptiert
🔍 Prüfidentifikatoren verstehen (RFF+Z13)
Was ist ein Prüfidentifikator?
Der Prüfidentifikator (im Segment RFF+Z13
) ist die wichtigste Kennung in einer MaKo-Nachricht. Er identifiziert eindeutig den Geschäftsvorfall und bestimmt:
- Welcher Prozess läuft (Anmeldung, Abmeldung, etc.)
- Welche Segmente Pflicht sind
- Welche Validierungsregeln gelten
- Welche Antwort-Nachrichten zu erwarten sind
Format:
RFF+Z13:44001'
│ │
│ └─ Prüfidentifikator (5-stellig)
└─ Qualifier Z13
Die wichtigsten Prüfidentifikatoren
GPKE (Strom)
Prüf-ID | Prozess | Richtung | Typische Antwort |
---|---|---|---|
44001 | Anmeldung Netznutzung | Lieferant → NB | APERAK (44002/44003) |
44002 | Bestätigung Anmeldung | NB → Lieferant | - |
44003 | Ablehnung Anmeldung | NB → Lieferant | - |
44004 | Abmeldung Netznutzung | Lieferant → NB | APERAK (44005/44006) |
44005 | Bestätigung Abmeldung | NB → Lieferant | - |
44006 | Ablehnung Abmeldung | NB → Lieferant | - |
44016 | Kündigung beim alten LF | NB → Lieferant | APERAK (44017/44018) |
44017 | Bestätigung Kündigung | Lieferant → NB | - |
44018 | Ablehnung Kündigung | Lieferant → NB | - |
44112 | Stammdatenänderung | NB → Lieferant | - |
44123 | Bila.rel. Änderung | NB → Lieferant | - |
Messwerte
Prüf-ID | Prozess | Verwendung |
---|---|---|
13002 | Zählerstand | MSCONS |
13008 | Lastgang | MSCONS |
13009 | Energiemenge | MSCONS |
13007 | Gasbeschaffenheit | MSCONS (Gas) |
13006 | Messwert Storno | MSCONS |
Stammdaten-Anfragen
Prüf-ID | Prozess | Verwendung |
---|---|---|
17101 | Anfrage Stammdaten MaLo | ORDERS |
17102 | Anfrage Werte | ORDERS |
19101 | Ablehnung Anfrage Stammdaten | ORDRSP |
19102 | Ablehnung Anfrage Werte | ORDRSP |
WiM (Messwesen)
Prüf-ID | Prozess | Verwendung |
---|---|---|
17009 | Gerätewechsel | ORDERS |
19015 | Bestätigung Gerätewechsel | ORDRSP |
21039 | Auftragsstatus Sperren | IFTSTA |
21040 | Info Entsperrauftrag | IFTSTA |
Rechnungen
Prüf-ID | Prozess | Verwendung |
---|---|---|
31001 | Abschlagsrechnung | INVOIC |
31002 | NN-Rechnung | INVOIC |
31003 | WiM-Rechnung | INVOIC |
31004 | Stornorechnung | INVOIC |
33001 | Bestätigung | REMADV |
33002 | Abweisung | REMADV |
Code-Beispiel: Prüfidentifikator auswerten
const json = transformer.transform(edifactString);
const pruefId = json.metadata.pruefidentifikator.id;
switch(pruefId) {
case '44001':
console.log('Anmeldung zur Netznutzung');
// Validiere Pflichtfelder für Anmeldung
validateAnmeldung(json);
break;
case '44004':
console.log('Abmeldung vom Netz');
validateAbmeldung(json);
break;
case '13008':
console.log('Lastgangdaten');
processLastgang(json.body.messwerte);
break;
default:
console.warn(`Unbekannte Prüf-ID: ${pruefId}`);
}
Helper-Funktion
const { isGPKEProcess } = require('edifact-json-transformer');
// Prozess-Zuordnung prüfen
if (isGPKEProcess(edifactString)) {
console.log('GPKE-Prozess erkannt');
}
// Alle Prüfidentifikatoren verfügbar
const { pruefidentifikatoren } = require('edifact-json-transformer');
console.log(pruefidentifikatoren['44001']);
// → "Anmeldung NN"
💡 Praktische Tipps für den Alltag
1. Schnell-Check: Was ist das für eine Nachricht?
// Nur Metadaten ohne vollständige Transformation
const { EdifactTransformer } = require('edifact-json-transformer');
function quickInfo(edifactString) {
const transformer = new EdifactTransformer({
validateStructure: false, // Schneller
parseTimestamps: false
});
const json = transformer.transform(edifactString);
return {
typ: json.metadata.message_type,
name: json.metadata.message_name,
pruefId: json.metadata.pruefidentifikator?.id,
prozess: json.metadata.applicable_processes,
absender: json.parties.sender?.id,
empfaenger: json.parties.receiver?.id
};
}
console.log(quickInfo(edifactString));
// { typ: 'UTILMD', name: 'Stammdaten', pruefId: '44001', ... }
2. Marktlokationen schnell extrahieren
const { extractAllMarktlokationIds } = require('edifact-json-transformer');
// Super-schnelle Extraktion
const maloIds = extractAllMarktlokationIds(edifactString);
console.log(maloIds); // ['12345678901', '98765432109']
// In der Datenbank nachschlagen
maloIds.forEach(id => {
const kunde = db.findByMaloId(id);
console.log(`Kunde: ${kunde.name} (${id})`);
});
3. Zeitscheiben in MSCONS verarbeiten
function processTimeSeries(msconsString) {
const json = transformer.transform(msconsString);
const zeitreihen = json.body.messwerte.zeitreihen;
// Viertelstundenwerte
const viertelstundenwerte = zeitreihen.map((seq, idx) => {
const messwert = json.body.messwerte.messwerte[idx];
return {
sequenz: seq.sequence_number,
wert: messwert.value,
einheit: messwert.unit,
zeitpunkt: calculateTimeSlot(seq.sequence_number, startDate)
};
});
return viertelstundenwerte;
}
function calculateTimeSlot(sequenz, startDate) {
// Viertelstunde = 15 Minuten
const minutes = (sequenz - 1) * 15;
return new Date(startDate.getTime() + minutes * 60000);
}
4. Validierung mit Fehler-Report
const { validateAHB } = require('edifact-json-transformer');
function validateAndReport(edifactString) {
const report = validateAHB(edifactString);
if (!report.is_valid) {
console.error('⚠️ Validierungsfehler gefunden:');
report.errors.forEach(error => {
console.error(` ❌ [${error.category}] ${error.message}`);
});
report.warnings.forEach(warning => {
console.warn(` ⚠️ [${warning.category}] ${warning.message}`);
});
return false;
}
console.log('✅ Nachricht ist valide');
return true;
}
5. Datumswerte lesen
const json = transformer.transform(edifactString);
// Alle Datumswerte
console.log(json.dates);
// {
// message_date: '2024-10-19',
// start_date: '2024-01-01',
// end_date: '2024-12-31'
// }
// Spezifische Prüfungen
const { start_date, end_date } = json.dates;
if (new Date(start_date) > new Date(end_date)) {
console.error('Start liegt nach Ende!');
}
// Dauer berechnen
const days = (new Date(end_date) - new Date(start_date)) / (1000 * 60 * 60 * 24);
console.log(`Zeitraum: ${days} Tage`);
6. Graph-Beziehungen für Analyse
const transformer = new EdifactTransformer({
generateGraphRelations: true
});
const json = transformer.transform(edifactString);
// Neo4j Cypher generieren
const { convertToNeo4jCypher } = require('edifact-json-transformer');
const statements = convertToNeo4jCypher(edifactString);
statements.forEach(stmt => {
console.log(stmt.cypher);
console.log(stmt.parameters);
});
// Oder direkt aus JSON
json.graph_relations.forEach(rel => {
console.log(`${rel.from.type}:${rel.from.id} -[${rel.relationship}]-> ${rel.to.type}:${rel.to.id}`);
});
7. Batch-Verarbeitung
const fs = require('fs');
const { EdifactTransformer } = require('edifact-json-transformer');
function processBatch(directory) {
const transformer = new EdifactTransformer({
enableAHBValidation: true
});
const files = fs.readdirSync(directory);
const results = {
success: 0,
errors: [],
warnings: []
};
files.forEach(file => {
try {
const content = fs.readFileSync(`${directory}/${file}`, 'utf8');
const json = transformer.transform(content);
if (json.validation?.is_valid !== false) {
results.success++;
} else {
results.errors.push({
file,
errors: json.validation.errors
});
}
if (json.validation?.warnings?.length > 0) {
results.warnings.push({
file,
warnings: json.validation.warnings
});
}
} catch (error) {
results.errors.push({ file, error: error.message });
}
});
console.log(`
✅ Erfolgreich: ${results.success}
❌ Fehler: ${results.errors.length}
⚠️ Warnungen: ${results.warnings.length}
`);
return results;
}
🐛 Häufige Fehler und Lösungen
1. Syntaxfehler
Problem: Falsche Trennzeichen
// ❌ Falsch
"UNH+1+UTILMD:D:11A:UN:2.6'BGM+E01+12345+9'..." // Fehlt Zeilenumbruch-Escape
// ✅ Richtig
const edifact = normalizeEdifact(rawString); // Transformer macht das automatisch
Problem: Fehlende Pflichtsegmente
const json = transformer.transform(edifactString);
if (json.validation?.errors) {
json.validation.errors.forEach(error => {
if (error.message.includes('Fehlendes UNH')) {
console.error('Nachrichtenkopf fehlt!');
}
});
}
Lösung: Prüfe die AHB-Vorgaben für den jeweiligen Nachrichtentyp.
Problem: Falsche Datenformate
// DTM-Segment mit falschem Format
"DTM+137:19.10.2024:102'" // ❌ Falsch: TT.MM.JJJJ
"DTM+137:20241019:102'" // ✅ Richtig: JJJJMMTT
Debugging:
const json = transformer.transform(edifactString, {
includeRawSegments: true
});
// Rohe Segmente inspizieren
json.raw_segments.filter(s => s.tag === 'DTM').forEach(dtm => {
console.log('DTM:', dtm.raw);
});
2. Semantische Fehler
Problem: Referenznummer-Mismatch
// ❌ UNH und UNT stimmen nicht überein
"UNH+1+UTILMD:D:11A:UN:2.6'...UNT+7+2'"
// ✅ Korrekt
"UNH+1+UTILMD:D:11A:UN:2.6'...UNT+7+1'"
Automatische Prüfung:
const json = transformer.transform(edifactString);
if (json.validation?.errors.some(e => e.message.includes('Referenznummer-Mismatch'))) {
console.error('UNH/UNT Referenzen stimmen nicht überein!');
}
Problem: Ungültige MP-ID
const json = transformer.transform(edifactString);
Object.entries(json.parties).forEach(([role, party]) => {
if (!party.valid_mp_id) {
console.error(`
❌ Ungültige MP-ID für ${role}
Wert: ${party.id}
Erwartet: 13-stellig numerisch
`);
}
});
Validierung:
function isValidMpId(id) {
return /^\d{13}$/.test(id);
}
Problem: Start-/Enddatum vertauscht
const json = transformer.transform(edifactString);
if (json.validation?.errors) {
json.validation.errors.forEach(error => {
if (error.message.includes('Start-Datum liegt nach End-Datum')) {
console.error('Zeitlogik ist falsch!');
console.log('Start:', json.dates.start_date);
console.log('Ende:', json.dates.end_date);
}
});
}
3. Geschäftsprozess-Fehler
Problem: Falscher Prüfidentifikator
const json = transformer.transform(edifactString);
const pruefId = json.metadata.pruefidentifikator?.id;
// Prüfen ob Prüf-ID zum Nachrichtentyp passt
const validCombinations = {
'UTILMD': ['44001', '44002', '44003', '44004', '44005', '44006', '44016', '44112', '44123'],
'MSCONS': ['13002', '13007', '13008', '13009'],
'ORDERS': ['17009', '17101', '17102'],
'INVOIC': ['31001', '31002', '31003', '31004']
};
const messageType = json.metadata.message_type;
if (!validCombinations[messageType]?.includes(pruefId)) {
console.error(`
❌ Prüf-ID ${pruefId} passt nicht zu ${messageType}
Erlaubte IDs: ${validCombinations[messageType].join(', ')}
`);
}
Problem: Fehlende Rollen
// Für Anmeldung (44001) sind spezifische Rollen erforderlich
if (json.metadata.pruefidentifikator.id === '44001') {
const hasLieferant = json.parties.lieferant || json.parties.sender;
const hasNetzbetreiber = json.parties.netzbetreiber || json.parties.receiver;
if (!hasLieferant) {
console.error('❌ Lieferant-Rolle fehlt für Anmeldung');
}
if (!hasNetzbetreiber) {
console.error('❌ Netzbetreiber-Rolle fehlt für Anmeldung');
}
}
Problem: Ungültige Marktlokations-ID
const json = transformer.transform(edifactString);
json.body.stammdaten?.marktlokationen?.forEach((malo, idx) => {
if (!malo.valid) {
console.error(`
❌ Marktlokation ${idx + 1} ungültig
ID: ${malo.id}
Erwartet: 11-stellig
Tatsächlich: ${malo.id?.length} Zeichen
`);
}
});
json.body.stammdaten?.messlokationen?.forEach((melo, idx) => {
if (!melo.valid) {
console.error(`
❌ Messlokation ${idx + 1} ungültig
ID: ${melo.id}
Erwartet: 33-stellig
Tatsächlich: ${melo.id?.length} Zeichen
`);
}
});
✅ Best Practices für Entwickler
1. Validierung auf allen Ebenen
class EdifactProcessor {
constructor() {
this.transformer = new EdifactTransformer({
enableAHBValidation: true,
validateStructure: true,
validateBusinessRules: true
});
}
async process(edifactString) {
// 1. Syntax-Validierung
if (!this.validateSyntax(edifactString)) {
throw new Error('Syntax ungültig');
}
// 2. Transformation
const json = this.transformer.transform(edifactString);
// 3. Struktur-Validierung
if (!json.validation?.is_valid) {
this.logErrors(json.validation.errors);
throw new Error('Struktur ungültig');
}
// 4. Geschäftsprozess-Validierung
this.validateBusinessLogic(json);
// 5. Verarbeitung
return this.processMessage(json);
}
validateSyntax(edifactString) {
// Grundlegende Checks
return edifactString.includes("UNH") &&
edifactString.includes("UNT");
}
validateBusinessLogic(json) {
const pruefId = json.metadata.pruefidentifikator?.id;
// Prozessspezifische Validierung
switch(pruefId) {
case '44001':
this.validateAnmeldung(json);
break;
case '13008':
this.validateLastgang(json);
break;
}
}
}
2. Umfassendes Error Handling
class MessageValidator {
validate(json) {
const errors = [];
const warnings = [];
// Pflichtfelder prüfen
if (!json.metadata.pruefidentifikator) {
errors.push({
category: 'AHB',
message: 'Prüfidentifikator fehlt',
severity: 'ERROR'
});
}
// Marktlokationen prüfen
json.body.stammdaten?.marktlokationen?.forEach((malo, idx) => {
if (!malo.valid) {
warnings.push({
category: 'STAMMDATEN',
message: `MaLo ${idx + 1}: ${malo.id} ist ungültig`,
severity: 'WARNING'
});
}
});
return {
is_valid: errors.length === 0,
errors,
warnings
};
}
generateAPERAK(originalMessage, errors) {
// APERAK-Nachricht generieren
return `
UNH+${generateRef()}+APERAK:D:11A:UN:2.0'
BGM+${errors.length > 0 ? '27' : '7'}+${generateRef()}+9'
${errors.map(e => `ERC+${e.code}'`).join('')}
UNT+${2 + errors.length}+${generateRef()}'
`;
}
}
3. Strukturierte Logging-Strategie
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'edifact-error.log', level: 'error' }),
new winston.transports.File({ filename: 'edifact-combined.log' })
]
});
function processMessage(edifactString, source) {
const startTime = Date.now();
try {
const json = transformer.transform(edifactString);
logger.info({
event: 'message_processed',
type: json.metadata.message_type,
pruefId: json.metadata.pruefidentifikator?.id,
source,
duration: Date.now() - startTime,
validation: json.validation?.is_valid
});
return json;
} catch (error) {
logger.error({
event: 'processing_failed',
source,
error: error.message,
stack: error.stack,
duration: Date.now() - startTime
});
throw error;
}
}
4. Modularer Aufbau
// parser.js
class EdifactParser {
parse(edifactString) {
return transformer.transform(edifactString);
}
}
// validator.js
class EdifactValidator {
validateAHB(json) { /* ... */ }
validateStructure(json) { /* ... */ }
validateBusinessRules(json) { /* ... */ }
}
// processor.js
class MessageProcessor {
processUTILMD(json) { /* ... */ }
processMSCONS(json) { /* ... */ }
processORDERS(json) { /* ... */ }
}
// main.js
class EdifactService {
constructor() {
this.parser = new EdifactParser();
this.validator = new EdifactValidator();
this.processor = new MessageProcessor();
}
async handleMessage(edifactString) {
const json = this.parser.parse(edifactString);
if (!this.validator.validateAHB(json)) {
throw new ValidationError('AHB-Validierung fehlgeschlagen');
}
const messageType = json.metadata.message_type;
return this.processor[`process${messageType}`](json);
}
}
5. Testing-Strategie
// test/utilmd.test.js
const { EdifactTransformer } = require('edifact-json-transformer');
describe('UTILMD Transformation', () => {
let transformer;
beforeEach(() => {
transformer = new EdifactTransformer({
enableAHBValidation: true
});
});
test('Anmeldung NN (44001) korrekt verarbeitet', () => {
const edifact = loadTestFile('utilmd_anmeldung_44001.txt');
const json = transformer.transform(edifact);
expect(json.metadata.message_type).toBe('UTILMD');
expect(json.metadata.pruefidentifikator.id).toBe('44001');
expect(json.body.stammdaten.marktlokationen).toHaveLength(1);
expect(json.validation.is_valid).toBe(true);
});
test('Ungültige MaLo-ID wird erkannt', () => {
const edifact = loadTestFile('utilmd_invalid_malo.txt');
const json = transformer.transform(edifact);
expect(json.validation.is_valid).toBe(false);
expect(json.validation.errors).toContainEqual(
expect.objectContaining({
message: expect.stringContaining('11-stellig')
})
);
});
test('Fehlender Prüfidentifikator wird gemeldet', () => {
const edifact = loadTestFile('utilmd_no_pruefid.txt');
const json = transformer.transform(edifact);
expect(json.validation.errors).toContainEqual(
expect.objectContaining({
message: expect.stringContaining('Prüfidentifikator')
})
);
});
});
6. Versionierung und Updates
class EdifactService {
constructor() {
this.transformers = {
'v2.6': new EdifactTransformer({ /* config für 2.6 */ }),
'v2.7': new EdifactTransformer({ /* config für 2.7 */ })
};
}
getTransformer(version) {
return this.transformers[`v${version}`] || this.transformers['v2.6'];
}
process(edifactString) {
// Version aus UNH extrahieren
const versionMatch = edifactString.match(/UNH\+\d+\+[^:]+:[^:]+:[^:]+:[^:]+:([^']+)'/);
const version = versionMatch ? versionMatch[1] : '2.6';
const transformer = this.getTransformer(version);
return transformer.transform(edifactString);
}
}
7. Performance-Optimierung
// Caching für wiederholte Transformationen
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
function transformWithCache(edifactString) {
const hash = crypto.createHash('md5').update(edifactString).digest('hex');
let json = cache.get(hash);
if (json) {
console.log('Cache hit');
return json;
}
json = transformer.transform(edifactString);
cache.set(hash, json);
return json;
}
// Bulk-Processing mit Worker Threads
const { Worker } = require('worker_threads');
async function processBulk(messages) {
const chunkSize = Math.ceil(messages.length / 4);
const workers = [];
for (let i = 0; i < messages.length; i += chunkSize) {
const chunk = messages.slice(i, i + chunkSize);
workers.push(
new Promise((resolve) => {
const worker = new Worker('./edifact-worker.js', {
workerData: chunk
});
worker.on('message', resolve);
})
);
}
const results = await Promise.all(workers);
return results.flat();
}
📚 Weiterführende Ressourcen
Offizielle Quellen
-
EDI@Energy Portal (BDEW)
- URL: https://www.edi-energy.de/
- Registrierung erforderlich
- Enthält: AHB, MIG, Codelisten, Prozessbeschreibungen
-
Bundesnetzagentur
- Festlegungen zu GPKE, GeLi Gas, WiM, MaBiS
- URL: https://www.bundesnetzagentur.de/
-
BDEW Codelisten
- Aktuelle Qualifier und Codes
- Updates bei Prozessänderungen
Tools und Plattformen
-
Willi Mako – Intelligente MaKo-Plattform
- Web-App: https://stromhaltig.de/app/
- Open-Source Client: https://github.com/energychain/willi-mako-client
- Regulatorisches Wissen, Beispiele, Validierung
-
edifact-json-transformer
- Repository: https://github.com/energychain/edifact-to-json-transformer
- NPM:
npm install edifact-json-transformer
- MIT-Lizenz, Community-Support
Lernressourcen
- EDIFACT ISO 9735 Standard: Grundlagen zu Segmenten und Syntax
- BDEW Schulungen: Workshops zu Marktkommunikation
- Online-Communities: Austausch mit anderen Entwickler:innen
Support
- Issues/Bugs: GitHub Issues
- Pull Requests: Contributions willkommen!
- Maintainer: STROMDAO GmbH (https://stromdao.de/)
🎓 Zusammenfassung
Du hast jetzt gelernt:
✅ Grundlagen: Was EDIFACT ist und warum es in der Energiewirtschaft genutzt wird
✅ Prozesse: GPKE, GeLi Gas, WiM und MaBiS im Detail
✅ Nachrichtentypen: UTILMD, MSCONS, ORDERS, INVOIC, APERAK
✅ Prüfidentifikatoren: Die wichtigsten IDs und ihre Bedeutung
✅ Praktische Tools: Wie du mit dem Transformer effizient arbeitest
✅ Fehlerbehandlung: Häufige Probleme erkennen und lösen
✅ Best Practices: Professionelle Entwicklung von MaKo-Software
Nächste Schritte
-
Installiere den Transformer:
npm install edifact-json-transformer
- Probiere die Beispiele aus diesem Guide
- Validiere echte Nachrichten aus deinem Projekt
- Erkunde Willi Mako für tieferes regulatorisches Wissen
- Teile deine Erfahrungen und trage zur Community bei
Denk daran
"EDIFACT ist komplex, aber mit den richtigen Tools und etwas Übung wirst du zum MaKo-Profi!"
Viel Erfolg bei deinen ersten (oder nächsten) Schritten in der Marktkommunikation! 🚀
Lizenz: MIT
Maintainer: STROMDAO GmbH
Powered by: Willi Mako – Intelligente Marktkommunikation
Veröffentlicht auf: Corrently.io
Letzte Aktualisierung: Oktober 2024
No Comments