flexmeasures.data.schemas.scheduling

Modules

flexmeasures.data.schemas.scheduling.config

What makes up a scheduler's configuration, as recorded on its data source.

flexmeasures.data.schemas.scheduling.groups

Schema for referencing a group of devices whose aggregate power is constrained.

flexmeasures.data.schemas.scheduling.metadata

These descriptions are used in documentation/features/scheduling.rst and in OpenAPI.

flexmeasures.data.schemas.scheduling.process

flexmeasures.data.schemas.scheduling.storage

flexmeasures.data.schemas.scheduling.utils

Classes

class flexmeasures.data.schemas.scheduling.AssetTriggerSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)

Example:

{
    "start": "2025-01-21T15:00+01",
    "flex-model": [
        {
            "sensor": 1,
            "soc-at-start": "10 kWh"
        },
        {
            "sensor": 2,
            "soc-at-start": "20 kWh"
        },
    ]
}

This schema is also used outside of the (versioned) API, e.g. by the CLI, so it stays canonical (no legacy field-name aliasing here). API-version- specific backward compatibility, such as accepting the legacy force_new_job_creation field name, is layered on top in flexmeasures/api/v3_0/assets.py (AssetTriggerSchemaV3), so it can be deleted in one place once v3_0 is sunset, without touching this shared domain schema.

check_flex_model_sensors(data, **kwargs)

Verify that the flex-model’s sensors live under the asset for which a schedule is triggered.

fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

normalize_flex_context_format(data, **kwargs)

Normalize flex_context to always be a dict.

Accepts both:

  • Single commodity dict: {“commodity”: “electricity”, …}

  • List of commodity dicts: [{“commodity”: “electricity”, …}, {“commodity”: “heat”, …}]

  • MultiDict with multiple ‘flex-context’ entries (when JSON list is parsed by webargs)

If a list is provided, it is wrapped under the ‘commodities’ field. If a dict is provided, it is kept as-is. This ensures downstream code always sees a dict structure.

validate_schedule_durations(data, **kwargs)

Require a positive horizon and a positive fixed resolution.

class flexmeasures.data.schemas.scheduling.CommitmentSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)
fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

forbid_scope_conflict(commitment, **kwargs)

A commitment’s scope is a sensor list or a group reference, not both.

require_a_price(commitment, **kwargs)

A commitment is worthless without at least one deviation price.

The baseline requirement is enforced at the field level (required=True), so it also shows up in the generated OpenAPI schema; the either/or price requirement cannot be expressed per field.

class flexmeasures.data.schemas.scheduling.CommodityFlexContextSchema(*args, **kwargs)
__init__(*args, **kwargs)
_default_zero_capacity_as_soft_constraint(data: dict, field: str, zero_capacity: Quantity)

Default a site capacity field to 0, as a soft constraint.

Also fills in a default breach price for that direction (unless one was already set), so the 0 capacity is enforced as a soft constraint (breaching is possible, but penalized) rather than a hard, potentially infeasible, one. This mirrors FlexContextSchema.check_prices, but scoped to a single commodity context, and only fired for capacities defaulted here (not for capacities the caller explicitly set to 0).

fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

fill_grid_connection_defaults(data: dict, original_data: dict, **kwargs)

Fill in smarter defaults for a commodity context’s grid-connection fields.

A commodity context (an entry of the top-level commodities list) may omit some or all of the grid-connection fields (consumption-price, production-price, site-consumption-capacity, site-production-capacity, site-power-capacity). Rather than leaving those simply unset (which, for consumption-price, would make the scheduler fail, since it requires one), we derive sensible defaults from which of those five fields were explicitly given (inspecting the original input, not post-default-fill presence).

A price given for a direction (consumption or production) implies a grid connection in that direction, with an unlimited capacity unless a capacity is also given; a capacity given for a direction (without a price) implies a 0 price in that direction; and anything not implied by a given field defaults to “no connection” (0 capacity, as a soft constraint). The exception is site-power-capacity given on its own, which sets a hard (symmetric) capacity limit instead. See Smart defaults for commodity-context grid connections for the full user-facing explanation, including worked examples.

Precedence (single-field triggers):

  1. None of the five given (e.g. just {“commodity”: “gas”}): no grid connection at all. site-consumption-capacity and site-production-capacity default to 0, as soft constraints (a default breach price is filled in, so breaching is possible but penalized – this relies on relax-constraints/relax-site-capacity-constraints, which default to True). site-power-capacity is left unlimited (unset).

  2. Only consumption-price given: assume a grid connection for consumption. site-power-capacity and site-consumption-capacity stay unlimited. site-production-capacity defaults to 0 (soft).

  3. Only production-price given: the mirror image of (2), for production.

  4. Only site-consumption-capacity given: site-power-capacity stays unlimited; consumption-price defaults to 0; site-production-capacity (and, transitively, production-price) default to 0.

  5. Only site-production-capacity given: the mirror image of (4).

  6. Only site-power-capacity given: a hard constraint at that capacity. site-consumption-capacity and site-production-capacity are both set equal to it (no breach price is filled in, so the constraint stays hard); consumption-price and production-price default to 0.

As a safety net (since the scheduler requires a resolvable consumption price), consumption-price defaults to 0 if still unset after applying the rules above (production-price already falls back to consumption-price at the scheduler level, so no separate safety net is needed for it).

A commodity context with no user-given price fields does not trip a spurious cross-currency error against a differently-currencied portfolio; its 0-price/breach-price fields instead inherit the portfolio’s real currency where determinable (from a top-level price or a sibling commodity context).

class flexmeasures.data.schemas.scheduling.DBCommitmentSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)
fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

class flexmeasures.data.schemas.scheduling.DBFlexContextSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)
_forbid_fixed_prices(data: dict, **kwargs)

Do not allow fixed consumption price or fixed production price in the flex-context fields saved in the db.

This is a temporary restriction as future iterations will allow fixed prices on these fields as well.

_validate_field(data: dict, field_type: str, field: str, unit_validator)

Validate fields based on type and unit validator.

_validate_inflexible_device_sensors(data: dict)

Validate inflexible device sensors (both Sensor and SensorReference entries).

_validate_power_fields(data: dict)

Validate power fields.

_validate_price_fields(data: dict)

Validate price fields.

fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

validate_fields_unit(data: dict, **kwargs)

Check that each field value has a valid unit.

class flexmeasures.data.schemas.scheduling.FlexContextSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)

This schema defines fields that provide context to the portfolio to be optimized.

_check_deprecated_price_sensor_migration(data: dict, original_data: dict)

New price fields can only be used after updating to consumption-price/production-price.

_reconcile_commodity_context_currencies(data: dict) → str

Backfill price-free contexts’ currency with the portfolio’s real currency.

Determines the portfolio’s real (user-given) shared currency, if any: the top-level one, unless it’s itself just a fallback (no user-given price fields at the top level), in which case falls back to the first non-default commodity context’s currency, if any. Then rebases any price-free (“default currency”) commodity context onto that real currency, so their 0-price/breach-price fills inherit it. Returns the (possibly just-updated) top-level shared_currency_unit.

check_prices(data: dict, original_data: dict, **kwargs)

Check assumptions about prices.

  1. The flex-context must contain at most 1 consumption price and at most 1 production price field.

  2. All prices must share the same currency.

fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

validate_commodity_contexts_shared_currency(commodity_contexts: list[dict], **kwargs)

Validate that all prices across commodity contexts share the same currency.

Each commodity context already computed its own normalized shared_currency_unit (a base-unit currency string, e.g. “EUR”) via the inherited _try_to_convert_price_units schema-level validator. We simply compare those.

validate_commodity_contexts_unique(commodity_contexts: list[dict], **kwargs)

Validate that each commodity is listed at most once.

_get_commodity_contexts (storage.py) builds a dict keyed by commodity, so duplicate entries would otherwise silently overwrite each other.

class flexmeasures.data.schemas.scheduling.GetScheduleSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)
fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

class flexmeasures.data.schemas.scheduling.MultiSensorFlexModelSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)

This schema is agnostic to the underlying type of flex-model, which is governed by the chosen Scheduler instead. Therefore, the underlying type of flex-model is not deserialized.

So:

{
    "sensor": 1,
    "soc-at-start": "10 kWh"
}

becomes:

{
    "sensor": <Sensor 1>,
    "sensor_flex_model": {
        "soc-at-start": "10 kWh"
    }
}
fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

unwrap_envelope(data, **kwargs)

Any field other than ‘sensor’ and ‘asset’ becomes part of the sensor’s flex-model.

wrap_with_envelope(data, **kwargs)

Any field in the ‘sensor-flex-model’ field becomes a main field.

class flexmeasures.data.schemas.scheduling.NoTimeSeriesSpecs(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)
fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

forbid_time_series_specs(data: dict, **kwargs)

Do not allow time series specs for the flex-context fields saved in the db.

class flexmeasures.data.schemas.scheduling.ScheduleSignConvention

Named constants for the three sign-convention modes of the get_schedule endpoint.

Variables:
  • CONSUMPTION_POSITIVE – Always return schedules with consumption as positive values and production as negative values. This is the default and matches the view a consumer has of their device.

  • PRODUCTION_POSITIVE – Always return schedules with production as positive values and consumption as negative values. This matches the view a producer (or generator) has of their device.

  • WYSIWYG – Return the raw values from the database without any sign inversion, regardless of the sensor’s consumption_is_positive attribute. Useful when you want to see exactly what was stored.

class flexmeasures.data.schemas.scheduling.SharedSchema(*, only: Sequence[str] | AbstractSet[str] | None = None, exclude: Sequence[str] | AbstractSet[str] = (), many: bool | None = None, load_only: Sequence[str] | AbstractSet[str] = (), dump_only: Sequence[str] | AbstractSet[str] = (), partial: bool | Sequence[str] | AbstractSet[str] | None = None, unknown: Literal['exclude', 'include', 'raise'] | None = None)

Shared schema for fields common across commodities in flex-context and commodity-context.

classmethod _currency_denominated_fields() → tuple[str, ...]

Names of the schema’s currency-denominated fields, selected by type (PriceField).

These are the fields that CommodityFlexContextSchema’s smart defaults (fill_grid_connection_defaults) may fill with a fallback “EUR” price/breach price when a context has no user-given price fields at all.

>>> FlexContextSchema._currency_denominated_fields()[:2]
('consumption_price', 'production_price')
static _extract_currency_unit(price_unit: str) → str

Obtain the currency part of a price unit, whose denominator may vary.

>>> FlexContextSchema()._extract_currency_unit("EUR/MWh")
'EUR'
>>> FlexContextSchema()._extract_currency_unit("USD/MW")
'USD'
classmethod _rebase_default_context_currency(context: dict, new_currency: str)

Re-express a price-free context’s fallback-currency fields in another currency.

Only called for a commodity context that had no user-given price fields (shared_currency_unit_is_default is True), once a real portfolio currency becomes known (e.g. from the top-level flex-context, or from a sibling commodity context). All of that context’s currency-denominated fields were filled with plain quantities in a fallback “EUR”, so their magnitudes carry over unchanged under the new currency label (no FX conversion is implied or attempted).

static _to_currency_per_mwh(price_unit: str) → str

Convert a price unit to a base currency used to express that price per MWh.

>>> FlexContextSchema()._to_currency_per_mwh("EUR/MWh")
'EUR'
>>> FlexContextSchema()._to_currency_per_mwh("EUR/kWh")
'EUR'
_try_to_convert_price_units(data: dict, original_data: dict, **kwargs)

Convert price units to the same unit and scale if they can (incl. same currency).

_validate_commitment_price_units(data: dict, shared_currency_unit: str | None, previous_field_name: str | None) → tuple[str | None, str | None]

Hold the nested commitment prices to the shared currency.

check_inflexible_devices(data: dict, **kwargs)

Check assumptions about inflexible devices.

  1. The deprecated inflexible-device-sensors field must not be mixed with inflexible-consumption/inflexible-production.

  2. Each sensor may be listed at most once across the two new fields.

  3. A sensor’s explicit consumption_is_positive attribute must not contradict the sign convention of the field it is listed under. Note the deliberate asymmetry with the legacy read path: here only the sensor’s own explicitly set attribute counts (as in the flex-model’s consumption/production output sensors), whereas inflexible-device-sensors entries defer to Sensor.get_attribute (which falls back to the sensor’s asset).

fields: dict[str, Field]

Dictionary mapping field_names -> Field objects

static relaxation_asked_for(data: dict, specific_field: str) → bool

Resolve a specific relax flag, falling back to the umbrella flag when the specific flag is not set.

An explicitly set specific flag (e.g. relax-soc-constraints) takes precedence; otherwise, the umbrella relax-constraints flag (which defaults to True) decides.

Parameters:
  • data – The deserialized (snake_case) flex-context data.

  • specific_field – Name of the specific relax flag to resolve, e.g. “relax_soc_constraints”.

Returns:

Whether the given kind of constraint relaxation is asked for.

set_default_breach_prices(data: dict, fields: list[str], price: Quantity)

Fill in default breach prices, for fields without an explicitly set price.

This relies on _try_to_convert_price_units to run first, setting a shared currency unit.