powerio-tx 0.11.7

Compiler infrastructure for power systems: parse, convert, validate, and lower grid models
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
//! The codes this crate emits, and the registry gates over them.
//!
//! The record, the code grammar, the severity ladder, and the stage family live
//! in `powerio-core`. What lives here is the transmission side registry: one
//! [`DiagnosticInfo`] per code, declared once, so an emission site names an
//! entry rather than a loose string and every emitted code is registered by
//! construction.
//!
//! Codes are families, not one per site: what differs between two sites of a
//! family is which field or record it was, which belongs in `details` where a
//! consumer can read it, rather than in a code nobody can enumerate.

// The collector is crate-private implementation support, not API: each
// emitting crate carries its own copy (src/collect.rs) and never exports it.
pub(crate) use crate::collect::Diagnostics;

pub use powerio_core::{
    Diagnostic, DiagnosticCode, DiagnosticInfo, DiagnosticSeverity, DiagnosticStage, ErrorCategory,
    check_registry, code_is_well_formed, render_diagnostic, render_diagnostics,
};

use crate::format::TargetFormat;

/// The write side family every target shares.
///
/// A writer's fidelity losses are the same eleven questions for every target —
/// what was dropped, what was defaulted, what was collapsed — so the family is
/// declared once per target and the shared writer passes take the target's
/// family rather than a label they cannot turn into a code.
#[derive(Clone, Copy, Debug)]
pub struct EmitFamily {
    /// A field the target format has no column or key for.
    pub field_dropped: DiagnosticInfo,
    /// A whole element or record the target does not model.
    pub record_dropped: DiagnosticInfo,
    /// A value the target requires and the source never stated.
    pub value_defaulted: DiagnosticInfo,
    /// Richer structure reduced to what the target's one field can hold.
    pub value_collapsed: DiagnosticInfo,
    /// A stated value replaced by another the target can represent.
    pub value_substituted: DiagnosticInfo,
    /// A value shortened to the target's width, e.g. a cost curve order.
    pub value_truncated: DiagnosticInfo,
    /// A branch rating set beyond the target's rate_a/rate_b/rate_c.
    pub rating_set_dropped: DiagnosticInfo,
    /// Source format passthrough fields the writer does not replay.
    pub extras_dropped: DiagnosticInfo,
    /// The area table, which is typed rather than passthrough.
    pub areas_dropped: DiagnosticInfo,
    /// A non-finite value written as a sentinel or a JSON null.
    pub not_a_number: DiagnosticInfo,
    /// The network has no reference bus for the target's solver to key on.
    pub reference_missing: DiagnosticInfo,
    /// A normalized line lands in the target's transformer section.
    pub element_relabeled: DiagnosticInfo,
}

macro_rules! emit_family {
    ($name:ident, $scope:literal, $label:literal) => {
        /// The write side family for this target.
        pub const $name: EmitFamily = EmitFamily {
            field_dropped: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".FIELD_DROPPED"),
                DiagnosticSeverity::Warning,
                concat!("a field ", $label, " has no place for was dropped"),
            ),
            record_dropped: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".RECORD_DROPPED"),
                DiagnosticSeverity::Warning,
                concat!("an element ", $label, " does not model was dropped"),
            ),
            value_defaulted: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".VALUE_DEFAULTED"),
                DiagnosticSeverity::Warning,
                concat!("a value ", $label, " requires was synthesized"),
            ),
            value_collapsed: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".VALUE_COLLAPSED"),
                DiagnosticSeverity::Warning,
                concat!("structure reduced to what ", $label, " can carry"),
            ),
            value_substituted: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".VALUE_SUBSTITUTED"),
                DiagnosticSeverity::Warning,
                concat!("a stated value was replaced by one ", $label, " can hold"),
            ),
            value_truncated: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".VALUE_TRUNCATED"),
                DiagnosticSeverity::Warning,
                concat!("a value was shortened to the width ", $label, " carries"),
            ),
            rating_set_dropped: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".RATING_SET_DROPPED"),
                DiagnosticSeverity::Warning,
                concat!(
                    "a branch rating set beyond rate_a/rate_b/rate_c was dropped: ",
                    $label,
                    " has no field for it"
                ),
            ),
            extras_dropped: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".EXTRAS_DROPPED"),
                DiagnosticSeverity::Warning,
                concat!(
                    "source format passthrough fields the ",
                    $label,
                    " writer does not replay were dropped"
                ),
            ),
            areas_dropped: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".AREAS_DROPPED"),
                DiagnosticSeverity::Warning,
                concat!("the area table was dropped: ", $label, " emits none"),
            ),
            not_a_number: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".NOT_A_NUMBER"),
                DiagnosticSeverity::Warning,
                concat!(
                    "a non-finite value was written as the sentinel ",
                    $label,
                    " uses, because it has no Inf or NaN"
                ),
            ),
            reference_missing: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".REFERENCE_MISSING"),
                DiagnosticSeverity::Warning,
                concat!(
                    "the network has no reference bus, which ",
                    $label,
                    " consumers reject"
                ),
            ),
            element_relabeled: DiagnosticInfo::new(
                concat!("EMIT.", $scope, ".ELEMENT_RELABELED"),
                DiagnosticSeverity::Warning,
                concat!(
                    "a normalized line reads as a transformer in the ",
                    $label,
                    " layout"
                ),
            ),
        };
    };
}

impl EmitFamily {
    /// Every entry, for the registry gates and the generated reference.
    #[must_use]
    pub fn entries(&'static self) -> [&'static DiagnosticInfo; 12] {
        [
            &self.field_dropped,
            &self.record_dropped,
            &self.value_defaulted,
            &self.value_collapsed,
            &self.value_substituted,
            &self.value_truncated,
            &self.rating_set_dropped,
            &self.extras_dropped,
            &self.areas_dropped,
            &self.not_a_number,
            &self.reference_missing,
            &self.element_relabeled,
        ]
    }
}

/// One [`EmitFamily`] per write target, plus the codes a single reader or a
/// single writer owns.
pub mod codes {
    use super::{DiagnosticInfo, DiagnosticSeverity, EmitFamily};

    emit_family!(EMIT_MATPOWER, "MATPOWER", "MATPOWER .m");
    emit_family!(EMIT_PSSE, "PSSE", "PSS/E .raw");
    emit_family!(EMIT_PSLF, "PSLF", "PSLF .epc");
    emit_family!(EMIT_PANDAPOWER, "PANDAPOWER", "pandapower JSON");
    emit_family!(EMIT_PYPSA, "PYPSA", "the PyPSA CSV folder");
    emit_family!(EMIT_POWERWORLD, "POWERWORLD", "PowerWorld .aux");
    emit_family!(EMIT_POWERMODELS, "POWERMODELS", "PowerModels JSON");
    emit_family!(EMIT_EGRET, "EGRET", "egret JSON");
    emit_family!(EMIT_SURGE, "SURGE", "Surge JSON");
    emit_family!(EMIT_XIIDM, "XIIDM", "XIIDM 1.17 XML");
    emit_family!(EMIT_JIIDM, "JIIDM", "JIIDM 1.17 JSON");
    emit_family!(EMIT_CGMES, "CGMES", "CGMES 3.0");
    emit_family!(EMIT_UCTE, "UCTE", "UCTE-DEF .uct");
    emit_family!(EMIT_UNSUPPORTED, "UNSUPPORTED", "a read only format");

    powerio_core::diagnostic_codes! {
        REQUEST_SINCAL_PROFILE_REQUIRED = "REQUEST.SINCAL.PROFILE_REQUIRED", Error,
            "SINCAL requires an explicit electrical profile", category = Request;
        REQUEST_SINCAL_FEATURE_DISABLED = "REQUEST.SINCAL.FEATURE_DISABLED", Error,
            "SINCAL is not available through this build or entry point", category = Request;
        PARSE_SINCAL_MALFORMED = "PARSE.SINCAL.MALFORMED", Error,
            "a required SINCAL record or electrical mode is invalid or unsupported", category = Parse;
        READ_SINCAL_CONVERSION_BASE = "READ.SINCAL.CONVERSION_BASE", Remark,
            "physical SINCAL quantities use a declared internal MVA conversion base";
        READ_SINCAL_GEOMETRY = "READ.SINCAL.GEOMETRY", Warning,
            "optional SINCAL drawing geometry retains native view metadata and reports mapping limits";
        READ_SINCAL_VALUE_DEFAULTED = "READ.SINCAL.VALUE_DEFAULTED", Warning,
            "a versioned SINCAL field default was applied";
        READ_SINCAL_LIMITS_UNSPECIFIED = "READ.SINCAL.LIMITS_UNSPECIFIED", Remark,
            "inactive native generator capability limits are represented as unbounded";
        READ_SINCAL_RETAINED_SOURCE_ONLY = "READ.SINCAL.RETAINED_SOURCE_ONLY", Remark,
            "native data outside the selected steady-state profile remains in retained source";
        EMIT_SINCAL_RETAINED_SOURCE_OMITTED = "EMIT.SINCAL.RETAINED_SOURCE_OMITTED", Warning,
            "native data retained only in SINCAL source is not included in cross-format output";
        EMIT_SINCAL_EXPERIMENTAL_UNSUPPORTED = "EMIT.SINCAL.EXPERIMENTAL_UNSUPPORTED", Error,
            "invalid or unsupported candidate SINCAL output", category = Output;
        EMIT_SINCAL_EXPERIMENTAL_LOSS = "EMIT.SINCAL.EXPERIMENTAL_LOSS", Warning,
            "candidate SINCAL output omits data outside its declared profile";
        EMIT_SINCAL_EXPERIMENTAL = "EMIT.SINCAL.EXPERIMENTAL", Remark,
            "candidate SINCAL output has not been validated in native SINCAL";
        EMIT_SINCAL_FRESH_UNSUPPORTED = "EMIT.SINCAL.FRESH_UNSUPPORTED", Error,
            "fresh SINCAL emission is not available through the universal emitter", category = Output;
        // PARSE: the source text could not be decoded as given.
        PARSE_MATPOWER_MALFORMED = "PARSE.MATPOWER.MALFORMED", Error,
            "a MATPOWER matrix is missing, short, unparseable, or unbalanced", category = Parse;
        READ_MATPOWER_INDEXED_ASSIGNMENT = "READ.MATPOWER.INDEXED_ASSIGNMENT", Warning,
            "a MATPOWER indexed assignment is not evaluated by the literal reader";
        PARSE_SOURCE_MALFORMED = "PARSE.SOURCE.MALFORMED", Error,
            "a format reader refused the source it was given", category = Parse;
        PARSE_GOC3_MALFORMED = "PARSE.GOC3.MALFORMED", Error,
            "a GO Challenge 3 data file is not well formed JSON", category = Parse;
        PARSE_XIIDM_VERSION_UNSUPPORTED = "PARSE.XIIDM.VERSION_UNSUPPORTED", Error,
            "the XIIDM namespace names a version PowerIO has not tested", category = Parse;

        // READ: decoded, but not representable in the canonical model.
        READ_PSSE_FIELD_DROPPED = "READ.PSSE.FIELD_DROPPED", Warning,
            "a PSS/E field with no canonical home was dropped";
        READ_PSSE_VALUE_SUBSTITUTED = "READ.PSSE.VALUE_SUBSTITUTED", Warning,
            "a PSS/E value the record states could not be used as given";
        READ_PSSE_VALUE_UNSUPPORTED = "READ.PSSE.VALUE_UNSUPPORTED", Warning,
            "a PSS/E code word (CZ, CW, CM) outside the modeled set was read as the default";
        READ_PSSE_REFERENCE_DROPPED = "READ.PSSE.REFERENCE_DROPPED", Warning,
            "a PSS/E control pointer names a bus the case does not declare";
        READ_PSSE_SECTION_UNSUPPORTED = "READ.PSSE.SECTION_UNSUPPORTED", Warning,
            "a PSS/E section is preserved in a same-format echo only";
        READ_PSSE_RETAINED_SOURCE_ONLY = "READ.PSSE.RETAINED_SOURCE_ONLY", Remark,
            "a PSS/E field survives in extras rather than in a typed field";
        READ_PSSE_VALUE_DEFAULTED = "READ.PSSE.VALUE_DEFAULTED", Warning,
            "a PSS/E record ends before a field the typed model reads, so that field took its default";

        READ_PSLF_VALUE_DEFAULTED = "READ.PSLF.VALUE_DEFAULTED", Warning,
            "a PSLF value the model needs was not in the source and was defaulted";
        READ_PSLF_VALUE_APPROXIMATED = "READ.PSLF.VALUE_APPROXIMATED", Warning,
            "a PSLF value was read through an approximation the .epc model forces";
        READ_PSLF_RECORD_DROPPED = "READ.PSLF.RECORD_DROPPED", Warning,
            "a PSLF record could not be mapped and was dropped";
        READ_PSLF_SOURCE_MALFORMED = "READ.PSLF.SOURCE_MALFORMED", Warning,
            "a PSLF section header, count, or end marker disagrees with the records";
        READ_PSLF_RETAINED_SOURCE_ONLY = "READ.PSLF.RETAINED_SOURCE_ONLY", Remark,
            "a PSLF section survives in the retained source or in extras only";

        READ_PANDAPOWER_FIELD_DROPPED = "READ.PANDAPOWER.FIELD_DROPPED", Warning,
            "a pandapower field with no canonical home was dropped";
        READ_CGMES_RECORD_UNMAPPED = "READ.CGMES.RECORD_UNMAPPED", Warning,
            "a CGMES record has no representation in the balanced network model";
        READ_CGMES_FIELD_UNMAPPED = "READ.CGMES.FIELD_UNMAPPED", Warning,
            "a field on a mapped CGMES record has no representation in the balanced network model";
        READ_CGMES_VALUE_DEFAULTED = "READ.CGMES.VALUE_DEFAULTED", Warning,
            "a value absent from the CGMES profile set was defaulted";
        READ_CGMES_VALUE_APPROXIMATED = "READ.CGMES.VALUE_APPROXIMATED", Warning,
            "a CGMES value was represented through an explicit approximation";
        READ_CGMES_TOPOLOGY_CALCULATED = "READ.CGMES.TOPOLOGY_CALCULATED", Remark,
            "the set carries no TopologicalNode data, so buses were calculated from ConnectivityNodes and switch positions";
        READ_CGMES_CONNECTIVITY_INSUFFICIENT = "READ.CGMES.CONNECTIVITY_INSUFFICIENT", Error,
            "the set carries neither TopologicalNode data nor enough connectivity to calculate buses",
            category = Parse;
        READ_PANDAPOWER_VALUE_INFERRED = "READ.PANDAPOWER.VALUE_INFERRED", Warning,
            "a value pandapower does not store was reconstructed on a declared convention";
        READ_PANDAPOWER_TABLE_UNSUPPORTED = "READ.PANDAPOWER.TABLE_UNSUPPORTED", Warning,
            "a pandapower table is not mapped into the canonical model";

        READ_PYPSA_TABLE_UNSUPPORTED = "READ.PYPSA.TABLE_UNSUPPORTED", Warning,
            "a PyPSA table is not mapped into the canonical model";
        READ_PYPSA_VALUE_APPROXIMATED = "READ.PYPSA.VALUE_APPROXIMATED", Warning,
            "a PyPSA element was read through the nearest canonical element";
        READ_PYPSA_NAME_REMAPPED = "READ.PYPSA.NAME_REMAPPED", Warning,
            "a PyPSA bus name collides with another and was keyed by its numeric id";

        READ_POWERWORLD_VALUE_DEFAULTED = "READ.POWERWORLD.VALUE_DEFAULTED", Warning,
            "a PowerWorld field this binary vintage does not locate was defaulted";
        READ_POWERWORLD_RETAINED_SOURCE_ONLY = "READ.POWERWORLD.RETAINED_SOURCE_ONLY", Warning,
            "a PowerWorld aux data block survives in the retained source only";

        READ_POWERMODELS_RECORD_DROPPED = "READ.POWERMODELS.RECORD_DROPPED", Warning,
            "a PowerModels document states more than the canonical snapshot holds";
        READ_POWERMODELS_FIELD_DROPPED = "READ.POWERMODELS.FIELD_DROPPED", Warning,
            "a PowerModels field the canonical model cannot state was dropped";

        READ_GOC3_AMBIGUOUS_DOCUMENTS = "READ.GOC3.AMBIGUOUS_DOCUMENTS", Error,
            "a GO Challenge 3 source contains more than one problem or solution data file",
            category = Parse;
        READ_GOC3_PROBLEM_REQUIRED = "READ.GOC3.PROBLEM_REQUIRED", Error,
            "a GO Challenge 3 solution data file requires its matching problem data file",
            category = Parse;
        READ_GOC3_SOURCE_UNRECOGNIZED = "READ.GOC3.SOURCE_UNRECOGNIZED", Error,
            "a declared GO Challenge 3 source contains no problem or solution data file",
            category = Parse;
        READ_GOC3_INVALID_DOCUMENT = "READ.GOC3.INVALID_DOCUMENT", Error,
            "the GO Challenge 3 document decodes but is not a valid problem or solution file",
            category = Parse;
        READ_GOC3_VALUE_INFERRED = "READ.GOC3.VALUE_INFERRED", Warning,
            "a GO Challenge 3 value the document never states was inferred";
        READ_GOC3_OPTIONAL_FIELD_UNTYPED = "READ.GOC3.OPTIONAL_FIELD_UNTYPED", Remark,
            "an optional GO Challenge 3 field is retained as untyped source metadata";
        READ_GOC3_RETAINED_SOURCE_ONLY = "READ.GOC3.RETAINED_SOURCE_ONLY", Warning,
            "a GO Challenge 3 section survives in the retained source only";

        READ_OPFDATA_FIELD_DROPPED = "READ.OPFDATA.FIELD_DROPPED", Warning,
            "an OPFData field outside the published schema is not in the snapshot";
        READ_OPFDATA_VALUE_INFERRED = "READ.OPFDATA.VALUE_INFERRED", Warning,
            "OPFData carries no identity or frequency, so the reader synthesized them";
        READ_OPFDATA_RETAINED_SOURCE_ONLY = "READ.OPFDATA.RETAINED_SOURCE_ONLY", Warning,
            "an OPFData generator's solver initial values are carried in the parsed solution instead of the network snapshot";

        READ_XIIDM_FIELD_UNMAPPED = "READ.XIIDM.FIELD_UNMAPPED", Warning,
            "an XIIDM field is not represented in the PowerIO model";
        READ_XIIDM_ELEMENT_UNMAPPED = "READ.XIIDM.ELEMENT_UNMAPPED", Warning,
            "an XIIDM element is not represented in the PowerIO model";
        READ_XIIDM_CALCULATION_VIEW = "READ.XIIDM.CALCULATION_VIEW", Warning,
            "an XIIDM value is retained in detailed connectivity but represented differently in the balanced calculation view";
        READ_XIIDM_VALUE_DEFAULTED = "READ.XIIDM.VALUE_DEFAULTED", Warning,
            "a PowerIO value absent from XIIDM was assigned a documented default";
        READ_XIIDM_VERSION_COMPATIBILITY = "READ.XIIDM.VERSION_COMPATIBILITY", Remark,
            "an older XIIDM input version was read; fresh XIIDM output uses 1.17";

        READ_SURGE_RETAINED_SOURCE_ONLY = "READ.SURGE.RETAINED_SOURCE_ONLY", Warning,
            "a Surge section survives in the retained source only";

        READ_UCTE_VALUE_DEFAULTED = "READ.UCTE.VALUE_DEFAULTED", Warning,
            "a UCTE-DEF value the model needs was not in the record and was defaulted";
        READ_UCTE_VALUE_SUBSTITUTED = "READ.UCTE.VALUE_SUBSTITUTED", Warning,
            "a UCTE-DEF value the record states could not be used as given";
        READ_UCTE_REFERENCE_DROPPED = "READ.UCTE.REFERENCE_DROPPED", Warning,
            "a UCTE-DEF regulation or special description names a transformer the case does not declare";
        READ_UCTE_RECORD_IGNORED = "READ.UCTE.RECORD_IGNORED", Warning,
            "a UCTE-DEF record names no usable electrical element and was ignored";
        READ_UCTE_RETAINED_SOURCE_ONLY = "READ.UCTE.RETAINED_SOURCE_ONLY", Warning,
            "a UCTE-DEF block survives in the retained source only";

        PARSE_IEEE_CDF_MALFORMED = "PARSE.IEEE_CDF.MALFORMED", Error,
            "an IEEE CDF title card or record could not be decoded", category = Parse;
        READ_IEEE_CDF_RECORD_TRUNCATED = "READ.IEEE_CDF.RECORD_TRUNCATED", Warning,
            "an IEEE CDF record ends before a mandatory field, which was read as zero";
        READ_IEEE_CDF_VALUE_DEFAULTED = "READ.IEEE_CDF.VALUE_DEFAULTED", Warning,
            "a value the balanced model needs is absent from the IEEE CDF and was defaulted";
        READ_IEEE_CDF_VALUE_SUBSTITUTED = "READ.IEEE_CDF.VALUE_SUBSTITUTED", Warning,
            "an IEEE CDF type or side code outside the documented set was read as the nearest documented value";
        READ_IEEE_CDF_SOURCE_MALFORMED = "READ.IEEE_CDF.SOURCE_MALFORMED", Warning,
            "an IEEE CDF section header, item count, terminator, record placement, or bus reference disagrees with the records";
        READ_IEEE_CDF_RETAINED_SOURCE_ONLY = "READ.IEEE_CDF.RETAINED_SOURCE_ONLY", Remark,
            "an IEEE CDF field or section survives in the retained source only";

        READ_CON_STATEMENT_UNRECOGNIZED = "READ.CON.STATEMENT_UNRECOGNIZED", Warning,
            "a PSS/E contingency statement outside the grammar was kept as text";
        READ_CON_TEXT_AFTER_END = "READ.CON.TEXT_AFTER_END", Warning,
            "a PSS/E contingency file states text after its file END";
        READ_CON_NOTES_TRUNCATED = "READ.CON.NOTES_TRUNCATED", Warning,
            "the PSS/E contingency reader stopped recording notes at its budget";
        READ_CON_SOURCE_MALFORMED = "READ.CON.SOURCE_MALFORMED", Warning,
            "a PSS/E contingency line could not be read as the statement its block requires";
        READ_CON_NOT_TEXT = "READ.CON.NOT_TEXT", Error,
            "a PSS/E contingency description file is not valid UTF-8 text", category = Parse;

        READ_SUB_STATEMENT_UNRECOGNIZED = "READ.SUB.STATEMENT_UNRECOGNIZED", Warning,
            "a PSS/E subsystem statement outside the grammar was kept as its original line";
        READ_SUB_TEXT_AFTER_END = "READ.SUB.TEXT_AFTER_END", Warning,
            "a PSS/E subsystem description file states text after its file END";
        READ_SUB_NOTES_TRUNCATED = "READ.SUB.NOTES_TRUNCATED", Warning,
            "the PSS/E subsystem reader stopped recording notes at its budget";
        READ_SUB_SOURCE_MALFORMED = "READ.SUB.SOURCE_MALFORMED", Warning,
            "a PSS/E subsystem selector states a keyword whose values are not the numbers it needs";
        READ_SUB_NOT_TEXT = "READ.SUB.NOT_TEXT", Error,
            "a PSS/E subsystem description file is not valid UTF-8 text", category = Parse;

        READ_MON_STATEMENT_UNRECOGNIZED = "READ.MON.STATEMENT_UNRECOGNIZED", Warning,
            "a PSS/E monitored element statement outside the grammar was kept as its original line";
        READ_MON_TEXT_AFTER_END = "READ.MON.TEXT_AFTER_END", Warning,
            "a PSS/E monitored element file states text after its file END";
        READ_MON_NOTES_TRUNCATED = "READ.MON.NOTES_TRUNCATED", Warning,
            "the PSS/E monitored element reader stopped recording notes at its budget";
        READ_MON_SOURCE_MALFORMED = "READ.MON.SOURCE_MALFORMED", Warning,
            "a line inside a PSS/E monitored element block states no branch";
        READ_MON_VOLTAGE_BAND_ORDER_ASSUMED = "READ.MON.VOLTAGE_BAND_ORDER_ASSUMED", Remark,
            "a four-value PSS/E monitored voltage range was read as the post-contingency band, then the pre-contingency band, an order no public source states";
        READ_MON_NOT_TEXT = "READ.MON.NOT_TEXT", Error,
            "a PSS/E monitored element file is not valid UTF-8 text", category = Parse;

        READ_GEO_SOURCE_MALFORMED = "READ.GEO.SOURCE_MALFORMED", Warning,
            "a geo layer row could not be read and was skipped";
        READ_GEO_NOTES_TRUNCATED = "READ.GEO.NOTES_TRUNCATED", Warning,
            "the geo reader stopped recording notes at its budget";
        READ_GEO_NOT_TEXT = "READ.GEO.NOT_TEXT", Error,
            "a geographic layer document is not valid UTF-8 text", category = Parse;

        READ_IO_FAILED = "READ.IO.FAILED", Error,
            "the case file could not be read", category = Io;

        // CANONICALIZE: normalization of an already-read network.
        CANONICALIZE_NORMALIZE_BOUNDS_CLAMPED = "CANONICALIZE.NORMALIZE.BOUNDS_CLAMPED", Remark,
            "a branch angle difference bound was clamped into the modeled range";
        CANONICALIZE_NORMALIZE_NO_REFERENCE_BUS = "CANONICALIZE.NORMALIZE.NO_REFERENCE_BUS", Error,
            "no reference bus can be established: no bus hosts an in-service generator",
            category = Data;
        CANONICALIZE_NORMALIZE_REFERENCE_DESIGNATED =
            "CANONICALIZE.NORMALIZE.REFERENCE_DESIGNATED", Warning,
            "the case states no surviving reference bus, so normalization designated a slack";
        CANONICALIZE_NORMALIZE_GEN_COST_ABSENT =
            "CANONICALIZE.NORMALIZE.GEN_COST_ABSENT", Warning,
            "the solver-ready copy has in-service generators and no cost data, so any cost objective built from it is zero";
        CANONICALIZE_NORMALIZE_INVALID_OPTION = "CANONICALIZE.NORMALIZE.INVALID_OPTION", Error,
            "a normalize option is outside the range it is defined on", category = Data;
        CANONICALIZE_NORMALIZE_INVALID_BASE_MVA = "CANONICALIZE.NORMALIZE.INVALID_BASE_MVA", Error,
            "the case base MVA is not a positive finite number", category = Data;
        CANONICALIZE_NORMALIZE_GENERATOR_BUS_RETYPED =
            "CANONICALIZE.NORMALIZE.GENERATOR_BUS_RETYPED", Warning,
            "PQ buses hosting an in-service generator were retyped PV, so the generator holds the bus voltage";

        // CANONICALIZE.ISLAND: one reference bus per island.
        CANONICALIZE_ISLAND_REFERENCE_DESIGNATED =
            "CANONICALIZE.ISLAND.REFERENCE_DESIGNATED", Warning,
            "an island stated no reference bus, so its largest generator's bus was designated";
        CANONICALIZE_ISLAND_REFERENCE_DEMOTED = "CANONICALIZE.ISLAND.REFERENCE_DEMOTED", Warning,
            "an island stated several reference buses; one was kept and the others demoted";
        CANONICALIZE_ISLAND_DE_ENERGIZED = "CANONICALIZE.ISLAND.DE_ENERGIZED", Warning,
            "an island no in-service generator supplies was de-energized";
        CANONICALIZE_ISLAND_HVDC_FED_DE_ENERGIZED =
            "CANONICALIZE.ISLAND.HVDC_FED_DE_ENERGIZED", Warning,
            "an island fed only by HVDC, with no in-service generator, was de-energized with its HVDC lines";

        // CANONICALIZE.MERGE: the explicit bus merge.
        CANONICALIZE_MERGE_ZERO_IMPEDANCE = "CANONICALIZE.MERGE.ZERO_IMPEDANCE", Warning,
            "zero impedance branches were merged and their flows are no longer network variables";
        CANONICALIZE_MERGE_CLOSED_SWITCH = "CANONICALIZE.MERGE.CLOSED_SWITCH", Warning,
            "closed switches were merged and their flows are no longer network variables";
        CANONICALIZE_MERGE_ATTRIBUTE_CONFLICT = "CANONICALIZE.MERGE.ATTRIBUTE_CONFLICT", Warning,
            "merged buses stated different attributes; the surviving bus's values were kept";
        CANONICALIZE_MERGE_ELEMENT_SHORTED = "CANONICALIZE.MERGE.ELEMENT_SHORTED", Warning,
            "an element the rule did not select had both ends merged into one bus";
        CANONICALIZE_MERGE_WINDING_PAIR_KEPT = "CANONICALIZE.MERGE.WINDING_PAIR_KEPT", Warning,
            "an element was not merged because it would join two windings of one three winding transformer";
        CANONICALIZE_MERGE_DETAIL_DROPPED = "CANONICALIZE.MERGE.DETAIL_DROPPED", Warning,
            "the detailed connectivity's calculated bus assignments were dropped because the merge joined buses its hierarchy keeps apart";
        CANONICALIZE_MERGE_FLOW_UNDETERMINED = "CANONICALIZE.MERGE.FLOW_UNDETERMINED", Warning,
            "a removed element's recovered flow is not fixed by the network, so a minimum norm split is reported";
        CANONICALIZE_MERGE_FLOW_RESIDUAL = "CANONICALIZE.MERGE.FLOW_RESIDUAL", Warning,
            "a merged group's injections from the solution do not balance its stated devices";
        CANONICALIZE_MERGE_INVALID_RULE = "CANONICALIZE.MERGE.INVALID_RULE", Error,
            "a bus merge rule cannot be applied to this network", category = Data;
        CANONICALIZE_MERGE_FLOW_SHAPE_MISMATCH = "CANONICALIZE.MERGE.FLOW_SHAPE_MISMATCH", Error,
            "a merged network solution does not match the merge's tables", category = Data;
        CANONICALIZE_NORMALIZE_WINDING_TAKEN_OUT =
            "CANONICALIZE.NORMALIZE.WINDING_TAKEN_OUT", Warning,
            "a three winding transformer winding on a bus normalization drops was taken out of service";

        // BUILD: assembling a derived object from a network that already parsed.
        BUILD_INDEX_UNKNOWN_BUS = "BUILD.INDEX.UNKNOWN_BUS", Error,
            "an element references a bus id the case does not declare", category = Data;
        BUILD_INDEX_REFERENCE_BUS_COUNT = "BUILD.INDEX.REFERENCE_BUS_COUNT", Error,
            "the index needs exactly one reference bus", category = Data;
        BUILD_INDEX_UNGROUNDED_COMPONENT = "BUILD.INDEX.UNGROUNDED_COMPONENT", Error,
            "a connected component has no reference bus to ground", category = Data;
        BUILD_SWITCH_CLOSED = "BUILD.SWITCH.CLOSED", Error,
            "a closed switch joins two buses that a calculation which models no switches would leave apart",
            category = Data;
        BUILD_BRANCH_ZERO_IMPEDANCE = "BUILD.BRANCH.ZERO_IMPEDANCE", Error,
            "a branch has a zero matrix denominator under the selected build options",
            category = Data;
        BUILD_BRANCH_NOT_A_NUMBER = "BUILD.BRANCH.NOT_A_NUMBER", Error,
            "a branch susceptance is not finite", category = Data;
        BUILD_BRANCH_DEGENERATE_TAP = "BUILD.BRANCH.DEGENERATE_TAP", Error,
            "a branch tap ratio is too small to divide by", category = Data;
        BUILD_GEO_UNLOCATED_ELEMENTS = "BUILD.GEO.UNLOCATED_ELEMENTS", Error,
            "a geo apply left elements with no location or route", category = Data;
        BUILD_GEO_APPLY_SUMMARY = "BUILD.GEO.APPLY_SUMMARY", Remark,
            "how many elements a geo apply located";
        BUILD_GEO_UNMATCHED_FEATURE = "BUILD.GEO.UNMATCHED_FEATURE", Warning,
            "a geo feature matched no element in the network";
        BUILD_CON_CASE_UNRESOLVED = "BUILD.CON.CASE_UNRESOLVED", Warning,
            "an action of a contingency case bound to no element, or named more than one where one was required";
        BUILD_CON_NOTES_TRUNCATED = "BUILD.CON.NOTES_TRUNCATED", Warning,
            "the contingency resolver stopped recording notes at its budget";
        BUILD_CON_SUBSYSTEM_UNKNOWN = "BUILD.CON.SUBSYSTEM_UNKNOWN", Warning,
            "an automatic contingency specification names a subsystem the subsystem set does not state";
        BUILD_CON_SPECIFICATION_EMPTY = "BUILD.CON.SPECIFICATION_EMPTY", Warning,
            "an automatic contingency specification names fewer in-service elements of its target family than its order needs";
        BUILD_MON_STATEMENT_UNRESOLVED = "BUILD.MON.STATEMENT_UNRESOLVED", Warning,
            "a monitored element statement did not bind to exactly one element, or names a subsystem the subsystem set does not state";
        BUILD_HVDC_FIXED_INJECTION = "BUILD.HVDC.FIXED_INJECTION", Remark,
            "in service HVDC lines enter the bus balance as fixed injections at their stated terminal powers";
        BUILD_HVDC_IGNORED = "BUILD.HVDC.IGNORED", Warning,
            "in service HVDC lines were left out of the bus balance by request";
        BUILD_HVDC_TERMINAL_INACTIVE = "BUILD.HVDC.TERMINAL_INACTIVE", Warning,
            "an in service HVDC line names an isolated or undeclared terminal bus and injects nothing";

        // VALIDATE: the case's own internal consistency.
        /// Emitted by the stored document's payload validation in the facade;
        /// declared here because this crate owns the balanced model.
        VALIDATE_BALANCED_STRUCTURE = "VALIDATE.BALANCED.STRUCTURE", Error,
            "a balanced payload's referential integrity does not hold";
        VALIDATE_BALANCED_VALUE_DOMAIN = "VALIDATE.BALANCED.VALUE_DOMAIN", Warning,
            "a balanced payload value is outside the domain the model states";
        VALIDATE_BALANCED_PAYLOAD_IDENTITY = "VALIDATE.BALANCED.PAYLOAD_IDENTITY", Error,
            "a balanced payload's uid identity does not hold";
        VALIDATE_GEN_COST_MISSING = "VALIDATE.GEN_COST.MISSING", Error,
            "a generator carries no cost data under a policy that requires one", category = Data;
        VALIDATE_GEN_COST_NOT_A_NUMBER = "VALIDATE.GEN_COST.NOT_A_NUMBER", Error,
            "a default generator cost field is not finite", category = Data;
        VALIDATE_GEN_COST_PATCH_INVALID = "VALIDATE.GEN_COST.PATCH_INVALID", Error,
            "a generator cost patch row is not usable", category = Data;
        VALIDATE_GEN_COST_COUNT_MISMATCH = "VALIDATE.GEN_COST.COUNT_MISMATCH", Error,
            "the cost table has neither one row per generator nor two", category = Data;
        VALIDATE_DC_LINE_COST_COUNT_MISMATCH = "VALIDATE.DC_LINE_COST.COUNT_MISMATCH", Error,
            "the dcline cost table has other than one row per dcline", category = Data;

        // LOWER: a policy applied on the way into a target.
        TRANSFORM_GEN_COST_POLICY_APPLIED = "TRANSFORM.GEN_COST.POLICY_APPLIED", Remark,
            "a write time generator cost policy patched or synthesized costs";

        // REQUEST: the call named something powerio does not provide.
        REQUEST_FORMAT_UNKNOWN = "REQUEST.FORMAT.UNKNOWN", Error,
            "the named case format is not one powerio reads", category = Request;
        REQUEST_FORMAT_WRITE_UNSUPPORTED = "REQUEST.FORMAT.WRITE_UNSUPPORTED", Error,
            "the named case format is read only and has no writer", category = Request;

        EMIT_FORMAT_REQUIRED_VALUE_MISSING = "EMIT.FORMAT.REQUIRED_VALUE_MISSING", Error,
            "the requested format requires a value the module does not contain", category = Output;

        // Write side codes a single target owns.
        /// The default `.raw` target is revision 33, so writing a newer source
        /// through it re-emits the older layout.
        EMIT_PSSE_DOWNGRADED = "EMIT.PSSE.DOWNGRADED", Warning,
            "a newer PSS/E revision was written into an older layout";
        EMIT_PSSE_RATING_SET_REMAPPED = "EMIT.PSSE.RATING_SET_REMAPPED", Remark,
            "a named branch rating set was written into a PSS/E numbered rating slot";
    }

    /// Every write target's family, in the order [`super::registry`] reports
    /// them.
    pub const EMIT_FAMILIES: [&EmitFamily; 14] = [
        &EMIT_MATPOWER,
        &EMIT_PSSE,
        &EMIT_PSLF,
        &EMIT_PANDAPOWER,
        &EMIT_PYPSA,
        &EMIT_POWERWORLD,
        &EMIT_POWERMODELS,
        &EMIT_EGRET,
        &EMIT_SURGE,
        &EMIT_XIIDM,
        &EMIT_JIIDM,
        &EMIT_CGMES,
        &EMIT_UCTE,
        &EMIT_UNSUPPORTED,
    ];
}

/// Every code this crate declares.
#[must_use]
pub fn registry() -> Vec<&'static DiagnosticInfo> {
    let mut all: Vec<&'static DiagnosticInfo> = codes::ALL.to_vec();
    for family in codes::EMIT_FAMILIES {
        all.extend(family.entries());
    }
    all
}

impl TargetFormat {
    /// The write side family for this target.
    #[must_use]
    pub fn emit_family(self) -> &'static EmitFamily {
        match self {
            TargetFormat::Matpower => &codes::EMIT_MATPOWER,
            TargetFormat::Psse { .. } | TargetFormat::PsseRawx => &codes::EMIT_PSSE,
            TargetFormat::Pslf => &codes::EMIT_PSLF,
            TargetFormat::PandapowerJson => &codes::EMIT_PANDAPOWER,
            TargetFormat::PowerWorld => &codes::EMIT_POWERWORLD,
            TargetFormat::PowerModelsJson => &codes::EMIT_POWERMODELS,
            TargetFormat::EgretJson => &codes::EMIT_EGRET,
            TargetFormat::SurgeJson => &codes::EMIT_SURGE,
            TargetFormat::Xiidm => &codes::EMIT_XIIDM,
            TargetFormat::Jiidm => &codes::EMIT_JIIDM,
            TargetFormat::Cgmes => &codes::EMIT_CGMES,
            TargetFormat::Ucte => &codes::EMIT_UCTE,
            // This transmission layer has no GOC3 problem or OPFData writer.
            // The facade emits a complete GOC3 solution before reaching this
            // branch. Other requests are refused before any family is
            // consulted, and `REQUEST.FORMAT.WRITE_UNSUPPORTED` carries it.
            TargetFormat::Goc3Json | TargetFormat::DeepMindOpfDataJson => &codes::EMIT_UNSUPPORTED,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_registry_is_sound() {
        let problems = check_registry(registry());
        assert!(problems.is_empty(), "{problems:#?}");
    }

    #[test]
    fn every_write_target_has_a_family_of_its_own() {
        let mut scopes: Vec<&str> = codes::EMIT_FAMILIES
            .iter()
            .map(|f| f.field_dropped.code.split('.').nth(1).unwrap())
            .collect();
        scopes.sort_unstable();
        scopes.dedup();
        assert_eq!(scopes.len(), codes::EMIT_FAMILIES.len());
        assert_eq!(
            TargetFormat::Psse { rev: 33 }
                .emit_family()
                .field_dropped
                .code,
            "EMIT.PSSE.FIELD_DROPPED"
        );
    }
}