Skip to content

CRUD for automations in the API and UI - #2294

Open
Flix6x wants to merge 24 commits into
feat/2288-schedule-automationsfrom
feat/2288-automations-crud
Open

CRUD for automations in the API and UI#2294
Flix6x wants to merge 24 commits into
feat/2288-schedule-automationsfrom
feat/2288-automations-crud

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description

Automations can now be created, updated and deleted through the API and the UI, not only from the
CLI.

API. [POST] /assets/(id)/automations, [PATCH] /assets/(id)/automations/(automation_id) and
[DELETE] /assets/(id)/automations/(automation_id). They require the same rights as deleting the
asset — account admins and consultants — which matches the automation's own access rules: whoever
may read an asset may read its automations, and whoever may delete it may change them. A PATCH
covers the name, the recurrence, the timezone and the activation status; the parameters are
deliberately not editable, so the sensors an automation involves stay the ones its creator was
checked against.

UI. The asset's Automations page gains a creation modal and per-row actions for those users.

Only sensors the creator can access. An automation administered this way may only involve
sensors its creator can access themselves: read access to the sensors it reads from, and
create-children on the sensors it writes to, which is the permission the API already requires for
recording data on a sensor. A refused request gets a 403 naming the sensor and the action. This
sits behind a flag that the API passes; the CLI creates automations without a user and stays
unrestricted.

Which sensors those are depends on the type:

  • Forecasts — the forecaster reports its own input and output sensors, derived from the very
    config and parameters it will run with, so a regressor that filters on sources counts too even
    though it is a sensor reference rather than a plain sensor.
  • Schedules — the scheduler resolves its flex config first, so sensors inherited from the asset
    tree are included, and the outputs are then taken from the fields that name where results are
    recorded: a device's power sensor, its state of charge, consumption and production sensors, and
    the flex-context's aggregates. Everything else the parameters refer to counts as an input.

Timezone through the API. An automation carries the timezone its recurrence is interpreted in.
Creation and update now accept it, so automations administered through the API or the UI are no
longer stuck on the server's timezone. Changing it rebases the scheduling cursor, exactly as the CLI
does.

Refusals leave nothing behind. The data source holding a forecaster's configuration is set up
only once the automation is allowed, so a refused request adds nothing. The check on where a
forecast may be recorded runs after the access check, so a sensor the caller may not read is refused
as forbidden rather than described as being outside the asset.

Consolidation. Creating, updating and deleting live in flexmeasures/data/services/automations.py,
so the CLI and the API share one implementation rather than each building automations inline.

  • Added changelog item in documentation/changelog.rst

Look & Feel

An account admin gets a New automation button and per-row Edit, Activate /
Deactivate and Delete actions:

The Automations page as an account admin, with a New automation button and Edit, Deactivate and Delete on every row

The same page, for the same asset, as a plain user of the same organisation. The automations and
their details are still readable; nothing that would change them is offered:

The same Automations page as a plain user, showing only the listing and the Details button

Creating one asks for the type, the recurrence and the timezone it is interpreted in, and the
parameters — forecast parameters, or a schedule trigger message:

The New automation modal, with fields for name, type, recurrence, timezone and parameters

How to test

See the manual test walkthrough in the PR comments.

pytest \
  flexmeasures/api/v3_0/tests/test_automations_api.py \
  flexmeasures/api/v3_0/tests/test_automations_api_fresh_db.py \
  flexmeasures/cli/tests/test_automations.py \
  flexmeasures/data/tests/test_automations_fresh_db.py \
  flexmeasures/ui/tests/test_asset_crud.py

Coverage includes a forecast on another organisation's sensor, a forecast whose source-filtered
regressor
is another organisation's sensor, a schedule aggregated onto another organisation's
sensor, and the timezone roundtrip through creation and update. Each was verified to fail without
the check it covers.

Further improvements

  • Which sensors a schedule would be recorded on is worked out by resolving the flex config and then
    reading the fields that name where results go. That agrees with the scheduler's own resolution for
    a device's sensor, consumption and production, and errs towards reporting more rather than
    fewer. Checking outputs against what a scheduler actually returns at run time is tracked in Check the sensors a scheduler actually writes to, instead of predicting them when an automation is created #2421.
  • The parameters of an existing automation cannot be edited. Changing what an automation computes
    means deleting it and creating a new one, which keeps the access check honest but is blunt.

Related items

Closes #2372. Part of the automations story #2334. Stacked on #2293.


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 2 commits July 11, 2026 18:44
- 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
@BelhsanHmida BelhsanHmida linked an issue Jul 30, 2026 that may be closed by this pull request
Flix6x added 4 commits August 5, 2026 18:59
…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>
Context:
- The endpoint description now states the sensor access rule

Change:
- Regenerated the specs

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- The sensor access rule for created automations needs regression coverage

Change:
- An account admin creating an automation on another account's sensor gets a 403
  naming that sensor, and no automation is created; the same request on their own
  sensor still succeeds (verified to fail without the check)

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- The sensor access rule is user-facing

Change:
- Documented it in the forecasting feature docs, the changelog entry of #2294
  and the API change log

Signed-off-by: F.N. Claessen <felix@seita.nl>
@Flix6x
Flix6x requested a review from BelhsanHmida August 5, 2026 17:00
Flix6x added 2 commits August 5, 2026 23:33
Context:
- Schedulers hand their results to make_schedule as (sensor, data) pairs, and
  those sensors are not only the flex-model's device sensors: a schedule is also
  recorded on a device's state-of-charge, consumption and production sensors, and
  on the flex-context's aggregate-consumption and aggregate-production sensors

Change:
- Derive a schedule's output sensors from all the fields that name where generated
  data goes, at any depth in the flex-model and flex-context (which schedulers
  deserialize themselves, so their sensor references are still raw)
- Everything else the parameters refer to (e.g. price sensors and the sensors of
  inflexible devices, which may also live on the flex-context) counts as an input

Signed-off-by: F.N. Claessen <felix@seita.nl>
Context:
- The flex-context's aggregate-consumption sensor is written to, so it needs the
  same check as the flex-model's own sensors

Change:
- Posting such an automation gets a 403 that names the sensor and the action
  (verified to fail when only the flex-model's sensors are treated as outputs)

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

Flix6x commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@BelhsanHmida — Felix reviewed the automations work and asked for one thing to land here: an automation administered through the UI (and hence the API) may only involve sensors that its creator can access. Could you review these commits, and then take the PR over again?

What we contributed:

  • create_automation now works out which sensors an automation would read from and write to, and requires read access to the former and create-children on the latter — the same permission the API requires for recording data on a sensor. A refused request gets a 403 naming the sensor and the action, through the existing api_message mechanism.
  • This is behind a check_permissions flag that the API passes; the CLI creates automations without a user and stays unrestricted.
  • Forecasts: the sensor to forecast plus its regressors are inputs, the sensor to save to (the same sensor by default) is the output.
  • Schedules: schedulers hand their results to make_schedule as (sensor, data) pairs, so the outputs are not only the flex-model's device sensors — a schedule is also recorded on a device's state-of-charge, consumption and production sensors, and on the flex-context's aggregate-consumption and aggregate-production. We derive the outputs from all of those fields at any depth, since the flex-model and flex-context are deserialized by the scheduler itself and their sensor references are still raw at this point. Everything else the parameters refer to (price sensors, inflexible-device sensors — these can live on the flex-context too) counts as an input.
  • Tests cover both a forecast on another account's sensor and a schedule aggregated onto another account's sensor; both were verified to fail without the check. Docs, changelog and API change log updated.

Two things to be aware of:

  1. This branch is ~108 commits behind its base, so it does not yet have the input_sensors / output_sensors properties we added to DataGenerator in Automations - first roundtrip for forecasts #2290. We kept the sensor resolution inside create_automation rather than duplicating those properties, so syncing the stack should be a small edit rather than a conflict — but the two should be collapsed into one implementation then.
  2. Deriving output sensors statically is an approximation of what a scheduler actually returns at run time. If a scheduler starts writing somewhere else, this check will not know about it, so it is worth revisiting whenever a new output field is added.

🤖 Generated with Claude Code

Brings in schedule automations, the timezone and catch-up work, the sensor links and the review fixes from further down the stack.

The CRUD refactor is kept: the CLI still calls create_automation and update_automation rather than building automations inline,
so the services carry the logic that the base had grown there.
create_automation therefore takes a timezone, and update_automation takes one too and rebases the scheduling cursor
whenever it changes what is due, namely the recurrence, the timezone or reactivation.

Two resolutions went further than picking a side.
The forecast output scope is now validated after the access check rather than before,
so that a sensor the user may not read is refused as forbidden rather than described as being outside the asset.
A test that created a forecast automation without a data generator now passes one, as the base requires forecasts to have one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@BelhsanHmida
BelhsanHmida marked this pull request as ready for review August 10, 2026 23:55
BelhsanHmida and others added 12 commits August 11, 2026 01:08
…olves

A regressor that filters on sources deserializes into a sensor reference rather than a sensor,
which collect_sensors skipped, so such a regressor was left out of the sensors an automation reads from.
The access check is built on that list, so a user could set up an automation reading a sensor they cannot read themselves.

Ask the forecaster instead, as it derives its input and output sensors from the same config and parameters it will run with,
and already resolves sensor references. Schedules keep their own collection, as they have no data generator to ask.
Displaying the sensors involved and checking access to them now share one implementation, so they cannot disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
… API

An automation carries the timezone its cron expression is interpreted in, and the CLI can set and change it,
but the API could do neither, so every automation created through the API or the UI was stuck on the server's timezone.
Both the creation and the update schema now accept a timezone, defaulting to FLEXMEASURES_TIMEZONE on creation.

Also restores the OpenAPI spec's version string, which a regeneration during the merge had replaced with the locally installed version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
…mation is allowed

Creating an automation looked up or created the data source holding the forecaster configuration before checking
whether the user may involve the sensors at all, so a refused request still added a data source within that request.
Nothing committed in between, so this did not outlive the request, but it relied on that rather than on the order of events.
The data source is now set up after the access check, which makes a refused request leave nothing behind by construction.

Also records what the output sensor field list approximates, namely the sensors a scheduler returns results for at run time,
and therefore how it can drift away from them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>

# Conflicts:
#	flexmeasures/api/v3_0/assets.py
#	flexmeasures/data/schemas/automations.py
#	flexmeasures/data/services/automations.py
Permission failures now identify an inaccessible automation dependency only by the sensor ID supplied in the request. This preserves a useful reference for the caller without confirming private sensor names across organisation boundaries.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Automation endpoint tests now use function-scoped fresh database fixtures because they create, update, and delete automations and related sensors. The permission cases also assert that forbidden responses retain the submitted sensor ID without disclosing its private name.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The asset automations view now supplies the canonical IANA timezone choices accepted by the automation schema. Keeping the options server-side ensures the create and edit controls offer the same vocabulary that the API validates.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Managers can now choose an IANA timezone when creating an automation and edit its name, recurrence, timezone, and active state from the asset page. New automations default to the asset timezone, while the API remains responsible for validating every submitted value.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The asset page regression test verifies that managers receive create and edit timezone fields, that creation starts from the asset timezone, and that both forms include their selected timezone in the corresponding API request.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The automation CRUD entry now records that recurrence timezones are selectable in the user interface and uses the established organisation terminology for the people allowed to manage them.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Schedule sensor discovery now leaves creation-time schema and scheduler errors intact so the CLI and API can render their established validation responses. Stored automation resolution still wraps those failures as unknown dependencies for strict permission checks and lenient displays.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
@BelhsanHmida BelhsanHmida mentioned this pull request Aug 12, 2026
3 tasks
@BelhsanHmida

Copy link
Copy Markdown
Contributor

Manual test walkthrough

Covers what CRUD adds over #2290 and #2293: administering automations through the API and the UI,
and the rule that you may only involve sensors you can access yourself. The responses below were
captured against a development database on this branch.

You need two logins to see the interesting half:

  • an account admin (or consultant) on the asset — can create, edit and delete automations
  • a plain user of the same organisation — can read automations, and is offered nothing that
    would change them

The examples use asset 242 (Campus, owned by the Campus Demo organisation) and its power sensor
913.

1. The page gains controls, for the right people

/assets/242/automations

As an account admin the listing gains a New automation button and per-row Edit,
Activate / Deactivate and Delete actions. As a plain user of the same organisation the
listing and its Details are unchanged, and none of those controls appear. Both are shown in the
PR description.

2. Create one from the UI

New automation asks for the name, the type, the recurrence, the timezone it is interpreted in,
and the parameters — forecast parameters for a forecast automation, or a schedule trigger message
for a schedule one. Create a forecast automation on sensor 913, daily at 06:00, in
Europe/Amsterdam, and it appears in the listing with its recurrence in words and its timezone.

3. The same over the API

# create
POST /api/v3_0/assets/242/automations
{
  "name": "Daily campus forecast",
  "type": "forecasts",
  "cronstr": "0 6 * * *",
  "timezone": "Europe/Amsterdam",
  "parameters": {"sensor": 913}
}
# → 201, with the automation, its timezone and its scheduling cursor

# change the recurrence, the timezone and the activation status
PATCH /api/v3_0/assets/242/automations/<N>
{"cronstr": "30 7 * * *", "timezone": "Asia/Seoul", "active": false}
# → 200

# delete
DELETE /api/v3_0/assets/242/automations/<N>
# → 204

The timezone is worth checking specifically: an automation created through the API used to be stuck
on the server's timezone, with only the CLI able to change it. Changing it also rebases the
scheduling cursor, so the automation does not immediately catch up on occurrences that only exist in
the new timezone's past.

Note that parameters cannot be patched. What an automation computes is fixed when it is created,
which is what keeps the access check below meaningful — the sensors it involves stay the ones its
creator was checked against.

4. You may only automate sensors you can access

This is the substance of the PR. As an account admin of one organisation, try to forecast a sensor
belonging to another. Sensor 426 below belongs to a different organisation than asset 242:

POST /api/v3_0/assets/242/automations
{"name": "Refused", "type": "forecasts", "cronstr": "0 6 * * *",
 "parameters": {"sensor": 426}}
HTTP 403
{
  "message": "You cannot set up an automation that would read data from sensor 426, because you cannot read data from it yourself.",
  "result": "Rejected",
  "status": "INVALID_SENDER"
}

A regressor that filters on sources is a sensor reference rather than a plain sensor, and is checked
just the same:

POST /api/v3_0/assets/242/automations
{"name": "Refused", "type": "forecasts", "cronstr": "0 6 * * *",
 "parameters": {"sensor": 913},
 "config": {"regressors": [{"sensor": 426, "source-types": ["forecaster"]}]}}
HTTP 403
{
  "message": "You cannot set up an automation that would read data from sensor 426, because you cannot read data from it yourself.",
  "result": "Rejected",
  "status": "INVALID_SENDER"
}

Three things are worth confirming while you are here:

  • Nothing is created. The automation does not appear in the listing, and no data source is left
    behind for the forecaster it would have used — the data source is set up only once the automation
    is allowed.
  • The message names the id, not the name. Refusing access should not disclose what a sensor you
    cannot read is called.
  • Writing is checked separately from reading. Reading a sensor's data needs read access;
    recording data on one needs the same permission the API requires for recording data by hand.

For schedules the same applies to whatever the schedule would be recorded on, including sensors the
scheduler inherits from the asset tree rather than ones written out in the trigger message.

5. The CLI stays unrestricted

flexmeasures add automation --asset 242 --name "From the CLI" --cron "0 6 * * *" --sensor 913

The CLI runs without a user, so it is trusted and not subject to the check above. That is deliberate:
the restriction is about what a user may set up through the API or the UI.

6. Clean up

Delete anything you created, from the listing's Delete action or with
flexmeasures delete automation --id N --force.

@nhoening nhoening mentioned this pull request Aug 14, 2026
15 tasks
…ons-crud

Context:
- Picking up the review changes from #2290 and #2293, in which scheduling_cursor became cursor, "occurrences" became "runs", and the automations documentation moved to its own page.

Change:
- Updated this branch's update_automation service, which git merged without a conflict because the two sides touched different regions, but which still called get_initial_scheduling_cursor and assigned to automation.scheduling_cursor. Both would have failed at runtime.
- Kept the edit logic in the service, so cli/data_edit.py neither imports get_initial_cursor nor rebases the cursor itself.
- Kept both automation imports in cli/data_delete.py, one for deleting an automation and one for warning which automations use a sensor.
- Said "run" instead of "occurrence" in the update_automation docstring and the automations UI template.
- Regenerated the OpenAPI specs.

Signed-off-by: F.N. Claessen <felix@seita.nl>
Flix6x added a commit that referenced this pull request Sep 1, 2026
Context:
- The separate revision added for this index branched off 9f2b6e1d4a73, but so does c63896a97a8e on the branches stacked on top of this one. Merging this branch down therefore left two alembic heads, and `flexmeasures db upgrade` fails on multiple heads. That broke the Docker build job on #2293, #2294, #2297 and #2299, while this PR itself stayed green with its single head.
- Adding the index in its own revision was meant to spare a database that had already run 9f2b6e1d4a73. Breaking the upgrade on four stacked PRs is the greater harm, so that trade-off no longer holds.

Change:
- Folded the index into 9f2b6e1d4a73, which every branch in the stack shares, and dropped the separate revision. No branch gains a head.
- Anyone whose database already ran 9f2b6e1d4a73 will not have the index; recreate the database or add the index by hand.

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

Signed-off-by: F.N. Claessen <felix@seita.nl>
Flix6x added a commit that referenced this pull request Sep 1, 2026
* feat: add Automation data model

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

* feat: CLI commands to manage and run automations

- `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

* feat: record on forecasting jobs how they were created

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

* feat: API endpoints to list an asset's automations

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

* feat: UI page listing an asset's automations

/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

* test: cover automations CLI, API and UI

Part of #2288

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX

* docs: document automations

Part of #2288

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX

* docs: changelog entry for automations

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX

* fix: render cron descriptions in 24-hour format regardless of locale

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

* fix: address code review findings for automations

- 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

* fix: address code review findings for automations (remaining files)

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

* test: assert on the cron validation failure without pinning click's message 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

* data/schemas: restrict automations to five-field cron

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>

* data/schemas/tests: cover automation cron field count

Test valid five-field expressions and reject unsupported seconds, year, and alias formats.

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

* cli/jobs: retain automation guard after queueing failure

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>

* cli/tests: cover partial automation queue failure

Verify that retrying a failed partial queueing attempt does not create duplicate jobs.

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

* cli: normalize YAML forecasting option files

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>

* cli/tests: cover automation YAML option files

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>

* data/services: redact inaccessible automation provenance

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>

* api/v3_0/tests: cover automation provenance authorization

Verify inaccessible automation provenance is redacted while authorized callers still receive the full identity.

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

* ui/assets: distinguish automation load failures

Show a persistent API error instead of presenting failed automation requests as an empty list.

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

* ui/tests: cover automation load error state

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>

* utils/docs: preserve standalone asterisks in RST conversion

Avoid interpreting cron wildcard asterisks as RST italic markup when generating OpenAPI documentation.

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

* utils/tests: cover RST cron wildcard conversion

Verify cron wildcards remain unchanged while ordinary italic markup is still converted.

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

* docs/forecasting: clarify automation execution contract

Document five-field cron expressions, at-most-once queueing attempts, and the automations API endpoint.

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

* changelog: record automation API and runner contract

Record the automation endpoints, authorization-aware provenance, and five-field runner behavior.

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

* api/docs: show job creation provenance

Include the created_via field in the asset jobs OpenAPI example.

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

* test: keep forecast CLI stub compatible with job provenance

Add the trigger method required by the forecasting CLI to the regressor parsing test double.

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

* fix: require valid automation generators

Require every automation to reference a data generator and prevent deleting a data source while an automation still depends on it, matching the retention policy for belief and annotation sources.

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

* test: cover automation generator retention

Verify referenced generators cannot be deleted, generator references cannot be cleared, and automation API fixtures always use valid generators.

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

* fix: constrain forecast automation outputs

Allow forecast output only on the automation asset or its descendants and revalidate that relationship before every scheduled run, including explicit sensor-to-save targets.

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

* test: cover forecast automation output scope

Cover same-asset, child, grandchild, ancestor, and unrelated output targets, explicit sensor-to-save behavior, and runtime revalidation after an asset is moved.

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

* docs: explain forecast automation ownership rules

Document output-sensor scope, runtime relationship checks, and the requirement to retain a generator while its automation exists.

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

* fix: merge automation and main migration heads

Join the automation and main Alembic branches so installations have a single database upgrade target.

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

* data/models: let data generators report their input and output sensors

Context:
- Review of #2290 asked for a data generator property listing the sensors it
  reads from and writes to, so that automations can link to those sensors
  (and, later, check the creating user's permissions on them)

Change:
- Added input_sensors and output_sensors to DataGenerator (empty by default),
  implemented for Forecaster from its regressors and target sensor
- Added the same properties to Automation, resolved from its data generator
  configured with the automation's own parameters
- Added get_automations_feeding_sensor to look up automations by output sensor

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

* data/models/forecasting: only announce a pipeline run when actually running it

Context:
- 'flexmeasures jobs run-automations' logged 'Starting Train-Predict Pipeline'
  for every automation, while it only queues the cycles as jobs

Change:
- Log that line at debug level when running with as_job, where the workers
  running the cycles log their own start

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

* cli: default the automation recurrence to daily, and reject options that --source already determines

Context:
- Review of #2290: --cron should not be required, and --forecaster/--config are
  redundant with --source, whose data generator attributes already hold both
  (get_data_generator silently ignores them when a source is given)

Change:
- Default --cron to '0 0 * * *' (daily at midnight)
- Abort when --source is combined with --forecaster, --config or any of the
  forecaster configuration options, naming the conflicting options

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

* api/v3_0: report an automation's input and output sensors

Context:
- The automation details modal should link to the sensors an automation feeds

Change:
- Added input_sensors and output_sensors (id and name each) to
  GET /assets/<id>/automations/<automation_id>

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

* api/v3_0: add an endpoint for one data source

Context:
- The sensor page should be able to show the full record of a data source,
  including the attributes in which data generators store their configuration

Change:
- Added GET /sources/<id>, with the same access rules as listing sources
- Let _serialize_source optionally include the attributes and unset fields

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

* api/v3_0: regenerate the OpenAPI specs

Context:
- The specs are generated from the endpoint docstrings by a pre-commit hook

Change:
- Regenerated after adding the data source endpoint and the automation's
  input and output sensors

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

* ui: link an automation's details to its sensors, and make the listing sortable

Context:
- Review of #2290: the details modal should link to the sensors an automation
  feeds, with its data source pre-selected there, and the listing was not sortable

Change:
- Show the input and output sensors in the details modal, linking to
  /sensors/<id>?source=<generator id>
- Enabled ordering, sorting the rendered columns on separate values (the ISO
  timestamp, the activation status and the cron string), newest first

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

* ui: show a sensor's data source record and the automations feeding it

Context:
- Review of #2290: the sensor page should be able to show all details of a data
  source, and list the automations that write data to the sensor

Change:
- Added an info button next to the source selector, opening a modal with the
  full data source record
- Pre-select the source given in the source query parameter, so that links from
  an automation land on its own source
- List the automations feeding the sensor (those the user may read), linking to
  the automations page of their asset
- Added user_can_read to the UI's permission helpers

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

* tests: cover the automation and data source review follow-ups

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>

* docs: describe the automation and data source follow-ups

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>

* api/v3_0: regenerate the OpenAPI specs after merging

Context:
- The merge combined endpoint docstring changes from both sides

Change:
- Regenerated the specs

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

* cli: only reject configuration options that were actually given with --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>

* data/services: only consider automations that could feed a sensor

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>

* tests: follow the merged automation behaviour

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>

* cli: keep mypy happy about click 8 attributes

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>

* cli: keep the automation help focused on the automation

`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>

* data/models: count a source-filtered regressor as an input sensor

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>

* data/services: do not report no sensors when an automation's sensors 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>

* Feat automation timezones catchup (#2396)

* 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>

* fix(data/schemas): reject cron expressions without dates

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>

* fix(data/services): isolate invalid recurrences and stale claims

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>

* fix(api/v3_0): protect automation sensor details

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>

* test(cli): cover impossible recurrence input

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>

* test(data/services): cover resilient automation claims

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>

* test(api/v3_0): cover private automation dependencies

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>

* data/models: name an automation's cursor after what it points at

Context:
- Review of #2396 asked what the "scheduling cursor" is, how an automation is "watermarked" (watermarks do not update), and why the field is needed at all.
- "scheduling" also collides with FlexMeasures' scheduling machinery (the "scheduling" queue, StorageScheduler), which this field has nothing to do with.
- The feature is unreleased, so the column, the API field and the migration can still be renamed without a compatibility burden.

Change:
- Renamed Automation.scheduling_cursor to Automation.cursor, and get_initial_scheduling_cursor to get_initial_cursor. As a column on the automation table, it reads as an automation's cursor without further qualification, like the neighbouring timezone column.
- Replaced the "watermark" wording everywhere with what the field holds: the scheduled time of the most recent run the automation committed to, advanced just before queueing, and therefore not a record of success.
- Said "run" instead of "occurrence" throughout the automation code, matching the vocabulary already used for run time, run-automations and the automation-run guard key.
- Explained in the migration why both columns are added nullable and backfilled before NOT NULL, and why the backfilled cursor is one minute before the upgrade.
- Regenerated the OpenAPI specs.

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

* tests: follow the automation cursor rename

Context:
- Automation.scheduling_cursor became Automation.cursor, and automation "occurrences" became "runs".

Change:
- Updated the field name in the automation fixtures and assertions, and the UI assertion on the "Cursor (UTC)" heading.
- Renamed the coalescing and spring-forward test cases to speak of runs.

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

* docs: explain the automation cursor, and say "run" instead of "occurrence"

Context:
- Review of #2396 found "the cursor is a watermark", "migration watermark", "the cursor is committed" and "durable run records" unclear, and asked why the cursor is needed at all.
- The docs already spoke of a run time, run records and run-automations, so "occurrence" was a second word for the same thing.

Change:
- Introduced the cursor by the problem it solves: the runner is a stateless once-a-minute command, so it needs a durable record of how far each automation has got.
- Stated what it holds, that it advances before queueing (so it is not a record of success), and that keeping one moving timestamp instead of a record per run is what produces the catch-up and concurrency behaviour described below it.
- Replaced the "migration watermark" sentence with what an upgrade actually does to existing automations.
- Linked issue #2393 where the docs referred to "durable run records".
- Applied the suggested wording for the "add automation" command summary.

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

* docs/changelog: give the automation API changes their own version section

Context:
- Review of #2290 asked to move the automation API entries to a new v3.0-33 section, and noted that a revision to a CLI command introduced in the same version does not warrant its own entry.

Change:
- Moved the automation and data source entries from v3.0-32 to a new "v3.0-33 | September 1, 2026" section.
- Folded the timezone and cursor entry into the entry introducing the automation endpoints, applying the same reasoning as for the CLI changelog, and described the cursor in terms of the run it points at.
- Folded the --timezone and catch-up entry into the two CLI entries introducing the commands it revises.
- Folded the #2396 entry in the main changelog into the #2290 entry it refines, listing both PRs.

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

* docs/changelog: restore the v3.0-32 underline to full length

Context:
- This branch had shortened the underline of "v3.0-32 | August 11, 2026" from 26 to 24 characters, one short of the 25-character title, which makes docutils warn that the title underline is too short.

Change:
- Set the underline to exactly the title length.

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

* data/services: address review findings on the automations service

Context:
- Reviewing #2290 turned up three issues in how the automations service handles shared state and asset trees.

Change:
- run_automation now works on a copy of the data generator, like resolve_automation_sensors already did. The generator is cached on the data source, which several automations may share, so setting the job trigger on the shared instance would attribute jobs to the wrong automation as soon as anything runs concurrently.
- Moved the upward tree walk to asset_and_ancestor_ids in data/queries/generic_assets, and expressed asset_is_in_subtree in terms of it, so the two copies of that walk introduced by this branch became one.
- Added get_automations_involving_sensor, which considers every automation rather than only those on the sensor's asset and its ancestors, because a regressor may live anywhere in the tree.

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

* data/models: index the automation asset foreign key

Context:
- PostgreSQL does not index a foreign key by itself, and automations are looked up by asset on an asset's automations page and when finding the automations that feed a sensor.
- Reviewing #2290 also showed that a bool would be read as a sensor ID, as bool is a subclass of int.

Change:
- Added an index on automation.asset_id, in a new migration rather than in the migration that creates the table, so a database that already ran that one still gets the index.
- Excluded bools from the integer branch of DataGenerator._resolve_sensors.

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

* api/v3_0: work out an automation's sensors once when they cannot be resolved

Context:
- On the error path, the automation details endpoint called resolve_automation_sensors and then get_automation_sensors, which calls resolve_automation_sensors again and swallows the error, so a broken automation set up its data generator and loaded its parameters twice.

Change:
- Log the reason and fall back to empty sensor lists directly, which is what the second call amounted to.

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

* cli: warn which automations a sensor deletion would break

Context:
- An automation refers to its sensors by ID inside its parameters, which no foreign key protects. Deleting such a sensor left the automation looking healthy while failing on its next run, with the reason visible only in the runner's output.
- A data source is protected from this by a foreign key, so the sensors were the remaining gap.

Change:
- flexmeasures delete sensor now lists the automations that read from or write to each sensor before asking for confirmation. The deletion is still allowed, as the host may well intend it.

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

* tests: cover the sensor deletion warning, and stop depending on caplog

Context:
- test_invalid_cron_does_not_hide_other_due_automations failed whenever an earlier test in the session had built an app: creating one reconfigures logging and replaces the root handlers, after which pytest's caplog captures nothing. Reproduced with utils/tests/test_job_utils.py::test_app_queues_use_custom_global_and_queue_job_timeout running first.
- The condition is pre-existing and hits any test that reads caplog afterwards, including data/tests/test_utils.py::test_schema_mismatch_log_record_is_deduplicated on main, which already uses caplog.at_level. So at_level is not a workaround: the handler is gone, not merely filtered.
- The test's behavioural assertion passed throughout; only the log assertion failed.

Change:
- Assert on the logger itself rather than on caplog, which makes the test independent of what ran before it.
- Cover that deleting a sensor names the automations using it, including a regressor-only sensor, which get_automations_feeding_sensor does not find.

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

* docs/changelog: record the sensor deletion warning

Context:
- flexmeasures delete sensor now warns which automations use a sensor.

Change:
- Added a CLI changelog entry, as delete sensor is a pre-existing command rather than one introduced in this version.
- Folded the behaviour into the automations entry in the main changelog, which already covers this feature.

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

* docs: give automations their own page

Context:
- Review of #2290 found the automations section sitting under forecasting, although most of it will apply to scheduling and reporting automations too.
- The same review found the CLI example silent about what it automates, --forecaster and --config referred to before being introduced, the daylight-saving-time rules reading as a developer's note in the main body, and the pointer to issue #2393 reading as a todo.

Change:
- Moved the section to features/automations.rst, split into creating, running and viewing automations, and left a pointer in features/forecasting.rst. Wrote it in terms of automations in general, mentioning forecasts as today's only type.
- Made the example pass --type forecasts explicitly, which is the option the follow-up PRs use to distinguish schedules and reports.
- Introduced --forecaster and --config before the sentence that says --source makes them unnecessary.
- Moved the cursor and daylight-saving-time rules to an appendix, marked as bookkeeping you do not need in order to use automations.
- Dropped the sentence pointing at issue #2393, keeping the statement that a failed attempt is not retried, which is the part users need.

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

* data/migrations: index the automation asset FK without adding a revision

Context:
- The separate revision added for this index branched off 9f2b6e1d4a73, but so does c63896a97a8e on the branches stacked on top of this one. Merging this branch down therefore left two alembic heads, and `flexmeasures db upgrade` fails on multiple heads. That broke the Docker build job on #2293, #2294, #2297 and #2299, while this PR itself stayed green with its single head.
- Adding the index in its own revision was meant to spare a database that had already run 9f2b6e1d4a73. Breaking the upgrade on four stacked PRs is the greater harm, so that trade-off no longer holds.

Change:
- Folded the index into 9f2b6e1d4a73, which every branch in the stack shares, and dropped the separate revision. No branch gains a head.
- Anyone whose database already ran 9f2b6e1d4a73 will not have the index; recreate the database or add the index by hand.

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

---------

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Signed-off-by: F.N. Claessen <felix@seita.nl>
Signed-off-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Co-authored-by: Mohamed Belhsan Hmida <149331360+BelhsanHmida@users.noreply.github.com>
…into HEAD

Signed-off-by: F.N. Claessen <felix@seita.nl>
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.

CRUD for automations in the UI

2 participants