dataset/1¶
A dataset is a directory of series in the library's own terms, so every microgrid
command runs without the plant it came from. The format is the library's
(microgrid.cli.dataset), not any integration's: whatever recorded the data, a
client writes it out as these files. Build a dataset
walks through making one.
my-dataset/
manifest.json what each entry is, and where its file is
plant.toml the plant (plant/2)
engine.toml optional: the engine's settings (engine/2)
series/soc.json a series of values
forecasts/prices.json a series of forecasts, as they were received
...
manifest.json¶
{
"format": "dataset/1",
"entries": {
"soc": {"file": "series/soc.json", "kind": "number", "forecasts": false},
"prices": {"file": "forecasts/prices.json", "kind": "price", "forecasts": true}
}
}
| Field | Meaning |
|---|---|
format |
always "dataset/1" |
entries |
each entry by its name |
entries.<name>.file |
the entry's file, relative to the directory; it must stay inside it |
entries.<name>.kind |
how its values read: number, bool, price or quantiles |
entries.<name>.forecasts |
true for a series of forecasts, false for a series of values |
limits |
optional: how the record's silences are judged (below) |
A name is letters, digits, _, - and ..
limits¶
Optional, each key too: how long a silence the record's recorder can have before what it recorded is unknown. A command's flag wins over it; where neither says, the default holds.
| Key | Default | Meaning |
|---|---|---|
telemetry_seconds |
180 | the meters are unknown once the heartbeat has been silent this long (--stale-after) |
dc_stale_after_seconds |
300 | a DC-bus load's reading other than 0, silent this long, is stale: the 0 that ended its draw was lost (--dc-stale-after) |
The defaults were measured on one plant (its heartbeat reported every few seconds and was silent at most 157 s in a week; its DC charger wrote every 5 s while it drew, and was silent at most 103 s). A dataset from another recorder states its own:
Values by kind¶
| Kind | A value | Example |
|---|---|---|
number |
a finite number | 0.42 |
bool |
true or false |
true |
price |
import and export per energy unit | {"import": 0.30, "export": 0.07} |
quantiles |
any quantiles between 0 and 1 that include the median, "0.5" |
{"0.1": 1.7, "0.5": 2.8, "0.9": 3.4}, {"0.25": 2.1, "0.5": 2.8, "0.75": 3.2} or {"0.5": 2.8} |
null in place of a value means no value from that sample to the next: the source
reported none, or the writer cannot vouch for that stretch.
A series file (forecasts: false)¶
{
"unit": "%",
"known_until": "2026-10-27T23:00:00+00:00",
"samples": [
["2026-10-20T22:00:00+00:00", 50.0],
["2026-10-20T22:10:00+00:00", 49.3],
["2026-10-21T03:00:00+00:00", null]
]
}
samplesare[time, value]pairs in time order, each value holding until the next (Series). Many recorders store only changes; write their rows as they are, without filling in repeats.known_untilis how far the writer vouches that no change went unreported. No sample may be later than it. Past it, the series has no value.unitis the source's unit, converted on reading (Units, below).
A forecasts file (forecasts: true)¶
Each forecast issue, as the client received it:
{
"unit": "kW",
"known_until": "2026-10-27T23:00:00+00:00",
"issues": [
{
"received_at": "2026-10-21T04:00:30+00:00",
"issued_at": "2026-10-21T04:00:00+00:00",
"source": "my-pv-forecast",
"intervals": [
["2026-10-21T13:00:00+00:00", "2026-10-21T13:30:00+00:00", {"0.1": 1.68, "0.5": 2.80, "0.9": 3.36}],
["2026-10-21T13:30:00+00:00", "2026-10-21T14:00:00+00:00", {"0.1": 1.35, "0.5": 2.25, "0.9": 2.70}]
]
},
{"received_at": "2026-10-21T05:00:30+00:00", "issue": null}
]
}
| Field | Meaning |
|---|---|
received_at |
when the client had the issue: what the engine reads it by |
issued_at |
the source's own stamp: what the issue's age is judged by |
source |
a label, for the record |
intervals |
[start, end, value], half-open [start, end), sorted, not overlapping; a gap is unknown, never an error |
"issue": null |
the source answered nothing at received_at; the issue before it stays in force until it is too old |
Times¶
ISO 8601 with an offset, every one. They are read as UTC. A time without an offset is an error.
Units¶
Each file states its source's unit. Reading the dataset converts it to the engine's and refuses a unit it does not know (Units lists them).
The names¶
The names are conventional; Inputs defines what each holds and which command needs which.
| Name | Kind | Forecasts | What it holds |
|---|---|---|---|
prices |
price |
yes | the prices as received |
pv_forecast, or pv_forecast.<array> for each array |
quantiles |
yes | the PV forecast |
load_forecast |
number |
yes | the AC loads' forecast |
dc_load_forecast |
number |
yes | optional: the DC-bus loads' forecast |
soc |
number |
no | the battery's SOC |
battery_first |
bool |
no | whether the plant runs the dispatch its model describes (the engine's dispatch_as_modelled); without it a plant with a battery falls back, naming work_mode. A plant that always does records one true sample |
each of the plant's meters, by its plant/2 name (default pv, grid, battery, dc_load) |
number |
no | the meters |
house |
number |
no | optional: the AC loads' metered draw |
heartbeat |
number (any values) |
no | optional: any series whose samples prove the readings were flowing; without one, each series is trusted up to its known_until |
import_price, export_price |
number |
no | optional: the prices in force as recorded; without them, the ledger takes those the price forecasts put in force |
inverter |
number |
no | optional: the AC power at the battery's converter, for fit-battery |
The names are kept as dataset/1 gave them (battery_first, inverter); a new name
would be a new format.
pv_forecast.<array> joins the entry name and the array's name with a dot, for a plant
whose arrays have both couplings. plant/2's default PV meters for such a plant are
named pv:<array>, with a colon, which a dataset name cannot hold: name those meters
yourself in plant/2 ([[meters]], for example pv_roof) to record them in a dataset.