1pub(crate) use crate::collect::Diagnostics;
16
17pub use powerio_core::{
18 Diagnostic, DiagnosticCode, DiagnosticInfo, DiagnosticSeverity, DiagnosticStage, ErrorCategory,
19 check_registry, code_is_well_formed, render_diagnostic, render_diagnostics,
20};
21
22use crate::format::TargetFormat;
23
24#[derive(Clone, Copy, Debug)]
31pub struct EmitFamily {
32 pub field_dropped: DiagnosticInfo,
34 pub record_dropped: DiagnosticInfo,
36 pub value_defaulted: DiagnosticInfo,
38 pub value_collapsed: DiagnosticInfo,
40 pub value_substituted: DiagnosticInfo,
42 pub value_truncated: DiagnosticInfo,
44 pub rating_set_dropped: DiagnosticInfo,
46 pub extras_dropped: DiagnosticInfo,
48 pub areas_dropped: DiagnosticInfo,
50 pub not_a_number: DiagnosticInfo,
52 pub reference_missing: DiagnosticInfo,
54 pub element_relabeled: DiagnosticInfo,
56}
57
58macro_rules! emit_family {
59 ($name:ident, $scope:literal, $label:literal) => {
60 pub const $name: EmitFamily = EmitFamily {
62 field_dropped: DiagnosticInfo::new(
63 concat!("EMIT.", $scope, ".FIELD_DROPPED"),
64 DiagnosticSeverity::Warning,
65 concat!("a field ", $label, " has no place for was dropped"),
66 ),
67 record_dropped: DiagnosticInfo::new(
68 concat!("EMIT.", $scope, ".RECORD_DROPPED"),
69 DiagnosticSeverity::Warning,
70 concat!("an element ", $label, " does not model was dropped"),
71 ),
72 value_defaulted: DiagnosticInfo::new(
73 concat!("EMIT.", $scope, ".VALUE_DEFAULTED"),
74 DiagnosticSeverity::Warning,
75 concat!("a value ", $label, " requires was synthesized"),
76 ),
77 value_collapsed: DiagnosticInfo::new(
78 concat!("EMIT.", $scope, ".VALUE_COLLAPSED"),
79 DiagnosticSeverity::Warning,
80 concat!("structure reduced to what ", $label, " can carry"),
81 ),
82 value_substituted: DiagnosticInfo::new(
83 concat!("EMIT.", $scope, ".VALUE_SUBSTITUTED"),
84 DiagnosticSeverity::Warning,
85 concat!("a stated value was replaced by one ", $label, " can hold"),
86 ),
87 value_truncated: DiagnosticInfo::new(
88 concat!("EMIT.", $scope, ".VALUE_TRUNCATED"),
89 DiagnosticSeverity::Warning,
90 concat!("a value was shortened to the width ", $label, " carries"),
91 ),
92 rating_set_dropped: DiagnosticInfo::new(
93 concat!("EMIT.", $scope, ".RATING_SET_DROPPED"),
94 DiagnosticSeverity::Warning,
95 concat!(
96 "a branch rating set beyond rate_a/rate_b/rate_c was dropped: ",
97 $label,
98 " has no field for it"
99 ),
100 ),
101 extras_dropped: DiagnosticInfo::new(
102 concat!("EMIT.", $scope, ".EXTRAS_DROPPED"),
103 DiagnosticSeverity::Warning,
104 concat!(
105 "source format passthrough fields the ",
106 $label,
107 " writer does not replay were dropped"
108 ),
109 ),
110 areas_dropped: DiagnosticInfo::new(
111 concat!("EMIT.", $scope, ".AREAS_DROPPED"),
112 DiagnosticSeverity::Warning,
113 concat!("the area table was dropped: ", $label, " emits none"),
114 ),
115 not_a_number: DiagnosticInfo::new(
116 concat!("EMIT.", $scope, ".NOT_A_NUMBER"),
117 DiagnosticSeverity::Warning,
118 concat!(
119 "a non-finite value was written as the sentinel ",
120 $label,
121 " uses, because it has no Inf or NaN"
122 ),
123 ),
124 reference_missing: DiagnosticInfo::new(
125 concat!("EMIT.", $scope, ".REFERENCE_MISSING"),
126 DiagnosticSeverity::Warning,
127 concat!(
128 "the network has no reference bus, which ",
129 $label,
130 " consumers reject"
131 ),
132 ),
133 element_relabeled: DiagnosticInfo::new(
134 concat!("EMIT.", $scope, ".ELEMENT_RELABELED"),
135 DiagnosticSeverity::Warning,
136 concat!(
137 "a normalized line reads as a transformer in the ",
138 $label,
139 " layout"
140 ),
141 ),
142 };
143 };
144}
145
146impl EmitFamily {
147 #[must_use]
149 pub fn entries(&'static self) -> [&'static DiagnosticInfo; 12] {
150 [
151 &self.field_dropped,
152 &self.record_dropped,
153 &self.value_defaulted,
154 &self.value_collapsed,
155 &self.value_substituted,
156 &self.value_truncated,
157 &self.rating_set_dropped,
158 &self.extras_dropped,
159 &self.areas_dropped,
160 &self.not_a_number,
161 &self.reference_missing,
162 &self.element_relabeled,
163 ]
164 }
165}
166
167pub mod codes {
170 use super::{DiagnosticInfo, DiagnosticSeverity, EmitFamily};
171
172 emit_family!(EMIT_MATPOWER, "MATPOWER", "MATPOWER .m");
173 emit_family!(EMIT_PSSE, "PSSE", "PSS/E .raw");
174 emit_family!(EMIT_PSLF, "PSLF", "PSLF .epc");
175 emit_family!(EMIT_PANDAPOWER, "PANDAPOWER", "pandapower JSON");
176 emit_family!(EMIT_PYPSA, "PYPSA", "the PyPSA CSV folder");
177 emit_family!(EMIT_POWERWORLD, "POWERWORLD", "PowerWorld .aux");
178 emit_family!(EMIT_POWERMODELS, "POWERMODELS", "PowerModels JSON");
179 emit_family!(EMIT_EGRET, "EGRET", "egret JSON");
180 emit_family!(EMIT_SURGE, "SURGE", "Surge JSON");
181 emit_family!(EMIT_RAVENS, "RAVENS", "MG-RAVENS JSON");
182 emit_family!(EMIT_XIIDM, "XIIDM", "XIIDM 1.17 XML");
183 emit_family!(EMIT_JIIDM, "JIIDM", "JIIDM 1.17 JSON");
184 emit_family!(EMIT_CGMES, "CGMES", "CGMES 3.0");
185 emit_family!(EMIT_UCTE, "UCTE", "UCTE-DEF .uct");
186 emit_family!(EMIT_UNSUPPORTED, "UNSUPPORTED", "a read only format");
187
188 powerio_core::diagnostic_codes! {
189 REQUEST_SINCAL_PROFILE_REQUIRED = "REQUEST.SINCAL.PROFILE_REQUIRED", Error,
190 "SINCAL requires an explicit electrical profile", category = Request;
191 REQUEST_SINCAL_FEATURE_DISABLED = "REQUEST.SINCAL.FEATURE_DISABLED", Error,
192 "SINCAL is not available through this build or entry point", category = Request;
193 PARSE_SINCAL_MALFORMED = "PARSE.SINCAL.MALFORMED", Error,
194 "a required SINCAL record or electrical mode is invalid or unsupported", category = Parse;
195 READ_SINCAL_CONVERSION_BASE = "READ.SINCAL.CONVERSION_BASE", Remark,
196 "physical SINCAL quantities use a declared internal MVA conversion base";
197 READ_SINCAL_GEOMETRY = "READ.SINCAL.GEOMETRY", Warning,
198 "optional SINCAL drawing geometry retains native view metadata and reports mapping limits";
199 READ_SINCAL_VALUE_DEFAULTED = "READ.SINCAL.VALUE_DEFAULTED", Warning,
200 "a versioned SINCAL field default was applied";
201 READ_SINCAL_LIMITS_UNSPECIFIED = "READ.SINCAL.LIMITS_UNSPECIFIED", Remark,
202 "inactive native generator capability limits are represented as unbounded";
203 READ_SINCAL_RETAINED_SOURCE_ONLY = "READ.SINCAL.RETAINED_SOURCE_ONLY", Remark,
204 "native data outside the selected steady-state profile remains in retained source";
205 EMIT_SINCAL_RETAINED_SOURCE_OMITTED = "EMIT.SINCAL.RETAINED_SOURCE_OMITTED", Warning,
206 "native data retained only in SINCAL source is not included in cross-format output";
207 EMIT_SINCAL_EXPERIMENTAL_UNSUPPORTED = "EMIT.SINCAL.EXPERIMENTAL_UNSUPPORTED", Error,
208 "invalid or unsupported candidate SINCAL output", category = Output;
209 EMIT_SINCAL_EXPERIMENTAL_LOSS = "EMIT.SINCAL.EXPERIMENTAL_LOSS", Warning,
210 "candidate SINCAL output omits data outside its declared profile";
211 EMIT_SINCAL_EXPERIMENTAL = "EMIT.SINCAL.EXPERIMENTAL", Remark,
212 "candidate SINCAL output has not been validated in native SINCAL";
213 EMIT_SINCAL_FRESH_UNSUPPORTED = "EMIT.SINCAL.FRESH_UNSUPPORTED", Error,
214 "fresh SINCAL emission is not available through the universal emitter", category = Output;
215 PARSE_MATPOWER_MALFORMED = "PARSE.MATPOWER.MALFORMED", Error,
217 "a MATPOWER matrix is missing, short, unparseable, or unbalanced", category = Parse;
218 READ_MATPOWER_INDEXED_ASSIGNMENT = "READ.MATPOWER.INDEXED_ASSIGNMENT", Warning,
219 "a MATPOWER indexed assignment is not evaluated by the literal reader";
220 PARSE_SOURCE_MALFORMED = "PARSE.SOURCE.MALFORMED", Error,
221 "a format reader refused the source it was given", category = Parse;
222 PARSE_GOC3_MALFORMED = "PARSE.GOC3.MALFORMED", Error,
223 "a GO Challenge 3 data file is not well formed JSON", category = Parse;
224 PARSE_XIIDM_VERSION_UNSUPPORTED = "PARSE.XIIDM.VERSION_UNSUPPORTED", Error,
225 "the XIIDM namespace names a version PowerIO has not tested", category = Parse;
226
227 READ_PSSE_FIELD_DROPPED = "READ.PSSE.FIELD_DROPPED", Warning,
229 "a PSS/E field with no canonical home was dropped";
230 READ_PSSE_VALUE_SUBSTITUTED = "READ.PSSE.VALUE_SUBSTITUTED", Warning,
231 "a PSS/E value the record states could not be used as given";
232 READ_PSSE_VALUE_UNSUPPORTED = "READ.PSSE.VALUE_UNSUPPORTED", Warning,
233 "a PSS/E code word (CZ, CW, CM) outside the modeled set was read as the default";
234 READ_PSSE_REFERENCE_DROPPED = "READ.PSSE.REFERENCE_DROPPED", Warning,
235 "a PSS/E control pointer names a bus the case does not declare";
236 READ_PSSE_SECTION_UNSUPPORTED = "READ.PSSE.SECTION_UNSUPPORTED", Warning,
237 "a PSS/E section is preserved in a same-format echo only";
238 READ_PSSE_RETAINED_SOURCE_ONLY = "READ.PSSE.RETAINED_SOURCE_ONLY", Remark,
239 "a PSS/E field survives in extras rather than in a typed field";
240 READ_PSSE_VALUE_DEFAULTED = "READ.PSSE.VALUE_DEFAULTED", Warning,
241 "a PSS/E record ends before a field the typed model reads, so that field took its default";
242
243 READ_PSLF_VALUE_DEFAULTED = "READ.PSLF.VALUE_DEFAULTED", Warning,
244 "a PSLF value the model needs was not in the source and was defaulted";
245 READ_PSLF_VALUE_APPROXIMATED = "READ.PSLF.VALUE_APPROXIMATED", Warning,
246 "a PSLF value was read through an approximation the .epc model forces";
247 READ_PSLF_RECORD_DROPPED = "READ.PSLF.RECORD_DROPPED", Warning,
248 "a PSLF record could not be mapped and was dropped";
249 READ_PSLF_SOURCE_MALFORMED = "READ.PSLF.SOURCE_MALFORMED", Warning,
250 "a PSLF section header, count, or end marker disagrees with the records";
251 READ_PSLF_RETAINED_SOURCE_ONLY = "READ.PSLF.RETAINED_SOURCE_ONLY", Remark,
252 "a PSLF section survives in the retained source or in extras only";
253
254 READ_PANDAPOWER_FIELD_DROPPED = "READ.PANDAPOWER.FIELD_DROPPED", Warning,
255 "a pandapower field with no canonical home was dropped";
256 READ_CGMES_RECORD_UNMAPPED = "READ.CGMES.RECORD_UNMAPPED", Warning,
257 "a CGMES record has no representation in the balanced network model";
258 READ_CGMES_FIELD_UNMAPPED = "READ.CGMES.FIELD_UNMAPPED", Warning,
259 "a field on a mapped CGMES record has no representation in the balanced network model";
260 READ_CGMES_VALUE_DEFAULTED = "READ.CGMES.VALUE_DEFAULTED", Warning,
261 "a value absent from the CGMES profile set was defaulted";
262 READ_CGMES_VALUE_APPROXIMATED = "READ.CGMES.VALUE_APPROXIMATED", Warning,
263 "a CGMES value was represented through an explicit approximation";
264 READ_CGMES_TOPOLOGY_CALCULATED = "READ.CGMES.TOPOLOGY_CALCULATED", Remark,
265 "the set carries no TopologicalNode data, so buses were calculated from ConnectivityNodes and switch positions";
266 READ_CGMES_CONNECTIVITY_INSUFFICIENT = "READ.CGMES.CONNECTIVITY_INSUFFICIENT", Error,
267 "the set carries neither TopologicalNode data nor enough connectivity to calculate buses",
268 category = Parse;
269 READ_PANDAPOWER_VALUE_INFERRED = "READ.PANDAPOWER.VALUE_INFERRED", Warning,
270 "a value pandapower does not store was reconstructed on a declared convention";
271 READ_PANDAPOWER_TABLE_UNSUPPORTED = "READ.PANDAPOWER.TABLE_UNSUPPORTED", Warning,
272 "a pandapower table is not mapped into the canonical model";
273
274 READ_PYPSA_TABLE_UNSUPPORTED = "READ.PYPSA.TABLE_UNSUPPORTED", Warning,
275 "a PyPSA table is not mapped into the canonical model";
276 READ_PYPSA_VALUE_APPROXIMATED = "READ.PYPSA.VALUE_APPROXIMATED", Warning,
277 "a PyPSA element was read through the nearest canonical element";
278 READ_PYPSA_NAME_REMAPPED = "READ.PYPSA.NAME_REMAPPED", Warning,
279 "a PyPSA bus name collides with another and was keyed by its numeric id";
280
281 READ_POWERWORLD_VALUE_DEFAULTED = "READ.POWERWORLD.VALUE_DEFAULTED", Warning,
282 "a PowerWorld field this binary vintage does not locate was defaulted";
283 READ_POWERWORLD_RETAINED_SOURCE_ONLY = "READ.POWERWORLD.RETAINED_SOURCE_ONLY", Warning,
284 "a PowerWorld aux data block survives in the retained source only";
285
286 READ_POWERMODELS_RECORD_DROPPED = "READ.POWERMODELS.RECORD_DROPPED", Warning,
287 "a PowerModels document states more than the canonical snapshot holds";
288 READ_POWERMODELS_FIELD_DROPPED = "READ.POWERMODELS.FIELD_DROPPED", Warning,
289 "a PowerModels field the canonical model cannot state was dropped";
290
291 READ_GOC3_AMBIGUOUS_DOCUMENTS = "READ.GOC3.AMBIGUOUS_DOCUMENTS", Error,
292 "a GO Challenge 3 source contains more than one problem or solution data file",
293 category = Parse;
294 READ_GOC3_PROBLEM_REQUIRED = "READ.GOC3.PROBLEM_REQUIRED", Error,
295 "a GO Challenge 3 solution data file requires its matching problem data file",
296 category = Parse;
297 READ_GOC3_SOURCE_UNRECOGNIZED = "READ.GOC3.SOURCE_UNRECOGNIZED", Error,
298 "a declared GO Challenge 3 source contains no problem or solution data file",
299 category = Parse;
300 READ_GOC3_INVALID_DOCUMENT = "READ.GOC3.INVALID_DOCUMENT", Error,
301 "the GO Challenge 3 document decodes but is not a valid problem or solution file",
302 category = Parse;
303 READ_GOC3_VALUE_INFERRED = "READ.GOC3.VALUE_INFERRED", Warning,
304 "a GO Challenge 3 value the document never states was inferred";
305 READ_GOC3_OPTIONAL_FIELD_UNTYPED = "READ.GOC3.OPTIONAL_FIELD_UNTYPED", Remark,
306 "an optional GO Challenge 3 field is retained as untyped source metadata";
307 READ_GOC3_RETAINED_SOURCE_ONLY = "READ.GOC3.RETAINED_SOURCE_ONLY", Warning,
308 "a GO Challenge 3 section survives in the retained source only";
309
310 READ_OPFDATA_FIELD_DROPPED = "READ.OPFDATA.FIELD_DROPPED", Warning,
311 "an OPFData field outside the published schema is not in the snapshot";
312 READ_OPFDATA_VALUE_INFERRED = "READ.OPFDATA.VALUE_INFERRED", Warning,
313 "OPFData carries no identity or frequency, so the reader synthesized them";
314 READ_OPFDATA_RETAINED_SOURCE_ONLY = "READ.OPFDATA.RETAINED_SOURCE_ONLY", Warning,
315 "an OPFData generator's solver initial values are carried in the parsed solution instead of the network snapshot";
316
317 READ_XIIDM_FIELD_UNMAPPED = "READ.XIIDM.FIELD_UNMAPPED", Warning,
318 "an XIIDM field is not represented in the PowerIO model";
319 READ_XIIDM_ELEMENT_UNMAPPED = "READ.XIIDM.ELEMENT_UNMAPPED", Warning,
320 "an XIIDM element is not represented in the PowerIO model";
321 READ_XIIDM_CALCULATION_VIEW = "READ.XIIDM.CALCULATION_VIEW", Warning,
322 "an XIIDM value is retained in detailed connectivity but represented differently in the balanced calculation view";
323 READ_XIIDM_VALUE_DEFAULTED = "READ.XIIDM.VALUE_DEFAULTED", Warning,
324 "a PowerIO value absent from XIIDM was assigned a documented default";
325 READ_XIIDM_VERSION_COMPATIBILITY = "READ.XIIDM.VERSION_COMPATIBILITY", Remark,
326 "an older XIIDM input version was read; fresh XIIDM output uses 1.17";
327
328 READ_SURGE_RETAINED_SOURCE_ONLY = "READ.SURGE.RETAINED_SOURCE_ONLY", Warning,
329 "a Surge section survives in the retained source only";
330
331 READ_RAVENS_VALUE_DEFAULTED = "READ.RAVENS.VALUE_DEFAULTED", Warning,
333 "a value the balanced model needs is absent from the MG-RAVENS document and was defaulted";
334 READ_RAVENS_VALUE_SUBSTITUTED = "READ.RAVENS.VALUE_SUBSTITUTED", Warning,
335 "an MG-RAVENS value could not be used as stated and was replaced";
336 READ_RAVENS_VALUE_COLLAPSED = "READ.RAVENS.VALUE_COLLAPSED", Warning,
337 "an MG-RAVENS object was reduced to what one balanced element can carry";
338 READ_RAVENS_RECORD_DROPPED = "READ.RAVENS.RECORD_DROPPED", Warning,
339 "an MG-RAVENS equipment record could not be mapped and was dropped";
340 READ_RAVENS_REFERENCE_DROPPED = "READ.RAVENS.REFERENCE_DROPPED", Warning,
341 "an MG-RAVENS pointer names a connectivity node the document does not declare";
342 READ_RAVENS_RETAINED_SOURCE_ONLY = "READ.RAVENS.RETAINED_SOURCE_ONLY", Warning,
343 "MG-RAVENS objects of a class the balanced mapping does not read stay only in the retained source";
344
345 READ_UCTE_VALUE_DEFAULTED = "READ.UCTE.VALUE_DEFAULTED", Warning,
346 "a UCTE-DEF value the model needs was not in the record and was defaulted";
347 READ_UCTE_VALUE_SUBSTITUTED = "READ.UCTE.VALUE_SUBSTITUTED", Warning,
348 "a UCTE-DEF value the record states could not be used as given";
349 READ_UCTE_REFERENCE_DROPPED = "READ.UCTE.REFERENCE_DROPPED", Warning,
350 "a UCTE-DEF regulation or special description names a transformer the case does not declare";
351 READ_UCTE_RECORD_IGNORED = "READ.UCTE.RECORD_IGNORED", Warning,
352 "a UCTE-DEF record names no usable electrical element and was ignored";
353 READ_UCTE_RETAINED_SOURCE_ONLY = "READ.UCTE.RETAINED_SOURCE_ONLY", Warning,
354 "a UCTE-DEF block survives in the retained source only";
355
356 PARSE_IEEE_CDF_MALFORMED = "PARSE.IEEE_CDF.MALFORMED", Error,
357 "an IEEE CDF title card or record could not be decoded", category = Parse;
358 READ_IEEE_CDF_RECORD_TRUNCATED = "READ.IEEE_CDF.RECORD_TRUNCATED", Warning,
359 "an IEEE CDF record ends before a mandatory field, which was read as zero";
360 READ_IEEE_CDF_VALUE_DEFAULTED = "READ.IEEE_CDF.VALUE_DEFAULTED", Warning,
361 "a value the balanced model needs is absent from the IEEE CDF and was defaulted";
362 READ_IEEE_CDF_VALUE_SUBSTITUTED = "READ.IEEE_CDF.VALUE_SUBSTITUTED", Warning,
363 "an IEEE CDF type or side code outside the documented set was read as the nearest documented value";
364 READ_IEEE_CDF_SOURCE_MALFORMED = "READ.IEEE_CDF.SOURCE_MALFORMED", Warning,
365 "an IEEE CDF section header, item count, terminator, record placement, or bus reference disagrees with the records";
366 READ_IEEE_CDF_RETAINED_SOURCE_ONLY = "READ.IEEE_CDF.RETAINED_SOURCE_ONLY", Remark,
367 "an IEEE CDF field or section survives in the retained source only";
368
369 READ_CON_STATEMENT_UNRECOGNIZED = "READ.CON.STATEMENT_UNRECOGNIZED", Warning,
370 "a PSS/E contingency statement outside the grammar was kept as text";
371 READ_CON_TEXT_AFTER_END = "READ.CON.TEXT_AFTER_END", Warning,
372 "a PSS/E contingency file states text after its file END";
373 READ_CON_NOTES_TRUNCATED = "READ.CON.NOTES_TRUNCATED", Warning,
374 "the PSS/E contingency reader stopped recording notes at its budget";
375 READ_CON_SOURCE_MALFORMED = "READ.CON.SOURCE_MALFORMED", Warning,
376 "a PSS/E contingency line could not be read as the statement its block requires";
377 READ_CON_NOT_TEXT = "READ.CON.NOT_TEXT", Error,
378 "a PSS/E contingency description file is not valid UTF-8 text", category = Parse;
379
380 READ_SUB_STATEMENT_UNRECOGNIZED = "READ.SUB.STATEMENT_UNRECOGNIZED", Warning,
381 "a PSS/E subsystem statement outside the grammar was kept as its original line";
382 READ_SUB_TEXT_AFTER_END = "READ.SUB.TEXT_AFTER_END", Warning,
383 "a PSS/E subsystem description file states text after its file END";
384 READ_SUB_NOTES_TRUNCATED = "READ.SUB.NOTES_TRUNCATED", Warning,
385 "the PSS/E subsystem reader stopped recording notes at its budget";
386 READ_SUB_SOURCE_MALFORMED = "READ.SUB.SOURCE_MALFORMED", Warning,
387 "a PSS/E subsystem selector states a keyword whose values are not the numbers it needs";
388 READ_SUB_NOT_TEXT = "READ.SUB.NOT_TEXT", Error,
389 "a PSS/E subsystem description file is not valid UTF-8 text", category = Parse;
390
391 READ_MON_STATEMENT_UNRECOGNIZED = "READ.MON.STATEMENT_UNRECOGNIZED", Warning,
392 "a PSS/E monitored element statement outside the grammar was kept as its original line";
393 READ_MON_TEXT_AFTER_END = "READ.MON.TEXT_AFTER_END", Warning,
394 "a PSS/E monitored element file states text after its file END";
395 READ_MON_NOTES_TRUNCATED = "READ.MON.NOTES_TRUNCATED", Warning,
396 "the PSS/E monitored element reader stopped recording notes at its budget";
397 READ_MON_SOURCE_MALFORMED = "READ.MON.SOURCE_MALFORMED", Warning,
398 "a line inside a PSS/E monitored element block states no branch";
399 READ_MON_VOLTAGE_BAND_ORDER_ASSUMED = "READ.MON.VOLTAGE_BAND_ORDER_ASSUMED", Remark,
400 "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";
401 READ_MON_NOT_TEXT = "READ.MON.NOT_TEXT", Error,
402 "a PSS/E monitored element file is not valid UTF-8 text", category = Parse;
403
404 READ_GEO_SOURCE_MALFORMED = "READ.GEO.SOURCE_MALFORMED", Warning,
405 "a geo layer row could not be read and was skipped";
406 READ_GEO_NOTES_TRUNCATED = "READ.GEO.NOTES_TRUNCATED", Warning,
407 "the geo reader stopped recording notes at its budget";
408 READ_GEO_NOT_TEXT = "READ.GEO.NOT_TEXT", Error,
409 "a geographic layer document is not valid UTF-8 text", category = Parse;
410
411 READ_IO_FAILED = "READ.IO.FAILED", Error,
412 "the case file could not be read", category = Io;
413
414 CANONICALIZE_NORMALIZE_BOUNDS_CLAMPED = "CANONICALIZE.NORMALIZE.BOUNDS_CLAMPED", Remark,
416 "a branch angle difference bound was clamped into the modeled range";
417 CANONICALIZE_NORMALIZE_NO_REFERENCE_BUS = "CANONICALIZE.NORMALIZE.NO_REFERENCE_BUS", Error,
418 "no reference bus can be established: no bus hosts an in-service generator",
419 category = Data;
420 CANONICALIZE_NORMALIZE_REFERENCE_DESIGNATED =
421 "CANONICALIZE.NORMALIZE.REFERENCE_DESIGNATED", Warning,
422 "the case states no surviving reference bus, so normalization designated a slack";
423 CANONICALIZE_NORMALIZE_GEN_COST_ABSENT =
424 "CANONICALIZE.NORMALIZE.GEN_COST_ABSENT", Warning,
425 "the solver-ready copy has in-service generators and no cost data, so any cost objective built from it is zero";
426 CANONICALIZE_NORMALIZE_INVALID_OPTION = "CANONICALIZE.NORMALIZE.INVALID_OPTION", Error,
427 "a normalize option is outside the range it is defined on", category = Data;
428 CANONICALIZE_NORMALIZE_INVALID_BASE_MVA = "CANONICALIZE.NORMALIZE.INVALID_BASE_MVA", Error,
429 "the case base MVA is not a positive finite number", category = Data;
430 CANONICALIZE_NORMALIZE_GENERATOR_BUS_RETYPED =
431 "CANONICALIZE.NORMALIZE.GENERATOR_BUS_RETYPED", Warning,
432 "PQ buses hosting an in-service generator were retyped PV, so the generator holds the bus voltage";
433
434 CANONICALIZE_ISLAND_REFERENCE_DESIGNATED =
436 "CANONICALIZE.ISLAND.REFERENCE_DESIGNATED", Warning,
437 "an island stated no reference bus, so its largest generator's bus was designated";
438 CANONICALIZE_ISLAND_REFERENCE_DEMOTED = "CANONICALIZE.ISLAND.REFERENCE_DEMOTED", Warning,
439 "an island stated several reference buses; one was kept and the others demoted";
440 CANONICALIZE_ISLAND_DE_ENERGIZED = "CANONICALIZE.ISLAND.DE_ENERGIZED", Warning,
441 "an island no in-service generator supplies was de-energized";
442 CANONICALIZE_ISLAND_HVDC_FED_DE_ENERGIZED =
443 "CANONICALIZE.ISLAND.HVDC_FED_DE_ENERGIZED", Warning,
444 "an island fed only by HVDC, with no in-service generator, was de-energized with its HVDC lines";
445
446 CANONICALIZE_MERGE_ZERO_IMPEDANCE = "CANONICALIZE.MERGE.ZERO_IMPEDANCE", Warning,
448 "zero impedance branches were merged and their flows are no longer network variables";
449 CANONICALIZE_MERGE_CLOSED_SWITCH = "CANONICALIZE.MERGE.CLOSED_SWITCH", Warning,
450 "closed switches were merged and their flows are no longer network variables";
451 CANONICALIZE_MERGE_ATTRIBUTE_CONFLICT = "CANONICALIZE.MERGE.ATTRIBUTE_CONFLICT", Warning,
452 "merged buses stated different attributes; the surviving bus's values were kept";
453 CANONICALIZE_MERGE_ELEMENT_SHORTED = "CANONICALIZE.MERGE.ELEMENT_SHORTED", Warning,
454 "an element the rule did not select had both ends merged into one bus";
455 CANONICALIZE_MERGE_WINDING_PAIR_KEPT = "CANONICALIZE.MERGE.WINDING_PAIR_KEPT", Warning,
456 "an element was not merged because it would join two windings of one three winding transformer";
457 CANONICALIZE_MERGE_DETAIL_DROPPED = "CANONICALIZE.MERGE.DETAIL_DROPPED", Warning,
458 "the detailed connectivity's calculated bus assignments were dropped because the merge joined buses its hierarchy keeps apart";
459 CANONICALIZE_MERGE_FLOW_UNDETERMINED = "CANONICALIZE.MERGE.FLOW_UNDETERMINED", Warning,
460 "a removed element's recovered flow is not fixed by the network, so a minimum norm split is reported";
461 CANONICALIZE_MERGE_FLOW_RESIDUAL = "CANONICALIZE.MERGE.FLOW_RESIDUAL", Warning,
462 "a merged group's injections from the solution do not balance its stated devices";
463 CANONICALIZE_MERGE_INVALID_RULE = "CANONICALIZE.MERGE.INVALID_RULE", Error,
464 "a bus merge rule cannot be applied to this network", category = Data;
465 CANONICALIZE_MERGE_FLOW_SHAPE_MISMATCH = "CANONICALIZE.MERGE.FLOW_SHAPE_MISMATCH", Error,
466 "a merged network solution does not match the merge's tables", category = Data;
467 CANONICALIZE_NORMALIZE_WINDING_TAKEN_OUT =
468 "CANONICALIZE.NORMALIZE.WINDING_TAKEN_OUT", Warning,
469 "a three winding transformer winding on a bus normalization drops was taken out of service";
470
471 BUILD_INDEX_UNKNOWN_BUS = "BUILD.INDEX.UNKNOWN_BUS", Error,
473 "an element references a bus id the case does not declare", category = Data;
474 BUILD_INDEX_REFERENCE_BUS_COUNT = "BUILD.INDEX.REFERENCE_BUS_COUNT", Error,
475 "the index needs exactly one reference bus", category = Data;
476 BUILD_INDEX_UNGROUNDED_COMPONENT = "BUILD.INDEX.UNGROUNDED_COMPONENT", Error,
477 "a connected component has no reference bus to ground", category = Data;
478 BUILD_SWITCH_CLOSED = "BUILD.SWITCH.CLOSED", Error,
479 "a closed switch joins two buses that a calculation which models no switches would leave apart",
480 category = Data;
481 BUILD_BRANCH_ZERO_IMPEDANCE = "BUILD.BRANCH.ZERO_IMPEDANCE", Error,
482 "a branch has a zero matrix denominator under the selected build options",
483 category = Data;
484 BUILD_BRANCH_NOT_A_NUMBER = "BUILD.BRANCH.NOT_A_NUMBER", Error,
485 "a branch susceptance is not finite", category = Data;
486 BUILD_BRANCH_DEGENERATE_TAP = "BUILD.BRANCH.DEGENERATE_TAP", Error,
487 "a branch tap ratio is too small to divide by", category = Data;
488 BUILD_GEO_UNLOCATED_ELEMENTS = "BUILD.GEO.UNLOCATED_ELEMENTS", Error,
489 "a geo apply left elements with no location or route", category = Data;
490 BUILD_GEO_APPLY_SUMMARY = "BUILD.GEO.APPLY_SUMMARY", Remark,
491 "how many elements a geo apply located";
492 BUILD_GEO_UNMATCHED_FEATURE = "BUILD.GEO.UNMATCHED_FEATURE", Warning,
493 "a geo feature matched no element in the network";
494 BUILD_CON_CASE_UNRESOLVED = "BUILD.CON.CASE_UNRESOLVED", Warning,
495 "an action of a contingency case bound to no element, or named more than one where one was required";
496 BUILD_CON_NOTES_TRUNCATED = "BUILD.CON.NOTES_TRUNCATED", Warning,
497 "the contingency resolver stopped recording notes at its budget";
498 BUILD_CON_SUBSYSTEM_UNKNOWN = "BUILD.CON.SUBSYSTEM_UNKNOWN", Warning,
499 "an automatic contingency specification names a subsystem the subsystem set does not state";
500 BUILD_CON_SPECIFICATION_EMPTY = "BUILD.CON.SPECIFICATION_EMPTY", Warning,
501 "an automatic contingency specification names fewer in-service elements of its target family than its order needs";
502 BUILD_MON_STATEMENT_UNRESOLVED = "BUILD.MON.STATEMENT_UNRESOLVED", Warning,
503 "a monitored element statement did not bind to exactly one element, or names a subsystem the subsystem set does not state";
504 BUILD_HVDC_FIXED_INJECTION = "BUILD.HVDC.FIXED_INJECTION", Remark,
505 "in service HVDC lines enter the bus balance as fixed injections at their stated terminal powers";
506 BUILD_HVDC_IGNORED = "BUILD.HVDC.IGNORED", Warning,
507 "in service HVDC lines were left out of the bus balance by request";
508 BUILD_HVDC_TERMINAL_INACTIVE = "BUILD.HVDC.TERMINAL_INACTIVE", Warning,
509 "an in service HVDC line names an isolated or undeclared terminal bus and injects nothing";
510
511 VALIDATE_BALANCED_STRUCTURE = "VALIDATE.BALANCED.STRUCTURE", Error,
515 "a balanced payload's referential integrity does not hold";
516 VALIDATE_BALANCED_VALUE_DOMAIN = "VALIDATE.BALANCED.VALUE_DOMAIN", Warning,
517 "a balanced payload value is outside the domain the model states";
518 VALIDATE_BALANCED_PAYLOAD_IDENTITY = "VALIDATE.BALANCED.PAYLOAD_IDENTITY", Error,
519 "a balanced payload's uid identity does not hold";
520 VALIDATE_GEN_COST_MISSING = "VALIDATE.GEN_COST.MISSING", Error,
521 "a generator carries no cost data under a policy that requires one", category = Data;
522 VALIDATE_GEN_COST_NOT_A_NUMBER = "VALIDATE.GEN_COST.NOT_A_NUMBER", Error,
523 "a default generator cost field is not finite", category = Data;
524 VALIDATE_GEN_COST_PATCH_INVALID = "VALIDATE.GEN_COST.PATCH_INVALID", Error,
525 "a generator cost patch row is not usable", category = Data;
526 VALIDATE_GEN_COST_COUNT_MISMATCH = "VALIDATE.GEN_COST.COUNT_MISMATCH", Error,
527 "the cost table has neither one row per generator nor two", category = Data;
528 VALIDATE_DC_LINE_COST_COUNT_MISMATCH = "VALIDATE.DC_LINE_COST.COUNT_MISMATCH", Error,
529 "the dcline cost table has other than one row per dcline", category = Data;
530
531 TRANSFORM_GEN_COST_POLICY_APPLIED = "TRANSFORM.GEN_COST.POLICY_APPLIED", Remark,
533 "a write time generator cost policy patched or synthesized costs";
534
535 REQUEST_FORMAT_UNKNOWN = "REQUEST.FORMAT.UNKNOWN", Error,
537 "the named case format is not one powerio reads", category = Request;
538 REQUEST_FORMAT_WRITE_UNSUPPORTED = "REQUEST.FORMAT.WRITE_UNSUPPORTED", Error,
539 "the named case format is read only and has no writer", category = Request;
540
541 EMIT_FORMAT_REQUIRED_VALUE_MISSING = "EMIT.FORMAT.REQUIRED_VALUE_MISSING", Error,
542 "the requested format requires a value the module does not contain", category = Output;
543
544 EMIT_PSSE_DOWNGRADED = "EMIT.PSSE.DOWNGRADED", Warning,
548 "a newer PSS/E revision was written into an older layout";
549 EMIT_PSSE_RATING_SET_REMAPPED = "EMIT.PSSE.RATING_SET_REMAPPED", Remark,
550 "a named branch rating set was written into a PSS/E numbered rating slot";
551 }
552
553 pub const EMIT_FAMILIES: [&EmitFamily; 15] = [
556 &EMIT_MATPOWER,
557 &EMIT_PSSE,
558 &EMIT_PSLF,
559 &EMIT_PANDAPOWER,
560 &EMIT_PYPSA,
561 &EMIT_POWERWORLD,
562 &EMIT_POWERMODELS,
563 &EMIT_EGRET,
564 &EMIT_SURGE,
565 &EMIT_RAVENS,
566 &EMIT_XIIDM,
567 &EMIT_JIIDM,
568 &EMIT_CGMES,
569 &EMIT_UCTE,
570 &EMIT_UNSUPPORTED,
571 ];
572}
573
574#[must_use]
576pub fn registry() -> Vec<&'static DiagnosticInfo> {
577 let mut all: Vec<&'static DiagnosticInfo> = codes::ALL.to_vec();
578 for family in codes::EMIT_FAMILIES {
579 all.extend(family.entries());
580 }
581 all
582}
583
584impl TargetFormat {
585 #[must_use]
587 pub fn emit_family(self) -> &'static EmitFamily {
588 match self {
589 TargetFormat::Matpower => &codes::EMIT_MATPOWER,
590 TargetFormat::Psse { .. } | TargetFormat::PsseRawx => &codes::EMIT_PSSE,
591 TargetFormat::Pslf => &codes::EMIT_PSLF,
592 TargetFormat::PandapowerJson => &codes::EMIT_PANDAPOWER,
593 TargetFormat::PowerWorld => &codes::EMIT_POWERWORLD,
594 TargetFormat::PowerModelsJson => &codes::EMIT_POWERMODELS,
595 TargetFormat::EgretJson => &codes::EMIT_EGRET,
596 TargetFormat::SurgeJson => &codes::EMIT_SURGE,
597 TargetFormat::RavensJson => &codes::EMIT_RAVENS,
598 TargetFormat::Xiidm => &codes::EMIT_XIIDM,
599 TargetFormat::Jiidm => &codes::EMIT_JIIDM,
600 TargetFormat::Cgmes => &codes::EMIT_CGMES,
601 TargetFormat::Ucte => &codes::EMIT_UCTE,
602 TargetFormat::Goc3Json | TargetFormat::DeepMindOpfDataJson => &codes::EMIT_UNSUPPORTED,
607 }
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614
615 #[test]
616 fn the_registry_is_sound() {
617 let problems = check_registry(registry());
618 assert!(problems.is_empty(), "{problems:#?}");
619 }
620
621 #[test]
622 fn every_write_target_has_a_family_of_its_own() {
623 let mut scopes: Vec<&str> = codes::EMIT_FAMILIES
624 .iter()
625 .map(|f| f.field_dropped.code.split('.').nth(1).unwrap())
626 .collect();
627 scopes.sort_unstable();
628 scopes.dedup();
629 assert_eq!(scopes.len(), codes::EMIT_FAMILIES.len());
630 assert_eq!(
631 TargetFormat::Psse { rev: 33 }
632 .emit_family()
633 .field_dropped
634 .code,
635 "EMIT.PSSE.FIELD_DROPPED"
636 );
637 }
638}