Run reports via CLI and API - #2298
Conversation
Automations are recurring tasks (for now: computing forecasts) defined per asset. The recurrence is defined by a cron string, and the work to be done is defined by a data generator (linked through a data source) together with the parameters to call it with. Includes a migration for the new table, and new dependencies on croniter (cron matching/validation) and cron-descriptor (natural-language recurrence descriptions). Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
- `flexmeasures add automation` creates an automation (active by default), validating the forecast parameters with the forecast parameter schema and storing the forecaster config on a data source. - `flexmeasures edit automation` edits the name, recurrence (cron string) or activation status. - `flexmeasures delete automation` deletes an automation. - All three record their events in the asset's audit log. - `flexmeasures jobs run-automations` queues jobs for all automations due this minute (to be run once per minute, e.g. via cron), with a Redis-based guard against duplicate runs within the same minute. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Data generators can now be told how their queued jobs got triggered (via the CLI, the API or an automation), and the train-predict pipeline stores this on the jobs as meta data. The asset's status page shows it in a new 'Created Via' column of the jobs table. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
GET /api/v3_0/assets/<id>/automations lists the automations defined on an asset (without generator and parameters details). GET /api/v3_0/assets/<id>/automations/<automation_id> additionally provides the parameters, data generator info and counts of recently created jobs per job status. Both are documented in the OpenAPI specs. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
/assets/<id>/automations shows the asset's automations in a tabbed view (schedules and reports tabs are prepared but deactivated), with per-row details (parameters, data generator, job counts) loaded asynchronously into a modal. The page is linked in the breadcrumbs dropdown and links to the status page, where recent jobs are listed. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
CI runners have no locale set (POSIX), which made cron-descriptor render 'At 06:00' while dev environments with an en_US-style locale rendered 'At 06:00 AM'. Request 24-hour format explicitly so the description is deterministic across environments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pxkeq64jtENY7fiWjwUsVS
- Escape automation names (and other user-controlled strings) in the Automations page and the status page's jobs table, closing two stored HTML/script injection sinks. - Wipe parameter state on the (possibly shared) cached data generator before each automation run, so automations sharing a generator data source don't pollute each other's runs. - Count automation job stats under the forecast target sensor(s) from the automation's parameters, which may belong to a different asset. - Release the per-minute Redis guard when a run fails, so a retry within the same minute can still queue jobs. - Return 404 (as documented) for nonexistent automation ids on the detail endpoint, and check permissions on the asset, so automation ids can no longer be enumerated across accounts via 403-vs-422 differences. - Use ondelete=SET NULL for the generator FK: deleting a data source no longer silently deletes automations. - Delegate Automation ACL to the asset's ACL instead of duplicating it. - Extract the config/parameters assembly shared by `add forecasts` and `add automation` into a helper (which no longer drops falsy config values). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Completes the previous commit, whose staged files were dropped by an interrupted pre-commit run: template escaping, shared-generator state reset, job stats under target sensors, Redis guard release on failure, 404 for nonexistent automations, SET NULL generator FK, ACL delegation, and the shared CLI config/parameters assembly helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
The scheduling job creators accept an optional trigger dict (stored as job meta data), like the forecasting pipeline already does. The API trigger endpoint records origin API; the CLI and automations follow in the next commit. The status page's 'Created Via' column picks this up automatically. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Automations now also support the 'schedules' type: - `flexmeasures add automation --type schedules` validates the parameters as a schedule trigger message (per the AssetTriggerSchema, as accepted by the API trigger endpoint, without the asset id). The schedule 'start' may be omitted, in which case each run schedules from the run time (floored to the message's resolution, if given) — a fixed start draws a warning. - The runner dispatches schedules automations to the same job creators as the API trigger endpoint (sequential or simultaneous), recording trigger meta data (origin automation) on the queued jobs; `flexmeasures add schedule --as-job` now records origin CLI. - Job stats for schedules automations are counted from the scheduling job cache (asset-level wrap-up jobs and per-sensor device jobs). - The UI automations page's Schedules tab is now enabled, with automations filtered by type per tab. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
- New endpoints on assets: POST /automations (create, validating parameters by automation type), PATCH /automations/<id> (name, cron string, activation status) and DELETE /automations/<id>. Managing automations requires the same principals that may delete the asset (account admins and consultants). - The UI automations page gets a 'New automation' modal and per-row (de)activate and delete actions, shown to users with management rights. - Creation, update and deletion logic (incl. audit log records) moved into the automations service, shared by the CLI commands and the API endpoints. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
- Activate the reporting queue (it was prepared but commented out), including worker help texts and queue cleanup. - Reporters accept as_job: a job is queued (with trigger meta data) that rebuilds the reporter from its data source, computes the report and saves the results to the database. - `flexmeasures add report --as-job` queues such a job; reporting jobs show up in the asset's jobs overview (status page and API). Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Automations can now compute reports on a recurring basis: - `flexmeasures add automation --type reports --reporter <class>` stores the reporter config on a data source (steady across runs, so all report results attribute to the same source) and validates the report parameters. - The report window resolves freshly on each run: 'start-offset'/'end-offset' fields (comma-separated Pandas offsets, applied to the run time in the first output sensor's timezone) express a rolling window, and without any timing fields the window defaults to the last cron period (from the previous cron fire time until the run time). Absolute start/end still work, but draw a warning. - The API creation field 'forecaster' is generalized to 'generator' (also accepting reporter classes), and the UI's New automation modal gains data generator and config fields; the Reports tab is now enabled. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Each automation run is recorded in Redis; a report automation without timing fields then reports on the period since its actual last run, falling back to the last cron period when no last run is known (e.g. on the first run, or after a Redis flush). This gives gapless coverage even when runs are missed. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Consolidates the shared automations concept (model, lifecycle, runner deployment, provenance) into documentation/features/automations.rst, with the per-feature pages linking to it and keeping only their type-specific parameter semantics. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Add POST /api/v3_0/assets/<id>/reports/trigger, which queues a one-off reporting job on the reporting queue, mirroring how forecasts and schedules are triggered via the API. The job status can be polled via the generic GET /api/v3_0/jobs/<uuid> endpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Documentation build overview
145 files changed ·
|
- API automation creation now checks that the caller may read every sensor referenced in the parameters/config and record data on the sensors the automation writes to, closing a cross-account data read/write hole. - `add report --as-job` implies --save-config (the worker rebuilds the reporter from its data source, so jobs without stored config always crashed). - Report automation parameters are validated with the chosen reporter's own parameters schema, not the base schema. - Invalid start-offset/end-offset strings are rejected at creation instead of being silently skipped at run time (which yielded empty report windows). - run_report_job wipes the shared cached reporter's parameter state, like the automation runner already did, so consecutive jobs in one worker process don't pollute each other. - Default report windows now anchor to the end of the last *successfully* covered window, recorded by the reporting job upon success — failed jobs no longer create permanent reporting gaps, and the enqueue-time minute-rollover gap is gone (the recorded anchor is the window end itself). - The cron-period fallback window is computed in the platform timezone, matching how the runner decides when automations fire. - Job stats for schedules automations also scan flex-model device sensors (which may belong to child assets), so failed per-device jobs show up. - The trigger provenance kwarg is excluded from the job cache hash, so identical schedule requests from different origins dedupe again. Part of #2288 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
The report trigger endpoint now applies the same sensor-access guard as automation creation: the caller must be able to read every input sensor and record data on every output sensor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
…essage format PR #2303 makes click report the validation message rather than the offending value, which changes the exact wording of this error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Merge current main, resolve the shared forecasting and documentation changes, regenerate the lockfile, and move the automation migration after the current migration head. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Reject cron expressions with seconds, year fields, or aliases because the automation runner executes once per minute. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Test valid five-field expressions and reject unsupported seconds, year, and alias formats. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Context: - The report trigger advertised a canonical asynchronous response, but its generated OpenAPI operation documented only the 202 status code. Change: - Describe the required status, message, job and job-url fields and provide a concrete accepted-response example in the generated API contract. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Context: - Host documentation omitted reporting from configurable timeout and installation guidance, and its combined worker example left shell pipe characters unquoted. Change: - Document reporting timeout configuration, include reporting in worker setup commands and quote the multi-queue value so the example executes as intended. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Context: - Queue registration tests did not prove that created report jobs inherited the configured timeout, and status tests did not exercise reporting failures. Change: - Assert the effective timeout on a CLI-created report job and verify that failed reporting jobs retain their queue-specific status label. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
|
@nhoening Thanks, I followed your proposal and made #2298 standalone on It now includes the reporting queue and worker, I also added defensive validation for non-empty reporter inputs and outputs before The I have also updated the PR description to reflect the 202 |
Signed-off-by: Nicolas Höning <nicolas@seita.nl>
Context: - #2290 was squash-merged into main. Unlike the branches stacked under it, this one does not contain #2290's commits, so this is a real merge rather than a formality. Change: - Took main's version of every conflict, each being a superset of what this branch had: set_job_trigger now records the automation an job came from, input_sensors and output_sensors explain what they are for, _resolve_sensors documents how it treats sensor references and no longer reads a bool as a sensor ID, and the upward tree walk moved into the shared asset_and_ancestor_ids helper. - Kept this branch's API changelog entry for the report trigger endpoint, under the section underline main corrected. - Regenerated the OpenAPI specs. The migration lineages joined without a new head. Signed-off-by: F.N. Claessen <felix@seita.nl>
Context: - Merging main left this import twice, once from each side, which flake8 rejects as F811. The file merged without a conflict, and I had only linted the files I resolved by hand. Change: - Kept one import. Signed-off-by: F.N. Claessen <felix@seita.nl>
nhoening
left a comment
There was a problem hiding this comment.
This is looking pretty good. I have comments, but only small ones.
I did try the manual testing via SwaggerUI. It went okay, only I believe we are not failing when we should when computing the job.
I used the PandasReporter example from the PR description, but my input sensors have different units (EUR and kW, outut sensor has kW). I expected an error on this.
Instead, the worker shows no error, and the job result (via the job status endpoint) looks like this:
{
"ended-at": "2026-09-01T16:33:56.598447+00:00",
"ended_at": "2026-09-01T16:33:56.598447+00:00",
"enqueued-at": "2026-09-01T16:32:45.590679+00:00",
"enqueued_at": "2026-09-01T16:32:45.590679+00:00",
"exc-info": null,
"exc_info": null,
"func-name": "flexmeasures.data.services.reporting.run_report_job",
"func_name": "flexmeasures.data.services.reporting.run_report_job",
"message": "Reporting job has finished.",
"origin": "reporting",
"result": [
{
"n_rows": 194,
"sensor_id": 49348
}
],
"started-at": "2026-09-01T16:33:56.339937+00:00",
"started_at": "2026-09-01T16:33:56.339937+00:00",
"status": "FINISHED"
}
I checked and sensor 49348 has no data.
I am not sure if what I saw is something that belongs in this PR as a fix or not, but it would be good to investigate.
| reporter._data_source = db.session.merge(reporter.data_source) | ||
| db.session.flush() | ||
| data_source_id = reporter._data_source.id | ||
| db.session.commit() |
There was a problem hiding this comment.
Why is a commit needed here? I can't what changrd on reporter, as data_source was on it before already.
If we need to commit, why also do a flush right before?
There was a problem hiding this comment.
Added a comment for the first one.
The commit is needed because reporter.data_source is a lazy property (data_sources.py:245) that can create a brand-new source row. The worker is a separate process and our views don't auto-commit, so without it the worker's lookup by id fails. No test catches this work_on_rq runs SimpleWorker in-process on the same session which is why the comment seemed worth adding.
On the flush: you're right, it's redundant given the commit right after. It only matters because the id is read before the commit; moving that read below would let it go. Left it for consistency with the same block in train_predict.py:454-458 happy to drop it in both in a follow-up commit.
Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
Context: - The newly added return annotation referenced Sensor only through a function-local import, so flake8 reported an undefined name at module scope. Change: - Import Sensor under TYPE_CHECKING so the annotation remains cycle-safe and lintable. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Context: - FlexMeasures v1.0 was released before the report trigger endpoint, so documenting it under v3.0-32 incorrectly placed it in the previous API release. Change: - Move the report trigger changelog entry to the existing v3.0-33 section. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
Log which data source ran and how many values landed on which sensors.
I verified this locally. The example did not declare expected units, so I updated the example to declare kW units, added coverage confirming that a EUR input makes the job fail, and fixed the job result to report zero persistable rows instead of counting dropped NaN rows. |
Description
Closes #2426.
This PR makes one-off reports available as background jobs, independently from report automations. It brings the non-automation-specific reporting infrastructure forward from #2297, #2290 and #2294 so the CLI and API functionality can be reviewed, released and tested on its own.
reportingRQ queue with configurable job timeouts and worker support.flexmeasures add report --as-jobfor queueing reports from the CLI.POST /api/v3_0/assets/<id>/reports/triggerfor queueing reports through the API.create-childrenaccess to every output sensor.documentation/changelog.rst.The API request envelope is validated first. The selected reporter then validates
configandparameterswith its concrete schemas. FlexMeasures resolves and authorizes all declared dependencies before creating the reporting job.Look & Feel
CLI
Queue a report instead of computing it in the CLI process:
Process queued reports with a reporting worker:
flexmeasures jobs run-worker --queue reporting--as-jobstores report results in the database and therefore cannot be combined with--dry-runor--output-file.API
The endpoint is included in the generated OpenAPI specification and can be tried interactively through Swagger UI:
http://127.0.0.1:5000/api/v3_0/docs.POST /api/v3_0/assets/{id}/reports/trigger.42.{ "reporter": "PandasReporter", "config": { "required_input": [ {"name": "one", "unit": "kW"}, {"name": "two", "unit": "kW"} ], "required_output": [ {"name": "sum", "unit": "kW"} ], "transformations": [ { "df_input": "one", "method": "add", "args": ["@two"], "df_output": "sum" } ] }, "parameters": { "input": [ {"name": "one", "sensor": 12}, {"name": "two", "sensor": 13} ], "output": [ {"name": "sum", "sensor": 14} ], "start": "2023-04-10T00:00:00+00:00", "end": "2023-04-10T10:00:00+00:00" } }Select Execute. Swagger UI shows the generated request and the server response. A valid request returns HTTP
202 Acceptedusing the canonical asynchronous-job response:{ "status": "ACCEPTED", "message": "Request has been accepted for processing.", "job": "<job UUID>", "job-url": "/api/v3_0/jobs/<job UUID>" }The response is defined by the generated OpenAPI documentation. The caller can follow
job-urlor useGET /api/v3_0/jobs/{uuid}in Swagger UI until the job finishes. The queued job records{"origin": "API"}in its metadata.How to test
Automated tests
The focused suite covers:
Latest local result:
119 passed, 22 warnings.Manual test
startandendtimes.flexmeasures add report ... --as-joband confirm a job is added to thereportingqueue.flexmeasures jobs run-worker --queue reportingand confirm the job finishes and beliefs are stored on the output sensor.jobandjob-url.GET /api/v3_0/jobs/<job UUID>and confirm the job moves from queued to finished.Further Improvements
Recurring report automations, automation UI work and prepared report templates remain outside this standalone PR and continue in their respective follow-up work.
Related Items
Sign-off