diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 10cd23ef76..1f34a4e4c4 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-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 @@ -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/documentation/changelog.rst b/documentation/changelog.rst index 0f41c0a1d4..5f6ee91d3b 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -15,6 +15,7 @@ New features * In the UI, asset and sensor lists can be filtered by ID prefix through API-backed search fields [see `PR #2231 `_] * 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 `_] * Let storage scheduling infer missing ``power-capacity`` from directional device capacities before falling back to site capacity, and default the missing opposite capacity to zero when only a non-zero ``consumption-capacity`` or ``production-capacity`` is configured [see `PR #2222 `_] * CLI support for adding/editing account attributes [see `PR #2242 `_] 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/scripts/run-tutorial-in-docker.sh b/documentation/tut/scripts/run-tutorial-in-docker.sh index 624a91adc7..f1d2c3a575 100755 --- a/documentation/tut/scripts/run-tutorial-in-docker.sh +++ b/documentation/tut/scripts/run-tutorial-in-docker.sh @@ -37,7 +37,7 @@ ${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/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." diff --git a/documentation/tut/toy-example-expanded.rst b/documentation/tut/toy-example-expanded.rst index 3922ad9a3e..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 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 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. @@ -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-multiasset-curtailment.rst b/documentation/tut/toy-example-multiasset-curtailment.rst index 3fb281986a..6541297414 100644 --- a/documentation/tut/toy-example-multiasset-curtailment.rst +++ b/documentation/tut/toy-example-multiasset-curtailment.rst @@ -42,12 +42,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/ @@ -76,12 +76,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"} ] } } @@ -114,12 +114,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"} ] }, ) @@ -137,7 +137,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 │ ▄▄▄▄▞ ▌ ▗▄▄▄▖ │ │ ▗▘ ▚ ▐ ▝▖ │ │ ▛▀▀▀▘ ▐ ▐ ▝▀▀▀▜ │ @@ -145,7 +145,7 @@ Great. Let's see what we made: │ ▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ │ ▗▘ ▐ ▌ ▐ │ │ ▐ ▐ ▌ ▌ │ - │▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ 0.1MW + │▄▄▄▄▌ ▐ ▌ ▐▄▄▄▄ │ 100kW │ ▌ ▌ ▐ │ │ ▌ ▐ ▌ │ │ ▌ ▐ ▐ │ @@ -153,7 +153,7 @@ Great. Let's see what we made: │ ▌ ▐ │ │ ▌ ▐ │ │ ▚ ▐ │ - │▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▐▄▄▄▄▄▄▄▄▌▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁│ -0.0MW + │▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▐▄▄▄▄▄▄▄▄▌▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁│ 0kW └────────────────────────────────────────────────────────────┘ 06:00 09:00 12:00 15:00 ██ production (toy-solar) @@ -210,12 +210,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"} ] } } @@ -249,12 +249,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"} ] }, ) @@ -281,22 +281,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) @@ -330,7 +330,7 @@ We see the battery cycling twice, as before, but now it also soaks up solar prod │▄▄▌▄▄▀▀▀▀▀▀▀▀▀▐ ▐ ▝▀▀▀▀▀▀▀▀▀▄▄▄▄▄ ▌ ▙▘ │ │ ▌ ▐ ▌ ▚▄▄▄▐▖ █ │ │ ▐ ▌ ▌ ▐▝▀▀▀▐▚▄▄▄▄│ - │▔▔▝▀▀▀▀▀▀▌▔▔▔▔▌▔▔▔▔▔▝▀▀▀▀▀▀▀▀▌▔▔▔▔▔▔▔▔▔▔▞▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔│ 0.0MW + │▔▔▝▀▀▀▀▀▀▌▔▔▔▔▌▔▔▔▔▔▝▀▀▀▀▀▀▀▀▌▔▔▔▔▔▔▔▔▔▔▞▀▀▀▀▀▀▀▀▀▔▔▔▔▔▔▔▔▔▔│ 0kW │ ▌ ▐ ▐ ▞ │ │ ▚ ▐ ▐ ▌ │ │ ▐ ▌ ▐ ▌ │ @@ -338,7 +338,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) diff --git a/documentation/tut/toy-example-setup.rst b/documentation/tut/toy-example-setup.rst index ddcee09284..c47dc351a5 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! @@ -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 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: @@ -265,7 +265,7 @@ 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 @@ -313,7 +313,7 @@ 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 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 @@ -348,7 +348,7 @@ This is time series data, in FlexMeasures we call *"beliefs"*. Beliefs can also .. 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. @@ -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 @@ -395,5 +395,3 @@ Again, we can also view these prices in the `FlexMeasures UI 1 + assert max_power <= 700 def test_add_storage_schedule_uses_state_of_charge_sensor_for_soc_at_start(