mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-03-18 18:44:04 +00:00
Migrate from Flask to FastAPI (#163)
* Migrate from Flask to FastAPI * FastAPI migration: - Use pydantic model classes as input parameters to the data/calculation classes. - Interface field names changed to constructor parameter names (for simplicity only during transition, should be updated in a followup PR). - Add basic interface requirements (e.g. some values > 0, etc.). * Update tests for new data format. * Python requirement down to 3.9 (TypeGuard no longer needed) * Makefile: Add helpful targets (e.g. development server with reload) * Move API doc from README to pydantic model classes (swagger) * Link to swagger.io with own openapi.yml. * Commit openapi.json and check with pytest for changes so the documentation is always up-to-date. * Streamline docker * FastAPI: Run startup action on dev server * Fix config for /strompreis, endpoint still broken however. * test_openapi: Compare against docs/.../openapi.json * Move fastapi to server/ submodule * See #187 for new repository structure.
This commit is contained in:
parent
ed3226e522
commit
f61665669f
6
.env
6
.env
@ -1,10 +1,4 @@
|
||||
EOS_VERSION=latest
|
||||
EOS_PORT=8503
|
||||
|
||||
MARIADB_VERSION=11.1.6
|
||||
PYTHON_VERSION=3.12.6
|
||||
|
||||
MARIADB_ROOT_PASSWORD=ruth
|
||||
MARIADB_DATABASE=eos
|
||||
MARIADB_USER=eos
|
||||
MARIADB_PASSWORD=eos
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -254,3 +254,6 @@ visualize_output_*.pdf
|
||||
|
||||
# Test images
|
||||
*_pdf.png
|
||||
|
||||
# Test files
|
||||
openapi-new.json
|
||||
|
@ -30,6 +30,8 @@ COPY src .
|
||||
USER eos
|
||||
ENTRYPOINT []
|
||||
|
||||
CMD ["python", "-m", "akkudoktoreosserver.flask_server"]
|
||||
EXPOSE 8503
|
||||
|
||||
CMD ["python", "-m", "akkudoktoreos.server.fastapi_server"]
|
||||
|
||||
VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}"]
|
||||
|
20
Makefile
20
Makefile
@ -1,5 +1,5 @@
|
||||
# Define the targets
|
||||
.PHONY: help venv pip install dist test test-full docker-run docs read-docs clean
|
||||
.PHONY: help venv pip install dist test test-full docker-run docker-build docs read-docs clean format run run-dev
|
||||
|
||||
# Default target
|
||||
all: help
|
||||
@ -10,12 +10,14 @@ help:
|
||||
@echo " venv - Set up a Python 3 virtual environment."
|
||||
@echo " pip - Install dependencies from requirements.txt."
|
||||
@echo " pip-dev - Install dependencies from requirements-dev.txt."
|
||||
@echo " format - Format source code."
|
||||
@echo " install - Install EOS in editable form (development mode) into virtual environment."
|
||||
@echo " docker-run - Run entire setup on docker"
|
||||
@echo " docker-build - Rebuild docker image"
|
||||
@echo " docs - Generate HTML documentation (in build/docs/html/)."
|
||||
@echo " read-docs - Read HTML documentation in your browser."
|
||||
@echo " run - Run flask_server in the virtual environment (needs install before)."
|
||||
@echo " run - Run FastAPI server in the virtual environment (needs install before)."
|
||||
@echo " run-dev - Run FastAPI development server in the virtual environment (automatically reloads)."
|
||||
@echo " dist - Create distribution (in dist/)."
|
||||
@echo " clean - Remove generated documentation, distribution and virtual environment."
|
||||
|
||||
@ -66,8 +68,12 @@ clean:
|
||||
@echo "Deletion complete."
|
||||
|
||||
run:
|
||||
@echo "Starting flask server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreosserver.flask_server
|
||||
@echo "Starting FastAPI server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreos.server.fastapi_server
|
||||
|
||||
run-dev:
|
||||
@echo "Starting FastAPI development server, please wait..."
|
||||
.venv/bin/fastapi dev src/akkudoktoreos/server/fastapi_server.py
|
||||
|
||||
# Target to setup tests.
|
||||
test-setup: pip-dev
|
||||
@ -83,9 +89,13 @@ test-full:
|
||||
@echo "Running all tests..."
|
||||
.venv/bin/pytest --full-run
|
||||
|
||||
# Target to format code.
|
||||
format:
|
||||
pre-commit run --all-files
|
||||
|
||||
# Run entire setup on docker
|
||||
docker-run:
|
||||
@docker compose up --remove-orphans
|
||||
|
||||
docker-build:
|
||||
@docker compose build
|
||||
@docker compose build --pull
|
||||
|
2
NOTICE
2
NOTICE
@ -17,7 +17,7 @@ This product may utilize technologies covered under international patents and/or
|
||||
|
||||
ADDITIONAL ATTRIBUTIONS:
|
||||
The following is a list of licensors and other acknowledgements for third-party software that may be contained within this system:
|
||||
- Flask, licensed under the BSD License, see https://flask.palletsprojects.com/
|
||||
- FastAPI, licensed under the MIT License, see https://fastapi.tiangolo.com/
|
||||
- NumPy, licensed under the BSD License, see https://numpy.org/
|
||||
- Requests, licensed under the Apache License 2.0, see https://requests.readthedocs.io/
|
||||
- matplotlib, licensed under the matplotlib License (a variant of the Python Software Foundation License), see https://matplotlib.org/
|
||||
|
260
README.md
260
README.md
@ -83,10 +83,18 @@ source .venv/bin/activate
|
||||
|
||||
## Usage
|
||||
|
||||
To use the system, run `flask_server.py`, which starts the server:
|
||||
To use the system, run `fastapi_server.py`, which starts the server:
|
||||
|
||||
```bash
|
||||
./flask_server.py
|
||||
./src/akkudoktoreos/server/fastapi_server.py
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
To run the system with Docker:
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
## Classes and Functionalities
|
||||
@ -111,250 +119,6 @@ These classes work together to enable a detailed simulation and optimization of
|
||||
|
||||
Each class is designed to be easily customized and extended to integrate additional functions or improvements. For example, new methods can be added for more accurate modeling of PV system or battery behavior. Developers are invited to modify and extend the system according to their needs.
|
||||
|
||||
# Input for the Flask Server (as of 30.07.2024)
|
||||
## Server API
|
||||
|
||||
Describes the structure and data types of the JSON object sent to the Flask server, with a forecast period of 48 hours.
|
||||
|
||||
## JSON Object Fields
|
||||
|
||||
### `strompreis_euro_pro_wh`
|
||||
|
||||
- **Description**: An array of floats representing the electricity price in euros per watt-hour for different time intervals.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 48
|
||||
|
||||
### `gesamtlast`
|
||||
|
||||
- **Description**: An array of floats representing the total load (consumption) in watts for different time intervals.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 48
|
||||
|
||||
### `pv_forecast`
|
||||
|
||||
- **Description**: An array of floats representing the forecasted photovoltaic output in watts for different time intervals.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 48
|
||||
|
||||
### `temperature_forecast`
|
||||
|
||||
- **Description**: An array of floats representing the temperature forecast in degrees Celsius for different time intervals.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 48
|
||||
|
||||
### `pv_soc`
|
||||
|
||||
- **Description**: An integer representing the state of charge of the PV battery at the **start** of the current hour (not the current state).
|
||||
- **Type**: Integer
|
||||
|
||||
### `pv_akku_cap`
|
||||
|
||||
- **Description**: An integer representing the capacity of the photovoltaic battery in watt-hours.
|
||||
- **Type**: Integer
|
||||
|
||||
### `einspeiseverguetung_euro_pro_wh`
|
||||
|
||||
- **Description**: A float representing the feed-in compensation in euros per watt-hour.
|
||||
- **Type**: Float
|
||||
|
||||
### `eauto_min_soc`
|
||||
|
||||
- **Description**: An integer representing the minimum state of charge (SOC) of the electric vehicle in percentage.
|
||||
- **Type**: Integer
|
||||
|
||||
### `eauto_cap`
|
||||
|
||||
- **Description**: An integer representing the capacity of the electric vehicle battery in watt-hours.
|
||||
- **Type**: Integer
|
||||
|
||||
### `eauto_charge_efficiency`
|
||||
|
||||
- **Description**: A float representing the charging efficiency of the electric vehicle.
|
||||
- **Type**: Float
|
||||
|
||||
### `eauto_charge_power`
|
||||
|
||||
- **Description**: An integer representing the charging power of the electric vehicle in watts.
|
||||
- **Type**: Integer
|
||||
|
||||
### `eauto_soc`
|
||||
|
||||
- **Description**: An integer representing the current state of charge (SOC) of the electric vehicle in percentage.
|
||||
- **Type**: Integer
|
||||
|
||||
### `start_solution`
|
||||
|
||||
- **Description**: Can be `null` or contain a previous solution (if available).
|
||||
- **Type**: `null` or object
|
||||
|
||||
### `haushaltsgeraet_wh`
|
||||
|
||||
- **Description**: An integer representing the energy consumption of a household device in watt-hours.
|
||||
- **Type**: Integer
|
||||
|
||||
### `haushaltsgeraet_dauer`
|
||||
|
||||
- **Description**: An integer representing the usage duration of a household device in hours.
|
||||
- **Type**: Integer
|
||||
|
||||
# JSON Output Description
|
||||
|
||||
This document describes the structure and data types of the JSON output returned by the Flask server, with a forecast period of 48 hours.
|
||||
|
||||
**Note**: The first value of "Last_Wh_pro_Stunde", "Netzeinspeisung_Wh_pro_Stunde" and "Netzbezug_Wh_pro_Stunde", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged.
|
||||
|
||||
## JSON Output Fields (as of 30.7.2024)
|
||||
|
||||
### discharge_hours_bin
|
||||
|
||||
An array that indicates for each hour of the forecast period (in this example, 48 hours) whether energy is discharged from the battery or not. The values are either `0` (no discharge) or `1` (discharge).
|
||||
|
||||
### eauto_obj
|
||||
|
||||
This object contains information related to the electric vehicle and its charging and discharging behavior:
|
||||
|
||||
- **charge_array**: Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging).
|
||||
- **Type**: Array
|
||||
- **Element Type**: Integer (0 or 1)
|
||||
- **Length**: 48
|
||||
- **discharge_array**: Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging).
|
||||
- **Type**: Array
|
||||
- **Element Type**: Integer (0 or 1)
|
||||
- **Length**: 48
|
||||
- **entlade_effizienz**: The discharge efficiency as a float.
|
||||
- **Type**: Float
|
||||
- **hours**: Amount of hours the simulation is done for.
|
||||
- **Type**: Integer
|
||||
- **kapazitaet_wh**: The capacity of the EV’s battery in watt-hours.
|
||||
- **Type**: Integer
|
||||
- **lade_effizienz**: The charging efficiency as a float.
|
||||
- **Type**: Float
|
||||
- **max_ladeleistung_w**: The maximum charging power of the EV in watts.
|
||||
- **Type**: Float
|
||||
- **max_ladeleistung_w**: Max charging power of the EV in Watts.
|
||||
- **Type**: Integer
|
||||
- **soc_wh**: The state of charge of the battery in watt-hours at the start of the simulation.
|
||||
- **Type**: Integer
|
||||
- **start_soc_prozent**: The state of charge of the battery in percentage at the start of the simulation.
|
||||
- **Type**: Integer
|
||||
|
||||
### eautocharge_hours_float
|
||||
|
||||
An array of binary values (0 or 1) that indicates whether the EV will be charged in a certain hour.
|
||||
|
||||
- **Type**: Array
|
||||
- **Element Type**: Integer (0 or 1)
|
||||
- **Length**: 48
|
||||
|
||||
### result
|
||||
|
||||
This object contains the results of the simulation and provides insights into various parameters over the entire forecast period:
|
||||
|
||||
- **E-Auto_SoC_pro_Stunde**: The state of charge of the EV for each hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Eigenverbrauch_Wh_pro_Stunde**: The self-consumption of the system in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Einnahmen_Euro_pro_Stunde**: The revenue from grid feed-in or other sources in euros per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Gesamt_Verluste**: The total losses in watt-hours over the entire period.
|
||||
- **Type**: Float
|
||||
- **Gesamtbilanz_Euro**: The total balance of revenues minus costs in euros.
|
||||
- **Type**: Float
|
||||
- **Gesamteinnahmen_Euro**: The total revenues in euros.
|
||||
- **Type**: Float
|
||||
- **Gesamtkosten_Euro**: The total costs in euros.
|
||||
- **Type**: Float
|
||||
- **Haushaltsgeraet_wh_pro_stunde**: The energy consumption of a household appliance in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Kosten_Euro_pro_Stunde**: The costs in euros per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Netzbezug_Wh_pro_Stunde**: The grid energy drawn in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Netzeinspeisung_Wh_pro_Stunde**: The energy fed into the grid in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Verluste_Pro_Stunde**: The losses in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **akku_soc_pro_stunde**: The state of charge of the battery (not the EV) in percentage per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
|
||||
### simulation_data
|
||||
|
||||
An object containing the simulated data.
|
||||
|
||||
- **E-Auto_SoC_pro_Stunde**: An array of floats representing the simulated state of charge of the electric car per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Eigenverbrauch_Wh_pro_Stunde**: An array of floats representing the simulated self-consumption in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Einnahmen_Euro_pro_Stunde**: An array of floats representing the simulated income in euros per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Gesamt_Verluste**: The total simulated losses in watt-hours.
|
||||
- **Type**: Float
|
||||
- **Gesamtbilanz_Euro**: The total simulated balance in euros.
|
||||
- **Type**: Float
|
||||
- **Gesamteinnahmen_Euro**: The total simulated income in euros.
|
||||
- **Type**: Float
|
||||
- **Gesamtkosten_Euro**: The total simulated costs in euros.
|
||||
- **Type**: Float
|
||||
- **Haushaltsgeraet_wh_pro_stunde**: An array of floats representing the simulated energy consumption of a household appliance in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Kosten_Euro_pro_Stunde**: An array of floats representing the simulated costs in euros per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Netzbezug_Wh_pro_Stunde**: An array of floats representing the simulated grid consumption in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Netzeinspeisung_Wh_pro_Stunde**: An array of floats representing the simulated grid feed-in in watt-hours per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **Verluste_Pro_Stunde**: An array of floats representing the simulated losses per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
- **akku_soc_pro_stunde**: An array of floats representing the simulated state of charge of the battery in percentage per hour.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Float
|
||||
- **Length**: 35
|
||||
|
||||
### spuelstart
|
||||
|
||||
- **Description**: Can be `null` or contain an object representing the start of washing (if applicable).
|
||||
- **Type**: null or object
|
||||
|
||||
### start_solution
|
||||
|
||||
- **Description**: An array of binary values (0 or 1) representing a possible starting solution for the simulation.
|
||||
- **Type**: Array
|
||||
- **Element Type**: Integer (0 or 1)
|
||||
- **Length**: 48
|
||||
See the Swagger documentation for detailed information: [EOS OpenAPI Spec](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/docs/akkudoktoreos/openapi.json)
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
networks:
|
||||
eos:
|
||||
default:
|
||||
name: "eos"
|
||||
services:
|
||||
eos:
|
||||
@ -11,11 +11,6 @@ services:
|
||||
dockerfile: "Dockerfile"
|
||||
args:
|
||||
PYTHON_VERSION: "${PYTHON_VERSION}"
|
||||
init: true
|
||||
environment:
|
||||
FLASK_RUN_PORT: "${EOS_PORT}"
|
||||
networks:
|
||||
- "eos"
|
||||
volumes:
|
||||
- ./src/akkudoktoreos/default.config.json:/opt/eos/EOS.config.json:ro
|
||||
ports:
|
||||
|
@ -13,4 +13,3 @@ EOS API
|
||||
:recursive:
|
||||
|
||||
akkudoktoreos
|
||||
akkudoktoreosserver
|
||||
|
@ -31,7 +31,7 @@
|
||||
"/gesamtlast": {
|
||||
"post": {
|
||||
"summary": "Fastapi Gesamtlast",
|
||||
"description": "Endpoint to handle total load calculation based on the latest measured data",
|
||||
"description": "Endpoint to handle total load calculation based on the latest measured data.",
|
||||
"operationId": "fastapi_gesamtlast_gesamtlast_post",
|
||||
"parameters": [
|
||||
{
|
||||
@ -260,8 +260,10 @@
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
"application/pdf": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,7 +401,7 @@
|
||||
"start_soc_prozent"
|
||||
],
|
||||
"title": "EAutoResult",
|
||||
"description": "\"This object contains information related to the electric vehicle and its charging and discharging behavior"
|
||||
"description": "This object contains information related to the electric vehicle and its charging and discharging behavior."
|
||||
},
|
||||
"EnergieManagementSystemParameters": {
|
||||
"properties": {
|
||||
@ -420,9 +422,19 @@
|
||||
"description": "An array of floats representing the electricity price in euros per watt-hour for different time intervals."
|
||||
},
|
||||
"einspeiseverguetung_euro_pro_wh": {
|
||||
"type": "number",
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"title": "Einspeiseverguetung Euro Pro Wh",
|
||||
"description": "A float representing the feed-in compensation in euros per watt-hour."
|
||||
"description": "A float or array of floats representing the feed-in compensation in euros per watt-hour."
|
||||
},
|
||||
"preis_euro_pro_wh_akku": {
|
||||
"type": "number",
|
||||
@ -567,21 +579,29 @@
|
||||
},
|
||||
"OptimizeResponse": {
|
||||
"properties": {
|
||||
"discharge_hours_bin": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Discharge Hours Bin",
|
||||
"description": "An array that indicates for each hour of the forecast period whether energy is discharged from the battery or not. The values are either `0` (no discharge) or `1` (discharge)."
|
||||
},
|
||||
"eautocharge_hours_float": {
|
||||
"ac_charge": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Eautocharge Hours Float",
|
||||
"description": "An array of binary values (0 or 1) that indicates whether the EV will be charged in a certain hour."
|
||||
"title": "Ac Charge",
|
||||
"description": "Array with AC charging values as relative power (0-1), other values set to 0."
|
||||
},
|
||||
"dc_charge": {
|
||||
"items": {
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Dc Charge",
|
||||
"description": "Array with DC charging values as relative power (0-1), other values set to 0."
|
||||
},
|
||||
"discharge_allowed": {
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Discharge Allowed",
|
||||
"description": "Array with discharge values (1 for discharge, 0 otherwise)."
|
||||
},
|
||||
"result": {
|
||||
"$ref": "#/components/schemas/SimulationResult"
|
||||
@ -619,8 +639,9 @@
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"discharge_hours_bin",
|
||||
"eautocharge_hours_float",
|
||||
"ac_charge",
|
||||
"dc_charge",
|
||||
"discharge_allowed",
|
||||
"result",
|
||||
"eauto_obj"
|
||||
],
|
||||
@ -869,7 +890,7 @@
|
||||
"akku_soc_pro_stunde"
|
||||
],
|
||||
"title": "SimulationResult",
|
||||
"description": "This object contains the results of the simulation and provides insights into various parameters over the entire forecast period"
|
||||
"description": "This object contains the results of the simulation and provides insights into various parameters over the entire forecast period."
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
@ -14,7 +14,7 @@ welcome.md
|
||||
akkudoktoreos/about.md
|
||||
develop/getting_started.md
|
||||
develop/CONTRIBUTING.md
|
||||
akkudoktoreosserver/serverapi.rst
|
||||
akkudoktoreos/serverapi.rst
|
||||
akkudoktoreos/api.rst
|
||||
```
|
||||
|
||||
|
@ -7,7 +7,7 @@ authors = [
|
||||
description = "This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period."
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.9"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python :: 3",
|
||||
@ -28,11 +28,10 @@ optional-dependencies = {dev = { file = ["requirements-dev.txt"] }}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src/"]
|
||||
include = ["akkudoktoreos", "akkudoktoreosserver", ]
|
||||
include = ["akkudoktoreos"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
akkudoktoreos = ["*.json", ]
|
||||
akkudoktoreosserver = ["data/*.npz", ]
|
||||
akkudoktoreos = ["*.json", "data/*.npz", ]
|
||||
|
||||
[tool.pyright]
|
||||
# used in Pylance extension for language server
|
||||
|
@ -1,6 +1,8 @@
|
||||
numpy==2.1.3
|
||||
matplotlib==3.9.2
|
||||
flask==3.0.3
|
||||
fastapi[standard]==0.115.0
|
||||
uvicorn==0.31.1
|
||||
pydantic==2.9.2
|
||||
scikit-learn==1.5.2
|
||||
timezonefinder==6.5.4
|
||||
deap==1.4.1
|
||||
|
@ -5,7 +5,11 @@ import time
|
||||
import numpy as np
|
||||
|
||||
from akkudoktoreos.class_numpy_encoder import NumpyEncoder
|
||||
from akkudoktoreos.class_optimize import optimization_problem
|
||||
from akkudoktoreos.class_optimize import (
|
||||
OptimizationParameters,
|
||||
OptimizeResponse,
|
||||
optimization_problem,
|
||||
)
|
||||
from akkudoktoreos.config import get_working_dir, load_config
|
||||
from akkudoktoreos.visualize import visualisiere_ergebnisse
|
||||
|
||||
@ -229,48 +233,52 @@ gesamtlast = [
|
||||
start_solution = None
|
||||
|
||||
# Define parameters for the optimization problem
|
||||
parameter = {
|
||||
# Value of energy in battery (per Wh)
|
||||
"preis_euro_pro_wh_akku": 0e-05,
|
||||
# Initial state of charge (SOC) of PV battery (%)
|
||||
"pv_soc": 15,
|
||||
# Battery capacity (in Wh)
|
||||
"pv_akku_cap": 26400,
|
||||
# Yearly energy consumption (in Wh)
|
||||
"year_energy": 4100000,
|
||||
# Feed-in tariff for exporting electricity (per Wh)
|
||||
"einspeiseverguetung_euro_pro_wh": 7e-05,
|
||||
# Maximum heating power (in W)
|
||||
"max_heizleistung": 1000,
|
||||
# Overall load on the system
|
||||
"gesamtlast": gesamtlast,
|
||||
# PV generation forecast (48 hours)
|
||||
"pv_forecast": pv_forecast,
|
||||
# Temperature forecast (48 hours)
|
||||
"temperature_forecast": temperature_forecast,
|
||||
# Electricity price forecast (48 hours)
|
||||
"strompreis_euro_pro_wh": strompreis_euro_pro_wh,
|
||||
# Minimum SOC for electric car
|
||||
"eauto_min_soc": 50,
|
||||
# Electric car battery capacity (Wh)
|
||||
"eauto_cap": 60000,
|
||||
# Charging efficiency of the electric car
|
||||
"eauto_charge_efficiency": 0.95,
|
||||
# Charging power of the electric car (W)
|
||||
"eauto_charge_power": 11040,
|
||||
# Current SOC of the electric car (%)
|
||||
"eauto_soc": 15,
|
||||
# Current PV power generation (W)
|
||||
"pvpowernow": 211.137503624,
|
||||
# Initial solution for the optimization
|
||||
"start_solution": start_solution,
|
||||
# Household appliance consumption (Wh)
|
||||
"haushaltsgeraet_wh": 5000,
|
||||
# Duration of appliance usage (hours)
|
||||
"haushaltsgeraet_dauer": 0,
|
||||
# Minimum Soc PV Battery
|
||||
"min_soc_prozent": 15,
|
||||
}
|
||||
parameters = OptimizationParameters(
|
||||
**{
|
||||
"ems": {
|
||||
# Value of energy in battery (per Wh)
|
||||
"preis_euro_pro_wh_akku": 0e-05,
|
||||
# Feed-in tariff for exporting electricity (per Wh)
|
||||
"einspeiseverguetung_euro_pro_wh": 7e-05,
|
||||
# Overall load on the system
|
||||
"gesamtlast": gesamtlast,
|
||||
# PV generation forecast (48 hours)
|
||||
"pv_prognose_wh": pv_forecast,
|
||||
# Electricity price forecast (48 hours)
|
||||
"strompreis_euro_pro_wh": strompreis_euro_pro_wh,
|
||||
},
|
||||
"pv_akku": {
|
||||
# Battery capacity (in Wh)
|
||||
"kapazitaet_wh": 26400,
|
||||
# Initial state of charge (SOC) of PV battery (%)
|
||||
"start_soc_prozent": 15,
|
||||
# Minimum Soc PV Battery
|
||||
"min_soc_prozent": 15,
|
||||
},
|
||||
"eauto": {
|
||||
# Minimum SOC for electric car
|
||||
"min_soc_prozent": 50,
|
||||
# Electric car battery capacity (Wh)
|
||||
"kapazitaet_wh": 60000,
|
||||
# Charging efficiency of the electric car
|
||||
"lade_effizienz": 0.95,
|
||||
# Charging power of the electric car (W)
|
||||
"max_ladeleistung_w": 11040,
|
||||
# Current SOC of the electric car (%)
|
||||
"start_soc_prozent": 5,
|
||||
},
|
||||
# "spuelmaschine": {
|
||||
# # Household appliance consumption (Wh)
|
||||
# "verbrauch_wh": 5000,
|
||||
# # Duration of appliance usage (hours)
|
||||
# "dauer_h": 0,
|
||||
# },
|
||||
# Temperature forecast (48 hours)
|
||||
"temperature_forecast": temperature_forecast,
|
||||
# Initial solution for the optimization
|
||||
"start_solution": start_solution,
|
||||
}
|
||||
)
|
||||
|
||||
# Startzeit nehmen
|
||||
start_time = time.time()
|
||||
@ -281,7 +289,7 @@ config = load_config(working_dir)
|
||||
opt_class = optimization_problem(config, verbose=True, fixed_seed=42)
|
||||
|
||||
# Perform the optimisation based on the provided parameters and start hour
|
||||
ergebnis = opt_class.optimierung_ems(parameter=parameter, start_hour=start_hour)
|
||||
ergebnis = opt_class.optimierung_ems(parameters=parameters, start_hour=start_hour)
|
||||
|
||||
# Endzeit nehmen
|
||||
end_time = time.time()
|
||||
@ -298,23 +306,23 @@ ac_charge, dc_charge, discharge = (
|
||||
)
|
||||
|
||||
visualisiere_ergebnisse(
|
||||
gesamtlast=gesamtlast,
|
||||
pv_forecast=pv_forecast,
|
||||
strompreise=strompreis_euro_pro_wh,
|
||||
ergebnisse=ergebnis["result"],
|
||||
ac=ac_charge,
|
||||
dc=dc_charge,
|
||||
discharge=discharge,
|
||||
temperature=temperature_forecast,
|
||||
start_hour=start_hour,
|
||||
parameters.ems.gesamtlast,
|
||||
parameters.ems.pv_prognose_wh,
|
||||
parameters.ems.strompreis_euro_pro_wh,
|
||||
ergebnis["result"],
|
||||
ac_charge,
|
||||
dc_charge,
|
||||
discharge,
|
||||
parameters.temperature_forecast,
|
||||
start_hour,
|
||||
einspeiseverguetung_euro_pro_wh=np.full(
|
||||
config.eos.feed_in_tariff_eur_per_wh, parameter["einspeiseverguetung_euro_pro_wh"]
|
||||
config.eos.feed_in_tariff_eur_per_wh, parameters.ems.einspeiseverguetung_euro_pro_wh
|
||||
),
|
||||
config=config,
|
||||
filename="visualization_results.pdf",
|
||||
extra_data=None,
|
||||
)
|
||||
|
||||
|
||||
json_data = NumpyEncoder.dumps(ergebnis)
|
||||
print(json_data)
|
||||
|
||||
OptimizeResponse(**ergebnis)
|
||||
|
@ -1,32 +1,74 @@
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def max_ladeleistung_w_field(default=None):
|
||||
return Field(
|
||||
default,
|
||||
gt=0,
|
||||
description="An integer representing the charging power of the battery in watts.",
|
||||
)
|
||||
|
||||
|
||||
def start_soc_prozent_field(description: str):
|
||||
return Field(0, ge=0, le=100, description=description)
|
||||
|
||||
|
||||
class BaseAkkuParameters(BaseModel):
|
||||
kapazitaet_wh: int = Field(
|
||||
gt=0, description="An integer representing the capacity of the battery in watt-hours."
|
||||
)
|
||||
lade_effizienz: float = Field(
|
||||
0.88, gt=0, le=1, description="A float representing the charging efficiency of the battery."
|
||||
)
|
||||
entlade_effizienz: float = Field(0.88, gt=0, le=1)
|
||||
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field()
|
||||
start_soc_prozent: int = start_soc_prozent_field(
|
||||
"An integer representing the state of charge of the battery at the **start** of the current hour (not the current state)."
|
||||
)
|
||||
min_soc_prozent: int = Field(
|
||||
0,
|
||||
ge=0,
|
||||
le=100,
|
||||
description="An integer representing the minimum state of charge (SOC) of the battery in percentage.",
|
||||
)
|
||||
max_soc_prozent: int = Field(100, ge=0, le=100)
|
||||
|
||||
|
||||
class PVAkkuParameters(BaseAkkuParameters):
|
||||
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field(5000)
|
||||
|
||||
|
||||
class EAutoParameters(BaseAkkuParameters):
|
||||
entlade_effizienz: float = 1.0
|
||||
start_soc_prozent: int = start_soc_prozent_field(
|
||||
"An integer representing the current state of charge (SOC) of the battery in percentage."
|
||||
)
|
||||
|
||||
|
||||
class PVAkku:
|
||||
def __init__(
|
||||
self,
|
||||
kapazitaet_wh=None,
|
||||
hours=None,
|
||||
lade_effizienz=0.88,
|
||||
entlade_effizienz=0.88,
|
||||
max_ladeleistung_w=None,
|
||||
start_soc_prozent=0,
|
||||
min_soc_prozent=0,
|
||||
max_soc_prozent=100,
|
||||
):
|
||||
def __init__(self, parameters: BaseAkkuParameters, hours: int = 24):
|
||||
# Battery capacity in Wh
|
||||
self.kapazitaet_wh = kapazitaet_wh
|
||||
self.kapazitaet_wh = parameters.kapazitaet_wh
|
||||
# Initial state of charge in Wh
|
||||
self.start_soc_prozent = start_soc_prozent
|
||||
self.soc_wh = (start_soc_prozent / 100) * kapazitaet_wh
|
||||
self.hours = hours if hours is not None else 24 # Default to 24 hours if not specified
|
||||
self.start_soc_prozent = parameters.start_soc_prozent
|
||||
self.soc_wh = (parameters.start_soc_prozent / 100) * parameters.kapazitaet_wh
|
||||
self.hours = hours
|
||||
self.discharge_array = np.full(self.hours, 1)
|
||||
self.charge_array = np.full(self.hours, 1)
|
||||
# Charge and discharge efficiency
|
||||
self.lade_effizienz = lade_effizienz
|
||||
self.entlade_effizienz = entlade_effizienz
|
||||
self.max_ladeleistung_w = max_ladeleistung_w if max_ladeleistung_w else self.kapazitaet_wh
|
||||
self.min_soc_prozent = min_soc_prozent
|
||||
self.max_soc_prozent = max_soc_prozent
|
||||
self.lade_effizienz = parameters.lade_effizienz
|
||||
self.entlade_effizienz = parameters.entlade_effizienz
|
||||
self.max_ladeleistung_w = (
|
||||
parameters.max_ladeleistung_w if parameters.max_ladeleistung_w else self.kapazitaet_wh
|
||||
)
|
||||
# Only assign for storage battery
|
||||
self.min_soc_prozent = (
|
||||
parameters.min_soc_prozent if isinstance(parameters, PVAkkuParameters) else 0
|
||||
)
|
||||
self.max_soc_prozent = parameters.max_soc_prozent
|
||||
# Calculate min and max SoC in Wh
|
||||
self.min_soc_wh = (self.min_soc_prozent / 100) * self.kapazitaet_wh
|
||||
self.max_soc_wh = (self.max_soc_prozent / 100) * self.kapazitaet_wh
|
||||
|
@ -2,27 +2,63 @@ from datetime import datetime
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet
|
||||
from akkudoktoreos.class_inverter import Wechselrichter
|
||||
from akkudoktoreos.config import EOSConfig
|
||||
|
||||
|
||||
class EnergieManagementSystemParameters(BaseModel):
|
||||
pv_prognose_wh: list[float] = Field(
|
||||
description="An array of floats representing the forecasted photovoltaic output in watts for different time intervals."
|
||||
)
|
||||
strompreis_euro_pro_wh: list[float] = Field(
|
||||
description="An array of floats representing the electricity price in euros per watt-hour for different time intervals."
|
||||
)
|
||||
einspeiseverguetung_euro_pro_wh: list[float] | float = Field(
|
||||
description="A float or array of floats representing the feed-in compensation in euros per watt-hour."
|
||||
)
|
||||
preis_euro_pro_wh_akku: float
|
||||
gesamtlast: list[float] = Field(
|
||||
description="An array of floats representing the total load (consumption) in watts for different time intervals."
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_list_length(self) -> Self:
|
||||
pv_prognose_length = len(self.pv_prognose_wh)
|
||||
if (
|
||||
pv_prognose_length != len(self.strompreis_euro_pro_wh)
|
||||
or pv_prognose_length != len(self.gesamtlast)
|
||||
or (
|
||||
isinstance(self.einspeiseverguetung_euro_pro_wh, list)
|
||||
and pv_prognose_length != len(self.einspeiseverguetung_euro_pro_wh)
|
||||
)
|
||||
):
|
||||
raise ValueError("Input lists have different lengths")
|
||||
return self
|
||||
|
||||
|
||||
class EnergieManagementSystem:
|
||||
def __init__(
|
||||
self,
|
||||
config: EOSConfig,
|
||||
pv_prognose_wh: Optional[np.ndarray] = None,
|
||||
strompreis_euro_pro_wh: Optional[np.ndarray] = None,
|
||||
einspeiseverguetung_euro_pro_wh: Optional[np.ndarray] = None,
|
||||
eauto: Optional[object] = None,
|
||||
gesamtlast: Optional[np.ndarray] = None,
|
||||
haushaltsgeraet: Optional[object] = None,
|
||||
wechselrichter: Optional[object] = None,
|
||||
parameters: EnergieManagementSystemParameters,
|
||||
eauto: Optional[PVAkku] = None,
|
||||
haushaltsgeraet: Optional[Haushaltsgeraet] = None,
|
||||
wechselrichter: Optional[Wechselrichter] = None,
|
||||
):
|
||||
self.akku = wechselrichter.akku
|
||||
self.gesamtlast = gesamtlast
|
||||
self.pv_prognose_wh = pv_prognose_wh
|
||||
self.strompreis_euro_pro_wh = strompreis_euro_pro_wh
|
||||
self.einspeiseverguetung_euro_pro_wh = einspeiseverguetung_euro_pro_wh
|
||||
self.gesamtlast = np.array(parameters.gesamtlast, float)
|
||||
self.pv_prognose_wh = np.array(parameters.pv_prognose_wh, float)
|
||||
self.strompreis_euro_pro_wh = np.array(parameters.strompreis_euro_pro_wh, float)
|
||||
self.einspeiseverguetung_euro_pro_wh_arr = (
|
||||
parameters.einspeiseverguetung_euro_pro_wh
|
||||
if isinstance(parameters.einspeiseverguetung_euro_pro_wh, list)
|
||||
else np.full(len(self.gesamtlast), parameters.einspeiseverguetung_euro_pro_wh, float)
|
||||
)
|
||||
self.eauto = eauto
|
||||
self.haushaltsgeraet = haushaltsgeraet
|
||||
self.wechselrichter = wechselrichter
|
||||
@ -134,7 +170,7 @@ class EnergieManagementSystem:
|
||||
netzbezug * self.strompreis_euro_pro_wh[stunde]
|
||||
)
|
||||
einnahmen_euro_pro_stunde[stunde_since_now] = (
|
||||
netzeinspeisung * self.einspeiseverguetung_euro_pro_wh[stunde]
|
||||
netzeinspeisung * self.einspeiseverguetung_euro_pro_wh_arr[stunde]
|
||||
)
|
||||
|
||||
# Akku SOC tracking
|
||||
@ -152,7 +188,7 @@ class EnergieManagementSystem:
|
||||
"akku_soc_pro_stunde": akku_soc_pro_stunde,
|
||||
"Einnahmen_Euro_pro_Stunde": einnahmen_euro_pro_stunde,
|
||||
"Gesamtbilanz_Euro": gesamtkosten_euro,
|
||||
"E-Auto_SoC_pro_Stunde": eauto_soc_pro_stunde,
|
||||
"EAuto_SoC_pro_Stunde": eauto_soc_pro_stunde,
|
||||
"Gesamteinnahmen_Euro": np.nansum(einnahmen_euro_pro_stunde),
|
||||
"Gesamtkosten_Euro": np.nansum(kosten_euro_pro_stunde),
|
||||
"Verluste_Pro_Stunde": verluste_wh_pro_stunde,
|
||||
|
@ -1,11 +1,25 @@
|
||||
import numpy as np
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class HaushaltsgeraetParameters(BaseModel):
|
||||
verbrauch_wh: int = Field(
|
||||
gt=0,
|
||||
description="An integer representing the energy consumption of a household device in watt-hours.",
|
||||
)
|
||||
dauer_h: int = Field(
|
||||
gt=0,
|
||||
description="An integer representing the usage duration of a household device in hours.",
|
||||
)
|
||||
|
||||
|
||||
class Haushaltsgeraet:
|
||||
def __init__(self, hours=None, verbrauch_wh=None, dauer_h=None):
|
||||
def __init__(self, parameters: HaushaltsgeraetParameters, hours=24):
|
||||
self.hours = hours # Total duration for which the planning is done
|
||||
self.verbrauch_wh = verbrauch_wh # Total energy consumption of the device in kWh
|
||||
self.dauer_h = dauer_h # Duration of use in hours
|
||||
self.verbrauch_wh = (
|
||||
parameters.verbrauch_wh # Total energy consumption of the device in kWh
|
||||
)
|
||||
self.dauer_h = parameters.dauer_h # Duration of use in hours
|
||||
self.lastkurve = np.zeros(self.hours) # Initialize the load curve with zeros
|
||||
|
||||
def set_startzeitpunkt(self, start_hour, global_start_hour=0):
|
||||
|
@ -1,6 +1,17 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
|
||||
|
||||
class WechselrichterParameters(BaseModel):
|
||||
max_leistung_wh: float = Field(10000, gt=0)
|
||||
|
||||
|
||||
class Wechselrichter:
|
||||
def __init__(self, max_leistung_wh, akku):
|
||||
self.max_leistung_wh = max_leistung_wh # Maximum power that the inverter can handle
|
||||
def __init__(self, parameters: WechselrichterParameters, akku: PVAkku):
|
||||
self.max_leistung_wh = (
|
||||
parameters.max_leistung_wh # Maximum power that the inverter can handle
|
||||
)
|
||||
self.akku = akku # Connection to a battery object
|
||||
|
||||
def energie_verarbeiten(self, erzeugung, verbrauch, hour):
|
||||
|
@ -1,17 +1,165 @@
|
||||
import random
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from deap import algorithms, base, creator, tools
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from typing_extensions import Self
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
from akkudoktoreos.class_ems import EnergieManagementSystem
|
||||
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet
|
||||
from akkudoktoreos.class_inverter import Wechselrichter
|
||||
from akkudoktoreos.class_akku import EAutoParameters, PVAkku, PVAkkuParameters
|
||||
from akkudoktoreos.class_ems import (
|
||||
EnergieManagementSystem,
|
||||
EnergieManagementSystemParameters,
|
||||
)
|
||||
from akkudoktoreos.class_haushaltsgeraet import (
|
||||
Haushaltsgeraet,
|
||||
HaushaltsgeraetParameters,
|
||||
)
|
||||
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
|
||||
from akkudoktoreos.config import AppConfig
|
||||
from akkudoktoreos.visualize import visualisiere_ergebnisse
|
||||
|
||||
|
||||
class OptimizationParameters(BaseModel):
|
||||
ems: EnergieManagementSystemParameters
|
||||
pv_akku: PVAkkuParameters
|
||||
wechselrichter: WechselrichterParameters = WechselrichterParameters()
|
||||
eauto: EAutoParameters
|
||||
spuelmaschine: Optional[HaushaltsgeraetParameters] = None
|
||||
temperature_forecast: list[float] = Field(
|
||||
"An array of floats representing the temperature forecast in degrees Celsius for different time intervals."
|
||||
)
|
||||
start_solution: Optional[list[float]] = Field(
|
||||
None, description="Can be `null` or contain a previous solution (if available)."
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_list_length(self) -> Self:
|
||||
arr_length = len(self.ems.pv_prognose_wh)
|
||||
if arr_length != len(self.temperature_forecast):
|
||||
raise ValueError("Input lists have different lenghts")
|
||||
return self
|
||||
|
||||
|
||||
class EAutoResult(BaseModel):
|
||||
"""This object contains information related to the electric vehicle and its charging and discharging behavior."""
|
||||
|
||||
charge_array: list[float] = Field(
|
||||
description="Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging)."
|
||||
)
|
||||
discharge_array: list[int] = Field(
|
||||
description="Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging)."
|
||||
)
|
||||
entlade_effizienz: float = Field(description="The discharge efficiency as a float.")
|
||||
hours: int = Field("Amount of hours the simulation is done for.")
|
||||
kapazitaet_wh: int = Field("The capacity of the EV’s battery in watt-hours.")
|
||||
lade_effizienz: float = Field("The charging efficiency as a float.")
|
||||
max_ladeleistung_w: int = Field(description="The maximum charging power of the EV in watts.")
|
||||
soc_wh: float = Field(
|
||||
description="The state of charge of the battery in watt-hours at the start of the simulation."
|
||||
)
|
||||
start_soc_prozent: int = Field(
|
||||
description="The state of charge of the battery in percentage at the start of the simulation."
|
||||
)
|
||||
|
||||
|
||||
class SimulationResult(BaseModel):
|
||||
"""This object contains the results of the simulation and provides insights into various parameters over the entire forecast period."""
|
||||
|
||||
Last_Wh_pro_Stunde: list[Optional[float]] = Field(description="TBD")
|
||||
EAuto_SoC_pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The state of charge of the EV for each hour."
|
||||
)
|
||||
Einnahmen_Euro_pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The revenue from grid feed-in or other sources in euros per hour."
|
||||
)
|
||||
Gesamt_Verluste: float = Field(
|
||||
description="The total losses in watt-hours over the entire period."
|
||||
)
|
||||
Gesamtbilanz_Euro: float = Field(
|
||||
description="The total balance of revenues minus costs in euros."
|
||||
)
|
||||
Gesamteinnahmen_Euro: float = Field(description="The total revenues in euros.")
|
||||
Gesamtkosten_Euro: float = Field(description="The total costs in euros.")
|
||||
Haushaltsgeraet_wh_pro_stunde: list[Optional[float]] = Field(
|
||||
description="The energy consumption of a household appliance in watt-hours per hour."
|
||||
)
|
||||
Kosten_Euro_pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The costs in euros per hour."
|
||||
)
|
||||
Netzbezug_Wh_pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The grid energy drawn in watt-hours per hour."
|
||||
)
|
||||
Netzeinspeisung_Wh_pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The energy fed into the grid in watt-hours per hour."
|
||||
)
|
||||
Verluste_Pro_Stunde: list[Optional[float]] = Field(
|
||||
description="The losses in watt-hours per hour."
|
||||
)
|
||||
akku_soc_pro_stunde: list[Optional[float]] = Field(
|
||||
description="The state of charge of the battery (not the EV) in percentage per hour."
|
||||
)
|
||||
|
||||
|
||||
# class SimulationData(BaseModel):
|
||||
# """An object containing the simulated data."""
|
||||
#
|
||||
# Last_Wh_pro_Stunde: list[Optional[float]] = Field(description="TBD")
|
||||
# EAuto_SoC_pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated state of charge of the electric car per hour.",
|
||||
# )
|
||||
# Einnahmen_Euro_pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated income in euros per hour."
|
||||
# )
|
||||
# Gesamt_Verluste: float = Field(description="The total simulated losses in watt-hours.")
|
||||
# Gesamtbilanz_Euro: float = Field(description="The total simulated balance in euros.")
|
||||
# Gesamteinnahmen_Euro: float = Field(description="The total simulated income in euros.")
|
||||
# Gesamtkosten_Euro: float = Field(description="The total simulated costs in euros.")
|
||||
# Haushaltsgeraet_wh_pro_stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated energy consumption of a household appliance in watt-hours per hour."
|
||||
# )
|
||||
# Kosten_Euro_pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated costs in euros per hour."
|
||||
# )
|
||||
# Netzbezug_Wh_pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated grid consumption in watt-hours per hour."
|
||||
# )
|
||||
# Netzeinspeisung_Wh_pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated grid feed-in in watt-hours per hour."
|
||||
# )
|
||||
# Verluste_Pro_Stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated losses per hour."
|
||||
# )
|
||||
# akku_soc_pro_stunde: list[Optional[float]] = Field(
|
||||
# description="An array of floats representing the simulated state of charge of the battery in percentage per hour."
|
||||
# )
|
||||
|
||||
|
||||
class OptimizeResponse(BaseModel):
|
||||
"""**Note**: The first value of "Last_Wh_pro_Stunde", "Netzeinspeisung_Wh_pro_Stunde" and "Netzbezug_Wh_pro_Stunde", will be set to null in the JSON output and represented as NaN or None in the corresponding classes' data returns. This approach is adopted to ensure that the current hour's processing remains unchanged."""
|
||||
|
||||
ac_charge: list[float] = Field(
|
||||
description="Array with AC charging values as relative power (0-1), other values set to 0."
|
||||
)
|
||||
dc_charge: list[float] = Field(
|
||||
description="Array with DC charging values as relative power (0-1), other values set to 0."
|
||||
)
|
||||
discharge_allowed: list[int] = Field(
|
||||
description="Array with discharge values (1 for discharge, 0 otherwise)."
|
||||
)
|
||||
result: SimulationResult
|
||||
eauto_obj: EAutoResult
|
||||
start_solution: Optional[list[float]] = Field(
|
||||
None,
|
||||
description="An array of binary values (0 or 1) representing a possible starting solution for the simulation.",
|
||||
)
|
||||
spuelstart: Optional[int] = Field(
|
||||
None,
|
||||
description="Can be `null` or contain an object representing the start of washing (if applicable).",
|
||||
)
|
||||
# simulation_data: Optional[SimulationData] = None
|
||||
|
||||
|
||||
class optimization_problem:
|
||||
def __init__(
|
||||
self,
|
||||
@ -164,8 +312,8 @@ class optimization_problem:
|
||||
return creator.Individual(individual_components)
|
||||
|
||||
def split_individual(
|
||||
self, individual: List[float]
|
||||
) -> Tuple[List[int], List[float], Optional[int]]:
|
||||
self, individual: list[float]
|
||||
) -> Tuple[list[int], list[float], Optional[int]]:
|
||||
"""Split the individual solution into its components.
|
||||
|
||||
Components:
|
||||
@ -187,7 +335,7 @@ class optimization_problem:
|
||||
)
|
||||
return discharge_hours_bin, eautocharge_hours_float, spuelstart_int
|
||||
|
||||
def setup_deap_environment(self, opti_param: Dict[str, Any], start_hour: int) -> None:
|
||||
def setup_deap_environment(self, opti_param: dict[str, Any], start_hour: int) -> None:
|
||||
"""Set up the DEAP environment with fitness and individual creation rules."""
|
||||
self.opti_param = opti_param
|
||||
|
||||
@ -250,8 +398,8 @@ class optimization_problem:
|
||||
self.toolbox.register("select", tools.selTournament, tournsize=3)
|
||||
|
||||
def evaluate_inner(
|
||||
self, individual: List[float], ems: EnergieManagementSystem, start_hour: int
|
||||
) -> Dict[str, Any]:
|
||||
self, individual: list[float], ems: EnergieManagementSystem, start_hour: int
|
||||
) -> dict[str, Any]:
|
||||
"""Simulates the energy management system (EMS) using the provided individual solution.
|
||||
|
||||
This is an internal function.
|
||||
@ -283,9 +431,9 @@ class optimization_problem:
|
||||
|
||||
def evaluate(
|
||||
self,
|
||||
individual: List[float],
|
||||
individual: list[float],
|
||||
ems: EnergieManagementSystem,
|
||||
parameter: Dict[str, Any],
|
||||
parameters: OptimizationParameters,
|
||||
start_hour: int,
|
||||
worst_case: bool,
|
||||
) -> Tuple[float]:
|
||||
@ -305,7 +453,7 @@ class optimization_problem:
|
||||
)
|
||||
|
||||
# Penalty for not meeting the minimum SOC (State of Charge) requirement
|
||||
# if parameter["eauto_min_soc"] - ems.eauto.ladezustand_in_prozent() <= 0.0 and self.optimize_ev:
|
||||
# if parameters.eauto_min_soc_prozent - ems.eauto.ladezustand_in_prozent() <= 0.0 and self.optimize_ev:
|
||||
# gesamtbilanz += sum(
|
||||
# self.strafe for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0
|
||||
# )
|
||||
@ -313,26 +461,27 @@ class optimization_problem:
|
||||
individual.extra_data = (
|
||||
o["Gesamtbilanz_Euro"],
|
||||
o["Gesamt_Verluste"],
|
||||
parameter["eauto_min_soc"] - ems.eauto.ladezustand_in_prozent(),
|
||||
parameters.eauto.min_soc_prozent - ems.eauto.ladezustand_in_prozent(),
|
||||
)
|
||||
|
||||
# Adjust total balance with battery value and penalties for unmet SOC
|
||||
|
||||
restwert_akku = ems.akku.aktueller_energieinhalt() * parameter["preis_euro_pro_wh_akku"]
|
||||
# print(ems.akku.aktueller_energieinhalt()," * ", parameter["preis_euro_pro_wh_akku"] , " ", restwert_akku, " ", gesamtbilanz)
|
||||
restwert_akku = ems.akku.aktueller_energieinhalt() * parameters.ems.preis_euro_pro_wh_akku
|
||||
# print(ems.akku.aktueller_energieinhalt()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
|
||||
gesamtbilanz += -restwert_akku
|
||||
# print(gesamtbilanz)
|
||||
if self.optimize_ev:
|
||||
gesamtbilanz += max(
|
||||
0,
|
||||
(parameter["eauto_min_soc"] - ems.eauto.ladezustand_in_prozent()) * self.strafe,
|
||||
(parameters.eauto.min_soc_prozent - ems.eauto.ladezustand_in_prozent())
|
||||
* self.strafe,
|
||||
)
|
||||
|
||||
return (gesamtbilanz,)
|
||||
|
||||
def optimize(
|
||||
self, start_solution: Optional[List[float]] = None, ngen: int = 400
|
||||
) -> Tuple[Any, Dict[str, List[Any]]]:
|
||||
self, start_solution: Optional[list[float]] = None, ngen: int = 400
|
||||
) -> Tuple[Any, dict[str, list[Any]]]:
|
||||
"""Run the optimization process using a genetic algorithm."""
|
||||
population = self.toolbox.population(n=300)
|
||||
hof = tools.HallOfFame(1)
|
||||
@ -373,61 +522,50 @@ class optimization_problem:
|
||||
|
||||
def optimierung_ems(
|
||||
self,
|
||||
parameter: Optional[Dict[str, Any]] = None,
|
||||
parameters: OptimizationParameters,
|
||||
start_hour: Optional[int] = None,
|
||||
worst_case: bool = False,
|
||||
startdate: Optional[Any] = None, # startdate is not used!
|
||||
*,
|
||||
ngen: int = 600,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Perform EMS (Energy Management System) optimization and visualize results."""
|
||||
einspeiseverguetung_euro_pro_wh = np.full(
|
||||
self.prediction_hours, parameter["einspeiseverguetung_euro_pro_wh"]
|
||||
self.prediction_hours, parameters.ems.einspeiseverguetung_euro_pro_wh
|
||||
)
|
||||
|
||||
# Initialize PV and EV batteries
|
||||
akku = PVAkku(
|
||||
kapazitaet_wh=parameter["pv_akku_cap"],
|
||||
parameters.pv_akku,
|
||||
hours=self.prediction_hours,
|
||||
start_soc_prozent=parameter["pv_soc"],
|
||||
min_soc_prozent=parameter["min_soc_prozent"],
|
||||
max_ladeleistung_w=5000,
|
||||
)
|
||||
akku.set_charge_per_hour(np.full(self.prediction_hours, 1))
|
||||
|
||||
self.optimize_ev = True
|
||||
if parameter["eauto_min_soc"] - parameter["eauto_soc"] < 0:
|
||||
if parameters.eauto.min_soc_prozent - parameters.eauto.start_soc_prozent < 0:
|
||||
self.optimize_ev = False
|
||||
|
||||
eauto = PVAkku(
|
||||
kapazitaet_wh=parameter["eauto_cap"],
|
||||
parameters.eauto,
|
||||
hours=self.prediction_hours,
|
||||
lade_effizienz=parameter["eauto_charge_efficiency"],
|
||||
entlade_effizienz=1.0,
|
||||
max_ladeleistung_w=parameter["eauto_charge_power"],
|
||||
start_soc_prozent=parameter["eauto_soc"],
|
||||
)
|
||||
eauto.set_charge_per_hour(np.full(self.prediction_hours, 1))
|
||||
|
||||
# Initialize household appliance if applicable
|
||||
spuelmaschine = (
|
||||
Haushaltsgeraet(
|
||||
parameters=parameters.spuelmaschine,
|
||||
hours=self.prediction_hours,
|
||||
verbrauch_wh=parameter["haushaltsgeraet_wh"],
|
||||
dauer_h=parameter["haushaltsgeraet_dauer"],
|
||||
)
|
||||
if parameter["haushaltsgeraet_dauer"] > 0
|
||||
if parameters.spuelmaschine is not None
|
||||
else None
|
||||
)
|
||||
|
||||
# Initialize the inverter and energy management system
|
||||
wr = Wechselrichter(10000, akku)
|
||||
wr = Wechselrichter(parameters.wechselrichter, akku)
|
||||
ems = EnergieManagementSystem(
|
||||
config=self._config.eos,
|
||||
gesamtlast=parameter["gesamtlast"],
|
||||
pv_prognose_wh=parameter["pv_forecast"],
|
||||
strompreis_euro_pro_wh=parameter["strompreis_euro_pro_wh"],
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
self._config.eos,
|
||||
parameters.ems,
|
||||
eauto=eauto,
|
||||
haushaltsgeraet=spuelmaschine,
|
||||
wechselrichter=wr,
|
||||
@ -437,9 +575,9 @@ class optimization_problem:
|
||||
self.setup_deap_environment({"haushaltsgeraete": 1 if spuelmaschine else 0}, start_hour)
|
||||
self.toolbox.register(
|
||||
"evaluate",
|
||||
lambda ind: self.evaluate(ind, ems, parameter, start_hour, worst_case),
|
||||
lambda ind: self.evaluate(ind, ems, parameters, start_hour, worst_case),
|
||||
)
|
||||
start_solution, extra_data = self.optimize(parameter["start_solution"], ngen=ngen) #
|
||||
start_solution, extra_data = self.optimize(parameters.start_solution, ngen=ngen)
|
||||
|
||||
# Perform final evaluation on the best solution
|
||||
o = self.evaluate_inner(start_solution, ems, start_hour)
|
||||
@ -455,16 +593,16 @@ class optimization_problem:
|
||||
ac_charge, dc_charge, discharge = self.decode_charge_discharge(discharge_hours_bin)
|
||||
# Visualize the results
|
||||
visualisiere_ergebnisse(
|
||||
gesamtlast=parameter["gesamtlast"],
|
||||
pv_forecast=parameter["pv_forecast"],
|
||||
strompreise=parameter["strompreis_euro_pro_wh"],
|
||||
ergebnisse=o,
|
||||
ac=ac_charge,
|
||||
dc=dc_charge,
|
||||
discharge=discharge,
|
||||
temperature=parameter["temperature_forecast"],
|
||||
start_hour=start_hour,
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
parameters.ems.gesamtlast,
|
||||
parameters.ems.pv_prognose_wh,
|
||||
parameters.ems.strompreis_euro_pro_wh,
|
||||
o,
|
||||
ac_charge,
|
||||
dc_charge,
|
||||
discharge,
|
||||
parameters.temperature_forecast,
|
||||
start_hour,
|
||||
einspeiseverguetung_euro_pro_wh,
|
||||
config=self._config,
|
||||
extra_data=extra_data,
|
||||
)
|
||||
@ -477,7 +615,7 @@ class optimization_problem:
|
||||
"Netzbezug_Wh_pro_Stunde",
|
||||
"Kosten_Euro_pro_Stunde",
|
||||
"Einnahmen_Euro_pro_Stunde",
|
||||
"E-Auto_SoC_pro_Stunde",
|
||||
"EAuto_SoC_pro_Stunde",
|
||||
"Verluste_Pro_Stunde",
|
||||
"Haushaltsgeraet_wh_pro_stunde",
|
||||
]
|
||||
@ -507,5 +645,5 @@ class optimization_problem:
|
||||
"eauto_obj": ems.eauto.to_dict(),
|
||||
"start_solution": start_solution,
|
||||
"spuelstart": spuelstart_int,
|
||||
"simulation_data": o,
|
||||
# "simulation_data": o,
|
||||
}
|
||||
|
@ -108,6 +108,11 @@ def validate_pv_forecast_data(data) -> str:
|
||||
return data_type
|
||||
|
||||
|
||||
class ForecastResponse(BaseModel):
|
||||
temperature: list[float]
|
||||
pvpower: list[float]
|
||||
|
||||
|
||||
class ForecastData:
|
||||
"""Stores forecast data for PV power and weather parameters.
|
||||
|
||||
|
0
src/akkudoktoreos/server/__init__.py
Normal file
0
src/akkudoktoreos/server/__init__.py
Normal file
252
src/akkudoktoreos/server/fastapi_server.py
Executable file
252
src/akkudoktoreos/server/fastapi_server.py
Executable file
@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated, Any, Optional
|
||||
|
||||
import matplotlib
|
||||
import uvicorn
|
||||
from fastapi.exceptions import HTTPException
|
||||
|
||||
# Sets the Matplotlib backend to 'Agg' for rendering plots in environments without a display
|
||||
matplotlib.use("Agg")
|
||||
|
||||
import pandas as pd
|
||||
from fastapi import FastAPI, Query
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
|
||||
from akkudoktoreos.class_load import LoadForecast
|
||||
from akkudoktoreos.class_load_container import Gesamtlast
|
||||
from akkudoktoreos.class_load_corrector import LoadPredictionAdjuster
|
||||
from akkudoktoreos.class_optimize import (
|
||||
OptimizationParameters,
|
||||
OptimizeResponse,
|
||||
optimization_problem,
|
||||
)
|
||||
from akkudoktoreos.class_pv_forecast import ForecastResponse, PVForecast
|
||||
from akkudoktoreos.class_strompreis import HourlyElectricityPriceForecast
|
||||
from akkudoktoreos.config import (
|
||||
SetupIncomplete,
|
||||
get_start_enddate,
|
||||
get_working_dir,
|
||||
load_config,
|
||||
)
|
||||
|
||||
app = FastAPI(
|
||||
title="Akkudoktor-EOS",
|
||||
description="This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period.",
|
||||
summary="Comprehensive solution for simulating and optimizing an energy system based on renewable energy sources",
|
||||
version="0.0.1",
|
||||
license_info={
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
},
|
||||
)
|
||||
|
||||
working_dir = get_working_dir()
|
||||
# copy config to working directory. Make this a CLI option later
|
||||
config = load_config(working_dir, True)
|
||||
opt_class = optimization_problem(config)
|
||||
server_dir = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
class PdfResponse(FileResponse):
|
||||
media_type = "application/pdf"
|
||||
|
||||
|
||||
@app.get("/strompreis")
|
||||
def fastapi_strompreis() -> list[float]:
|
||||
# Get the current date and the end date based on prediction hours
|
||||
date_now, date = get_start_enddate(config.eos.prediction_hours, startdate=datetime.now().date())
|
||||
price_forecast = HourlyElectricityPriceForecast(
|
||||
source=f"https://api.akkudoktor.net/prices?start={date_now}&end={date}",
|
||||
config=config,
|
||||
use_cache=False,
|
||||
)
|
||||
specific_date_prices = price_forecast.get_price_for_daterange(
|
||||
date_now, date
|
||||
) # Fetch prices for the specified date range
|
||||
return specific_date_prices.tolist()
|
||||
|
||||
|
||||
@app.post("/gesamtlast")
|
||||
def fastapi_gesamtlast(
|
||||
year_energy: float,
|
||||
measured_data: list[dict[str, Any]],
|
||||
hours: int = config.eos.prediction_hours,
|
||||
) -> list[float]:
|
||||
"""Endpoint to handle total load calculation based on the latest measured data."""
|
||||
# Measured data in JSON format
|
||||
measured_data_df = pd.DataFrame(measured_data)
|
||||
measured_data_df["time"] = pd.to_datetime(measured_data_df["time"])
|
||||
|
||||
# Ensure datetime has timezone info for accurate calculations
|
||||
if measured_data_df["time"].dt.tz is None:
|
||||
measured_data_df["time"] = measured_data_df["time"].dt.tz_localize("Europe/Berlin")
|
||||
else:
|
||||
measured_data_df["time"] = measured_data_df["time"].dt.tz_convert("Europe/Berlin")
|
||||
|
||||
# Remove timezone info after conversion to simplify further processing
|
||||
measured_data_df["time"] = measured_data_df["time"].dt.tz_localize(None)
|
||||
|
||||
# Instantiate LoadForecast and generate forecast data
|
||||
lf = LoadForecast(
|
||||
filepath=server_dir / ".." / "data" / "load_profiles.npz", year_energy=year_energy
|
||||
)
|
||||
forecast_list = []
|
||||
|
||||
# Generate daily forecasts for the date range based on measured data
|
||||
for single_date in pd.date_range(
|
||||
measured_data_df["time"].min().date(), measured_data_df["time"].max().date()
|
||||
):
|
||||
date_str = single_date.strftime("%Y-%m-%d")
|
||||
daily_forecast = lf.get_daily_stats(date_str)
|
||||
mean_values = daily_forecast[0]
|
||||
fc_hours = [single_date + pd.Timedelta(hours=i) for i in range(24)]
|
||||
daily_forecast_df = pd.DataFrame({"time": fc_hours, "Last Pred": mean_values})
|
||||
forecast_list.append(daily_forecast_df)
|
||||
|
||||
# Concatenate all daily forecasts into a single DataFrame
|
||||
predicted_data = pd.concat(forecast_list, ignore_index=True)
|
||||
|
||||
# Create LoadPredictionAdjuster instance to adjust the predictions based on measured data
|
||||
adjuster = LoadPredictionAdjuster(measured_data_df, predicted_data, lf)
|
||||
adjuster.calculate_weighted_mean() # Calculate weighted mean for adjustment
|
||||
adjuster.adjust_predictions() # Adjust predictions based on measured data
|
||||
future_predictions = adjuster.predict_next_hours(hours) # Predict future load
|
||||
|
||||
# Extract household power predictions
|
||||
leistung_haushalt = future_predictions["Adjusted Pred"].values
|
||||
gesamtlast = Gesamtlast(prediction_hours=hours)
|
||||
gesamtlast.hinzufuegen(
|
||||
"Haushalt", leistung_haushalt
|
||||
) # Add household load to total load calculation
|
||||
|
||||
# Calculate the total load
|
||||
last = gesamtlast.gesamtlast_berechnen() # Compute total load
|
||||
return last.tolist()
|
||||
|
||||
|
||||
@app.get("/gesamtlast_simple")
|
||||
def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
||||
date_now, date = get_start_enddate(
|
||||
config.eos.prediction_hours, startdate=datetime.now().date()
|
||||
) # Get the current date and prediction end date
|
||||
|
||||
###############
|
||||
# Load Forecast
|
||||
###############
|
||||
lf = LoadForecast(
|
||||
filepath=server_dir / ".." / "data" / "load_profiles.npz", year_energy=year_energy
|
||||
) # Instantiate LoadForecast with specified parameters
|
||||
leistung_haushalt = lf.get_stats_for_date_range(date_now, date)[
|
||||
0
|
||||
] # Get expected household load for the date range
|
||||
|
||||
gesamtlast = Gesamtlast(
|
||||
prediction_hours=config.eos.prediction_hours
|
||||
) # Create Gesamtlast instance
|
||||
gesamtlast.hinzufuegen(
|
||||
"Haushalt", leistung_haushalt
|
||||
) # Add household load to total load calculation
|
||||
|
||||
# ###############
|
||||
# # WP (Heat Pump)
|
||||
# ##############
|
||||
# leistung_wp = wp.simulate_24h(temperature_forecast) # Simulate heat pump load for 24 hours
|
||||
# gesamtlast.hinzufuegen("Heatpump", leistung_wp) # Add heat pump load to total load calculation
|
||||
|
||||
last = gesamtlast.gesamtlast_berechnen() # Calculate total load
|
||||
print(last) # Output total load
|
||||
return last.tolist() # Return total load as JSON
|
||||
|
||||
|
||||
@app.get("/pvforecast")
|
||||
def fastapi_pvprognose(url: str, ac_power_measurement: Optional[float] = None) -> ForecastResponse:
|
||||
date_now, date = get_start_enddate(config.eos.prediction_hours, startdate=datetime.now().date())
|
||||
|
||||
###############
|
||||
# PV Forecast
|
||||
###############
|
||||
PVforecast = PVForecast(
|
||||
prediction_hours=config.eos.prediction_hours, url=url
|
||||
) # Instantiate PVForecast with given parameters
|
||||
if ac_power_measurement is not None:
|
||||
PVforecast.update_ac_power_measurement(
|
||||
date_time=datetime.now(),
|
||||
ac_power_measurement=ac_power_measurement,
|
||||
) # Update measurement
|
||||
|
||||
# Get PV forecast and temperature forecast for the specified date range
|
||||
pv_forecast = PVforecast.get_pv_forecast_for_date_range(date_now, date)
|
||||
temperature_forecast = PVforecast.get_temperature_for_date_range(date_now, date)
|
||||
|
||||
# Return both forecasts as a JSON response
|
||||
ret = {
|
||||
"temperature": temperature_forecast.tolist(),
|
||||
"pvpower": pv_forecast.tolist(),
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
@app.post("/optimize")
|
||||
def fastapi_optimize(
|
||||
parameters: OptimizationParameters,
|
||||
start_hour: Annotated[
|
||||
Optional[int], Query(description="Defaults to current hour of the day.")
|
||||
] = None,
|
||||
) -> OptimizeResponse:
|
||||
if start_hour is None:
|
||||
start_hour = datetime.now().hour
|
||||
|
||||
# Perform optimization simulation
|
||||
result = opt_class.optimierung_ems(parameters=parameters, start_hour=start_hour)
|
||||
# print(result)
|
||||
# convert to JSON (None accepted by dumps)
|
||||
return result
|
||||
|
||||
|
||||
@app.get("/visualization_results.pdf", response_class=PdfResponse)
|
||||
def get_pdf():
|
||||
# Endpoint to serve the generated PDF with visualization results
|
||||
output_path = config.working_dir / config.directories.output
|
||||
if not output_path.is_dir():
|
||||
raise SetupIncomplete(f"Output path does not exist: {output_path}.")
|
||||
file_path = output_path / "visualization_results.pdf"
|
||||
if not file_path.is_file():
|
||||
raise HTTPException(status_code=404, detail="No visualization result available.")
|
||||
return FileResponse(file_path)
|
||||
|
||||
|
||||
@app.get("/site-map", include_in_schema=False)
|
||||
def site_map():
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
def root():
|
||||
# Redirect the root URL to the site map
|
||||
return RedirectResponse(url="/docs")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
config.run_setup()
|
||||
except Exception as e:
|
||||
print(f"Failed to initialize: {e}")
|
||||
exit(1)
|
||||
|
||||
# Set host and port from environment variables or defaults
|
||||
host = os.getenv("EOS_RUN_HOST", "0.0.0.0")
|
||||
port = os.getenv("EOS_RUN_PORT", 8503)
|
||||
try:
|
||||
uvicorn.run(app, host=host, port=int(port)) # Run the FastAPI application
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Could not bind to host {host}:{port}. Error: {e}"
|
||||
) # Error handling for binding issues
|
||||
exit(1)
|
||||
else:
|
||||
# started from cli / dev server
|
||||
config.run_setup()
|
@ -179,7 +179,7 @@ def visualisiere_ergebnisse(
|
||||
plt.plot(hours, ergebnisse["akku_soc_pro_stunde"], label="PV Battery (%)", marker="x")
|
||||
plt.plot(
|
||||
hours,
|
||||
ergebnisse["E-Auto_SoC_pro_Stunde"],
|
||||
ergebnisse["EAuto_SoC_pro_Stunde"],
|
||||
label="E-Car Battery (%)",
|
||||
marker="x",
|
||||
)
|
||||
|
@ -1,305 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any, TypeGuard
|
||||
|
||||
import matplotlib
|
||||
|
||||
# Sets the Matplotlib backend to 'Agg' for rendering plots in environments without a display
|
||||
matplotlib.use("Agg")
|
||||
|
||||
import pandas as pd
|
||||
from flask import Flask, jsonify, redirect, request, send_from_directory, url_for
|
||||
|
||||
from akkudoktoreos.class_load import LoadForecast
|
||||
from akkudoktoreos.class_load_container import Gesamtlast
|
||||
from akkudoktoreos.class_load_corrector import LoadPredictionAdjuster
|
||||
from akkudoktoreos.class_numpy_encoder import NumpyEncoder
|
||||
from akkudoktoreos.class_optimize import optimization_problem
|
||||
from akkudoktoreos.class_pv_forecast import PVForecast
|
||||
from akkudoktoreos.class_strompreis import HourlyElectricityPriceForecast
|
||||
from akkudoktoreos.config import (
|
||||
SetupIncomplete,
|
||||
get_start_enddate,
|
||||
get_working_dir,
|
||||
load_config,
|
||||
)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
working_dir = get_working_dir()
|
||||
# copy config to working directory. Make this a CLI option later
|
||||
config = load_config(working_dir, True)
|
||||
opt_class = optimization_problem(config)
|
||||
|
||||
|
||||
def isfloat(num: Any) -> TypeGuard[float]:
|
||||
"""Check if a given input can be converted to float."""
|
||||
if num is None:
|
||||
return False
|
||||
|
||||
if isinstance(num, str):
|
||||
num = num.strip() # Strip any surrounding whitespace
|
||||
|
||||
try:
|
||||
float_value = float(num)
|
||||
return not (
|
||||
float_value == float("inf")
|
||||
or float_value == float("-inf")
|
||||
or float_value != float_value
|
||||
) # Excludes NaN or Infinity
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
@app.route("/strompreis", methods=["GET"])
|
||||
def flask_strompreis():
|
||||
# Get the current date and the end date based on prediction hours
|
||||
date_now, date = get_start_enddate(config.eos.prediction_hours, startdate=datetime.now().date())
|
||||
price_forecast = HourlyElectricityPriceForecast(
|
||||
source=f"https://api.akkudoktor.net/prices?start={date_now}&end={date}",
|
||||
config=config.eos.prediction_hours,
|
||||
use_cache=False,
|
||||
)
|
||||
specific_date_prices = price_forecast.get_price_for_daterange(
|
||||
date_now, date
|
||||
) # Fetch prices for the specified date range
|
||||
return jsonify(specific_date_prices.tolist())
|
||||
|
||||
|
||||
# Endpoint to handle total load calculation based on the latest measured data
|
||||
@app.route("/gesamtlast", methods=["POST"])
|
||||
def flask_gesamtlast():
|
||||
# Retrieve data from the JSON body
|
||||
data = request.get_json()
|
||||
|
||||
# Extract year_energy and prediction_hours from the request JSON
|
||||
year_energy = float(data.get("year_energy"))
|
||||
prediction_hours = int(data.get("hours", 48)) # Default to 48 hours if not specified
|
||||
|
||||
# Measured data in JSON format
|
||||
measured_data_json = data.get("measured_data")
|
||||
measured_data = pd.DataFrame(measured_data_json)
|
||||
measured_data["time"] = pd.to_datetime(measured_data["time"])
|
||||
|
||||
# Ensure datetime has timezone info for accurate calculations
|
||||
if measured_data["time"].dt.tz is None:
|
||||
measured_data["time"] = measured_data["time"].dt.tz_localize("Europe/Berlin")
|
||||
else:
|
||||
measured_data["time"] = measured_data["time"].dt.tz_convert("Europe/Berlin")
|
||||
|
||||
# Remove timezone info after conversion to simplify further processing
|
||||
measured_data["time"] = measured_data["time"].dt.tz_localize(None)
|
||||
|
||||
# Instantiate LoadForecast and generate forecast data
|
||||
file_path = os.path.join("data", "load_profiles.npz")
|
||||
lf = LoadForecast(filepath=file_path, year_energy=year_energy)
|
||||
forecast_list = []
|
||||
|
||||
# Generate daily forecasts for the date range based on measured data
|
||||
for single_date in pd.date_range(
|
||||
measured_data["time"].min().date(), measured_data["time"].max().date()
|
||||
):
|
||||
date_str = single_date.strftime("%Y-%m-%d")
|
||||
daily_forecast = lf.get_daily_stats(date_str)
|
||||
mean_values = daily_forecast[0]
|
||||
hours = [single_date + pd.Timedelta(hours=i) for i in range(24)]
|
||||
daily_forecast_df = pd.DataFrame({"time": hours, "Last Pred": mean_values})
|
||||
forecast_list.append(daily_forecast_df)
|
||||
|
||||
# Concatenate all daily forecasts into a single DataFrame
|
||||
predicted_data = pd.concat(forecast_list, ignore_index=True)
|
||||
|
||||
# Create LoadPredictionAdjuster instance to adjust the predictions based on measured data
|
||||
adjuster = LoadPredictionAdjuster(measured_data, predicted_data, lf)
|
||||
adjuster.calculate_weighted_mean() # Calculate weighted mean for adjustment
|
||||
adjuster.adjust_predictions() # Adjust predictions based on measured data
|
||||
future_predictions = adjuster.predict_next_hours(prediction_hours) # Predict future load
|
||||
|
||||
# Extract household power predictions
|
||||
leistung_haushalt = future_predictions["Adjusted Pred"].values
|
||||
gesamtlast = Gesamtlast(prediction_hours=prediction_hours)
|
||||
gesamtlast.hinzufuegen(
|
||||
"Haushalt", leistung_haushalt
|
||||
) # Add household load to total load calculation
|
||||
|
||||
# Calculate the total load
|
||||
last = gesamtlast.gesamtlast_berechnen() # Compute total load
|
||||
return jsonify(last.tolist())
|
||||
|
||||
|
||||
@app.route("/gesamtlast_simple", methods=["GET"])
|
||||
def flask_gesamtlast_simple():
|
||||
if request.method == "GET":
|
||||
year_energy = float(
|
||||
request.args.get("year_energy")
|
||||
) # Get annual energy value from query parameters
|
||||
date_now, date = get_start_enddate(
|
||||
config.eos.prediction_hours, startdate=datetime.now().date()
|
||||
) # Get the current date and prediction end date
|
||||
|
||||
###############
|
||||
# Load Forecast
|
||||
###############
|
||||
server_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
file_path = os.path.join(server_dir, "data", "load_profiles.npz")
|
||||
|
||||
print(file_path)
|
||||
|
||||
lf = LoadForecast(
|
||||
filepath=file_path, year_energy=year_energy
|
||||
) # Instantiate LoadForecast with specified parameters
|
||||
leistung_haushalt = lf.get_stats_for_date_range(date_now, date)[
|
||||
0
|
||||
] # Get expected household load for the date range
|
||||
|
||||
gesamtlast = Gesamtlast(
|
||||
prediction_hours=config.eos.prediction_hours
|
||||
) # Create Gesamtlast instance
|
||||
gesamtlast.hinzufuegen(
|
||||
"Haushalt", leistung_haushalt
|
||||
) # Add household load to total load calculation
|
||||
|
||||
# ###############
|
||||
# # WP (Heat Pump)
|
||||
# ##############
|
||||
# leistung_wp = wp.simulate_24h(temperature_forecast) # Simulate heat pump load for 24 hours
|
||||
# gesamtlast.hinzufuegen("Heatpump", leistung_wp) # Add heat pump load to total load calculation
|
||||
|
||||
last = gesamtlast.gesamtlast_berechnen() # Calculate total load
|
||||
print(last) # Output total load
|
||||
return jsonify(last.tolist()) # Return total load as JSON
|
||||
|
||||
|
||||
@app.route("/pvforecast", methods=["GET"])
|
||||
def flask_pvprognose():
|
||||
if request.method == "GET":
|
||||
# Retrieve URL and AC power measurement from query parameters
|
||||
url = request.args.get("url")
|
||||
ac_power_measurement = request.args.get("ac_power_measurement")
|
||||
date_now, date = get_start_enddate(
|
||||
config.eos.prediction_hours, startdate=datetime.now().date()
|
||||
)
|
||||
|
||||
###############
|
||||
# PV Forecast
|
||||
###############
|
||||
PVforecast = PVForecast(
|
||||
prediction_hours=config.eos.prediction_hours, url=url
|
||||
) # Instantiate PVForecast with given parameters
|
||||
if isfloat(ac_power_measurement): # Check if the AC power measurement is a valid float
|
||||
PVforecast.update_ac_power_measurement(
|
||||
date_time=datetime.now(),
|
||||
ac_power_measurement=float(ac_power_measurement),
|
||||
) # Update measurement
|
||||
|
||||
# Get PV forecast and temperature forecast for the specified date range
|
||||
pv_forecast = PVforecast.get_pv_forecast_for_date_range(date_now, date)
|
||||
temperature_forecast = PVforecast.get_temperature_for_date_range(date_now, date)
|
||||
|
||||
# Return both forecasts as a JSON response
|
||||
ret = {
|
||||
"temperature": temperature_forecast.tolist(),
|
||||
"pvpower": pv_forecast.tolist(),
|
||||
}
|
||||
return jsonify(ret)
|
||||
|
||||
|
||||
@app.route("/optimize", methods=["POST"])
|
||||
def flask_optimize():
|
||||
if request.method == "POST":
|
||||
from datetime import datetime
|
||||
|
||||
# Retrieve optimization parameters from the request JSON
|
||||
parameter = request.json
|
||||
|
||||
# Check for required parameters
|
||||
required_parameters = [
|
||||
"preis_euro_pro_wh_akku",
|
||||
"strompreis_euro_pro_wh",
|
||||
"gesamtlast",
|
||||
"pv_akku_cap",
|
||||
"einspeiseverguetung_euro_pro_wh",
|
||||
"pv_forecast",
|
||||
"temperature_forecast",
|
||||
"eauto_min_soc",
|
||||
"eauto_cap",
|
||||
"eauto_charge_efficiency",
|
||||
"eauto_charge_power",
|
||||
"eauto_soc",
|
||||
"pv_soc",
|
||||
"start_solution",
|
||||
"haushaltsgeraet_dauer",
|
||||
"haushaltsgeraet_wh",
|
||||
]
|
||||
# Identify any missing parameters
|
||||
missing_params = [p for p in required_parameters if p not in parameter]
|
||||
if missing_params:
|
||||
return jsonify(
|
||||
{"error": f"Missing parameter: {', '.join(missing_params)}"}
|
||||
), 400 # Return error for missing parameters
|
||||
|
||||
# Optional min SoC PV Battery
|
||||
if "min_soc_prozent" not in parameter:
|
||||
parameter["min_soc_prozent"] = 0
|
||||
|
||||
# Perform optimization simulation
|
||||
result = opt_class.optimierung_ems(parameter=parameter, start_hour=datetime.now().hour)
|
||||
# print(result)
|
||||
# convert to JSON (None accepted by dumps)
|
||||
return NumpyEncoder.dumps(result)
|
||||
|
||||
|
||||
@app.route("/visualization_results.pdf")
|
||||
def get_pdf():
|
||||
# Endpoint to serve the generated PDF with visualization results
|
||||
output_path = config.working_dir / config.directories.output
|
||||
if not output_path.is_dir():
|
||||
raise SetupIncomplete(f"Output path does not exist: {output_path}.")
|
||||
return send_from_directory(output_path, "visualization_results.pdf")
|
||||
|
||||
|
||||
@app.route("/site-map")
|
||||
def site_map():
|
||||
# Function to generate a site map of valid routes in the application
|
||||
def print_links(links):
|
||||
content = "<h1>Valid routes</h1><ul>"
|
||||
for link in links:
|
||||
content += f"<li><a href='{link}'>{link}</a></li>"
|
||||
content += "</ul>"
|
||||
return content
|
||||
|
||||
# Check if the route has no empty parameters
|
||||
def has_no_empty_params(rule):
|
||||
defaults = rule.defaults if rule.defaults is not None else ()
|
||||
arguments = rule.arguments if rule.arguments is not None else ()
|
||||
return len(defaults) >= len(arguments)
|
||||
|
||||
# Collect all valid GET routes without empty parameters
|
||||
links = []
|
||||
for rule in app.url_map.iter_rules():
|
||||
if "GET" in rule.methods and has_no_empty_params(rule):
|
||||
url = url_for(rule.endpoint, **(rule.defaults or {}))
|
||||
links.append(url)
|
||||
return print_links(sorted(links)) # Return the sorted links as HTML
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
# Redirect the root URL to the site map
|
||||
return redirect("/site-map", code=302)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
config.run_setup()
|
||||
|
||||
# Set host and port from environment variables or defaults
|
||||
host = os.getenv("FLASK_RUN_HOST", "0.0.0.0")
|
||||
port = os.getenv("FLASK_RUN_PORT", 8503)
|
||||
app.run(debug=True, host=host, port=port) # Run the Flask application
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Could not bind to host {host}:{port}. Error: {e}"
|
||||
) # Error handling for binding issues
|
@ -50,7 +50,7 @@ def server(xprocess, tmp_path: Path):
|
||||
# assure server to be installed
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", "import akkudoktoreosserver"],
|
||||
[sys.executable, "-c", "import akkudoktoreos.server"],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
@ -65,11 +65,11 @@ def server(xprocess, tmp_path: Path):
|
||||
)
|
||||
|
||||
# command to start server process
|
||||
args = [sys.executable, "-m", "akkudoktoreosserver.flask_server"]
|
||||
args = [sys.executable, "-m", "akkudoktoreos.server.fastapi_server"]
|
||||
env = {EOS_DIR: f"{tmp_path}", **os.environ.copy()}
|
||||
|
||||
# startup pattern
|
||||
pattern = "Debugger PIN:"
|
||||
pattern = "Application startup complete."
|
||||
# search the first 30 lines for the startup pattern, if not found
|
||||
# a RuntimeError will be raised informing the user
|
||||
max_read_lines = 30
|
||||
@ -81,14 +81,14 @@ def server(xprocess, tmp_path: Path):
|
||||
terminate_on_interrupt = True
|
||||
|
||||
# ensure process is running and return its logfile
|
||||
logfile = xprocess.ensure("akkudoktoreosserver", Starter)
|
||||
logfile = xprocess.ensure("eos", Starter)
|
||||
|
||||
# create url/port info to the server
|
||||
url = "http://127.0.0.1:8503"
|
||||
yield url
|
||||
|
||||
# clean up whole process tree afterwards
|
||||
xprocess.getinfo("akkudoktoreosserver").terminate()
|
||||
xprocess.getinfo("eos").terminate()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
25
tests/generate_openapi.py
Normal file
25
tests/generate_openapi.py
Normal file
@ -0,0 +1,25 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from akkudoktoreos.server.fastapi_server import app
|
||||
|
||||
|
||||
def generate_openapi(filename: str | Path = "openapi.json"):
|
||||
with open(filename, "w") as f:
|
||||
json.dump(
|
||||
get_openapi(
|
||||
title=app.title,
|
||||
version=app.version,
|
||||
openapi_version=app.openapi_version,
|
||||
description=app.description,
|
||||
routes=app.routes,
|
||||
),
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_openapi()
|
@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
from akkudoktoreos.class_akku import PVAkku, PVAkkuParameters
|
||||
|
||||
|
||||
class TestPVAkku(unittest.TestCase):
|
||||
@ -14,21 +14,25 @@ class TestPVAkku(unittest.TestCase):
|
||||
|
||||
def test_initial_state_of_charge(self):
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
self.assertEqual(akku.ladezustand_in_prozent(), 50.0, "Initial SoC should be 50%")
|
||||
|
||||
def test_discharge_below_min_soc(self):
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
akku.reset()
|
||||
# Try to discharge more energy than available above min_soc
|
||||
@ -43,11 +47,13 @@ class TestPVAkku(unittest.TestCase):
|
||||
|
||||
def test_charge_above_max_soc(self):
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
akku.reset()
|
||||
# Try to charge more energy than available up to max_soc
|
||||
@ -62,11 +68,13 @@ class TestPVAkku(unittest.TestCase):
|
||||
|
||||
def test_charging_at_max_soc(self):
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=80,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=80,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
akku.reset()
|
||||
# Try to charge when SoC is already at max_soc
|
||||
@ -80,11 +88,13 @@ class TestPVAkku(unittest.TestCase):
|
||||
|
||||
def test_discharging_at_min_soc(self):
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=20,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=20,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
akku.reset()
|
||||
# Try to discharge when SoC is already at min_soc
|
||||
@ -99,11 +109,13 @@ class TestPVAkku(unittest.TestCase):
|
||||
def test_soc_limits(self):
|
||||
# Test to ensure that SoC never exceeds max_soc or drops below min_soc
|
||||
akku = PVAkku(
|
||||
self.kapazitaet_wh,
|
||||
PVAkkuParameters(
|
||||
kapazitaet_wh=self.kapazitaet_wh,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
),
|
||||
hours=1,
|
||||
start_soc_prozent=50,
|
||||
min_soc_prozent=self.min_soc_prozent,
|
||||
max_soc_prozent=self.max_soc_prozent,
|
||||
)
|
||||
akku.reset()
|
||||
akku.soc_wh = (
|
||||
|
@ -1,10 +1,16 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
from akkudoktoreos.class_ems import EnergieManagementSystem
|
||||
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet
|
||||
from akkudoktoreos.class_inverter import Wechselrichter
|
||||
from akkudoktoreos.class_akku import EAutoParameters, PVAkku, PVAkkuParameters
|
||||
from akkudoktoreos.class_ems import (
|
||||
EnergieManagementSystem,
|
||||
EnergieManagementSystemParameters,
|
||||
)
|
||||
from akkudoktoreos.class_haushaltsgeraet import (
|
||||
Haushaltsgeraet,
|
||||
HaushaltsgeraetParameters,
|
||||
)
|
||||
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
|
||||
from akkudoktoreos.config import AppConfig
|
||||
|
||||
prediction_hours = 48
|
||||
@ -17,21 +23,30 @@ start_hour = 1
|
||||
def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Initialize the battery and the inverter
|
||||
akku = PVAkku(kapazitaet_wh=5000, start_soc_prozent=80, hours=48, min_soc_prozent=10)
|
||||
akku = PVAkku(
|
||||
PVAkkuParameters(kapazitaet_wh=5000, start_soc_prozent=80, min_soc_prozent=10),
|
||||
hours=prediction_hours,
|
||||
)
|
||||
akku.reset()
|
||||
wechselrichter = Wechselrichter(10000, akku)
|
||||
wechselrichter = Wechselrichter(WechselrichterParameters(max_leistung_wh=10000), akku)
|
||||
|
||||
# Household device (currently not used, set to None)
|
||||
home_appliance = Haushaltsgeraet(
|
||||
HaushaltsgeraetParameters(
|
||||
verbrauch_wh=2000,
|
||||
dauer_h=2,
|
||||
),
|
||||
hours=prediction_hours,
|
||||
verbrauch_wh=2000,
|
||||
dauer_h=2,
|
||||
)
|
||||
home_appliance.set_startzeitpunkt(2)
|
||||
|
||||
# Example initialization of electric car battery
|
||||
eauto = PVAkku(kapazitaet_wh=26400, start_soc_prozent=10, hours=48, min_soc_prozent=10)
|
||||
eauto.set_charge_per_hour(np.full(48, 1))
|
||||
eauto = PVAkku(
|
||||
EAutoParameters(kapazitaet_wh=26400, start_soc_prozent=10, min_soc_prozent=10),
|
||||
hours=prediction_hours,
|
||||
)
|
||||
eauto.set_charge_per_hour(np.full(prediction_hours, 1))
|
||||
|
||||
# Parameters based on previous example data
|
||||
pv_prognose_wh = [
|
||||
0,
|
||||
@ -135,7 +150,8 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
|
||||
0.0002780,
|
||||
]
|
||||
|
||||
einspeiseverguetung_euro_pro_wh = [0.00007] * len(strompreis_euro_pro_wh)
|
||||
einspeiseverguetung_euro_pro_wh = 0.00007
|
||||
preis_euro_pro_wh_akku = 0.0001
|
||||
|
||||
gesamtlast = [
|
||||
676.71,
|
||||
@ -190,12 +206,15 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
|
||||
|
||||
# Initialize the energy management system with the respective parameters
|
||||
ems = EnergieManagementSystem(
|
||||
config=tmp_config.eos,
|
||||
pv_prognose_wh=pv_prognose_wh,
|
||||
strompreis_euro_pro_wh=strompreis_euro_pro_wh,
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
tmp_config.eos,
|
||||
EnergieManagementSystemParameters(
|
||||
pv_prognose_wh=pv_prognose_wh,
|
||||
strompreis_euro_pro_wh=strompreis_euro_pro_wh,
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
preis_euro_pro_wh_akku=preis_euro_pro_wh_akku,
|
||||
gesamtlast=gesamtlast,
|
||||
),
|
||||
eauto=eauto,
|
||||
gesamtlast=gesamtlast,
|
||||
haushaltsgeraet=home_appliance,
|
||||
wechselrichter=wechselrichter,
|
||||
)
|
||||
@ -249,7 +268,7 @@ def test_simulation(create_ems_instance):
|
||||
"akku_soc_pro_stunde",
|
||||
"Einnahmen_Euro_pro_Stunde",
|
||||
"Gesamtbilanz_Euro",
|
||||
"E-Auto_SoC_pro_Stunde",
|
||||
"EAuto_SoC_pro_Stunde",
|
||||
"Gesamteinnahmen_Euro",
|
||||
"Gesamtkosten_Euro",
|
||||
"Verluste_Pro_Stunde",
|
||||
|
@ -1,10 +1,16 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from akkudoktoreos.class_akku import PVAkku
|
||||
from akkudoktoreos.class_ems import EnergieManagementSystem
|
||||
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet
|
||||
from akkudoktoreos.class_inverter import Wechselrichter
|
||||
from akkudoktoreos.class_akku import EAutoParameters, PVAkku, PVAkkuParameters
|
||||
from akkudoktoreos.class_ems import (
|
||||
EnergieManagementSystem,
|
||||
EnergieManagementSystemParameters,
|
||||
)
|
||||
from akkudoktoreos.class_haushaltsgeraet import (
|
||||
Haushaltsgeraet,
|
||||
HaushaltsgeraetParameters,
|
||||
)
|
||||
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
|
||||
from akkudoktoreos.config import AppConfig
|
||||
|
||||
prediction_hours = 48
|
||||
@ -17,20 +23,28 @@ start_hour = 0
|
||||
def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
|
||||
"""Fixture to create an EnergieManagementSystem instance with given test parameters."""
|
||||
# Initialize the battery and the inverter
|
||||
akku = PVAkku(kapazitaet_wh=5000, start_soc_prozent=80, hours=48, min_soc_prozent=10)
|
||||
akku = PVAkku(
|
||||
PVAkkuParameters(kapazitaet_wh=5000, start_soc_prozent=80, min_soc_prozent=10),
|
||||
hours=prediction_hours,
|
||||
)
|
||||
akku.reset()
|
||||
wechselrichter = Wechselrichter(10000, akku)
|
||||
wechselrichter = Wechselrichter(WechselrichterParameters(max_leistung_wh=10000), akku)
|
||||
|
||||
# Household device (currently not used, set to None)
|
||||
home_appliance = Haushaltsgeraet(
|
||||
HaushaltsgeraetParameters(
|
||||
verbrauch_wh=2000,
|
||||
dauer_h=2,
|
||||
),
|
||||
hours=prediction_hours,
|
||||
verbrauch_wh=2000,
|
||||
dauer_h=2,
|
||||
)
|
||||
home_appliance.set_startzeitpunkt(2)
|
||||
|
||||
# Example initialization of electric car battery
|
||||
eauto = PVAkku(kapazitaet_wh=26400, start_soc_prozent=100, hours=48, min_soc_prozent=100)
|
||||
eauto = PVAkku(
|
||||
EAutoParameters(kapazitaet_wh=26400, start_soc_prozent=100, min_soc_prozent=100),
|
||||
hours=prediction_hours,
|
||||
)
|
||||
|
||||
# Parameters based on previous example data
|
||||
pv_prognose_wh = np.full(prediction_hours, 0)
|
||||
@ -97,12 +111,15 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
|
||||
|
||||
# Initialize the energy management system with the respective parameters
|
||||
ems = EnergieManagementSystem(
|
||||
config=tmp_config.eos,
|
||||
pv_prognose_wh=pv_prognose_wh,
|
||||
strompreis_euro_pro_wh=strompreis_euro_pro_wh,
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
tmp_config.eos,
|
||||
EnergieManagementSystemParameters(
|
||||
pv_prognose_wh=pv_prognose_wh,
|
||||
strompreis_euro_pro_wh=strompreis_euro_pro_wh,
|
||||
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
|
||||
preis_euro_pro_wh_akku=0,
|
||||
gesamtlast=gesamtlast,
|
||||
),
|
||||
eauto=eauto,
|
||||
gesamtlast=gesamtlast,
|
||||
haushaltsgeraet=home_appliance,
|
||||
wechselrichter=wechselrichter,
|
||||
)
|
||||
@ -163,7 +180,7 @@ def test_simulation(create_ems_instance):
|
||||
"akku_soc_pro_stunde",
|
||||
"Einnahmen_Euro_pro_Stunde",
|
||||
"Gesamtbilanz_Euro",
|
||||
"E-Auto_SoC_pro_Stunde",
|
||||
"EAuto_SoC_pro_Stunde",
|
||||
"Gesamteinnahmen_Euro",
|
||||
"Gesamtkosten_Euro",
|
||||
"Verluste_Pro_Stunde",
|
||||
|
@ -5,7 +5,11 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from akkudoktoreos.class_optimize import optimization_problem
|
||||
from akkudoktoreos.class_optimize import (
|
||||
OptimizationParameters,
|
||||
OptimizeResponse,
|
||||
optimization_problem,
|
||||
)
|
||||
from akkudoktoreos.config import AppConfig
|
||||
|
||||
DIR_TESTDATA = Path(__file__).parent / "testdata"
|
||||
@ -46,7 +50,7 @@ def test_optimize(
|
||||
# Load input and output data
|
||||
file = DIR_TESTDATA / fn_in
|
||||
with file.open("r") as f_in:
|
||||
input_data = json.load(f_in)
|
||||
input_data = OptimizationParameters(**json.load(f_in))
|
||||
|
||||
file = DIR_TESTDATA / fn_out
|
||||
with file.open("r") as f_out:
|
||||
@ -59,7 +63,7 @@ def test_optimize(
|
||||
pytest.skip()
|
||||
|
||||
# Call the optimization function
|
||||
ergebnis = opt_class.optimierung_ems(parameter=input_data, start_hour=start_hour, ngen=ngen)
|
||||
ergebnis = opt_class.optimierung_ems(parameters=input_data, start_hour=start_hour, ngen=ngen)
|
||||
# with open(f"new_{fn_out}", "w") as f_out:
|
||||
# from akkudoktoreos.class_numpy_encoder import NumpyEncoder
|
||||
# json_data_str = NumpyEncoder.dumps(ergebnis)
|
||||
@ -72,3 +76,5 @@ def test_optimize(
|
||||
|
||||
# The function creates a visualization result PDF as a side-effect.
|
||||
visualisiere_ergebnisse_patch.assert_called_once()
|
||||
|
||||
OptimizeResponse(**ergebnis)
|
||||
|
19
tests/test_openapi.py
Normal file
19
tests/test_openapi.py
Normal file
@ -0,0 +1,19 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from generate_openapi import generate_openapi
|
||||
|
||||
DIR_PROJECT_ROOT = Path(__file__).parent.parent
|
||||
DIR_TESTDATA = Path(__file__).parent / "testdata"
|
||||
|
||||
|
||||
def test_openapi_spec_current():
|
||||
"""Verify the openapi spec hasn´t changed."""
|
||||
old_spec_path = DIR_PROJECT_ROOT / "docs" / "akkudoktoreos" / "openapi.json"
|
||||
new_spec_path = DIR_TESTDATA / "openapi-new.json"
|
||||
generate_openapi(new_spec_path)
|
||||
with open(new_spec_path) as f_new:
|
||||
new_spec = json.load(f_new)
|
||||
with open(old_spec_path) as f_old:
|
||||
old_spec = json.load(f_old)
|
||||
assert new_spec == old_spec
|
82
tests/testdata/optimize_input_1.json
vendored
82
tests/testdata/optimize_input_1.json
vendored
@ -1,52 +1,58 @@
|
||||
{
|
||||
"preis_euro_pro_wh_akku": 0.0001,
|
||||
"pv_soc": 80,
|
||||
"pv_akku_cap": 26400,
|
||||
"year_energy": 4100000,
|
||||
"einspeiseverguetung_euro_pro_wh": 0.00007,
|
||||
"max_heizleistung": 1000,
|
||||
"gesamtlast": [
|
||||
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
|
||||
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
|
||||
1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
|
||||
488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
|
||||
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"pv_forecast": [
|
||||
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69,
|
||||
6018.82, 5519.07, 3969.88, 3017.96, 1943.07, 1007.17, 319.67, 7.88, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
|
||||
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
|
||||
],
|
||||
"ems": {
|
||||
"preis_euro_pro_wh_akku": 0.0001,
|
||||
"einspeiseverguetung_euro_pro_wh": 0.00007,
|
||||
"gesamtlast": [
|
||||
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
|
||||
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
|
||||
1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
|
||||
488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
|
||||
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"pv_prognose_wh": [
|
||||
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69,
|
||||
6018.82, 5519.07, 3969.88, 3017.96, 1943.07, 1007.17, 319.67, 7.88, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
|
||||
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
|
||||
],
|
||||
"strompreis_euro_pro_wh": [
|
||||
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334, 0.0003290,
|
||||
0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212, 0.0002093, 0.0001879,
|
||||
0.0001838, 0.0002004, 0.0002198, 0.0002270, 0.0002997, 0.0003195, 0.0003081,
|
||||
0.0002969, 0.0002921, 0.0002780, 0.0003384, 0.0003318, 0.0003284, 0.0003283,
|
||||
0.0003289, 0.0003334, 0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280,
|
||||
0.0002212, 0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
||||
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780
|
||||
]
|
||||
},
|
||||
"pv_akku": {
|
||||
"kapazitaet_wh": 26400,
|
||||
"max_ladeleistung_w": 5000,
|
||||
"start_soc_prozent": 80,
|
||||
"min_soc_prozent": 15
|
||||
},
|
||||
"wechselrichter": {
|
||||
"max_leistung_wh": 10000
|
||||
},
|
||||
"eauto": {
|
||||
"kapazitaet_wh": 60000,
|
||||
"lade_effizienz": 0.95,
|
||||
"entlade_effizienz": 1.0,
|
||||
"max_ladeleistung_w": 11040,
|
||||
"start_soc_prozent": 54,
|
||||
"min_soc_prozent": 0
|
||||
},
|
||||
"temperature_forecast": [
|
||||
18.3, 17.8, 16.9, 16.2, 15.6, 15.1, 14.6, 14.2, 14.3, 14.8, 15.7, 16.7, 17.4,
|
||||
18.0, 18.6, 19.2, 19.1, 18.7, 18.5, 17.7, 16.2, 14.6, 13.6, 13.0, 12.6, 12.2,
|
||||
11.7, 11.6, 11.3, 11.0, 10.7, 10.2, 11.4, 14.4, 16.4, 18.3, 19.5, 20.7, 21.9,
|
||||
22.7, 23.1, 23.1, 22.8, 21.8, 20.2, 19.1, 18.0, 17.4
|
||||
],
|
||||
"strompreis_euro_pro_wh": [
|
||||
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334, 0.0003290,
|
||||
0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212, 0.0002093, 0.0001879,
|
||||
0.0001838, 0.0002004, 0.0002198, 0.0002270, 0.0002997, 0.0003195, 0.0003081,
|
||||
0.0002969, 0.0002921, 0.0002780, 0.0003384, 0.0003318, 0.0003284, 0.0003283,
|
||||
0.0003289, 0.0003334, 0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280,
|
||||
0.0002212, 0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
||||
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780
|
||||
],
|
||||
"eauto_min_soc": 0,
|
||||
"eauto_cap": 60000,
|
||||
"eauto_charge_efficiency": 0.95,
|
||||
"eauto_charge_power": 11040,
|
||||
"eauto_soc": 54,
|
||||
"pvpowernow": 211.137503624,
|
||||
"start_solution": [
|
||||
1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
|
||||
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
],
|
||||
"haushaltsgeraet_wh": 937,
|
||||
"haushaltsgeraet_dauer": 0,
|
||||
"min_soc_prozent": 15
|
||||
]
|
||||
}
|
||||
|
84
tests/testdata/optimize_input_2.json
vendored
84
tests/testdata/optimize_input_2.json
vendored
@ -1,47 +1,51 @@
|
||||
{
|
||||
"preis_euro_pro_wh_akku": 0.0001,
|
||||
"pv_soc": 80,
|
||||
"pv_akku_cap": 26400,
|
||||
"year_energy": 4100000,
|
||||
"einspeiseverguetung_euro_pro_wh": 0.00007,
|
||||
"max_heizleistung": 1000,
|
||||
"gesamtlast": [
|
||||
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
|
||||
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
|
||||
1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
|
||||
488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
|
||||
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"pv_forecast": [
|
||||
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69,
|
||||
6018.82, 5519.07, 3969.88, 3017.96, 1943.07, 1007.17, 319.67, 7.88, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
|
||||
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
|
||||
],
|
||||
"ems": {
|
||||
"preis_euro_pro_wh_akku": 0.0001,
|
||||
"einspeiseverguetung_euro_pro_wh": 0.00007,
|
||||
"gesamtlast": [
|
||||
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
|
||||
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
|
||||
1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
|
||||
488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
|
||||
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"pv_prognose_wh": [
|
||||
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69,
|
||||
6018.82, 5519.07, 3969.88, 3017.96, 1943.07, 1007.17, 319.67, 7.88, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
|
||||
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
|
||||
],
|
||||
"strompreis_euro_pro_wh": [
|
||||
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334, 0.0003290,
|
||||
0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212, 0.0002093, 0.0001879,
|
||||
0.0001838, 0.0002004, 0.0002198, 0.0002270, 0.0002997, 0.0003195, 0.0003081,
|
||||
0.0002969, 0.0002921, 0.0002780, 0.0003384, 0.0003318, 0.0003284, 0.0003283,
|
||||
0.0003289, 0.0003334, 0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280,
|
||||
0.0002212, 0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
||||
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780
|
||||
]
|
||||
},
|
||||
"pv_akku": {
|
||||
"kapazitaet_wh": 26400,
|
||||
"start_soc_prozent": 80,
|
||||
"min_soc_prozent": 15
|
||||
},
|
||||
"eauto": {
|
||||
"kapazitaet_wh": 60000,
|
||||
"lade_effizienz": 0.95,
|
||||
"max_ladeleistung_w": 11040,
|
||||
"start_soc_prozent": 5,
|
||||
"min_soc_prozent": 80
|
||||
},
|
||||
"spuelmaschine" :{
|
||||
"verbrauch_wh": 5000,
|
||||
"dauer_h": 2
|
||||
},
|
||||
"temperature_forecast": [
|
||||
18.3, 17.8, 16.9, 16.2, 15.6, 15.1, 14.6, 14.2, 14.3, 14.8, 15.7, 16.7, 17.4,
|
||||
18.0, 18.6, 19.2, 19.1, 18.7, 18.5, 17.7, 16.2, 14.6, 13.6, 13.0, 12.6, 12.2,
|
||||
11.7, 11.6, 11.3, 11.0, 10.7, 10.2, 11.4, 14.4, 16.4, 18.3, 19.5, 20.7, 21.9,
|
||||
22.7, 23.1, 23.1, 22.8, 21.8, 20.2, 19.1, 18.0, 17.4
|
||||
],
|
||||
"strompreis_euro_pro_wh": [
|
||||
0.0003384, 0.0003318, 0.0003284, 0.0003283, 0.0003289, 0.0003334, 0.0003290,
|
||||
0.0003302, 0.0003042, 0.0002430, 0.0002280, 0.0002212, 0.0002093, 0.0001879,
|
||||
0.0001838, 0.0002004, 0.0002198, 0.0002270, 0.0002997, 0.0003195, 0.0003081,
|
||||
0.0002969, 0.0002921, 0.0002780, 0.0003384, 0.0003318, 0.0003284, 0.0003283,
|
||||
0.0003289, 0.0003334, 0.0003290, 0.0003302, 0.0003042, 0.0002430, 0.0002280,
|
||||
0.0002212, 0.0002093, 0.0001879, 0.0001838, 0.0002004, 0.0002198, 0.0002270,
|
||||
0.0002997, 0.0003195, 0.0003081, 0.0002969, 0.0002921, 0.0002780
|
||||
],
|
||||
"eauto_min_soc": 80,
|
||||
"eauto_cap": 60000,
|
||||
"eauto_charge_efficiency": 0.95,
|
||||
"eauto_charge_power": 11040,
|
||||
"eauto_soc": 5,
|
||||
"pvpowernow": 211.137503624,
|
||||
"start_solution": null,
|
||||
"haushaltsgeraet_wh": 5000,
|
||||
"haushaltsgeraet_dauer": 2,
|
||||
"min_soc_prozent": 15
|
||||
}
|
||||
|
||||
"start_solution": null
|
||||
}
|
370
tests/testdata/optimize_result_1.json
vendored
370
tests/testdata/optimize_result_1.json
vendored
@ -392,7 +392,7 @@
|
||||
0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 1.3505190567851246,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
"EAuto_SoC_pro_Stunde": [
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
@ -727,371 +727,5 @@
|
||||
1,
|
||||
1
|
||||
],
|
||||
"spuelstart": null,
|
||||
"simulation_data": {
|
||||
"Last_Wh_pro_Stunde": [
|
||||
1053.07,
|
||||
1063.91,
|
||||
1320.56,
|
||||
1132.03,
|
||||
1163.67,
|
||||
1176.82,
|
||||
1216.22,
|
||||
1103.78,
|
||||
1129.12,
|
||||
1178.71,
|
||||
1050.98,
|
||||
988.56,
|
||||
912.38,
|
||||
704.61,
|
||||
516.37,
|
||||
868.05,
|
||||
694.34,
|
||||
608.79,
|
||||
556.31,
|
||||
488.89,
|
||||
506.91,
|
||||
804.89,
|
||||
1141.98,
|
||||
1056.97,
|
||||
992.46,
|
||||
1155.99,
|
||||
827.01,
|
||||
1257.98,
|
||||
1232.67,
|
||||
871.26,
|
||||
860.88,
|
||||
1158.03,
|
||||
1222.72,
|
||||
1221.04,
|
||||
949.99,
|
||||
987.01,
|
||||
733.99,
|
||||
592.97
|
||||
],
|
||||
"Netzeinspeisung_Wh_pro_Stunde": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2792.3879958677676,
|
||||
2753.66,
|
||||
1914.18,
|
||||
813.95,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2738.2769214876007,
|
||||
3682.7,
|
||||
2166.67,
|
||||
1416.43,
|
||||
497.68000000000006,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Netzbezug_Wh_pro_Stunde": [
|
||||
0.0,
|
||||
20.660000000000082,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
171.54000000000008,
|
||||
0.0,
|
||||
980.68,
|
||||
0.0,
|
||||
704.61,
|
||||
0.0,
|
||||
868.05,
|
||||
694.34,
|
||||
608.79,
|
||||
556.31,
|
||||
488.89,
|
||||
506.91,
|
||||
799.85,
|
||||
0.0,
|
||||
351.65,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
257.64,
|
||||
0.0,
|
||||
987.01,
|
||||
0.0,
|
||||
592.97
|
||||
],
|
||||
"Kosten_Euro_pro_Stunde": [
|
||||
0.0,
|
||||
0.004569992000000018,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.05480703000000003,
|
||||
0.0,
|
||||
0.291163892,
|
||||
0.0,
|
||||
0.19588158,
|
||||
0.0,
|
||||
0.28801899,
|
||||
0.22802125600000003,
|
||||
0.199865757,
|
||||
0.182970359,
|
||||
0.162995926,
|
||||
0.16677339,
|
||||
0.26411047,
|
||||
0.0,
|
||||
0.08545095,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.08231598,
|
||||
0.0,
|
||||
0.293043269,
|
||||
0.0,
|
||||
0.16484566
|
||||
],
|
||||
"akku_soc_pro_stunde": [
|
||||
79.4714617768595,
|
||||
79.4714617768595,
|
||||
78.55109331955923,
|
||||
78.6499599862259,
|
||||
94.83379331955922,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
96.85214359504131,
|
||||
96.85214359504131,
|
||||
92.92488808539943,
|
||||
92.92488808539943,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
90.70222107438015,
|
||||
87.231189738292,
|
||||
87.231189738292,
|
||||
87.66005640495867,
|
||||
89.15605640495866,
|
||||
93.59062307162533,
|
||||
94.17485640495867,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
100.0,
|
||||
97.56073519283747,
|
||||
97.56073519283747,
|
||||
94.40134297520663,
|
||||
94.40134297520663
|
||||
],
|
||||
"Einnahmen_Euro_pro_Stunde": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.19546715971074372,
|
||||
0.19275619999999996,
|
||||
0.1339926,
|
||||
0.0569765,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.19167938450413202,
|
||||
0.257789,
|
||||
0.1516669,
|
||||
0.09915009999999999,
|
||||
0.0348376,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 1.3505190567851246,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0,
|
||||
54.0
|
||||
],
|
||||
"Gesamteinnahmen_Euro": 1.3143154442148755,
|
||||
"Gesamtkosten_Euro": 2.664834501,
|
||||
"Verluste_Pro_Stunde": [
|
||||
16.744090909090914,
|
||||
0.0,
|
||||
29.157272727272726,
|
||||
3.5592000000000112,
|
||||
582.6179999999995,
|
||||
185.98344049586785,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
99.72409090909093,
|
||||
0.0,
|
||||
124.41545454545451,
|
||||
0.0,
|
||||
70.41409090909087,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
109.96227272727276,
|
||||
0.0,
|
||||
15.439199999999985,
|
||||
53.855999999999995,
|
||||
159.6443999999999,
|
||||
21.032399999999996,
|
||||
209.70516942148788,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
77.27590909090907,
|
||||
0.0,
|
||||
100.08954545454549,
|
||||
0.0
|
||||
],
|
||||
"Gesamt_Verluste": 1859.6205371900821,
|
||||
"Haushaltsgeraet_wh_pro_stunde": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
"spuelstart": null
|
||||
}
|
37
tests/testdata/optimize_result_2.json
vendored
37
tests/testdata/optimize_result_2.json
vendored
@ -31,7 +31,7 @@
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0569765, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.009006199999999989, 0.03141599999999999, 0.0931259, 0.012268899999999998, 0.31400739999999994, 0.257789, 0.1516669, 0.09915009999999999, 0.0348376, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 13.525878719636365,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
"EAuto_SoC_pro_Stunde": [
|
||||
13.74, 20.294999999999998, 37.775, 53.06999999999999, 70.55, 81.475, 90.215, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0
|
||||
],
|
||||
"Gesamteinnahmen_Euro": 1.0602444999999998,
|
||||
@ -62,38 +62,5 @@
|
||||
"start_solution": [
|
||||
0, 0, 4, 3, 1, 3, 1, 5, 6, 4, 1, 3, 6, 4, 6, 0, 6, 6, 0, 0, 3, 4, 1, 3, 2, 4, 4, 2, 3, 2, 1, 0, 2, 4, 1, 0, 6, 6, 2, 0, 4, 3, 3, 2, 6, 4, 1, 1, 0, 1, 5, 1, 2, 4, 6, 6, 5, 0, 2, 1, 6, 5, 6, 3, 2, 6, 6, 1, 1, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13
|
||||
],
|
||||
"spuelstart": 13,
|
||||
"simulation_data": {
|
||||
"Last_Wh_pro_Stunde": [
|
||||
6297.07, 6756.91, 16208.56, 15449.029999999999, 16313.488181818184, 7731.82, 6460.22, 6974.78, 1129.12, 1178.71, 1050.98, 988.56, 912.38, 1741.405454545456, 516.37, 868.05, 694.34, 608.79, 556.31, 488.89, 506.91, 804.89, 1718.014090909092, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67, 871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"Netzeinspeisung_Wh_pro_Stunde": [
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 813.95, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 128.65999999999985, 448.79999999999995, 1330.3700000000001, 175.26999999999998, 4485.82, 3682.7, 2166.67, 1416.43, 497.68000000000006, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Netzbezug_Wh_pro_Stunde": [
|
||||
366.78999999999996, 5713.66, 15101.82, 14287.339999999998, 10294.668181818184, 2212.75, 2490.34, 3956.8199999999997, 0.0, 171.54000000000008, 731.31, 980.68, 0.0, 1741.405454545456, 516.37, 868.05, 694.34, 608.79, 556.31, 488.89, 0.0, 799.85, 1382.424090909092, 351.65, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 257.64, 566.69, 987.01, 0.0, 0.0
|
||||
],
|
||||
"Kosten_Euro_pro_Stunde": [
|
||||
0.08362812, 1.263861592, 3.160810926, 2.6845911859999996, 1.8921600118181823, 0.44343509999999997, 0.547376732, 0.8981981399999999, 0.0, 0.05480703000000003, 0.225316611, 0.291163892, 0.0, 0.48411071636363673, 0.174739608, 0.28801899, 0.22802125600000003, 0.199865757, 0.182970359, 0.162995926, 0.0, 0.26411047, 0.42053340845454584, 0.08545095, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.08231598, 0.174597189, 0.293043269, 0.0, 0.0
|
||||
],
|
||||
"akku_soc_pro_stunde": [
|
||||
58.47796143250689, 65.14462809917354, 81.81129476584022, 91.81129476584022, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 96.07274449035812, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 97.8180526859504, 97.8180526859504, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 96.84060778236915, 94.28822314049587
|
||||
],
|
||||
"Einnahmen_Euro_pro_Stunde": [
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0569765, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.009006199999999989, 0.03141599999999999, 0.0931259, 0.012268899999999998, 0.31400739999999994, 0.257789, 0.1516669, 0.09915009999999999, 0.0348376, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 13.525878719636365,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
13.74, 20.294999999999998, 37.775, 53.06999999999999, 70.55, 81.475, 90.215, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0
|
||||
],
|
||||
"Gesamteinnahmen_Euro": 1.0602444999999998,
|
||||
"Gesamtkosten_Euro": 14.586123219636365,
|
||||
"Verluste_Pro_Stunde": [
|
||||
957.818181818182, 447.0, 1152.0, 843.0, 846.7933884297522, 345.0, 276.0, 309.0, 0.0, 0.0, 0.0, 0.0, 124.41545454545451, 141.38119834710756, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 69.12409090909085, 0.0, 78.55010330578523, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.08954545454549, 80.85954545454547
|
||||
],
|
||||
"Gesamt_Verluste": 5771.031508264463,
|
||||
"Haushaltsgeraet_wh_pro_stunde": [
|
||||
0.0, 0.0, 0.0, 2500.0, 2500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
]
|
||||
}
|
||||
"spuelstart": 13
|
||||
}
|
37
tests/testdata/optimize_result_2_full.json
vendored
37
tests/testdata/optimize_result_2_full.json
vendored
@ -31,7 +31,7 @@
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.013086500000000003, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.257789, 0.1516669, 0.09915009999999999, 0.0348376, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 11.315688587156364,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
"EAuto_SoC_pro_Stunde": [
|
||||
13.74, 13.74, 31.22, 48.699999999999996, 66.18, 81.475, 90.215, 98.955, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0
|
||||
],
|
||||
"Gesamteinnahmen_Euro": 0.5565300999999999,
|
||||
@ -62,38 +62,5 @@
|
||||
"start_solution": [
|
||||
3, 2, 6, 3, 5, 4, 1, 5, 4, 6, 1, 1, 2, 6, 6, 6, 6, 6, 1, 2, 2, 6, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 6, 2, 2, 2, 6, 1, 1, 1, 1, 1, 0, 5, 3, 1, 1, 2, 0, 1, 0, 6, 2, 0, 6, 6, 6, 5, 2, 2, 3, 6, 1, 5, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13
|
||||
],
|
||||
"spuelstart": 13,
|
||||
"simulation_data": {
|
||||
"Last_Wh_pro_Stunde": [
|
||||
6297.07, 1063.91, 12688.56, 18520.03, 18551.67, 11659.115454545456, 6460.22, 6347.78, 1756.12, 1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31, 488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 2494.6908545454626, 871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
|
||||
],
|
||||
"Netzeinspeisung_Wh_pro_Stunde": [
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 186.95000000000005, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3682.7, 2166.67, 1416.43, 497.68000000000006, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Netzbezug_Wh_pro_Stunde": [
|
||||
366.78999999999996, 0.0, 11581.82, 17358.339999999997, 12532.85, 6140.045454545456, 2490.34, 3329.8199999999997, 0.0, 171.54000000000008, 731.31, 980.68, 912.38, 704.61, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1262.0208545454625, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Kosten_Euro_pro_Stunde": [
|
||||
0.08362812, 0.0, 2.424074926, 3.261632085999999, 2.30353783, 1.2304651090909093, 0.547376732, 0.7558691399999999, 0.0, 0.05480703000000003, 0.225316611, 0.291163892, 0.26650619799999997, 0.19588158, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.231959433065456, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"akku_soc_pro_stunde": [
|
||||
58.47796143250689, 58.38903236914601, 61.722365702479344, 78.38903236914601, 95.05569903581267, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 97.77733298898072, 94.04089187327823, 91.05216942148759, 88.43168904958677, 86.03710399449034, 83.93272210743798, 81.75077479338839, 78.30789428374652, 74.83686294765836, 73.32321797520657, 73.75208464187324, 75.24808464187323, 79.6826513085399, 80.26688464187325, 100.0, 100.0, 100.0, 100.0, 100.0, 98.89101239669421, 96.45174758953168, 92.20325413223141, 89.04386191460057, 86.4914772727273
|
||||
],
|
||||
"Einnahmen_Euro_pro_Stunde": [
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.013086500000000003, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.257789, 0.1516669, 0.09915009999999999, 0.0348376, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 11.315688587156364,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
13.74, 13.74, 31.22, 48.699999999999996, 66.18, 81.475, 90.215, 98.955, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0
|
||||
],
|
||||
"Gesamteinnahmen_Euro": 0.5565300999999999,
|
||||
"Gesamtkosten_Euro": 11.872218687156364,
|
||||
"Verluste_Pro_Stunde": [
|
||||
957.818181818182, 2.817272727272737, 672.0, 1152.0, 1152.0, 660.994834710744, 276.0, 276.0, 33.0, 0.0, 0.0, 0.0, 0.0, 0.0, 70.41409090909087, 118.37045454545455, 94.68272727272722, 83.01681818181817, 75.86045454545456, 66.66681818181814, 69.12409090909085, 109.0704545454546, 109.96227272727276, 47.952272727272714, 15.439199999999985, 53.855999999999995, 159.6443999999999, 21.032399999999996, 710.3921528925632, 0.0, 0.0, 0.0, 0.0, 35.132727272727266, 77.27590909090907, 134.59227272727276, 100.08954545454549, 80.85954545454547
|
||||
],
|
||||
"Gesamt_Verluste": 7416.064896694217,
|
||||
"Haushaltsgeraet_wh_pro_stunde": [
|
||||
0.0, 0.0, 0.0, 2500.0, 2500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
||||
]
|
||||
}
|
||||
"spuelstart": 13
|
||||
}
|
2
tests/testdata/visualize_input_1.json
vendored
2
tests/testdata/visualize_input_1.json
vendored
@ -72,7 +72,7 @@
|
||||
0.0, 0.0, 0.0, 0.0, 0.0
|
||||
],
|
||||
"Gesamtbilanz_Euro": 27.732796636363638,
|
||||
"E-Auto_SoC_pro_Stunde": [
|
||||
"EAuto_SoC_pro_Stunde": [
|
||||
30.294999999999998, 43.405, 60.885, 78.365, 93.66, 93.66, 100.0, 100.0,
|
||||
100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0,
|
||||
100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0,
|
||||
|
Loading…
Reference in New Issue
Block a user