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:
Dominique Lasserre 2024-11-15 22:27:25 +01:00 committed by GitHub
parent ed3226e522
commit f61665669f
38 changed files with 997 additions and 1331 deletions

6
.env
View File

@ -1,10 +1,4 @@
EOS_VERSION=latest EOS_VERSION=latest
EOS_PORT=8503 EOS_PORT=8503
MARIADB_VERSION=11.1.6
PYTHON_VERSION=3.12.6 PYTHON_VERSION=3.12.6
MARIADB_ROOT_PASSWORD=ruth
MARIADB_DATABASE=eos
MARIADB_USER=eos
MARIADB_PASSWORD=eos

3
.gitignore vendored
View File

@ -254,3 +254,6 @@ visualize_output_*.pdf
# Test images # Test images
*_pdf.png *_pdf.png
# Test files
openapi-new.json

View File

@ -30,6 +30,8 @@ COPY src .
USER eos USER eos
ENTRYPOINT [] ENTRYPOINT []
CMD ["python", "-m", "akkudoktoreosserver.flask_server"] EXPOSE 8503
CMD ["python", "-m", "akkudoktoreos.server.fastapi_server"]
VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}"] VOLUME ["${MPLCONFIGDIR}", "${EOS_CACHE_DIR}", "${EOS_OUTPUT_DIR}"]

View File

@ -1,5 +1,5 @@
# Define the targets # 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 # Default target
all: help all: help
@ -10,12 +10,14 @@ help:
@echo " venv - Set up a Python 3 virtual environment." @echo " venv - Set up a Python 3 virtual environment."
@echo " pip - Install dependencies from requirements.txt." @echo " pip - Install dependencies from requirements.txt."
@echo " pip-dev - Install dependencies from requirements-dev.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 " install - Install EOS in editable form (development mode) into virtual environment."
@echo " docker-run - Run entire setup on docker" @echo " docker-run - Run entire setup on docker"
@echo " docker-build - Rebuild docker image" @echo " docker-build - Rebuild docker image"
@echo " docs - Generate HTML documentation (in build/docs/html/)." @echo " docs - Generate HTML documentation (in build/docs/html/)."
@echo " read-docs - Read HTML documentation in your browser." @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 " dist - Create distribution (in dist/)."
@echo " clean - Remove generated documentation, distribution and virtual environment." @echo " clean - Remove generated documentation, distribution and virtual environment."
@ -66,8 +68,12 @@ clean:
@echo "Deletion complete." @echo "Deletion complete."
run: run:
@echo "Starting flask server, please wait..." @echo "Starting FastAPI server, please wait..."
.venv/bin/python -m akkudoktoreosserver.flask_server .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. # Target to setup tests.
test-setup: pip-dev test-setup: pip-dev
@ -83,9 +89,13 @@ test-full:
@echo "Running all tests..." @echo "Running all tests..."
.venv/bin/pytest --full-run .venv/bin/pytest --full-run
# Target to format code.
format:
pre-commit run --all-files
# Run entire setup on docker # Run entire setup on docker
docker-run: docker-run:
@docker compose up --remove-orphans @docker compose up --remove-orphans
docker-build: docker-build:
@docker compose build @docker compose build --pull

2
NOTICE
View File

@ -17,7 +17,7 @@ This product may utilize technologies covered under international patents and/or
ADDITIONAL ATTRIBUTIONS: ADDITIONAL ATTRIBUTIONS:
The following is a list of licensors and other acknowledgements for third-party software that may be contained within this system: 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/ - NumPy, licensed under the BSD License, see https://numpy.org/
- Requests, licensed under the Apache License 2.0, see https://requests.readthedocs.io/ - 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/ - matplotlib, licensed under the matplotlib License (a variant of the Python Software Foundation License), see https://matplotlib.org/

260
README.md
View File

@ -83,10 +83,18 @@ source .venv/bin/activate
## Usage ## 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 ```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 ## 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. 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. 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)
## 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 EVs 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

View File

@ -1,6 +1,6 @@
--- ---
networks: networks:
eos: default:
name: "eos" name: "eos"
services: services:
eos: eos:
@ -11,11 +11,6 @@ services:
dockerfile: "Dockerfile" dockerfile: "Dockerfile"
args: args:
PYTHON_VERSION: "${PYTHON_VERSION}" PYTHON_VERSION: "${PYTHON_VERSION}"
init: true
environment:
FLASK_RUN_PORT: "${EOS_PORT}"
networks:
- "eos"
volumes: volumes:
- ./src/akkudoktoreos/default.config.json:/opt/eos/EOS.config.json:ro - ./src/akkudoktoreos/default.config.json:/opt/eos/EOS.config.json:ro
ports: ports:

View File

@ -13,4 +13,3 @@ EOS API
:recursive: :recursive:
akkudoktoreos akkudoktoreos
akkudoktoreosserver

View File

@ -31,7 +31,7 @@
"/gesamtlast": { "/gesamtlast": {
"post": { "post": {
"summary": "Fastapi Gesamtlast", "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", "operationId": "fastapi_gesamtlast_gesamtlast_post",
"parameters": [ "parameters": [
{ {
@ -260,8 +260,10 @@
"200": { "200": {
"description": "Successful Response", "description": "Successful Response",
"content": { "content": {
"application/json": { "application/pdf": {
"schema": {} "schema": {
"type": "string"
}
} }
} }
} }
@ -399,7 +401,7 @@
"start_soc_prozent" "start_soc_prozent"
], ],
"title": "EAutoResult", "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": { "EnergieManagementSystemParameters": {
"properties": { "properties": {
@ -420,9 +422,19 @@
"description": "An array of floats representing the electricity price in euros per watt-hour for different time intervals." "description": "An array of floats representing the electricity price in euros per watt-hour for different time intervals."
}, },
"einspeiseverguetung_euro_pro_wh": { "einspeiseverguetung_euro_pro_wh": {
"type": "number", "anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "number"
}
],
"title": "Einspeiseverguetung Euro Pro Wh", "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": { "preis_euro_pro_wh_akku": {
"type": "number", "type": "number",
@ -567,21 +579,29 @@
}, },
"OptimizeResponse": { "OptimizeResponse": {
"properties": { "properties": {
"discharge_hours_bin": { "ac_charge": {
"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": {
"items": { "items": {
"type": "number" "type": "number"
}, },
"type": "array", "type": "array",
"title": "Eautocharge Hours Float", "title": "Ac Charge",
"description": "An array of binary values (0 or 1) that indicates whether the EV will be charged in a certain hour." "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": { "result": {
"$ref": "#/components/schemas/SimulationResult" "$ref": "#/components/schemas/SimulationResult"
@ -619,8 +639,9 @@
}, },
"type": "object", "type": "object",
"required": [ "required": [
"discharge_hours_bin", "ac_charge",
"eautocharge_hours_float", "dc_charge",
"discharge_allowed",
"result", "result",
"eauto_obj" "eauto_obj"
], ],
@ -869,7 +890,7 @@
"akku_soc_pro_stunde" "akku_soc_pro_stunde"
], ],
"title": "SimulationResult", "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": { "ValidationError": {
"properties": { "properties": {

View File

@ -14,7 +14,7 @@ welcome.md
akkudoktoreos/about.md akkudoktoreos/about.md
develop/getting_started.md develop/getting_started.md
develop/CONTRIBUTING.md develop/CONTRIBUTING.md
akkudoktoreosserver/serverapi.rst akkudoktoreos/serverapi.rst
akkudoktoreos/api.rst akkudoktoreos/api.rst
``` ```

View File

@ -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." 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" readme = "README.md"
license = {file = "LICENSE"} license = {file = "LICENSE"}
requires-python = ">=3.10" requires-python = ">=3.9"
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
@ -28,11 +28,10 @@ optional-dependencies = {dev = { file = ["requirements-dev.txt"] }}
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src/"] where = ["src/"]
include = ["akkudoktoreos", "akkudoktoreosserver", ] include = ["akkudoktoreos"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
akkudoktoreos = ["*.json", ] akkudoktoreos = ["*.json", "data/*.npz", ]
akkudoktoreosserver = ["data/*.npz", ]
[tool.pyright] [tool.pyright]
# used in Pylance extension for language server # used in Pylance extension for language server

View File

@ -1,6 +1,8 @@
numpy==2.1.3 numpy==2.1.3
matplotlib==3.9.2 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 scikit-learn==1.5.2
timezonefinder==6.5.4 timezonefinder==6.5.4
deap==1.4.1 deap==1.4.1

View File

@ -5,7 +5,11 @@ import time
import numpy as np import numpy as np
from akkudoktoreos.class_numpy_encoder import NumpyEncoder 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.config import get_working_dir, load_config
from akkudoktoreos.visualize import visualisiere_ergebnisse from akkudoktoreos.visualize import visualisiere_ergebnisse
@ -229,48 +233,52 @@ gesamtlast = [
start_solution = None start_solution = None
# Define parameters for the optimization problem # Define parameters for the optimization problem
parameter = { parameters = OptimizationParameters(
# Value of energy in battery (per Wh) **{
"preis_euro_pro_wh_akku": 0e-05, "ems": {
# Initial state of charge (SOC) of PV battery (%) # Value of energy in battery (per Wh)
"pv_soc": 15, "preis_euro_pro_wh_akku": 0e-05,
# Battery capacity (in Wh) # Feed-in tariff for exporting electricity (per Wh)
"pv_akku_cap": 26400, "einspeiseverguetung_euro_pro_wh": 7e-05,
# Yearly energy consumption (in Wh) # Overall load on the system
"year_energy": 4100000, "gesamtlast": gesamtlast,
# Feed-in tariff for exporting electricity (per Wh) # PV generation forecast (48 hours)
"einspeiseverguetung_euro_pro_wh": 7e-05, "pv_prognose_wh": pv_forecast,
# Maximum heating power (in W) # Electricity price forecast (48 hours)
"max_heizleistung": 1000, "strompreis_euro_pro_wh": strompreis_euro_pro_wh,
# Overall load on the system },
"gesamtlast": gesamtlast, "pv_akku": {
# PV generation forecast (48 hours) # Battery capacity (in Wh)
"pv_forecast": pv_forecast, "kapazitaet_wh": 26400,
# Temperature forecast (48 hours) # Initial state of charge (SOC) of PV battery (%)
"temperature_forecast": temperature_forecast, "start_soc_prozent": 15,
# Electricity price forecast (48 hours) # Minimum Soc PV Battery
"strompreis_euro_pro_wh": strompreis_euro_pro_wh, "min_soc_prozent": 15,
# Minimum SOC for electric car },
"eauto_min_soc": 50, "eauto": {
# Electric car battery capacity (Wh) # Minimum SOC for electric car
"eauto_cap": 60000, "min_soc_prozent": 50,
# Charging efficiency of the electric car # Electric car battery capacity (Wh)
"eauto_charge_efficiency": 0.95, "kapazitaet_wh": 60000,
# Charging power of the electric car (W) # Charging efficiency of the electric car
"eauto_charge_power": 11040, "lade_effizienz": 0.95,
# Current SOC of the electric car (%) # Charging power of the electric car (W)
"eauto_soc": 15, "max_ladeleistung_w": 11040,
# Current PV power generation (W) # Current SOC of the electric car (%)
"pvpowernow": 211.137503624, "start_soc_prozent": 5,
# Initial solution for the optimization },
"start_solution": start_solution, # "spuelmaschine": {
# Household appliance consumption (Wh) # # Household appliance consumption (Wh)
"haushaltsgeraet_wh": 5000, # "verbrauch_wh": 5000,
# Duration of appliance usage (hours) # # Duration of appliance usage (hours)
"haushaltsgeraet_dauer": 0, # "dauer_h": 0,
# Minimum Soc PV Battery # },
"min_soc_prozent": 15, # Temperature forecast (48 hours)
} "temperature_forecast": temperature_forecast,
# Initial solution for the optimization
"start_solution": start_solution,
}
)
# Startzeit nehmen # Startzeit nehmen
start_time = time.time() start_time = time.time()
@ -281,7 +289,7 @@ config = load_config(working_dir)
opt_class = optimization_problem(config, verbose=True, fixed_seed=42) opt_class = optimization_problem(config, verbose=True, fixed_seed=42)
# Perform the optimisation based on the provided parameters and start hour # 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 # Endzeit nehmen
end_time = time.time() end_time = time.time()
@ -298,23 +306,23 @@ ac_charge, dc_charge, discharge = (
) )
visualisiere_ergebnisse( visualisiere_ergebnisse(
gesamtlast=gesamtlast, parameters.ems.gesamtlast,
pv_forecast=pv_forecast, parameters.ems.pv_prognose_wh,
strompreise=strompreis_euro_pro_wh, parameters.ems.strompreis_euro_pro_wh,
ergebnisse=ergebnis["result"], ergebnis["result"],
ac=ac_charge, ac_charge,
dc=dc_charge, dc_charge,
discharge=discharge, discharge,
temperature=temperature_forecast, parameters.temperature_forecast,
start_hour=start_hour, start_hour,
einspeiseverguetung_euro_pro_wh=np.full( 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, config=config,
filename="visualization_results.pdf",
extra_data=None,
) )
json_data = NumpyEncoder.dumps(ergebnis) json_data = NumpyEncoder.dumps(ergebnis)
print(json_data) print(json_data)
OptimizeResponse(**ergebnis)

View File

@ -1,32 +1,74 @@
from typing import Optional
import numpy as np 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: class PVAkku:
def __init__( def __init__(self, parameters: BaseAkkuParameters, hours: int = 24):
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,
):
# Battery capacity in Wh # Battery capacity in Wh
self.kapazitaet_wh = kapazitaet_wh self.kapazitaet_wh = parameters.kapazitaet_wh
# Initial state of charge in Wh # Initial state of charge in Wh
self.start_soc_prozent = start_soc_prozent self.start_soc_prozent = parameters.start_soc_prozent
self.soc_wh = (start_soc_prozent / 100) * kapazitaet_wh self.soc_wh = (parameters.start_soc_prozent / 100) * parameters.kapazitaet_wh
self.hours = hours if hours is not None else 24 # Default to 24 hours if not specified self.hours = hours
self.discharge_array = np.full(self.hours, 1) self.discharge_array = np.full(self.hours, 1)
self.charge_array = np.full(self.hours, 1) self.charge_array = np.full(self.hours, 1)
# Charge and discharge efficiency # Charge and discharge efficiency
self.lade_effizienz = lade_effizienz self.lade_effizienz = parameters.lade_effizienz
self.entlade_effizienz = entlade_effizienz self.entlade_effizienz = parameters.entlade_effizienz
self.max_ladeleistung_w = max_ladeleistung_w if max_ladeleistung_w else self.kapazitaet_wh self.max_ladeleistung_w = (
self.min_soc_prozent = min_soc_prozent parameters.max_ladeleistung_w if parameters.max_ladeleistung_w else self.kapazitaet_wh
self.max_soc_prozent = max_soc_prozent )
# 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 # Calculate min and max SoC in Wh
self.min_soc_wh = (self.min_soc_prozent / 100) * self.kapazitaet_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 self.max_soc_wh = (self.max_soc_prozent / 100) * self.kapazitaet_wh

View File

@ -2,27 +2,63 @@ from datetime import datetime
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union
import numpy as np 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 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: class EnergieManagementSystem:
def __init__( def __init__(
self, self,
config: EOSConfig, config: EOSConfig,
pv_prognose_wh: Optional[np.ndarray] = None, parameters: EnergieManagementSystemParameters,
strompreis_euro_pro_wh: Optional[np.ndarray] = None, eauto: Optional[PVAkku] = None,
einspeiseverguetung_euro_pro_wh: Optional[np.ndarray] = None, haushaltsgeraet: Optional[Haushaltsgeraet] = None,
eauto: Optional[object] = None, wechselrichter: Optional[Wechselrichter] = None,
gesamtlast: Optional[np.ndarray] = None,
haushaltsgeraet: Optional[object] = None,
wechselrichter: Optional[object] = None,
): ):
self.akku = wechselrichter.akku self.akku = wechselrichter.akku
self.gesamtlast = gesamtlast self.gesamtlast = np.array(parameters.gesamtlast, float)
self.pv_prognose_wh = pv_prognose_wh self.pv_prognose_wh = np.array(parameters.pv_prognose_wh, float)
self.strompreis_euro_pro_wh = strompreis_euro_pro_wh self.strompreis_euro_pro_wh = np.array(parameters.strompreis_euro_pro_wh, float)
self.einspeiseverguetung_euro_pro_wh = einspeiseverguetung_euro_pro_wh 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.eauto = eauto
self.haushaltsgeraet = haushaltsgeraet self.haushaltsgeraet = haushaltsgeraet
self.wechselrichter = wechselrichter self.wechselrichter = wechselrichter
@ -134,7 +170,7 @@ class EnergieManagementSystem:
netzbezug * self.strompreis_euro_pro_wh[stunde] netzbezug * self.strompreis_euro_pro_wh[stunde]
) )
einnahmen_euro_pro_stunde[stunde_since_now] = ( 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 # Akku SOC tracking
@ -152,7 +188,7 @@ class EnergieManagementSystem:
"akku_soc_pro_stunde": akku_soc_pro_stunde, "akku_soc_pro_stunde": akku_soc_pro_stunde,
"Einnahmen_Euro_pro_Stunde": einnahmen_euro_pro_stunde, "Einnahmen_Euro_pro_Stunde": einnahmen_euro_pro_stunde,
"Gesamtbilanz_Euro": gesamtkosten_euro, "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), "Gesamteinnahmen_Euro": np.nansum(einnahmen_euro_pro_stunde),
"Gesamtkosten_Euro": np.nansum(kosten_euro_pro_stunde), "Gesamtkosten_Euro": np.nansum(kosten_euro_pro_stunde),
"Verluste_Pro_Stunde": verluste_wh_pro_stunde, "Verluste_Pro_Stunde": verluste_wh_pro_stunde,

View File

@ -1,11 +1,25 @@
import numpy as np 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: 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.hours = hours # Total duration for which the planning is done
self.verbrauch_wh = verbrauch_wh # Total energy consumption of the device in kWh self.verbrauch_wh = (
self.dauer_h = dauer_h # Duration of use in hours 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 self.lastkurve = np.zeros(self.hours) # Initialize the load curve with zeros
def set_startzeitpunkt(self, start_hour, global_start_hour=0): def set_startzeitpunkt(self, start_hour, global_start_hour=0):

View File

@ -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: class Wechselrichter:
def __init__(self, max_leistung_wh, akku): def __init__(self, parameters: WechselrichterParameters, akku: PVAkku):
self.max_leistung_wh = max_leistung_wh # Maximum power that the inverter can handle self.max_leistung_wh = (
parameters.max_leistung_wh # Maximum power that the inverter can handle
)
self.akku = akku # Connection to a battery object self.akku = akku # Connection to a battery object
def energie_verarbeiten(self, erzeugung, verbrauch, hour): def energie_verarbeiten(self, erzeugung, verbrauch, hour):

View File

@ -1,17 +1,165 @@
import random import random
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Optional, Tuple
import numpy as np import numpy as np
from deap import algorithms, base, creator, tools 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_akku import EAutoParameters, PVAkku, PVAkkuParameters
from akkudoktoreos.class_ems import EnergieManagementSystem from akkudoktoreos.class_ems import (
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet EnergieManagementSystem,
from akkudoktoreos.class_inverter import Wechselrichter EnergieManagementSystemParameters,
)
from akkudoktoreos.class_haushaltsgeraet import (
Haushaltsgeraet,
HaushaltsgeraetParameters,
)
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
from akkudoktoreos.config import AppConfig from akkudoktoreos.config import AppConfig
from akkudoktoreos.visualize import visualisiere_ergebnisse 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 EVs 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: class optimization_problem:
def __init__( def __init__(
self, self,
@ -164,8 +312,8 @@ class optimization_problem:
return creator.Individual(individual_components) return creator.Individual(individual_components)
def split_individual( def split_individual(
self, individual: List[float] self, individual: list[float]
) -> Tuple[List[int], List[float], Optional[int]]: ) -> Tuple[list[int], list[float], Optional[int]]:
"""Split the individual solution into its components. """Split the individual solution into its components.
Components: Components:
@ -187,7 +335,7 @@ class optimization_problem:
) )
return discharge_hours_bin, eautocharge_hours_float, spuelstart_int 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.""" """Set up the DEAP environment with fitness and individual creation rules."""
self.opti_param = opti_param self.opti_param = opti_param
@ -250,8 +398,8 @@ class optimization_problem:
self.toolbox.register("select", tools.selTournament, tournsize=3) self.toolbox.register("select", tools.selTournament, tournsize=3)
def evaluate_inner( def evaluate_inner(
self, individual: List[float], ems: EnergieManagementSystem, start_hour: int self, individual: list[float], ems: EnergieManagementSystem, start_hour: int
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Simulates the energy management system (EMS) using the provided individual solution. """Simulates the energy management system (EMS) using the provided individual solution.
This is an internal function. This is an internal function.
@ -283,9 +431,9 @@ class optimization_problem:
def evaluate( def evaluate(
self, self,
individual: List[float], individual: list[float],
ems: EnergieManagementSystem, ems: EnergieManagementSystem,
parameter: Dict[str, Any], parameters: OptimizationParameters,
start_hour: int, start_hour: int,
worst_case: bool, worst_case: bool,
) -> Tuple[float]: ) -> Tuple[float]:
@ -305,7 +453,7 @@ class optimization_problem:
) )
# Penalty for not meeting the minimum SOC (State of Charge) requirement # 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( # gesamtbilanz += sum(
# self.strafe for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0 # self.strafe for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0
# ) # )
@ -313,26 +461,27 @@ class optimization_problem:
individual.extra_data = ( individual.extra_data = (
o["Gesamtbilanz_Euro"], o["Gesamtbilanz_Euro"],
o["Gesamt_Verluste"], 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 # Adjust total balance with battery value and penalties for unmet SOC
restwert_akku = ems.akku.aktueller_energieinhalt() * parameter["preis_euro_pro_wh_akku"] restwert_akku = ems.akku.aktueller_energieinhalt() * parameters.ems.preis_euro_pro_wh_akku
# print(ems.akku.aktueller_energieinhalt()," * ", parameter["preis_euro_pro_wh_akku"] , " ", restwert_akku, " ", gesamtbilanz) # print(ems.akku.aktueller_energieinhalt()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
gesamtbilanz += -restwert_akku gesamtbilanz += -restwert_akku
# print(gesamtbilanz) # print(gesamtbilanz)
if self.optimize_ev: if self.optimize_ev:
gesamtbilanz += max( gesamtbilanz += max(
0, 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,) return (gesamtbilanz,)
def optimize( def optimize(
self, start_solution: Optional[List[float]] = None, ngen: int = 400 self, start_solution: Optional[list[float]] = None, ngen: int = 400
) -> Tuple[Any, Dict[str, List[Any]]]: ) -> Tuple[Any, dict[str, list[Any]]]:
"""Run the optimization process using a genetic algorithm.""" """Run the optimization process using a genetic algorithm."""
population = self.toolbox.population(n=300) population = self.toolbox.population(n=300)
hof = tools.HallOfFame(1) hof = tools.HallOfFame(1)
@ -373,61 +522,50 @@ class optimization_problem:
def optimierung_ems( def optimierung_ems(
self, self,
parameter: Optional[Dict[str, Any]] = None, parameters: OptimizationParameters,
start_hour: Optional[int] = None, start_hour: Optional[int] = None,
worst_case: bool = False, worst_case: bool = False,
startdate: Optional[Any] = None, # startdate is not used! startdate: Optional[Any] = None, # startdate is not used!
*, *,
ngen: int = 600, ngen: int = 600,
) -> Dict[str, Any]: ) -> dict[str, Any]:
"""Perform EMS (Energy Management System) optimization and visualize results.""" """Perform EMS (Energy Management System) optimization and visualize results."""
einspeiseverguetung_euro_pro_wh = np.full( 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 # Initialize PV and EV batteries
akku = PVAkku( akku = PVAkku(
kapazitaet_wh=parameter["pv_akku_cap"], parameters.pv_akku,
hours=self.prediction_hours, 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)) akku.set_charge_per_hour(np.full(self.prediction_hours, 1))
self.optimize_ev = True 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 self.optimize_ev = False
eauto = PVAkku( eauto = PVAkku(
kapazitaet_wh=parameter["eauto_cap"], parameters.eauto,
hours=self.prediction_hours, 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)) eauto.set_charge_per_hour(np.full(self.prediction_hours, 1))
# Initialize household appliance if applicable # Initialize household appliance if applicable
spuelmaschine = ( spuelmaschine = (
Haushaltsgeraet( Haushaltsgeraet(
parameters=parameters.spuelmaschine,
hours=self.prediction_hours, 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 else None
) )
# Initialize the inverter and energy management system # Initialize the inverter and energy management system
wr = Wechselrichter(10000, akku) wr = Wechselrichter(parameters.wechselrichter, akku)
ems = EnergieManagementSystem( ems = EnergieManagementSystem(
config=self._config.eos, self._config.eos,
gesamtlast=parameter["gesamtlast"], parameters.ems,
pv_prognose_wh=parameter["pv_forecast"],
strompreis_euro_pro_wh=parameter["strompreis_euro_pro_wh"],
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
eauto=eauto, eauto=eauto,
haushaltsgeraet=spuelmaschine, haushaltsgeraet=spuelmaschine,
wechselrichter=wr, wechselrichter=wr,
@ -437,9 +575,9 @@ class optimization_problem:
self.setup_deap_environment({"haushaltsgeraete": 1 if spuelmaschine else 0}, start_hour) self.setup_deap_environment({"haushaltsgeraete": 1 if spuelmaschine else 0}, start_hour)
self.toolbox.register( self.toolbox.register(
"evaluate", "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 # Perform final evaluation on the best solution
o = self.evaluate_inner(start_solution, ems, start_hour) 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) ac_charge, dc_charge, discharge = self.decode_charge_discharge(discharge_hours_bin)
# Visualize the results # Visualize the results
visualisiere_ergebnisse( visualisiere_ergebnisse(
gesamtlast=parameter["gesamtlast"], parameters.ems.gesamtlast,
pv_forecast=parameter["pv_forecast"], parameters.ems.pv_prognose_wh,
strompreise=parameter["strompreis_euro_pro_wh"], parameters.ems.strompreis_euro_pro_wh,
ergebnisse=o, o,
ac=ac_charge, ac_charge,
dc=dc_charge, dc_charge,
discharge=discharge, discharge,
temperature=parameter["temperature_forecast"], parameters.temperature_forecast,
start_hour=start_hour, start_hour,
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh, einspeiseverguetung_euro_pro_wh,
config=self._config, config=self._config,
extra_data=extra_data, extra_data=extra_data,
) )
@ -477,7 +615,7 @@ class optimization_problem:
"Netzbezug_Wh_pro_Stunde", "Netzbezug_Wh_pro_Stunde",
"Kosten_Euro_pro_Stunde", "Kosten_Euro_pro_Stunde",
"Einnahmen_Euro_pro_Stunde", "Einnahmen_Euro_pro_Stunde",
"E-Auto_SoC_pro_Stunde", "EAuto_SoC_pro_Stunde",
"Verluste_Pro_Stunde", "Verluste_Pro_Stunde",
"Haushaltsgeraet_wh_pro_stunde", "Haushaltsgeraet_wh_pro_stunde",
] ]
@ -507,5 +645,5 @@ class optimization_problem:
"eauto_obj": ems.eauto.to_dict(), "eauto_obj": ems.eauto.to_dict(),
"start_solution": start_solution, "start_solution": start_solution,
"spuelstart": spuelstart_int, "spuelstart": spuelstart_int,
"simulation_data": o, # "simulation_data": o,
} }

View File

@ -108,6 +108,11 @@ def validate_pv_forecast_data(data) -> str:
return data_type return data_type
class ForecastResponse(BaseModel):
temperature: list[float]
pvpower: list[float]
class ForecastData: class ForecastData:
"""Stores forecast data for PV power and weather parameters. """Stores forecast data for PV power and weather parameters.

View File

View 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()

View File

@ -179,7 +179,7 @@ def visualisiere_ergebnisse(
plt.plot(hours, ergebnisse["akku_soc_pro_stunde"], label="PV Battery (%)", marker="x") plt.plot(hours, ergebnisse["akku_soc_pro_stunde"], label="PV Battery (%)", marker="x")
plt.plot( plt.plot(
hours, hours,
ergebnisse["E-Auto_SoC_pro_Stunde"], ergebnisse["EAuto_SoC_pro_Stunde"],
label="E-Car Battery (%)", label="E-Car Battery (%)",
marker="x", marker="x",
) )

View File

@ -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

View File

@ -50,7 +50,7 @@ def server(xprocess, tmp_path: Path):
# assure server to be installed # assure server to be installed
try: try:
subprocess.run( subprocess.run(
[sys.executable, "-c", "import akkudoktoreosserver"], [sys.executable, "-c", "import akkudoktoreos.server"],
check=True, check=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
@ -65,11 +65,11 @@ def server(xprocess, tmp_path: Path):
) )
# command to start server process # 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()} env = {EOS_DIR: f"{tmp_path}", **os.environ.copy()}
# startup pattern # startup pattern
pattern = "Debugger PIN:" pattern = "Application startup complete."
# search the first 30 lines for the startup pattern, if not found # search the first 30 lines for the startup pattern, if not found
# a RuntimeError will be raised informing the user # a RuntimeError will be raised informing the user
max_read_lines = 30 max_read_lines = 30
@ -81,14 +81,14 @@ def server(xprocess, tmp_path: Path):
terminate_on_interrupt = True terminate_on_interrupt = True
# ensure process is running and return its logfile # 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 # create url/port info to the server
url = "http://127.0.0.1:8503" url = "http://127.0.0.1:8503"
yield url yield url
# clean up whole process tree afterwards # clean up whole process tree afterwards
xprocess.getinfo("akkudoktoreosserver").terminate() xprocess.getinfo("eos").terminate()
@pytest.fixture @pytest.fixture

25
tests/generate_openapi.py Normal file
View 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()

View File

@ -1,6 +1,6 @@
import unittest import unittest
from akkudoktoreos.class_akku import PVAkku from akkudoktoreos.class_akku import PVAkku, PVAkkuParameters
class TestPVAkku(unittest.TestCase): class TestPVAkku(unittest.TestCase):
@ -14,21 +14,25 @@ class TestPVAkku(unittest.TestCase):
def test_initial_state_of_charge(self): def test_initial_state_of_charge(self):
akku = PVAkku( 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, 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%") self.assertEqual(akku.ladezustand_in_prozent(), 50.0, "Initial SoC should be 50%")
def test_discharge_below_min_soc(self): def test_discharge_below_min_soc(self):
akku = PVAkku( 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, hours=1,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
) )
akku.reset() akku.reset()
# Try to discharge more energy than available above min_soc # 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): def test_charge_above_max_soc(self):
akku = PVAkku( 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, hours=1,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
) )
akku.reset() akku.reset()
# Try to charge more energy than available up to max_soc # 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): def test_charging_at_max_soc(self):
akku = PVAkku( 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, hours=1,
start_soc_prozent=80,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
) )
akku.reset() akku.reset()
# Try to charge when SoC is already at max_soc # 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): def test_discharging_at_min_soc(self):
akku = PVAkku( 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, hours=1,
start_soc_prozent=20,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
) )
akku.reset() akku.reset()
# Try to discharge when SoC is already at min_soc # Try to discharge when SoC is already at min_soc
@ -99,11 +109,13 @@ class TestPVAkku(unittest.TestCase):
def test_soc_limits(self): def test_soc_limits(self):
# Test to ensure that SoC never exceeds max_soc or drops below min_soc # Test to ensure that SoC never exceeds max_soc or drops below min_soc
akku = PVAkku( 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, hours=1,
start_soc_prozent=50,
min_soc_prozent=self.min_soc_prozent,
max_soc_prozent=self.max_soc_prozent,
) )
akku.reset() akku.reset()
akku.soc_wh = ( akku.soc_wh = (

View File

@ -1,10 +1,16 @@
import numpy as np import numpy as np
import pytest import pytest
from akkudoktoreos.class_akku import PVAkku from akkudoktoreos.class_akku import EAutoParameters, PVAkku, PVAkkuParameters
from akkudoktoreos.class_ems import EnergieManagementSystem from akkudoktoreos.class_ems import (
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet EnergieManagementSystem,
from akkudoktoreos.class_inverter import Wechselrichter EnergieManagementSystemParameters,
)
from akkudoktoreos.class_haushaltsgeraet import (
Haushaltsgeraet,
HaushaltsgeraetParameters,
)
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
from akkudoktoreos.config import AppConfig from akkudoktoreos.config import AppConfig
prediction_hours = 48 prediction_hours = 48
@ -17,21 +23,30 @@ start_hour = 1
def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem: def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
"""Fixture to create an EnergieManagementSystem instance with given test parameters.""" """Fixture to create an EnergieManagementSystem instance with given test parameters."""
# Initialize the battery and the inverter # 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() akku.reset()
wechselrichter = Wechselrichter(10000, akku) wechselrichter = Wechselrichter(WechselrichterParameters(max_leistung_wh=10000), akku)
# Household device (currently not used, set to None) # Household device (currently not used, set to None)
home_appliance = Haushaltsgeraet( home_appliance = Haushaltsgeraet(
HaushaltsgeraetParameters(
verbrauch_wh=2000,
dauer_h=2,
),
hours=prediction_hours, hours=prediction_hours,
verbrauch_wh=2000,
dauer_h=2,
) )
home_appliance.set_startzeitpunkt(2) home_appliance.set_startzeitpunkt(2)
# Example initialization of electric car battery # Example initialization of electric car battery
eauto = PVAkku(kapazitaet_wh=26400, start_soc_prozent=10, hours=48, min_soc_prozent=10) eauto = PVAkku(
eauto.set_charge_per_hour(np.full(48, 1)) 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 # Parameters based on previous example data
pv_prognose_wh = [ pv_prognose_wh = [
0, 0,
@ -135,7 +150,8 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
0.0002780, 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 = [ gesamtlast = [
676.71, 676.71,
@ -190,12 +206,15 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
# Initialize the energy management system with the respective parameters # Initialize the energy management system with the respective parameters
ems = EnergieManagementSystem( ems = EnergieManagementSystem(
config=tmp_config.eos, tmp_config.eos,
pv_prognose_wh=pv_prognose_wh, EnergieManagementSystemParameters(
strompreis_euro_pro_wh=strompreis_euro_pro_wh, pv_prognose_wh=pv_prognose_wh,
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_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, eauto=eauto,
gesamtlast=gesamtlast,
haushaltsgeraet=home_appliance, haushaltsgeraet=home_appliance,
wechselrichter=wechselrichter, wechselrichter=wechselrichter,
) )
@ -249,7 +268,7 @@ def test_simulation(create_ems_instance):
"akku_soc_pro_stunde", "akku_soc_pro_stunde",
"Einnahmen_Euro_pro_Stunde", "Einnahmen_Euro_pro_Stunde",
"Gesamtbilanz_Euro", "Gesamtbilanz_Euro",
"E-Auto_SoC_pro_Stunde", "EAuto_SoC_pro_Stunde",
"Gesamteinnahmen_Euro", "Gesamteinnahmen_Euro",
"Gesamtkosten_Euro", "Gesamtkosten_Euro",
"Verluste_Pro_Stunde", "Verluste_Pro_Stunde",

View File

@ -1,10 +1,16 @@
import numpy as np import numpy as np
import pytest import pytest
from akkudoktoreos.class_akku import PVAkku from akkudoktoreos.class_akku import EAutoParameters, PVAkku, PVAkkuParameters
from akkudoktoreos.class_ems import EnergieManagementSystem from akkudoktoreos.class_ems import (
from akkudoktoreos.class_haushaltsgeraet import Haushaltsgeraet EnergieManagementSystem,
from akkudoktoreos.class_inverter import Wechselrichter EnergieManagementSystemParameters,
)
from akkudoktoreos.class_haushaltsgeraet import (
Haushaltsgeraet,
HaushaltsgeraetParameters,
)
from akkudoktoreos.class_inverter import Wechselrichter, WechselrichterParameters
from akkudoktoreos.config import AppConfig from akkudoktoreos.config import AppConfig
prediction_hours = 48 prediction_hours = 48
@ -17,20 +23,28 @@ start_hour = 0
def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem: def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
"""Fixture to create an EnergieManagementSystem instance with given test parameters.""" """Fixture to create an EnergieManagementSystem instance with given test parameters."""
# Initialize the battery and the inverter # 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() akku.reset()
wechselrichter = Wechselrichter(10000, akku) wechselrichter = Wechselrichter(WechselrichterParameters(max_leistung_wh=10000), akku)
# Household device (currently not used, set to None) # Household device (currently not used, set to None)
home_appliance = Haushaltsgeraet( home_appliance = Haushaltsgeraet(
HaushaltsgeraetParameters(
verbrauch_wh=2000,
dauer_h=2,
),
hours=prediction_hours, hours=prediction_hours,
verbrauch_wh=2000,
dauer_h=2,
) )
home_appliance.set_startzeitpunkt(2) home_appliance.set_startzeitpunkt(2)
# Example initialization of electric car battery # 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 # Parameters based on previous example data
pv_prognose_wh = np.full(prediction_hours, 0) 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 # Initialize the energy management system with the respective parameters
ems = EnergieManagementSystem( ems = EnergieManagementSystem(
config=tmp_config.eos, tmp_config.eos,
pv_prognose_wh=pv_prognose_wh, EnergieManagementSystemParameters(
strompreis_euro_pro_wh=strompreis_euro_pro_wh, pv_prognose_wh=pv_prognose_wh,
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_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, eauto=eauto,
gesamtlast=gesamtlast,
haushaltsgeraet=home_appliance, haushaltsgeraet=home_appliance,
wechselrichter=wechselrichter, wechselrichter=wechselrichter,
) )
@ -163,7 +180,7 @@ def test_simulation(create_ems_instance):
"akku_soc_pro_stunde", "akku_soc_pro_stunde",
"Einnahmen_Euro_pro_Stunde", "Einnahmen_Euro_pro_Stunde",
"Gesamtbilanz_Euro", "Gesamtbilanz_Euro",
"E-Auto_SoC_pro_Stunde", "EAuto_SoC_pro_Stunde",
"Gesamteinnahmen_Euro", "Gesamteinnahmen_Euro",
"Gesamtkosten_Euro", "Gesamtkosten_Euro",
"Verluste_Pro_Stunde", "Verluste_Pro_Stunde",

View File

@ -5,7 +5,11 @@ from unittest.mock import patch
import pytest import pytest
from akkudoktoreos.class_optimize import optimization_problem from akkudoktoreos.class_optimize import (
OptimizationParameters,
OptimizeResponse,
optimization_problem,
)
from akkudoktoreos.config import AppConfig from akkudoktoreos.config import AppConfig
DIR_TESTDATA = Path(__file__).parent / "testdata" DIR_TESTDATA = Path(__file__).parent / "testdata"
@ -46,7 +50,7 @@ def test_optimize(
# Load input and output data # Load input and output data
file = DIR_TESTDATA / fn_in file = DIR_TESTDATA / fn_in
with file.open("r") as f_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 file = DIR_TESTDATA / fn_out
with file.open("r") as f_out: with file.open("r") as f_out:
@ -59,7 +63,7 @@ def test_optimize(
pytest.skip() pytest.skip()
# Call the optimization function # 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: # with open(f"new_{fn_out}", "w") as f_out:
# from akkudoktoreos.class_numpy_encoder import NumpyEncoder # from akkudoktoreos.class_numpy_encoder import NumpyEncoder
# json_data_str = NumpyEncoder.dumps(ergebnis) # json_data_str = NumpyEncoder.dumps(ergebnis)
@ -72,3 +76,5 @@ def test_optimize(
# The function creates a visualization result PDF as a side-effect. # The function creates a visualization result PDF as a side-effect.
visualisiere_ergebnisse_patch.assert_called_once() visualisiere_ergebnisse_patch.assert_called_once()
OptimizeResponse(**ergebnis)

19
tests/test_openapi.py Normal file
View 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

View File

@ -1,52 +1,58 @@
{ {
"preis_euro_pro_wh_akku": 0.0001, "ems": {
"pv_soc": 80, "preis_euro_pro_wh_akku": 0.0001,
"pv_akku_cap": 26400, "einspeiseverguetung_euro_pro_wh": 0.00007,
"year_energy": 4100000, "gesamtlast": [
"einspeiseverguetung_euro_pro_wh": 0.00007, 676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
"max_heizleistung": 1000, 1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
"gesamtlast": [ 1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00, 488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12, 871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
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, "pv_prognose_wh": [
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97 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,
"pv_forecast": [ 0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69, 4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
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, "strompreis_euro_pro_wh": [
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0 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": [ "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.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, 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, 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 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": [ "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, 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, 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, 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 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
} }

View File

@ -1,47 +1,51 @@
{ {
"preis_euro_pro_wh_akku": 0.0001, "ems": {
"pv_soc": 80, "preis_euro_pro_wh_akku": 0.0001,
"pv_akku_cap": 26400, "einspeiseverguetung_euro_pro_wh": 0.00007,
"year_energy": 4100000, "gesamtlast": [
"einspeiseverguetung_euro_pro_wh": 0.00007, 676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00,
"max_heizleistung": 1000, 1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12,
"gesamtlast": [ 1178.71, 1050.98, 988.56, 912.38, 704.61, 516.37, 868.05, 694.34, 608.79, 556.31,
676.71, 876.19, 527.13, 468.88, 531.38, 517.95, 483.15, 472.28, 1011.68, 995.00, 488.89, 506.91, 804.89, 1141.98, 1056.97, 992.46, 1155.99, 827.01, 1257.98, 1232.67,
1053.07, 1063.91, 1320.56, 1132.03, 1163.67, 1176.82, 1216.22, 1103.78, 1129.12, 871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97
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, "pv_prognose_wh": [
871.26, 860.88, 1158.03, 1222.72, 1221.04, 949.99, 987.01, 733.99, 592.97 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,
"pv_forecast": [ 0, 0, 0, 0, 0, 5.04, 335.59, 705.32, 1121.12, 1604.79, 2157.38, 1433.25, 5718.49,
0, 0, 0, 0, 0, 0, 0, 8.05, 352.91, 728.51, 930.28, 1043.25, 1106.74, 1161.69, 4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0
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, "strompreis_euro_pro_wh": [
4553.96, 3027.55, 2574.46, 1720.4, 963.4, 383.3, 0, 0, 0 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": [ "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.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, 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, 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 22.7, 23.1, 23.1, 22.8, 21.8, 20.2, 19.1, 18.0, 17.4
], ],
"strompreis_euro_pro_wh": [ "start_solution": null
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
}

View File

@ -392,7 +392,7 @@
0.0 0.0
], ],
"Gesamtbilanz_Euro": 1.3505190567851246, "Gesamtbilanz_Euro": 1.3505190567851246,
"E-Auto_SoC_pro_Stunde": [ "EAuto_SoC_pro_Stunde": [
54.0, 54.0,
54.0, 54.0,
54.0, 54.0,
@ -727,371 +727,5 @@
1, 1,
1 1
], ],
"spuelstart": null, "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
]
}
} }

View File

@ -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 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, "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 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, "Gesamteinnahmen_Euro": 1.0602444999999998,
@ -62,38 +62,5 @@
"start_solution": [ "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 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, "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
]
}
} }

View File

@ -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 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, "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 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, "Gesamteinnahmen_Euro": 0.5565300999999999,
@ -62,38 +62,5 @@
"start_solution": [ "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 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, "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
]
}
} }

View File

@ -72,7 +72,7 @@
0.0, 0.0, 0.0, 0.0, 0.0 0.0, 0.0, 0.0, 0.0, 0.0
], ],
"Gesamtbilanz_Euro": 27.732796636363638, "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, 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,
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,