Skip to content

Automations - first roundtrip for forecasts - #2290

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

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

Conversation

@Flix6x

@Flix6x Flix6x commented Jul 11, 2026

Copy link
Copy Markdown
Member

Description

First roundtrip for automations (milestone 1 of #2288): recurring tasks defined per asset — for now, computing forecasts.

  • Data model: new Automation model (id, created_at, asset_id, type, name, cronstr, active, generator_id, parameters JSONB), with an __acl__ that lets account admins and consultants update/delete, and a migration. New dependencies: croniter (cron matching/validation) and cron-descriptor (natural-language recurrence descriptions).
  • CLI: flexmeasures add automation (active by default; validates parameters with the forecast parameter schema; stores the forecaster config on a data source), flexmeasures edit automation (name, cron string, activation status) and flexmeasures delete automation. All record to the asset's audit log.
  • Running automations: flexmeasures jobs run-automations queues forecasting jobs for all automations due this minute — to be run once per minute, e.g. via cron. A Redis-based guard prevents duplicate runs within the same minute.
  • Job provenance: data generators record how their queued jobs got created (CLI, API or automation) as job meta data; the status page's jobs table shows this in a new Created Via column.
  • API: GET /assets/<id>/automations (list, without generator/parameters) and GET /assets/<id>/automations/<automation_id> (details incl. parameters, generator info and job counts per status), documented in the OpenAPI specs.
  • UI: /assets/<id>/automations page with a tabbed view (Schedules and Reports tabs prepared but deactivated), listing Name, Created At (human-readable, ISO string in title tag), Active, Recurrence (human-readable, cron string in title tag), plus per-row job counts and a Details modal loaded asynchronously. Added to the breadcrumbs dropdown; links to the status page.

Look & feel

See the new Automations page under an asset's breadcrumbs dropdown.

How to test

flexmeasures db upgrade
flexmeasures add automation --asset <id> --name "Daily PV forecasts" --cron "0 6 * * *" --sensor <sensor-id>
flexmeasures jobs run-automations  # run once per minute via cron

Tests: pytest flexmeasures/cli/tests/test_automations.py flexmeasures/api/v3_0/tests/test_automations_api.py flexmeasures/ui/tests/test_asset_crud.py

Further Improvements

Per #2288: schedules and reports as automations, CRUD for automations in the UI (POST/PATCH/DELETE endpoints).

  • update pr description after fixes commits pushed

Closes #2288

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rbix8k1JfeUWNXEmHEZVpX

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>
@BelhsanHmida

Copy link
Copy Markdown
Contributor

I checked the current behavior and considered the trade-offs.

  1. Execution semantics

The current runner evaluates cron expressions in the global FLEXMEASURES_TIMEZONE. A skipped DST hour is missed, while a repeated hour can run twice. There is no downtime catch-up.

I recommend storing an IANA timezone per automation, defaulted at creation to FLEXMEASURES_TIMEZONE. For forecasts, I would run once per local wall-clock occurrence and coalesce missed occurrences into one catch-up run rather than replaying every stale forecast after downtime. Other automation types may later need configurable skip, run-once, or replay policies.

This requires persistent scheduling state, so I suggest handling it in a follow-up issue.

  1. Failure semantics

Retrying immediately is unsafe because an exception may occur after some jobs have already been queued. Until runs and jobs have durable occurrence IDs and idempotency protection, the current at-most-once guard is the safer milestone-one behavior.

I suggest a follow-up issue for durable automation-run records, partial-failure states and safe retries.

  1. Cross-asset targets

The current CLI only warns about a target sensor on another asset. I recommend rejecting output sensors that do not belong to the automation asset or one of its descendants. Arbitrary cross-organisation output targets should not be allowed.

Regressors are inputs and could remain cross-asset when readable, but output sensors need the appropriate write boundary. Run statistics should primarily belong to the automation asset; individual jobs can remain visible under their target sensor subject to normal permissions.

I think this validation belongs in the current PR because it protects a permission boundary.

  1. Generator lifecycle

I verified that beliefs and annotations reference their DataSource with restrictive foreign keys: PostgreSQL rejects deleting a DataSource while those records reference it.

Automations currently differ: generator_id uses ON DELETE SET NULL, which can leave an active automation without a generator and make every future run fail.

I recommend matching the existing belief/annotation policy: make generator_id required and reject DataSource deletion while an automation references it. The user should delete or repoint the automation first. I think this also belongs in the current PR.

Proposed scope: address target authorization and generator deletion integrity here; create follow-ups for timezone/catch-up and safe retry semantics. Does that split look right?

@Flix6x

Flix6x commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Proposed scope: address target authorization and generator deletion integrity here; create follow-ups for timezone/catch-up and safe retry semantics. Does that split look right?

Yes, that looks right. For 1 and 2, please open the issues and cross-reference them. For 3 and 4, remarks below.

3. Cross-asset targets Cross-account targets

The current CLI only warns about a target sensor on another asset. I recommend rejecting output sensors that do not belong to the automation asset or one of its descendants. Arbitrary cross-organisation output targets should not be allowed.

Regressors are inputs and could remain cross-asset when readable, but output sensors need the appropriate write boundary. Run statistics should primarily belong to the automation asset; individual jobs can remain visible under their target sensor subject to normal permissions.

I think this validation belongs in the current PR because it protects a permission boundary.

I seems reasonable to mandate that automations can only register beliefs on sensors that belong to the asset that the automation is registered on, or to an asset further down the tree (e.g. to a child or grandchild asset).

But note that tree structure and ownership are separate concepts! And it is ownership that is my primary concern. Tree structure is topology only, it grants nothing.

Here's an example to reason about:

flowchart TD
    classDef ownerless fill:#495057,stroke:#212529,color:#fff,stroke-dasharray: 5 3
    classDef foundation fill:#2d6a4f,stroke:#14342b,color:#fff
    classDef participant fill:#7f4f24,stroke:#3f2712,color:#fff
    classDef sensor fill:#f8f9fa,stroke:#adb5bd,color:#212529,stroke-dasharray: 3 3

    subgraph PUBLIC["🌍 No owner — readable by every logged-in user"]
        BZ["Bidding zone<br/><i>NL</i>"]
        BZ -.-> S1(["day-ahead price"]):::sensor
        BZ -.-> S2(["imbalance price"]):::sensor
    end

    subgraph FND["🤝 Account: Energy Community Foundation"]
        GRID["Grid connection point<br/><i>collective connection</i>"]
        PV["Shared solar park"]
        BAT["Community battery"]
        GRID --> PV
        GRID --> BAT
        GRID -.-> S3(["net exchange"]):::sensor
        BAT -.-> S4(["SoC / power"]):::sensor
    end

    subgraph P1["🏠 Account: Participant A"]
        HA["Household A"]
        EVA["EV charger"]
        HPA["Heat pump"]
        HA --> EVA
        HA --> HPA
        EVA -.-> S5(["power"]):::sensor
    end

    subgraph P2["🏠 Account: Participant B"]
        HB["Household B"]
        PVB["Rooftop PV"]
    end

    BZ ==>|"child asset,<br/>now owned"| GRID
    GRID ==>|"child asset,<br/>other account"| HA
    GRID ==>|"child asset,<br/>other account"| HB

    class BZ ownerless
    class GRID,PV,BAT foundation
    class HA,EVA,HPA,HB,PVB participant
Loading

Here are two permissions scenarios:

Community is a consultancy to its participants

flowchart TD
    classDef acct fill:#3d348b,stroke:#1d1846,color:#fff
    classDef asset fill:#f8f9fa,stroke:#adb5bd,color:#212529
    classDef ownerless fill:#495057,stroke:#212529,color:#fff,stroke-dasharray: 5 3

    subgraph ACCOUNTS["Account graph — where authority lives"]
        FA["Foundation account<br/><i>role: consultant</i>"]:::acct
        AA["Participant A account"]:::acct
        AB["Participant B account"]:::acct
        AA -->|"consultancy_account_id"| FA
        AB -->|"consultancy_account_id"| FA
    end

    subgraph ASSETS["Asset tree — where topology lives"]
        BZ2["Bidding zone<br/><i>no owner</i>"]:::ownerless
        GRID2["Community assets"]:::asset
        HA2["Household A"]:::asset
        HB2["Household B"]:::asset
        BZ2 --> GRID2
        GRID2 --> HA2
        GRID2 --> HB2
    end

    FA ==>|"CRUD (owner)"| GRID2
    FA ==>|"read + update + create-children<br/>via consultancy"| HA2
    FA ==>|"read + update + create-children<br/>via consultancy"| HB2
    AA ==>|"CRUD (owner)"| HA2
    AB ==>|"CRUD (owner)"| HB2
    AA -.->|"❌ no read — not the owner,<br/>not a consultant"| GRID2
    AA -.->|"❌ no read"| HB2
    AA -->|"read (ownerless)"| BZ2
    AB -->|"read (ownerless)"| BZ2
    FA -->|"read (ownerless)"| BZ2
Loading

Fully sovereign participants

flowchart TD
    classDef acct fill:#3d348b,stroke:#1d1846,color:#fff
    classDef asset fill:#f8f9fa,stroke:#adb5bd,color:#212529
    classDef ownerless fill:#495057,stroke:#212529,color:#fff,stroke-dasharray: 5 3

    subgraph ACCOUNTS["Account graph — no consultancy edges at all"]
        FA["Foundation account"]:::acct
        AA["Participant A account"]:::acct
        AB["Participant B account"]:::acct
    end

    subgraph ASSETS["Asset tree — topology only, grants nothing"]
        BZ2["Bidding zone<br/><i>no owner</i>"]:::ownerless
        GRID2["Community assets"]:::asset
        HA2["Household A"]:::asset
        HB2["Household B"]:::asset
        BZ2 --> GRID2
        GRID2 -->|"cross-account edge —<br/>site admin / CLI only"| HA2
        GRID2 -->|"cross-account edge —<br/>site admin / CLI only"| HB2
    end

    FA ==>|"CRUD (owner)"| GRID2
    AA ==>|"CRUD (owner)"| HA2
    AB ==>|"CRUD (owner)"| HB2

    FA -.->|"❌ no read, despite being the tree parent"| HA2
    FA -.->|"❌ no read"| HB2
    AA -.->|"❌ no read"| GRID2
    AA -.->|"❌ no read"| HB2

    FA -->|"read"| BZ2
    AA -->|"read"| BZ2
    AB -->|"read"| BZ2
Loading

4. Generator lifecycle

I recommend matching the existing belief/annotation policy: make generator_id required and reject DataSource deletion while an automation references it. The user should delete or repoint the automation first. I think this also belongs in the current PR.

I agree.

Bring the forecast automation branch up to date with current main while preserving both branches' CLI, API, documentation, dependency lock, and generated OpenAPI changes.

Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
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>
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>
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>
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>
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>
Join the automation and main Alembic branches so installations have a single database upgrade target.

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

Copy link
Copy Markdown
Contributor

Thanks for the detailed response, I pushed the changes addressing the agreed current-PR scope.

Follow-up issues

I created separate follow-up issues for:

Until safe retries have durable occurrence IDs and idempotency protection, the runner retains its at-most-once guard after a failed attempt.

Output target scope

The pushed changes now:

  • allow output sensors on the automation asset, its children or its grandchildren;
  • reject ancestor and unrelated output sensors;
  • validate the effective sensor-to-save, when supplied;
  • repeat the validation immediately before every unattended execution; and
  • prevent an automation from running if its target asset has since been moved outside the subtree.

One ownership question remains. Manual testing confirmed that an automation owned by organisation A can currently target a descendant sensor owned by unrelated organisation B, even when A has no consultancy relationship with B. The topology check passes because the target is a descendant, but topology itself grants no permissions.

For unattended execution, should authority come from:

  1. the automation asset’s owning organisation and its current consultancy relationships; or
  2. the individual creator’s identity and that user’s current permissions?

I recommend the first option. The automation belongs to the asset/organisation and should not depend on whether its original creator remains employed or keeps the same role. Current consultancy relationships should be checked again on every run, so revoking access immediately prevents subsequent writes.

Does that match the intended permissions model? Once confirmed, I will add the corresponding service-level permission check and cross-organisation regression test.

Generator lifecycle

The pushed changes also make generator handling consistent with beliefs and annotations:

  • generator_id is required;
  • PostgreSQL rejects deleting a referenced DataSource;
  • the automation must first be deleted or repointed; and
  • the generator can be deleted after its referencing automations are removed.

Flix6x added 15 commits August 5, 2026 17:59
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>
…unning 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>
…hat --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>
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>
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>
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>
… 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>
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>
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

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

2 participants