Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5d4dd2e
Initial plan
Copilot Apr 13, 2026
ec03f05
Move data ingestion logic to job queue (ingestion queue)
Copilot Apr 13, 2026
ea634db
Fix code review comments: use for loop and move imports to module level
Copilot Apr 13, 2026
3a98da8
Add warning when ingestion queue is not configured
Copilot Apr 16, 2026
eb0f886
docs: add changelog entry and upgrade notice for ingestion queue warn…
Copilot Apr 16, 2026
b1681e9
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity Apr 16, 2026
5d5b921
chore: remvoe wrong log entry
joshuaunity Apr 17, 2026
be2d019
docs: add ingestion queue changelog entry and upgrade notice to v0.33.0
Copilot Apr 17, 2026
bd53637
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity Apr 20, 2026
0bc6580
chore: remove duplicate changelog entry
joshuaunity Apr 20, 2026
842eae6
fix: update job queue command to include ingestion
joshuaunity Apr 20, 2026
c811cf0
docs: add ingestion queue to worker examples in forecasting_schedulin…
Copilot Apr 20, 2026
7d275a9
refactor: update data ingestion logic to enqueue forecasting jobs
joshuaunity Apr 21, 2026
cb133ee
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity Apr 22, 2026
acfb144
refactor: eliminate duplicated save/enqueue logic in save_and_enqueue…
Copilot Apr 22, 2026
aff5b0b
refactor: enhance comparison logic for unchanged beliefs to support j…
joshuaunity Apr 22, 2026
974cb78
feat: implement serialization and deserialization for ingestion data,…
joshuaunity Apr 24, 2026
6380541
refactor: improve cross-session comparison logic by using source_id f…
joshuaunity Apr 29, 2026
99cc90a
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity May 4, 2026
da441ef
Update API version to 0.32.0
joshuaunity May 4, 2026
513a81e
fix: pass keep_only_most_recent_belief=True to resample_events in fil…
Copilot May 4, 2026
0dd844f
fix: improve test docstring and remove debug print from new upload test
Copilot May 4, 2026
588f0ea
fix: add db.engine.dispose() to ingestion job function to fix forked …
Copilot May 4, 2026
3c8d776
fix: address code review comments - clearer test comment and simpler …
Copilot May 4, 2026
719ac8f
fix: enhance timezone handling in data ingestion serialization and de…
joshuaunity May 4, 2026
3b41915
revert: undo db.engine.dispose() ingestion fix and related changes
Copilot May 4, 2026
7df88bf
fix: update test_fetch_sensor_stats expected values to account for ne…
Copilot May 4, 2026
59e32a6
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity May 5, 2026
27654d5
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity May 6, 2026
706caf3
fix: remove unused event_start variable in time_series.py (flake8 F841)
Copilot May 6, 2026
3402684
fix: filter bdf_db by event_start in _drop_unchanged_beliefs_compared…
Copilot May 7, 2026
2e052f6
chore: modify chnagelog
joshuaunity May 12, 2026
25c63c5
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
joshuaunity May 12, 2026
06f86c9
feat: implement job status polling for background data processing
joshuaunity May 12, 2026
3c4d717
feat: enhance job status polling with in-progress notifications
joshuaunity May 13, 2026
38c81d0
move all work to the jobs, including resampling. Just a thin acceptan…
nhoening May 13, 2026
37c2823
also log that data has been saved successfully, the logs are not clea…
nhoening May 13, 2026
be2596e
move all work to the jobs, including resampling. Just a thin acceptan…
nhoening May 13, 2026
8efd483
Improve poling and Toasting
nhoening May 13, 2026
a3fd6ab
Use full sensor data range in delete dialog
nhoening May 13, 2026
08cc8e6
smaller self-review changes
nhoening May 13, 2026
b9ac887
make constants out of our various success messages
nhoening May 13, 2026
14f0b85
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
nhoening May 13, 2026
e89be4e
Merge remote-tracking branch 'origin/main' into copilot/move-post-dat…
Flix6x May 14, 2026
e8edfe0
docs: suggest revised command to run a worker assigned to our 3 stand…
Flix6x May 14, 2026
fed6da1
feat: let the docker compose worker also handle ingestion
Flix6x May 14, 2026
2a80e46
fix: if users can create children on the sensor, they should be allow…
Flix6x May 14, 2026
3002a19
refactor: utils work on any AuthModelMixin, not just on Asset
Flix6x May 14, 2026
7ea92ad
refactor: move auth utils to module that is not asset specific
Flix6x May 14, 2026
c626a19
Merge branch 'copilot/move-post-data-logic-to-job-queue' of github.co…
nhoening May 14, 2026
fef8dac
remove Toast about job acceptance
nhoening May 14, 2026
dced659
in uploaded data, we (keep) allow(ing) multiple beliefs per event
nhoening May 14, 2026
3816994
add comment on default ingestion limit
nhoening May 14, 2026
a387996
Merge branch 'main' into copilot/move-post-data-logic-to-job-queue
nhoening May 16, 2026
1bd5ac8
feat: adding TTL to ingestion jobs
joshuaunity May 19, 2026
9328aaf
docs: add comment explaining the TTL choice for ingestion
Flix6x May 20, 2026
4d55596
fix: put back filters for the sake of UX (speed)
Flix6x May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ FlexMeasures Changelog
v0.33.0 | May XX, 2026
============================

.. note:: It is recommended to add a worker to the ``ingestion`` queue (or configure existing workers to handle it, as well), so that sensor data posted via the API is processed asynchronously [see `PR #2101 <https://www.github.com/FlexMeasures/flexmeasures/pull/2101>`_].

New features
-------------
* Improve UX after deleting a child asset through the UI [see `PR #2119 <https://www.github.com/FlexMeasures/flexmeasures/pull/2119>`_]
* Improve source filtering in the sensor data GET endpoint by exposing the documented query parameters in Swagger and allowing filtering by the account linked to data sources [see `PR #2083 <https://www.github.com/FlexMeasures/flexmeasures/pull/2083>`_]

Infrastructure / Support
----------------------
* Move sensor data ingestion to a job queue for improved performance when POSTing large amounts of data to the sensor data API [see `PR #2101 <https://www.github.com/FlexMeasures/flexmeasures/pull/2101>`_]
* Upgraded dependencies [see `PR #2114 <https://www.github.com/FlexMeasures/flexmeasures/pull/2114>`_]

Bugfixes
Expand Down Expand Up @@ -43,6 +46,7 @@ New features
* Separate the ``StorageScheduler``'s tie-breaking preference for a full :abbr:`SoC (state of charge)` from its reported energy costs [see `PR #2023 <https://www.github.com/FlexMeasures/flexmeasures/pull/2023>`_ and `PR #2108 <https://www.github.com/FlexMeasures/flexmeasures/pull/2108>`_]
* Improve asset graph hover interaction with a vertical ruler across subcharts, while keeping hover dots for easier visual tracking [see `PR #2079 <https://www.github.com/FlexMeasures/flexmeasures/pull/2079>`_]
* Improve asset audit log messages for JSON field edits (especially ``sensors_to_show`` and nested flex-config values) [see `PR #2055 <https://www.github.com/FlexMeasures/flexmeasures/pull/2055>`_]
*

Infrastructure / Support
----------------------
Expand Down
2 changes: 1 addition & 1 deletion documentation/host/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ Then, start workers in a console (or some other method to keep a long-running pr

.. code-block:: bash

$ flexmeasures jobs run-worker --queue "scheduling|forecasting"
$ flexmeasures jobs run-worker --queue "scheduling|forecasting|ingestion"


You can go to `http://localhost:5000/tasks/` and see the state of job queues and find individual jobs (and investigate why they failed, for instance).
Expand Down
7 changes: 6 additions & 1 deletion documentation/host/queues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,30 @@ Here is how to run one worker for each kind of job (in separate terminals):

.. code-block:: bash

$ flexmeasures jobs run-worker --name our-only-worker --queue forecasting|scheduling
$ flexmeasures jobs run-worker --name our-only-worker --queue forecasting|scheduling|ingestion

Running multiple workers in parallel might be a great idea.

.. code-block:: bash

$ flexmeasures jobs run-worker --name forecaster --queue forecasting
$ flexmeasures jobs run-worker --name scheduler --queue scheduling
$ flexmeasures jobs run-worker --name ingester --queue ingestion

You can also clear the job queues:

.. code-block:: bash

$ flexmeasures jobs clear-queue --queue forecasting
$ flexmeasures jobs clear-queue --queue scheduling
$ flexmeasures jobs clear-queue --queue ingestion


When the main FlexMeasures process runs (e.g. by ``flexmeasures run``\ ), the queues of forecasting and scheduling jobs can be visited at ``http://localhost:5000/tasks/forecasting`` and ``http://localhost:5000/tasks/schedules``\ , respectively (by admins).

.. note::
The ``ingestion`` queue is used for sensor data posted via the API. If the queue is not configured, or if no worker is connected to it, data is processed synchronously (in the web process) with a warning logged. Running a dedicated ingestion worker is recommended in production to keep API responses fast when large amounts of data are posted.



Inspect the queue and jobs
Expand Down
2 changes: 2 additions & 0 deletions documentation/tut/forecasting_scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Start to run one worker for each kind of job (in a separate terminal):

$ flexmeasures jobs run-worker --queue forecasting
$ flexmeasures jobs run-worker --queue scheduling
$ flexmeasures jobs run-worker --queue ingestion


You can also clear the job queues:
Expand All @@ -36,6 +37,7 @@ You can also clear the job queues:

$ flexmeasures jobs clear-queue --queue forecasting
$ flexmeasures jobs clear-queue --queue scheduling
$ flexmeasures jobs clear-queue --queue ingestion


When the main FlexMeasures process runs (e.g. by ``flexmeasures run``), the queues of forecasting and scheduling jobs can be visited at ``http://localhost:5000/tasks/forecasting`` and ``http://localhost:5000/tasks/schedules``, respectively (by admins).
Expand Down
24 changes: 23 additions & 1 deletion flexmeasures/api/common/utils/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from werkzeug.exceptions import Forbidden, Unauthorized
from numpy import array
from psycopg2.errors import UniqueViolation
from rq import Worker
from rq.job import Job, JobStatus, NoSuchJobError
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError

from flexmeasures.data import db
from flexmeasures.data.models.user import Account
from flexmeasures.data.services.data_ingestion import add_beliefs_to_database
from flexmeasures.data.utils import save_to_db
from flexmeasures.auth.policy import check_access
from flexmeasures.api.common.responses import (
Expand Down Expand Up @@ -138,7 +140,27 @@ def save_and_enqueue(
forecasting_jobs: list[Job] | None = None,
save_changed_beliefs_only: bool = True,
) -> ResponseTuple:
# Attempt to save
ingestion_queue = current_app.queues.get("ingestion")
if ingestion_queue is None:
current_app.logger.warning(
"No ingestion queue configured. Processing sensor data directly."
)
else:
workers = Worker.all(queue=ingestion_queue)
if workers:
ingestion_queue.enqueue(
add_beliefs_to_database,
data,
forecasting_jobs=forecasting_jobs,
save_changed_beliefs_only=save_changed_beliefs_only,
)
return request_processed()
else:
current_app.logger.warning(
"No workers connected to the ingestion queue. Processing sensor data directly."
)

# Attempt to save directly (fallback when no ingestion queue or workers are available)
status = save_to_db(data, save_changed_beliefs_only=save_changed_beliefs_only)
Comment thread
Flix6x marked this conversation as resolved.
Outdated
db.session.commit()

Expand Down
1 change: 1 addition & 0 deletions flexmeasures/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def create( # noqa C901
app.queues = dict(
forecasting=Queue(connection=redis_conn, name="forecasting"),
scheduling=Queue(connection=redis_conn, name="scheduling"),
ingestion=Queue(connection=redis_conn, name="ingestion"),
# reporting=Queue(connection=redis_conn, name="reporting"),
# labelling=Queue(connection=redis_conn, name="labelling"),
# alerting=Queue(connection=redis_conn, name="alerting"),
Expand Down
4 changes: 2 additions & 2 deletions flexmeasures/cli/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def run_job(job_id: str):
"--queue",
default=None,
required=True,
help="State which queue(s) to work on (using '|' as separator), e.g. 'forecasting', 'scheduling' or 'forecasting|scheduling'.",
help="State which queue(s) to work on (using '|' as separator), e.g. 'forecasting', 'scheduling', 'ingestion' or 'forecasting|scheduling'.",
)
@click.option(
"--name",
Expand All @@ -302,7 +302,7 @@ def run_job(job_id: str):
)
def run_worker(queue: str, name: str | None):
"""
Start a worker process for forecasting and/or scheduling jobs.
Start a worker process for forecasting, scheduling and/or ingestion jobs.

We use the app context to find out which redis queues to use.
"""
Expand Down
42 changes: 42 additions & 0 deletions flexmeasures/data/services/data_ingestion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Logic around data ingestion (jobs)
"""

from __future__ import annotations

from flask import current_app
from rq.job import Job
import timely_beliefs as tb

from flexmeasures.data import db
from flexmeasures.data.utils import save_to_db


def add_beliefs_to_database(
Comment thread
joshuaunity marked this conversation as resolved.
Outdated
data: tb.BeliefsDataFrame | list[tb.BeliefsDataFrame],
forecasting_jobs: list[Job] | None = None,
save_changed_beliefs_only: bool = True,
) -> str:
"""Save sensor data to the database and optionally enqueue forecasting jobs.

This function is intended to be called as an RQ job by an ingestion queue worker,
but can also be called directly (e.g. as a fallback when no workers are available).

:param data: BeliefsDataFrame (or list thereof) to be saved.
:param forecasting_jobs: Optional list of forecasting Jobs to enqueue after saving.
:param save_changed_beliefs_only: If True, skip saving beliefs whose value hasn't changed.
:returns: Status string, one of:
- 'success'
- 'success_with_unchanged_beliefs_skipped'
- 'success_but_nothing_new'
"""
status = save_to_db(data, save_changed_beliefs_only=save_changed_beliefs_only)
db.session.commit()

# Only enqueue forecasting jobs upon successfully saving new data
if status[:7] == "success" and status != "success_but_nothing_new":
if forecasting_jobs is not None:
for job in forecasting_jobs:
current_app.queues["forecasting"].enqueue_job(job)

return status
Loading