Skip to content

Reference

Generated from docstrings at build time (DOC-4); never edit by hand.

myenergy

MyEnergyOptimizer: simulate electricity dispatch decided by user-written logic rules.

Built on SmartMDAO. Scope and design: planning/SPEC.md.

adapters

Adapters implementing the ports: Enedis, CSV, weather APIs, tariffs, storage, jobs.

errors

Typed errors of the adapters (PY-9).

AdapterError

Bases: MyEnergyError

An adapter could not read or reach its data.

DataFormatError

Bases: AdapterError

Input data breaks its declared format: the message says where and how.

packs

Tariff packs stored as folders of TOML files.

toml_files

Tariff packs as folders: pack.toml, a rates/ folder and an optional schedules.toml.

pack.toml describes the pack (ADR 0008)::

[pack]
name = "fr-household-vat-study"
title = "French households, with VAT at 5.5 % on everything"
extends = "fr-household"          # optional: inherit everything not redefined here
max_kva = 36                      # optional
sharing_step_minutes = 30         # optional: step of shared-energy allocation
tags = { country = "FR", purpose = "research" }

[fields.status]                   # contract fields: allowed values, optional default
values = ["consumer", "collective", "producer-injection"]
default = "consumer"

[network]                         # off-peak template on the contract's hours...
seasons.high = { dates = "11-01..03-31", peak = "HPH", off_peak = "HCH" }
seasons.low = { dates = "04-01..10-31", peak = "HPB", off_peak = "HCB" }
# ...or year-round:  peak = "HP"  off_peak = "HC"
# ...or fixed:       schedule = "<a schedule of schedules.toml>"

[supply]                          # supply options, named by a field
field = "supply_option"
options.base = { single = "base" }
options.hp-hc = { peak = "HP", off_peak = "HC" }

[[checks]]                        # data, not code: errors or warnings
level = "warning"
message = "8 off-peak hours a day is the norm"
when = { fta = ["CU4", "MU4"] }
off_peak_hours = 8

A pack that extends another inherits it: tags and fields merge by name; max_kva, sharing_step_minutes, network, supply and checks are replaced when given (checks = [] drops them all); a rate key or a schedule redefined here hides the inherited one.

MAX_BYTES module-attribute
MAX_BYTES = 256 * 1024

A pack.toml or schedules.toml is small; larger ones are refused (SEC-1).

TomlPackSource

Every pack found in the given folders (each pack is a sub-folder with a pack.toml).

catalog
catalog() -> Sequence[TariffPack]

Every pack, sorted by name.

pack
pack(name: str) -> TariffPack

The pack called name.

Raises:

Type Description
UnknownPackError

no such pack.

rates
rates(name: str) -> RateSource

The rates of pack name on top of those of the packs it extends.

Raises:

Type Description
UnknownPackError

no such pack.

DataFormatError

a malformed rate file.

schedules
schedules(name: str) -> Mapping[str, Schedule]

The schedules pack name ships, inherited ones included.

Raises:

Type Description
UnknownPackError

no such pack.

price_book
price_book(name: str) -> PriceBook

The price book of pack name on top of those of the packs it extends.

Raises:

Type Description
UnknownPackError

no such pack.

DataFormatError

a malformed price-book.toml, one that carries rates (pack rates go in rates/), or one that removes a line it does not inherit.

builtin_packs
builtin_packs() -> Traversable

The folder of the packs shipped with the package.

pricebooks

Price books stored as TOML files.

toml_files

Price books from TOML (ADR 0010).

The same format serves a tariff pack's regulated book (price-book.toml in the pack folder) and a user's book (a supplier's lines, with the supplier's dated prices in the same file)::

[book]
name = "my-supplier"
remove = []                                   # lines of the pack's book to drop
inclusion = { network = "included" }          # default per layer: itemised | included
settings = { proration_days = 365, rounding = 0.01, vat_on_rounded = true }

[[lines]]
name = "supply.energy"
layer = "supply"
basis = "per_kwh"            # fixed | per_kwh | tiered_per_kwh | percent_of | once
flow = "supplied"
schedule = "supply"                           # network | supply | a named schedule
rate = { component = "supply.energy", selectors = { period = "{period}" } }
vat = "consumption"

[[lines]]
name = "cta"
basis = "percent_of"
of = ["turpe.management", "turpe.metering", "turpe.power"]   # or: of_vat = "subscription"
rate = { component = "cta", selectors = { network = "distribution" } }
vat = "subscription"

[sources.my-offer]                            # a user's dated prices, as in rate files
title = "My supplier's price list"

[[rates]]
component = "supply.energy"
unit = "EUR/kWh"
by = "period"

[[rates.versions]]
from = 2025-08-01
source = "my-offer"
status = "user"
value = { HP = 0.2, HC = 0.15 }

Other line keys: payee, counterparty (shared flows), credit = true (revenue), inclusion, when = { field = [values] }, and for tiered prices reset and tiers. Anything malformed is a DataFormatError naming the file.

MAX_BYTES module-attribute
MAX_BYTES = 512 * 1024

Price books are small; larger inputs are refused (SEC-1).

LoadedBook dataclass

A price book, the dated prices it carries (if any), and whether it set its settings.

load_price_book
load_price_book(
    text: str, *, origin: str = "<text>"
) -> LoadedBook

The price book in text, with its own dated prices if it carries sources/rates.

Raises:

Type Description
DataFormatError

invalid TOML, an unknown key, or a book that breaks a rule.

read_price_book
read_price_book(
    path: Path, *, max_bytes: int = MAX_BYTES
) -> LoadedBook

The price book in the TOML file at path.

Raises:

Type Description
DataFormatError

the file is too large, not UTF-8, or malformed (see load_price_book).

with_user_book
with_user_book(
    packs: PackSource,
    pack: str,
    user: LoadedBook | None = None,
) -> tuple[PriceBook, RateSource]

The pack's book with user's on top, and the pack's rates under user's prices.

Raises:

Type Description
UnknownPackError

no such pack.

PriceBookError

user removes a line the pack's book does not have.

profiles

Adapters implementing the ProfileSource port.

csv_files

User-supplied profiles as CSV files: strict, small, and never guessed (SEC-1, SEC-2).

Each file has exactly the header timestamp,value. Timestamps are ISO 8601 with a UTC offset, strictly increasing, one regular step that divides an hour; values are finite numbers in the declared unit. Anything else is a DataFormatError naming the file and line.

MAX_BYTES module-attribute
MAX_BYTES = 20 * 1024 * 1024

About ten years of 5-minute values; larger inputs are refused (SEC-1).

CsvUnit

Bases: StrEnum

What the value column holds.

CsvProfile dataclass

One CSV file and what it means.

CsvProfileSource

A ProfileSource over a fixed list of checked CSV files.

name property
name: str

The source name used in ProfileRef.source.

catalog
catalog() -> Sequence[ProfileMeta]

Every configured profile.

shape
shape(key: str, grid: TimeGrid) -> TimeSeries

The profile key on grid (kW profiles are tagged dimensionless until scaled).

load
load(
    profile: CsvProfile, max_bytes: int = MAX_BYTES
) -> tuple[ProfileMeta, TimeSeries]

Read and check one CSV profile; kWh per step becomes kW.

Raises:

Type Description
DataFormatError

the file breaks the format (the message says where).

enedis

Enedis "coefficients de profils" stored as parquet files (one per sub-profile).

Files are named <Category>_<SUBPROFILE>.parquet with a UTC timestamp column labelled by interval start (SPEC decision 10) and coefficient columns, by default coefficient_ajuste (decision 11). The resolution changed from 30 to 15 minutes on 2024-10-04 22:00 UTC: each value is held over its own interval, on the finest native step. Gaps, duplicates and missing values are errors, never filled (decision 9).

EnedisParquetSource

A ProfileSource over a folder of Enedis parquet files.

Raises:

Type Description
AdapterError

the folder does not exist.

name property
name: str

The source name used in ProfileRef.source.

catalog
catalog() -> Sequence[ProfileMeta]

Every sub-profile found in the folder.

shape
shape(key: str, grid: TimeGrid) -> TimeSeries

The shape of sub-profile key on grid.

memory

Shapes held in memory: the base of the CSV and synthetic sources, and a reference adapter.

InMemoryProfileSource

A ProfileSource over start-labelled shapes already loaded in memory.

Raises:

Type Description
SeriesError

a shape is not dimensionless, or does not sit on its declared coverage.

UnknownProfileError

a profile's key differs from the key it is registered under.

name property
name: str

The source name used in ProfileRef.source.

catalog
catalog() -> Sequence[ProfileMeta]

Every profile, in registration order.

shape
shape(key: str, grid: TimeGrid) -> TimeSeries

The shape of key resampled onto grid.

Raises:

Type Description
UnknownProfileError

no profile key in this source.

CoverageError

grid reaches outside the profile's data.

plugins

Plugins for the built-in profile sources, registered as entry points like any third party's.

EnedisPlugin

Options: root (the parquet folder; without it the source is off) and column.

create
create(options: Mapping[str, str]) -> ProfileSource | None

The Enedis source over root, or None when no folder is configured.

SyntheticPlugin

Options: seed (int), latitude and longitude (degrees). Always available.

create
create(options: Mapping[str, str]) -> ProfileSource | None

The synthetic source with the configured seed and PV location.

synthetic

Deterministic synthetic shapes for tests, notebooks and quick studies.

Values depend only on the time and the seed, never on the requested window: each UTC day is generated at 5-minute resolution with its own random generator (seed, day), then resampled. Every shape is normalised to a mean of 1 over the reference year 2021, like an Enedis coefficient.

SyntheticProfileSource

Seeded household, clear-sky PV and constant shapes over 2000-2100.

name property
name: str

The source name used in ProfileRef.source.

catalog
catalog() -> Sequence[ProfileMeta]

The three synthetic profiles.

shape
shape(key: str, grid: TimeGrid) -> TimeSeries

The shape key on grid, identical whatever window it is requested for.

Raises:

Type Description
UnknownProfileError

no such synthetic profile.

CoverageError

grid reaches outside 2000-2100.

rates

Adapters that provide dated rates (RateSource).

layered

Rates in layers: a key found in an upper layer hides the whole history of the lower ones.

A pack that extends another puts its own rates on top: redefining a key (say vat for a tax study) replaces that key's history; every other key is inherited.

LayeredRateSource

layers[0] on top; each key comes from the first layer that holds it.

catalog
catalog() -> Sequence[RateKey]

Every key of every layer, sorted by component then selectors.

history
history(key: RateKey) -> RateHistory

The history of key from the topmost layer that holds it.

Raises:

Type Description
UnknownRateError

no layer holds key.

toml_files

Dated rates from TOML files, reviewed like code (ADR 0006).

A file declares the documents it cites under [sources.<id>] and its rates under [[rates]]. Each rate has a component, selectors, the unit as published (converted on load to a canonical RateUnit) and versions. by names one more selector; a version's value is then a table over it, for example the four time classes of one network option::

[sources.cre-2025-78]
title = "CRE deliberation n° 2025-78 of 2025-03-13: TURPE 7 HTA-BT from 2025-08-01"
url = "https://www.cre.fr/..."

[[rates]]
component = "turpe.withdrawal.energy"
selectors = { segment = "BT<=36kVA", fta = "CU4" }
unit = "cEUR/kWh"
by = "time_class"

[[rates.versions]]
from = 2025-08-01
source = "cre-2025-78"
status = "official"
value = { HPH = 7.49, HCH = 3.97, HPB = 1.66, HCB = 1.16 }

Versions of one key may come from several files. A version without to lasts until the next version of the same key starts, or stays open when it is the last. Anything malformed is a DataFormatError naming the file.

MAX_BYTES module-attribute
MAX_BYTES = 1024 * 1024

Rate files are small; larger inputs are refused (SEC-1).

UNITS module-attribute
UNITS: dict[str, tuple[RateUnit, Decimal]] = {
    "EUR/year": (RateUnit.EUR_PER_YEAR, Decimal(1)),
    "EUR/kVA/year": (RateUnit.EUR_PER_KVA_YEAR, Decimal(1)),
    "EUR/month": (RateUnit.EUR_PER_MONTH, Decimal(1)),
    "EUR": (RateUnit.EUR, Decimal(1)),
    "EUR/kWh": (RateUnit.EUR_PER_KWH, Decimal(1)),
    "cEUR/kWh": (RateUnit.EUR_PER_KWH, Decimal("0.01")),
    "EUR/MWh": (RateUnit.EUR_PER_KWH, Decimal("0.001")),
    "%": (RateUnit.FRACTION, Decimal("0.01")),
}

Published unit → canonical unit and the exact factor between them.

TomlRateSource

Rates read once from TOML files; the files together form one source.

catalog
catalog() -> Sequence[RateKey]

Every key, sorted by component then selectors.

history
history(key: RateKey) -> RateHistory

Every version of key.

Raises:

Type Description
UnknownRateError

no file defines key.

sites

Site descriptions stored as TOML files.

toml_files

Sites from TOML: a contract under a tariff pack, assets by kind, and shared energy (ADR 0009).

One file may describe several sites (a community is several sites)::

[sites.home]
pack = "fr-household"

[sites.home.contract]
subscribed_kva = 6
off_peak = "22:00-06:00"                      # as printed on a bill
seasonal_off_peak = { low = "02:00-06:00 11:00-15:00" }   # optional
fields = { fta = "CU4", supply_option = "hp-hc" }
supply_schedule = "ev-offer"                  # optional: a schedule shipped by the pack

[[sites.home.assets]]
id = "house"
kind = "consumer"
profile = { source = "synthetic", key = "household", annual_kwh = 3500 }

[[sites.home.assets]]
id = "roof"
kind = "pv"
profile = { source = "synthetic", key = "pv-clear-sky", peak_kwp = 3, yield_kwh_per_kwp = 1100 }

[[sites.home.shares]]
direction = "in"
counterparty = "street-operation"
profile = { source = "csv", key = "allocated", as_measured = true }

A profile gives its source and key, an optional time_shift_minutes, and exactly one scaling: annual_kwh, peak_kwp with yield_kwh_per_kwp, or as_measured = true. Anything malformed is a DataFormatError naming the file and the site.

MAX_BYTES module-attribute
MAX_BYTES = 256 * 1024

Site files are small; larger inputs are refused (SEC-1).

load_sites
load_sites(
    text: str, packs: PackSource, *, origin: str = "<text>"
) -> dict[str, SiteSpec]

Every site described in text, by id; contracts are checked against their packs.

Raises:

Type Description
DataFormatError

invalid TOML, an unknown field, an unknown pack or schedule, or a site that breaks a rule (a pack check of level error included).

Warns:

Type Description
TariffWarning

a pack check of level warning.

read_sites
read_sites(
    path: Path,
    packs: PackSource,
    *,
    max_bytes: int = MAX_BYTES,
) -> dict[str, SiteSpec]

Every site in the TOML file at path, by id.

Raises:

Type Description
DataFormatError

the file is too large, not UTF-8, or malformed (see load_sites).

tariffs

Adapters that read user-defined tariff data (schedules, later price books).

schedules

User-defined time-of-use schedules from TOML.

One file may hold several schedules, each under [schedules.<name>]::

[schedules.ev-offer]
default = "peak"                      # instants no period claims (optional)

[schedules.ev-offer.seasons]          # optional; each date of the year in exactly one season
winter = "11-01..03-31"
summer = { dates = "04-01..10-31", default = "summer-peak" }

[[schedules.ev-offer.periods]]
name = "super-off-peak"
windows = "02:00-06:00"               # as printed on a bill; leave out for the whole day

[[schedules.ev-offer.periods]]
name = "off-peak"
windows = "22:00-02:00 06:00-08:00"
days = ["sat", "sun"]                 # labels of the day calendar (optional)
seasons = ["winter"]                  # declared seasons (optional)

Anything malformed or ambiguous is a DataFormatError naming the file and the schedule.

MAX_BYTES module-attribute
MAX_BYTES = 256 * 1024

Schedule files are small; larger inputs are refused (SEC-1).

load_schedules
load_schedules(
    text: str, *, origin: str = "<text>"
) -> dict[str, Schedule]

Every schedule in text, by name.

Raises:

Type Description
DataFormatError

invalid TOML, an unknown field, or a schedule that breaks a rule.

read_schedules
read_schedules(
    path: Path, *, max_bytes: int = MAX_BYTES
) -> dict[str, Schedule]

Every schedule in the TOML file at path, by name.

Raises:

Type Description
DataFormatError

the file is too large, not UTF-8, or malformed (see load_schedules).

api

HTTP API (FastAPI): schemas, routers and the mapping between schemas and the domain.

bootstrap

Composition root: the only place that wires concrete adapters to ports (ADR 0002, MEO-DI-1).

make_app_container() builds the APP-scoped container: settings, the configured profile sources, the ProfileResolver, the PackSource (built-in and user tariff packs) and the SiteResolver (with every installed asset kind) and the PriceBookResolver (with every installed price basis). Tests pass their own Settings and plugin registries instead of patching anything (DI-3).

AppProvider

Bases: Provider

APP scope: settings, profiles, tariff packs, sites and price books.

settings
settings() -> Settings

The application settings.

profile_sources
profile_sources(
    settings: Settings,
) -> Mapping[str, ProfileSource]

Every configured source, by name; plugins without configuration are left out.

profile_resolver
profile_resolver(
    sources: Mapping[str, ProfileSource],
) -> ProfileResolver

The resolver over every configured source.

packs
packs(settings: Settings) -> PackSource

The built-in tariff packs and those in settings.pack_roots.

Raises:

Type Description
DataFormatError

a malformed pack, or two packs with one name.

site_resolver
site_resolver(
    profiles: ProfileResolver, packs: PackSource
) -> SiteResolver

Sites resolved with every installed asset kind.

price_book_resolver
price_book_resolver() -> PriceBookResolver

Price books resolved with every installed price basis.

profile_source_plugins

profile_source_plugins() -> Registry[ProfileSourcePlugin]

Every installed profile-source plugin (built-in or third-party).

asset_kind_plugins

asset_kind_plugins() -> Registry[AssetKind]

Every installed asset kind (built-in or third-party).

price_basis_plugins

price_basis_plugins() -> Registry[PriceBasis]

Every installed price basis (built-in or third-party).

make_app_container

make_app_container(
    settings: Settings | None = None,
    plugins: Registry[ProfileSourcePlugin] | None = None,
    asset_kinds: Registry[AssetKind] | None = None,
    price_bases: Registry[PriceBasis] | None = None,
) -> Container

The application container (call close() when done).

registry

Typed registries filled from entry points (MEO-PLG-1, MEO-PLG-2).

RegistryError

Bases: MyEnergyError

A plugin is missing, duplicated, or not what its group expects.

Registry

Named plugins of one kind, in registration order.

register
register(name: str, item: T) -> None

Add item under name.

Raises:

Type Description
RegistryError

name is already registered.

get
get(name: str) -> T

The plugin registered under name.

Raises:

Type Description
RegistryError

nothing is registered under name.

names
names() -> tuple[str, ...]

Every registered name.

load_entry_points
load_entry_points(
    group: str,
    kind: str,
    check: type[T],
    points: Iterable[EntryPoint] | None = None,
) -> Registry[T]

A registry with every plugin of the entry-point group, checked to be a check.

points defaults to the installed entry points of group.

Raises:

Type Description
RegistryError

an entry point does not provide a check (runtime protocol check).

settings

Application settings, from the environment or a git-ignored .env file (SEC-7).

Each profile source reads its own options, MYENERGY_SOURCES__<SOURCE>__<OPTION>, for example MYENERGY_SOURCES__ENEDIS__ROOT=/data/enedis or MYENERGY_SOURCES__SYNTHETIC__SEED=7. ENEDIS_DB_PATH is also accepted for the Enedis folder. MYENERGY_PACK_ROOTS lists folders of user-written tariff packs, as JSON ('["/data/packs"]'), searched beside the built-in ones.

Settings

Bases: BaseSettings

Every setting of the application.

sources class-attribute instance-attribute
sources: dict[str, dict[str, str | int | float | bool]] = (
    Field(default_factory=dict)
)

Environment values arrive parsed as JSON (7 is an int); options() returns text.

pack_roots class-attribute instance-attribute
pack_roots: list[Path] = Field(default_factory=list[Path])

Folders of user-written tariff packs (ADR 0008).

options
options(source: str) -> dict[str, str]

Options of one source (names are case-insensitive), with ENEDIS_DB_PATH folded in.

domain

Pure domain model: time grid, series, assets, topology, state, decisions, flows, KPIs.

Imports only the standard library and numpy (MEO-ARC-1). Every value is immutable.

contract

A supply contract, as generic data: power, free fields, off-peak hours and two schedules.

Nothing here is specific to a country or an offer. What the fields mean, which values they may take, and which rules apply to them come from a tariff pack (myenergy.domain.packs); the pack also turns the contract into its network and supply schedules. Fields double as rate selectors: a field fta = "CU4" selects the rates whose key has fta=CU4.

Contract dataclass

One connection's contract under a tariff pack.

Build it with TariffPack.contract(...), which checks it against the pack's rules and fills the pack's defaults.

Raises:

Type Description
ContractError

no pack, a power that is not positive, or a field repeated.

fields class-attribute instance-attribute
fields: tuple[tuple[str, str], ...] = ()

Free fields, sorted by name (for example fta, status).

off_peak class-attribute instance-attribute
off_peak: TimeWindows = NO_WINDOWS

The connection's off-peak hours, in every season without its own.

seasonal_off_peak class-attribute instance-attribute
seasonal_off_peak: tuple[tuple[str, TimeWindows], ...] = ()

Off-peak hours of particular seasons of the pack's network schedule.

network class-attribute instance-attribute
network: Schedule | None = None

An explicit network schedule, instead of the pack's.

supply class-attribute instance-attribute
supply: Schedule | None = None

The supplier's own schedule, instead of the pack's supply option.

selectors property
selectors: Mapping[str, str]

The fields, by name.

field
field(name: str) -> str

The value of field name.

Raises:

Type Description
ContractError

the contract has no such field.

off_peak_for
off_peak_for(season: str | None) -> TimeWindows

Off-peak hours of season (its own, else the general ones).

errors

Typed errors of the domain (PY-9); every project error derives from MyEnergyError.

MyEnergyError

Bases: Exception

Root of every error raised by MyEnergyOptimizer.

DomainError

Bases: MyEnergyError

A domain rule was violated.

GridError

Bases: DomainError

A time grid is invalid, or a timestamp does not fit it.

SeriesError

Bases: DomainError

Time series are incompatible, or a series holds invalid values.

CoverageError

Bases: DomainError

A requested period lies outside the data that would have to cover it.

UnknownProfileError

Bases: DomainError

A profile reference names a source or a key that does not exist.

CalibrationError

Bases: DomainError

A profile cannot be scaled as requested (e.g. a window with no energy).

RateError

Bases: DomainError

A rate is missing, ambiguous, unverified, or its history is inconsistent.

UnknownRateError

Bases: RateError

No rate table has this component and these selectors.

UnverifiedRateError

Bases: RateError

A to-verify rate was requested where only official rates are allowed.

ContractError

Bases: DomainError

A supply contract breaks a tariff rule.

SiteError

Bases: DomainError

A site description is inconsistent, or names an asset kind that does not exist.

FlowError

Bases: DomainError

Energy flows break an invariant: negative energy, or more shared than metered.

PriceBookError

Bases: DomainError

A price book is malformed, or cannot be applied to a contract over a period.

problems lists every problem found, so all of them can be fixed at once.

UnknownPackError

Bases: DomainError

No tariff pack has this name.

ScheduleError

Bases: DomainError

A time-of-use schedule is malformed, ambiguous, or leaves instants without a period.

TariffWarning

Bases: UserWarning

A contract departs from the usual regulated values; it is still priced as given.

flows

Energy flows at a site's meter: what bills charge.

The same kWh is priced differently depending on where it comes from and where it goes, so a bill charges flows, not "consumption". SiteFlows holds, as kWh series on one grid:

  • import and export: what the meter records drawing from and injecting into the network;
  • self_consumption: production used on site, never seen by the meter;
  • shared_in / shared_out: the parts of import / export allocated by a sharing scheme to or from a counterparty (an operation, a neighbour);
  • derived: supplied = import - shared_in (what the supplier sells) and sold = export - shared_out (what goes to the grid buyer).

Flows describe what meters and sharing schemes measure; what each costs is the price book's business. Built from asset powers, flows depend on the grid's step: comparing production and consumption over 30 minutes hides moments when the site both imported and exported, so coarse steps show more self-consumption than really happens (see notebook 06).

TOLERANCE_KWH module-attribute
TOLERANCE_KWH = 1e-09

Rounding noise allowed when checking that flows are non-negative and shares fit.

FlowKind

Bases: StrEnum

The flows a price book can charge.

SharedFlow dataclass

Energy (kWh per step) shared with counterparty in direction.

SiteFlows dataclass

Metered and shared energy of one site; see the module docstring.

Raises:

Type Description
FlowError

a series that is not energy or not on grid, a negative value, a shared total above import or export, or one counterparty twice in one direction.

counterparties property
counterparties: Mapping[ShareDirection, tuple[str, ...]]

The counterparties of each direction.

flow
flow(
    kind: FlowKind, counterparty: str | None = None
) -> TimeSeries

The kWh series of kind; for shared flows, one counterparty or (None) all.

Raises:

Type Description
FlowError

a counterparty for a flow that has none, or an unknown counterparty.

totals
totals() -> Mapping[FlowKind, float]

Energy (kWh) over the grid of every flow kind.

from_powers classmethod
from_powers(
    site: str,
    powers: Sequence[TimeSeries],
    shared: Sequence[SharedFlow] = (),
) -> SiteFlows

Flows of a site whose assets have these signed powers (kW: + produces, - consumes).

Production and consumption are compared per step of the grid.

Raises:

Type Description
FlowError

a power series that is not kW, or not on one common grid.

measured classmethod
measured(
    site: str,
    imported: TimeSeries,
    exported: TimeSeries,
    self_consumed: TimeSeries | None = None,
    shared: Sequence[SharedFlow] = (),
) -> SiteFlows

Flows as a meter recorded them (kW or kWh); a step may hold both import and export.

Raises:

Type Description
FlowError

see the class.

packs

Tariff packs: user-definable rules that give contract fields their meaning.

A pack says which fields a contract has and which values they may take, the subscribed-power limit, checks (errors or warnings, written as data), how a contract's off-peak hours become its network schedule, and which supply options exist. Its rates live beside it (see myenergy.ports.packs). Packs are data: the built-in French household pack is one of them, and users write their own, from scratch or by extending another, for another country, a research scenario or a tax study. Free tags (country, sector, purpose, …) describe a pack.

YEAR_ROUND module-attribute
YEAR_ROUND = OffPeakTemplate(
    (OffPeakSeason(None, "HP", "HC"),)
)

Peak and off-peak all year, on the contract's off-peak hours.

Condition
Condition = tuple[tuple[str, frozenset[str]], ...]

Fields and the values each may take: (("fta", {"CU4", "MU4"}),).

FieldSpec dataclass

A contract field: its allowed values and its default (None: the field is required).

CheckLevel

Bases: StrEnum

What a failed check does.

ERROR class-attribute instance-attribute
ERROR = 'error'

The contract is refused.

WARNING class-attribute instance-attribute
WARNING = 'warning'

A TariffWarning; the contract is priced as given.

ContractCheck dataclass

When the contract matches when, it must match require and have off_peak_hours.

off_peak_hours class-attribute instance-attribute
off_peak_hours: float | None = None

Hours of off-peak a day, in every season of the network schedule.

failures
failures(
    contract: Contract, seasons: tuple[str | None, ...]
) -> list[str]

Why contract fails this check (empty when it passes).

OffPeakSeason dataclass

A season of an off-peak template and the names of its peak and off-peak periods.

season instance-attribute
season: Season | None

None: the whole year.

OffPeakTemplate dataclass

Builds a schedule from a contract's off-peak hours: peak and off-peak, per season.

season_names property
season_names: tuple[str | None, ...]

The seasons' names (None for a year-round template).

renamed
renamed(peak: str, off_peak: str) -> OffPeakTemplate

The same seasons with other period names (one pair for every season).

build
build(name: str, contract: Contract) -> Schedule

The schedule of contract's off-peak hours.

SupplyOption dataclass

A supply option: one period all the time, or peak/off-peak on the contract's hours.

single class-attribute instance-attribute
single: str | None = None

The one period of a single-price option.

TariffPack dataclass

The rules of one tariff framework. Rates are provided alongside, by a PackSource.

tags class-attribute instance-attribute
tags: tuple[tuple[str, str], ...] = ()

Free tags, e.g. (("country", "FR"), ("sector", "household")).

network class-attribute instance-attribute
network: OffPeakTemplate | Schedule = YEAR_ROUND

How the network schedule is built: a template on the contract's hours, or a fixed one.

supply_field class-attribute instance-attribute
supply_field: str | None = None

The field that names the supply option.

sharing_step class-attribute instance-attribute
sharing_step: timedelta | None = None

The step at which shared energy is allocated, if the pack says (e.g. 30 minutes).

tag
tag(key: str) -> str | None

The value of tag key, if any.

contract
contract(
    *,
    subscribed_kva: Decimal | int | str,
    fields: Mapping[str, str] | None = None,
    off_peak: TimeWindows = NO_WINDOWS,
    seasonal_off_peak: Mapping[str, TimeWindows]
    | None = None,
    network: Schedule | None = None,
    supply: Schedule | None = None,
) -> Contract

A contract under this pack, with defaults filled in and every check applied.

Raises:

Type Description
ContractError

an unknown or missing field, a value not allowed, a power above the limit, an unknown season, or a failed check of level error.

Warns:

Type Description
TariffWarning

a failed check of level warning.

network_schedule
network_schedule(contract: Contract) -> Schedule

The contract's own network schedule, or the pack's, built from its off-peak hours.

supply_schedule
supply_schedule(contract: Contract) -> Schedule

The contract's own supply schedule, or the one of its supply option.

Raises:

Type Description
ContractError

neither is available.

tagged
tagged(
    packs: Iterable[TariffPack], **tags: str
) -> list[TariffPack]

The packs having every tag given, e.g. tagged(packs, country="FR").

pricebooks

Price books: what a bill charges, as data (ADR 0010).

A PriceBook is a list of PriceLine. A line names its basis (how its amount is built: fixed, per_kwh, tiered_per_kwh, percent_of, once; bases are plugins), the flow it charges and the schedule whose periods split it, a rate found in dated rate tables, a VAT category, whether it is a credit (revenue), and whether it is itemised (added to the total) or included in other prices (shown as "of which", but not added: a supplier that bundles the network tariff into its own prices).

Rate selectors are templates: {fta} takes the contract field fta, {subscribed_kva} the subscribed power, {period} each period of the line's schedule, and a basis may add its own ({tier}). One line therefore prices every period without being repeated.

A tariff pack ships the regulated lines; a user's book adds the supplier's lines and prices. merged puts a user book on a pack book: a line with the same name replaces the pack's, and remove drops lines by name.

Inclusion

Bases: StrEnum

Whether a line adds to the bill's total.

ITEMISED class-attribute instance-attribute
ITEMISED = 'itemised'

Charged: added to the total.

INCLUDED class-attribute instance-attribute
INCLUDED = 'included'

Inside other prices: shown as "of which", not added; still a base for percent_of.

RateRef dataclass

A rate component and selector templates ({field}, {period}, ...).

placeholders property
placeholders: frozenset[str]

Every {name} used in the selectors.

key
key(values: Mapping[str, str]) -> RateKey

The concrete key once every placeholder has a value.

Raises:

Type Description
PriceBookError

a placeholder without a value.

PriceLine dataclass

One line of a price book; see the module docstring.

schedule class-attribute instance-attribute
schedule: str | None = None

network, supply (the contract's schedules) or the name of a schedule.

vat class-attribute instance-attribute
vat: str | None = None

The VAT category whose VAT line charges this line, or None.

inclusion class-attribute instance-attribute
inclusion: Inclusion | None = None

None: the book's default for the line's layer, else itemised.

params class-attribute instance-attribute
params: Mapping[str, object] = field(
    default_factory=dict[str, object]
)

Basis-specific parameters (e.g. of for percent_of, tiers for tiered prices).

applies_to
applies_to(contract: Contract) -> bool

Whether the line's when condition holds for contract.

BookSettings dataclass

How amounts are prorated and rounded.

proration_days class-attribute instance-attribute
proration_days: int | None = 365

Fixed charges are prorated by days / this; None: by the actual length of each year.

rounding class-attribute instance-attribute
rounding: Decimal = Decimal('0.01')

Each line is rounded to this (euros) at the end.

vat_on_rounded class-attribute instance-attribute
vat_on_rounded: bool = True

VAT is computed on rounded lines (else on exact amounts).

PriceBook dataclass

Named price lines, their layers' default inclusion, and settings.

inclusion class-attribute instance-attribute
inclusion: tuple[tuple[str, Inclusion], ...] = ()

Default inclusion per layer, e.g. (("network", Inclusion.INCLUDED),).

remove class-attribute instance-attribute
remove: tuple[str, ...] = ()

Lines of the book underneath to drop when this book is merged onto it.

line
line(name: str) -> PriceLine

The line called name.

Raises:

Type Description
PriceBookError

no such line.

inclusion_of
inclusion_of(line: PriceLine) -> Inclusion

The line's own inclusion, else its layer's default, else itemised.

merged
merged(
    user: PriceBook, *, settings: BookSettings | None = None
) -> PriceBook

user on top of this book: same-named lines replaced, user.remove dropped.

Layer defaults merge (user wins). Settings are settings when given, else this book's.

Raises:

Type Description
PriceBookError

user removes a line this book does not have.

profiles

Profiles: where a load or production curve comes from, and how it becomes kilowatts.

A shape is a dimensionless curve whose mean is about 1 over a year (an Enedis coefficient, a synthetic curve). A scaling turns it into power with one constant factor, so the same profile serves any simulation period and any step (SPEC decision 7):

  • Nominal(annual_kwh): factor = annual energy / 8760 h; year-to-year variation in the data is kept, as in reality;
  • Calibrated(energy_kwh, window): factor = energy / energy of the shape over window, which may be any period (a billing year starting in November, a month, a week);
  • PVSystem(peak_kwp, specific_yield_kwh_per_kwp): a nominal annual energy, until a PV API computes production directly;
  • AsMeasured(): the source holds kW already (factor 1).

Shapes take Nominal, PVSystem or Calibrated; measured kW takes AsMeasured or Calibrated. Any other pairing is refused, never guessed.

HOURS_PER_NOMINAL_YEAR module-attribute
HOURS_PER_NOMINAL_YEAR = 8760.0

365 days. A leap year of data integrates to about 0.27 % more energy, which is kept.

ProfileSpec
ProfileSpec = Shaped

Every way to describe a profile; PV APIs will add a computed form (SPEC §6).

ProfileKind

Bases: StrEnum

Whether a profile draws or produces energy.

ProfileUnit

Bases: StrEnum

What a source's values are: a dimensionless shape to scale, or measured power.

TimestampLabel

Bases: StrEnum

Which end of its interval a source's timestamps name (Enedis: the start).

ProfileRef dataclass

A profile in a source, optionally shifted in time on purpose (SPEC decision 10).

A positive time_shift moves the curve later: the value used at t is the source's value at t - time_shift. It must be a whole number of the grid's steps when used.

Raises:

Type Description
UnknownProfileError

an empty source or key name.

ProfileMeta dataclass

What a source says about one of its profiles.

coverage instance-attribute
coverage: TimeGrid

The period the data covers, on its finest native step.

Nominal dataclass

annual_kwh per nominal year (8760 h).

Raises:

Type Description
CalibrationError

a non-positive energy.

kw_per_unit
kw_per_unit() -> float

Kilowatts per unit of shape.

Calibrated dataclass

energy_kwh over exactly window.

Raises:

Type Description
CalibrationError

a non-positive energy.

kw_per_unit
kw_per_unit(shape_unit_hours: float) -> float

Kilowatts per unit of shape, given the shape's integral over the window (unit-hours).

Raises:

Type Description
CalibrationError

the shape has no positive energy over the window.

PVSystem dataclass

A PV array of peak_kwp producing specific_yield_kwh_per_kwp per nominal year.

Raises:

Type Description
CalibrationError

a non-positive peak power or yield.

nominal
nominal() -> Nominal

The equivalent nominal annual energy.

AsMeasured dataclass

The source already holds kW (measured data, e.g. a meter export): factor 1.

kw_per_unit
kw_per_unit() -> float

Kilowatts per unit: the values are kW already.

Shaped dataclass

A profile made of a shape and a scaling.

rates

Dated rates: every regulated price is data with a validity period, a source and a status.

A RateKey names what is priced (a component and its selectors, such as the network option or the time class); a RateHistory holds the successive RateVersion of one key. Values are Decimal in canonical units, so a rate reads exactly as published. Lookups are exact: a date outside every version is an error, never the nearest value (SPEC decision 9, ADR 0006).

RateUnit

Bases: StrEnum

Canonical units of rate values.

EUR class-attribute instance-attribute
EUR = 'EUR'

A one-off amount.

FRACTION class-attribute instance-attribute
FRACTION = 'fraction'

A share of another amount: 0.2193 for 21.93 %.

RateStatus

Bases: StrEnum

How far a value can be trusted.

OFFICIAL class-attribute instance-attribute
OFFICIAL = 'official'

Read in the regulator's, the operator's or the law's own publication.

TO_VERIFY class-attribute instance-attribute
TO_VERIFY = 'to-verify'

From a secondary source or an agent; refused in bills unless explicitly allowed.

USER class-attribute instance-attribute
USER = 'user'

Entered by the user (a supplier's prices from a bill or an offer); usable in bills.

RateKey dataclass

What is priced: a component and the selectors that pick one value of it.

of classmethod
of(component: str, **selectors: str) -> RateKey

RateKey.of("turpe.withdrawal.power", segment="BT<=36kVA", fta="CU4").

RateVersion dataclass

One value of a rate over [valid_from, valid_to); valid_to=None means still valid.

covers
covers(day: date) -> bool

Whether day lies in the validity period.

RateHistory

The successive versions of one rate: one unit, no overlap; gaps are allowed.

unit property
unit: RateUnit

The unit of every version.

between
between(
    start: date, end: date, *, allow_to_verify: bool = False
) -> tuple[RateVersion, ...]

The versions that cover [start, end) without a gap, oldest first.

Raises:

Type Description
CoverageError

a day of [start, end) that no version covers (the first one).

UnverifiedRateError

a to-verify version in the period, unless allowed.

on
on(
    day: date, *, allow_to_verify: bool = False
) -> RateVersion

The version valid on day.

Raises:

Type Description
CoverageError

no version covers day.

UnverifiedRateError

the version is to-verify and allow_to_verify is false.

schedules

Time-of-use schedules: user-defined periods by time of day, day type and season.

A Schedule names periods ("HC", "super-off-peak", "HPH", …) and says, with PeriodRule values, which instants belong to each: daily time windows, optionally only on some day types ("sat", "weekend", or labels such as a Tempo colour from a DayCalendar) and in some seasons. Whatever no rule claims goes to the season's default period, else the schedule's default. Every instant belongs to exactly one period: two rules that claim the same instant, or an instant that nothing claims, are errors that name the instant.

Rules are applied per instant: a window is laid on every local day, then kept only where the instant's own local date has the rule's day type and season. So 22:00-06:00 limited to "sat" covers Saturday 00:00-06:00 and 22:00-24:00.

weights(grid) gives the share of each step in each period. Shares are exact: a step that straddles a window edge is split by time, so energy split by period adds up to the total on any grid. Shares are worked out in UTC, so daylight-saving days need no special case: a window 02:00-07:00 lasts 4 real hours on the spring day and 6 on the autumn day. A wall-clock time that does not exist (02:30 on the spring day) is read with the offset in force before the change, as Python does (PEP 495).

TimeWindow dataclass

From start to end on the wall clock; it may run past midnight.

minutes property
minutes: int

Length in minutes on the wall clock.

TimeWindows dataclass

Daily windows that do not overlap, for example a connection's off-peak hours.

hours property
hours: float

Hours covered on a normal day.

parse classmethod
parse(text: str) -> TimeWindows

Read windows as written on a contract or a bill.

"02:00-07:00 12:30-15:30", "2H00-7H00 12H30-15H30" and "22h30-6h30" are all accepted; spaces, commas, ; and / between windows are ignored. "" is no window.

Raises:

Type Description
ScheduleError

unreadable text, or an hour or minute out of range.

Season dataclass

Local dates from first to last ((month, day), inclusive; may wrap the year).

default class-attribute instance-attribute
default: str | None = None

The period of instants in this season that no rule claims (else the schedule's default).

parse classmethod
parse(
    name: str, text: str, default: str | None = None
) -> Season

Season.parse("winter", "11-01..03-31").

Raises:

Type Description
ScheduleError

the text is not MM-DD..MM-DD.

contains
contains(day: date) -> bool

Whether day falls in the season.

DayCalendar

Bases: Protocol

Labels each local date (weekday names, a Tempo colour, "holiday", …).

vocabulary property
vocabulary: frozenset[str]

Every label this calendar can give; rules may only use these.

labels
labels(day: date) -> frozenset[str]

The labels of day.

WeekdayCalendar

"mon" … "sun", plus "weekday" or "weekend".

labels
labels(day: date) -> frozenset[str]

The weekday name and whether it is a weekday or the weekend.

PeriodRule dataclass

Instants of period: in windows (all day if None), on days, in seasons.

days class-attribute instance-attribute
days: frozenset[str] | None = None

Day labels from the DayCalendar; None is every day.

seasons class-attribute instance-attribute
seasons: frozenset[str] | None = None

Season names of the schedule; None is every season.

PeriodWeights dataclass

The share of each step in each period of a schedule; every row sums to 1.

shares class-attribute instance-attribute
shares: NDArray[float64] = field(repr=False)

Shape (len(grid), len(periods)), columns in periods order.

share
share(period: str) -> TimeSeries

The share of each step in period (dimensionless, 0 to 1).

Raises:

Type Description
ScheduleError

the schedule has no such period.

split
split(series: TimeSeries) -> Mapping[str, float]

Energy (kWh) of a power or energy series in each period; the values add up to its total.

Raises:

Type Description
SeriesError

the series is on another grid, or is neither power nor energy.

Schedule dataclass

Named periods defined by rules, seasons and defaults; see the module docstring.

default class-attribute instance-attribute
default: str | None = None

The period of instants no rule claims, in seasons without their own default.

seasons class-attribute instance-attribute
seasons: tuple[Season, ...] = ()

When given, every date of the year must fall in exactly one season.

periods property
periods: tuple[str, ...]

Every period, rules first (in order), then defaults.

weights
weights(
    grid: TimeGrid, calendar: DayCalendar = WEEKDAYS
) -> PeriodWeights

The share of each step of grid in each period, on the grid's wall clock.

calendar labels the days (default: weekday names); a Tempo-like offer passes a calendar that also gives each day its colour.

Raises:

Type Description
ScheduleError

a rule uses a day label the calendar does not know, two rules claim the same instant, or an instant has no period.

series

Time series: one value per step of a grid, tagged with what the value measures.

Profiles are held as average power over the step (POWER_KW): unlike energy, this does not depend on the step length, which is what lets one profile serve any grid. Converting to energy multiplies by the step length. Resampling uses one overlap-weighted method for any pair of grids: intensive quantities (power, price, shapes) become time-weighted means, and energy is summed or split in proportion to the overlap, so it is conserved exactly.

Quantity

Bases: StrEnum

What a series measures, which decides how it combines and resamples.

extensive property
extensive: bool

True when values add up over time (energy), false for rates and shapes.

TimeSeries dataclass

Read-only values on a grid.

Raises:

Type Description
SeriesError

the values do not match the grid, or are not finite numbers.

constant classmethod
constant(
    grid: TimeGrid, value: float, quantity: Quantity
) -> Self

The same value on every step.

scaled
scaled(
    factor: float, quantity: Quantity | None = None
) -> TimeSeries

Multiply by factor; quantity re-tags the result (shape times kW is power).

energy
energy() -> TimeSeries

Energy per step (kWh) from average power (kW times step hours).

Raises:

Type Description
SeriesError

the series is not power.

power
power() -> TimeSeries

Average power per step (kW) from energy per step.

Raises:

Type Description
SeriesError

the series is not energy.

total
total() -> float

Sum over the grid (energy only: summing power or prices has no meaning).

Raises:

Type Description
SeriesError

the series is not energy.

time_mean
time_mean() -> float

Time-weighted mean over the grid (rates and shapes; all steps have equal length).

Raises:

Type Description
SeriesError

the series is energy (use total()).

resample
resample(grid: TimeGrid) -> TimeSeries

These values on another grid, which must lie within this series' range.

Raises:

Type Description
CoverageError

grid reaches outside this series' range.

isclose
isclose(
    other: TimeSeries,
    *,
    rel: float = 1e-12,
    abs_: float = 1e-12,
) -> bool

Same grid, same quantity, and values equal within tolerance.

sites

Sites: what is installed behind one grid connection, described as data.

A SiteSpec lists assets by id and kind (consumer, pv, later battery, ev, ...). What a kind does is a plugin (myenergy.ports.assets.AssetKind); the flows only look at its role: a source produces, a sink consumes, a storage does either. Adding a household, an EV or a battery is one more entry in a site file, never a change to the flow or bill code (ADR 0009).

AssetRole

Bases: StrEnum

What an asset does to the site's energy balance.

SOURCE class-attribute instance-attribute
SOURCE = 'source'

Produces energy (PV, a generator).

SINK class-attribute instance-attribute
SINK = 'sink'

Consumes energy (a household, an EV, a heat pump).

STORAGE class-attribute instance-attribute
STORAGE = 'storage'

Stores and returns energy (a battery); its power is signed.

ShareDirection

Bases: StrEnum

Whether shared energy comes to the site or leaves it.

IN class-attribute instance-attribute
IN = 'in'

Allocated to this site by a sharing scheme: part of what its meter imports.

OUT class-attribute instance-attribute
OUT = 'out'

Allocated to others: part of what its meter exports.

AssetSpec dataclass

One asset: an id unique in its site, a kind, and what the kind needs to compute its power.

options class-attribute instance-attribute
options: tuple[tuple[str, str], ...] = ()

Free options for the kind's plugin, as text.

ShareSpec dataclass

Energy shared with a counterparty (an operation, a neighbour), as a power profile.

SiteSpec dataclass

A grid connection: its contract, its assets and the energy it shares.

time

Time grids: the half-open UTC ranges, split into equal steps, that every series lives on.

A grid is held in UTC, so daylight-saving changes cannot break it: a local day simply has 23 or 25 hours. The grid's timezone is only used for wall-clock logic (seasons, peak hours). Steps divide an hour (SPEC decision 8), and the start is aligned on the step, so grids of the same step line up.

TimeGrid dataclass

[start, end) in UTC, divided into step-long intervals labelled by their start.

Raises:

Type Description
GridError

naive or misaligned timestamps, an unsupported step, or an empty range.

step_hours property
step_hours: float

The step length in hours (to turn kW into kWh).

span property
span: timedelta

end - start.

covering classmethod
covering(
    start: datetime, end: datetime, step: timedelta
) -> Self

The grid from start to end in the default timezone (convenience).

timestamp
timestamp(index: int) -> datetime

Start of step index (negative indices count from the end), in UTC.

Raises:

Type Description
GridError

the index is outside the grid.

local
local(index: int) -> datetime

Start of step index on the grid's wall clock.

index
index(moment: datetime) -> int

The step that starts at moment.

Raises:

Type Description
GridError

moment is naive, outside the grid, or not a step boundary.

edges_seconds
edges_seconds() -> npt.NDArray[np.float64]

The len + 1 step edges, in seconds since start (for resampling).

contains
contains(other: TimeGrid) -> bool

True when other's range lies within this grid's range (any step).

sub
sub(start: datetime, end: datetime) -> TimeGrid

The part of this grid from start to end, same step and timezone.

Raises:

Type Description
GridError

the range is outside this grid or not aligned on its steps.

with_step
with_step(step: timedelta) -> TimeGrid

The same range with another step.

Raises:

Type Description
GridError

the range is not a whole number of the new steps.

engine

Simulation engine: the time loop, the information boundary and the SmartMDAO step pipeline.

assets

Built-in asset kinds, registered as entry points like any third party's (ADR 0009).

ProfiledKind

A source or a sink whose power is its profile (a household, a PV array).

power
power(
    asset: AssetSpec,
    grid: TimeGrid,
    profiles: PowerProfiles,
) -> TimeSeries

The asset's profile in kW.

Raises:

Type Description
SiteError

the asset has no profile, or has options this kind does not take.

pricebases

Built-in price bases, registered as entry points like any third party's (ADR 0010).

FIXED module-attribute
FIXED = _Simple(
    "fixed",
    frozenset(
        {
            RateUnit.EUR_PER_YEAR,
            RateUnit.EUR_PER_KVA_YEAR,
            RateUnit.EUR_PER_MONTH,
        }
    ),
    needs_flow=False,
)

A yearly or monthly charge (per kVA if the rate is per kVA), prorated over the bill period.

PER_KWH module-attribute
PER_KWH = _Simple(
    "per_kwh",
    frozenset({RateUnit.EUR_PER_KWH}),
    needs_flow=True,
)

The line's flow in each period, times that period's rate.

ONCE module-attribute
ONCE = _Simple(
    "once", frozenset({RateUnit.EUR}), needs_flow=False
)

A one-off amount, charged once per bill.

TieredBasis

Price per kWh by tranche of energy accumulated over a reset period ({tier}).

problems
problems(line: PriceLine) -> list[str]

reset and ascending tiers (each named; all but the last with up_to_kwh).

expansions
expansions(
    line: PriceLine,
) -> Mapping[str, tuple[str, ...]]

{tier}: the tier names.

depends_on
depends_on(
    line: PriceLine, book: PriceBook
) -> tuple[str, ...]

None.

PercentOfBasis

A share of other lines.

of names lines (itemised or included); of_vat takes every itemised line of a VAT category.

problems
problems(line: PriceLine) -> list[str]

Exactly one of of (a list of line names) or of_vat (a category).

expansions
expansions(
    line: PriceLine,
) -> Mapping[str, tuple[str, ...]]

None.

depends_on
depends_on(
    line: PriceLine, book: PriceBook
) -> tuple[str, ...]

The named lines, or every itemised line of the VAT category.

pricing

Resolve a price book against a contract and a bill period (ADR 0010).

Resolution decides which lines apply (when), finds each line's schedule periods and concrete rate keys, checks that every rate covers the whole period with the right unit and status, orders lines so that a percentage comes after what it is a percentage of, and reports every problem at once. Amounts are computed from a resolved book (M2 task 5).

ResolvedRate dataclass

The rate of one combination of placeholder values (a period, a tier, ...).

values instance-attribute
values: tuple[tuple[str, str], ...]

The expanded placeholders, e.g. (("period", "HPH"),); empty for a single rate.

versions instance-attribute
versions: tuple[RateVersion, ...]

The versions covering the bill period, oldest first.

ResolvedLine dataclass

A line ready to be computed.

boundaries instance-attribute
boundaries: tuple[date, ...]

Dates inside the bill period where one of the line's rates changes.

ResolvedBook dataclass

Every applicable line of a book, resolved for one contract and period, in compute order.

line
line(name: str) -> ResolvedLine

The resolved line called name.

Raises:

Type Description
PriceBookError

no such applicable line.

PricingContext dataclass

What a price book is resolved against: a contract under a pack, its rates, a period.

end instance-attribute
end: date

The bill period is [start, end), in local dates.

schedules class-attribute instance-attribute
schedules: Mapping[str, Schedule] = field(
    default_factory=dict[str, Schedule]
)

Named schedules lines may use besides network and supply.

PriceBookResolver

Resolves price books with the registered price bases.

resolve
resolve(
    book: PriceBook, context: PricingContext
) -> ResolvedBook

The applicable lines of book for the context's contract and period, in order.

Raises:

Type Description
PriceBookError

every problem found: an unknown basis or schedule, bad parameters, a missing flow, a rate that is missing, of the wrong unit, not covering the period or still to verify, an unknown or circular line reference, an empty period.

profiles

Turn profile specs into kilowatt series, from whichever source holds the shape.

ProfileResolver

Resolve ProfileSpec values against the registered profile sources.

shape
shape(ref: ProfileRef, grid: TimeGrid) -> TimeSeries

The shape of ref on grid, with its time_shift applied.

meta
meta(ref: ProfileRef) -> ProfileMeta

What the source says about ref.

Raises:

Type Description
UnknownProfileError

the source has no such profile.

power
power(spec: ProfileSpec, grid: TimeGrid) -> TimeSeries

Average power (kW) of spec on grid.

Raises:

Type Description
CalibrationError

the scaling does not fit the profile's unit.

sites

Resolve a site description into asset powers and meter flows (ADR 0009).

The resolver knows asset kinds only through their plugins and roles, so a new kind never changes this module. Flows are built per step of the requested grid (see myenergy.domain.flows).

SiteResolver

Asset powers and flows of sites, from the registered asset kinds.

powers
powers(
    site: SiteSpec, grid: TimeGrid
) -> dict[str, TimeSeries]

Each asset's signed power on grid (kW: + produces, - consumes), by asset id.

Raises:

Type Description
SiteError

an unknown kind, or a kind that returns something other than kW on grid, or a negative power from a source or a sink.

flows
flows(site: SiteSpec, grid: TimeGrid) -> SiteFlows

The site's meter flows on grid, its shares included.

Warns:

Type Description
TariffWarning

the site shares energy and grid's step differs from the step at which its tariff pack allocates shared energy.

Raises:

Type Description
SiteError

see powers.

FlowError

shares above import or export, or other broken flow invariants.

policies

Dispatch policies: baseline, rule-based (SmartMDAO RuleDiscipline), forecast, MPC.

ports

Ports: the Protocol interfaces every external capability is reached through.

Profile and irradiance sources, tariffs, forecasters, dispatch policies, observers, repositories, job runners and the clock (SPEC §8).

assets

Asset kinds: what an asset of a given kind does, as a plugin (ADR 0009).

A site file names assets by kind (consumer, pv, ...). Each kind is an AssetKind registered in the myenergy.asset_kinds entry-point group, built-ins included, so a third party adds a kind (an EV, a heat pump, a battery) without editing this package.

PowerProfiles

Bases: Protocol

Turns profile specs into kW series (myenergy.engine.profiles.ProfileResolver).

power
power(spec: ProfileSpec, grid: TimeGrid) -> TimeSeries

Average power (kW) of spec on grid.

AssetKind

Bases: Protocol

One kind of asset.

name property
name: str

The kind's name, as written in site files.

role property
role: AssetRole

Source, sink or storage.

power
power(
    asset: AssetSpec,
    grid: TimeGrid,
    profiles: PowerProfiles,
) -> TimeSeries

The asset's power on grid (kW).

Non-negative for a source or a sink; signed for a storage (positive when it returns energy).

Raises:

Type Description
SiteError

the asset lacks what the kind needs (a profile, an option).

packs

The PackSource port: where tariff packs, their rates and their schedules come from.

Folders of TOML files today (built-in and user-written); a database later, for packs whose rates an agent maintains (ADR 0006, ADR 0008).

PackSource

Bases: Protocol

A store of tariff packs.

catalog
catalog() -> Sequence[TariffPack]

Every pack, sorted by name (filter by tag with myenergy.domain.packs.tagged).

pack
pack(name: str) -> TariffPack

The pack called name.

Raises:

Type Description
UnknownPackError

no such pack.

rates
rates(name: str) -> RateSource

The rates of pack name, those it inherits included.

Raises:

Type Description
UnknownPackError

no such pack.

schedules
schedules(name: str) -> Mapping[str, Schedule]

The named schedules a pack ships (e.g. example offers), those it inherits included.

Raises:

Type Description
UnknownPackError

no such pack.

price_book
price_book(name: str) -> PriceBook

The regulated price book of pack name, merged with those it extends.

Raises:

Type Description
UnknownPackError

no such pack.

plugins

Plugins: how a data source joins the application without editing it (MEO-PLG-1, MEO-PLG-2).

Built-in and third-party sources register the same way, through the myenergy.profile_sources entry-point group, each pointing at an object that satisfies ProfileSourcePlugin. Tariff packs need no code: they are folders of data (MYENERGY_PACK_ROOTS).

ProfileSourcePlugin

Bases: Protocol

Builds one ProfileSource from plain text options (from settings).

name property
name: str

The source name, also the key of its options in the settings.

create
create(options: Mapping[str, str]) -> ProfileSource | None

The source, or None when it is not configured (e.g. no data folder given).

pricing

Price bases: how a price line's amount is built, as plugins (ADR 0010).

Built-in bases (fixed, per_kwh, tiered_per_kwh, percent_of, once) register in the myenergy.price_bases entry-point group like any third party's, so a new kind of charge (for example a power-overrun charge for businesses) needs no change to the core.

PriceBasis

Bases: Protocol

One way of building a line's amount.

name property
name: str

The basis name, as written in price books.

units property
units: frozenset[RateUnit]

The rate units this basis accepts (empty: it takes no rate).

needs_flow property
needs_flow: bool

Whether the line must name a flow.

problems
problems(line: PriceLine) -> list[str]

What is wrong with the line's basis parameters (empty when nothing is).

expansions
expansions(
    line: PriceLine,
) -> Mapping[str, tuple[str, ...]]

Extra placeholders this basis fills in rate selectors, with their values.

depends_on
depends_on(
    line: PriceLine, book: PriceBook
) -> tuple[str, ...]

Names of the lines whose amounts this line is computed from.

profiles

The port through which profile shapes are read (MEO-PLG-1, PRT-1).

ProfileSource

Bases: Protocol

A provider of dimensionless shapes (Enedis, CSV, synthetic…).

Contract (tests/contract/test_profile_sources.py):

  • catalog() lists every profile, with keys unique within the source;
  • shape(key, grid) returns a DIMENSIONLESS series on exactly grid, labelled by interval start whatever the native convention;
  • a grid outside the profile's coverage raises CoverageError;
  • an unknown key raises UnknownProfileError.
name property
name: str

The source name used in ProfileRef.source.

catalog
catalog() -> Sequence[ProfileMeta]

Every profile this source offers.

shape
shape(key: str, grid: TimeGrid) -> TimeSeries

The shape of profile key on grid.

rates

The RateSource port: where dated rates come from (ADR 0006).

Packaged TOML tables now; a database filled by the billing-data agent later. Callers only ask for the history of a key, so the storage can change without touching the bill engine.

RateSource

Bases: Protocol

A store of dated rates.

name property
name: str

A short, unique name for messages and settings.

catalog
catalog() -> Sequence[RateKey]

Every key this source holds.

history
history(key: RateKey) -> RateHistory

Every version of key.

Raises:

Type Description
UnknownRateError

the source does not hold key.