Scheduling
Scheduling is the main value-drive of FlexMeasures. We have two major types of schedulers built-in, for storage devices (usually batteries or hot water storage) and processes (usually in industry).
FlexMeasures computes schedules for energy systems that consist of multiple devices that consume and/or produce a commodity (e.g. electricity or gas). We model a device as an asset with a consumption/production sensor recording power values, and compute schedules only for flexible devices, while taking into account inflexible devices.
Describing the optimization problem
Before FlexMeasures can compute a schedule, it needs a description of the
available flexibility and its context. The flex-context describes the
system-wide situation and objective, while each flex-model describes one
flexible device. For example, this configuration tells FlexMeasures which
sensor holds the electricity price and gives the current state and operating
limits of a battery:
{
"flex-context": {
"consumption-price": {"sensor": 7}
},
"flex-model": [
{
"sensor": 8,
"soc-at-start": "50 kWh",
"soc-min": "10 kWh",
"soc-max": "100 kWh",
"power-capacity": "50 kW"
}
]
}
Here, sensor 8 is the power sensor on the battery asset and identifies the device to be scheduled. Sensor 7 supplies the electricity-price series against which the battery is optimized.
More than one device can be scheduled in the same optimization: add one flex-model entry per flexible device. See Toy example III: PV curtailment an Multi-asset scheduling for a tutorial that jointly schedules a battery and curtailable PV.
Configurations that change rarely can be stored on assets. A scheduling request can add or override the parts that are specific to that run. See Flexibility configuration for the complete flex-context and flex-model reference.
Triggering a schedule computation
To start a computation, select the asset whose energy system should be optimized and provide a scheduling window. If the configuration above is stored in the asset tree, an API request for a four-hour schedule can be this small:
POST /api/v3_0/assets/6/schedules/trigger
{
"start": "2026-08-10T07:00:00+02:00",
"duration": "PT4H",
"resolution": "PT1H"
}
Calling this endpoint makes asset 6 the root of the optimization. FlexMeasures collects stored flex-models from asset 6 and its descendants, and collects flex-context fields from asset 6 upwards through its ancestors. Nearer context values take precedence. Only devices represented by the collected flex-models are scheduled—not every descendant with a power sensor automatically—and they are considered together in one optimization problem.
Alternatively, include flex-context and flex-model in this body to
supply or override them for this computation. The endpoint returns
202 Accepted with a job UUID. Poll
GET /api/v3_0/jobs/<uuid> until the job finishes, then retrieve each
device’s schedule from GET /api/v3_0/sensors/<id>/schedules/<uuid>. The same
workflow is available through the CLI and FlexMeasures Client. See
Toy example I: Scheduling a battery, from scratch for a short hands-on example and the
asset scheduling endpoint
for all request fields.
The schedule
A schedule produced by FlexMeasures is a series of power setpoints for each
flexible device, represented by its power sensor. For example, the values
[0.5, 1.0, 1.5, 0.0] in kW describe four consecutive setpoints: the
battery first charges gently, increases its charging power, and then becomes
idle. The result also states the series start and duration, so a controller can
apply each value at the scheduling resolution.
After the scheduling job has finished, retrieving this simplified schedule
through GET /api/v3_0/sensors/8/schedules/<uuid> returns JSON like this:
{
"scheduler_info": {
"scheduler": "StorageScheduler"
},
"values": [0.5, 1.0, 1.5, 0.0],
"start": "2026-08-10T07:00:00+02:00",
"duration": "PT4H",
"unit": "kW",
"status": "PROCESSED",
"message": "StorageScheduler was used."
}
Together with the one-hour resolution requested above, this response describes four hourly events: 07:00–08:00, 08:00–09:00, 09:00–10:00, and 10:00–11:00. The schedule therefore covers the four-hour period from 07:00 to 11:00.
Schedules can be inspected alongside prices, forecasts, measurements, and state of charge in the FlexMeasures UI:
A battery schedule in the sensor data view.
For detailed constraint analysis (unresolved constraints and margins), use the GET /api/v3_0/jobs/<uuid> endpoint, which provides structured information about constraints organized by asset. See the Accessing constraint results section below for details.
Inspecting schedules
It can be crucial to inspect how your scheduling job is doing. Here are some ways to do that:
Errors
FlexMeasures will validate flex-config and asset & sensor IDs before starting the job, and let you know (in the console or API response) what went wrong.
Checking the status via the API
There is an API endpoint specifically for checking status, result and configuration info for jobs:
GET /api/v3_0/jobs/{uuid} returns JSON with the job status, result, queue and function metadata, timestamps, and exception traceback information for failed jobs.
For scheduling jobs specifically, this includes the constraint analysis described in Accessing constraint results below.
Checking the status via the CLI
There is also a CLI command, which basically mirrors what the API endpoint does (see above). Here is an example call:
flexmeasures jobs inspect-job --job 40ac6f2e-690d-4865-8203-429e54179112
The asset status page: listing jobs and more info
Each asset has a status page where you can find recent jobs which were run in the context of this asset. Clicking the “Info” button will give you a lot more insights into the jobs’ configuration than the above methods.
The RQ-dashboard: complete overview
Internally, jobs are queued with the python-rq library. For this, a job dashboard is available, which
users with the admin role can access via the menu. This gives a complete overview over all jobs
running in FlexMeasures.
You find your jobs via the queues, see screenshot below. Clicking a job gives you more information, similar to the status page.
Accessing constraint results
When a schedule is computed for a device with state-of-charge constraints, FlexMeasures analyzes whether the constraints can be met.
Use the jobs endpoint (GET /api/v3_0/jobs/<uuid>) to retrieve detailed constraint analysis for all assets involved in the scheduling job, organized by asset ID.
This endpoint is useful when you want to inspect constraint violations without retrieving the full schedule.
Consider a site (asset ID 123) with four assets, each with a power sensor:
Sensors 1 & 2: Inflexible devices (e.g. PV panel and building load)
Sensors 3 & 4: Flexible devices (e.g. a battery and an EV charger), each with a state-of-charge sensor (sensors 5 and 6, respectively)
The scheduling workflow looks like this:
Trigger the schedule for site asset 123 via
POST /api/v3_0/assets/123/schedules/trigger. The endpoint returns a job UUID, e.g."5d28df1b-9f16-4177-ae43-6e750d80fad3".Retrieve the scheduled power series for the flexible devices once scheduling is done, via
GET /api/v3_0/sensors/3/schedules/<uuid>andGET /api/v3_0/sensors/4/schedules/<uuid>. Each response contains the power setpoints for that device:{ "values": [0.5, 1.0, 1.5, 0.0], "start": "2024-01-15T08:00:00+00:00", "duration": "PT4H", "unit": "kW" }
Retrieve constraint analysis for all flexible assets via
GET /api/v3_0/jobs/<uuid>. Theresultfield in the response shows whether the state-of-charge targets for sensors 5 and 6 could be met, and by how much. For a finishedStorageSchedulerjob,resultis always an object withunresolvedandresolvedconstraint analysis (as shown below); both arrays are simply empty when the flex model defines nosoc-minima/soc-maxima, or when a scheduler other thanStorageSchedulerwas used.
The constraint results distinguish between:
Constraints that were unresolved: Soft constraints that could not be satisfied during optimization, with the shortfall or excess reported as their violation.
Constraints that were resolved: Soft constraints that were satisfied, with the headroom remaining reported as their margin.
For each device, the soc-minima/soc-maxima value under unresolved or resolved is a list of entries — one per violated slot (unresolved) or per met slot with its margin (resolved), ordered chronologically.
By default, every violated or met slot is listed (this is not currently configurable via the API).
Each list entry includes:
datetime: ISO 8601 UTC timestamp of that slot.violation(unresolved only): Magnitude of the violation at that slot (shortage for minima, excess for maxima).margin(resolved only): Headroom remaining at that slot.
Both violation and margin are always reported as positive numbers (magnitudes), never negative — whether a violation is a shortage or an excess follows from the constraint type (soc-minima vs. soc-maxima), not from its sign.
Suppose you schedule a battery device (asset ID 42) with the following constraints:
soc-minima: Battery must stay above 60 kWh
soc-maxima: Battery must not exceed 100 kWh
If the optimization cannot satisfy the minimum constraint at 10:30 UTC (falling short by 20 kWh) and again at 10:45 UTC (falling short by 15 kWh), but does satisfy the maximum constraint with margins of 40 kWh at 11:00 UTC and 35 kWh at 12:00 UTC, the constraint results would show:
Response via GET /api/v3_0/jobs/<uuid>:
{
"status": "FINISHED",
"message": "Scheduling job finished.",
"result": {
"unresolved": [
{
"asset": 42,
"soc-minima": [
{
"datetime": "2024-01-15T10:30:00+00:00",
"violation": "20.0 kWh"
},
{
"datetime": "2024-01-15T10:45:00+00:00",
"violation": "15.0 kWh"
}
]
}
],
"resolved": [
{
"asset": 42,
"soc-maxima": [
{
"datetime": "2024-01-15T11:00:00+00:00",
"margin": "40.0 kWh"
},
{
"datetime": "2024-01-15T12:00:00+00:00",
"margin": "35.0 kWh"
}
]
}
]
}
}
When constraints are all met:
An empty unresolved array indicates successful optimization.
However, check the margins in resolved to understand how tight the constraints were:
Large margins (e.g., 50 kWh) suggest the device has significant flexibility headroom.
Small margins (e.g., 5 kWh) indicate the constraints were nearly violated.
Zero margin would mean the device hit the exact constraint limit.
Use case: If you see very small margins, you may want to relax constraints or provide additional flexibility to create a more robust schedule.
When constraints are unresolved:
Unresolved constraints indicate the optimization problem was over-constrained. Common causes:
Conflicting constraints, such as a high minimum on too short notice.
Insufficient headroom within the grid capacity, caused by inflexible devices.
The violation values tell you how much shortfall exists:
For
soc-minimaviolations: The shortage in kWh. The device could not charge enough.For
soc-maximaviolations: The excess in kWh. The device could not discharge enough.
Use case: If a battery is reporting 20 kWh shortage for a planned trip, you may need to:
Allow more time for charging.
Install a larger battery.
Reduce the minimum SoC requirement.
Stretch the minimum SoC requirement over a longer time period (using the
durationfield) to continue charging in case the user plugs out later than expected.Warn the user about the shortfall.
If the battery is in an EV, charge en-route.
When no constraints are defined:
If unresolved and resolved are both empty, no state-of-charge constraints were set.
Note
soc-targets are reported under unresolved only, and only while constraint relaxation is on.
A target is a two-sided constraint, so its reported violation is the absolute deviation from the target, in either direction,
and there is no headroom to report when a target is met.
With relaxation off, a target that cannot be met makes the entire scheduling job fail instead of producing results with violations.
Work on other schedulers
We believe the two schedulers (and their flex-models) we describe here are covering a lot of use cases already. Here are some thoughts on further innovation:
Writing your own scheduler. You can always write your own scheduler (see Plugin Customization). You then might want to add your own flex model, as well. FlexMeasures will let the scheduler decide which flexibility model is relevant and how it should be validated.
We also aim to model situations with more than one flexible asset, and that have different types of flexibility (e.g. EV charging and smart heating in the same site). This is ongoing architecture design work, and therefore happens in development settings, until we are happy with the outcomes. Thoughts welcome :)
Aggregating flexibility of a group of assets (e.g. a neighborhood) and optimizing its aggregated usage (e.g. for grid congestion support) is also an exciting direction for expansion.