Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The .pio.json schema

.pio.json is the serialized form of powerio_pkg::NetworkPackage: a versioned envelope around one PowerIO IR payload. The envelope shape and stability policy are below. The crate is the implementation; compiler-ir.md is the architecture note.

Two stability tiers

A .pio.json file has two parts with different stability promises.

  1. The envelope — every field except model. This is the versioned, documented surface: schema, schema_version, producer, model_kind, origin, sources, source_maps, diagnostics, validation, summary, lowering_history, operating_points, derived. Its shape changes only under the versioning policy below.

  2. The payload — the model field’s balanced_network / multiconductor_network object: the serde snapshot of the PowerIO Rust IR (powerio::Network / powerio_dist::DistNetwork). The payload is a declared contract of its own, named by the top-level payload_schema URL and versioned by payload_schema_version. A consumer that computes on payload fields pins the payload version; a tool that routes or audits packages pins the envelope version and can keep treating the payload as opaque.

The two versions are independent because they change at different rates and break different consumers: the payload grows whenever the IR grows (a minor payload_schema_version bump), while the envelope bookkeeping barely moves.

The payload is PowerIO’s own IR schema for both model kinds. Interchange formats stay at the converter boundary: for distribution models the multiconductor payload is the same model the BMOPF reader and writer translate to and from, and powerio convert --to bmopf-json emits a standalone BMOPF exchange file when one is needed. The BMOPF schema itself (bmopf-report) never defines the payload.

Versioning policy (envelope)

  • schema_version is semver. The current value is 0.1.1; the schema URL is https://powerio.dev/schema/pio-package/0.1.
  • Optional additive envelope fields (a reader that ignores them loses nothing it relied on before) land without a version change; operating_points landed this way. The minor version bumps when a reader needs to depend on a field being present.
  • Envelope field moves or removals bump the major version, or ship a migration.
  • A reader tolerates unknown later top-level fields (they are ignored, not an error), so a package from a newer producer still loads. A later version can preserve them in an extras map instead of dropping them.
  • A reader accepts same major schema_version values and rejects a different major version before using the payload.
  • Every package states producer.version and schema_version.

Versioning policy (payload)

  • payload_schema names the payload contract per model kind: https://powerio.dev/schema/pio-payload-balanced/1 and https://powerio.dev/schema/pio-payload-multiconductor/1. The URLs are identifiers, not fetch locations (the JSON Schema $id convention).
  • payload_schema_version is semver, currently 1.0.0 for both kinds. Additive optional fields bump the minor; field moves or removals bump the major.
  • A reader rejects a payload_schema_version with a different major (or one that does not parse as semver) before computing on payload fields. Absent fields — every package written before 0.1.1 — are accepted; such payloads predate the declared contract.
  • The payload field tables are the rustdoc of powerio::Network and powerio_dist::DistNetwork; the serde snapshot of those structs is the normative shape.

Row identity

Every row of the balanced payload tables (buses, loads, shunts, branches, switches, generators, storage, hvdc, transformers_3w) carries a uid string: the source record uid where the format defines one (GOC3), and a {table}:{row} value synthesized at package build otherwise. A synthesized uid records the row the element had when the package was built and sticks to the element from then on. Uids are unique per table; a duplicate is a validation error. Operating point updates resolve against these identities (below). Rows in packages written before 0.1.1 carry no uid, which is what keeps their row-addressed operating points valid.

Explicit model kind

model_kind is a standalone top-level field and is authoritative. A reader must branch on it and must not infer the payload kind from which field is present. The payload is additionally self-describing: model is tagged by kind, so model.kind and model_kind carry the same value. NetworkPackage::kind_is_consistent asserts the two agree; a reader should reject a package where they disagree.

"model_kind": "balanced",
"model": { "kind": "balanced", "balanced_network": { "...": "..." } }

model_kind values: balanced, multiconductor (the enum is non-exhaustive; later families can be added).

Envelope reference

fieldtyperequirednotes
schemastring (URL)yesidentifies the package format; defaults to the current URL on read
schema_versionstring (semver)yesenvelope version; defaults to current on read
producerobjectyes{tool, version, git_commit?, features[]}
package_idstringnostable content id, e.g. "sha256:..."; unset by the scaffold
created_atstring (RFC 3339)nounset by default for deterministic output
model_kindenumyesbalanced | multiconductor; authoritative
payload_schemastring (URL)nodeclared payload contract for model_kind; absent on pre-0.1.1 packages
payload_schema_versionstring (semver)nopayload version; a different major is rejected on read
modelobjectyes{kind, <kind>_network}; follows the Rust model payload
originobjectyestagged by kind: in_memory | file | folder | binary_file | derived | composite
sourcesarraynodeclared source artifacts: {id, kind, path?, format?, hash?}
source_mapsarrayno{element_path, source_ref, mapping_kind, confidence}
diagnosticsarraynostructured findings (see below)
validationobjectyes{status, counts, passes[]}
summaryobjectyes{elements{}, topology?, units?}
lowering_historyarraynoLoweringRecord per pass
operating_pointsobjectnoreplayable updates over the one static payload
derivedobjectnooptional matrix stats, normalized solver table metadata, and cache keys

Operating points

operating_points records a time axis and an ordered list of payload field updates. A point names a table, a row identity and/or a zero based row, and the fields to overwrite. Materializing a point clones the static payload, applies those field updates, and clears operating_points in the returned package.

Updates resolve by identity first. When the referenced table carries uid values, element.source_uid is authoritative: it selects the row, a present element.row must agree with the resolved row, and an unknown or duplicated uid is an error (reported by validation and fatal to materialization). A producer that knows the identity can omit row entirely. When the table carries no uids (packages written before 0.1.1), source_uid is advisory and row addresses the update alone. An update may not overwrite uid itself, and an element ref with neither row nor source_uid does not parse.

The block shape is:

fieldtypenotes
time_axis.periodsintegernumber of available operating points
time_axis.duration_hoursarray of numbersoptional per period duration
time_axis.labelsarray of stringsoptional labels, such as "1", "2", …
points[]arrayone replayable state
points[].indexintegerzero based period index; addresses time_axis.duration_hours and time_axis.labels
points[].updates[]arrayrow field updates to apply for this point
updates[].element.tablestringpayload table name, such as generators, loads, branches, or hvdc
updates[].element.rowintegerzero based row; optional when source_uid is present, then a consistency check
updates[].element.source_uidstringthe target row’s payload identity (uid); authoritative when the table carries uids
updates[].fieldsobjectfield names and JSON values to overwrite
metadataobjectoptional series or point metadata

GO Challenge 3 packages use this block for the scheduling time series. The static model reflects the first interval that can be represented by Network; operating_points carries replayable updates for every interval. NetworkPackage::materialize_operating_point(index) returns a new static package with origin.kind = "derived" and origin.pass = "materialize-operating-point".

"operating_points": {
  "time_axis": { "periods": 2, "duration_hours": [1.0, 1.0], "labels": ["1", "2"] },
  "points": [
    { "index": 0, "updates": [] },
    { "index": 1,
      "updates": [
        { "element": { "table": "loads", "row": 0, "source_uid": "device_1" },
          "fields": { "p": 12.5, "q": 3.2 } }
      ] }
  ],
  "metadata": { "source_format": "goc3-json" }
}

Derived metadata

derived.normalized_solver_tables records the compact identity metadata for powerio::Network::to_normalized_solver_tables() without embedding every table row in the package. The full tables are a derived artifact; this metadata lets a compiler cache prove it was built from the same lowering pass and row order.

The block carries:

  • pass: "balanced-to-normalized-solver-tables";
  • units: per unit power, per unit voltage, radian angles, per unit impedance and admittance, zero based dense indices;
  • row_counts: counts for buses, loads, shunts, branches, switches, arcs, generators, storage, and HVDC rows;
  • bus_ids, reference_bus_indices, and component_labels;
  • branch_from_arc_indices and branch_to_arc_indices;
  • source_rows: source row indices for rows that survived normalization, with null for synthetic rows such as 3-winding star buses and branches.

Diagnostics

Each diagnostic carries a stable dotted code, a severity (debug, info, warning, error, fatal; ordered worst-last), the stage it came from (parse, read, canonicalize, validate, lower, emit, bind, partner), a human message, and where known an element_path, a source_ref, a details object, a suggested_action, and a safe_to_ignore list. Code namespaces by leading segment: PARSE, READ, IR, VALIDATE, FIDELITY, LOWER, EMIT, BINDING, PARTNER, PERF.

Source maps

A source_map entry records where a canonical field came from: an element_path (a JSON pointer, or a best-effort locator in v0.1), a source_ref into a declared source, a mapping_kind (exact, defaulted, inferred, converted_units, lowered, aggregated, split, synthetic, retained_extra), and a confidence (exact, high, medium, low). Balanced packages emit source maps for stable bus, load, shunt, branch, and generator fields. Balanced source_ref.field values use the same canonical field names as the payload, so they can be compared directly with element_path. When a source format folds several canonical elements into one source row, the source map records that relation with another mapping kind; MATPOWER load and shunt fields use mapping_kind = split and point to the bus record while keeping fields such as p, q, g, and b. Values that the source format does not carry are not mapped as exact; MATPOWER base_frequency has no source map. When a multiconductor network is packaged, its defaulted fields lift into source maps with mapping_kind = defaulted, and its retained source becomes origin.retained_source. Validation diagnostics attach the matching source_ref when the package has a source map for the reported field.

NetworkPackage::lower_multiconductor_to_balanced(options) returns a new balanced package with origin.kind = derived and origin.pass = "multiconductor-to-balanced". It preserves the parent lowering_history and appends a LoweringRecord whose options, assumptions, approximations, dropped fields, diagnostics, and validation status describe the pass. Lowered balanced source maps use lowered, aggregated, converted_units, synthetic, and defaulted mapping kinds. The pass is never implicit during package readback, format conversion, matrix construction, bindings, or MCP operations.

Example

{
  "schema": "https://powerio.dev/schema/pio-package/0.1",
  "schema_version": "0.1.1",
  "producer": { "tool": "powerio", "version": "0.5.1" },
  "model_kind": "multiconductor",
  "payload_schema": "https://powerio.dev/schema/pio-payload-multiconductor/1",
  "payload_schema_version": "1.0.0",
  "model": {
    "kind": "multiconductor",
    "multiconductor_network": {
      "base_frequency": 60.0,
      "loads": [
        { "name": "l1", "bus": "b1", "configuration": "wye",
          "voltage_model": { "model": "zip", "v_nom": [230.0], "alpha_z": [0.5], "...": "..." } }
      ]
    }
  },
  "origin": { "kind": "file", "format": "dss", "retained_source": true },
  "sources": [ { "id": "src0", "kind": "file", "format": "dss" } ],
  "source_maps": [
    { "element_path": "/model/multiconductor_network/vsource.source#basekv",
      "source_ref": { "source_id": "src0", "field": "basekv" },
      "mapping_kind": "defaulted", "confidence": "high" }
  ],
  "validation": { "status": "ok", "counts": { "fatal": 0, "error": 0, "warning": 0, "info": 0, "debug": 0 } },
  "summary": { "elements": { "buses": 1, "loads": 1 }, "units": { "power": "W/var", "angle": "radians" } }
}