edi_energy/messages/segments.rs
1//! Typed EDIFACT segment structs shared across all EDI@Energy message types.
2//!
3//! Each struct maps directly to one EDIFACT segment via the `#[edifact(segment = "TAG")]`
4//! derive attribute and implements both [`EdifactDeserialize`] and [`EdifactSerialize`].
5//!
6//! # Element / component indexing
7//!
8//! EDIFACT uses 0-based indices:
9//! - `#[edifact(element = N)]` — selects component 0 of the Nth element.
10//! - `#[edifact(element = N, component = M)]` — selects component M of the Nth element.
11//!
12//! For composite data elements (e.g. C507 in DTM) you need multiple fields
13//! sharing the same `element = N` but with different `component` indices.
14//!
15//! [`EdifactDeserialize`]: edifact_rs::EdifactDeserialize
16//! [`EdifactSerialize`]: edifact_rs::EdifactSerialize
17
18use edifact_rs::{EdifactDeserialize, EdifactSerialize};
19
20// ── BGM ───────────────────────────────────────────────────────────────────────
21
22/// `BGM` — Beginning of Message.
23///
24/// Structure: `BGM+<document_code>+<document_id>+<function>'`
25///
26/// | Element | DE | Meaning |
27/// |---------|------|----------------------------------------------------------|
28/// | 0 | 1001 | Document / message name code (e.g. `E01`, `1000`) |
29/// | 1 | 1004 | Document / message number (Pruefidentifikator value) |
30/// | 2 | 1225 | Message function, coded (e.g. `9` = original) |
31#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
32#[edifact(segment = "BGM")]
33pub struct Bgm {
34 /// DE 1001 — document / message name code. In EDI@Energy this is the
35 /// message category identifier (e.g. `"E01"` for grid-feed-in UTILMD,
36 /// `"1000"` for a positive APERAK).
37 #[edifact(element = 0)]
38 pub document_code: String,
39 /// DE 1004 — document / message number. Carries the Pruefidentifikator
40 /// value (e.g. `"11001"`) or an 8-digit document reference number.
41 #[edifact(element = 1)]
42 pub document_id: Option<String>,
43 /// DE 1225 — message function, coded (e.g. `"9"` = original).
44 #[edifact(element = 2)]
45 pub function: Option<String>,
46}
47
48impl Bgm {
49 /// Parse `document_id` as a [`crate::Pruefidentifikator`].
50 ///
51 /// Returns `None` when the field is absent or the value is not a valid
52 /// 5-digit Pruefidentifikator.
53 #[must_use]
54 pub fn pruefidentifikator(&self) -> Option<crate::Pruefidentifikator> {
55 self.document_id
56 .as_deref()
57 .and_then(|s| s.parse::<u32>().ok())
58 .and_then(|code| crate::Pruefidentifikator::new(code).ok())
59 }
60}
61
62// ── DTM ───────────────────────────────────────────────────────────────────────
63
64/// `DTM` — Date/Time/Period.
65///
66/// Structure: `DTM+<qualifier>:<value>:<format>'` (C507 composite in element 0)
67///
68/// | Element | Component | DE | Meaning |
69/// |---------|-----------|------|-----------------------------------------|
70/// | 0 | 0 | 2005 | Date/time/period function qualifier |
71/// | 0 | 1 | 2380 | Date/time/period text value |
72/// | 0 | 2 | 2379 | Date/time/period format qualifier |
73#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
74#[edifact(segment = "DTM")]
75pub struct Dtm {
76 /// DE 2005 — date/time/period qualifier (e.g. `"137"` = document date,
77 /// `"163"` = beginning of delivery period).
78 #[edifact(element = 0)]
79 pub qualifier: String,
80 /// DE 2380 — date/time/period value (e.g. `"20230101"`).
81 #[edifact(element = 0, component = 1)]
82 pub value: Option<String>,
83 /// DE 2379 — date/time/period format qualifier (e.g. `"102"` = CCYYMMDD).
84 #[edifact(element = 0, component = 2)]
85 pub format: Option<String>,
86}
87
88impl Dtm {
89 /// Returns `true` when this is the document date / creation timestamp
90 /// (`qualifier == "137"`).
91 #[must_use]
92 pub fn is_document_date(&self) -> bool {
93 self.qualifier == "137"
94 }
95
96 /// Returns `true` when this marks the beginning of a supply/delivery period
97 /// (`qualifier == "163"`).
98 #[must_use]
99 pub fn is_period_start(&self) -> bool {
100 self.qualifier == "163"
101 }
102
103 /// Returns `true` when this marks the end of a supply/delivery period
104 /// (`qualifier == "164"`).
105 #[must_use]
106 pub fn is_period_end(&self) -> bool {
107 self.qualifier == "164"
108 }
109
110 /// Returns the date/time value as a `&str`, if present.
111 #[must_use]
112 pub fn value_str(&self) -> Option<&str> {
113 self.value.as_deref()
114 }
115}
116
117// ── NAD ───────────────────────────────────────────────────────────────────────
118
119/// `NAD` — Name and Address, qualified by element 0 (DE 3035).
120///
121/// Structure: `NAD+<qualifier>+<party_id>::<agency>'`
122///
123/// | Element | Component | DE | Meaning |
124/// |---------|-----------|------|--------------------------------------|
125/// | 0 | 0 | 3035 | Party function qualifier |
126/// | 1 | 0 | 3039 | Party identification |
127/// | 1 | 2 | 3055 | Code list responsible agency |
128/// | 3 | 0 | 3036 | Party name |
129///
130/// Common qualifiers:
131/// - `"MS"` — message sender (DE 3035)
132/// - `"MR"` — message recipient (DE 3035)
133/// - `"AG"` — authorised/requesting agent
134#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
135#[edifact(segment = "NAD", qualifier_from = 0)]
136pub struct Nad {
137 /// DE 3035 — party function qualifier (e.g. `"MS"` = message sender).
138 #[edifact(element = 0)]
139 pub qualifier: String,
140 /// DE 3039 — party identification code (GLN / BDEW code), component 0 of C082.
141 #[edifact(element = 1)]
142 pub party_id: Option<String>,
143 /// DE 3055 — code list responsible agency, component 2 of C082.
144 ///
145 /// Common values in EDI@Energy:
146 /// - `"293"` — BDEW (the standard for German `MaKo` market participants)
147 /// - `"9"` — GS1/EAN (global GLN scheme, rare in German `MaKo`)
148 /// - `"305"` — ECOD/ENTSO-E (EIC codes for TSOs and Regelzonen)
149 /// - `"332"` — DVGW (legacy gas-sector codes)
150 ///
151 /// Use [`crate::AgencyCode`] to parse or format this value.
152 #[edifact(element = 1, component = 2)]
153 pub agency_code: Option<String>,
154 /// DE 3036 — party name, component 0 of C080.
155 #[edifact(element = 3)]
156 pub party_name: Option<String>,
157}
158
159// ── RFF ───────────────────────────────────────────────────────────────────────
160
161/// `RFF` — Reference.
162///
163/// Structure: `RFF+<qualifier>:<reference>'` (C506 composite in element 0)
164///
165/// | Element | Component | DE | Meaning |
166/// |---------|-----------|------|-------------------------------------|
167/// | 0 | 0 | 1153 | Reference function qualifier |
168/// | 0 | 1 | 1154 | Reference identifier |
169///
170/// Common qualifiers in EDI@Energy:
171/// - `"ACW"` — acknowledgement reference (APERAK)
172/// - `"TN"` — transaction reference number
173/// - `"Z13"` — Pruefidentifikator of referenced message
174#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
175#[edifact(segment = "RFF")]
176pub struct Rff {
177 /// DE 1153 — reference function qualifier (e.g. `"ACW"`, `"TN"`, `"Z13"`).
178 #[edifact(element = 0)]
179 pub qualifier: String,
180 /// DE 1154 — reference identifier value.
181 #[edifact(element = 0, component = 1)]
182 pub reference: Option<String>,
183}
184
185// ── IDE ───────────────────────────────────────────────────────────────────────
186
187/// `IDE` — Identity.
188///
189/// Used in UTILMD to carry market location IDs (Marktlokation / Messlokation).
190///
191/// | Element | Component | DE | Meaning |
192/// |---------|-----------|------|-------------------------------------|
193/// | 0 | 0 | 7495 | Object type qualifier |
194/// | 1 | 0 | 7402 | Identity number (MaLo / MeLo ID) |
195#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
196#[edifact(segment = "IDE")]
197pub struct Ide {
198 /// DE 7495 — object type qualifier (e.g. `"Z18"` = Marktlokation,
199 /// `"Z19"` = Messlokation).
200 #[edifact(element = 0)]
201 pub qualifier: String,
202 /// DE 7402 — object identity number (component 0 of C206).
203 /// Must be exactly 11 upper-case alphanumeric characters for EDI@Energy.
204 #[edifact(element = 1)]
205 pub object_id: Option<String>,
206}
207
208// ── LOC ───────────────────────────────────────────────────────────────────────
209
210/// `LOC` — Place/Location Identification.
211///
212/// | Element | Component | DE | Meaning |
213/// |---------|-----------|------|-------------------------------------|
214/// | 0 | 0 | 3227 | Location function qualifier |
215/// | 1 | 0 | 3225 | Location name code |
216#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
217#[edifact(segment = "LOC")]
218pub struct Loc {
219 /// DE 3227 — location function qualifier (e.g. `"172"` = measurement point).
220 #[edifact(element = 0)]
221 pub qualifier: String,
222 /// DE 3225 — location name code / identifier, component 0 of C517.
223 #[edifact(element = 1)]
224 pub location_id: Option<String>,
225}
226
227// ── ERC ───────────────────────────────────────────────────────────────────────
228
229/// `ERC` — Application Error Information.
230///
231/// Used in APERAK to carry application-level error codes.
232///
233/// | Element | Component | DE | Meaning |
234/// |---------|-----------|------|-------------------------------------|
235/// | 0 | 0 | 9321 | Application error code |
236#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
237#[edifact(segment = "ERC")]
238pub struct Erc {
239 /// DE 9321 — application error code (component 0 of C901).
240 #[edifact(element = 0)]
241 pub error_code: String,
242 /// DE 1131 — code list identification (component 1 of C901), if present.
243 #[edifact(element = 0, component = 1)]
244 pub code_list_id: Option<String>,
245 /// DE 3055 — responsible agency code (component 2 of C901), if present.
246 #[edifact(element = 0, component = 2)]
247 pub agency_code: Option<String>,
248}
249
250// ── FTX ───────────────────────────────────────────────────────────────────────
251
252/// `FTX` — Free Text.
253///
254/// Used for human-readable notes and, in APERAK, for error descriptions.
255///
256/// | Element | Component | DE | Meaning |
257/// |---------|-----------|------|-------------------------------------|
258/// | 0 | 0 | 4451 | Text subject qualifier |
259/// | 1 | 0 | 4453 | Text function, coded |
260/// | 3 | 0 | 4440 | Free text (first line) |
261#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
262#[edifact(segment = "FTX")]
263pub struct Ftx {
264 /// DE 4451 — text subject qualifier (e.g. `"AAI"` = general information,
265 /// `"AIM"` = APERAK error text, `"ZZZ"` = mutually defined).
266 #[edifact(element = 0)]
267 pub qualifier: String,
268 /// DE 4440 — free text, component 0 of C108 (element 3).
269 #[edifact(element = 3)]
270 pub text: Option<String>,
271}
272
273// ── QTY ───────────────────────────────────────────────────────────────────────
274
275/// `QTY` — Quantity.
276///
277/// Used in MSCONS to carry metered quantity values.
278///
279/// | Element | Component | DE | Meaning |
280/// |---------|-----------|------|-------------------------------------|
281/// | 0 | 0 | 6063 | Quantity type code qualifier |
282/// | 0 | 1 | 6060 | Quantity value |
283/// | 0 | 2 | 6411 | Measurement unit code |
284#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
285#[edifact(segment = "QTY")]
286pub struct Qty {
287 /// DE 6063 — quantity type code qualifier (e.g. `"220"` = metered quantity).
288 #[edifact(element = 0)]
289 pub qualifier: String,
290 /// DE 6060 — quantity value as string (e.g. `"1234.5"`).
291 #[edifact(element = 0, component = 1)]
292 pub value: Option<String>,
293 /// DE 6411 — measurement unit code (e.g. `"KWH"`, `"MWH"`, `"M3"`).
294 #[edifact(element = 0, component = 2)]
295 pub unit: Option<String>,
296}
297
298impl Qty {
299 /// Parse `value` as an `f64`.
300 ///
301 /// Returns `None` when the field is absent or the string is not a valid
302 /// decimal number. The EDIFACT decimal mark may be either `.` or `,`.
303 #[must_use]
304 pub fn value_f64(&self) -> Option<f64> {
305 self.value
306 .as_deref()
307 .map(|s| s.replace(',', "."))
308 .and_then(|s| s.parse::<f64>().ok())
309 }
310
311 /// Returns `true` when this is a metered quantity (`qualifier == "220"`).
312 #[must_use]
313 pub fn is_metered(&self) -> bool {
314 self.qualifier == "220"
315 }
316}
317
318// ── UCI ───────────────────────────────────────────────────────────────────────
319
320/// `UCI` — Interchange Response.
321///
322/// The mandatory segment in CONTRL messages. Carries the interchange
323/// control reference and the acknowledgement action code.
324///
325/// | Element | Component | DE | Meaning |
326/// |---------|-----------|------|--------------------------------------|
327/// | 0 | 0 | 0020 | Interchange control reference |
328/// | 1 | 0 | 0004 | Sender identification |
329/// | 2 | 0 | 0010 | Recipient identification |
330/// | 3 | 0 | 0083 | Action, coded (4/7/8) |
331#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
332#[edifact(segment = "UCI")]
333pub struct Uci {
334 /// DE 0020 — interchange control reference.
335 #[edifact(element = 0)]
336 pub interchange_ref: String,
337 /// DE 0004 — sender identification (S002 component 0).
338 #[edifact(element = 1)]
339 pub sender: Option<String>,
340 /// DE 0010 — recipient identification (S003 component 0).
341 #[edifact(element = 2)]
342 pub recipient: Option<String>,
343 /// DE 0083 — action, coded: `"4"` = acknowledged, `"7"` = rejected (group),
344 /// `"8"` = rejected (interchange).
345 #[edifact(element = 3)]
346 pub action_code: Option<String>,
347}
348
349// ── UNH ───────────────────────────────────────────────────────────────────────
350
351/// `UNH` — Message Header.
352///
353/// | Element | Component | DE | Meaning |
354/// |---------|-----------|------|--------------------------------------|
355/// | 0 | 0 | 0062 | Message reference number |
356/// | 1 | 0 | 0065 | Message type identifier |
357/// | 1 | 4 | 0057 | Association assigned code (release) |
358#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
359#[edifact(segment = "UNH")]
360pub struct Unh {
361 /// DE 0062 — message reference number.
362 #[edifact(element = 0)]
363 pub message_ref: String,
364 /// DE 0065 — message type identifier (e.g. `"UTILMD"`, `"MSCONS"`).
365 #[edifact(element = 1)]
366 pub message_type: String,
367 /// DE 0057 — association assigned code, component 4 of S009
368 /// (e.g. `"5.5.3a"`). This is the EDI@Energy release identifier.
369 #[edifact(element = 1, component = 4)]
370 pub assoc_code: Option<String>,
371}
372
373// ── UNT ───────────────────────────────────────────────────────────────────────
374
375/// `UNT` — Message Trailer.
376///
377/// | Element | Component | DE | Meaning |
378/// |---------|-----------|------|--------------------------------------|
379/// | 0 | 0 | 0074 | Number of segments in message |
380/// | 1 | 0 | 0062 | Message reference number |
381#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
382#[edifact(segment = "UNT")]
383pub struct Unt {
384 /// DE 0074 — number of segments in the message (including UNH and UNT).
385 #[edifact(element = 0)]
386 pub segment_count: String,
387 /// DE 0062 — message reference number (must match UNH element 0).
388 #[edifact(element = 1)]
389 pub message_ref: String,
390}
391
392// ── helpers ───────────────────────────────────────────────────────────────────
393
394/// Find the first `NAD` segment with the given `qualifier` in a segment slice.
395///
396/// Used by message constructors to extract sender (`"MS"`) and receiver (`"MR"`).
397pub(crate) fn find_nad(segments: &[edifact_rs::Segment<'_>], qualifier: &str) -> Option<Nad> {
398 segments
399 .iter()
400 .filter(|s| s.tag == "NAD")
401 .find(|s| s.element_str(0) == Some(qualifier))
402 .and_then(|seg| Nad::edifact_deserialize(std::slice::from_ref(seg)).ok())
403}
404
405/// Find all `DTM` segments in a segment slice.
406pub(crate) fn collect_dtm(segments: &[edifact_rs::Segment<'_>]) -> Vec<Dtm> {
407 segments
408 .iter()
409 .filter(|s| s.tag == "DTM")
410 .filter_map(|seg| Dtm::edifact_deserialize(std::slice::from_ref(seg)).ok())
411 .collect()
412}
413
414/// Find the first `BGM` segment.
415pub(crate) fn find_bgm(segments: &[edifact_rs::Segment<'_>]) -> Option<Bgm> {
416 segments
417 .iter()
418 .filter(|s| s.tag == "BGM")
419 .find_map(|seg| Bgm::edifact_deserialize(std::slice::from_ref(seg)).ok())
420}
421
422/// Find the first `UCI` segment.
423pub(crate) fn find_uci(segments: &[edifact_rs::Segment<'_>]) -> Option<Uci> {
424 segments
425 .iter()
426 .filter(|s| s.tag == "UCI")
427 .find_map(|seg| Uci::edifact_deserialize(std::slice::from_ref(seg)).ok())
428}
429
430/// Find the first `RFF` segment with the given qualifier.
431pub(crate) fn find_rff(segments: &[edifact_rs::Segment<'_>], qualifier: &str) -> Option<Rff> {
432 segments
433 .iter()
434 .filter(|s| s.tag == "RFF")
435 .find(|s| s.element_str(0) == Some(qualifier))
436 .and_then(|seg| Rff::edifact_deserialize(std::slice::from_ref(seg)).ok())
437}
438
439/// Collect all `COM` segments from a segment slice.
440///
441/// Used by `PartinMessage` to extract communication channels
442/// (AS4 endpoint, email, phone) declared by the described party.
443#[cfg(feature = "partin")]
444pub(crate) fn collect_com(segments: &[edifact_rs::Segment<'_>]) -> Vec<Com> {
445 segments
446 .iter()
447 .filter(|s| s.tag == "COM")
448 .filter_map(|seg| Com::edifact_deserialize(std::slice::from_ref(seg)).ok())
449 .collect()
450}
451
452// ── UCM ───────────────────────────────────────────────────────────────────────
453
454/// `UCM` — Message Response (CONTRL SG1).
455///
456/// Acknowledges or rejects one specific message within the interchange.
457///
458/// | Element | Component | DE | Meaning |
459/// |---------|-----------|------|--------------------------------------|
460/// | 0 | 0 | 0062 | Message reference number |
461/// | 1 | 0 | 0065 | Message type identifier |
462/// | 1 | 4 | 0057 | Association assigned code |
463/// | 2 | 0 | 0083 | Action, coded (`"4"` = acknowledged, `"7"` = rejected) |
464/// | 3 | 0 | 0085 | Syntax error code (if rejected) |
465#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
466#[edifact(segment = "UCM")]
467pub struct Ucm {
468 /// DE 0062 — message reference number (matches UNH element 0).
469 #[edifact(element = 0)]
470 pub message_ref: String,
471 /// DE 0065 — message type identifier (e.g. `"UTILMD"`).
472 #[edifact(element = 1)]
473 pub message_type: String,
474 /// DE 0057 — association assigned code (component 4 of S009).
475 #[edifact(element = 1, component = 4)]
476 pub assoc_code: Option<String>,
477 /// DE 0083 — action, coded: `"4"` = acknowledged, `"7"` = rejected.
478 #[edifact(element = 2)]
479 pub action_code: String,
480 /// DE 0085 — syntax error code (component 0 of S011), present when rejected.
481 #[edifact(element = 3)]
482 pub syntax_error: Option<String>,
483}
484
485// ── UCS ───────────────────────────────────────────────────────────────────────
486
487/// `UCS` — Segment Identification (CONTRL SG2).
488///
489/// Identifies the erroneous segment within a message.
490///
491/// | Element | Component | DE | Meaning |
492/// |---------|-----------|------|-------------------------------------|
493/// | 0 | 0 | 0096 | Segment position in message body |
494/// | 0 | 1 | 0135 | Service segment tag (if applicable) |
495/// | 1 | 0 | 0085 | Syntax error code |
496#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
497#[edifact(segment = "UCS")]
498pub struct Ucs {
499 /// DE 0096 — segment position in message body (component 0 of S011).
500 #[edifact(element = 0)]
501 pub segment_position: String,
502 /// DE 0135 — service segment tag (component 1 of S011).
503 #[edifact(element = 0, component = 1)]
504 pub segment_tag: Option<String>,
505 /// DE 0085 — syntax error, coded.
506 #[edifact(element = 1)]
507 pub error_code: Option<String>,
508}
509
510// ── UCD ───────────────────────────────────────────────────────────────────────
511
512/// `UCD` — Data Element Error Identification (CONTRL SG3).
513///
514/// | Element | Component | DE | Meaning |
515/// |---------|-----------|------|-------------------------------------|
516/// | 0 | 0 | 0085 | Syntax error code |
517/// | 1 | 0 | 0098 | Data element position |
518/// | 1 | 1 | 0104 | Component position (optional) |
519#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
520#[edifact(segment = "UCD")]
521pub struct Ucd {
522 /// DE 0085 — syntax error, coded.
523 #[edifact(element = 0)]
524 pub error_code: String,
525 /// DE 0098 — data element position (component 0 of C085).
526 #[edifact(element = 1)]
527 pub element_position: Option<String>,
528 /// DE 0104 — component data element position (component 1 of C085).
529 #[edifact(element = 1, component = 1)]
530 pub component_position: Option<String>,
531}
532
533// ── LIN ───────────────────────────────────────────────────────────────────────
534
535/// `LIN` — Line Item (MSCONS SG9).
536///
537/// Marks the start of a new metered-quantity line item.
538///
539/// | Element | Component | DE | Meaning |
540/// |---------|-----------|------|-------------------------------------|
541/// | 0 | 0 | 1082 | Line item number (sequential) |
542#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
543#[edifact(segment = "LIN")]
544pub struct Lin {
545 /// DE 1082 — line item number.
546 #[edifact(element = 0)]
547 pub line_number: Option<String>,
548}
549
550// ── PIA ───────────────────────────────────────────────────────────────────────
551
552/// `PIA` — Additional Product ID (MSCONS SG9).
553///
554/// Carries the OBIS code or metering location identifier.
555///
556/// `PIA` — Additional product ID (MSCONS SG8).
557///
558/// In MSCONS, carries the OBIS measurement identifier for a line item.
559///
560/// `PIA` — Additional Product ID (MSCONS SG9, item identification).
561///
562/// Carries the OBIS measurement identifier for the current line item.
563///
564/// | Element | Component | DE | Meaning |
565/// |---------|-----------|------|-------------------------------------|
566/// | 0 | — | 4347 | Product ID function qualifier |
567/// | 1 | 0 | 7140 | OBIS code (full, e.g. `1-0:1.8.0`) |
568/// | 1 | 1 | 7143 | Item type code (`Z12`, `SRW`, …) |
569///
570/// ## EDIFACT release characters and OBIS codes
571///
572/// OBIS codes (IEC 62056-61) use `:` as part of their notation, which is also
573/// the EDIFACT composite component separator. The BDEW MSCONS AHB (since
574/// FV2025-10-01) mandates the **EDIFACT release character `?`** to escape each
575/// `:` inside the OBIS value:
576///
577/// ```text
578/// PIA+5+1-1?:1.9.1:SRW'
579/// ^^ ^^ release-char escapes the OBIS colon
580/// ^^ unescaped colon → component separator → DE 7143 = "SRW"
581/// ```
582///
583/// `edifact-rs` correctly processes release characters: after parsing,
584/// `item_number` contains the full clean OBIS string (`"1-1:1.9.1"`)
585/// and `item_type` contains the DE 7143 qualifier (`"SRW"` or `"Z12"`).
586///
587/// **Builder note:** write the PIA composite through
588/// `Writer::write_composites` (the builders' `emit_comp!`), where the OBIS is
589/// one component and its colons are escaped structurally — never pre-join the
590/// composite with `:` and hand it to `write_raw`.
591#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
592#[edifact(segment = "PIA")]
593pub struct Pia {
594 /// DE 4347 — product ID function qualifier (e.g. `"5"` = product ID).
595 #[edifact(element = 0)]
596 pub qualifier: String,
597 /// DE 7140 — full OBIS code (e.g. `"1-0:1.8.0"`).
598 ///
599 /// This field contains the complete OBIS identifier because the BDEW AHB
600 /// requires the `:` inside OBIS values to be escaped with `?:`.
601 /// `edifact-rs` strips the release character during parsing.
602 #[edifact(element = 1)]
603 pub item_number: Option<String>,
604 /// DE 7143 — item type code (e.g. `"Z12"` for OBIS, `"SRW"` for
605 /// Strom-Richtung-Wirkleistung).
606 #[edifact(element = 1, component = 1)]
607 pub item_type: Option<String>,
608}
609
610impl Pia {
611 /// Return the OBIS code from DE 7140.
612 ///
613 /// Convenience accessor for `item_number`. Returns the full OBIS string
614 /// (e.g. `"1-0:1.8.0"`) after release-character processing by `edifact-rs`.
615 ///
616 /// # Example
617 ///
618 /// ```
619 /// # use edi_energy::messages::segments::Pia;
620 /// let pia = Pia {
621 /// qualifier: "5".to_owned(),
622 /// item_number: Some("1-0:1.8.0".to_owned()),
623 /// item_type: Some("Z12".to_owned()),
624 /// };
625 /// assert_eq!(pia.obis_code().as_deref(), Some("1-0:1.8.0"));
626 /// assert_eq!(pia.item_type.as_deref(), Some("Z12"));
627 /// ```
628 #[must_use]
629 pub fn obis_code(&self) -> Option<&str> {
630 self.item_number.as_deref()
631 }
632}
633
634// ── CCI ───────────────────────────────────────────────────────────────────────
635
636/// `CCI` — Characteristic/Class ID (MSCONS SG8).
637///
638/// In MSCONS, carries the time-series type (Zeitreihentyp).
639///
640/// | Element | Component | DE | Meaning |
641/// |---------|-----------|------|--------------------------------------|
642/// | 0 | 0 | 7059 | Property class code (optional) |
643/// | 2 | 0 | 7037 | Characteristic ID (Zeitreihentyp) |
644/// | 2 | 1 | 1131 | Code list identification |
645/// | 2 | 2 | 3055 | Responsible agency code |
646#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
647#[edifact(segment = "CCI")]
648pub struct Cci {
649 /// DE 7059 — property class code (element 0), usually empty in MSCONS.
650 #[edifact(element = 0)]
651 pub category: Option<String>,
652 /// DE 7037 — characteristic identifier (component 0 of C240, element 2).
653 /// In MSCONS: time-series type code, e.g. `"Z05"` = measured quantity.
654 #[edifact(element = 2)]
655 pub characteristic_id: Option<String>,
656 /// DE 1131 — code list identification (component 1 of C240).
657 #[edifact(element = 2, component = 1)]
658 pub code_list_id: Option<String>,
659 /// DE 3055 — responsible agency code (component 2 of C240).
660 #[edifact(element = 2, component = 2)]
661 pub agency_code: Option<String>,
662}
663
664// ── STS ───────────────────────────────────────────────────────────────────────
665
666/// `STS` — Status (MSCONS SG10).
667///
668/// Carries the quality / validation status of a quantity reading.
669///
670/// | Element | Component | DE | Meaning |
671/// |---------|-----------|------|-------------------------------------|
672/// | 0 | 0 | 9015 | Status category code |
673/// | 1 | 0 | 9011 | Status value code |
674/// | 1 | 1 | 4405 | Status value sub-qualifier |
675/// | 1 | 2 | 3055 | Agency code for status value |
676#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
677#[edifact(segment = "STS")]
678pub struct Sts {
679 /// DE 9015 — status category (component 0 of C601, e.g. `"7"` = measurement).
680 #[edifact(element = 0)]
681 pub category: Option<String>,
682 /// DE 9011 — status value code (component 0 of C555, e.g. `"Z03"` = validated).
683 #[edifact(element = 1)]
684 pub status_code: Option<String>,
685 /// DE 4405 — status value sub-qualifier (component 1 of C555).
686 ///
687 /// Present in APERAK and CONTRL `STS` segments that carry a two-level qualifier,
688 /// e.g. `STS+E10:ZF3'` where `ZF3` is the sub-qualifier.
689 #[edifact(element = 1, component = 1)]
690 pub sub_qualifier: Option<String>,
691 /// DE 3055 — agency code (component 2 of C555).
692 #[edifact(element = 1, component = 2)]
693 pub agency_code: Option<String>,
694}
695
696// ── UNS ───────────────────────────────────────────────────────────────────────
697
698/// `UNS` — Section Control (MSCONS).
699///
700/// Marks the transition from the header section to the detail section.
701///
702/// | Element | Component | DE | Meaning |
703/// |---------|-----------|------|-------------------------------------|
704/// | 0 | 0 | 0081 | Section identification |
705#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
706#[edifact(segment = "UNS")]
707pub struct Uns {
708 /// DE 0081 — section identification (`"D"` = detail section).
709 #[edifact(element = 0)]
710 pub section_id: String,
711}
712
713// ── CTA ───────────────────────────────────────────────────────────────────────
714
715/// `CTA` — Contact Information (MSCONS SG4).
716///
717/// | Element | Component | DE | Meaning |
718/// |---------|-----------|------|-------------------------------------|
719/// | 0 | 0 | 3139 | Contact function code |
720/// | 1 | 0 | 3413 | Department / employee name |
721#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
722#[edifact(segment = "CTA")]
723pub struct Cta {
724 /// DE 3139 — contact function, coded.
725 #[edifact(element = 0)]
726 pub function_code: Option<String>,
727 /// DE 3413 — department or employee name (component 0 of C056).
728 #[edifact(element = 1)]
729 pub name: Option<String>,
730}
731
732// ── COM ───────────────────────────────────────────────────────────────────────
733
734/// `COM` — Communication Contact (MSCONS SG4).
735///
736/// | Element | Component | DE | Meaning |
737/// |---------|-----------|------|-------------------------------------|
738/// | 0 | 0 | 3148 | Communication number |
739/// | 0 | 1 | 3155 | Communication channel qualifier |
740#[derive(Debug, Clone, PartialEq, Eq, EdifactDeserialize, EdifactSerialize)]
741#[edifact(segment = "COM")]
742pub struct Com {
743 /// DE 3148 — communication number (component 0 of C076).
744 #[edifact(element = 0)]
745 pub number: Option<String>,
746 /// DE 3155 — communication channel qualifier (component 1 of C076,
747 /// e.g. `"EM"` = email, `"TE"` = telephone).
748 #[edifact(element = 0, component = 1)]
749 pub channel: Option<String>,
750}
751
752// ── additional helpers ────────────────────────────────────────────────────────
753
754/// Deserialize a single segment tag from a segment slice, returning `None`
755/// on failure. Convenience wrapper used by group parsers.
756pub(crate) fn try_deserialize<T: edifact_rs::EdifactDeserialize>(
757 seg: &edifact_rs::Segment<'_>,
758) -> Option<T> {
759 T::edifact_deserialize(std::slice::from_ref(seg)).ok()
760}