Skip to content

Automations - first roundtrip for forecasts - #2290

Open
Flix6x wants to merge 65 commits into
mainfrom
feat/2288-automations-for-forecasts
Open

Automations - first roundtrip for forecasts#2290
Flix6x wants to merge 65 commits into
mainfrom
feat/2288-automations-for-forecasts

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description

An automation is a recurring task defined on an asset. This first roundtrip covers forecasts: the
automation decides when work is due and queues it; the existing forecasting worker still computes
the forecast and stores it as timed beliefs. Nothing about how a forecast is computed changes.

Data model. A new automation table, with per-asset ownership (cascade delete), a type
(forecasts for now), a name, a cronstr recurrence, an IANA timezone in which that recurrence
is interpreted, a scheduling_cursor, an active flag, a generator_id pointing at the data source
that holds the forecaster configuration, and JSONB parameters for the per-run forecast parameters.

CLI.

  • flexmeasures add automation — create one, defaulting to daily at midnight in
    FLEXMEASURES_TIMEZONE. The forecaster configuration is stored on a data source; the forecast
    parameters are validated and stored on the automation. --source reuses an existing forecaster's
    data source, in which case the options that source already determines are refused.
  • flexmeasures edit automation — rename, re-schedule (--cron), change the --timezone, activate
    or deactivate.
  • flexmeasures delete automation
  • flexmeasures jobs run-automations — queue jobs for whatever is due. Run once per minute from
    cron or another host scheduler.

Recurrence and timezones. Each automation's cron expression is interpreted in its own IANA
timezone, so moving the server or changing FLEXMEASURES_TIMEZONE does not silently reschedule
existing automations. Daylight saving is handled explicitly: a local time skipped in spring runs once
at the transition boundary, and a local time that occurs twice in autumn is not queued twice.

Catching up. Each automation stores a UTC scheduling cursor, so restarting the runner does not
lose occurrences that fell during downtime. Several missed occurrences are coalesced into the latest
useful forecast rather than replayed one by one, and timing parameters that default to the run time
resolve when the caught-up job is queued, so the result is a current forecast. A new automation
starts from its own creation and does not replay history.

At most once. An occurrence is claimed before it is queued, with a compare-and-swap that also
requires the recurrence, the timezone and the active flag to be unchanged since the occurrence was
read. Concurrent runners therefore cannot queue the same occurrence twice, and an edit made between
reading and claiming invalidates the claim rather than clobbering the rebased cursor. A failed
attempt is not retried automatically, because it may already have queued some jobs — durable run
records and safe retries are tracked in #2393.

Validation. A recurrence must be a five-field cron expression that matches at least one real
date, so 0 0 30 2 * is refused rather than accepted as an automation that can never fire. A
timezone must be a real IANA name.

API. [GET] /assets/(id)/automations and [GET] /assets/(id)/automations/(automation_id) list
and inspect an asset's automations, including the sensors each reads from and writes to, its
timezone and scheduling cursor, and counts of recently created jobs per status. A new
[GET] /sources/(id) returns the full record of one data source, including the attributes where
data generators keep their configuration. Asset job entries carry created_via provenance.

UI. The asset gains an Automations page: a sortable listing, and a details modal linking to the
sensors an automation reads from and writes to. A sensor's own page lists the automations that feed
it, and its data source can be inspected as a full record.

Provenance. Every queued job records how it came about — via the CLI, the API, or an automation
(with the automation's id) — so the status page can tell them apart.

  • Added changelog item in documentation/changelog.rst

Look & Feel

Automations defined on an asset get their own page. The listing is sortable, describes each
recurrence in natural language, and shows the timezone that recurrence is interpreted in. The
Schedules and Reports tabs are placeholders until #2293 and #2297:

The Automations page of an asset, listing forecast automations with their recurrence, timezone and job counts, with the Schedules and Reports tabs disabled

An automation's details show the timezone its cron expression is interpreted in, its scheduling
cursor, the data generator holding the forecaster configuration, and the sensors it reads from and
writes to, each linking to its own page. Recently created jobs are counted per status:

The details of one automation, showing its timezone, scheduling cursor, data generator, the sensors it reads from and writes to as links, its stored parameters, and its recent job counts

How to test

See the manual test walkthrough in the PR comments. In short: create an automation with
flexmeasures add automation, activate it, run flexmeasures jobs run-automations, and confirm a
forecasting job was queued and appears on the asset's status page.

Automated coverage:

pytest \
  flexmeasures/cli/tests/test_automations.py \
  flexmeasures/data/tests/test_automations_fresh_db.py \
  flexmeasures/data/tests/test_automation_scheduling_fresh_db.py \
  flexmeasures/data/schemas/tests/test_automations.py \
  flexmeasures/api/v3_0/tests/test_automations_api.py

Further improvements

Related items

Closes #2288. Closes #2392 (timezone-aware recurrence and catch-up, merged in from #2396).
Part of the automations story #2334. Followed by #2293 (schedules as automations).


Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or another incompatible license.

Flix6x and others added 7 commits July 11, 2026 15:06
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
@socket-security

socket-security Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcron-descriptor@​2.1.0100100100100100

View full report

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX
Flix6x and others added 3 commits July 11, 2026 16:09
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
…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
@BelhsanHmida
BelhsanHmida self-requested a review July 31, 2026 01:22
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>
Keep the per-minute Redis guard after failures because an attempt may already have queued some forecast jobs.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Verify that retrying a failed partial queueing attempt does not create duplicate jobs.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Convert YAML dates to ISO strings, accept empty files, and report non-object config or parameter files as usage errors.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Test YAML dates and timestamps, empty files, and invalid top-level list values for automation options.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Hide automation names and IDs from asset job responses when the current user cannot read the source automation.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Verify inaccessible automation provenance is redacted while authorized callers still receive the full identity.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Show a persistent API error instead of presenting failed automation requests as an empty list.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Check that the automations page renders the warning target and hides the table when loading fails.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Avoid interpreting cron wildcard asterisks as RST italic markup when generating OpenAPI documentation.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Flix6x added 7 commits August 5, 2026 18:00
Context:
- New behaviour from the #2290 review needs regression coverage

Change:
- CLI: the daily default recurrence, the --source conflict, and an automation's
  input and output sensors
- API: an automation without a data generator reports no sensors; the new data
  source endpoint, its access rules and its 404
- UI: the source query parameter reaches the page, and automations feeding a
  sensor are listed on it

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- The #2290 review changed user-facing CLI, API and UI behaviour

Change:
- Documented the daily default recurrence and reusing a forecaster via --source
- Documented the links between automations and the sensors they feed
- Added API change log entries for the automations and data source endpoints
- Extended the changelog entry of #2290

Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: F.N. Claessen <felix@seita.nl>

# Conflicts:
#	documentation/api/change_log.rst
#	documentation/changelog.rst
#	documentation/features/forecasting.rst
#	flexmeasures/ui/templates/assets/asset_automations.html
Context:
- The merge combined endpoint docstring changes from both sides

Change:
- Regenerated the specs

Signed-off-by: F.N. Claessen <felix@seita.nl>
…--source

Context:
- The guard added on this branch compared against the assembled config, which
  always holds the schema defaults of the list-valued options, so any use of
  --source was rejected

Change:
- Detect the conflicting options from click's parameter sources, so --source on
  its own works again while explicitly given configuration options still abort
- Name the conflicting options in the error message

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- Listing the automations feeding a sensor sets up a data generator per
  candidate, which does not need to happen for every automation in the database

Change:
- Narrowed the candidates to automations on the sensor's asset or an ancestor,
  which is where an automation writing to it must live

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- Automations now always have a data generator, and the --source guard also
  covers the forecaster configuration options

Change:
- Assert the sensors an automation with a generator reads from and writes to
- Cover a configuration option conflicting with --source

Signed-off-by: F.N. Claessen <felix@seita.nl>
Flix6x added a commit that referenced this pull request Aug 5, 2026
…elves

Context:
- Review of #2290 asked that automations administered through the UI (and hence
  the API) may only involve sensors the creating user has access to; account
  admin rights on the asset should not grant access to another account's sensors

Change:
- Work out the sensors an automation would read from and write to (forecasts:
  the sensor to forecast plus its regressors, and the sensor to save to;
  schedules: the flex-model's device sensors, and whatever the parameters refer to)
- Require read access to the former and create-children (the permission for
  recording data through the API) on the latter, when creating via the API
- The CLI creates automations without a user, and stays unrestricted

Signed-off-by: F.N. Claessen <felix@seita.nl>
@Flix6x

Flix6x commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@BelhsanHmida — Felix went through this PR and we picked up his review points, so here is what changed since you last saw it. Could you review these commits, and then take the PR over again?

What we contributed (on top of your branch, after merging it in):

  • Data generators now report their sensors. DataGenerator got input_sensors / output_sensors properties (implemented for Forecaster: the sensor to forecast plus its regressors, and the sensor to save to), mirrored on Automation. This is what the UI links use, and it is also the hook for permission checks (see CRUD for automations in the API and UI #2294).
  • Automation details link to the sensors it feeds. The details modal now shows "Reads from" and "Writes to", linking to /sensors/<id>?source=<generator id>, and the sensor page pre-selects that data source in its statistics panel. GET /assets/<id>/automations/<automation_id> returns input_sensors and output_sensors for this.
  • The sensor page lists the automations feeding it, permission-filtered, linking to the automations page of their asset. To keep that cheap, only automations on the sensor's asset or an ancestor are considered — which is where an automation writing to it must live, per your validate_forecast_output_scope.
  • A data source can be inspected from the sensor page: an info button next to the source selector opens a modal with the full record, including the attributes where data generators store their configuration. Backed by a new GET /api/v3_0/sources/<id>.
  • The automations listing is sortable (on the ISO timestamp, the activation status and the cron string, rather than on the rendered text), newest first.
  • --cron now defaults to daily ("0 0 * * *"), so flexmeasures add automation no longer requires it.
  • flexmeasures jobs run-automations no longer logs "Starting Train-Predict Pipeline" per automation: run() logged that even when it only queues the cycles, which the workers then announce themselves.

One bug fix worth a closer look: the --source guard compared against the assembled config, which always contains the schema defaults of the list-valued options — so any use of --source was rejected. It now detects the conflicting options from click's parameter sources, so --source on its own works again while explicitly given configuration options still abort, and the error names them.

Note on the timezone review point: it is already covered by #2396 (which also adds the scheduling cursor, catch-up and DST handling), so we deliberately left it out here to avoid a second, conflicting migration.

Heads-up for #2396: it will hit small conflicts in asset_automations.html and data/services/automations.py when it merges this base.

🤖 Generated with Claude Code

BelhsanHmida and others added 4 commits August 7, 2026 20:32
types-Flask pins types-click 7.1, whose stubs shadow the inline types that click ships itself.
Those stubs predate ParameterSource and Context.get_parameter_source, both added in click 8.0,
so mypy rejected the --source conflict detection and pre-commit failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`add automation` reuses the forecast schemas, so Click rendered every forecaster and pipeline option in its help,
burying the options that describe the automation itself.
Accept those options still, but hide them, and let the parameters file supply a field the schema requires,
so --sensor no longer has to be repeated on the command line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SensorIdOrReferenceField deserializes a regressor that filters on sources into a SensorReference rather than a Sensor,
which _resolve_sensors skipped, so those regressors were missing from a data generator's input sensors.
The source filters only narrow down which beliefs are read from a sensor, not which sensor is involved,
so the wrapped sensor counts as an input just like a plain sensor ID does.
This matters beyond the sensor links in the UI: the input and output sensors are meant to carry the access checks
for automations administered through the API, where a missing input sensor means a missing permission check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…are unknown

Working out an automation's sensors could fail for several reasons, and every one of them was reported as no sensors at all.
That is fine for the sensor links in the UI, but the same answer is meant to carry the access checks
for automations administered through the API, where no sensors reads as nothing to check,
so a broken automation would pass every check on the sensors it involves.
Split the two uses: resolve_automation_sensors raises AutomationSensorsUnknown,
while get_automation_sensors keeps reporting none for display.
The broad exception handler is narrowed to the failures that can actually occur here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BelhsanHmida added a commit that referenced this pull request Aug 10, 2026
…ch-up work

Brings in the sensor links, the sortable listing, the data source endpoint and the review fixes from #2290.

Resolved by keeping both sides throughout: the automation now carries a timezone and a scheduling cursor
as well as the input and output sensors, and the CLI hides the forecast schema options while still taking --timezone.

One conflict was not textual. #2290's test for the daily default asserted that an automation is no longer due an hour later,
which held while due automations were matched statelessly against the cron expression.
Here a missed occurrence stays due until it is claimed, so the test now claims the occurrence before asserting that,
and reads the automation off the DueAutomation that get_due_automations returns.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BelhsanHmida and others added 2 commits August 11, 2026 00:07
* feat: add timezone-aware automation catch-up

Store each automation's IANA timezone and a durable UTC scheduling watermark. Canonicalize daylight-saving transitions, coalesce missed forecasts, and claim occurrences before queueing while retaining the existing at-most-once failure policy.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

* test: cover timezone-aware automation scheduling

Exercise timezone defaults and validation, independent timezone evaluation, normal and missed occurrences, DST gaps and folds, durable cursor claims, inactive automations, API/UI exposure, and the existing Redis failure guard.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

* docs: explain automation timezone and catch-up semantics

Document per-automation timezone snapshots, scheduling watermarks, downtime coalescing, daylight-saving behavior, reconfiguration boundaries, and the at-most-once retry limitation. Refresh the published automation API examples.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

* changelog: record automation timezone and catch-up support

Announce the new CLI options, additive automation response fields, persistent scheduling progress, and DST-aware catch-up behavior for issue #2392.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

* docs: link automation catch-up changelog to PR

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

---------

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
BelhsanHmida added a commit that referenced this pull request Aug 10, 2026
Brings in the timezone and catch-up work from #2396, the sensor links, the sortable listing and the review fixes from #2290.

Resolved by keeping both sides, so an automation carries a timezone and a scheduling cursor as well as its input and output sensors,
and the job statistics still scan the scheduling queue for schedule automations while the forecast path is unchanged.

Two resolutions went further than picking a side.
make_cli_options_optional is gone: add_cli_options_from_schema now takes force_optional, which relaxes the same requirement and also hides the forecast options,
which is what a schedule automation wants anyway, since those options only apply to forecasts.
The asset page test no longer expects a single automations table to be hidden, as the listing is now one table per automation type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Validate that a syntactically correct five-field recurrence can produce an actual calendar occurrence, preventing impossible dates such as February 31 from entering the automation table through either CLI or API input.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Keep one legacy or corrupted recurrence from aborting global discovery, and make occurrence claiming an atomic comparison against the active state, recurrence, timezone, and cursor observed by the runner so concurrent edits cannot queue stale work.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Require read access to every resolved input and output sensor before returning full automation details, ensuring the derived metadata and raw parameters cannot reveal cross-organisation sensor information to an asset reader.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Exercise the CLI validation path with a syntactically valid recurrence that can never match, while updating the runner fixture to carry the scheduling snapshot required by atomic occurrence claims.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Verify that corrupt recurrences are isolated and that deactivation, deletion, recurrence edits, timezone edits, or cursor movement after discovery prevent a stale claim, while non-execution name edits remain harmless.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Build an automation with a supplier-owned regressor and confirm that a plain member who may read the automation asset receives a generic denial without the inaccessible sensor name.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
@BelhsanHmida

Copy link
Copy Markdown
Contributor

Manual test walkthrough

A reviewer can exercise the whole roundtrip with this. It is written against this branch only:
schedules and reports are still "coming soon", and automations are created from the CLI
(administering them through the API and UI arrives in #2294).

Start a worker in a second terminal, and pick a sensor that already holds history so a forecast has
something to train on. The examples use asset 242 and its power sensor 913.

flexmeasures jobs run-worker --name automations-demo --queue forecasting

1. The command explains itself

flexmeasures add automation --help

The help stays focused on the automation: the asset, the name, the recurrence and its timezone, the
type, and how the forecaster is configured. Every option flexmeasures add forecast accepts is
still accepted, but is kept out of the help so it does not bury the options that describe the
automation itself.

Options:
  --asset GENERICASSETIDFIELD  ID of the asset to automate a recurring task
                               for.  [required]
  --name TEXT                  Name of the automation.  [required]
  --cron CRONFIELD             Recurrence as a standard five-field cron
                               expression, e.g. "0 6 * * *" for daily at
                               06:00. The expression is interpreted in the
                               automation timezone. Defaults to daily at
                               midnight.  [default: 0 0 * * *]
  --timezone TIMEZONEFIELD     IANA timezone in which to interpret --cron,
                               e.g. "UTC" or "Europe/Amsterdam". Defaults to
                               FLEXMEASURES_TIMEZONE.  [default:
                               (FLEXMEASURES_TIMEZONE)]
  --type [forecasts]           Type of task to automate.  [default: forecasts]
  --inactive                   Add this flag to create the automation in
                               deactivated state.
  --forecaster TEXT            Forecaster class registered in
                               flexmeasures.data.models.forecasting or in an
                               available flexmeasures plugin. Defaults to
                               TrainPredictPipeline. Use the command
                               `flexmeasures show forecasters` to list all the
                               available forecasters. Cannot be combined with
                               --source, which already determines the
                               forecaster.
  --source DATASOURCEIDFIELD   DataSource ID of the `Forecaster`. The
                               forecaster class and its configuration are read
                               from the data source's data generator
                               attributes, so --forecaster and --config are
                               not needed (or allowed) with it.
  --config FILENAME            Path to the JSON or YAML file with the
                               configuration of the forecaster. Cannot be
                               combined with --source, which already
                               determines the configuration.
  --parameters FILENAME        Path to the JSON or YAML file with the forecast
                               parameters (passed to the compute step on each
                               run of the automation).
  --help                       Show this message and exit.

2. Validation refuses what could never work

# a recurrence that matches no real date
flexmeasures add automation --asset 242 --name "Impossible" --cron "0 0 30 2 *" --sensor 913
# → Error: Invalid value for '--cron': '0 0 30 2 *' does not match any possible date.

# six fields instead of five
flexmeasures add automation --asset 242 --name "Six fields" --cron "0 0 * * * *" --sensor 913
# → Error: ... must contain exactly five fields (minute, hour, day of month, month, and day of week).

# a timezone that does not exist
flexmeasures add automation --asset 242 --name "Bad tz" --cron "0 6 * * *" \
  --timezone Europe/NotAmsterdam --sensor 913
# → Error: Invalid value for '--timezone': Timezone 'Europe/NotAmsterdam' does not exist.

Nothing is created by any of these. The first is the interesting one: 0 0 30 2 * is a perfectly
well-formed cron expression, and without this check it would be accepted as an automation that can
never fire.

3. Create one, inactive, and read it back

flexmeasures add automation \
  --asset 242 \
  --name "Campus power forecast" \
  --cron "0 6 * * *" \
  --timezone Europe/Amsterdam \
  --inactive \
  --sensor 913
# → Successfully created inactive automation '...' (ID: N) to compute forecasts for asset 242,
#   recurring per cron string '0 6 * * *' in timezone 'Europe/Amsterdam'.
/assets/242/automations

The listing is sortable and shows the recurrence in natural language alongside the timezone it is
interpreted in. On this branch the Schedules and Reports tabs are present but disabled.

Details shows what the automation would read and write. Both are sensor 913 here, because a
forecast is saved to the sensor it forecasts unless --sensor-to-save says otherwise, and each
sensor links to its own page.

The same information is available over the API:

/api/v3_0/assets/242/automations              # the listing
The automations listing over the API
/api/v3_0/assets/242/automations/<N>          # one automation, in full

The detail response carries input_sensors, output_sensors, timezone, scheduling_cursor,
recurrence_description, the generator holding the forecaster configuration, the stored
parameters, and job_stats counting recently created jobs per status.

One automation in full, over the API

4. The recurrence belongs to the automation, not the server

flexmeasures edit automation --id N --timezone Asia/Seoul
# → Successfully updated automation '...' (ID: N): timezone: 'Europe/Amsterdam' → 'Asia/Seoul'.

0 6 * * * in Asia/Seoul is 21:00 UTC the day before, so moving the server or changing
FLEXMEASURES_TIMEZONE does not silently reschedule anything. Changing the timezone also rebases
the scheduling cursor, so the automation does not immediately catch up on occurrences that only
exist in the new timezone's past. Put it back:

flexmeasures edit automation --id N --timezone Europe/Amsterdam

5. Run it

flexmeasures edit automation --id N --cron "* * * * *" --activate
flexmeasures jobs run-automations
# → Automation N ('Campus power forecast') queued 1 forecasting job(s) for asset 242.

Run it again within the same minute:

flexmeasures jobs run-automations
# → the occurrence was already claimed; nothing is queued

That is the at-most-once guard. An occurrence is claimed before it is queued, and the claim also
requires the recurrence, the timezone and the active flag to be unchanged since it was read — so two
runners cannot queue the same occurrence, and an edit made in between invalidates the claim instead
of being overwritten.

6. See where the job came from

/assets/242/status

Jobs record how they were created — via the CLI, the API, or an automation, with the automation's
id — so a job queued here is distinguishable from one triggered by hand.

Once the worker has run it, the forecast is ordinary belief data:

/sensors/913

The sensor's page also lists the automations that write to it, and its data source can be inspected
as a full record.

7. Catching up after downtime

Leave the automation active and due, but stop running the runner for a few minutes. Then run it
once. It queues a single job, not one per missed minute: several missed occurrences are coalesced
into the latest useful forecast, and timing parameters that default to the run time resolve when the
job is queued, so the result is a current forecast rather than a replay of an old window.

A newly created automation starts from its own creation and does not replay history, and the cursor
lives in the database, so restarting the runner does not lose due occurrences.

8. Clean up

flexmeasures edit automation --id N --deactivate
flexmeasures delete automation --id N --force

Deleting an automation removes only its definition. Forecasts it already produced are ordinary
beliefs and stay.

What this deliberately does not cover

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automations - first roundtrip for forecasts Add per-automation timezones and catch-up semantics

2 participants