Rules¶
rules is a list. Every entry is one of three kinds of rule (copy, constant
or each), or a use of a definition. Rules apply in the order written, and
a later write to a path replaces an earlier one.
| Kind | Recognised by | Purpose |
|---|---|---|
| copy | from and to, no value or template |
move a value between the two sides, optionally converted |
| constant | value or template |
write a fixed value on one side |
each |
each |
apply nested rules to every element of a collection |
| use | use |
splice a named rule list from definitions |
Every rule accepts description, free text that T1K ignores.
Copy rules¶
{"from": "cont_energy_cap_max", "to": "capacity.max",
"convert": [{"absent": {"values": ["inf"]}}, {"linear": {"divisor": 1000}}],
"reverse_default": "inf",
"description": "kW to MW; 'inf' means unbounded"}
| Key | Meaning |
|---|---|
from |
the path on the source side: read by the forward direction, written by the reverse |
to |
the path on the target side: written by the forward direction, read by the reverse |
convert |
a converter or a list of them, applied in order; the reverse direction runs them backwards |
default |
written to to by the forward direction when from is absent, or the converter made the value absent |
reverse_default |
written to from by the reverse direction when to is absent |
when |
conditions per direction |
from and to must use the same variables, apart from those an enclosing
each binds. A default applies only when the path is concrete (no unbound
variables), because the absence of an iterated path is simply zero matches.
The value at the source path is copied as a whole: an object or array moves with everything inside it, and nothing is taken by reference.
Constant rules¶
{"to": "experiment.mode", "value": "plan"}
{"from": "geometry.type", "value": "Point"}
{"to": "node", "template": "n$k"}
A constant has exactly one side. A constant with to is written by the
forward direction, one with from by the reverse direction; the other
direction ignores the rule. value may be any JSON value, including null,
objects and arrays; template is a string in which $k, ${k} and $$
are substituted from the enclosing each bindings. A constant's path may use
only variables an enclosing each binds.
Constants are how the reverse direction restores what the forward direction cannot carry: declare the value a source field should have when its target counterpart does not exist. Place such a constant before the copy rule for the same path, so a real value, when present, wins.
Each rules¶
{
"each": {"from": "topology[$i].from", "to": "model.nodes{n$k}", "bind": {"$k": "id"}},
"when": {"forward": {"path": "properties.feature_type", "equals": "BasePOI"}},
"rules": [
{"from": "geometry.coordinates[1]", "to": "coords.lat", "convert": "number"},
{"from": "properties.osm_id", "to": "name"},
{"from": "properties.demand_energy", "to": "/model.technologies{demand_$k}.demand_profile"}
]
}
| Key | Meaning |
|---|---|
each.from |
the path of the source elements |
each.to |
the path of the target elements |
each.bind |
variables read from values inside each source element (see bind) |
rules |
the rules applied to every pair of elements, with paths relative to the pair; may be omitted or empty |
when |
conditions, evaluated per element |
Inside the nested rules, relative paths start at the current element on each
side; absolute paths (/...) start at the document root. The variables bound
by the each, and by enclosing ones, are available in nested paths,
templates and conditions.
In the forward direction the elements of each.from are enumerated in the
source document and the nested rules write below each.to; in the reverse
direction the entries of each.to are enumerated and the nested rules write
below each.from. An each whose paths have no variables applies its rules
once if the element exists and not at all otherwise, which makes it the way
to write a group of constants only when a source object is present.
bind¶
bind maps variables to paths read from each source element:
The value at the path must be a string, number or boolean and becomes the
variable's binding, as text; a missing or non-scalar value fails the rule,
so a mapping never drops elements silently. A bind variable must be new (not
bound by an enclosing each nor by each.from), must appear in each.to,
and its path may use the variables the iteration binds.
With bind, the target side can be keyed by a value instead of a position:
topology[$i].from paired with model.nodes{n$k} and {"$k": "id"} turns
an array of features into an object keyed by their ids, and writes a feature
that appears several times only once.
How the reverse direction enumerates depends on whether each.to determines
every variable of each.from:
- Iterate mode, when it does (
conns[$i]witharcs{${p}_$i}and{"$p": "pipe"}): the target entries are enumerated, each key is parsed, and every bound variable is written back to its bind path as a string (pipegets"lv"). - Join mode, when it does not (
topology[$i].fromwithmodel.nodes{n$k}:$iis not in the key): theeachlooks at the source elements that earlier rules have already created in the output, reads the bind path from each of them (id), and fills the element from the matching target entry. Such a rule must therefore come after the rules that create those elements and their bind values; entries without a created element are not restored.
Nesting¶
each rules nest. A nested each may address its own collections relative
to the current element or, with absolute paths, elsewhere in the document,
which is how per-element technologies are collected into one top-level map:
Definitions and use¶
definitions holds named rule lists; {"use": "name"} splices one in place.
The spliced rules are compiled where they are used, so they may rely on the
variables bound at that place (a definition used inside an each over
model.nodes{n$k} may use $k), and one definition can serve several each
rules. A definition may use other definitions; cycles are rejected.
Conditions¶
"when": {
"forward": [{"path": "feature_type", "equals": "TopologyNode"}, {"path": "rated_power", "gt": 0}],
"reverse": {"path": "/model.trade{grid_$k}", "exists": true}
}
when gates a rule per direction: forward applies to the forward
direction, reverse to the reverse direction, and a direction without
conditions always applies. Each direction takes one condition or a list of
conditions, all of which must hold. A condition names a path, evaluated
against the source document of that direction, relative to the current
element (for an each, the element itself) or absolute with /. The path
may use bound variables and must address a single value. Its remaining keys
are operators, all of which must hold:
| Operator | Holds when |
|---|---|
exists: true / false |
the path is present / absent |
equals: v |
the value is present and equals v (structurally; numbers by value) |
not_equals: v |
the value is absent or differs from v |
in: [v, ...] |
the value is present and equals one of the list |
gt, gte, lt, lte: n |
the value is a number in the given relation to n; a non-number never holds |
The forward conditions of an each run before its bind paths are read, so
they may use the iteration variables but not the bound ones; reverse
conditions have every variable bound.