mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-04-19 08:55:15 +00:00
Add Documentation 2 (#334)
Add documentation that covers: - configuration - prediction Add Python scripts that support automatic documentation generation for configuration data defined with pydantic. Adapt EOS configuration to provide more methods for REST API and automatic documentation generation. Adapt REST API to allow for EOS configuration file load and save. Sort REST API on generation of openapi markdown for docs. Move logutil to core/logging to allow configuration of logging by standard config. Make Akkudoktor predictions always start extraction of prediction data at start of day. Previously extraction started at actual hour. This is to support the code that assumes prediction data to start at start of day. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
parent
03ec729e50
commit
d4e31d556a
89
docs/akkudoktoreos/configuration.md
Normal file
89
docs/akkudoktoreos/configuration.md
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
% SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
The configuration controls all aspects of EOS: optimization, prediction, measurement, and energy
|
||||||
|
management.
|
||||||
|
|
||||||
|
## Storing Configuration
|
||||||
|
|
||||||
|
EOS stores configuration data in a **key-value store**, where a `configuration key` refers to the
|
||||||
|
unique identifier used to store and retrieve specific configuration data. Note that the key-value
|
||||||
|
store is memory-based, meaning all stored data will be lost upon restarting the EOS REST server if
|
||||||
|
not saved to the `EOS configuration file`.
|
||||||
|
|
||||||
|
Some `configuration keys` are read-only and cannot be altered. These keys are either set up by other
|
||||||
|
means, such as environment variables, or determined from other information.
|
||||||
|
|
||||||
|
Several endpoints of the EOS REST server allow for the management and retrieval of configuration
|
||||||
|
data.
|
||||||
|
|
||||||
|
### Save Configuration File
|
||||||
|
|
||||||
|
Use endpoint `PUT /v1/config/file` to save the current configuration to the
|
||||||
|
`EOS configuration file`.
|
||||||
|
|
||||||
|
### Load Configuration File
|
||||||
|
|
||||||
|
Use endpoint `POST /v1/config/update` to update the configuration from the `EOS configuration file`.
|
||||||
|
|
||||||
|
## Configuration Sources and Priorities
|
||||||
|
|
||||||
|
The configuration sources and their priorities are as follows:
|
||||||
|
|
||||||
|
1. **Settings**: Provided during runtime by the REST interface
|
||||||
|
2. **Environment Variables**: Defined at startup of the REST server and during runtime
|
||||||
|
3. **EOS Configuration File**: Read at startup of the REST server and on request
|
||||||
|
4. **Default Values**
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
Settings are sets of configuration data that take precedence over all other configuration data from
|
||||||
|
different sources. Note that settings are not persistent. To make the current configuration with the
|
||||||
|
current settings persistent, save the configuration to the `EOS configuration file`.
|
||||||
|
|
||||||
|
Use the following endpoints to change the current configuration settings:
|
||||||
|
|
||||||
|
- `PUT /v1/config`: Replaces the entire configuration settings.
|
||||||
|
- `PUT /v1/config/value`: Sets a specific configuration option.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
All `configuration keys` can be set by environment variables with the same name. EOS recognizes the
|
||||||
|
following special environment variables:
|
||||||
|
|
||||||
|
- `EOS_CONFIG_DIR`: The directory to search for an EOS configuration file.
|
||||||
|
- `EOS_DIR`: The directory used by EOS for data, which will also be searched for an EOS
|
||||||
|
configuration file.
|
||||||
|
- `EOS_LOGGING_LEVEL`: The logging level to use in EOS.
|
||||||
|
|
||||||
|
### EOS Configuration File
|
||||||
|
|
||||||
|
The EOS configuration file provides persistent storage for configuration data. It can be modified
|
||||||
|
directly or through the REST interface.
|
||||||
|
|
||||||
|
If you do not have a configuration file, it will be automatically created on the first startup of
|
||||||
|
the REST server in a system-dependent location.
|
||||||
|
|
||||||
|
To determine the location of the configuration file used by EOS, ask the REST server. The endpoint
|
||||||
|
`GET /v1/config` provides the `config_file_path` configuration key.
|
||||||
|
|
||||||
|
EOS searches for the configuration file in the following order:
|
||||||
|
|
||||||
|
1. The directory specified by the `EOS_CONFIG_DIR` environment variable
|
||||||
|
2. The directory specified by the `EOS_DIR` environment variable
|
||||||
|
3. A platform-specific default directory for EOS
|
||||||
|
4. The current working directory
|
||||||
|
|
||||||
|
The first available configuration file found in these directories is loaded. If no configuration
|
||||||
|
file is found, a default configuration file is created in the platform-specific default directory,
|
||||||
|
and default settings are loaded into it.
|
||||||
|
|
||||||
|
### Default Values
|
||||||
|
|
||||||
|
Some of the `configuration keys` have default values by definition. For most of the
|
||||||
|
`configuration keys` the default value is just `None`, which means no default value.
|
||||||
|
|
||||||
|
```{eval-sh}
|
||||||
|
./scripts/generate_config_md.py | ./scripts/extract_markdown.py --input-stdin --heading-level 1
|
||||||
|
```
|
@ -30,7 +30,7 @@ The measurement data must be or is provided in one of the following formats:
|
|||||||
|
|
||||||
A dictionary with the following structure:
|
A dictionary with the following structure:
|
||||||
|
|
||||||
```JSON
|
```python
|
||||||
{
|
{
|
||||||
"start_datetime": "2024-01-01 00:00:00",
|
"start_datetime": "2024-01-01 00:00:00",
|
||||||
"interval": "1 Hour",
|
"interval": "1 Hour",
|
||||||
|
@ -21,7 +21,7 @@ data is lost on re-start of the EOS REST server.
|
|||||||
Most predictions can be sourced from various providers. The specific provider to use is configured
|
Most predictions can be sourced from various providers. The specific provider to use is configured
|
||||||
in the EOS configuration. For example:
|
in the EOS configuration. For example:
|
||||||
|
|
||||||
```plaintext
|
```python
|
||||||
weather_provider = "ClearOutside"
|
weather_provider = "ClearOutside"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ The prediction data must be provided in one of the following formats:
|
|||||||
|
|
||||||
A dictionary with the following structure:
|
A dictionary with the following structure:
|
||||||
|
|
||||||
```JSON
|
```python
|
||||||
{
|
{
|
||||||
"start_datetime": "2024-01-01 00:00:00",
|
"start_datetime": "2024-01-01 00:00:00",
|
||||||
"interval": "1 Hour",
|
"interval": "1 Hour",
|
||||||
@ -214,6 +214,67 @@ Configuration options:
|
|||||||
- `pvforecastimport_file_path`: Path to the file to import PV forecast data from.
|
- `pvforecastimport_file_path`: Path to the file to import PV forecast data from.
|
||||||
- `pvforecastimport_json`: JSON string, dictionary of PV forecast value lists.
|
- `pvforecastimport_json`: JSON string, dictionary of PV forecast value lists.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
Some of the configuration options directly follow the [PVGIS](https://joint-research-centre.ec.europa.eu/photovoltaic-geographical-information-system-pvgis/getting-started-pvgis/pvgis-user-manual_en) nomenclature.
|
||||||
|
|
||||||
|
Detailed definitions taken from **PVGIS**:
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_pvtechchoice`
|
||||||
|
|
||||||
|
The performance of PV modules depends on the temperature and on the solar irradiance, but the exact dependence varies between different types of PV modules. At the moment we can estimate the losses due to temperature and irradiance effects for the following types of modules: crystalline silicon cells; thin film modules made from CIS or CIGS and thin film modules made from Cadmium Telluride (CdTe).
|
||||||
|
|
||||||
|
For other technologies (especially various amorphous technologies), this correction cannot be calculated here. If you choose one of the first three options here the calculation of performance will take into account the temperature dependence of the performance of the chosen technology. If you choose the other option (other/unknown), the calculation will assume a loss of 8% of power due to temperature effects (a generic value which has found to be reasonable for temperate climates).
|
||||||
|
|
||||||
|
PV power output also depends on the spectrum of the solar radiation. PVGIS can calculate how the variations of the spectrum of sunlight affects the overall energy production from a PV system. At the moment this calculation can be done for crystalline silicon and CdTe modules. Note that this calculation is not yet available when using the NSRDB solar radiation database.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_peakpower`
|
||||||
|
|
||||||
|
This is the power that the manufacturer declares that the PV array can produce under standard test conditions (STC), which are a constant 1000W of solar irradiation per square meter in the plane of the array, at an array temperature of 25°C. The peak power should be entered in kilowatt-peak (kWp). If you do not know the declared peak power of your modules but instead know the area of the modules and the declared conversion efficiency (in percent), you can calculate the peak power as power = area * efficiency / 100.
|
||||||
|
|
||||||
|
Bifacial modules: PVGIS doesn't make specific calculations for bifacial modules at present. Users who wish to explore the possible benefits of this technology can input the power value for Bifacial Nameplate Irradiance. This can also be can also be estimated from the front side peak power P_STC value and the bifaciality factor, φ (if reported in the module data sheet) as: P_BNPI = P_STC * (1 + φ * 0.135). NB this bifacial approach is not appropriate for BAPV or BIPV installations or for modules mounting on a N-S axis i.e. facing E-W.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_loss`
|
||||||
|
|
||||||
|
The estimated system losses are all the losses in the system, which cause the power actually delivered to the electricity grid to be lower than the power produced by the PV modules. There are several causes for this loss, such as losses in cables, power inverters, dirt (sometimes snow) on the modules and so on. Over the years the modules also tend to lose a bit of their power, so the average yearly output over the lifetime of the system will be a few percent lower than the output in the first years.
|
||||||
|
|
||||||
|
We have given a default value of 14% for the overall losses. If you have a good idea that your value will be different (maybe due to a really high-efficiency inverter) you may reduce this value a little.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_mountingplace`
|
||||||
|
|
||||||
|
For fixed (non-tracking) systems, the way the modules are mounted will have an influence on the temperature of the module, which in turn affects the efficiency. Experiments have shown that if the movement of air behind the modules is restricted, the modules can get considerably hotter (up to 15°C at 1000W/m2 of sunlight).
|
||||||
|
|
||||||
|
In PVGIS there are two possibilities: free-standing, meaning that the modules are mounted on a rack with air flowing freely behind the modules; and building- integrated, which means that the modules are completely built into the structure of the wall or roof of a building, with no air movement behind the modules.
|
||||||
|
|
||||||
|
Some types of mounting are in between these two extremes, for instance if the modules are mounted on a roof with curved roof tiles, allowing air to move behind the modules. In such cases, the performance will be somewhere between the results of the two calculations that are possible here.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_userhorizon`
|
||||||
|
|
||||||
|
Elevation of horizon in degrees, at equally spaced azimuth clockwise from north. In the user horizon
|
||||||
|
data each number represents the horizon height in degrees in a certain compass direction around the
|
||||||
|
point of interest. The horizon heights should be given in a clockwise direction starting at North;
|
||||||
|
that is, from North, going to East, South, West, and back to North. The values are assumed to
|
||||||
|
represent equal angular distance around the horizon. For instance, if you have 36 values, the first
|
||||||
|
point is due north, the next is 10 degrees east of north, and so on, until the last point, 10
|
||||||
|
degrees west of north.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
Most of the configuration options are in line with the [PVLib](https://pvlib-python.readthedocs.io/en/stable/_modules/pvlib/iotools/pvgis.html) definition for PVGIS data.
|
||||||
|
|
||||||
|
Detailed definitions from **PVLib** for PVGIS data.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_surface_tilt`:
|
||||||
|
|
||||||
|
Tilt angle from horizontal plane.
|
||||||
|
|
||||||
|
- `pvforecast<0..5>_surface_azimuth`
|
||||||
|
|
||||||
|
Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180,
|
||||||
|
west=270). This is offset 180 degrees from the convention used by PVGIS.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
### PVForecastAkkudoktor Provider
|
### PVForecastAkkudoktor Provider
|
||||||
|
|
||||||
The `PVForecastAkkudoktor` provider retrieves the PV power forecast data directly from
|
The `PVForecastAkkudoktor` provider retrieves the PV power forecast data directly from
|
||||||
|
@ -14,6 +14,7 @@ welcome.md
|
|||||||
akkudoktoreos/about.md
|
akkudoktoreos/about.md
|
||||||
develop/getting_started.md
|
develop/getting_started.md
|
||||||
develop/CONTRIBUTING.md
|
develop/CONTRIBUTING.md
|
||||||
|
akkudoktoreos/configuration.md
|
||||||
akkudoktoreos/prediction.md
|
akkudoktoreos/prediction.md
|
||||||
akkudoktoreos/measurement.md
|
akkudoktoreos/measurement.md
|
||||||
akkudoktoreos/serverapi.md
|
akkudoktoreos/serverapi.md
|
||||||
|
3132
openapi.json
3132
openapi.json
File diff suppressed because it is too large
Load Diff
161
scripts/generate_config_md.py
Executable file
161
scripts/generate_config_md.py
Executable file
@ -0,0 +1,161 @@
|
|||||||
|
#!.venv/bin/python
|
||||||
|
"""Utility functions for Configuration specification generation."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from akkudoktoreos.config.config import get_config
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
config_eos = get_config()
|
||||||
|
|
||||||
|
# Fixed set of prefixes to filter configuration values and their respective titles
|
||||||
|
CONFIG_PREFIXES = {
|
||||||
|
"battery": "Battery Device Simulation Configuration",
|
||||||
|
"bev": "Battery Electric Vehicle Device Simulation Configuration",
|
||||||
|
"dishwasher": "Dishwasher Device Simulation Configuration",
|
||||||
|
"inverter": "Inverter Device Simulation Configuration",
|
||||||
|
"measurement": "Measurement Configuration",
|
||||||
|
"optimization": "General Optimization Configuration",
|
||||||
|
"server": "Server Configuration",
|
||||||
|
"elecprice": "Electricity Price Prediction Configuration",
|
||||||
|
"load": "Load Prediction Configuration",
|
||||||
|
"logging": "Logging Configuration",
|
||||||
|
"prediction": "General Prediction Configuration",
|
||||||
|
"pvforecast": "PV Forecast Configuration",
|
||||||
|
"weather": "Weather Forecast Configuration",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static set of configuration names to include in a separate table
|
||||||
|
GENERAL_CONFIGS = [
|
||||||
|
"config_default_file_path",
|
||||||
|
"config_file_path",
|
||||||
|
"config_folder_path",
|
||||||
|
"config_keys",
|
||||||
|
"config_keys_read_only",
|
||||||
|
"data_cache_path",
|
||||||
|
"data_cache_subpath",
|
||||||
|
"data_folder_path",
|
||||||
|
"data_output_path",
|
||||||
|
"data_output_subpath",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"package_root_path",
|
||||||
|
"timezone",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def generate_config_table_md(configs, title):
|
||||||
|
"""Generate a markdown table for given configurations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configs (dict): Configuration values with keys and their descriptions.
|
||||||
|
title (str): Title for the table.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The markdown table as a string.
|
||||||
|
"""
|
||||||
|
if not configs:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
table = f"## {title}\n\n"
|
||||||
|
table += ":::{table} " + f"{title}\n:widths: 10 10 5 5 30\n:align: left\n\n"
|
||||||
|
table += "| Name | Type | Read-Only | Default | Description |\n"
|
||||||
|
table += "| ---- | ---- | --------- | ------- | ----------- |\n"
|
||||||
|
for name, config in sorted(configs.items()):
|
||||||
|
type_name = config["type"]
|
||||||
|
if type_name.startswith("typing."):
|
||||||
|
type_name = type_name[len("typing.") :]
|
||||||
|
table += f"| `{config['name']}` | `{type_name}` | `{config['read-only']}` | `{config['default']}` | {config['description']} |\n"
|
||||||
|
table += ":::\n\n" # Add an empty line after the table
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def generate_config_md() -> str:
|
||||||
|
"""Generate configuration specification in Markdown with extra tables for prefixed values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The Markdown representation of the configuration spec.
|
||||||
|
"""
|
||||||
|
configs = {}
|
||||||
|
config_keys = config_eos.config_keys
|
||||||
|
config_keys_read_only = config_eos.config_keys_read_only
|
||||||
|
for config_key in config_keys:
|
||||||
|
config = {}
|
||||||
|
config["name"] = config_key
|
||||||
|
config["value"] = getattr(config_eos, config_key)
|
||||||
|
|
||||||
|
if config_key in config_keys_read_only:
|
||||||
|
config["read-only"] = "ro"
|
||||||
|
computed_field_info = config_eos.__pydantic_decorators__.computed_fields[
|
||||||
|
config_key
|
||||||
|
].info
|
||||||
|
config["default"] = "N/A"
|
||||||
|
config["description"] = computed_field_info.description
|
||||||
|
config["type"] = str(computed_field_info.return_type)
|
||||||
|
else:
|
||||||
|
config["read-only"] = "rw"
|
||||||
|
field_info = config_eos.model_fields[config_key]
|
||||||
|
config["default"] = field_info.default
|
||||||
|
config["description"] = field_info.description
|
||||||
|
config["type"] = str(field_info.annotation)
|
||||||
|
|
||||||
|
configs[config_key] = config
|
||||||
|
|
||||||
|
# Generate markdown for the main table
|
||||||
|
markdown = "# Configuration Table\n\n"
|
||||||
|
|
||||||
|
# Generate table for general configuration names
|
||||||
|
general_configs = {k: v for k, v in configs.items() if k in GENERAL_CONFIGS}
|
||||||
|
for k in general_configs.keys():
|
||||||
|
del configs[k] # Remove general configs from the main configs dictionary
|
||||||
|
markdown += generate_config_table_md(general_configs, "General Configuration Values")
|
||||||
|
|
||||||
|
non_prefixed_configs = {k: v for k, v in configs.items()}
|
||||||
|
|
||||||
|
# Generate tables for each prefix (sorted by value) and remove prefixed configs from the main dictionary
|
||||||
|
sorted_prefixes = sorted(CONFIG_PREFIXES.items(), key=lambda item: item[1])
|
||||||
|
for prefix, title in sorted_prefixes:
|
||||||
|
prefixed_configs = {k: v for k, v in configs.items() if k.startswith(prefix)}
|
||||||
|
for k in prefixed_configs.keys():
|
||||||
|
del non_prefixed_configs[k]
|
||||||
|
markdown += generate_config_table_md(prefixed_configs, title)
|
||||||
|
|
||||||
|
# Generate markdown for the remaining non-prefixed configs if any
|
||||||
|
if non_prefixed_configs:
|
||||||
|
markdown += generate_config_table_md(non_prefixed_configs, "Other Configuration Values")
|
||||||
|
|
||||||
|
return markdown
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function to run the generation of the Configuration specification as Markdown."""
|
||||||
|
parser = argparse.ArgumentParser(description="Generate Configuration Specification as Markdown")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-file",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="File to write the Configuration Specification to",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_md = generate_config_md()
|
||||||
|
if args.output_file:
|
||||||
|
# Write to file
|
||||||
|
with open(args.output_file, "w") as f:
|
||||||
|
f.write(config_md)
|
||||||
|
else:
|
||||||
|
# Write to std output
|
||||||
|
print(config_md)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during Configuration Specification generation: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -189,8 +189,10 @@ def openapi_to_markdown(openapi_json: dict) -> str:
|
|||||||
|
|
||||||
markdown += "**Endpoints**:\n\n"
|
markdown += "**Endpoints**:\n\n"
|
||||||
paths = openapi_json.get("paths", {})
|
paths = openapi_json.get("paths", {})
|
||||||
for path, methods in paths.items():
|
for path in sorted(paths):
|
||||||
for method, details in methods.items():
|
methods = paths[path]
|
||||||
|
for method in sorted(methods):
|
||||||
|
details = methods[method]
|
||||||
markdown += format_endpoint(path, method, details)
|
markdown += format_endpoint(path, method, details)
|
||||||
|
|
||||||
# Assure the is no double \n at end of file
|
# Assure the is no double \n at end of file
|
||||||
|
@ -20,6 +20,8 @@ from pydantic import Field, ValidationError, computed_field
|
|||||||
# settings
|
# settings
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.core.coreabc import SingletonMixin
|
from akkudoktoreos.core.coreabc import SingletonMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
from akkudoktoreos.core.logsettings import LoggingCommonSettings
|
||||||
from akkudoktoreos.devices.devices import DevicesCommonSettings
|
from akkudoktoreos.devices.devices import DevicesCommonSettings
|
||||||
from akkudoktoreos.measurement.measurement import MeasurementCommonSettings
|
from akkudoktoreos.measurement.measurement import MeasurementCommonSettings
|
||||||
from akkudoktoreos.optimization.optimization import OptimizationCommonSettings
|
from akkudoktoreos.optimization.optimization import OptimizationCommonSettings
|
||||||
@ -34,7 +36,6 @@ from akkudoktoreos.prediction.pvforecastimport import PVForecastImportCommonSett
|
|||||||
from akkudoktoreos.prediction.weather import WeatherCommonSettings
|
from akkudoktoreos.prediction.weather import WeatherCommonSettings
|
||||||
from akkudoktoreos.prediction.weatherimport import WeatherImportCommonSettings
|
from akkudoktoreos.prediction.weatherimport import WeatherImportCommonSettings
|
||||||
from akkudoktoreos.server.server import ServerCommonSettings
|
from akkudoktoreos.server.server import ServerCommonSettings
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
from akkudoktoreos.utils.utils import UtilsCommonSettings
|
from akkudoktoreos.utils.utils import UtilsCommonSettings
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@ -90,6 +91,7 @@ class ConfigCommonSettings(SettingsBaseModel):
|
|||||||
|
|
||||||
class SettingsEOS(
|
class SettingsEOS(
|
||||||
ConfigCommonSettings,
|
ConfigCommonSettings,
|
||||||
|
LoggingCommonSettings,
|
||||||
DevicesCommonSettings,
|
DevicesCommonSettings,
|
||||||
MeasurementCommonSettings,
|
MeasurementCommonSettings,
|
||||||
OptimizationCommonSettings,
|
OptimizationCommonSettings,
|
||||||
@ -188,7 +190,13 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
|
|||||||
@property
|
@property
|
||||||
def config_default_file_path(self) -> Path:
|
def config_default_file_path(self) -> Path:
|
||||||
"""Compute the default config file path."""
|
"""Compute the default config file path."""
|
||||||
return Path(__file__).parent.parent.joinpath("data/default.config.json")
|
return self.package_root_path.joinpath("data/default.config.json")
|
||||||
|
|
||||||
|
@computed_field # type: ignore[prop-decorator]
|
||||||
|
@property
|
||||||
|
def package_root_path(self) -> Path:
|
||||||
|
"""Compute the package root path."""
|
||||||
|
return Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
# Computed fields
|
# Computed fields
|
||||||
@computed_field # type: ignore[prop-decorator]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@ -200,6 +208,15 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
|
|||||||
key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys()))
|
key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys()))
|
||||||
return key_list
|
return key_list
|
||||||
|
|
||||||
|
# Computed fields
|
||||||
|
@computed_field # type: ignore[prop-decorator]
|
||||||
|
@property
|
||||||
|
def config_keys_read_only(self) -> List[str]:
|
||||||
|
"""Returns the keys of all read only fields in the configuration."""
|
||||||
|
key_list = []
|
||||||
|
key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys()))
|
||||||
|
return key_list
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initializes the singleton ConfigEOS instance.
|
"""Initializes the singleton ConfigEOS instance.
|
||||||
|
|
||||||
@ -239,7 +256,7 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
|
|||||||
settings (SettingsEOS): The settings to apply globally.
|
settings (SettingsEOS): The settings to apply globally.
|
||||||
force (Optional[bool]): If True, overwrites the existing settings completely.
|
force (Optional[bool]): If True, overwrites the existing settings completely.
|
||||||
If False, the new settings are merged to the existing ones with priority for
|
If False, the new settings are merged to the existing ones with priority for
|
||||||
the new ones.
|
the new ones. Defaults to False.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If settings are already set and `force` is not True or
|
ValueError: If settings are already set and `force` is not True or
|
||||||
@ -349,14 +366,23 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
|
|||||||
return cfile, True
|
return cfile, True
|
||||||
return config_dirs[0].joinpath(self.CONFIG_FILE_NAME), False
|
return config_dirs[0].joinpath(self.CONFIG_FILE_NAME), False
|
||||||
|
|
||||||
def from_config_file(self) -> None:
|
def settings_from_config_file(self) -> tuple[SettingsEOS, Path]:
|
||||||
"""Loads the configuration file settings for EOS.
|
"""Load settings from the configuration file.
|
||||||
|
|
||||||
|
If the config file does not exist, it will be created.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple of settings and path
|
||||||
|
settings (SettingsEOS): The settings defined by the EOS configuration file.
|
||||||
|
path (pathlib.Path): The path of the configuration file.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the configuration file is invalid or incomplete.
|
ValueError: If the configuration file is invalid or incomplete.
|
||||||
"""
|
"""
|
||||||
config_file, exists = self._get_config_file_path()
|
config_file, exists = self._get_config_file_path()
|
||||||
config_dir = config_file.parent
|
config_dir = config_file.parent
|
||||||
|
|
||||||
|
# Create config directory and copy default config if file does not exist
|
||||||
if not exists:
|
if not exists:
|
||||||
config_dir.mkdir(parents=True, exist_ok=True)
|
config_dir.mkdir(parents=True, exist_ok=True)
|
||||||
try:
|
try:
|
||||||
@ -366,18 +392,39 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
|
|||||||
config_file = self.config_default_file_path
|
config_file = self.config_default_file_path
|
||||||
config_dir = config_file.parent
|
config_dir = config_file.parent
|
||||||
|
|
||||||
|
# Load and validate the configuration file
|
||||||
with config_file.open("r", encoding=self.ENCODING) as f_in:
|
with config_file.open("r", encoding=self.ENCODING) as f_in:
|
||||||
try:
|
try:
|
||||||
json_str = f_in.read()
|
json_str = f_in.read()
|
||||||
ConfigEOS._file_settings = SettingsEOS.model_validate_json(json_str)
|
settings = SettingsEOS.model_validate_json(json_str)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
raise ValueError(f"Configuration '{config_file}' is incomplete or not valid: {exc}")
|
raise ValueError(f"Configuration '{config_file}' is incomplete or not valid: {exc}")
|
||||||
|
|
||||||
|
return settings, config_file
|
||||||
|
|
||||||
|
def from_config_file(self) -> tuple[SettingsEOS, Path]:
|
||||||
|
"""Load the configuration file settings for EOS.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple of settings and path
|
||||||
|
settings (SettingsEOS): The settings defined by the EOS configuration file.
|
||||||
|
path (pathlib.Path): The path of the configuration file.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the configuration file is invalid or incomplete.
|
||||||
|
"""
|
||||||
|
# Load settings from config file
|
||||||
|
ConfigEOS._file_settings, config_file = self.settings_from_config_file()
|
||||||
|
|
||||||
|
# Update configuration in memory
|
||||||
self.update()
|
self.update()
|
||||||
# Everthing worked, remember the values
|
|
||||||
self._config_folder_path = config_dir
|
# Everything worked, remember the values
|
||||||
|
self._config_folder_path = config_file.parent
|
||||||
self._config_file_path = config_file
|
self._config_file_path = config_file
|
||||||
|
|
||||||
|
return ConfigEOS._file_settings, config_file
|
||||||
|
|
||||||
def to_config_file(self) -> None:
|
def to_config_file(self) -> None:
|
||||||
"""Saves the current configuration to the configuration file.
|
"""Saves the current configuration to the configuration file.
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from typing import Any, ClassVar, Dict, Optional, Type
|
|||||||
from pendulum import DateTime
|
from pendulum import DateTime
|
||||||
from pydantic import computed_field
|
from pydantic import computed_field
|
||||||
|
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@ from pydantic import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from akkudoktoreos.core.coreabc import ConfigMixin, SingletonMixin, StartMixin
|
from akkudoktoreos.core.coreabc import ConfigMixin, SingletonMixin, StartMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import (
|
from akkudoktoreos.core.pydantic import (
|
||||||
PydanticBaseModel,
|
PydanticBaseModel,
|
||||||
PydanticDateTimeData,
|
PydanticDateTimeData,
|
||||||
PydanticDateTimeDataFrame,
|
PydanticDateTimeDataFrame,
|
||||||
)
|
)
|
||||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -583,20 +583,48 @@ class DataSequence(DataBase, MutableSequence):
|
|||||||
# Sort the list by datetime after adding/updating
|
# Sort the list by datetime after adding/updating
|
||||||
self.sort_by_datetime()
|
self.sort_by_datetime()
|
||||||
|
|
||||||
def update_value(self, date: DateTime, key: str, value: Any) -> None:
|
@overload
|
||||||
"""Updates a specific value in the data record for a given date.
|
def update_value(self, date: DateTime, key: str, value: Any) -> None: ...
|
||||||
|
|
||||||
If a record for the date exists, updates the specified attribute with the new value.
|
@overload
|
||||||
Otherwise, appends a new record with the given value and maintains chronological order.
|
def update_value(self, date: DateTime, values: Dict[str, Any]) -> None: ...
|
||||||
|
|
||||||
|
def update_value(self, date: DateTime, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Updates specific values in the data record for a given date.
|
||||||
|
|
||||||
|
If a record for the date exists, updates the specified attributes with the new values.
|
||||||
|
Otherwise, appends a new record with the given values and maintains chronological order.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date (datetime): The date for which the weather value is to be added or updated.
|
date (datetime): The date for which the values are to be added or updated.
|
||||||
key (str): The attribute name to be updated.
|
key (str), value (Any): Single key-value pair to update
|
||||||
value: The new value to set for the specified attribute.
|
OR
|
||||||
|
values (Dict[str, Any]): Dictionary of key-value pairs to update
|
||||||
|
OR
|
||||||
|
**kwargs: Key-value pairs as keyword arguments
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> update_value(date, 'temperature', 25.5)
|
||||||
|
>>> update_value(date, {'temperature': 25.5, 'humidity': 80})
|
||||||
|
>>> update_value(date, temperature=25.5, humidity=80)
|
||||||
"""
|
"""
|
||||||
self._validate_key_writable(key)
|
# Process input arguments into a dictionary
|
||||||
|
values: Dict[str, Any] = {}
|
||||||
|
if len(args) == 2: # Single key-value pair
|
||||||
|
values[args[0]] = args[1]
|
||||||
|
elif len(args) == 1 and isinstance(args[0], dict): # Dictionary input
|
||||||
|
values.update(args[0])
|
||||||
|
elif len(args) > 0: # Invalid number of arguments
|
||||||
|
raise ValueError("Expected either 2 arguments (key, value) or 1 dictionary argument")
|
||||||
|
values.update(kwargs) # Add any keyword arguments
|
||||||
|
|
||||||
|
# Validate all keys are writable
|
||||||
|
for key in values:
|
||||||
|
self._validate_key_writable(key)
|
||||||
|
|
||||||
# Ensure datetime objects are normalized
|
# Ensure datetime objects are normalized
|
||||||
date = to_datetime(date, to_maxtime=False)
|
date = to_datetime(date, to_maxtime=False)
|
||||||
|
|
||||||
# Check if a record with the given date already exists
|
# Check if a record with the given date already exists
|
||||||
for record in self.records:
|
for record in self.records:
|
||||||
if not isinstance(record.date_time, DateTime):
|
if not isinstance(record.date_time, DateTime):
|
||||||
@ -604,12 +632,13 @@ class DataSequence(DataBase, MutableSequence):
|
|||||||
f"Record date '{record.date_time}' is not a datetime, but a `{type(record.date_time).__name__}`."
|
f"Record date '{record.date_time}' is not a datetime, but a `{type(record.date_time).__name__}`."
|
||||||
)
|
)
|
||||||
if compare_datetimes(record.date_time, date).equal:
|
if compare_datetimes(record.date_time, date).equal:
|
||||||
# Update the DataRecord with the new value for the specified key
|
# Update the DataRecord with all new values
|
||||||
setattr(record, key, value)
|
for key, value in values.items():
|
||||||
|
setattr(record, key, value)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Create a new record and append to the list
|
# Create a new record and append to the list
|
||||||
record = self.record_class()(date_time=date, **{key: value})
|
record = self.record_class()(date_time=date, **values)
|
||||||
self.records.append(record)
|
self.records.append(record)
|
||||||
# Sort the list by datetime after adding/updating
|
# Sort the list by datetime after adding/updating
|
||||||
self.sort_by_datetime()
|
self.sort_by_datetime()
|
||||||
@ -841,7 +870,7 @@ class DataSequence(DataBase, MutableSequence):
|
|||||||
if start_index == 0:
|
if start_index == 0:
|
||||||
# No value before start
|
# No value before start
|
||||||
# Add dummy value
|
# Add dummy value
|
||||||
dates.insert(0, dates[0] - interval)
|
dates.insert(0, start_datetime - interval)
|
||||||
values.insert(0, values[0])
|
values.insert(0, values[0])
|
||||||
elif start_index > 1:
|
elif start_index > 1:
|
||||||
# Truncate all values before latest value before start_datetime
|
# Truncate all values before latest value before start_datetime
|
||||||
|
@ -7,12 +7,12 @@ from pydantic import ConfigDict, Field, computed_field, field_validator, model_v
|
|||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin
|
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||||
from akkudoktoreos.devices.battery import Battery
|
from akkudoktoreos.devices.battery import Battery
|
||||||
from akkudoktoreos.devices.generic import HomeAppliance
|
from akkudoktoreos.devices.generic import HomeAppliance
|
||||||
from akkudoktoreos.devices.inverter import Inverter
|
from akkudoktoreos.devices.inverter import Inverter
|
||||||
from akkudoktoreos.utils.datetimeutil import to_datetime
|
from akkudoktoreos.utils.datetimeutil import to_datetime
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
from akkudoktoreos.utils.utils import NumpyEncoder
|
from akkudoktoreos.utils.utils import NumpyEncoder
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
20
src/akkudoktoreos/core/logabc.py
Normal file
20
src/akkudoktoreos/core/logabc.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"""Abstract and base classes for logging."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def logging_str_to_level(level_str: str) -> int:
|
||||||
|
"""Convert log level string to logging level."""
|
||||||
|
if level_str == "DEBUG":
|
||||||
|
level = logging.DEBUG
|
||||||
|
elif level_str == "INFO":
|
||||||
|
level = logging.INFO
|
||||||
|
elif level_str == "WARNING":
|
||||||
|
level = logging.WARNING
|
||||||
|
elif level_str == "CRITICAL":
|
||||||
|
level = logging.CRITICAL
|
||||||
|
elif level_str == "ERROR":
|
||||||
|
level = logging.ERROR
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown loggin level: {level_str}")
|
||||||
|
return level
|
@ -15,19 +15,21 @@ Notes:
|
|||||||
- The logger supports rotating log files to prevent excessive log file size.
|
- The logger supports rotating log files to prevent excessive log file size.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging as pylogging
|
||||||
import os
|
import os
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logabc import logging_str_to_level
|
||||||
|
|
||||||
|
|
||||||
def get_logger(
|
def get_logger(
|
||||||
name: str,
|
name: str,
|
||||||
log_file: Optional[str] = None,
|
log_file: Optional[str] = None,
|
||||||
logging_level: Optional[str] = "INFO",
|
logging_level: Optional[str] = None,
|
||||||
max_bytes: int = 5000000,
|
max_bytes: int = 5000000,
|
||||||
backup_count: int = 5,
|
backup_count: int = 5,
|
||||||
) -> logging.Logger:
|
) -> pylogging.Logger:
|
||||||
"""Creates and configures a logger with a given name.
|
"""Creates and configures a logger with a given name.
|
||||||
|
|
||||||
The logger supports logging to both the console and an optional log file. File logging is
|
The logger supports logging to both the console and an optional log file. File logging is
|
||||||
@ -48,31 +50,22 @@ def get_logger(
|
|||||||
logger.info("Application started")
|
logger.info("Application started")
|
||||||
"""
|
"""
|
||||||
# Create a logger with the specified name
|
# Create a logger with the specified name
|
||||||
logger = logging.getLogger(name)
|
logger = pylogging.getLogger(name)
|
||||||
logger.propagate = True
|
logger.propagate = True
|
||||||
if (env_level := os.getenv("EOS_LOGGING_LEVEL")) is not None:
|
if logging_level is not None:
|
||||||
logging_level = env_level
|
level = logging_str_to_level(logging_level)
|
||||||
if logging_level == "DEBUG":
|
logger.setLevel(level)
|
||||||
level = logging.DEBUG
|
|
||||||
elif logging_level == "INFO":
|
|
||||||
level = logging.INFO
|
|
||||||
elif logging_level == "WARNING":
|
|
||||||
level = logging.WARNING
|
|
||||||
elif logging_level == "ERROR":
|
|
||||||
level = logging.ERROR
|
|
||||||
else:
|
|
||||||
level = logging.DEBUG
|
|
||||||
logger.setLevel(level)
|
|
||||||
|
|
||||||
# The log message format
|
# The log message format
|
||||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
formatter = pylogging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||||
|
|
||||||
# Prevent loggers from being added multiple times
|
# Prevent loggers from being added multiple times
|
||||||
# There may already be a logger from pytest
|
# There may already be a logger from pytest
|
||||||
if not logger.handlers:
|
if not logger.handlers:
|
||||||
# Create a console handler with a standard output stream
|
# Create a console handler with a standard output stream
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = pylogging.StreamHandler()
|
||||||
console_handler.setLevel(level)
|
if logging_level is not None:
|
||||||
|
console_handler.setLevel(level)
|
||||||
console_handler.setFormatter(formatter)
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
# Add the console handler to the logger
|
# Add the console handler to the logger
|
||||||
@ -88,7 +81,8 @@ def get_logger(
|
|||||||
|
|
||||||
# Create a rotating file handler
|
# Create a rotating file handler
|
||||||
file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
|
file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
|
||||||
file_handler.setLevel(level)
|
if logging_level is not None:
|
||||||
|
file_handler.setLevel(level)
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
# Add the file handler to the logger
|
# Add the file handler to the logger
|
45
src/akkudoktoreos/core/logsettings.py
Normal file
45
src/akkudoktoreos/core/logsettings.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""Settings for logging.
|
||||||
|
|
||||||
|
Kept in an extra module to avoid cyclic dependencies on package import.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import Field, computed_field, field_validator
|
||||||
|
|
||||||
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logabc import logging_str_to_level
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingCommonSettings(SettingsBaseModel):
|
||||||
|
"""Common settings for logging."""
|
||||||
|
|
||||||
|
logging_level_default: Optional[str] = Field(
|
||||||
|
default=None, description="EOS default logging level."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validators
|
||||||
|
@field_validator("logging_level_default", mode="after")
|
||||||
|
@classmethod
|
||||||
|
def set_default_logging_level(cls, value: Optional[str]) -> Optional[str]:
|
||||||
|
if isinstance(value, str) and value.upper() == "NONE":
|
||||||
|
value = None
|
||||||
|
if value is None and (env_level := os.getenv("EOS_LOGGING_LEVEL")) is not None:
|
||||||
|
# Take default logging level from special environment variable
|
||||||
|
value = env_level
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
level = logging_str_to_level(value)
|
||||||
|
logging.getLogger().setLevel(level)
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Computed fields
|
||||||
|
@computed_field # type: ignore[prop-decorator]
|
||||||
|
@property
|
||||||
|
def logging_level_root(self) -> str:
|
||||||
|
"""Root logger logging level."""
|
||||||
|
level = logging.getLogger().getEffectiveLevel()
|
||||||
|
level_name = logging.getLevelName(level)
|
||||||
|
return level_name
|
@ -275,6 +275,7 @@ class PydanticDateTimeDataFrame(PydanticBaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@field_validator("tz")
|
@field_validator("tz")
|
||||||
|
@classmethod
|
||||||
def validate_timezone(cls, v: Optional[str]) -> Optional[str]:
|
def validate_timezone(cls, v: Optional[str]) -> Optional[str]:
|
||||||
"""Validate that the timezone is valid."""
|
"""Validate that the timezone is valid."""
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"load_name": null,
|
"load_name": null,
|
||||||
"load_provider": null,
|
"load_provider": null,
|
||||||
"loadakkudoktor_year_energy": null,
|
"loadakkudoktor_year_energy": null,
|
||||||
|
"logging_level": "INFO",
|
||||||
"longitude": 13.4,
|
"longitude": 13.4,
|
||||||
"optimization_ev_available_charge_rates_percent": null,
|
"optimization_ev_available_charge_rates_percent": null,
|
||||||
"optimization_hours": 48,
|
"optimization_hours": 48,
|
||||||
|
@ -3,8 +3,8 @@ from typing import Any, Optional
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
from akkudoktoreos.utils.utils import NumpyEncoder
|
from akkudoktoreos.utils.utils import NumpyEncoder
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
@ -6,13 +6,13 @@ from pydantic import Field, computed_field
|
|||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.core.coreabc import SingletonMixin
|
from akkudoktoreos.core.coreabc import SingletonMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.devices.battery import Battery
|
from akkudoktoreos.devices.battery import Battery
|
||||||
from akkudoktoreos.devices.devicesabc import DevicesBase
|
from akkudoktoreos.devices.devicesabc import DevicesBase
|
||||||
from akkudoktoreos.devices.generic import HomeAppliance
|
from akkudoktoreos.devices.generic import HomeAppliance
|
||||||
from akkudoktoreos.devices.inverter import Inverter
|
from akkudoktoreos.devices.inverter import Inverter
|
||||||
from akkudoktoreos.prediction.interpolator import SelfConsumptionPropabilityInterpolator
|
from akkudoktoreos.prediction.interpolator import SelfConsumptionPropabilityInterpolator
|
||||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ from akkudoktoreos.core.coreabc import (
|
|||||||
EnergyManagementSystemMixin,
|
EnergyManagementSystemMixin,
|
||||||
PredictionMixin,
|
PredictionMixin,
|
||||||
)
|
)
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ from typing import Optional
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@ from typing import Optional
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from scipy.interpolate import RegularGridInterpolator
|
from scipy.interpolate import RegularGridInterpolator
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.devices.battery import Battery
|
from akkudoktoreos.devices.battery import Battery
|
||||||
from akkudoktoreos.devices.devicesabc import DeviceBase
|
from akkudoktoreos.devices.devicesabc import DeviceBase
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ from pydantic import Field, computed_field
|
|||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.core.coreabc import SingletonMixin
|
from akkudoktoreos.core.coreabc import SingletonMixin
|
||||||
from akkudoktoreos.core.dataabc import DataImportMixin, DataRecord, DataSequence
|
from akkudoktoreos.core.dataabc import DataImportMixin, DataRecord, DataSequence
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from typing import List, Optional
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
from pydantic import ConfigDict
|
from pydantic import ConfigDict
|
||||||
|
|
||||||
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin
|
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from pydantic import Field, computed_field
|
from pydantic import Field, computed_field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ import requests
|
|||||||
from numpydantic import NDArray, Shape
|
from numpydantic import NDArray, Shape
|
||||||
from pydantic import Field, ValidationError
|
from pydantic import Field, ValidationError
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||||
from akkudoktoreos.prediction.elecpriceabc import ElecPriceDataRecord, ElecPriceProvider
|
from akkudoktoreos.prediction.elecpriceabc import ElecPriceDataRecord, ElecPriceProvider
|
||||||
from akkudoktoreos.utils.cacheutil import CacheFileStore, cache_in_file
|
from akkudoktoreos.utils.cacheutil import CacheFileStore, cache_in_file
|
||||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -218,17 +218,14 @@ class ElecPriceAkkudoktor(ElecPriceProvider):
|
|||||||
akkudoktor_value.marketpriceEurocentPerKWh / (100 * 1000) + charges_kwh / 1000
|
akkudoktor_value.marketpriceEurocentPerKWh / (100 * 1000) + charges_kwh / 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
if compare_datetimes(dt, self.start_datetime).lt:
|
# We provide prediction starting at start of day, to be compatible to old system.
|
||||||
# forecast data is too old
|
if compare_datetimes(dt, self.start_datetime.start_of("day")).lt:
|
||||||
|
# forecast data is too old - older than start_datetime with time set to 00:00:00
|
||||||
self.elecprice_8days[dt.hour, dt.day_of_week] = price_wh
|
self.elecprice_8days[dt.hour, dt.day_of_week] = price_wh
|
||||||
continue
|
continue
|
||||||
self.elecprice_8days[dt.hour, 7] = price_wh
|
self.elecprice_8days[dt.hour, 7] = price_wh
|
||||||
|
|
||||||
record = ElecPriceDataRecord(
|
self.update_value(dt, "elecprice_marketprice_wh", price_wh)
|
||||||
date_time=dt,
|
|
||||||
elecprice_marketprice_wh=price_wh,
|
|
||||||
)
|
|
||||||
self.append(record)
|
|
||||||
|
|
||||||
# Update 8day cache
|
# Update 8day cache
|
||||||
elecprice_cache_file.seek(0)
|
elecprice_cache_file.seek(0)
|
||||||
|
@ -12,9 +12,9 @@ from typing import Optional, Union
|
|||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.elecpriceabc import ElecPriceProvider
|
from akkudoktoreos.prediction.elecpriceabc import ElecPriceProvider
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ from typing import Optional
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LoadCommonSettings(SettingsBaseModel):
|
class LoadCommonSettings(SettingsBaseModel):
|
||||||
|
"""Common settings for loaod forecast providers."""
|
||||||
|
|
||||||
load_provider: Optional[str] = Field(
|
load_provider: Optional[str] = Field(
|
||||||
default=None, description="Load provider id of provider to be used."
|
default=None, description="Load provider id of provider to be used."
|
||||||
)
|
)
|
||||||
|
@ -9,8 +9,8 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
"""Retrieves load forecast data from Akkudoktor load profiles."""
|
"""Retrieves load forecast data from Akkudoktor load profiles."""
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.loadabc import LoadProvider
|
from akkudoktoreos.prediction.loadabc import LoadProvider
|
||||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ class LoadAkkudoktor(LoadProvider):
|
|||||||
|
|
||||||
def load_data(self) -> np.ndarray:
|
def load_data(self) -> np.ndarray:
|
||||||
"""Loads data from the Akkudoktor load file."""
|
"""Loads data from the Akkudoktor load file."""
|
||||||
load_file = Path(__file__).parent.parent.joinpath("data/load_profiles.npz")
|
load_file = self.config.package_root_path.joinpath("data/load_profiles.npz")
|
||||||
data_year_energy = None
|
data_year_energy = None
|
||||||
try:
|
try:
|
||||||
file_data = np.load(load_file)
|
file_data = np.load(load_file)
|
||||||
@ -107,23 +106,25 @@ class LoadAkkudoktor(LoadProvider):
|
|||||||
"""Adds the load means and standard deviations."""
|
"""Adds the load means and standard deviations."""
|
||||||
data_year_energy = self.load_data()
|
data_year_energy = self.load_data()
|
||||||
weekday_adjust, weekend_adjust = self._calculate_adjustment(data_year_energy)
|
weekday_adjust, weekend_adjust = self._calculate_adjustment(data_year_energy)
|
||||||
date = self.start_datetime
|
# We provide prediction starting at start of day, to be compatible to old system.
|
||||||
for i in range(self.config.prediction_hours):
|
# End date for prediction is prediction hours from now.
|
||||||
|
date = self.start_datetime.start_of("day")
|
||||||
|
end_date = self.start_datetime.add(hours=self.config.prediction_hours)
|
||||||
|
while compare_datetimes(date, end_date).lt:
|
||||||
# Extract mean (index 0) and standard deviation (index 1) for the given day and hour
|
# Extract mean (index 0) and standard deviation (index 1) for the given day and hour
|
||||||
# Day indexing starts at 0, -1 because of that
|
# Day indexing starts at 0, -1 because of that
|
||||||
hourly_stats = data_year_energy[date.day_of_year - 1, :, date.hour]
|
hourly_stats = data_year_energy[date.day_of_year - 1, :, date.hour]
|
||||||
self.update_value(date, "load_mean", hourly_stats[0])
|
values = {
|
||||||
self.update_value(date, "load_std", hourly_stats[1])
|
"load_mean": hourly_stats[0],
|
||||||
|
"load_std": hourly_stats[1],
|
||||||
|
}
|
||||||
if date.day_of_week < 5:
|
if date.day_of_week < 5:
|
||||||
# Monday to Friday (0..4)
|
# Monday to Friday (0..4)
|
||||||
self.update_value(
|
values["load_mean_adjusted"] = hourly_stats[0] + weekday_adjust[date.hour]
|
||||||
date, "load_mean_adjusted", hourly_stats[0] + weekday_adjust[date.hour]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# Saturday, Sunday (5, 6)
|
# Saturday, Sunday (5, 6)
|
||||||
self.update_value(
|
values["load_mean_adjusted"] = hourly_stats[0] + weekend_adjust[date.hour]
|
||||||
date, "load_mean_adjusted", hourly_stats[0] + weekend_adjust[date.hour]
|
self.update_value(date, values)
|
||||||
)
|
|
||||||
date += to_duration("1 hour")
|
date += to_duration("1 hour")
|
||||||
# We are working on fresh data (no cache), report update time
|
# We are working on fresh data (no cache), report update time
|
||||||
self.update_datetime = to_datetime(in_timezone=self.config.timezone)
|
self.update_datetime = to_datetime(in_timezone=self.config.timezone)
|
||||||
|
@ -12,9 +12,9 @@ from typing import Optional, Union
|
|||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.loadabc import LoadProvider
|
from akkudoktoreos.prediction.loadabc import LoadProvider
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ from akkudoktoreos.core.dataabc import (
|
|||||||
DataRecord,
|
DataRecord,
|
||||||
DataSequence,
|
DataSequence,
|
||||||
)
|
)
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any, ClassVar, List, Optional
|
|||||||
from pydantic import Field, computed_field
|
from pydantic import Field, computed_field
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast0_loss: Optional[float] = Field(
|
pvforecast0_loss: Optional[float] = Field(
|
||||||
default=None, description="Sum of PV system losses in percent"
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
)
|
)
|
||||||
pvforecast0_trackingtype: Optional[int] = Field(
|
pvforecast0_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
@ -98,7 +98,9 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
default="free",
|
default="free",
|
||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast1_loss: Optional[float] = Field(0, description="Sum of PV system losses in percent")
|
pvforecast1_loss: Optional[float] = Field(
|
||||||
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
|
)
|
||||||
pvforecast1_trackingtype: Optional[int] = Field(
|
pvforecast1_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
||||||
@ -152,7 +154,9 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
default="free",
|
default="free",
|
||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast2_loss: Optional[float] = Field(0, description="Sum of PV system losses in percent")
|
pvforecast2_loss: Optional[float] = Field(
|
||||||
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
|
)
|
||||||
pvforecast2_trackingtype: Optional[int] = Field(
|
pvforecast2_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
||||||
@ -206,7 +210,9 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
default="free",
|
default="free",
|
||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast3_loss: Optional[float] = Field(0, description="Sum of PV system losses in percent")
|
pvforecast3_loss: Optional[float] = Field(
|
||||||
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
|
)
|
||||||
pvforecast3_trackingtype: Optional[int] = Field(
|
pvforecast3_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
||||||
@ -260,7 +266,9 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
default="free",
|
default="free",
|
||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast4_loss: Optional[float] = Field(0, description="Sum of PV system losses in percent")
|
pvforecast4_loss: Optional[float] = Field(
|
||||||
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
|
)
|
||||||
pvforecast4_trackingtype: Optional[int] = Field(
|
pvforecast4_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
||||||
@ -314,7 +322,9 @@ class PVForecastCommonSettings(SettingsBaseModel):
|
|||||||
default="free",
|
default="free",
|
||||||
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
description="Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.",
|
||||||
)
|
)
|
||||||
pvforecast5_loss: Optional[float] = Field(0, description="Sum of PV system losses in percent")
|
pvforecast5_loss: Optional[float] = Field(
|
||||||
|
default=14.0, description="Sum of PV system losses in percent"
|
||||||
|
)
|
||||||
pvforecast5_trackingtype: Optional[int] = Field(
|
pvforecast5_trackingtype: Optional[int] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
description="Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.",
|
||||||
|
@ -9,8 +9,8 @@ from typing import List, Optional
|
|||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ from typing import Any, List, Optional, Union
|
|||||||
import requests
|
import requests
|
||||||
from pydantic import Field, ValidationError, computed_field
|
from pydantic import Field, ValidationError, computed_field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||||
from akkudoktoreos.prediction.pvforecastabc import (
|
from akkudoktoreos.prediction.pvforecastabc import (
|
||||||
PVForecastDataRecord,
|
PVForecastDataRecord,
|
||||||
@ -75,7 +76,6 @@ from akkudoktoreos.prediction.pvforecastabc import (
|
|||||||
)
|
)
|
||||||
from akkudoktoreos.utils.cacheutil import cache_in_file
|
from akkudoktoreos.utils.cacheutil import cache_in_file
|
||||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
|
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -283,27 +283,21 @@ class PVForecastAkkudoktor(PVForecastProvider):
|
|||||||
original_datetime = akkudoktor_data.values[0][i].datetime
|
original_datetime = akkudoktor_data.values[0][i].datetime
|
||||||
dt = to_datetime(original_datetime, in_timezone=self.config.timezone)
|
dt = to_datetime(original_datetime, in_timezone=self.config.timezone)
|
||||||
|
|
||||||
# iso_datetime = parser.parse(original_datetime).isoformat() # Konvertiere zu ISO-Format
|
# We provide prediction starting at start of day, to be compatible to old system.
|
||||||
# print()
|
if compare_datetimes(dt, self.start_datetime.start_of("day")).lt:
|
||||||
# Optional: 2 Stunden abziehen, um die Zeitanpassung zu testen
|
|
||||||
# adjusted_datetime = parser.parse(original_datetime) - timedelta(hours=2)
|
|
||||||
# print(f"Angepasste Zeitstempel: {adjusted_datetime.isoformat()}")
|
|
||||||
|
|
||||||
if compare_datetimes(dt, self.start_datetime).lt:
|
|
||||||
# forecast data is too old
|
# forecast data is too old
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sum_dc_power = sum(values[i].dcPower for values in akkudoktor_data.values)
|
sum_dc_power = sum(values[i].dcPower for values in akkudoktor_data.values)
|
||||||
sum_ac_power = sum(values[i].power for values in akkudoktor_data.values)
|
sum_ac_power = sum(values[i].power for values in akkudoktor_data.values)
|
||||||
|
|
||||||
record = PVForecastAkkudoktorDataRecord(
|
data = {
|
||||||
date_time=dt, # Verwende angepassten Zeitstempel
|
"pvforecast_dc_power": sum_dc_power,
|
||||||
pvforecast_dc_power=sum_dc_power,
|
"pvforecast_ac_power": sum_ac_power,
|
||||||
pvforecast_ac_power=sum_ac_power,
|
"pvforecastakkudoktor_wind_speed_10m": akkudoktor_data.values[0][i].windspeed_10m,
|
||||||
pvforecastakkudoktor_wind_speed_10m=akkudoktor_data.values[0][i].windspeed_10m,
|
"pvforecastakkudoktor_temp_air": akkudoktor_data.values[0][i].temperature,
|
||||||
pvforecastakkudoktor_temp_air=akkudoktor_data.values[0][i].temperature,
|
}
|
||||||
)
|
self.update_value(dt, data)
|
||||||
self.append(record)
|
|
||||||
|
|
||||||
if len(self) < self.config.prediction_hours:
|
if len(self) < self.config.prediction_hours:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -12,9 +12,9 @@ from typing import Optional, Union
|
|||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
||||||
from akkudoktoreos.prediction.pvforecastabc import PVForecastProvider
|
from akkudoktoreos.prediction.pvforecastabc import PVForecastProvider
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ import pandas as pd
|
|||||||
import pvlib
|
import pvlib
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -13,10 +13,10 @@ import pandas as pd
|
|||||||
import pvlib
|
import pvlib
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider
|
from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider
|
||||||
from akkudoktoreos.utils.cacheutil import cache_in_file
|
from akkudoktoreos.utils.cacheutil import cache_in_file
|
||||||
from akkudoktoreos.utils.datetimeutil import to_datetime
|
from akkudoktoreos.utils.datetimeutil import to_datetime
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ import pandas as pd
|
|||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider
|
from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider
|
||||||
from akkudoktoreos.utils.cacheutil import cache_in_file
|
from akkudoktoreos.utils.cacheutil import cache_in_file
|
||||||
from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration, to_timezone
|
from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration, to_timezone
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ from typing import Optional, Union
|
|||||||
from pydantic import Field, field_validator
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
from akkudoktoreos.prediction.predictionabc import PredictionImportProvider
|
||||||
from akkudoktoreos.prediction.weatherabc import WeatherProvider
|
from akkudoktoreos.prediction.weatherabc import WeatherProvider
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ from pathlib import Path
|
|||||||
from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union
|
from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import pandas as pd
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI, Query, Request
|
from fastapi import FastAPI, Query, Request
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
@ -15,6 +14,7 @@ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, Resp
|
|||||||
|
|
||||||
from akkudoktoreos.config.config import ConfigEOS, SettingsEOS, get_config
|
from akkudoktoreos.config.config import ConfigEOS, SettingsEOS, get_config
|
||||||
from akkudoktoreos.core.ems import get_ems
|
from akkudoktoreos.core.ems import get_ems
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.core.pydantic import (
|
from akkudoktoreos.core.pydantic import (
|
||||||
PydanticBaseModel,
|
PydanticBaseModel,
|
||||||
PydanticDateTimeData,
|
PydanticDateTimeData,
|
||||||
@ -29,7 +29,6 @@ from akkudoktoreos.optimization.genetic import (
|
|||||||
)
|
)
|
||||||
from akkudoktoreos.prediction.prediction import get_prediction
|
from akkudoktoreos.prediction.prediction import get_prediction
|
||||||
from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration
|
from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
config_eos = get_config()
|
config_eos = get_config()
|
||||||
@ -182,33 +181,112 @@ class PdfResponse(FileResponse):
|
|||||||
media_type = "application/pdf"
|
media_type = "application/pdf"
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/v1/config/value")
|
||||||
|
def fastapi_config_value_put(
|
||||||
|
key: Annotated[str, Query(description="configuration key")],
|
||||||
|
value: Annotated[Any, Query(description="configuration value")],
|
||||||
|
) -> ConfigEOS:
|
||||||
|
"""Set the configuration option in the settings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): configuration key
|
||||||
|
value (Any): configuration value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after the write.
|
||||||
|
"""
|
||||||
|
if key not in config_eos.config_keys:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
|
if key in config_eos.config_keys_read_only:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is read only.")
|
||||||
|
try:
|
||||||
|
setattr(config_eos, key, value)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Error on update of configuration: {e}")
|
||||||
|
return config_eos
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/v1/config/update")
|
||||||
|
def fastapi_config_update_post() -> ConfigEOS:
|
||||||
|
"""Update the configuration from the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after update.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_, config_file_path = config_eos.from_config_file()
|
||||||
|
except:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Cannot update configuration from file '{config_file_path}'.",
|
||||||
|
)
|
||||||
|
return config_eos
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/v1/config/file")
|
||||||
|
def fastapi_config_file_get() -> SettingsEOS:
|
||||||
|
"""Get the settings as defined by the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
settings (SettingsEOS): The settings defined by the EOS configuration file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
settings, config_file_path = config_eos.settings_from_config_file()
|
||||||
|
except:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Cannot read configuration from file '{config_file_path}'.",
|
||||||
|
)
|
||||||
|
return settings
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/v1/config/file")
|
||||||
|
def fastapi_config_file_put() -> ConfigEOS:
|
||||||
|
"""Save the current configuration to the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration that was saved.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
config_eos.to_config_file()
|
||||||
|
except:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Cannot save configuration to file '{config_eos.config_file_path}'.",
|
||||||
|
)
|
||||||
|
return config_eos
|
||||||
|
|
||||||
|
|
||||||
@app.get("/v1/config")
|
@app.get("/v1/config")
|
||||||
def fastapi_config_get() -> ConfigEOS:
|
def fastapi_config_get() -> ConfigEOS:
|
||||||
"""Get the current configuration."""
|
"""Get the current configuration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration.
|
||||||
|
"""
|
||||||
return config_eos
|
return config_eos
|
||||||
|
|
||||||
|
|
||||||
@app.put("/v1/config")
|
@app.put("/v1/config")
|
||||||
def fastapi_config_put(
|
def fastapi_config_put(
|
||||||
settings: SettingsEOS,
|
settings: Annotated[SettingsEOS, Query(description="settings")],
|
||||||
save: Optional[bool] = None,
|
|
||||||
) -> ConfigEOS:
|
) -> ConfigEOS:
|
||||||
"""Merge settings into current configuration.
|
"""Write the provided settings into the current settings.
|
||||||
|
|
||||||
|
The existing settings are completely overwritten. Note that for any setting
|
||||||
|
value that is None, the configuration will fall back to values from other sources such as
|
||||||
|
environment variables, the EOS configuration file, or default values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
settings (SettingsEOS): The settings to merge into the current configuration.
|
settings (SettingsEOS): The settings to write into the current settings.
|
||||||
save (Optional[bool]): Save the resulting configuration to the configuration file.
|
|
||||||
Defaults to False.
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after the write.
|
||||||
"""
|
"""
|
||||||
config_eos.merge_settings(settings)
|
try:
|
||||||
if save:
|
config_eos.merge_settings(settings, force=True)
|
||||||
try:
|
except Exception as e:
|
||||||
config_eos.to_config_file()
|
raise HTTPException(status_code=400, detail=f"Error on update of configuration: {e}")
|
||||||
except:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=404,
|
|
||||||
detail=f"Cannot save configuration to file '{config_eos.config_file_path}'.",
|
|
||||||
)
|
|
||||||
return config_eos
|
return config_eos
|
||||||
|
|
||||||
|
|
||||||
@ -226,10 +304,10 @@ def fastapi_measurement_load_mr_series_by_name_get(
|
|||||||
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
||||||
if key is None:
|
if key is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404, detail=f"Measurement load with name '{name}' not available."
|
status_code=404, detail=f"Measurement load with name '{name}' is not available."
|
||||||
)
|
)
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
return PydanticDateTimeSeries.from_series(pdseries)
|
return PydanticDateTimeSeries.from_series(pdseries)
|
||||||
|
|
||||||
@ -244,10 +322,10 @@ def fastapi_measurement_load_mr_value_by_name_put(
|
|||||||
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
||||||
if key is None:
|
if key is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404, detail=f"Measurement load with name '{name}' not available."
|
status_code=404, detail=f"Measurement load with name '{name}' is not available."
|
||||||
)
|
)
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
measurement_eos.update_value(datetime, key, value)
|
measurement_eos.update_value(datetime, key, value)
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
return PydanticDateTimeSeries.from_series(pdseries)
|
return PydanticDateTimeSeries.from_series(pdseries)
|
||||||
@ -261,10 +339,10 @@ def fastapi_measurement_load_mr_series_by_name_put(
|
|||||||
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
key = measurement_eos.name_to_key(name=name, topic="measurement_load")
|
||||||
if key is None:
|
if key is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404, detail=f"Measurement load with name '{name}' not available."
|
status_code=404, detail=f"Measurement load with name '{name}' is not available."
|
||||||
)
|
)
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
pdseries = series.to_series() # make pandas series from PydanticDateTimeSeries
|
pdseries = series.to_series() # make pandas series from PydanticDateTimeSeries
|
||||||
measurement_eos.key_from_series(key=key, series=pdseries)
|
measurement_eos.key_from_series(key=key, series=pdseries)
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
@ -277,7 +355,7 @@ def fastapi_measurement_series_get(
|
|||||||
) -> PydanticDateTimeSeries:
|
) -> PydanticDateTimeSeries:
|
||||||
"""Get the measurements of given key as series."""
|
"""Get the measurements of given key as series."""
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
return PydanticDateTimeSeries.from_series(pdseries)
|
return PydanticDateTimeSeries.from_series(pdseries)
|
||||||
|
|
||||||
@ -290,7 +368,7 @@ def fastapi_measurement_value_put(
|
|||||||
) -> PydanticDateTimeSeries:
|
) -> PydanticDateTimeSeries:
|
||||||
"""Merge the measurement of given key and value into EOS measurements at given datetime."""
|
"""Merge the measurement of given key and value into EOS measurements at given datetime."""
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
measurement_eos.update_value(datetime, key, value)
|
measurement_eos.update_value(datetime, key, value)
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
return PydanticDateTimeSeries.from_series(pdseries)
|
return PydanticDateTimeSeries.from_series(pdseries)
|
||||||
@ -302,7 +380,7 @@ def fastapi_measurement_series_put(
|
|||||||
) -> PydanticDateTimeSeries:
|
) -> PydanticDateTimeSeries:
|
||||||
"""Merge measurement given as series into given key."""
|
"""Merge measurement given as series into given key."""
|
||||||
if key not in measurement_eos.record_keys:
|
if key not in measurement_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
pdseries = series.to_series() # make pandas series from PydanticDateTimeSeries
|
pdseries = series.to_series() # make pandas series from PydanticDateTimeSeries
|
||||||
measurement_eos.key_from_series(key=key, series=pdseries)
|
measurement_eos.key_from_series(key=key, series=pdseries)
|
||||||
pdseries = measurement_eos.key_to_series(key=key)
|
pdseries = measurement_eos.key_to_series(key=key)
|
||||||
@ -351,7 +429,7 @@ def fastapi_prediction_series_get(
|
|||||||
Defaults to end datetime of latest prediction.
|
Defaults to end datetime of latest prediction.
|
||||||
"""
|
"""
|
||||||
if key not in prediction_eos.record_keys:
|
if key not in prediction_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
if start_datetime is None:
|
if start_datetime is None:
|
||||||
start_datetime = prediction_eos.start_datetime
|
start_datetime = prediction_eos.start_datetime
|
||||||
else:
|
else:
|
||||||
@ -394,7 +472,7 @@ def fastapi_prediction_list_get(
|
|||||||
Defaults to 1 hour.
|
Defaults to 1 hour.
|
||||||
"""
|
"""
|
||||||
if key not in prediction_eos.record_keys:
|
if key not in prediction_eos.record_keys:
|
||||||
raise HTTPException(status_code=404, detail=f"Key '{key}' not available.")
|
raise HTTPException(status_code=404, detail=f"Key '{key}' is not available.")
|
||||||
if start_datetime is None:
|
if start_datetime is None:
|
||||||
start_datetime = prediction_eos.start_datetime
|
start_datetime = prediction_eos.start_datetime
|
||||||
else:
|
else:
|
||||||
@ -429,7 +507,7 @@ def fastapi_prediction_update(force_update: bool = False, force_enable: bool = F
|
|||||||
try:
|
try:
|
||||||
prediction_eos.update_data(force_update=force_update, force_enable=force_enable)
|
prediction_eos.update_data(force_update=force_update, force_enable=force_enable)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error while trying to update provider: {e}")
|
raise HTTPException(status_code=400, detail=f"Error on update of provider: {e}")
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
@ -453,7 +531,7 @@ def fastapi_prediction_update_provider(
|
|||||||
try:
|
try:
|
||||||
provider.update_data(force_update=force_update, force_enable=force_enable)
|
provider.update_data(force_update=force_update, force_enable=force_enable)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error while trying to update provider: {e}")
|
raise HTTPException(status_code=400, detail=f"Error on update of provider: {e}")
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
@ -461,6 +539,13 @@ def fastapi_prediction_update_provider(
|
|||||||
def fastapi_strompreis() -> list[float]:
|
def fastapi_strompreis() -> list[float]:
|
||||||
"""Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
"""Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
||||||
|
|
||||||
|
Electricity prices start at 00.00.00 today and are provided for 48 hours.
|
||||||
|
If no prices are available the missing ones at the start of the series are
|
||||||
|
filled with the first available price.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Electricity price charges are added.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Set ElecPriceAkkudoktor as elecprice_provider, then update data with
|
Set ElecPriceAkkudoktor as elecprice_provider, then update data with
|
||||||
'/v1/prediction/update'
|
'/v1/prediction/update'
|
||||||
@ -479,11 +564,21 @@ def fastapi_strompreis() -> list[float]:
|
|||||||
|
|
||||||
# Get the current date and the end date based on prediction hours
|
# Get the current date and the end date based on prediction hours
|
||||||
# Fetch prices for the specified date range
|
# Fetch prices for the specified date range
|
||||||
return prediction_eos.key_to_array(
|
start_datetime = to_datetime().start_of("day")
|
||||||
key="elecprice_marketprice_wh",
|
end_datetime = start_datetime.add(days=2)
|
||||||
start_datetime=prediction_eos.start_datetime,
|
try:
|
||||||
end_datetime=prediction_eos.end_datetime,
|
elecprice = prediction_eos.key_to_array(
|
||||||
).tolist()
|
key="elecprice_marketprice_wh",
|
||||||
|
start_datetime=start_datetime,
|
||||||
|
end_datetime=end_datetime,
|
||||||
|
).tolist()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Can not get the electricity price forecast: {e}. Did you configure the electricity price forecast provider?",
|
||||||
|
)
|
||||||
|
|
||||||
|
return elecprice
|
||||||
|
|
||||||
|
|
||||||
class GesamtlastRequest(PydanticBaseModel):
|
class GesamtlastRequest(PydanticBaseModel):
|
||||||
@ -498,6 +593,10 @@ def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
|||||||
|
|
||||||
Endpoint to handle total load prediction adjusted by latest measured data.
|
Endpoint to handle total load prediction adjusted by latest measured data.
|
||||||
|
|
||||||
|
Total load prediction starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no prediction values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available prediction value.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Use '/v1/prediction/list?key=load_mean_adjusted' instead.
|
Use '/v1/prediction/list?key=load_mean_adjusted' instead.
|
||||||
Load energy meter readings to be added to EOS measurement by:
|
Load energy meter readings to be added to EOS measurement by:
|
||||||
@ -534,11 +633,21 @@ def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
|||||||
# Create load forecast
|
# Create load forecast
|
||||||
prediction_eos.update_data(force_update=True)
|
prediction_eos.update_data(force_update=True)
|
||||||
|
|
||||||
prediction_list = prediction_eos.key_to_array(
|
# Get the forcast starting at start of day
|
||||||
key="load_mean_adjusted",
|
start_datetime = to_datetime().start_of("day")
|
||||||
start_datetime=prediction_eos.start_datetime,
|
end_datetime = start_datetime.add(days=2)
|
||||||
end_datetime=prediction_eos.end_datetime,
|
try:
|
||||||
).tolist()
|
prediction_list = prediction_eos.key_to_array(
|
||||||
|
key="load_mean_adjusted",
|
||||||
|
start_datetime=start_datetime,
|
||||||
|
end_datetime=end_datetime,
|
||||||
|
).tolist()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Can not get the total load forecast: {e}. Did you configure the load forecast provider?",
|
||||||
|
)
|
||||||
|
|
||||||
return prediction_list
|
return prediction_list
|
||||||
|
|
||||||
|
|
||||||
@ -548,6 +657,10 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
|||||||
|
|
||||||
Endpoint to handle total load prediction.
|
Endpoint to handle total load prediction.
|
||||||
|
|
||||||
|
Total load prediction starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no prediction values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available prediction value.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Set LoadAkkudoktor as load_provider, then update data with
|
Set LoadAkkudoktor as load_provider, then update data with
|
||||||
'/v1/prediction/update'
|
'/v1/prediction/update'
|
||||||
@ -564,11 +677,21 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
|||||||
# Create load forecast
|
# Create load forecast
|
||||||
prediction_eos.update_data(force_update=True)
|
prediction_eos.update_data(force_update=True)
|
||||||
|
|
||||||
prediction_list = prediction_eos.key_to_array(
|
# Get the forcast starting at start of day
|
||||||
key="load_mean",
|
start_datetime = to_datetime().start_of("day")
|
||||||
start_datetime=prediction_eos.start_datetime,
|
end_datetime = start_datetime.add(days=2)
|
||||||
end_datetime=prediction_eos.end_datetime,
|
try:
|
||||||
).tolist()
|
prediction_list = prediction_eos.key_to_array(
|
||||||
|
key="load_mean",
|
||||||
|
start_datetime=start_datetime,
|
||||||
|
end_datetime=end_datetime,
|
||||||
|
).tolist()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Can not get the total load forecast: {e}. Did you configure the load forecast provider?",
|
||||||
|
)
|
||||||
|
|
||||||
return prediction_list
|
return prediction_list
|
||||||
|
|
||||||
|
|
||||||
@ -583,6 +706,10 @@ def fastapi_pvforecast() -> ForecastResponse:
|
|||||||
|
|
||||||
Endpoint to handle PV forecast prediction.
|
Endpoint to handle PV forecast prediction.
|
||||||
|
|
||||||
|
PVForecast starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no forecast values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available forecast value.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Set PVForecastAkkudoktor as pvforecast_provider, then update data with
|
Set PVForecastAkkudoktor as pvforecast_provider, then update data with
|
||||||
'/v1/prediction/update'
|
'/v1/prediction/update'
|
||||||
@ -590,41 +717,38 @@ def fastapi_pvforecast() -> ForecastResponse:
|
|||||||
'/v1/prediction/list?key=pvforecast_ac_power' and
|
'/v1/prediction/list?key=pvforecast_ac_power' and
|
||||||
'/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.
|
'/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.
|
||||||
"""
|
"""
|
||||||
###############
|
settings = SettingsEOS(
|
||||||
# PV Forecast
|
elecprice_provider="PVForecastAkkudoktor",
|
||||||
###############
|
)
|
||||||
prediction_key = "pvforecast_ac_power"
|
config_eos.merge_settings(settings=settings)
|
||||||
pvforecast_ac_power = prediction_eos.get(prediction_key)
|
|
||||||
if pvforecast_ac_power is None:
|
|
||||||
raise HTTPException(status_code=404, detail=f"Prediction not available: {prediction_key}")
|
|
||||||
|
|
||||||
# On empty Series.loc TypeError: Cannot compare tz-naive and tz-aware datetime-like objects
|
ems_eos.set_start_datetime() # Set energy management start datetime to current hour.
|
||||||
if len(pvforecast_ac_power) == 0:
|
|
||||||
pvforecast_ac_power = pd.Series()
|
|
||||||
else:
|
|
||||||
# Fetch prices for the specified date range
|
|
||||||
pvforecast_ac_power = pvforecast_ac_power.loc[
|
|
||||||
prediction_eos.start_datetime : prediction_eos.end_datetime
|
|
||||||
]
|
|
||||||
|
|
||||||
prediction_key = "pvforecastakkudoktor_temp_air"
|
# Create PV forecast
|
||||||
pvforecastakkudoktor_temp_air = prediction_eos.get(prediction_key)
|
prediction_eos.update_data(force_update=True)
|
||||||
if pvforecastakkudoktor_temp_air is None:
|
|
||||||
raise HTTPException(status_code=404, detail=f"Prediction not available: {prediction_key}")
|
|
||||||
|
|
||||||
# On empty Series.loc TypeError: Cannot compare tz-naive and tz-aware datetime-like objects
|
# Get the forcast starting at start of day
|
||||||
if len(pvforecastakkudoktor_temp_air) == 0:
|
start_datetime = to_datetime().start_of("day")
|
||||||
pvforecastakkudoktor_temp_air = pd.Series()
|
end_datetime = start_datetime.add(days=2)
|
||||||
else:
|
try:
|
||||||
# Fetch prices for the specified date range
|
ac_power = prediction_eos.key_to_array(
|
||||||
pvforecastakkudoktor_temp_air = pvforecastakkudoktor_temp_air.loc[
|
key="pvforecast_ac_power",
|
||||||
prediction_eos.start_datetime : prediction_eos.end_datetime
|
start_datetime=start_datetime,
|
||||||
]
|
end_datetime=end_datetime,
|
||||||
|
).tolist()
|
||||||
|
temp_air = prediction_eos.key_to_array(
|
||||||
|
key="pvforecastakkudoktor_temp_air",
|
||||||
|
start_datetime=start_datetime,
|
||||||
|
end_datetime=end_datetime,
|
||||||
|
).tolist()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Can not get the PV forecast: {e}. Did you configure the PV forecast provider?",
|
||||||
|
)
|
||||||
|
|
||||||
# Return both forecasts as a JSON response
|
# Return both forecasts as a JSON response
|
||||||
return ForecastResponse(
|
return ForecastResponse(temperature=temp_air, pvpower=ac_power)
|
||||||
temperature=pvforecastakkudoktor_temp_air.tolist(), pvpower=pvforecast_ac_power.tolist()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/optimize")
|
@app.post("/optimize")
|
||||||
|
@ -2,7 +2,7 @@ import uvicorn
|
|||||||
from fasthtml.common import H1, FastHTML, Table, Td, Th, Thead, Titled, Tr
|
from fasthtml.common import H1, FastHTML, Table, Td, Th, Thead, Titled, Tr
|
||||||
|
|
||||||
from akkudoktoreos.config.config import get_config
|
from akkudoktoreos.config.config import get_config
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from typing import Optional
|
|||||||
from pydantic import Field, IPvAnyAddress, field_validator
|
from pydantic import Field, IPvAnyAddress, field_validator
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@ from pendulum import DateTime, Duration
|
|||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from akkudoktoreos.core.coreabc import ConfigMixin
|
from akkudoktoreos.core.coreabc import ConfigMixin
|
||||||
|
from akkudoktoreos.core.logging import get_logger
|
||||||
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ from pendulum import Date, DateTime, Duration
|
|||||||
from pendulum.tz.timezone import Timezone
|
from pendulum.tz.timezone import Timezone
|
||||||
from timezonefinder import TimezoneFinder
|
from timezonefinder import TimezoneFinder
|
||||||
|
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from akkudoktoreos.config.configabc import SettingsBaseModel
|
from akkudoktoreos.config.configabc import SettingsBaseModel
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import pytest
|
|||||||
from xprocess import ProcessStarter
|
from xprocess import ProcessStarter
|
||||||
|
|
||||||
from akkudoktoreos.config.config import ConfigEOS, get_config
|
from akkudoktoreos.config.config import ConfigEOS, get_config
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ def server(xprocess, config_eos, config_default_dirs):
|
|||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
project_dir = Path(__file__).parent.parent.parent
|
project_dir = config_eos.package_root_path
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[sys.executable, "-m", "pip", "install", "-e", project_dir],
|
[sys.executable, "-m", "pip", "install", "-e", project_dir],
|
||||||
check=True,
|
check=True,
|
||||||
|
@ -5,7 +5,7 @@ from unittest.mock import patch
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from akkudoktoreos.config.config import ConfigEOS
|
from akkudoktoreos.config.config import ConfigEOS
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -77,13 +77,9 @@ def test_loadakkudoktor_provider_id(load_provider):
|
|||||||
assert load_provider.provider_id() == "LoadAkkudoktor"
|
assert load_provider.provider_id() == "LoadAkkudoktor"
|
||||||
|
|
||||||
|
|
||||||
@patch("akkudoktoreos.prediction.loadakkudoktor.Path")
|
|
||||||
@patch("akkudoktoreos.prediction.loadakkudoktor.np.load")
|
@patch("akkudoktoreos.prediction.loadakkudoktor.np.load")
|
||||||
def test_load_data_from_mock(mock_np_load, mock_path, mock_load_profiles_file, load_provider):
|
def test_load_data_from_mock(mock_np_load, mock_load_profiles_file, load_provider):
|
||||||
"""Test the `load_data` method."""
|
"""Test the `load_data` method."""
|
||||||
# Mock path behavior to return the test file
|
|
||||||
mock_path.return_value.parent.parent.joinpath.return_value = mock_load_profiles_file
|
|
||||||
|
|
||||||
# Mock numpy load to return data similar to what would be in the file
|
# Mock numpy load to return data similar to what would be in the file
|
||||||
mock_np_load.return_value = {
|
mock_np_load.return_value = {
|
||||||
"yearly_profiles": np.ones((365, 24)),
|
"yearly_profiles": np.ones((365, 24)),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Test Module for logutil Module."""
|
"""Test Module for logging Module."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -6,7 +6,7 @@ from logging.handlers import RotatingFileHandler
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from akkudoktoreos.utils.logutil import get_logger
|
from akkudoktoreos.core.logging import get_logger
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# get_logger
|
# get_logger
|
||||||
@ -73,10 +73,5 @@ def test_get_logger_no_file_logging(clean_up_log_file):
|
|||||||
|
|
||||||
def test_get_logger_with_invalid_level(clean_up_log_file):
|
def test_get_logger_with_invalid_level(clean_up_log_file):
|
||||||
"""Test logger creation with an invalid logging level."""
|
"""Test logger creation with an invalid logging level."""
|
||||||
logger = get_logger("test_logger", logging_level="INVALID")
|
with pytest.raises(ValueError, match="Unknown loggin level: INVALID"):
|
||||||
|
logger = get_logger("test_logger", logging_level="INVALID")
|
||||||
# Check logger name
|
|
||||||
assert logger.name == "test_logger"
|
|
||||||
|
|
||||||
# Check default logging level is DEBUG
|
|
||||||
assert logger.level == logging.DEBUG
|
|
@ -9,5 +9,5 @@ def test_server(server, config_eos):
|
|||||||
assert config_eos.data_folder_path is not None
|
assert config_eos.data_folder_path is not None
|
||||||
assert config_eos.data_folder_path.is_dir()
|
assert config_eos.data_folder_path.is_dir()
|
||||||
|
|
||||||
result = requests.get(f"{server}/v1/config?")
|
result = requests.get(f"{server}/v1/config")
|
||||||
assert result.status_code == HTTPStatus.OK
|
assert result.status_code == HTTPStatus.OK
|
||||||
|
853
tests/testdata/openapi.md
vendored
853
tests/testdata/openapi.md
vendored
@ -8,6 +8,159 @@
|
|||||||
|
|
||||||
**Endpoints**:
|
**Endpoints**:
|
||||||
|
|
||||||
|
## POST /gesamtlast
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_gesamtlast_gesamtlast_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_gesamtlast_gesamtlast_post)
|
||||||
|
|
||||||
|
Fastapi Gesamtlast
|
||||||
|
|
||||||
|
```
|
||||||
|
Deprecated: Total Load Prediction with adjustment.
|
||||||
|
|
||||||
|
Endpoint to handle total load prediction adjusted by latest measured data.
|
||||||
|
|
||||||
|
Total load prediction starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no prediction values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available prediction value.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Use '/v1/prediction/list?key=load_mean_adjusted' instead.
|
||||||
|
Load energy meter readings to be added to EOS measurement by:
|
||||||
|
'/v1/measurement/load-mr/value/by-name' or
|
||||||
|
'/v1/measurement/value'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
- `application/json`: {
|
||||||
|
"$ref": "#/components/schemas/GesamtlastRequest"
|
||||||
|
}
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /gesamtlast_simple
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_gesamtlast_simple_gesamtlast_simple_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_gesamtlast_simple_gesamtlast_simple_get)
|
||||||
|
|
||||||
|
Fastapi Gesamtlast Simple
|
||||||
|
|
||||||
|
```
|
||||||
|
Deprecated: Total Load Prediction.
|
||||||
|
|
||||||
|
Endpoint to handle total load prediction.
|
||||||
|
|
||||||
|
Total load prediction starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no prediction values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available prediction value.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Set LoadAkkudoktor as load_provider, then update data with
|
||||||
|
'/v1/prediction/update'
|
||||||
|
and then request data with
|
||||||
|
'/v1/prediction/list?key=load_mean' instead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `year_energy` (query, required): No description provided.
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /optimize
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_optimize_optimize_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_optimize_optimize_post)
|
||||||
|
|
||||||
|
Fastapi Optimize
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `start_hour` (query, optional): Defaults to current hour of the day.
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
- `application/json`: {
|
||||||
|
"$ref": "#/components/schemas/OptimizationParameters"
|
||||||
|
}
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /pvforecast
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_pvforecast_pvforecast_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_pvforecast_pvforecast_get)
|
||||||
|
|
||||||
|
Fastapi Pvforecast
|
||||||
|
|
||||||
|
```
|
||||||
|
Deprecated: PV Forecast Prediction.
|
||||||
|
|
||||||
|
Endpoint to handle PV forecast prediction.
|
||||||
|
|
||||||
|
PVForecast starts at 00.00.00 today and is provided for 48 hours.
|
||||||
|
If no forecast values are available the missing ones at the start of the series are
|
||||||
|
filled with the first available forecast value.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Set PVForecastAkkudoktor as pvforecast_provider, then update data with
|
||||||
|
'/v1/prediction/update'
|
||||||
|
and then request data with
|
||||||
|
'/v1/prediction/list?key=pvforecast_ac_power' and
|
||||||
|
'/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /strompreis
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_strompreis_strompreis_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_strompreis_strompreis_get)
|
||||||
|
|
||||||
|
Fastapi Strompreis
|
||||||
|
|
||||||
|
```
|
||||||
|
Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
||||||
|
|
||||||
|
Electricity prices start at 00.00.00 today and are provided for 48 hours.
|
||||||
|
If no prices are available the missing ones at the start of the series are
|
||||||
|
filled with the first available price.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Electricity price charges are added.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Set ElecPriceAkkudoktor as elecprice_provider, then update data with
|
||||||
|
'/v1/prediction/update'
|
||||||
|
and then request data with
|
||||||
|
'/v1/prediction/list?key=elecprice_marketprice_wh' or
|
||||||
|
'/v1/prediction/list?key=elecprice_marketprice_kwh' instead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## GET /v1/config
|
## GET /v1/config
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_get_v1_config_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_get_v1_config_get)
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_get_v1_config_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_get_v1_config_get)
|
||||||
@ -16,6 +169,9 @@ Fastapi Config Get
|
|||||||
|
|
||||||
```
|
```
|
||||||
Get the current configuration.
|
Get the current configuration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Responses**:
|
**Responses**:
|
||||||
@ -31,22 +187,459 @@ Get the current configuration.
|
|||||||
Fastapi Config Put
|
Fastapi Config Put
|
||||||
|
|
||||||
```
|
```
|
||||||
Merge settings into current configuration.
|
Write the provided settings into the current settings.
|
||||||
|
|
||||||
|
The existing settings are completely overwritten. Note that for any setting
|
||||||
|
value that is None, the configuration will fall back to values from other sources such as
|
||||||
|
environment variables, the EOS configuration file, or default values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
settings (SettingsEOS): The settings to merge into the current configuration.
|
settings (SettingsEOS): The settings to write into the current settings.
|
||||||
save (Optional[bool]): Save the resulting configuration to the configuration file.
|
|
||||||
Defaults to False.
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after the write.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parameters**:
|
**Parameters**:
|
||||||
|
|
||||||
- `save` (query, optional): No description provided.
|
- `server_fastapi_host` (query, optional): FastAPI server IP address.
|
||||||
|
|
||||||
|
- `server_fastapi_port` (query, optional): FastAPI server IP port number.
|
||||||
|
|
||||||
|
- `server_fastapi_verbose` (query, optional): Enable debug output
|
||||||
|
|
||||||
|
- `server_fastapi_startup_server_fasthtml` (query, optional): FastAPI server to startup application FastHTML server.
|
||||||
|
|
||||||
|
- `server_fasthtml_host` (query, optional): FastHTML server IP address.
|
||||||
|
|
||||||
|
- `server_fasthtml_port` (query, optional): FastHTML server IP port number.
|
||||||
|
|
||||||
|
- `weatherimport_file_path` (query, optional): Path to the file to import weather data from.
|
||||||
|
|
||||||
|
- `weatherimport_json` (query, optional): JSON string, dictionary of weather forecast value lists.
|
||||||
|
|
||||||
|
- `weather_provider` (query, optional): Weather provider id of provider to be used.
|
||||||
|
|
||||||
|
- `pvforecastimport_file_path` (query, optional): Path to the file to import PV forecast data from.
|
||||||
|
|
||||||
|
- `pvforecastimport_json` (query, optional): JSON string, dictionary of PV forecast value lists.
|
||||||
|
|
||||||
|
- `pvforecast_provider` (query, optional): PVForecast provider id of provider to be used.
|
||||||
|
|
||||||
|
- `pvforecast0_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast0_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast0_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast0_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast0_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast0_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast0_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast0_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast0_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast0_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast0_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast0_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast0_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast0_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast0_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast0_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast1_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast1_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast1_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast1_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast1_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast1_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast1_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast1_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast1_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast1_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast1_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast1_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast1_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast1_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast1_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast1_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast2_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast2_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast2_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast2_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast2_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast2_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast2_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast2_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast2_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast2_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast2_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast2_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast2_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast2_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast2_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast2_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast3_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast3_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast3_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast3_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast3_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast3_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast3_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast3_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast3_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast3_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast3_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast3_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast3_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast3_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast3_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast3_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast4_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast4_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast4_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast4_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast4_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast4_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast4_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast4_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast4_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast4_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast4_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast4_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast4_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast4_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast4_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast4_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast5_surface_tilt` (query, optional): Tilt angle from horizontal plane. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast5_surface_azimuth` (query, optional): Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).
|
||||||
|
|
||||||
|
- `pvforecast5_userhorizon` (query, optional): Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.
|
||||||
|
|
||||||
|
- `pvforecast5_peakpower` (query, optional): Nominal power of PV system in kW.
|
||||||
|
|
||||||
|
- `pvforecast5_pvtechchoice` (query, optional): PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.
|
||||||
|
|
||||||
|
- `pvforecast5_mountingplace` (query, optional): Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.
|
||||||
|
|
||||||
|
- `pvforecast5_loss` (query, optional): Sum of PV system losses in percent
|
||||||
|
|
||||||
|
- `pvforecast5_trackingtype` (query, optional): Type of suntracking. 0=fixed, 1=single horizontal axis aligned north-south, 2=two-axis tracking, 3=vertical axis tracking, 4=single horizontal axis aligned east-west, 5=single inclined axis aligned north-south.
|
||||||
|
|
||||||
|
- `pvforecast5_optimal_surface_tilt` (query, optional): Calculate the optimum tilt angle. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast5_optimalangles` (query, optional): Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.
|
||||||
|
|
||||||
|
- `pvforecast5_albedo` (query, optional): Proportion of the light hitting the ground that it reflects back.
|
||||||
|
|
||||||
|
- `pvforecast5_module_model` (query, optional): Model of the PV modules of this plane.
|
||||||
|
|
||||||
|
- `pvforecast5_inverter_model` (query, optional): Model of the inverter of this plane.
|
||||||
|
|
||||||
|
- `pvforecast5_inverter_paco` (query, optional): AC power rating of the inverter. [W]
|
||||||
|
|
||||||
|
- `pvforecast5_modules_per_string` (query, optional): Number of the PV modules of the strings of this plane.
|
||||||
|
|
||||||
|
- `pvforecast5_strings_per_inverter` (query, optional): Number of the strings of the inverter of this plane.
|
||||||
|
|
||||||
|
- `load_import_file_path` (query, optional): Path to the file to import load data from.
|
||||||
|
|
||||||
|
- `load_import_json` (query, optional): JSON string, dictionary of load forecast value lists.
|
||||||
|
|
||||||
|
- `loadakkudoktor_year_energy` (query, optional): Yearly energy consumption (kWh).
|
||||||
|
|
||||||
|
- `load_provider` (query, optional): Load provider id of provider to be used.
|
||||||
|
|
||||||
|
- `elecpriceimport_file_path` (query, optional): Path to the file to import elecprice data from.
|
||||||
|
|
||||||
|
- `elecpriceimport_json` (query, optional): JSON string, dictionary of electricity price forecast value lists.
|
||||||
|
|
||||||
|
- `elecprice_provider` (query, optional): Electricity price provider id of provider to be used.
|
||||||
|
|
||||||
|
- `elecprice_charges_kwh` (query, optional): Electricity price charges (€/kWh).
|
||||||
|
|
||||||
|
- `prediction_hours` (query, optional): Number of hours into the future for predictions
|
||||||
|
|
||||||
|
- `prediction_historic_hours` (query, optional): Number of hours into the past for historical predictions data
|
||||||
|
|
||||||
|
- `latitude` (query, optional): Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (°)
|
||||||
|
|
||||||
|
- `longitude` (query, optional): Longitude in decimal degrees, within -180 to 180 (°)
|
||||||
|
|
||||||
|
- `optimization_hours` (query, optional): Number of hours into the future for optimizations.
|
||||||
|
|
||||||
|
- `optimization_penalty` (query, optional): Penalty factor used in optimization.
|
||||||
|
|
||||||
|
- `optimization_ev_available_charge_rates_percent` (query, optional): Charge rates available for the EV in percent of maximum charge.
|
||||||
|
|
||||||
|
- `measurement_load0_name` (query, optional): Name of the load0 source (e.g. 'Household', 'Heat Pump')
|
||||||
|
|
||||||
|
- `measurement_load1_name` (query, optional): Name of the load1 source (e.g. 'Household', 'Heat Pump')
|
||||||
|
|
||||||
|
- `measurement_load2_name` (query, optional): Name of the load2 source (e.g. 'Household', 'Heat Pump')
|
||||||
|
|
||||||
|
- `measurement_load3_name` (query, optional): Name of the load3 source (e.g. 'Household', 'Heat Pump')
|
||||||
|
|
||||||
|
- `measurement_load4_name` (query, optional): Name of the load4 source (e.g. 'Household', 'Heat Pump')
|
||||||
|
|
||||||
|
- `battery_provider` (query, optional): Id of Battery simulation provider.
|
||||||
|
|
||||||
|
- `battery_capacity` (query, optional): Battery capacity [Wh].
|
||||||
|
|
||||||
|
- `battery_initial_soc` (query, optional): Battery initial state of charge [%].
|
||||||
|
|
||||||
|
- `battery_soc_min` (query, optional): Battery minimum state of charge [%].
|
||||||
|
|
||||||
|
- `battery_soc_max` (query, optional): Battery maximum state of charge [%].
|
||||||
|
|
||||||
|
- `battery_charging_efficiency` (query, optional): Battery charging efficiency [%].
|
||||||
|
|
||||||
|
- `battery_discharging_efficiency` (query, optional): Battery discharging efficiency [%].
|
||||||
|
|
||||||
|
- `battery_max_charging_power` (query, optional): Battery maximum charge power [W].
|
||||||
|
|
||||||
|
- `bev_provider` (query, optional): Id of Battery Electric Vehicle simulation provider.
|
||||||
|
|
||||||
|
- `bev_capacity` (query, optional): Battery Electric Vehicle capacity [Wh].
|
||||||
|
|
||||||
|
- `bev_initial_soc` (query, optional): Battery Electric Vehicle initial state of charge [%].
|
||||||
|
|
||||||
|
- `bev_soc_max` (query, optional): Battery Electric Vehicle maximum state of charge [%].
|
||||||
|
|
||||||
|
- `bev_charging_efficiency` (query, optional): Battery Electric Vehicle charging efficiency [%].
|
||||||
|
|
||||||
|
- `bev_discharging_efficiency` (query, optional): Battery Electric Vehicle discharging efficiency [%].
|
||||||
|
|
||||||
|
- `bev_max_charging_power` (query, optional): Battery Electric Vehicle maximum charge power [W].
|
||||||
|
|
||||||
|
- `dishwasher_provider` (query, optional): Id of Dish Washer simulation provider.
|
||||||
|
|
||||||
|
- `dishwasher_consumption` (query, optional): Dish Washer energy consumption [Wh].
|
||||||
|
|
||||||
|
- `dishwasher_duration` (query, optional): Dish Washer usage duration [h].
|
||||||
|
|
||||||
|
- `inverter_provider` (query, optional): Id of PV Inverter simulation provider.
|
||||||
|
|
||||||
|
- `inverter_power_max` (query, optional): Inverter maximum power [W].
|
||||||
|
|
||||||
|
- `logging_level_default` (query, optional): EOS default logging level.
|
||||||
|
|
||||||
|
- `data_folder_path` (query, optional): Path to EOS data directory.
|
||||||
|
|
||||||
|
- `data_output_subpath` (query, optional): Sub-path for the EOS output data directory.
|
||||||
|
|
||||||
|
- `data_cache_subpath` (query, optional): Sub-path for the EOS cache data directory.
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GET /v1/config/file
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_file_get_v1_config_file_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_file_get_v1_config_file_get)
|
||||||
|
|
||||||
|
Fastapi Config File Get
|
||||||
|
|
||||||
|
```
|
||||||
|
Get the settings as defined by the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
settings (SettingsEOS): The settings defined by the EOS configuration file.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PUT /v1/config/file
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_file_put_v1_config_file_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_file_put_v1_config_file_put)
|
||||||
|
|
||||||
|
Fastapi Config File Put
|
||||||
|
|
||||||
|
```
|
||||||
|
Save the current configuration to the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration that was saved.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## POST /v1/config/update
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_update_post_v1_config_update_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_update_post_v1_config_update_post)
|
||||||
|
|
||||||
|
Fastapi Config Update Post
|
||||||
|
|
||||||
|
```
|
||||||
|
Update the configuration from the EOS configuration file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after update.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PUT /v1/config/value
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_config_value_put_v1_config_value_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_config_value_put_v1_config_value_put)
|
||||||
|
|
||||||
|
Fastapi Config Value Put
|
||||||
|
|
||||||
|
```
|
||||||
|
Set the configuration option in the settings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): configuration key
|
||||||
|
value (Any): configuration value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
configuration (ConfigEOS): The current configuration after the write.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `key` (query, required): configuration key
|
||||||
|
|
||||||
|
- `value` (query, required): configuration value
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PUT /v1/measurement/data
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_data_put_v1_measurement_data_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_data_put_v1_measurement_data_put)
|
||||||
|
|
||||||
|
Fastapi Measurement Data Put
|
||||||
|
|
||||||
|
```
|
||||||
|
Merge the measurement data given as datetime data into EOS measurements.
|
||||||
|
```
|
||||||
|
|
||||||
**Request Body**:
|
**Request Body**:
|
||||||
|
|
||||||
- `application/json`: {
|
- `application/json`: {
|
||||||
"$ref": "#/components/schemas/SettingsEOS"
|
"$ref": "#/components/schemas/PydanticDateTimeData"
|
||||||
|
}
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PUT /v1/measurement/dataframe
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_dataframe_put_v1_measurement_dataframe_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_dataframe_put_v1_measurement_dataframe_put)
|
||||||
|
|
||||||
|
Fastapi Measurement Dataframe Put
|
||||||
|
|
||||||
|
```
|
||||||
|
Merge the measurement data given as dataframe into EOS measurements.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
- `application/json`: {
|
||||||
|
"$ref": "#/components/schemas/PydanticDateTimeDataFrame"
|
||||||
}
|
}
|
||||||
|
|
||||||
**Responses**:
|
**Responses**:
|
||||||
@ -225,54 +818,6 @@ Merge the measurement of given key and value into EOS measurements at given date
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PUT /v1/measurement/dataframe
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_dataframe_put_v1_measurement_dataframe_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_dataframe_put_v1_measurement_dataframe_put)
|
|
||||||
|
|
||||||
Fastapi Measurement Dataframe Put
|
|
||||||
|
|
||||||
```
|
|
||||||
Merge the measurement data given as dataframe into EOS measurements.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request Body**:
|
|
||||||
|
|
||||||
- `application/json`: {
|
|
||||||
"$ref": "#/components/schemas/PydanticDateTimeDataFrame"
|
|
||||||
}
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PUT /v1/measurement/data
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_measurement_data_put_v1_measurement_data_put), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_measurement_data_put_v1_measurement_data_put)
|
|
||||||
|
|
||||||
Fastapi Measurement Data Put
|
|
||||||
|
|
||||||
```
|
|
||||||
Merge the measurement data given as datetime data into EOS measurements.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request Body**:
|
|
||||||
|
|
||||||
- `application/json`: {
|
|
||||||
"$ref": "#/components/schemas/PydanticDateTimeData"
|
|
||||||
}
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET /v1/prediction/keys
|
## GET /v1/prediction/keys
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_keys_get_v1_prediction_keys_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_keys_get_v1_prediction_keys_get)
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_keys_get_v1_prediction_keys_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_keys_get_v1_prediction_keys_get)
|
||||||
@ -289,39 +834,6 @@ Get a list of available prediction keys.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GET /v1/prediction/series
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_series_get_v1_prediction_series_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_series_get_v1_prediction_series_get)
|
|
||||||
|
|
||||||
Fastapi Prediction Series Get
|
|
||||||
|
|
||||||
```
|
|
||||||
Get prediction for given key within given date range as series.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
key (str): Prediction key
|
|
||||||
start_datetime (Optional[str]): Starting datetime (inclusive).
|
|
||||||
Defaults to start datetime of latest prediction.
|
|
||||||
end_datetime (Optional[str]: Ending datetime (exclusive).
|
|
||||||
Defaults to end datetime of latest prediction.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
|
|
||||||
- `key` (query, required): Prediction key.
|
|
||||||
|
|
||||||
- `start_datetime` (query, optional): Starting datetime (inclusive).
|
|
||||||
|
|
||||||
- `end_datetime` (query, optional): Ending datetime (exclusive).
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET /v1/prediction/list
|
## GET /v1/prediction/list
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_list_get_v1_prediction_list_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_list_get_v1_prediction_list_get)
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_list_get_v1_prediction_list_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_list_get_v1_prediction_list_get)
|
||||||
@ -359,6 +871,39 @@ Args:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## GET /v1/prediction/series
|
||||||
|
|
||||||
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_series_get_v1_prediction_series_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_series_get_v1_prediction_series_get)
|
||||||
|
|
||||||
|
Fastapi Prediction Series Get
|
||||||
|
|
||||||
|
```
|
||||||
|
Get prediction for given key within given date range as series.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): Prediction key
|
||||||
|
start_datetime (Optional[str]): Starting datetime (inclusive).
|
||||||
|
Defaults to start datetime of latest prediction.
|
||||||
|
end_datetime (Optional[str]: Ending datetime (exclusive).
|
||||||
|
Defaults to end datetime of latest prediction.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters**:
|
||||||
|
|
||||||
|
- `key` (query, required): Prediction key.
|
||||||
|
|
||||||
|
- `start_datetime` (query, optional): Starting datetime (inclusive).
|
||||||
|
|
||||||
|
- `end_datetime` (query, optional): Ending datetime (exclusive).
|
||||||
|
|
||||||
|
**Responses**:
|
||||||
|
|
||||||
|
- **200**: Successful Response
|
||||||
|
|
||||||
|
- **422**: Validation Error
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## POST /v1/prediction/update
|
## POST /v1/prediction/update
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_update_v1_prediction_update_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_update_v1_prediction_update_post)
|
**Links**: [local](http://localhost:8503/docs#/default/fastapi_prediction_update_v1_prediction_update_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_prediction_update_v1_prediction_update_post)
|
||||||
@ -422,140 +967,6 @@ Args:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## GET /strompreis
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_strompreis_strompreis_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_strompreis_strompreis_get)
|
|
||||||
|
|
||||||
Fastapi Strompreis
|
|
||||||
|
|
||||||
```
|
|
||||||
Deprecated: Electricity Market Price Prediction per Wh (€/Wh).
|
|
||||||
|
|
||||||
Note:
|
|
||||||
Set ElecPriceAkkudoktor as elecprice_provider, then update data with
|
|
||||||
'/v1/prediction/update'
|
|
||||||
and then request data with
|
|
||||||
'/v1/prediction/list?key=elecprice_marketprice_wh' or
|
|
||||||
'/v1/prediction/list?key=elecprice_marketprice_kwh' instead.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## POST /gesamtlast
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_gesamtlast_gesamtlast_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_gesamtlast_gesamtlast_post)
|
|
||||||
|
|
||||||
Fastapi Gesamtlast
|
|
||||||
|
|
||||||
```
|
|
||||||
Deprecated: Total Load Prediction with adjustment.
|
|
||||||
|
|
||||||
Endpoint to handle total load prediction adjusted by latest measured data.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
Use '/v1/prediction/list?key=load_mean_adjusted' instead.
|
|
||||||
Load energy meter readings to be added to EOS measurement by:
|
|
||||||
'/v1/measurement/load-mr/value/by-name' or
|
|
||||||
'/v1/measurement/value'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request Body**:
|
|
||||||
|
|
||||||
- `application/json`: {
|
|
||||||
"$ref": "#/components/schemas/GesamtlastRequest"
|
|
||||||
}
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET /gesamtlast_simple
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_gesamtlast_simple_gesamtlast_simple_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_gesamtlast_simple_gesamtlast_simple_get)
|
|
||||||
|
|
||||||
Fastapi Gesamtlast Simple
|
|
||||||
|
|
||||||
```
|
|
||||||
Deprecated: Total Load Prediction.
|
|
||||||
|
|
||||||
Endpoint to handle total load prediction.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
Set LoadAkkudoktor as load_provider, then update data with
|
|
||||||
'/v1/prediction/update'
|
|
||||||
and then request data with
|
|
||||||
'/v1/prediction/list?key=load_mean' instead.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
|
|
||||||
- `year_energy` (query, required): No description provided.
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET /pvforecast
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_pvforecast_pvforecast_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_pvforecast_pvforecast_get)
|
|
||||||
|
|
||||||
Fastapi Pvforecast
|
|
||||||
|
|
||||||
```
|
|
||||||
Deprecated: PV Forecast Prediction.
|
|
||||||
|
|
||||||
Endpoint to handle PV forecast prediction.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
Set PVForecastAkkudoktor as pvforecast_provider, then update data with
|
|
||||||
'/v1/prediction/update'
|
|
||||||
and then request data with
|
|
||||||
'/v1/prediction/list?key=pvforecast_ac_power' and
|
|
||||||
'/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## POST /optimize
|
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/fastapi_optimize_optimize_post), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/fastapi_optimize_optimize_post)
|
|
||||||
|
|
||||||
Fastapi Optimize
|
|
||||||
|
|
||||||
**Parameters**:
|
|
||||||
|
|
||||||
- `start_hour` (query, optional): Defaults to current hour of the day.
|
|
||||||
|
|
||||||
**Request Body**:
|
|
||||||
|
|
||||||
- `application/json`: {
|
|
||||||
"$ref": "#/components/schemas/OptimizationParameters"
|
|
||||||
}
|
|
||||||
|
|
||||||
**Responses**:
|
|
||||||
|
|
||||||
- **200**: Successful Response
|
|
||||||
|
|
||||||
- **422**: Validation Error
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## GET /visualization_results.pdf
|
## GET /visualization_results.pdf
|
||||||
|
|
||||||
**Links**: [local](http://localhost:8503/docs#/default/get_pdf_visualization_results_pdf_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/get_pdf_visualization_results_pdf_get)
|
**Links**: [local](http://localhost:8503/docs#/default/get_pdf_visualization_results_pdf_get), [swagger](https://petstore3.swagger.io/?url=https://raw.githubusercontent.com/Akkudoktor-EOS/EOS/refs/heads/main/openapi.json#/default/get_pdf_visualization_results_pdf_get)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user