Skip to content

microgrid.cli.offline

The library run on a dataset: the setup from the dataset's configuration files, one valuation at a moment, and the live value over a span, each read from the dataset as a client would have had it then; and the dataset's bindings, for the commands that read what its meters recorded. Everything is on the engine's slot (EngineConfig.slot, the dataset's engine.toml).

TELEMETRY module-attribute

TELEMETRY: Final = 'telemetry'

A client's own cause, beside the engine's: the heartbeat says the telemetry path was silent, so nothing measured can be vouched for.

PLANT_FILE module-attribute

PLANT_FILE = 'plant.toml'

ENGINE_FILE module-attribute

ENGINE_FILE = 'engine.toml'

LOTS_RUN_IN module-attribute

LOTS_RUN_IN: Final = timedelta(days=2)

How far back microgrid now runs the ledger for the battery's lots: two days drew down what the battery held before (energy of unknown origin) at every slot of a 16-day replay on one home's plant. A battery held full for longer keeps some of it.

Setup dataclass

Setup(plant: Plant, engine: Engine)

The plant and the engine a dataset is run with.

plant instance-attribute

plant: Plant

engine instance-attribute

engine: Engine

read_plant

read_plant(path: Path) -> PlantConfig

The plant's file (plant/2, TOML) at path; its timezone names the zone. Raises PlantConfigError naming each problem.

load_setup

load_setup(
    data: Path,
    plant: Path | None = None,
    engine: Path | None = None,
    *,
    beside: bool = True,
) -> Setup

The plant from plant (default: the dataset's plant.toml), the engine from engine (default: the dataset's engine.toml where beside, else the library's defaults).

engine_inputs

engine_inputs(
    setup: Setup,
    read: Inputs,
    at: datetime,
    lots: Series[Lots] | None = None,
) -> Inputs

The engine's inputs at at from a dataset's (inputs.inputs), with the battery's lots (dataset_lots). A plant with DC-bus loads and no forecast of them takes a forecast of 0, labelled so (inputs.no_dc_forecast).

client_causes

client_causes(
    setup: Setup, dataset: Dataset
) -> tuple[str, ...]

What the command assumed in valuing dataset, named beside the engine's causes (never in place of them): a plant with DC-bus loads and no forecast of them valued on 0 kW (ASSUMED_DC_LOAD).

limits_of

limits_of(
    dataset: Dataset, given: Limits | None = None
) -> Limits

How the dataset's record is judged: given (a command's flags), else the dataset's own (its manifest's limits), else the defaults.

with_recorded_prices

with_recorded_prices(dataset: Dataset) -> Dataset

dataset with the prices in force as recorded series (import_price, export_price), derived from its price forecasts where it records none (inputs.prices_in_force): a dataset need not carry the prices twice. Recorded ones are per kWh, all in one currency (inputs.recorded_prices).

dataset_lots_async async

dataset_lots_async(
    setup: Setup,
    dataset: Dataset,
    *,
    limits: Limits | None = None,
) -> Series[Lots] | None

The battery's lots over the dataset, after each slot (the ledger over what its meters recorded, history.read_recorded, on the engine's slot): what a valuation at a moment reads as the energy the battery holds then. The prices are the recorded ones, else those its forecasts put in force (with_recorded_prices); the path is alive within the heartbeat's limit where it has one (limits: limits_of), else wherever each series is known. None where the dataset cannot say (no battery, no recorded meters or SOC, or no prices): the engine then values all the battery holds as of unknown origin, at the import, and names battery_lots; lots_missing says why. The ledger starts at the dataset's first whole slot, all it holds then of unknown origin.

lots_missing

lots_missing(
    setup: Setup,
    dataset: Dataset,
    *,
    limits: Limits | None = None,
) -> str | None

Why dataset_lots gives none for a plant with a battery; None where it gives lots, or the plant has no battery.

lots_or_why async

lots_or_why(
    setup: Setup,
    dataset: Dataset,
    *,
    limits: Limits | None = None,
) -> tuple[Series[Lots] | None, str | None]

dataset_lots_async's lots, and where it gives none for a plant with a battery, why (else None).

dataset_lots

dataset_lots(
    setup: Setup,
    dataset: Dataset,
    *,
    limits: Limits | None = None,
) -> Series[Lots] | None

dataset_lots_async, for a caller outside an event loop.

value

value(
    setup: Setup,
    dataset: Dataset,
    at: datetime,
    *,
    limits: Limits | None = None,
) -> Valuation

The valuation at at from what the dataset says was received by then (unpriced without a price for the slot under way), the battery's lots from the dataset's meters (dataset_lots).

value_with

value_with(
    setup: Setup,
    dataset: Dataset,
    at: datetime,
    lots: Series[Lots] | None,
) -> Valuation

value with the battery's lots given (read once for many valuations).

live

live(
    setup: Setup,
    dataset: Dataset,
    start: datetime,
    end: datetime,
    *,
    every: timedelta,
    issue_every: timedelta,
    valid_for: timedelta,
    limits: Limits | None = None,
) -> Iterator[tuple[datetime, LiveValue]]

The live value every every in [start, end), valued again at the first reading in each issue_every from start (each valuation valid for valid_for), as a client running the library would have had it. A valuation that comes to nothing (unpriced) leaves the last one in place, as the service keeps its last issue (past its valid_until, the engine names issue).

dataset_bindings

dataset_bindings(
    setup: Setup,
    dataset: Dataset,
    *,
    limits: Limits | None = None,
) -> Bindings

The dataset's entries as the record the commands read (dataset.<name>, read by DatasetHistory), for its plant (setup), its silences judged by limits (limits_of). Its heartbeat where it has one (else each series is trusted as far as it is known), its house meter and its inverter's AC meter where it has them.