Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ services:
if [ -f /usr/var/flexmeasures-instance/requirements.txt ]; then
pip install --no-cache-dir -r /usr/var/flexmeasures-instance/requirements.txt
fi
flexmeasures jobs run-worker --name flexmeasures-worker --queue forecasting\|scheduling
flexmeasures jobs run-worker --name flexmeasures-worker --queue forecasting\|scheduling\|ingestion
test-db:
image: postgres
expose:
Expand Down
11 changes: 10 additions & 1 deletion documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ FlexMeasures Changelog
v0.33.0 | May XX, 2026
============================

.. note:: It is recommended to assign 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>`_].
For instance, using:

.. code-block:: bash

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

.. warning:: We are deprecating ``FLEXMEASURES_MONITORING_MAIL_RECIPIENTS`` in favor of ``FLEXMEASURES_DEFAULT_MONITORING_MAIL_RECIPIENTS``.

New features
Expand All @@ -21,6 +29,7 @@ New features

Infrastructure / Support
----------------------
* Move sensor data ingestion to a job queue for improved performance when POSTing large amounts of data to the sensor data API, returning a ``202 Accepted`` response with a job status URL when queued [see `PR #2101 <https://www.github.com/FlexMeasures/flexmeasures/pull/2101>`_]
* Remove legacy rolling viewpoint forecasting code and utilities after migrating to fixed-point forecasting [see `PR #2082 <https://www.github.com/FlexMeasures/flexmeasures/pull/2082>`_]
* Upgraded dependencies [see `PR #2114 <https://www.github.com/FlexMeasures/flexmeasures/pull/2114>`_, `PR #2148 <https://www.github.com/FlexMeasures/flexmeasures/pull/2148>`_, `PR #2161 <https://www.github.com/FlexMeasures/flexmeasures/pull/2161>`_ and `PR #2177 <https://www.github.com/FlexMeasures/flexmeasures/pull/2177>`_]
* Run ``flexmeasures jobs run-worker`` with RQ's embedded scheduler on by default so jobs created with ``enqueue_in`` are promoted from the scheduled registry when due; pass ``--without-scheduler`` to disable [see `PR #2112 <https://www.github.com/FlexMeasures/flexmeasures/pull/2112>`_]
Expand All @@ -32,6 +41,7 @@ Bugfixes
-----------
* Fix forecasting regressor filtering to use only regressor beliefs known at the forecast ``belief_time`` [see `PR #2134 <https://www.github.com/FlexMeasures/flexmeasures/pull/2134>`_]
* Check read permissions for sensors referenced in forecasting and scheduling config payloads, and return a clearer 403 error when a referenced sensor is not readable [see `PR #2096 <https://www.github.com/FlexMeasures/flexmeasures/pull/2096>`_ and `PR #2125 <https://www.github.com/FlexMeasures/flexmeasures/pull/2125>`_]
* Clean up stale sensor references from ``flex-config`` and ``sensors_to_show`` when deleting a sensor, using JSONB queries to find affected assets before pruning those references [see `PR #2106 <https://www.github.com/FlexMeasures/flexmeasures/pull/2106>`_]
* Standardize resolution formatting across API endpoints for consistent response payloads [see `PR #2152 <https://www.github.com/FlexMeasures/flexmeasures/pull/2152>`_]
* Make the auth check for CLI commands work with ``flask``, too, instead of only with the ``flexmeasures`` alias [see `PR #2169 <https://www.github.com/FlexMeasures/flexmeasures/pull/2169>`_]

Expand Down Expand Up @@ -93,7 +103,6 @@ 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>`_]
* Clean up stale sensor references from ``flex-config`` and ``sensors_to_show`` when deleting a sensor, using JSONB queries to find affected assets before pruning those references [see `PR #2106 <https://www.github.com/FlexMeasures/flexmeasures/pull/2106>`_]

Infrastructure / Support
----------------------
Expand Down
8 changes: 8 additions & 0 deletions documentation/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,14 @@ Set a negative value to persist forever.

Default: ``3600``

FLEXMEASURES_MAX_SENSOR_DATA_INGESTION_BYTES
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Maximum request body size for sensor data posted to the sensor data API, both for JSON data and file uploads.
Set to ``None`` to disable this FlexMeasures-specific limit.

Default: ``3 * 1024 * 1024``

.. _datasource_config:

FLEXMEASURES_DEFAULT_DATASOURCE
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. When ingestion is queued, the API returns ``202 Accepted`` with a job status URL.



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
3 changes: 3 additions & 0 deletions flexmeasures/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
from flexmeasures.data.models.user import User
from flexmeasures.api.common.utils.args_parsing import (
validation_error_handler,
request_entity_too_large_handler,
)
from flexmeasures.api.common.responses import invalid_sender
from flexmeasures.data.schemas.utils import FMValidationError
from werkzeug.exceptions import RequestEntityTooLarge
from flexmeasures.api.v3_0.users import AuthRequestSchema

# The api blueprint. It is registered with the Flask app (see app.py)
Expand Down Expand Up @@ -150,6 +152,7 @@ def register_at(app: Flask):

# handle API specific errors
app.register_error_handler(FMValidationError, validation_error_handler)
app.register_error_handler(RequestEntityTooLarge, request_entity_too_large_handler)
app.register_error_handler(IntegrityError, catch_timed_belief_replacements)
app.unauthorized_handler_api = invalid_sender

Expand Down
23 changes: 22 additions & 1 deletion flexmeasures/api/common/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import inflect
from functools import wraps

from flask import url_for

from flexmeasures.auth.error_handling import FORBIDDEN_MSG, FORBIDDEN_STATUS_CODE

p = inflect.engine()
Expand Down Expand Up @@ -62,7 +64,7 @@ def already_received_and_successfully_processed(message: str) -> ResponseTuple:


@BaseMessage(
"Some of the data represents a replacement, which is reserved for customized servers. If you are hosting FlexMeasures, you can enable replacements by setting FLEXMEASURES_ALLOW_DATA_OVERWRITE=True in the configuration settings. Alternatively, update the prior in your request."
"Some of the data represents a replacement: existing values would be changed for the same sensor, source, event timestamp and belief time. This is reserved for customized servers. If you are hosting FlexMeasures, you can enable replacements by setting FLEXMEASURES_ALLOW_DATA_OVERWRITE=True in the configuration settings. Alternatively, submit the data with a different prior, or check whether the same file was already uploaded with different values."
)
def invalid_replacement(message: str) -> ResponseTuple:
return (
Expand Down Expand Up @@ -378,6 +380,25 @@ def request_processed(message: str) -> ResponseTuple:
return dict(status="PROCESSED", message=message), 200


def request_accepted_for_processing(
job_id: str,
message: str = "Request has been accepted for processing.",
) -> ResponseTuple:
return (
dict(
status="ACCEPTED",
message=message,
job_monitor_url=url_for("JobAPI:get_job_status", uuid=job_id),
job_id=job_id,
),
202,
)


def request_too_large(message: str) -> ResponseTuple:
return dict(result="Rejected", status="PAYLOAD_TOO_LARGE", message=message), 413


def pluralize(usef_role_name: str) -> str:
"""Adding a trailing 's' works well for USEF roles."""
return "%ss" % usef_role_name
33 changes: 30 additions & 3 deletions flexmeasures/api/common/schemas/sensor_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from flexmeasures.data import ma
from flexmeasures.data.models.time_series import Sensor
from flexmeasures.data.models.user import User
from flexmeasures.api.common.schemas.sensors import (
SensorEntityAddressField,
SensorIdField,
Expand Down Expand Up @@ -328,6 +329,10 @@ class PostSensorDataSchema(SensorDataDescriptionSchema):
This schema includes data (values) and still describes it.
"""

def __init__(self, *args, source_user: User | None = None, **kwargs):
super().__init__(*args, **kwargs)
self.source_user = source_user

values = PolyField(
deserialization_schema_selector=select_schema_to_ensure_list_of_floats,
serialization_schema_selector=select_schema_to_ensure_list_of_floats,
Expand Down Expand Up @@ -469,12 +474,11 @@ def possibly_upsample_values(data):
)
return data

@staticmethod
def load_bdf(sensor_data: dict) -> BeliefsDataFrame:
def load_bdf(self, sensor_data: dict) -> BeliefsDataFrame:
"""
Turn the de-serialized and validated data into a BeliefsDataFrame.
"""
source = get_or_create_source(current_user)
source = get_or_create_source(self.source_user or current_user)
num_values = len(sensor_data["values"])
event_resolution = sensor_data["duration"] / num_values
start = sensor_data["start"]
Expand Down Expand Up @@ -512,6 +516,29 @@ def load_bdf(sensor_data: dict) -> BeliefsDataFrame:
)


class PostSensorDataRequestSchema(PostSensorDataSchema):
"""Validate posted sensor data without building a BeliefsDataFrame."""

@post_load()
def post_load_sequence(self, data: dict, **kwargs) -> dict:
sensor_data = {
"values": data["values"],
"start": datetime_isoformat(data["start"]),
"duration": duration_isoformat(data["duration"]),
"unit": data["unit"],
}
if "prior" in data:
sensor_data["prior"] = datetime_isoformat(data["prior"])
elif "horizon" in data:
sensor_data["horizon"] = duration_isoformat(data["horizon"])
else:
# Preserve request-time semantics when processing happens later in a worker.
sensor_data["prior"] = datetime_isoformat(server_now())
if "type" in data:
sensor_data["type"] = data["type"]
return dict(sensor=data["sensor"], sensor_data=sensor_data)


class GetSensorDataSchemaEntityAddress(GetSensorDataSchema):
"""DEPRECATED, only here to support deprecated endpoints"""

Expand Down
106 changes: 95 additions & 11 deletions flexmeasures/api/common/utils/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,30 @@
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.audit_log import AssetAuditLog
from flexmeasures.data.models.user import Account
from flexmeasures.data.services.data_ingestion import (
add_beliefs_to_db_and_enqueue_forecasting_jobs,
)
from flexmeasures.data.models.generic_assets import GenericAsset
from flexmeasures.data.models.time_series import Sensor
from flexmeasures.data.utils import save_to_db
from flexmeasures.data.utils import (
SAVE_TO_DB_SUCCESS,
SAVE_TO_DB_SUCCESS_BUT_NOTHING_NEW,
SAVE_TO_DB_SUCCESS_WITH_UNCHANGED_BELIEFS_SKIPPED,
)
from flexmeasures.auth.policy import check_access
from flexmeasures.api.common.responses import (
invalid_replacement,
ResponseTuple,
request_processed,
request_accepted_for_processing,
already_received_and_successfully_processed,
)
from flexmeasures.data.schemas.generic_assets import GenericAssetSchema as AssetSchema
Expand Down Expand Up @@ -146,20 +155,95 @@ def save_and_enqueue(
forecasting_jobs: list[Job] | None = None,
save_changed_beliefs_only: bool = True,
) -> ResponseTuple:
# Attempt to save
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":
enqueue_forecasting_jobs(forecasting_jobs)
status = add_beliefs_to_db_and_enqueue_forecasting_jobs(
data,
forecasting_jobs=forecasting_jobs,
save_changed_beliefs_only=save_changed_beliefs_only,
)

# Pick a response
if status == "success":
if status == SAVE_TO_DB_SUCCESS:
return request_processed()
elif status in (
SAVE_TO_DB_SUCCESS_WITH_UNCHANGED_BELIEFS_SKIPPED,
SAVE_TO_DB_SUCCESS_BUT_NOTHING_NEW,
):
return already_received_and_successfully_processed()
return invalid_replacement()


def process_sensor_data_ingestion(
sensor_id: int,
user_id: int,
sensor_data: dict | None = None,
uploaded_files: list[dict] | None = None,
upload_data: dict | None = None,
forecasting_jobs: list[Job] | None = None,
save_changed_beliefs_only: bool = True,
) -> ResponseTuple:
"""Process sensor data ingestion asynchronously when possible.

If an ingestion queue with connected workers is available, enqueue a background
job and return ``202 Accepted``. Otherwise, process the data synchronously and
return the resulting ingestion response.
"""
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:
forecasting_job_ids = (
[job.id for job in forecasting_jobs]
if forecasting_jobs is not None
else None
)
job = ingestion_queue.enqueue(
add_beliefs_to_db_and_enqueue_forecasting_jobs,
sensor_id=sensor_id,
user_id=user_id,
sensor_data=sensor_data,
uploaded_files=uploaded_files,
upload_data=upload_data,
forecasting_job_ids=forecasting_job_ids,
save_changed_beliefs_only=save_changed_beliefs_only,
meta={"sensor_id": sensor_id},
ttl=current_app.config.get(
"FLEXMEASURES_JOB_TTL", timedelta(-1)
).total_seconds(),
# No need to keep ingestion results for the FLEXMEASURES_PLANNING_TTL
result_ttl=int(
current_app.config.get(
"FLEXMEASURES_JOB_TTL", timedelta(-1)
).total_seconds()
),
)
return request_accepted_for_processing(
job.id,
"Sensor data has been accepted for processing.",
)
else:
current_app.logger.warning(
"No workers connected to the ingestion queue. Processing sensor data directly."
)

status = add_beliefs_to_db_and_enqueue_forecasting_jobs(
sensor_id=sensor_id,
user_id=user_id,
sensor_data=sensor_data,
uploaded_files=uploaded_files,
upload_data=upload_data,
forecasting_jobs=forecasting_jobs,
save_changed_beliefs_only=save_changed_beliefs_only,
)

if status == SAVE_TO_DB_SUCCESS:
return request_processed()
elif status in (
"success_with_unchanged_beliefs_skipped",
"success_but_nothing_new",
SAVE_TO_DB_SUCCESS_WITH_UNCHANGED_BELIEFS_SKIPPED,
SAVE_TO_DB_SUCCESS_BUT_NOTHING_NEW,
):
return already_received_and_successfully_processed()
return invalid_replacement()
Expand Down
Loading
Loading