The schema¶
One schema per record, written down as manifest.json (design).
import narwhals as nw
from datarecord import Schema, Dimension, AttributeSpec, Group, Trait
schema = Schema(
version=1,
dimensions={
"entity": Dimension(dtype=nw.String()),
"entity_type": Dimension(dtype=nw.Enum(["Bus", "Generator", "Link"])),
"bus": Dimension(dtype=nw.String()),
"scenario": Dimension(dtype=nw.String()),
"timestep": Dimension(dtype=nw.Datetime()),
},
groups={
"connection": Group(over={"entity": "entity", "bus": "bus"}),
"entity_type": Group(over=["entity"], into="entity_type"),
},
attributes={
"p_nom": AttributeSpec(
dtype=nw.Float64(), dims={"entity"}, default=0.0, unit="MW"
),
"carrier": AttributeSpec(dtype=nw.String(), dims={"entity"}),
"p_max_pu": AttributeSpec(
dtype=nw.Float64(), dims={"entity", "scenario", "timestep"}, default=1.0
),
"efficiency": AttributeSpec(
dtype=nw.Float64(), dims={"connection", "timestep"}, default=1.0
),
},
traits={
"dispatchable": Trait(
attributes={"p_max_pu"}, on={"entity_type": {"Generator"}}
)
},
partial={"entity", "bus", "scenario"},
)
Dimensiondeclares one axis: itsdtype(a narwhals dtype, translated to its DuckDB name — a DuckDB type name works too, for a type narwhals does not spell) andwithinfor an axis whose labels identify a point only inside another's — multi-period time being the case (design).Groupdeclares which tuples over several dims exist, mapping coordinate name → dim; a list is sugar where the two coincide.connectionover(entity, bus)is the one every network has. Addingintodeclares the group functional into that dim — eachovertuple carrying exactly one of its labels — which is a constraint checkable on write (design).entity_typeis an ordinary functional group —over=["entity"], into="entity_type"— so its rows land ondims/entity.parquetlike any classification's. AnEnumpins the vocabulary; a plainnw.String()leaves the labels as data. Omit the group entirely and components have no types (design).attributesis flat — one attribute, one spec, record-wide — and aTraitnarrows one to some entity types. An attribute no trait bundles is carried by every type itsdimscan address, sop_nomabove reaches all three whilep_max_pureaches onlyGenerator.Schema.attributes_for("Generator")resolves the two into what that type carries (design).AttributeSpec.dimsis the only addressing mechanism, naming dims and groups alike —efficiencyis a connection attribute because itsdimsname the group. A name is the dim of that name if one is declared, and otherwise the group expanded to its coordinates (design). It is what makes a scenario-varyingp_noma violation rather than data, and it decides the file split: naming exactly one coordinate puts an attribute on that thing's own table, anything more ininputs/(design).partialis the layering granularity — which dims a layer may patch value by value.scenariois patchable;timestepis not, so a patch to one hour restates that component's whole series rather than leaving a curve resolved across two layers with a hole in it (design).entityand every group key coordinate must be in it, since neither broadcasts. Omit it entirely for a record with no layers.unitanddescriptionare stored and never interpreted — no conversion, no dimensional analysis.Noneis undeclared,""genuinely dimensionless (design).
Compatibility¶
schema.compatible_with(other) answers whether layers written under other still read under self, returning one reason per incompatibility and an empty list when the change is compatible (design).