dvgw-edi
DVGW EDIFACT parser, validator and writer for the German gas transport and balancing market
Covers the DVGW-governed formats used in GaBi Gas 2.1 (BNetzA BK7-24-01-008):
ALOCAT, NOMINT, NOMRES and SSQNOT. The DVGW counterpart to edi-energy, which
covers BDEW EDI@Energy (UTILMD, MSCONS, INVOIC, APERAK, …).
The one thing to know first
A DVGW message does not name itself in UNH. Every format is a subset of a
UN/EDIFACT D.07A message, so UNH carries the carrier — ORDERS or ORDRSP
— and BGM C002 DE 1001 carries the message:
UNH+1+ORDERS:D:07A:UN:DVGW18' ← the carrier
BGM+01G::332+NOMINT00052' ← *this* says NOMINT
DTM+Z05:0:805' ← the timestamps below are UTC
DTM+137:201801042056:203' ← message date/time
DTM+Z01:201801050400201801060400:719' ← Gültigkeitszeitraum = the gas day
RFF+Z13:70030' ← Prüfidentifikator
Matching UNH against "NOMINT" therefore rejects every conformant message.
Identity here comes from DvgwDocument, with the carrier as a cross-check.
Two more consequences worth stating plainly:
DTM+137is not the gas day. It is when the message was written. The gas day isDTM+Z01, a period in format719.- DVGW publishes real Prüfidentifikatoren in
SG1 RFF+Z13— ALOCAT 70001–70023, NOMINT 70030–70034, NOMRES 70035–70039, SSQNOT 70095–70096. No synthetic encoding is needed, and the range does not collide with BDEW's.
Supported formats
| Message | Carrier | Document codes (BGM DE 1001) |
Prüfidentifikatoren |
|---|---|---|---|
| ALOCAT — Allokationsnachricht | ORDRSP |
X1G X2G X3G X4G X5G X6G X7G XBG |
70001–70023 |
| NOMINT — Nominierung | ORDERS |
01G 55G Y1G Y6G Y7G |
70030–70034 |
| NOMRES — Nominierungsantwort | ORDRSP |
07G 08G 19G 20G Y2G |
70035–70039 |
| SSQNOT — Mehr-/Mindermengenmeldung | ORDRSP |
BAG |
70095 (SLP), 70096 (RLM) |
CONTRL and APERAK acknowledge DVGW interchanges but are BDEW formats; they
live in edi-energy and are not reimplemented here.
UNH S009 DE 0057 holds either a package code (DVGW17) or the message version
(5.11a) depending on the format, so it is captured verbatim and nothing selects
behaviour from it; the builder writes the family's value unless told otherwise.
Reading
use ;
let platform = default;
for result in platform.parse_interchange
# Ok::
A quantity is a rate — unless its unit says otherwise
KW1 is kWh/h and KW2 kWh/d, so such a QTY states a rate over the
period its own DTM+2 names, and the energy is Σ(rate × duration); KWH is
the energy itself. Which units a family admits is the Segmentlayout's
(DvgwMessageType::admitted_units):
// 100 kWh/h for one hour + 200 kWh/h for two hours = 500 kWh (not 300).
let totals = msg.energy_by_qualifier;
assert_eq!;
Totals stay per qualifier because the qualifier is the direction — Z02 in,
Z03 out — and a VHP nomination states a purchase and a sale in one interchange.
single_energy_kwh(keep) returns one total only when there is one, refusing
when the selected positions mix directions or when any quantity could not be
integrated.
The keep filter is for NOMRES, which reports both sides of a match: IMD 17G
is what you nominated, 18G the counterparty's mirror, 16G the matched result.
Exactly one label may be counted — 16G when present.
Values are Decimal, not float: gas settles to at least three decimal places
(DVGW G 685 §7) and binary floating point cannot hold those fractions exactly.
Quantity::raw_value keeps the wire text so a non-numeric value is reportable
rather than silently zero.
The DVGW column of every Nachrichtenstruktur caps DTM+2 and SG37 QTY at one
per LOC group, so a profile is a run of LOC groups. The reader keeps every
QTY it meets under a LOC all the same, DVGW-LOC-MAX reports the excess,
and the builder writes the conformant shape.
SSQNOT as one record
use MehrMindermengenmeldung;
let record = from_message?;
// Netzkonto, Netzbetreiber, Abrechnungszeitraum, Verfahren (SLP/RLM),
// Mehrmenge and Mindermenge in kWh.
println!;
A message the Segmentlayout refuses — no NAD+ZSH, no STS Verfahren, a
non-numeric Menge — is refused here too rather than read as a partial figure.
How a message finds its process
ALOCAT 5.11a §3.3 publishes, per Prüfidentifikator, which Zuordnungstupel the
receiver applies, and names the segments each element comes from — ZO-T1
(Bilanzkreis, Netzbetreiber, Zeitreihentyp) through ZG-T1 (Clearingnummer).
The Zeitreihentyp is the STS code under the quantity (09G, 14G, …), not
LIN C212, which reads Z01 „allokiert" on every position. SSQNOT 5.7 §3.3
adds its own 2-Tupel (Netzkonto, Netzbetreiber), labelled ZO-T1:SSQNOT.
let key = msg.correlation_key.expect;
assert_eq!;
// `process_key` adds the gas day for the ZO-T* tuples: they identify an
// *object*, and a process is one gas day of it — or one Abrechnungszeitraum
// for a SSQNOT.
assert_eq!;
ZG-T1 is returned unchanged — a Clearingnummer already identifies one
Geschäftsvorfall, and a clearing case legitimately spans several days. A
Prüfidentifikator with no published assignment yields None rather than a
guessed key.
Nominations have no published tuple: a NOMRES carries no reference back to the NOMINT it answers, so the two are paired on the business key both carry.
Writing
A BKV that can only parse cannot nominate, and a Netzbetreiber that can only
parse cannot report its Mehr-/Mindermengen. MessageBuilder renders the header
and LIN loops the Nachrichtenbeschreibungen prescribe — SG1 in the order
each structure lists it, every coded value under its agency (332, or 9 for
a GLN through sender_coded/receiver_coded/party_coded), one LOC per
period, the family's unit and UNH Anwendungscode unless overridden:
use ;
use datetime;
let gas_day = DvgwPeriod ;
let wire = new
.document_number
.version
.pruefidentifikator
.message_datetime
.validity_period
.sender
.receiver
.position
.build?;
# Ok::
A re-nomination names the original with original_nomination(reference, processed_at), which writes RFF+AGO with the DTM+9 NOMINT marks
Erforderlich beside it. A SSQNOT is a MehrMindermengenmeldung position:
location("Z99", None), quantity("ZY2", "6782", zeitraum) (in KWH),
status("A1G") and party("ZSH", netzkonto).
build() refuses rather than emitting a message missing a Muss field. The
UNB/UNZ envelope is deliberately not written — the AS4 layer owns it and its
control reference.
Validation
DvgwPlatform::validate checks the message against the Segmentlayout of its
Nachrichtenbeschreibung: the mandatory BGM fields, the three header DTM rows
and whether each value matches the format code it declares, RFF+Z13 and its
range, NAD+MS/NAD+MR, at least one LIN, a LOC group per position with
one DTM+2 and one QTY, and the qualifiers, units and position NAD rows the
family's Segmentlayout lists. Rows that hang on the Anwendungsfall are keyed on
the Prüfidentifikator: BGM DE 1001 must be the code the column publishes
(DvgwDocument::for_pid), RFF+ANX is Muss on the six Allokationsclearing
columns only, DTM+9 beside a NOMINT's RFF+AGO, the STS Verfahren on every
SSQNOT Menge, and the RLM Anwendungsfall (70096, STS+A2G) is retired for
Zeiträume from 1.10.2015.
Findings come back as DvgwIssue with a typed Severity and a stable rule id;
only failures that stop the message being identified are Err.
Telling the families apart
A DVGW message rides ORDERS/ORDRSP, so a BDEW parser accepts one — and reads
70001 straight out of RFF+Z13, exactly where it looks for a
Prüfidentifikator. Neither UNH nor the Prüfidentifikator separates the
families; only BGM DE 1001 does.
sniff reads BGM DE 1001 out of the head of the interchange and stops, so an
ingest boundary can decide which parser owns the bytes for the price of one
segment:
match sniff
Market roles
| Role | Abbreviation |
|---|---|
| Fernleitungsnetzbetreiber | FNB |
| Verteilnetzbetreiber | VNB |
| Bilanzkreisverantwortlicher | BKV |
| Marktgebietsverantwortlicher | MGV |
Regulatory references
- § 20 Abs. 3 EnWG — Festlegungskompetenz for gas network access and balancing
- BNetzA BK7-24-01-008 — GaBi Gas 2.1
- Kooperationsvereinbarung Gas (KoV) — nomination and allocation deadlines
- DVGW-Nachrichtenbeschreibungen ALOCAT 5.11a, NOMINT 4.6, NOMRES 4.7, SSQNOT 5.7 — https://www.dvgw-sc.de/leistungen/it-dienstleistungen/datenaustausch-gas
Related crates
| Crate | Role |
|---|---|
dvgw-edi ← this crate |
DVGW EDIFACT — parse · validate · write (ALOCAT, NOMINT, NOMRES, SSQNOT) |
mako-gabi-gas |
GaBi Gas process engine — the workflows and deadlines these messages drive |
edi-energy |
The BDEW EDI@Energy formats, including the CONTRL/APERAK that acknowledge a DVGW interchange |
makod |
Production daemon — joins this crate with mako-gabi-gas |
Part of mako, an open-source Rust platform for German energy market communication (Marktkommunikation). Full documentation: https://hupe1980.github.io/mako/