From 8998ce970562454e2da35fc91c8cefaa9def0abd Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 4 Jun 2026 21:43:57 +0100 Subject: [PATCH 01/13] cli: create toy accounts with kW-scale units Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures/cli/data_add.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flexmeasures/cli/data_add.py b/flexmeasures/cli/data_add.py index 112b21dd4c..fd36967d20 100755 --- a/flexmeasures/cli/data_add.py +++ b/flexmeasures/cli/data_add.py @@ -1730,7 +1730,7 @@ def add_toy_account(kind: str, name: str): Sensor, name="day-ahead prices", generic_asset=nl_zone, - unit="EUR/MWh", + unit="EUR/kWh", timezone="Europe/Amsterdam", event_resolution=timedelta(minutes=60), knowledge_horizon=( @@ -1750,7 +1750,7 @@ def create_asset_with_one_sensor( asset_name: str, asset_type: str, sensor_name: str, - unit: str = "MW", + unit: str = "kW", parent_asset_id: int | None = None, flex_context: dict | None = None, flex_model: dict | None = None, @@ -1797,7 +1797,7 @@ def create_asset_with_one_sensor( latitude=location[0], longitude=location[1], flex_context={ - "site-power-capacity": "500 kVA", + "site-power-capacity": "500 kW", "consumption-price": {"sensor": day_ahead_sensor.id}, }, ) @@ -1811,7 +1811,7 @@ def create_asset_with_one_sensor( "discharging", parent_asset_id=building_asset.id, flex_model={ - "power-capacity": "500 kVA", + "power-capacity": "500 kW", "roundtrip-efficiency": "90%", "soc-max": "450 kWh", }, @@ -1918,7 +1918,7 @@ def create_asset_with_one_sensor( generic_asset=building_asset, timezone="Europe/Amsterdam", event_resolution="P1Y", - unit="MW", + unit="kW", ) db.session.commit() @@ -1934,7 +1934,7 @@ def create_asset_with_one_sensor( belief = TimedBelief( event_start=start_year, belief_time=server_now(), - event_value=0.5, + event_value=500, source=db.session.get(DataSource, 1), sensor=grid_connection_capacity, ) From 74fafb4b7608074b68bd3e5313a80c92002958cb Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 4 Jun 2026 21:44:30 +0100 Subject: [PATCH 02/13] cli/tests: cover kW-scale toy account data Signed-off-by: Mohamed Belhsan Hmida --- .../cli/tests/test_data_add_fresh_db.py | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/flexmeasures/cli/tests/test_data_add_fresh_db.py b/flexmeasures/cli/tests/test_data_add_fresh_db.py index cfd50741b9..f55aa1009b 100644 --- a/flexmeasures/cli/tests/test_data_add_fresh_db.py +++ b/flexmeasures/cli/tests/test_data_add_fresh_db.py @@ -374,6 +374,60 @@ def test_add_account( assert result.exit_code == 1 +def test_add_toy_account_battery_uses_kw_scale_units(app, fresh_db): + from flexmeasures.cli.data_add import add_toy_account + + result = app.test_cli_runner().invoke(add_toy_account, ["--kind", "battery"]) + + check_command_ran_without_error(result) + + toy_account = fresh_db.session.execute( + select(Account).filter_by(name="Toy Account") + ).scalar_one() + building = fresh_db.session.execute( + select(Asset).filter_by(name="toy-building", owner=toy_account) + ).scalar_one() + battery = fresh_db.session.execute( + select(Asset).filter_by(name="toy-battery", owner=toy_account) + ).scalar_one() + solar = fresh_db.session.execute( + select(Asset).filter_by(name="toy-solar", owner=toy_account) + ).scalar_one() + day_ahead_sensor = fresh_db.session.execute( + select(Sensor).filter_by(name="day-ahead prices") + ).scalar_one() + + assert day_ahead_sensor.unit == "EUR/kWh" + assert building.flex_context["site-power-capacity"] == "500 kW" + assert battery.flex_model["power-capacity"] == "500 kW" + assert battery.flex_model["soc-max"] == "450 kWh" + assert battery.sensors[0].unit == "kW" + assert solar.sensors[0].unit == "kW" + + +def test_add_toy_account_reporter_uses_kw_scale_units(app, fresh_db): + from flexmeasures.cli.data_add import add_toy_account + + result = app.test_cli_runner().invoke(add_toy_account, ["--kind", "reporter"]) + + check_command_ran_without_error(result) + + day_ahead_sensor = fresh_db.session.execute( + select(Sensor).filter_by(name="day-ahead prices") + ).scalar_one() + grid_connection_capacity = fresh_db.session.execute( + select(Sensor).filter_by(name="grid connection capacity") + ).scalar_one() + headroom = fresh_db.session.execute( + select(Sensor).filter_by(name="headroom") + ).scalar_one() + + assert day_ahead_sensor.unit == "EUR/kWh" + assert grid_connection_capacity.unit == "kW" + assert headroom.unit == "kW" + assert grid_connection_capacity.search_beliefs().values.flatten().tolist() == [500] + + @pytest.mark.parametrize("storage_power_capacity", ["sensor", "quantity", None]) @pytest.mark.parametrize("storage_efficiency", ["sensor", "quantity", None]) def test_add_storage_schedule( @@ -462,7 +516,12 @@ def test_add_storage_schedule( result = runner.invoke(add_schedule, cli_input) check_command_ran_without_error(result) - assert len(power_sensor.search_beliefs()) == 48 + schedule = power_sensor.search_beliefs() + max_power = schedule.event_value.abs().max() + assert len(schedule) == 48 + assert power_sensor.unit == "kW" + assert max_power > 1 + assert max_power <= 700 def test_add_storage_schedule_uses_state_of_charge_sensor_for_soc_at_start( From 360bac53b7ede728ee01a2e1f0612134f4edd6b9 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 4 Jun 2026 21:45:04 +0100 Subject: [PATCH 03/13] ci: use kW-scale toy tutorial inputs Signed-off-by: Mohamed Belhsan Hmida --- .github/workflows/build.yml | 2 +- .github/workflows/generate-dummy-price.sh | 48 +++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b97ca4545..4f9480db55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,4 +70,4 @@ jobs: run: | docker exec --env-file .env fm-container flexmeasures add schedule --sensor 5 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H --flex-context '{"consumption-price": {"sensor": 1}}' \ - --flex-model "{\"duration\": \"PT4H\", \"process-type\": \"BREAKABLE\", \"power\": 0.2, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}" + --flex-model "{\"duration\": \"PT4H\", \"process-type\": \"BREAKABLE\", \"power\": 200, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}" diff --git a/.github/workflows/generate-dummy-price.sh b/.github/workflows/generate-dummy-price.sh index 06ee670454..6b674649ab 100755 --- a/.github/workflows/generate-dummy-price.sh +++ b/.github/workflows/generate-dummy-price.sh @@ -6,27 +6,27 @@ set -x TOMORROW=$(date --date="next day" '+%Y-%m-%d') echo "Hour,Price -${TOMORROW}T00:00:00,10 -${TOMORROW}T01:00:00,11 -${TOMORROW}T02:00:00,12 -${TOMORROW}T03:00:00,15 -${TOMORROW}T04:00:00,18 -${TOMORROW}T05:00:00,17 -${TOMORROW}T06:00:00,10.5 -${TOMORROW}T07:00:00,9 -${TOMORROW}T08:00:00,9.5 -${TOMORROW}T09:00:00,9 -${TOMORROW}T10:00:00,8.5 -${TOMORROW}T11:00:00,10 -${TOMORROW}T12:00:00,8 -${TOMORROW}T13:00:00,5 -${TOMORROW}T14:00:00,4 -${TOMORROW}T15:00:00,4 -${TOMORROW}T16:00:00,5.5 -${TOMORROW}T17:00:00,8 -${TOMORROW}T18:00:00,12 -${TOMORROW}T19:00:00,13 -${TOMORROW}T20:00:00,14 -${TOMORROW}T21:00:00,12.5 -${TOMORROW}T22:00:00,10 -${TOMORROW}T23:00:00,7" > prices-tomorrow.csv +${TOMORROW}T00:00:00,0.010 +${TOMORROW}T01:00:00,0.011 +${TOMORROW}T02:00:00,0.012 +${TOMORROW}T03:00:00,0.015 +${TOMORROW}T04:00:00,0.018 +${TOMORROW}T05:00:00,0.017 +${TOMORROW}T06:00:00,0.0105 +${TOMORROW}T07:00:00,0.009 +${TOMORROW}T08:00:00,0.0095 +${TOMORROW}T09:00:00,0.009 +${TOMORROW}T10:00:00,0.0085 +${TOMORROW}T11:00:00,0.010 +${TOMORROW}T12:00:00,0.008 +${TOMORROW}T13:00:00,0.005 +${TOMORROW}T14:00:00,0.004 +${TOMORROW}T15:00:00,0.004 +${TOMORROW}T16:00:00,0.0055 +${TOMORROW}T17:00:00,0.008 +${TOMORROW}T18:00:00,0.012 +${TOMORROW}T19:00:00,0.013 +${TOMORROW}T20:00:00,0.014 +${TOMORROW}T21:00:00,0.0125 +${TOMORROW}T22:00:00,0.010 +${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv From f0be0ca04b85893daa280bdbac21e561aed51949 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 4 Jun 2026 21:45:41 +0100 Subject: [PATCH 04/13] docs/scripts: run toy tutorials with kW-scale data Signed-off-by: Mohamed Belhsan Hmida --- .../tut/scripts/run-tutorial-in-docker.sh | 48 +++++++++---------- .../tut/scripts/run-tutorial2-in-docker.sh | 48 +++++++++---------- .../tut/scripts/run-tutorial3-in-docker.sh | 8 ++-- .../tut/scripts/run-tutorial4-in-docker.sh | 6 +-- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/documentation/tut/scripts/run-tutorial-in-docker.sh b/documentation/tut/scripts/run-tutorial-in-docker.sh index 624a91adc7..1b8d47eb78 100755 --- a/documentation/tut/scripts/run-tutorial-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial-in-docker.sh @@ -9,30 +9,30 @@ echo "-----------------------------------------------------------------" echo "[TUTORIAL-RUNNER] loading prices..." TOMORROW=$(date --date="next day" '+%Y-%m-%d') echo "Hour,Price -${TOMORROW}T00:00:00,10 -${TOMORROW}T01:00:00,11 -${TOMORROW}T02:00:00,12 -${TOMORROW}T03:00:00,15 -${TOMORROW}T04:00:00,18 -${TOMORROW}T05:00:00,17 -${TOMORROW}T06:00:00,10.5 -${TOMORROW}T07:00:00,9 -${TOMORROW}T08:00:00,9.5 -${TOMORROW}T09:00:00,9 -${TOMORROW}T10:00:00,8.5 -${TOMORROW}T11:00:00,10 -${TOMORROW}T12:00:00,8 -${TOMORROW}T13:00:00,5 -${TOMORROW}T14:00:00,4 -${TOMORROW}T15:00:00,4 -${TOMORROW}T16:00:00,5.5 -${TOMORROW}T17:00:00,8 -${TOMORROW}T18:00:00,12 -${TOMORROW}T19:00:00,13 -${TOMORROW}T20:00:00,14 -${TOMORROW}T21:00:00,12.5 -${TOMORROW}T22:00:00,10 -${TOMORROW}T23:00:00,7" > prices-tomorrow.csv +${TOMORROW}T00:00:00,0.010 +${TOMORROW}T01:00:00,0.011 +${TOMORROW}T02:00:00,0.012 +${TOMORROW}T03:00:00,0.015 +${TOMORROW}T04:00:00,0.018 +${TOMORROW}T05:00:00,0.017 +${TOMORROW}T06:00:00,0.0105 +${TOMORROW}T07:00:00,0.009 +${TOMORROW}T08:00:00,0.0095 +${TOMORROW}T09:00:00,0.009 +${TOMORROW}T10:00:00,0.0085 +${TOMORROW}T11:00:00,0.010 +${TOMORROW}T12:00:00,0.008 +${TOMORROW}T13:00:00,0.005 +${TOMORROW}T14:00:00,0.004 +${TOMORROW}T15:00:00,0.004 +${TOMORROW}T16:00:00,0.0055 +${TOMORROW}T17:00:00,0.008 +${TOMORROW}T18:00:00,0.012 +${TOMORROW}T19:00:00,0.013 +${TOMORROW}T20:00:00,0.014 +${TOMORROW}T21:00:00,0.0125 +${TOMORROW}T22:00:00,0.010 +${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv docker cp prices-tomorrow.csv $CONTAINER_NAME:/app diff --git a/documentation/tut/scripts/run-tutorial2-in-docker.sh b/documentation/tut/scripts/run-tutorial2-in-docker.sh index 006c0b0e9e..cb4412c216 100755 --- a/documentation/tut/scripts/run-tutorial2-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial2-in-docker.sh @@ -11,30 +11,30 @@ echo "[TUTORIAL-RUNNER] loading solar production data..." TOMORROW=$(date --date="next day" '+%Y-%m-%d') echo "Hour,Production -${TOMORROW}T00:00:00,0.0 -${TOMORROW}T01:00:00,0.0 -${TOMORROW}T02:00:00,0.0 -${TOMORROW}T03:00:00,0.0 -${TOMORROW}T04:00:00,0.01 -${TOMORROW}T05:00:00,0.03 -${TOMORROW}T06:00:00,0.06 -${TOMORROW}T07:00:00,0.1 -${TOMORROW}T08:00:00,0.14 -${TOMORROW}T09:00:00,0.17 -${TOMORROW}T10:00:00,0.19 -${TOMORROW}T11:00:00,0.21 -${TOMORROW}T12:00:00,0.22 -${TOMORROW}T13:00:00,0.21 -${TOMORROW}T14:00:00,0.19 -${TOMORROW}T15:00:00,0.17 -${TOMORROW}T16:00:00,0.14 -${TOMORROW}T17:00:00,0.1 -${TOMORROW}T18:00:00,0.06 -${TOMORROW}T19:00:00,0.03 -${TOMORROW}T20:00:00,0.01 -${TOMORROW}T21:00:00,0.0 -${TOMORROW}T22:00:00,0.0 -${TOMORROW}T23:00:00,0.0" > solar-tomorrow.csv +${TOMORROW}T00:00:00,0 +${TOMORROW}T01:00:00,0 +${TOMORROW}T02:00:00,0 +${TOMORROW}T03:00:00,0 +${TOMORROW}T04:00:00,10 +${TOMORROW}T05:00:00,30 +${TOMORROW}T06:00:00,60 +${TOMORROW}T07:00:00,100 +${TOMORROW}T08:00:00,140 +${TOMORROW}T09:00:00,170 +${TOMORROW}T10:00:00,190 +${TOMORROW}T11:00:00,210 +${TOMORROW}T12:00:00,220 +${TOMORROW}T13:00:00,210 +${TOMORROW}T14:00:00,190 +${TOMORROW}T15:00:00,170 +${TOMORROW}T16:00:00,140 +${TOMORROW}T17:00:00,100 +${TOMORROW}T18:00:00,60 +${TOMORROW}T19:00:00,30 +${TOMORROW}T20:00:00,10 +${TOMORROW}T21:00:00,0 +${TOMORROW}T22:00:00,0 +${TOMORROW}T23:00:00,0" > solar-tomorrow.csv docker cp solar-tomorrow.csv $CONTAINER_NAME:/app/ diff --git a/documentation/tut/scripts/run-tutorial3-in-docker.sh b/documentation/tut/scripts/run-tutorial3-in-docker.sh index 5d18e11002..85d856f56a 100755 --- a/documentation/tut/scripts/run-tutorial3-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial3-in-docker.sh @@ -14,12 +14,12 @@ echo "[TUTORIAL-RUNNER] Computing schedule for PV curtailment (using artificial echo '''{ "consumption-price": [ - {"start": "'${TOMORROW}'T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "'${TOMORROW}'T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "'${TOMORROW}'T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "'${TOMORROW}'T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "'${TOMORROW}'T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "'${TOMORROW}'T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "'${TOMORROW}'T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "'${TOMORROW}'T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] }''' > tutorial3-priceprofile-flex-context.json docker cp tutorial3-priceprofile-flex-context.json $CONTAINER_NAME:/app/ diff --git a/documentation/tut/scripts/run-tutorial4-in-docker.sh b/documentation/tut/scripts/run-tutorial4-in-docker.sh index 9d9058e709..d9f118e564 100755 --- a/documentation/tut/scripts/run-tutorial4-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial4-in-docker.sh @@ -15,16 +15,16 @@ echo "[TUTORIAL-RUNNER] Creating three process schedules ..." docker exec -it $CONTAINER_NAME flexmeasures add schedule --sensor 4 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{"consumption-price": {"sensor": 1}}' \ - --flex-model '{"duration": "PT4H", "process-type": "INFLEXIBLE", "power": 0.2, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' + --flex-model '{"duration": "PT4H", "process-type": "INFLEXIBLE", "power": 200, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' docker exec -it $CONTAINER_NAME flexmeasures add schedule --sensor 5 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{"consumption-price": {"sensor": 1}}' \ - --flex-model '{"duration": "PT4H", "process-type": "BREAKABLE", "power": 0.2, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' + --flex-model '{"duration": "PT4H", "process-type": "BREAKABLE", "power": 200, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' docker exec -it $CONTAINER_NAME flexmeasures add schedule --sensor 6 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{"consumption-price": {"sensor": 1}}' \ - --flex-model '{"duration": "PT4H", "process-type": "SHIFTABLE", "power": 0.2, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' + --flex-model '{"duration": "PT4H", "process-type": "SHIFTABLE", "power": 200, "time-restrictions": [{"start": "'"${TOMORROW}"'T15:00:00+02:00", "duration": "PT1H"}]}' echo "Now visit http://localhost:5000/assets/6/graphs to see all three schedules." From 9fae10c622bf19fa6e38b47bab7cd586e95ddfd7 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 4 Jun 2026 21:47:37 +0100 Subject: [PATCH 05/13] docs/tutorial: show battery scheduling in kW units Signed-off-by: Mohamed Belhsan Hmida --- documentation/dev/docker-compose.rst | 6 +- documentation/tut/toy-example-expanded.rst | 52 +++++++------- .../tut/toy-example-from-scratch.rst | 6 +- documentation/tut/toy-example-setup.rst | 67 +++++++++---------- 4 files changed, 65 insertions(+), 66 deletions(-) diff --git a/documentation/dev/docker-compose.rst b/documentation/dev/docker-compose.rst index 759e33ec19..dac8cf9328 100644 --- a/documentation/dev/docker-compose.rst +++ b/documentation/dev/docker-compose.rst @@ -123,7 +123,7 @@ The charging/discharging schedule should be there: .. code-block:: bash ┌────────────────────────────────────────────────────────────┐ - │ ▐ ▐▀▀▌ ▛▀▀│ 0.5MW + │ ▐ ▐▀▀▌ ▛▀▀│ 500kW │ ▞▌ ▌ ▌ ▌ │ │ ▌▌ ▌ ▐ ▗▘ │ │ ▌▌ ▌ ▐ ▐ │ @@ -131,7 +131,7 @@ The charging/discharging schedule should be there: │ ▐ ▐ ▐ ▝▖ ▞ │ │ ▌ ▐ ▐ ▌ ▌ │ │ ▐ ▝▖ ▌ ▌ ▌ │ - │▀▘───▀▀▀▀▖─────▌────▀▀▀▀▀▀▀▀▀▌─────▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘───│ 0.0MW + │▀▘───▀▀▀▀▖─────▌────▀▀▀▀▀▀▀▀▀▌─────▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▘───│ 0kW │ ▌ ▐ ▚ ▌ │ │ ▌ ▞ ▐ ▗▘ │ │ ▌ ▌ ▐ ▞ │ @@ -139,7 +139,7 @@ The charging/discharging schedule should be there: │ ▐ ▐ ▌ ▗▘ │ │ ▐ ▌ ▌ ▐ │ │ ▝▖ ▌ ▌ ▞ │ - │ ▙▄▟ ▐▄▄▌ │ -0.5MW + │ ▙▄▟ ▐▄▄▌ │ -500kW └────────────────────────────────────────────────────────────┘ 10 20 30 40 ██ discharging diff --git a/documentation/tut/toy-example-expanded.rst b/documentation/tut/toy-example-expanded.rst index 3922ad9a3e..ade5c3fff5 100644 --- a/documentation/tut/toy-example-expanded.rst +++ b/documentation/tut/toy-example-expanded.rst @@ -6,7 +6,7 @@ Toy example II: Adding solar production, and a limit on the grid connection ============================================================================ -So far we haven't taken into account any other devices that consume or produce electricity. The battery was free to use all available capacity (which was 500 kVA, both its own maximum charge/discharge rate, and the maximum grid capacity). +So far we haven't taken into account any other devices that consume or produce electricity. The battery was free to use all available capacity (which was 500 kW, both its own maximum charge/discharge rate, and the maximum grid capacity). What if other devices will be using some of that capacity? Our schedules need to reflect that, so we stay within given limits. @@ -24,36 +24,36 @@ How does it work? Adding PV production forecasts ------------------------------ -First, we'll create a new CSV file with solar forecasts (MW, see the setup for sensor 3 in part I of this tutorial) for tomorrow. +First, we'll create a new CSV file with solar forecasts (kW, see the setup for sensor 3 in part I of this tutorial) for tomorrow. .. code-block:: bash $ TOMORROW=$(date --date="next day" '+%Y-%m-%d') $ echo "Hour,Price - $ ${TOMORROW}T00:00:00,0.0 - $ ${TOMORROW}T01:00:00,0.0 - $ ${TOMORROW}T02:00:00,0.0 - $ ${TOMORROW}T03:00:00,0.0 - $ ${TOMORROW}T04:00:00,0.01 - $ ${TOMORROW}T05:00:00,0.03 - $ ${TOMORROW}T06:00:00,0.06 - $ ${TOMORROW}T07:00:00,0.1 - $ ${TOMORROW}T08:00:00,0.14 - $ ${TOMORROW}T09:00:00,0.17 - $ ${TOMORROW}T10:00:00,0.19 - $ ${TOMORROW}T11:00:00,0.21 - $ ${TOMORROW}T12:00:00,0.22 - $ ${TOMORROW}T13:00:00,0.21 - $ ${TOMORROW}T14:00:00,0.19 - $ ${TOMORROW}T15:00:00,0.17 - $ ${TOMORROW}T16:00:00,0.14 - $ ${TOMORROW}T17:00:00,0.1 - $ ${TOMORROW}T18:00:00,0.06 - $ ${TOMORROW}T19:00:00,0.03 - $ ${TOMORROW}T20:00:00,0.01 - $ ${TOMORROW}T21:00:00,0.0 - $ ${TOMORROW}T22:00:00,0.0 - $ ${TOMORROW}T23:00:00,0.0" > solar-tomorrow.csv + $ ${TOMORROW}T00:00:00,0 + $ ${TOMORROW}T01:00:00,0 + $ ${TOMORROW}T02:00:00,0 + $ ${TOMORROW}T03:00:00,0 + $ ${TOMORROW}T04:00:00,10 + $ ${TOMORROW}T05:00:00,30 + $ ${TOMORROW}T06:00:00,60 + $ ${TOMORROW}T07:00:00,100 + $ ${TOMORROW}T08:00:00,140 + $ ${TOMORROW}T09:00:00,170 + $ ${TOMORROW}T10:00:00,190 + $ ${TOMORROW}T11:00:00,210 + $ ${TOMORROW}T12:00:00,220 + $ ${TOMORROW}T13:00:00,210 + $ ${TOMORROW}T14:00:00,190 + $ ${TOMORROW}T15:00:00,170 + $ ${TOMORROW}T16:00:00,140 + $ ${TOMORROW}T17:00:00,100 + $ ${TOMORROW}T18:00:00,60 + $ ${TOMORROW}T19:00:00,30 + $ ${TOMORROW}T20:00:00,10 + $ ${TOMORROW}T21:00:00,0 + $ ${TOMORROW}T22:00:00,0 + $ ${TOMORROW}T23:00:00,0" > solar-tomorrow.csv Then, we read in the created CSV file as beliefs data. This time, different to above, we want to use a new data source (not the user) ― it represents whoever is making these solar production forecasts. diff --git a/documentation/tut/toy-example-from-scratch.rst b/documentation/tut/toy-example-from-scratch.rst index e4a9d40d04..bb254f088a 100644 --- a/documentation/tut/toy-example-from-scratch.rst +++ b/documentation/tut/toy-example-from-scratch.rst @@ -139,7 +139,7 @@ Great. Let's see what we made: Data spans 12 hours and starts at 2025-11-29 07:00:00+01:00. The time resolution (x-axis) is 15 minutes. ┌────────────────────────────────────────────────────────────┐ - │ ▛▀▜ ▞▀▀▌ ▐▀▀▚ │ 0.5MW + │ ▛▀▜ ▞▀▀▌ ▐▀▀▚ │ 500kW │ ▌ ▌ ▌ ▌ ▐ ▐ │ │ ▗▘ ▌ ▌ ▌ ▐ ▐ │ │ ▐ ▌ ▌ ▐ ▌ ▐ │ @@ -147,7 +147,7 @@ Great. Let's see what we made: │▌ ▐ ▐ ▐ ▐ ▌ ▌│ │▐ ▌ ▐ ▐ ▌ ▐ ▌│ │ ▌ ▌ ▌ ▐ ▌ ▐ ▐│ - │─▚▄▄▌────▀▙▄▄▄▖────▐────▀▚▄▄▄▄▄▄▄▄▖─────▗▄▄▄▄▄▄▄▄▄▄▄▄▄▟────▝│ 0.0MW + │─▚▄▄▌────▀▙▄▄▄▖────▐────▀▚▄▄▄▄▄▄▄▄▖─────▗▄▄▄▄▄▄▄▄▄▄▄▄▄▟────▝│ 0kW │ ▌ ▞ ▐ ▌ │ │ ▚ ▌ ▐ ▗▘ │ │ ▐ ▌ ▐ ▞ │ @@ -155,7 +155,7 @@ Great. Let's see what we made: │ ▝▖ ▐ ▌ ▗▘ │ │ ▌ ▞ ▌ ▐ │ │ ▌ ▌ ▚ ▞ │ - │ ▙▄▄▘ ▐▄▄▌ │ -0.5MW + │ ▙▄▄▘ ▐▄▄▌ │ -500kW └────────────────────────────────────────────────────────────┘ 06:00 09:00 12:00 15:00 ██ discharging (toy-battery) diff --git a/documentation/tut/toy-example-setup.rst b/documentation/tut/toy-example-setup.rst index ddcee09284..82a6964e66 100644 --- a/documentation/tut/toy-example-setup.rst +++ b/documentation/tut/toy-example-setup.rst @@ -213,7 +213,7 @@ If you want, you can inspect what you created in the CLI (we'll also show the UI Flex-Context Flex-Model -------------------------------- ------------ - site-power-capacity: 500 kVA + site-power-capacity: 500 kW consumption-price: {'sensor': 1} ==================================== @@ -230,7 +230,7 @@ If you want, you can inspect what you created in the CLI (we'll also show the UI You can see that this building asset has some meta information about how FlexMeasures needs to schedule: - Within :ref:`flex_context`, we noted where to find the relevant optimization signal for electricity consumption (Sensor 1, which stores day-ahead prices). -- Also, the building has a grid connection capacity of 500 kVA, meaning that the total power flowing into or out of the building cannot exceed this value. +- Also, the building has a grid connection capacity of 500 kW, meaning that the total power flowing into or out of the building cannot exceed this value. Now let's look at the battery asset, as well: @@ -251,7 +251,7 @@ Now let's look at the battery asset, as well: Flex-Context Flex-Model -------------- ------------------------- - power-capacity: 500 kVA + power-capacity: 500 kW roundtrip-efficiency: 90% soc-max: 450 kWh @@ -265,14 +265,14 @@ Now let's look at the battery asset, as well: ID Name Unit Resolution Timezone Attributes ---- ----------- ------ ------------ ---------------- ------------ - 2 discharging MW 15 minutes Europe/Amsterdam + 2 discharging kW 15 minutes Europe/Amsterdam Yes, that is quite a large battery :) You can also see that the asset has some meta information about its scheduling. -- Within :ref:`flex_model`, we noted that the battery's power capacity is the same as the building's grid connection capacity (500 kVA), meaning the battery can charge or discharge at full power without overloading the connection, but no other devices can (we will come back to this limitation). +- Within :ref:`flex_model`, we noted that the battery's power capacity is the same as the building's grid connection capacity (500 kW), meaning the battery can charge or discharge at full power without overloading the connection, but no other devices can (we will come back to this limitation). - Also noted is the battery's roundtrip efficiency (90%) and maximum state of charge (450 kWh). .. note:: Obviously, you can use the ``flexmeasures`` command to create your own, custom account and assets. See :ref:`cli`. And to create, edit or read asset data via the API, see :ref:`v3_0`. @@ -313,36 +313,36 @@ And on the flex-model of the battery can be seen on its properties page (and is Add some price data --------------------------------------- -Now to add price data. First, we'll create the CSV file with prices (EUR/MWh, see the setup for sensor 1 above) for tomorrow. +Now to add price data. First, we'll create the CSV file with prices (EUR/kWh, see the setup for sensor 1 above) for tomorrow. .. code-block:: bash $ TOMORROW=$(date --date="next day" '+%Y-%m-%d') $ echo "Hour,Price - $ ${TOMORROW}T00:00:00,10 - $ ${TOMORROW}T01:00:00,11 - $ ${TOMORROW}T02:00:00,12 - $ ${TOMORROW}T03:00:00,15 - $ ${TOMORROW}T04:00:00,18 - $ ${TOMORROW}T05:00:00,17 - $ ${TOMORROW}T06:00:00,10.5 - $ ${TOMORROW}T07:00:00,9 - $ ${TOMORROW}T08:00:00,9.5 - $ ${TOMORROW}T09:00:00,9 - $ ${TOMORROW}T10:00:00,8.5 - $ ${TOMORROW}T11:00:00,10 - $ ${TOMORROW}T12:00:00,8 - $ ${TOMORROW}T13:00:00,5 - $ ${TOMORROW}T14:00:00,4 - $ ${TOMORROW}T15:00:00,4 - $ ${TOMORROW}T16:00:00,5.5 - $ ${TOMORROW}T17:00:00,8 - $ ${TOMORROW}T18:00:00,12 - $ ${TOMORROW}T19:00:00,13 - $ ${TOMORROW}T20:00:00,14 - $ ${TOMORROW}T21:00:00,12.5 - $ ${TOMORROW}T22:00:00,10 - $ ${TOMORROW}T23:00:00,7" > prices-tomorrow.csv + $ ${TOMORROW}T00:00:00,0.010 + $ ${TOMORROW}T01:00:00,0.011 + $ ${TOMORROW}T02:00:00,0.012 + $ ${TOMORROW}T03:00:00,0.015 + $ ${TOMORROW}T04:00:00,0.018 + $ ${TOMORROW}T05:00:00,0.017 + $ ${TOMORROW}T06:00:00,0.0105 + $ ${TOMORROW}T07:00:00,0.009 + $ ${TOMORROW}T08:00:00,0.0095 + $ ${TOMORROW}T09:00:00,0.009 + $ ${TOMORROW}T10:00:00,0.0085 + $ ${TOMORROW}T11:00:00,0.010 + $ ${TOMORROW}T12:00:00,0.008 + $ ${TOMORROW}T13:00:00,0.005 + $ ${TOMORROW}T14:00:00,0.004 + $ ${TOMORROW}T15:00:00,0.004 + $ ${TOMORROW}T16:00:00,0.0055 + $ ${TOMORROW}T17:00:00,0.008 + $ ${TOMORROW}T18:00:00,0.012 + $ ${TOMORROW}T19:00:00,0.013 + $ ${TOMORROW}T20:00:00,0.014 + $ ${TOMORROW}T21:00:00,0.0125 + $ ${TOMORROW}T22:00:00,0.010 + $ ${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv This is time series data, in FlexMeasures we call *"beliefs"*. Beliefs can also be sent to FlexMeasures via API or imported from open data hubs like `ENTSO-E `_ or `Weather Forecast APIs `_. However, in this tutorial we'll show how you can read data in from a CSV file. Sometimes that's just what you need :) @@ -368,19 +368,19 @@ Let's look at the price data we just loaded: │ ▗▀▚▖ │ │ ▗▘ ▝▖ │ │ ▞ ▌ │ - │ ▟ ▐ │ 15EUR/MWh + │ ▟ ▐ │ 0.015EUR/kWh │ ▗▘ ▝▖ ▗ │ │ ▗▘ ▚ ▄▞▘▚▖ │ │ ▞ ▐ ▄▀▘ ▝▄ │ │ ▄▞ ▌ ▛ ▖ │ │▀ ▚ ▐ ▝▖ │ - │ ▝▚ ▖ ▗▘ ▝▖ │ 10EUR/MWh + │ ▝▚ ▖ ▗▘ ▝▖ │ 0.010EUR/kWh │ ▀▄▄▞▀▄▄ ▗▀▝▖ ▞ ▐ │ │ ▀▀▜▘ ▝▚ ▗▘ ▚ │ │ ▌ ▞ ▌│ │ ▝▖ ▞ ▝│ │ ▐ ▞ │ - │ ▚ ▗▞ │ 5EUR/MWh + │ ▚ ▗▞ │ 0.005EUR/kWh │ ▀▚▄▄▄▄▘ │ └────────────────────────────────────────────────────────────┘ 5 10 15 20 @@ -396,4 +396,3 @@ Again, we can also view these prices in the `FlexMeasures UI Date: Thu, 4 Jun 2026 21:48:12 +0100 Subject: [PATCH 06/13] docs/tutorial: align follow-on examples with kW units Signed-off-by: Mohamed Belhsan Hmida --- .../toy-example-multiasset-curtailment.rst | 58 +++++++++---------- documentation/tut/toy-example-process.rst | 26 ++++----- documentation/tut/toy-example-reporter.rst | 8 +-- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/documentation/tut/toy-example-multiasset-curtailment.rst b/documentation/tut/toy-example-multiasset-curtailment.rst index b9b0e3ff99..7ffdc35f57 100644 --- a/documentation/tut/toy-example-multiasset-curtailment.rst +++ b/documentation/tut/toy-example-multiasset-curtailment.rst @@ -37,12 +37,12 @@ Also, we want to create a situation with negative prices, so curtailment makes s $ # this flex context has negative prices between 12:00 and 14:00 $ echo '''{ "consumption-price": [ - {"start": "'${TOMORROW}'T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "'${TOMORROW}'T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "'${TOMORROW}'T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "'${TOMORROW}'T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "'${TOMORROW}'T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "'${TOMORROW}'T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "'${TOMORROW}'T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "'${TOMORROW}'T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] }''' > tutorial3-priceprofile-flex-context.json $ docker cp tutorial3-priceprofile-flex-context.json flexmeasures-server-1:/app/ @@ -71,12 +71,12 @@ Also, we want to create a situation with negative prices, so curtailment makes s ], "flex-context": { "consumption-price": [ - {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] } } @@ -109,12 +109,12 @@ Also, we want to create a situation with negative prices, so curtailment makes s ], flex_context={ "consumption-price": [ - {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] }, ) @@ -132,7 +132,7 @@ Great. Let's see what we made: Data spans 12 hours and starts at 2025-11-29 07:00:00+01:00. The time resolution (x-axis) is 15 minutes. ┌────────────────────────────────────────────────────────────┐ - │ ▞▀▀▀▌ │ 0.2MW + │ ▞▀▀▀▌ │ 200kW │ ▄▄▄▄▞ ▌ ▗▄▄▄▖ │ │ ▗▘ ▚ ▐ ▝▖ │ │ ▛▀▀▀▘ ▐ ▐ ▝▀▀▀▜ │ @@ -140,7 +140,7 @@ Great. Let's see what we made: │ ▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ │ ▗▘ ▐ ▌ ▐ │ │ ▐ ▐ ▌ ▌ │ - │▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ 0.1MW + │▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ 100kW │ ▌ ▌ ▐ │ │ ▌ ▐ ▌ │ │ ▌ ▐ ▐ │ @@ -148,7 +148,7 @@ Great. Let's see what we made: │ ▌ ▐ │ │ ▌ ▐ │ │ ▚ ▐ │ - │▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▐▄▄▄▄▄▄▄▄▌▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁│ -0.0MW + │▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▐▄▄▄▄▄▄▄▄▌▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁│ 0kW └────────────────────────────────────────────────────────────┘ 06:00 09:00 12:00 15:00 ██ production (toy-solar) @@ -205,12 +205,12 @@ Note that we are still passing in the flex-context with block price profiles her ], "flex-context": { "consumption-price": [ - {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] } } @@ -244,12 +244,12 @@ Note that we are still passing in the flex-context with block price profiles her ], flex_context={ "consumption-price": [ - {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "10 EUR/MWh"} + {"start": "2025-11-18T00:00+01", "duration": "PT24H", "value": "0.010 EUR/kWh"} ], "production-price": [ - {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "4 EUR/MWh"}, - {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-10 EUR/MWh"}, - {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "4 EUR/MWh"} + {"start": "2025-11-18T05:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"}, + {"start": "2025-11-18T12:00+01", "duration": "PT2H", "value": "-0.010 EUR/kWh"}, + {"start": "2025-11-18T14:00+01", "duration": "PT7H", "value": "0.004 EUR/kWh"} ] }, ) @@ -276,22 +276,22 @@ And here is the CLI version: The time resolution (x-axis) is 15 minutes. ┌────────────────────────────────────────────────────────────┐ │ ▛▀▀│ - │ ▐ │ 0.4MW + │ ▐ │ 400kW │ ▐ │ │ ▞ │ │ ▐▀▌ ▌ │ │ ▐ ▌▄▄▄▄▄▖ ▌ │ - │ ▄▄▄▄▄▞▀▀▞▀▐ ▝▀▚▞▀▚▄▄▄▄▖ ▐ │ 0.2MW + │ ▄▄▄▄▄▞▀▀▞▀▐ ▝▀▚▞▀▚▄▄▄▄▖ ▐ │ 200kW │ ▄▄▄▄▞▀▀▀▀▘ ▌ ▐ ▝▀▀▀▀▚▄▄▄▄ ▐ │ │▄▄▄▄▞ ▗▘ ▐ ▚▄▄▄▄▌ │ │ ▞ ▐ ▌▄▄▄▄│ │ ▗▘ ▐ ▐ │ - │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▌▔▔▔▔▔▔▔▔▔▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔│ -0.0MW + │▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▌▔▔▔▔▔▔▔▔▔▐▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▔▔▔▔▔│ 0kW │ ▌ ▌ │ │ ▌ ▌ │ │ ▌ ▐ │ │ ▌ ▗▖▐ │ - │ ▐▄▄▄▄▞▀▘▝▘ │ -0.2MW + │ ▐▄▄▄▄▞▀▘▝▘ │ -200kW └────────────────────────────────────────────────────────────┘ 06:00 09:00 12:00 15:00 ██ production (toy-solar) ██ discharging (toy-battery) @@ -325,7 +325,7 @@ We see the battery cycling twice, as before, but now it also soaks up solar prod │▄▄▌▄▄▀▀▀▀▀▀▀▀▀▐ ▐ ▝▀▀▀▀▀▀▀▀▀▄▄▄▄▄ ▌ ▙▘ │ │ ▌ ▐ ▌ ▚▄▄▄▐▖ █ │ │ ▐ ▌ ▌ ▐▝▀▀▀▐▚▄▄▄▄│ - │▔▔▝▀▀▀▀▀▀▌▔▔▔▔▌▔▔▔▔▔▝▀▀▀▀▀▀▀▀▌▔▔▔▔▔▔▔▔▔▔▞▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔│ 0.0MW + │▔▔▝▀▀▀▀▀▀▌▔▔▔▔▌▔▔▔▔▔▝▀▀▀▀▀▀▀▀▌▔▔▔▔▔▔▔▔▔▔▞▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔│ 0kW │ ▌ ▐ ▐ ▞ │ │ ▚ ▐ ▐ ▌ │ │ ▐ ▌ ▐ ▌ │ @@ -333,7 +333,7 @@ We see the battery cycling twice, as before, but now it also soaks up solar prod │ ▐ ▐ ▌ ▐ │ │ ▌ ▞ ▌ ▐ │ │ ▌ ▌ ▌ ▌ │ - │ ▙▟ ▐▄▄▄▄▄▄▄▌ │ -0.5MW + │ ▙▟ ▐▄▄▄▄▄▄▄▌ │ -500kW └────────────────────────────────────────────────────────────┘ 09:00 12:00 15:00 18:00 ██ production (toy-solar) ██ discharging (toy-battery) diff --git a/documentation/tut/toy-example-process.rst b/documentation/tut/toy-example-process.rst index 0a2b2b01d7..9f32dfe14b 100644 --- a/documentation/tut/toy-example-process.rst +++ b/documentation/tut/toy-example-process.rst @@ -8,7 +8,7 @@ Until this point we've been using a static battery, one of the most flexible ene However, in some settings, we can reduce electricity bills by **just** smartly timing the necessary work that we know we have to do. We call this work a "process". In other words, if the process can be displaced, by breaking it into smaller consumption periods or shifting its start time, the process run can match the lower price hours better. -For example, we could have a load that consumes energy at a constant rate (e.g. 200kW) for a fixed duration (e.g. 4h), but there's some flexibility in the start time. In that case, we could find the optimal start time in order to minimize the energy cost. +For example, we could have a load that consumes energy at a constant rate (e.g. 200 kW) for a fixed duration (e.g. 4h), but there's some flexibility in the start time. In that case, we could find the optimal start time in order to minimize the energy cost. Examples of flexible processes are: - Water irrigation in agriculture @@ -39,9 +39,9 @@ Before moving forward, we'll add the `process` asset and three sensors to store User with email toy-user@flexmeasures.io already exists in account Docker Toy Account. The sensor recording day-ahead prices is day-ahead prices (ID: 1). Created - Created - Created - Created + Created + Created + Created The sensor recording the power of the inflexible load is Power (Inflexible) (ID: 4). The sensor recording the power of the breakable load is Power (Breakable) (ID: 5). The sensor recording the power of the shiftable load is Power (Shiftable) (ID: 6). @@ -50,7 +50,7 @@ Before moving forward, we'll add the `process` asset and three sensors to store Trigger an updated schedule ---------------------------- -In this example, we are planning to consume at a 200kW constant power for a period of 4h. +In this example, we are planning to consume at a 200 kW constant power for a period of 4h. This load is to be scheduled for tomorrow, except from the period from 3pm to 4pm (imposed using the ``time-restrictions`` parameter). @@ -62,7 +62,7 @@ Now we are ready to schedule a process. Let's start with the INFLEXIBLE policy, $ flexmeasures add schedule --sensor 4 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{\"consumption-price\": {\"sensor\": 1}}' \ - --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"INFLEXIBLE\", \"power\": 0.2, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ + --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"INFLEXIBLE\", \"power\": 200, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ Under the INFLEXIBLE policy, the process starts as soon as possible, in this case, coinciding with the start of the planning window. @@ -73,7 +73,7 @@ Following the INFLEXIBLE policy, we'll schedule the same 4h consumption requirem $ flexmeasures add schedule --sensor 5 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{\"consumption-price\": {\"sensor\": 1}}' \ - --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"BREAKABLE\", \"power\": 0.2, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ + --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"BREAKABLE\", \"power\": 200, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ The BREAKABLE policy splits or breaks the process into blocks that can be scheduled discontinuously. The smallest possible unit is (currently) determined by the sensor's resolution. @@ -84,7 +84,7 @@ Finally, we'll schedule the process using the SHIFTABLE policy. The 4h block can $ flexmeasures add schedule --sensor 6 --scheduler ProcessScheduler \ --start ${TOMORROW}T00:00:00+02:00 --duration PT24H \ --flex-context '{\"consumption-price\": {\"sensor\": 1}}' \ - --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"SHIFTABLE\", \"power\": 0.2, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ + --flex-model '{\"duration\": \"PT4H\", \"process-type\": \"SHIFTABLE\", \"power\": 200, \"time-restrictions\": [{\"start\": \"${TOMORROW}T15:00:00+02:00\", \"duration\": \"PT1H\"}]}' \ Results @@ -111,15 +111,15 @@ Let's list the power costs which the policies achieved for each of the four bloc +-------------------------+------------+-----------+-----------+ | Block | INFLEXIBLE | BREAKABLE | SHIFTABLE | +=========================+============+===========+===========+ -| 1 | 10.00 | 5.00 | 10.00 | +| 1 | 0.010 | 0.005 | 0.010 | +-------------------------+------------+-----------+-----------+ -| 2 | 11.00 | 4.00 | 8.00 | +| 2 | 0.011 | 0.004 | 0.008 | +-------------------------+------------+-----------+-----------+ -| 3 | 12.00 | 5.50 | 5.00 | +| 3 | 0.012 | 0.0055 | 0.005 | +-------------------------+------------+-----------+-----------+ -| 4 | 15.00 | 7.00 | 4.00 | +| 4 | 0.015 | 0.007 | 0.004 | +-------------------------+------------+-----------+-----------+ -| Average Price (EUR/MWh) | 12.00 | 5.37 | 6.75 | +| Average Price (EUR/kWh) | 0.012 | 0.00537 | 0.00675 | +-------------------------+------------+-----------+-----------+ | Total Cost (EUR) | 9.60 | 4.29 | 5.40 | +-------------------------+------------+-----------+-----------+ diff --git a/documentation/tut/toy-example-reporter.rst b/documentation/tut/toy-example-reporter.rst index 7457c200f3..1ea8377f10 100644 --- a/documentation/tut/toy-example-reporter.rst +++ b/documentation/tut/toy-example-reporter.rst @@ -55,19 +55,19 @@ Run the command below to show the values for our newly-created `grid connection │ │ │ │ │ │ - │ │ 1.0MW + │ │ 500.5kW │ │ │ │ │ │ - │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ 0.5MW + │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│ 500.0kW │ │ │ │ │ │ - │▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁│ 0.0MW + │ │ 499.5kW │ │ │ │ │ │ - │ │ -0.5MW + │ │ 499.0kW └────────────────────────────────────────────────────────────┘ 06:00 12:00 18:00 ██ grid connection capacity (toy-building) From 1aa042653efdca8f8d529e156a39009abf33e585 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Fri, 5 Jun 2026 03:57:01 +0100 Subject: [PATCH 07/13] docs: add changelog entry Signed-off-by: Mohamed Belhsan Hmida --- documentation/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/changelog.rst b/documentation/changelog.rst index c2d79a3685..b55c5ec94f 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -11,6 +11,7 @@ New features ------------- * Floor off-clock API datetimes to a non-instantaneous sensor's resolution by default when ingesting sensor data, uploading sensor data, and handling scheduler flex-model timed events; configurable with the ``floor_datetimes_to_resolution`` sensor attribute [see `PR #2146 `_] * Sensor references in flex-model and flex-context support various ways of filtering by source [see `PR #2209 `_] +* Create toy tutorial accounts with kW-scale power sensors and ``EUR/kWh`` day-ahead prices [see `PR #2223 `_] Infrastructure / Support From 03bc4da64de5bb509c16ac1ada1007e719b379ec Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Fri, 5 Jun 2026 13:25:43 +0100 Subject: [PATCH 08/13] docs/tutorial: import toy prices as EUR per MWh Signed-off-by: Mohamed Belhsan Hmida --- .../tut/scripts/run-tutorial-in-docker.sh | 50 ++++++++--------- documentation/tut/toy-example-setup.rst | 55 +++++++++---------- 2 files changed, 52 insertions(+), 53 deletions(-) diff --git a/documentation/tut/scripts/run-tutorial-in-docker.sh b/documentation/tut/scripts/run-tutorial-in-docker.sh index 1b8d47eb78..f1d2c3a575 100755 --- a/documentation/tut/scripts/run-tutorial-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial-in-docker.sh @@ -9,35 +9,35 @@ echo "-----------------------------------------------------------------" echo "[TUTORIAL-RUNNER] loading prices..." TOMORROW=$(date --date="next day" '+%Y-%m-%d') echo "Hour,Price -${TOMORROW}T00:00:00,0.010 -${TOMORROW}T01:00:00,0.011 -${TOMORROW}T02:00:00,0.012 -${TOMORROW}T03:00:00,0.015 -${TOMORROW}T04:00:00,0.018 -${TOMORROW}T05:00:00,0.017 -${TOMORROW}T06:00:00,0.0105 -${TOMORROW}T07:00:00,0.009 -${TOMORROW}T08:00:00,0.0095 -${TOMORROW}T09:00:00,0.009 -${TOMORROW}T10:00:00,0.0085 -${TOMORROW}T11:00:00,0.010 -${TOMORROW}T12:00:00,0.008 -${TOMORROW}T13:00:00,0.005 -${TOMORROW}T14:00:00,0.004 -${TOMORROW}T15:00:00,0.004 -${TOMORROW}T16:00:00,0.0055 -${TOMORROW}T17:00:00,0.008 -${TOMORROW}T18:00:00,0.012 -${TOMORROW}T19:00:00,0.013 -${TOMORROW}T20:00:00,0.014 -${TOMORROW}T21:00:00,0.0125 -${TOMORROW}T22:00:00,0.010 -${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv +${TOMORROW}T00:00:00,10 +${TOMORROW}T01:00:00,11 +${TOMORROW}T02:00:00,12 +${TOMORROW}T03:00:00,15 +${TOMORROW}T04:00:00,18 +${TOMORROW}T05:00:00,17 +${TOMORROW}T06:00:00,10.5 +${TOMORROW}T07:00:00,9 +${TOMORROW}T08:00:00,9.5 +${TOMORROW}T09:00:00,9 +${TOMORROW}T10:00:00,8.5 +${TOMORROW}T11:00:00,10 +${TOMORROW}T12:00:00,8 +${TOMORROW}T13:00:00,5 +${TOMORROW}T14:00:00,4 +${TOMORROW}T15:00:00,4 +${TOMORROW}T16:00:00,5.5 +${TOMORROW}T17:00:00,8 +${TOMORROW}T18:00:00,12 +${TOMORROW}T19:00:00,13 +${TOMORROW}T20:00:00,14 +${TOMORROW}T21:00:00,12.5 +${TOMORROW}T22:00:00,10 +${TOMORROW}T23:00:00,7" > prices-tomorrow.csv docker cp prices-tomorrow.csv $CONTAINER_NAME:/app docker exec -it $CONTAINER_NAME flexmeasures add beliefs \ - --sensor 1 --source toy-user /app/prices-tomorrow.csv --timezone Europe/Amsterdam + --sensor 1 --source toy-user /app/prices-tomorrow.csv --timezone Europe/Amsterdam --unit EUR/MWh echo "[TUTORIAL-RUNNER] creating schedule ..." docker exec -it $CONTAINER_NAME flexmeasures add schedule \ diff --git a/documentation/tut/toy-example-setup.rst b/documentation/tut/toy-example-setup.rst index 82a6964e66..df7fefd745 100644 --- a/documentation/tut/toy-example-setup.rst +++ b/documentation/tut/toy-example-setup.rst @@ -20,7 +20,7 @@ Below are the ``flexmeasures`` CLI commands we'll run, and which we'll explain s # setup an account with a user, assets for battery & solar and an energy market (ID 1) $ flexmeasures add toy-account # load prices to optimize schedules against - $ flexmeasures add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam + $ flexmeasures add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam --unit EUR/MWh Okay, let's get started! @@ -313,42 +313,42 @@ And on the flex-model of the battery can be seen on its properties page (and is Add some price data --------------------------------------- -Now to add price data. First, we'll create the CSV file with prices (EUR/kWh, see the setup for sensor 1 above) for tomorrow. +Now to add price data. First, we'll create the CSV file with prices in EUR/MWh for tomorrow. The price sensor stores data in EUR/kWh, and we'll ask FlexMeasures to convert the CSV values while loading them. .. code-block:: bash $ TOMORROW=$(date --date="next day" '+%Y-%m-%d') $ echo "Hour,Price - $ ${TOMORROW}T00:00:00,0.010 - $ ${TOMORROW}T01:00:00,0.011 - $ ${TOMORROW}T02:00:00,0.012 - $ ${TOMORROW}T03:00:00,0.015 - $ ${TOMORROW}T04:00:00,0.018 - $ ${TOMORROW}T05:00:00,0.017 - $ ${TOMORROW}T06:00:00,0.0105 - $ ${TOMORROW}T07:00:00,0.009 - $ ${TOMORROW}T08:00:00,0.0095 - $ ${TOMORROW}T09:00:00,0.009 - $ ${TOMORROW}T10:00:00,0.0085 - $ ${TOMORROW}T11:00:00,0.010 - $ ${TOMORROW}T12:00:00,0.008 - $ ${TOMORROW}T13:00:00,0.005 - $ ${TOMORROW}T14:00:00,0.004 - $ ${TOMORROW}T15:00:00,0.004 - $ ${TOMORROW}T16:00:00,0.0055 - $ ${TOMORROW}T17:00:00,0.008 - $ ${TOMORROW}T18:00:00,0.012 - $ ${TOMORROW}T19:00:00,0.013 - $ ${TOMORROW}T20:00:00,0.014 - $ ${TOMORROW}T21:00:00,0.0125 - $ ${TOMORROW}T22:00:00,0.010 - $ ${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv + $ ${TOMORROW}T00:00:00,10 + $ ${TOMORROW}T01:00:00,11 + $ ${TOMORROW}T02:00:00,12 + $ ${TOMORROW}T03:00:00,15 + $ ${TOMORROW}T04:00:00,18 + $ ${TOMORROW}T05:00:00,17 + $ ${TOMORROW}T06:00:00,10.5 + $ ${TOMORROW}T07:00:00,9 + $ ${TOMORROW}T08:00:00,9.5 + $ ${TOMORROW}T09:00:00,9 + $ ${TOMORROW}T10:00:00,8.5 + $ ${TOMORROW}T11:00:00,10 + $ ${TOMORROW}T12:00:00,8 + $ ${TOMORROW}T13:00:00,5 + $ ${TOMORROW}T14:00:00,4 + $ ${TOMORROW}T15:00:00,4 + $ ${TOMORROW}T16:00:00,5.5 + $ ${TOMORROW}T17:00:00,8 + $ ${TOMORROW}T18:00:00,12 + $ ${TOMORROW}T19:00:00,13 + $ ${TOMORROW}T20:00:00,14 + $ ${TOMORROW}T21:00:00,12.5 + $ ${TOMORROW}T22:00:00,10 + $ ${TOMORROW}T23:00:00,7" > prices-tomorrow.csv This is time series data, in FlexMeasures we call *"beliefs"*. Beliefs can also be sent to FlexMeasures via API or imported from open data hubs like `ENTSO-E `_ or `Weather Forecast APIs `_. However, in this tutorial we'll show how you can read data in from a CSV file. Sometimes that's just what you need :) .. code-block:: bash - $ flexmeasures add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam + $ flexmeasures add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam --unit EUR/MWh Successfully created beliefs In FlexMeasures, all beliefs have a data source. Here, we use the username of the user we created earlier. We could also pass a user ID, or the name of a new data source we want to use for CLI scripts. @@ -395,4 +395,3 @@ Again, we can also view these prices in the `FlexMeasures UI Date: Fri, 5 Jun 2026 13:26:16 +0100 Subject: [PATCH 09/13] ci: import toy prices as EUR per MWh Signed-off-by: Mohamed Belhsan Hmida --- .github/workflows/build.yml | 2 +- .github/workflows/generate-dummy-price.sh | 48 +++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f9480db55..bdff6d25cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: - name: Add beliefs run: | docker exec --env-file .env fm-container flexmeasures \ - add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam + add beliefs --sensor 1 --source toy-user prices-tomorrow.csv --timezone Europe/Amsterdam --unit EUR/MWh - name: Export TOMORROW run: echo "TOMORROW=$(date --date="next day" '+%Y-%m-%d')" >> $GITHUB_ENV diff --git a/.github/workflows/generate-dummy-price.sh b/.github/workflows/generate-dummy-price.sh index 6b674649ab..06ee670454 100755 --- a/.github/workflows/generate-dummy-price.sh +++ b/.github/workflows/generate-dummy-price.sh @@ -6,27 +6,27 @@ set -x TOMORROW=$(date --date="next day" '+%Y-%m-%d') echo "Hour,Price -${TOMORROW}T00:00:00,0.010 -${TOMORROW}T01:00:00,0.011 -${TOMORROW}T02:00:00,0.012 -${TOMORROW}T03:00:00,0.015 -${TOMORROW}T04:00:00,0.018 -${TOMORROW}T05:00:00,0.017 -${TOMORROW}T06:00:00,0.0105 -${TOMORROW}T07:00:00,0.009 -${TOMORROW}T08:00:00,0.0095 -${TOMORROW}T09:00:00,0.009 -${TOMORROW}T10:00:00,0.0085 -${TOMORROW}T11:00:00,0.010 -${TOMORROW}T12:00:00,0.008 -${TOMORROW}T13:00:00,0.005 -${TOMORROW}T14:00:00,0.004 -${TOMORROW}T15:00:00,0.004 -${TOMORROW}T16:00:00,0.0055 -${TOMORROW}T17:00:00,0.008 -${TOMORROW}T18:00:00,0.012 -${TOMORROW}T19:00:00,0.013 -${TOMORROW}T20:00:00,0.014 -${TOMORROW}T21:00:00,0.0125 -${TOMORROW}T22:00:00,0.010 -${TOMORROW}T23:00:00,0.007" > prices-tomorrow.csv +${TOMORROW}T00:00:00,10 +${TOMORROW}T01:00:00,11 +${TOMORROW}T02:00:00,12 +${TOMORROW}T03:00:00,15 +${TOMORROW}T04:00:00,18 +${TOMORROW}T05:00:00,17 +${TOMORROW}T06:00:00,10.5 +${TOMORROW}T07:00:00,9 +${TOMORROW}T08:00:00,9.5 +${TOMORROW}T09:00:00,9 +${TOMORROW}T10:00:00,8.5 +${TOMORROW}T11:00:00,10 +${TOMORROW}T12:00:00,8 +${TOMORROW}T13:00:00,5 +${TOMORROW}T14:00:00,4 +${TOMORROW}T15:00:00,4 +${TOMORROW}T16:00:00,5.5 +${TOMORROW}T17:00:00,8 +${TOMORROW}T18:00:00,12 +${TOMORROW}T19:00:00,13 +${TOMORROW}T20:00:00,14 +${TOMORROW}T21:00:00,12.5 +${TOMORROW}T22:00:00,10 +${TOMORROW}T23:00:00,7" > prices-tomorrow.csv From d3c2d82adeeaad2df8e31092aca1205679415948 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Fri, 5 Jun 2026 13:27:21 +0100 Subject: [PATCH 10/13] docs/tests: keep toy site capacity in kVA Signed-off-by: Mohamed Belhsan Hmida --- documentation/tut/toy-example-setup.rst | 4 ++-- flexmeasures/cli/data_add.py | 2 +- flexmeasures/cli/tests/test_data_add_fresh_db.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/tut/toy-example-setup.rst b/documentation/tut/toy-example-setup.rst index df7fefd745..774d5db3ca 100644 --- a/documentation/tut/toy-example-setup.rst +++ b/documentation/tut/toy-example-setup.rst @@ -213,7 +213,7 @@ If you want, you can inspect what you created in the CLI (we'll also show the UI Flex-Context Flex-Model -------------------------------- ------------ - site-power-capacity: 500 kW + site-power-capacity: 500 kVA consumption-price: {'sensor': 1} ==================================== @@ -230,7 +230,7 @@ If you want, you can inspect what you created in the CLI (we'll also show the UI You can see that this building asset has some meta information about how FlexMeasures needs to schedule: - Within :ref:`flex_context`, we noted where to find the relevant optimization signal for electricity consumption (Sensor 1, which stores day-ahead prices). -- Also, the building has a grid connection capacity of 500 kW, meaning that the total power flowing into or out of the building cannot exceed this value. +- Also, the building has a grid connection capacity of 500 kVA, meaning that the total power flowing into or out of the building cannot exceed this physical limit. Now let's look at the battery asset, as well: diff --git a/flexmeasures/cli/data_add.py b/flexmeasures/cli/data_add.py index fd36967d20..b4e0508e7f 100755 --- a/flexmeasures/cli/data_add.py +++ b/flexmeasures/cli/data_add.py @@ -1797,7 +1797,7 @@ def create_asset_with_one_sensor( latitude=location[0], longitude=location[1], flex_context={ - "site-power-capacity": "500 kW", + "site-power-capacity": "500 kVA", "consumption-price": {"sensor": day_ahead_sensor.id}, }, ) diff --git a/flexmeasures/cli/tests/test_data_add_fresh_db.py b/flexmeasures/cli/tests/test_data_add_fresh_db.py index f55aa1009b..a3327f13e5 100644 --- a/flexmeasures/cli/tests/test_data_add_fresh_db.py +++ b/flexmeasures/cli/tests/test_data_add_fresh_db.py @@ -398,7 +398,7 @@ def test_add_toy_account_battery_uses_kw_scale_units(app, fresh_db): ).scalar_one() assert day_ahead_sensor.unit == "EUR/kWh" - assert building.flex_context["site-power-capacity"] == "500 kW" + assert building.flex_context["site-power-capacity"] == "500 kVA" assert battery.flex_model["power-capacity"] == "500 kW" assert battery.flex_model["soc-max"] == "450 kWh" assert battery.sensors[0].unit == "kW" From 1be8deafe68cea14dc6218a014a136a8b5b5a85a Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Fri, 5 Jun 2026 13:28:18 +0100 Subject: [PATCH 11/13] docs/tests: keep toy battery capacity in kVA Signed-off-by: Mohamed Belhsan Hmida --- documentation/tut/toy-example-expanded.rst | 2 +- documentation/tut/toy-example-setup.rst | 4 ++-- flexmeasures/cli/data_add.py | 2 +- flexmeasures/cli/tests/test_data_add_fresh_db.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/tut/toy-example-expanded.rst b/documentation/tut/toy-example-expanded.rst index ade5c3fff5..e86be37f48 100644 --- a/documentation/tut/toy-example-expanded.rst +++ b/documentation/tut/toy-example-expanded.rst @@ -6,7 +6,7 @@ Toy example II: Adding solar production, and a limit on the grid connection ============================================================================ -So far we haven't taken into account any other devices that consume or produce electricity. The battery was free to use all available capacity (which was 500 kW, both its own maximum charge/discharge rate, and the maximum grid capacity). +So far we haven't taken into account any other devices that consume or produce electricity. The battery was free to use all available capacity (which was 500 kVA, both its own maximum charge/discharge rate, and the maximum grid capacity). What if other devices will be using some of that capacity? Our schedules need to reflect that, so we stay within given limits. diff --git a/documentation/tut/toy-example-setup.rst b/documentation/tut/toy-example-setup.rst index 774d5db3ca..c47dc351a5 100644 --- a/documentation/tut/toy-example-setup.rst +++ b/documentation/tut/toy-example-setup.rst @@ -251,7 +251,7 @@ Now let's look at the battery asset, as well: Flex-Context Flex-Model -------------- ------------------------- - power-capacity: 500 kW + power-capacity: 500 kVA roundtrip-efficiency: 90% soc-max: 450 kWh @@ -272,7 +272,7 @@ Now let's look at the battery asset, as well: Yes, that is quite a large battery :) You can also see that the asset has some meta information about its scheduling. -- Within :ref:`flex_model`, we noted that the battery's power capacity is the same as the building's grid connection capacity (500 kW), meaning the battery can charge or discharge at full power without overloading the connection, but no other devices can (we will come back to this limitation). +- Within :ref:`flex_model`, we noted that the battery's power capacity is the same as the building's grid connection capacity (500 kVA), meaning the battery can charge or discharge at full power without overloading the connection, but no other devices can (we will come back to this limitation). - Also noted is the battery's roundtrip efficiency (90%) and maximum state of charge (450 kWh). .. note:: Obviously, you can use the ``flexmeasures`` command to create your own, custom account and assets. See :ref:`cli`. And to create, edit or read asset data via the API, see :ref:`v3_0`. diff --git a/flexmeasures/cli/data_add.py b/flexmeasures/cli/data_add.py index b4e0508e7f..f6f1e31c73 100755 --- a/flexmeasures/cli/data_add.py +++ b/flexmeasures/cli/data_add.py @@ -1811,7 +1811,7 @@ def create_asset_with_one_sensor( "discharging", parent_asset_id=building_asset.id, flex_model={ - "power-capacity": "500 kW", + "power-capacity": "500 kVA", "roundtrip-efficiency": "90%", "soc-max": "450 kWh", }, diff --git a/flexmeasures/cli/tests/test_data_add_fresh_db.py b/flexmeasures/cli/tests/test_data_add_fresh_db.py index a3327f13e5..ff9f72c38c 100644 --- a/flexmeasures/cli/tests/test_data_add_fresh_db.py +++ b/flexmeasures/cli/tests/test_data_add_fresh_db.py @@ -374,7 +374,7 @@ def test_add_account( assert result.exit_code == 1 -def test_add_toy_account_battery_uses_kw_scale_units(app, fresh_db): +def test_add_toy_account_battery_uses_kw_sensors_and_kva_capacities(app, fresh_db): from flexmeasures.cli.data_add import add_toy_account result = app.test_cli_runner().invoke(add_toy_account, ["--kind", "battery"]) @@ -399,7 +399,7 @@ def test_add_toy_account_battery_uses_kw_scale_units(app, fresh_db): assert day_ahead_sensor.unit == "EUR/kWh" assert building.flex_context["site-power-capacity"] == "500 kVA" - assert battery.flex_model["power-capacity"] == "500 kW" + assert battery.flex_model["power-capacity"] == "500 kVA" assert battery.flex_model["soc-max"] == "450 kWh" assert battery.sensors[0].unit == "kW" assert solar.sensors[0].unit == "kW" From d90941a2d1792e95fdb5226c76f3bf371f8c6869 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Fri, 5 Jun 2026 13:28:57 +0100 Subject: [PATCH 12/13] tests: explain toy schedule power bounds Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures/cli/tests/test_data_add_fresh_db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flexmeasures/cli/tests/test_data_add_fresh_db.py b/flexmeasures/cli/tests/test_data_add_fresh_db.py index ff9f72c38c..83cbbca947 100644 --- a/flexmeasures/cli/tests/test_data_add_fresh_db.py +++ b/flexmeasures/cli/tests/test_data_add_fresh_db.py @@ -520,6 +520,8 @@ def test_add_storage_schedule( max_power = schedule.event_value.abs().max() assert len(schedule) == 48 assert power_sensor.unit == "kW" + # The 700 kW quantity override is the highest cap used by this parametrized test. + # The lower bound catches accidentally storing MW-scale values on the kW sensor. assert max_power > 1 assert max_power <= 700 From c7717244890c54511422f8dcd103ed99141637d1 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Sun, 5 Jul 2026 15:14:09 +0100 Subject: [PATCH 13/13] docs: restore main whitespace after merge Signed-off-by: Mohamed Belhsan Hmida --- documentation/dev/connection-secrets.rst | 4 ++-- documentation/features/scheduling.rst | 4 ++-- documentation/host/deployment.rst | 6 +++--- documentation/plugin/introduction.rst | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/documentation/dev/connection-secrets.rst b/documentation/dev/connection-secrets.rst index 4fdbfb7500..7a15f4edab 100644 --- a/documentation/dev/connection-secrets.rst +++ b/documentation/dev/connection-secrets.rst @@ -10,10 +10,10 @@ key ID and timestamps. Developers normally do not need to read or modify this JSON structure directly. For implementation examples, token lifecycle strategies and manually seeding a -credential through the CLI, see :ref:`storing_connection_secrets`. +credential through the CLI, see :ref:`storing_connection_secrets`. The encrypted values are protected by -``FLEXMEASURES_SECRETS_ENCRYPTION_KEYS``, see :ref:`flexmeasures_secrets_encryption_keys` +``FLEXMEASURES_SECRETS_ENCRYPTION_KEYS``, see :ref:`flexmeasures_secrets_encryption_keys` This setting accepts arbitrary non-empty strings, which FlexMeasures derives into Fernet-compatible keys. Hosts must configure this keyring before secrets can be stored - FlexMeasures will print a warning if it is not set and hints how to initialize it. diff --git a/documentation/features/scheduling.rst b/documentation/features/scheduling.rst index 770f7b0129..6399d867af 100644 --- a/documentation/features/scheduling.rst +++ b/documentation/features/scheduling.rst @@ -386,8 +386,8 @@ Clicking the "Info" button will give you a lot more insights into the jobs' conf The RQ-dashboard: complete overview ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Internally, jobs are queued with the python-rq library. For this, a job dashboard is available, which -users with the ``admin`` role can access via the menu. This gives a complete overview over all jobs +Internally, jobs are queued with the python-rq library. For this, a job dashboard is available, which +users with the ``admin`` role can access via the menu. This gives a complete overview over all jobs running in FlexMeasures. You find your jobs via the queues, see screenshot below. diff --git a/documentation/host/deployment.rst b/documentation/host/deployment.rst index 31d0196a36..db4549cfd4 100644 --- a/documentation/host/deployment.rst +++ b/documentation/host/deployment.rst @@ -13,10 +13,10 @@ Cloud setup using containers ------------------------------ The recommended way to run FlexMeasures is indeed via docker containers. -There are cloud infrastructures offered by the big providers, but there are many others, too. -You could use a container orchestration platform like Kubernetes, but also simpler options like Ansible/Terraform are great. +There are cloud infrastructures offered by the big providers, but there are many others, too. +You could use a container orchestration platform like Kubernetes, but also simpler options like Ansible/Terraform are great. -We recommend that several FlexMeasures containers are run, with different main tasks. +We recommend that several FlexMeasures containers are run, with different main tasks. The obvious tasks would be to serve web & API requests and to perform heavier background tasks (like computing schedules and forecasting). The image below shows an example architecture, and you can see that this allows you to scale each task on its own (by adding more containers to its stack). diff --git a/documentation/plugin/introduction.rst b/documentation/plugin/introduction.rst index 5ef9575776..c453746981 100644 --- a/documentation/plugin/introduction.rst +++ b/documentation/plugin/introduction.rst @@ -34,3 +34,4 @@ It also includes a few Blueprint examples and best practices. Continue reading the :ref:`plugin_showcase`. Continue to see more possibilities to do :ref:`plugin_customization`. And if your plugin connects to a 3rd-party platform, read about support for :ref:`storing_connection_secrets`. +