microgrid.cli.history¶
What a plant's meters recorded, read for the commands that score against them: the
battery's lots (ledger), the plant model against its meters (check-plant), the
valuations' scores (replay) and the battery's fit (fit-battery).
The record is read through History, a port a dataset answers
(DatasetHistory) and any recorder a client has (a
service's database). Bindings say which of its series is which: the
plant's meters, the SOC, the house meter, the heartbeat that proves the telemetry path
alive, and the recorded prices.
The meters are integrated by the library's ledger (microgrid.engine.ledger: PV, grid,
battery and a DC-bus load, allocated at every change, only while the path was alive:
within the heartbeat's limit where the bindings name one, else wherever each series is
known), the SOC read at each slot boundary, and the prices each slot was charged at as
the time-weighted mean of the recorded price in force over it (series.means): any
price resolution, on any slot, and no price read before it took effect. A recorder that
writes a change late (a sensor updating seconds after the hour) is the client's to
correct at its edge, as for any series. A PV lot's basis is the export price when it was
charged (what the site gave up to store it), a grid lot's the import price
(docs/methodology/lots.md). Every function takes the slot it works on
(EngineConfig.slot; SLOT by default).
DAY
module-attribute
¶
How far back a value is carried into a window: a day (a sensor that holds still, PV at night, writes nothing).
WEEK
module-attribute
¶
Read raw meters a week at a time: a busy sensor writes ~15k rows a day.
TELEMETRY
module-attribute
¶
How long a heartbeat may be silent before the readings it vouches for are unknown,
where the configuration says nothing (a dataset's limits, a command's
--stale-after). Three minutes suits a heartbeat that reports every few seconds:
it was measured on one home's plant, whose heartbeat's longest silence in a week was
157 s. A recorder with another cadence sets its own.
DC_STALE_AFTER
module-attribute
¶
How long a DC-bus load's reading other than 0 may be silent before it is stale (the 0
that ended its draw was lost), where the configuration says nothing (a dataset's
limits, a command's --dc-stale-after). Five minutes suits a meter that writes
every few seconds while it draws: measured on one home's DC charger, which wrote
every 5 s (median) and was silent at most 103 s during a session. A meter with another
cadence sets its own.
Limits
dataclass
¶
How a record's silences are judged: the heartbeat's (TELEMETRY by
default) and a DC-bus load's reading other than 0 (DC_STALE_AFTER).
LotsError
¶
Bases: RuntimeError
The record cannot be read as the commands need it: no prices, no SOC.
History
¶
Bases: Protocol
States as recorded, each as a series over [start, end) from the value in force
at start (the last within carry_in before it), known until end.
chunk: read so much at a time (a recorder's concern; a dataset ignores it).
power_series
async
¶
power_series(
entity_id: str,
start: datetime,
end: datetime,
*,
carry_in: timedelta,
chunk: timedelta = WEEK,
) -> Series[float]
Power in kW, converted from the entity's unit.
numeric_series
async
¶
numeric_series(
entity_id: str,
start: datetime,
end: datetime,
*,
carry_in: timedelta,
chunk: timedelta = WEEK,
) -> Series[float]
Any numeric entity's values, the unit unchecked.
sample_times
async
¶
sample_times(
entity_id: str,
start: datetime,
end: datetime,
*,
carry_in: timedelta,
chunk: timedelta = WEEK,
) -> tuple[datetime, ...]
When the entity's rows arrived (a liveness sensor's).
DcLoadMeter
dataclass
¶
A DC-bus load's meter, and its own rules: a reader that writes only on change has
its last reading carried in from carry_in back, and a reading other than 0
silent for stale_after is stale.
Bindings
dataclass
¶
Bindings(
plant: Plant,
pv: str,
grid: str,
battery: str,
soc: str,
soc_unit: str,
house: str,
heartbeat: str | None,
telemetry: timedelta,
import_price: str,
export_price: str,
inverter: str | None = None,
dc_load: DcLoadMeter | None = None,
)
Which recorded series is which, for a plant (its meters: PV in total, the grid, the battery, and a DC-bus load where it has one).
heartbeat
instance-attribute
¶
A series whose samples prove the telemetry path alive, and how long one does
(TELEMETRY). None: no heartbeat; each series is then trusted as far as it
is known (Series.known_until), its gaps as samples with no value.
import_price
instance-attribute
¶
The import and export prices in force, as recorded.
inverter
class-attribute
instance-attribute
¶
The battery inverter's AC power, > 0 out (None: not metered).
Recorded
dataclass
¶
Recorded(
starts: tuple[datetime, ...],
flows: tuple[Flows | None, ...],
p_import: tuple[float, ...],
p_export: tuple[float, ...],
stored_after: tuple[float | None, ...],
stored_before: float | None,
step: timedelta = SLOT,
)
LedgerRun
dataclass
¶
read_recorded
async
¶
read_recorded(
history: History,
bindings: Bindings,
start: datetime,
end: datetime,
*,
step: timedelta = SLOT,
) -> Recorded
What was recorded over [start, end), slot by slot of step.
A slot's prices are the time-weighted means of the recorded prices in force over it;
a slot without both moves nothing (its flows None). Raises LotsError when no
slot of the window has a recorded price.
step_slots
¶
recorded's slots, each stepped through ledger: with plant, each slot
also as its model's (Plant.metered_slot), so a ledger with the plant's storage
books each charge by its path (Ledger).
run_ledger
async
¶
run_ledger(
history: History,
bindings: Bindings,
start: datetime,
end: datetime,
*,
step: timedelta = SLOT,
) -> LedgerRun
The ledger over [start, end) on slots of step; what the battery held at
start is unknown energy.
stored_costs
¶
stored_costs(
recorded: Recorded,
plant: Plant,
lots: Series[Lots] | None = None,
) -> list[float | None]
Per slot of recorded, the running average cost of what the battery held at
its start, per stored kWh (Ledger.pool): what a battery kWh realised
under the stored-cost rule. From lots (the ledger the valuations read, so both
sides start alike) where they are in force; else the ledger over recorded, all
it held at the window's start of unknown origin, at the first slot's import, as if
bought (R-7).
read_recording
async
¶
read_recording(
history: History,
bindings: Bindings,
pinned: Plant,
first: date,
last: date,
*,
step: timedelta = SLOT,
) -> lgt.Recording
The local days first to last's meters for the ground truth, on slots of
step, split through the pinned conversion without the DC-bus loads
(lgt.meters_plant), and the DC-bus load's meter where the plant has one.