From d4e31d556a2b8f4a2584d02d63da47605787570e Mon Sep 17 00:00:00 2001 From: Bobby Noelte Date: Sun, 5 Jan 2025 14:41:07 +0100 Subject: [PATCH] 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 --- docs/akkudoktoreos/configuration.md | 89 + docs/akkudoktoreos/measurement.md | 2 +- docs/akkudoktoreos/prediction.md | 65 +- docs/index.md | 1 + openapi.json | 3132 ++++++++++++++++- scripts/generate_config_md.py | 161 + scripts/generate_openapi_md.py | 6 +- src/akkudoktoreos/config/config.py | 63 +- src/akkudoktoreos/core/coreabc.py | 2 +- src/akkudoktoreos/core/dataabc.py | 55 +- src/akkudoktoreos/core/ems.py | 2 +- src/akkudoktoreos/core/logabc.py | 20 + .../{utils/logutil.py => core/logging.py} | 36 +- src/akkudoktoreos/core/logsettings.py | 45 + src/akkudoktoreos/core/pydantic.py | 1 + src/akkudoktoreos/data/default.config.json | 1 + src/akkudoktoreos/devices/battery.py | 2 +- src/akkudoktoreos/devices/devices.py | 2 +- src/akkudoktoreos/devices/devicesabc.py | 2 +- src/akkudoktoreos/devices/generic.py | 2 +- src/akkudoktoreos/devices/inverter.py | 2 +- src/akkudoktoreos/measurement/measurement.py | 2 +- .../optimization/optimization.py | 2 +- .../optimization/optimizationabc.py | 2 +- src/akkudoktoreos/prediction/elecpriceabc.py | 2 +- .../prediction/elecpriceakkudoktor.py | 13 +- .../prediction/elecpriceimport.py | 2 +- src/akkudoktoreos/prediction/load.py | 4 +- src/akkudoktoreos/prediction/loadabc.py | 2 +- .../prediction/loadakkudoktor.py | 27 +- src/akkudoktoreos/prediction/loadimport.py | 2 +- src/akkudoktoreos/prediction/predictionabc.py | 2 +- src/akkudoktoreos/prediction/pvforecast.py | 24 +- src/akkudoktoreos/prediction/pvforecastabc.py | 2 +- .../prediction/pvforecastakkudoktor.py | 26 +- .../prediction/pvforecastimport.py | 2 +- src/akkudoktoreos/prediction/weatherabc.py | 2 +- .../prediction/weatherbrightsky.py | 2 +- .../prediction/weatherclearoutside.py | 2 +- src/akkudoktoreos/prediction/weatherimport.py | 2 +- src/akkudoktoreos/server/fastapi_server.py | 276 +- src/akkudoktoreos/server/fasthtml_server.py | 2 +- src/akkudoktoreos/server/server.py | 2 +- src/akkudoktoreos/utils/cacheutil.py | 2 +- src/akkudoktoreos/utils/datetimeutil.py | 2 +- src/akkudoktoreos/utils/utils.py | 2 +- tests/conftest.py | 4 +- tests/test_config.py | 2 +- tests/test_loadakkudoktor.py | 6 +- tests/{test_logutil.py => test_logging.py} | 13 +- tests/test_server.py | 2 +- tests/testdata/openapi.md | 853 +++-- 52 files changed, 4517 insertions(+), 462 deletions(-) create mode 100644 docs/akkudoktoreos/configuration.md create mode 100755 scripts/generate_config_md.py create mode 100644 src/akkudoktoreos/core/logabc.py rename src/akkudoktoreos/{utils/logutil.py => core/logging.py} (77%) create mode 100644 src/akkudoktoreos/core/logsettings.py rename tests/{test_logutil.py => test_logging.py} (87%) diff --git a/docs/akkudoktoreos/configuration.md b/docs/akkudoktoreos/configuration.md new file mode 100644 index 0000000..a8c498b --- /dev/null +++ b/docs/akkudoktoreos/configuration.md @@ -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 +``` diff --git a/docs/akkudoktoreos/measurement.md b/docs/akkudoktoreos/measurement.md index 067650a..843252e 100644 --- a/docs/akkudoktoreos/measurement.md +++ b/docs/akkudoktoreos/measurement.md @@ -30,7 +30,7 @@ The measurement data must be or is provided in one of the following formats: A dictionary with the following structure: -```JSON +```python { "start_datetime": "2024-01-01 00:00:00", "interval": "1 Hour", diff --git a/docs/akkudoktoreos/prediction.md b/docs/akkudoktoreos/prediction.md index 53c1889..c6a4a2d 100644 --- a/docs/akkudoktoreos/prediction.md +++ b/docs/akkudoktoreos/prediction.md @@ -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 in the EOS configuration. For example: -```plaintext +```python 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: -```JSON +```python { "start_datetime": "2024-01-01 00:00:00", "interval": "1 Hour", @@ -214,6 +214,67 @@ Configuration options: - `pvforecastimport_file_path`: Path to the file to import PV forecast data from. - `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 The `PVForecastAkkudoktor` provider retrieves the PV power forecast data directly from diff --git a/docs/index.md b/docs/index.md index 1672d71..5b40b1a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,7 @@ welcome.md akkudoktoreos/about.md develop/getting_started.md develop/CONTRIBUTING.md +akkudoktoreos/configuration.md akkudoktoreos/prediction.md akkudoktoreos/measurement.md akkudoktoreos/serverapi.md diff --git a/openapi.json b/openapi.json index 99e15da..684cc45 100644 --- a/openapi.json +++ b/openapi.json @@ -228,6 +228,15 @@ "title": "Config Keys", "type": "array" }, + "config_keys_read_only": { + "description": "Returns the keys of all read only fields in the configuration.", + "items": { + "type": "string" + }, + "readOnly": true, + "title": "Config Keys Read Only", + "type": "array" + }, "data_cache_path": { "anyOf": [ { @@ -476,6 +485,24 @@ "description": "Yearly energy consumption (kWh).", "title": "Loadakkudoktor Year Energy" }, + "logging_level_default": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "EOS default logging level.", + "title": "Logging Level Default" + }, + "logging_level_root": { + "description": "Root logger logging level.", + "readOnly": true, + "title": "Logging Level Root", + "type": "string" + }, "longitude": { "anyOf": [ { @@ -601,6 +628,13 @@ "description": "Penalty factor used in optimization.", "title": "Optimization Penalty" }, + "package_root_path": { + "description": "Compute the package root path.", + "format": "path", + "readOnly": true, + "title": "Package Root Path", + "type": "string" + }, "prediction_historic_hours": { "anyOf": [ { @@ -674,6 +708,7 @@ "type": "null" } ], + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast0 Loss" }, @@ -873,7 +908,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast1 Loss" }, @@ -1073,7 +1108,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast2 Loss" }, @@ -1273,7 +1308,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast3 Loss" }, @@ -1473,7 +1508,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast4 Loss" }, @@ -1673,7 +1708,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast5 Loss" }, @@ -2056,12 +2091,15 @@ "pvforecast_planes_userhorizon", "pvforecast_planes_inverter_paco", "timezone", + "logging_level_root", "data_output_path", "data_cache_path", "config_folder_path", "config_file_path", "config_default_file_path", - "config_keys" + "package_root_path", + "config_keys", + "config_keys_read_only" ], "title": "ConfigEOS", "type": "object" @@ -2825,6 +2863,20 @@ "description": "Battery Electric Vehicle maximum state of charge [%].", "title": "Bev Soc Max" }, + "data_cache_path": { + "anyOf": [ + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Compute data_cache_path based on data_folder_path.", + "readOnly": true, + "title": "Data Cache Path" + }, "data_cache_subpath": { "anyOf": [ { @@ -2852,6 +2904,20 @@ "description": "Path to EOS data directory.", "title": "Data Folder Path" }, + "data_output_path": { + "anyOf": [ + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Compute data_output_path based on data_folder_path.", + "readOnly": true, + "title": "Data Output Path" + }, "data_output_subpath": { "anyOf": [ { @@ -3045,6 +3111,24 @@ "description": "Yearly energy consumption (kWh).", "title": "Loadakkudoktor Year Energy" }, + "logging_level_default": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "EOS default logging level.", + "title": "Logging Level Default" + }, + "logging_level_root": { + "description": "Root logger logging level.", + "readOnly": true, + "title": "Logging Level Root", + "type": "string" + }, "longitude": { "anyOf": [ { @@ -3243,6 +3327,7 @@ "type": "null" } ], + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast0 Loss" }, @@ -3442,7 +3527,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast1 Loss" }, @@ -3642,7 +3727,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast2 Loss" }, @@ -3842,7 +3927,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast3 Loss" }, @@ -4042,7 +4127,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast4 Loss" }, @@ -4242,7 +4327,7 @@ "type": "null" } ], - "default": 0, + "default": 14.0, "description": "Sum of PV system losses in percent", "title": "Pvforecast5 Loss" }, @@ -4397,6 +4482,52 @@ "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", "title": "Pvforecast5 Userhorizon" }, + "pvforecast_planes": { + "description": "Compute a list of active planes.", + "items": { + "type": "string" + }, + "readOnly": true, + "title": "Pvforecast Planes", + "type": "array" + }, + "pvforecast_planes_azimuth": { + "description": "Compute a list of the azimuths per active planes.", + "items": { + "type": "number" + }, + "readOnly": true, + "title": "Pvforecast Planes Azimuth", + "type": "array" + }, + "pvforecast_planes_inverter_paco": { + "description": "Compute a list of the maximum power rating of the inverter per active planes.", + "readOnly": true, + "title": "Pvforecast Planes Inverter Paco" + }, + "pvforecast_planes_peakpower": { + "description": "Compute a list of the peak power per active planes.", + "items": { + "type": "number" + }, + "readOnly": true, + "title": "Pvforecast Planes Peakpower", + "type": "array" + }, + "pvforecast_planes_tilt": { + "description": "Compute a list of the tilts per active planes.", + "items": { + "type": "number" + }, + "readOnly": true, + "title": "Pvforecast Planes Tilt", + "type": "array" + }, + "pvforecast_planes_userhorizon": { + "description": "Compute a list of the user horizon per active planes.", + "readOnly": true, + "title": "Pvforecast Planes Userhorizon" + }, "pvforecast_provider": { "anyOf": [ { @@ -4517,6 +4648,19 @@ "description": "FastHTML server IP port number.", "title": "Server Fasthtml Port" }, + "timezone": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Compute timezone based on latitude and longitude.", + "readOnly": true, + "title": "Timezone" + }, "weather_provider": { "anyOf": [ { @@ -4558,6 +4702,18 @@ "title": "Weatherimport Json" } }, + "required": [ + "pvforecast_planes", + "pvforecast_planes_peakpower", + "pvforecast_planes_azimuth", + "pvforecast_planes_tilt", + "pvforecast_planes_userhorizon", + "pvforecast_planes_inverter_paco", + "timezone", + "logging_level_root", + "data_output_path", + "data_cache_path" + ], "title": "SettingsEOS", "type": "object" }, @@ -4867,7 +5023,7 @@ "paths": { "/gesamtlast": { "post": { - "description": "Deprecated: Total Load Prediction with adjustment.\n\nEndpoint to handle total load prediction adjusted by latest measured data.\n\nNote:\n Use '/v1/prediction/list?key=load_mean_adjusted' instead.\n Load energy meter readings to be added to EOS measurement by:\n '/v1/measurement/load-mr/value/by-name' or\n '/v1/measurement/value'", + "description": "Deprecated: Total Load Prediction with adjustment.\n\nEndpoint to handle total load prediction adjusted by latest measured data.\n\nTotal load prediction starts at 00.00.00 today and is provided for 48 hours.\nIf no prediction values are available the missing ones at the start of the series are\nfilled with the first available prediction value.\n\nNote:\n Use '/v1/prediction/list?key=load_mean_adjusted' instead.\n Load energy meter readings to be added to EOS measurement by:\n '/v1/measurement/load-mr/value/by-name' or\n '/v1/measurement/value'", "operationId": "fastapi_gesamtlast_gesamtlast_post", "requestBody": { "content": { @@ -4910,7 +5066,7 @@ }, "/gesamtlast_simple": { "get": { - "description": "Deprecated: Total Load Prediction.\n\nEndpoint to handle total load prediction.\n\nNote:\n Set LoadAkkudoktor as load_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=load_mean' instead.", + "description": "Deprecated: Total Load Prediction.\n\nEndpoint to handle total load prediction.\n\nTotal load prediction starts at 00.00.00 today and is provided for 48 hours.\nIf no prediction values are available the missing ones at the start of the series are\nfilled with the first available prediction value.\n\nNote:\n Set LoadAkkudoktor as load_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=load_mean' instead.", "operationId": "fastapi_gesamtlast_simple_gesamtlast_simple_get", "parameters": [ { @@ -5012,7 +5168,7 @@ }, "/pvforecast": { "get": { - "description": "Deprecated: PV Forecast Prediction.\n\nEndpoint to handle PV forecast prediction.\n\nNote:\n Set PVForecastAkkudoktor as pvforecast_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=pvforecast_ac_power' and\n '/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.", + "description": "Deprecated: PV Forecast Prediction.\n\nEndpoint to handle PV forecast prediction.\n\nPVForecast starts at 00.00.00 today and is provided for 48 hours.\nIf no forecast values are available the missing ones at the start of the series are\nfilled with the first available forecast value.\n\nNote:\n Set PVForecastAkkudoktor as pvforecast_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=pvforecast_ac_power' and\n '/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead.", "operationId": "fastapi_pvforecast_pvforecast_get", "responses": { "200": { @@ -5031,7 +5187,7 @@ }, "/strompreis": { "get": { - "description": "Deprecated: Electricity Market Price Prediction per Wh (\u20ac/Wh).\n\nNote:\n Set ElecPriceAkkudoktor as elecprice_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=elecprice_marketprice_wh' or\n '/v1/prediction/list?key=elecprice_marketprice_kwh' instead.", + "description": "Deprecated: Electricity Market Price Prediction per Wh (\u20ac/Wh).\n\nElectricity prices start at 00.00.00 today and are provided for 48 hours.\nIf no prices are available the missing ones at the start of the series are\nfilled with the first available price.\n\nNote:\n Electricity price charges are added.\n\nNote:\n Set ElecPriceAkkudoktor as elecprice_provider, then update data with\n '/v1/prediction/update'\n and then request data with\n '/v1/prediction/list?key=elecprice_marketprice_wh' or\n '/v1/prediction/list?key=elecprice_marketprice_kwh' instead.", "operationId": "fastapi_strompreis_strompreis_get", "responses": { "200": { @@ -5054,7 +5210,7 @@ }, "/v1/config": { "get": { - "description": "Get the current configuration.", + "description": "Get the current configuration.\n\nReturns:\n configuration (ConfigEOS): The current configuration.", "operationId": "fastapi_config_get_v1_config_get", "responses": { "200": { @@ -5071,12 +5227,52 @@ "summary": "Fastapi Config Get" }, "put": { - "description": "Merge settings into current configuration.\n\nArgs:\n settings (SettingsEOS): The settings to merge into the current configuration.\n save (Optional[bool]): Save the resulting configuration to the configuration file.\n Defaults to False.", + "description": "Write the provided settings into the current settings.\n\nThe existing settings are completely overwritten. Note that for any setting\nvalue that is None, the configuration will fall back to values from other sources such as\nenvironment variables, the EOS configuration file, or default values.\n\nArgs:\n settings (SettingsEOS): The settings to write into the current settings.\n\nReturns:\n configuration (ConfigEOS): The current configuration after the write.", "operationId": "fastapi_config_put_v1_config_put", "parameters": [ { + "description": "FastAPI server IP address.", "in": "query", - "name": "save", + "name": "server_fastapi_host", + "required": false, + "schema": { + "anyOf": [ + { + "format": "ipvanyaddress", + "type": "string" + }, + { + "type": "null" + } + ], + "default": "0.0.0.0", + "description": "FastAPI server IP address.", + "title": "Server Fastapi Host" + } + }, + { + "description": "FastAPI server IP port number.", + "in": "query", + "name": "server_fastapi_port", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 8503, + "description": "FastAPI server IP port number.", + "title": "Server Fastapi Port" + } + }, + { + "description": "Enable debug output", + "in": "query", + "name": "server_fastapi_verbose", "required": false, "schema": { "anyOf": [ @@ -5087,20 +5283,2791 @@ "type": "null" } ], - "title": "Save" + "default": false, + "description": "Enable debug output", + "title": "Server Fastapi Verbose" + } + }, + { + "description": "FastAPI server to startup application FastHTML server.", + "in": "query", + "name": "server_fastapi_startup_server_fasthtml", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": true, + "description": "FastAPI server to startup application FastHTML server.", + "title": "Server Fastapi Startup Server Fasthtml" + } + }, + { + "description": "FastHTML server IP address.", + "in": "query", + "name": "server_fasthtml_host", + "required": false, + "schema": { + "anyOf": [ + { + "format": "ipvanyaddress", + "type": "string" + }, + { + "type": "null" + } + ], + "default": "0.0.0.0", + "description": "FastHTML server IP address.", + "title": "Server Fasthtml Host" + } + }, + { + "description": "FastHTML server IP port number.", + "in": "query", + "name": "server_fasthtml_port", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 8504, + "description": "FastHTML server IP port number.", + "title": "Server Fasthtml Port" + } + }, + { + "description": "Path to the file to import weather data from.", + "in": "query", + "name": "weatherimport_file_path", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Path to the file to import weather data from.", + "title": "Weatherimport File Path" + } + }, + { + "description": "JSON string, dictionary of weather forecast value lists.", + "in": "query", + "name": "weatherimport_json", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "JSON string, dictionary of weather forecast value lists.", + "title": "Weatherimport Json" + } + }, + { + "description": "Weather provider id of provider to be used.", + "in": "query", + "name": "weather_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Weather provider id of provider to be used.", + "title": "Weather Provider" + } + }, + { + "description": "Path to the file to import PV forecast data from.", + "in": "query", + "name": "pvforecastimport_file_path", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Path to the file to import PV forecast data from.", + "title": "Pvforecastimport File Path" + } + }, + { + "description": "JSON string, dictionary of PV forecast value lists.", + "in": "query", + "name": "pvforecastimport_json", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "JSON string, dictionary of PV forecast value lists.", + "title": "Pvforecastimport Json" + } + }, + { + "description": "PVForecast provider id of provider to be used.", + "in": "query", + "name": "pvforecast_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "PVForecast provider id of provider to be used.", + "title": "Pvforecast Provider" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast0_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast0 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast0_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast0 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast0_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast0 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast0_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast0 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast0_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast0 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast0_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast0 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast0_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast0 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast0_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast0 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast0_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast0 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast0_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast0 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast0_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast0 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast0_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast0 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast0_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast0 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast0_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast0 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast0_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast0 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast0_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast0 Strings Per Inverter" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast1_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast1 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast1_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast1 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast1_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast1 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast1_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast1 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast1_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast1 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast1_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast1 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast1_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast1 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast1_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast1 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast1_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast1 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast1_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast1 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast1_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast1 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast1_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast1 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast1_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast1 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast1_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast1 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast1_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast1 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast1_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast1 Strings Per Inverter" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast2_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast2 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast2_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast2 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast2_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast2 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast2_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast2 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast2_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast2 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast2_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast2 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast2_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast2 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast2_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast2 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast2_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast2 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast2_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast2 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast2_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast2 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast2_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast2 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast2_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast2 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast2_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast2 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast2_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast2 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast2_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast2 Strings Per Inverter" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast3_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast3 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast3_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast3 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast3_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast3 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast3_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast3 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast3_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast3 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast3_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast3 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast3_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast3 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast3_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast3 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast3_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast3 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast3_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast3 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast3_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast3 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast3_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast3 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast3_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast3 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast3_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast3 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast3_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast3 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast3_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast3 Strings Per Inverter" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast4_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast4 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast4_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast4 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast4_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast4 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast4_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast4 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast4_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast4 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast4_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast4 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast4_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast4 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast4_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast4 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast4_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast4 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast4_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast4 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast4_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast4 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast4_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast4 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast4_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast4 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast4_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast4 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast4_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast4 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast4_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast4 Strings Per Inverter" + } + }, + { + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast5_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Tilt angle from horizontal plane. Ignored for two-axis tracking.", + "title": "Pvforecast5 Surface Tilt" + } + }, + { + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "in": "query", + "name": "pvforecast5_surface_azimuth", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Orientation (azimuth angle) of the (fixed) plane. Clockwise from north (north=0, east=90, south=180, west=270).", + "title": "Pvforecast5 Surface Azimuth" + } + }, + { + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "in": "query", + "name": "pvforecast5_userhorizon", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "description": "Elevation of horizon in degrees, at equally spaced azimuth clockwise from north.", + "title": "Pvforecast5 Userhorizon" + } + }, + { + "description": "Nominal power of PV system in kW.", + "in": "query", + "name": "pvforecast5_peakpower", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Nominal power of PV system in kW.", + "title": "Pvforecast5 Peakpower" + } + }, + { + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "in": "query", + "name": "pvforecast5_pvtechchoice", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "crystSi", + "description": "PV technology. One of 'crystSi', 'CIS', 'CdTe', 'Unknown'.", + "title": "Pvforecast5 Pvtechchoice" + } + }, + { + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "in": "query", + "name": "pvforecast5_mountingplace", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": "free", + "description": "Type of mounting for PV system. Options are 'free' for free-standing and 'building' for building-integrated.", + "title": "Pvforecast5 Mountingplace" + } + }, + { + "description": "Sum of PV system losses in percent", + "in": "query", + "name": "pvforecast5_loss", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": 14.0, + "description": "Sum of PV system losses in percent", + "title": "Pvforecast5 Loss" + } + }, + { + "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.", + "in": "query", + "name": "pvforecast5_trackingtype", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "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.", + "title": "Pvforecast5 Trackingtype" + } + }, + { + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast5_optimal_surface_tilt", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt angle. Ignored for two-axis tracking.", + "title": "Pvforecast5 Optimal Surface Tilt" + } + }, + { + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "in": "query", + "name": "pvforecast5_optimalangles", + "required": false, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": false, + "description": "Calculate the optimum tilt and azimuth angles. Ignored for two-axis tracking.", + "title": "Pvforecast5 Optimalangles" + } + }, + { + "description": "Proportion of the light hitting the ground that it reflects back.", + "in": "query", + "name": "pvforecast5_albedo", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Proportion of the light hitting the ground that it reflects back.", + "title": "Pvforecast5 Albedo" + } + }, + { + "description": "Model of the PV modules of this plane.", + "in": "query", + "name": "pvforecast5_module_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the PV modules of this plane.", + "title": "Pvforecast5 Module Model" + } + }, + { + "description": "Model of the inverter of this plane.", + "in": "query", + "name": "pvforecast5_inverter_model", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Model of the inverter of this plane.", + "title": "Pvforecast5 Inverter Model" + } + }, + { + "description": "AC power rating of the inverter. [W]", + "in": "query", + "name": "pvforecast5_inverter_paco", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "AC power rating of the inverter. [W]", + "title": "Pvforecast5 Inverter Paco" + } + }, + { + "description": "Number of the PV modules of the strings of this plane.", + "in": "query", + "name": "pvforecast5_modules_per_string", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the PV modules of the strings of this plane.", + "title": "Pvforecast5 Modules Per String" + } + }, + { + "description": "Number of the strings of the inverter of this plane.", + "in": "query", + "name": "pvforecast5_strings_per_inverter", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Number of the strings of the inverter of this plane.", + "title": "Pvforecast5 Strings Per Inverter" + } + }, + { + "description": "Path to the file to import load data from.", + "in": "query", + "name": "load_import_file_path", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Path to the file to import load data from.", + "title": "Load Import File Path" + } + }, + { + "description": "JSON string, dictionary of load forecast value lists.", + "in": "query", + "name": "load_import_json", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "JSON string, dictionary of load forecast value lists.", + "title": "Load Import Json" + } + }, + { + "description": "Yearly energy consumption (kWh).", + "in": "query", + "name": "loadakkudoktor_year_energy", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Yearly energy consumption (kWh).", + "title": "Loadakkudoktor Year Energy" + } + }, + { + "description": "Load provider id of provider to be used.", + "in": "query", + "name": "load_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Load provider id of provider to be used.", + "title": "Load Provider" + } + }, + { + "description": "Path to the file to import elecprice data from.", + "in": "query", + "name": "elecpriceimport_file_path", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Path to the file to import elecprice data from.", + "title": "Elecpriceimport File Path" + } + }, + { + "description": "JSON string, dictionary of electricity price forecast value lists.", + "in": "query", + "name": "elecpriceimport_json", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "JSON string, dictionary of electricity price forecast value lists.", + "title": "Elecpriceimport Json" + } + }, + { + "description": "Electricity price provider id of provider to be used.", + "in": "query", + "name": "elecprice_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Electricity price provider id of provider to be used.", + "title": "Elecprice Provider" + } + }, + { + "description": "Electricity price charges (\u20ac/kWh).", + "in": "query", + "name": "elecprice_charges_kwh", + "required": false, + "schema": { + "anyOf": [ + { + "minimum": 0.0, + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Electricity price charges (\u20ac/kWh).", + "title": "Elecprice Charges Kwh" + } + }, + { + "description": "Number of hours into the future for predictions", + "in": "query", + "name": "prediction_hours", + "required": false, + "schema": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 48, + "description": "Number of hours into the future for predictions", + "title": "Prediction Hours" + } + }, + { + "description": "Number of hours into the past for historical predictions data", + "in": "query", + "name": "prediction_historic_hours", + "required": false, + "schema": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 48, + "description": "Number of hours into the past for historical predictions data", + "title": "Prediction Historic Hours" + } + }, + { + "description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)", + "in": "query", + "name": "latitude", + "required": false, + "schema": { + "anyOf": [ + { + "maximum": 90.0, + "minimum": -90.0, + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Latitude in decimal degrees, between -90 and 90, north is positive (ISO 19115) (\u00b0)", + "title": "Latitude" + } + }, + { + "description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)", + "in": "query", + "name": "longitude", + "required": false, + "schema": { + "anyOf": [ + { + "maximum": 180.0, + "minimum": -180.0, + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Longitude in decimal degrees, within -180 to 180 (\u00b0)", + "title": "Longitude" + } + }, + { + "description": "Number of hours into the future for optimizations.", + "in": "query", + "name": "optimization_hours", + "required": false, + "schema": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 24, + "description": "Number of hours into the future for optimizations.", + "title": "Optimization Hours" + } + }, + { + "description": "Penalty factor used in optimization.", + "in": "query", + "name": "optimization_penalty", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": 10, + "description": "Penalty factor used in optimization.", + "title": "Optimization Penalty" + } + }, + { + "description": "Charge rates available for the EV in percent of maximum charge.", + "in": "query", + "name": "optimization_ev_available_charge_rates_percent", + "required": false, + "schema": { + "anyOf": [ + { + "items": { + "type": "number" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": [ + 0.0, + 0.375, + 0.5, + 0.625, + 0.75, + 0.875, + 1.0 + ], + "description": "Charge rates available for the EV in percent of maximum charge.", + "title": "Optimization Ev Available Charge Rates Percent" + } + }, + { + "description": "Name of the load0 source (e.g. 'Household', 'Heat Pump')", + "in": "query", + "name": "measurement_load0_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Name of the load0 source (e.g. 'Household', 'Heat Pump')", + "title": "Measurement Load0 Name" + } + }, + { + "description": "Name of the load1 source (e.g. 'Household', 'Heat Pump')", + "in": "query", + "name": "measurement_load1_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Name of the load1 source (e.g. 'Household', 'Heat Pump')", + "title": "Measurement Load1 Name" + } + }, + { + "description": "Name of the load2 source (e.g. 'Household', 'Heat Pump')", + "in": "query", + "name": "measurement_load2_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Name of the load2 source (e.g. 'Household', 'Heat Pump')", + "title": "Measurement Load2 Name" + } + }, + { + "description": "Name of the load3 source (e.g. 'Household', 'Heat Pump')", + "in": "query", + "name": "measurement_load3_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Name of the load3 source (e.g. 'Household', 'Heat Pump')", + "title": "Measurement Load3 Name" + } + }, + { + "description": "Name of the load4 source (e.g. 'Household', 'Heat Pump')", + "in": "query", + "name": "measurement_load4_name", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Name of the load4 source (e.g. 'Household', 'Heat Pump')", + "title": "Measurement Load4 Name" + } + }, + { + "description": "Id of Battery simulation provider.", + "in": "query", + "name": "battery_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Id of Battery simulation provider.", + "title": "Battery Provider" + } + }, + { + "description": "Battery capacity [Wh].", + "in": "query", + "name": "battery_capacity", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery capacity [Wh].", + "title": "Battery Capacity" + } + }, + { + "description": "Battery initial state of charge [%].", + "in": "query", + "name": "battery_initial_soc", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery initial state of charge [%].", + "title": "Battery Initial Soc" + } + }, + { + "description": "Battery minimum state of charge [%].", + "in": "query", + "name": "battery_soc_min", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery minimum state of charge [%].", + "title": "Battery Soc Min" + } + }, + { + "description": "Battery maximum state of charge [%].", + "in": "query", + "name": "battery_soc_max", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery maximum state of charge [%].", + "title": "Battery Soc Max" + } + }, + { + "description": "Battery charging efficiency [%].", + "in": "query", + "name": "battery_charging_efficiency", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Battery charging efficiency [%].", + "title": "Battery Charging Efficiency" + } + }, + { + "description": "Battery discharging efficiency [%].", + "in": "query", + "name": "battery_discharging_efficiency", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Battery discharging efficiency [%].", + "title": "Battery Discharging Efficiency" + } + }, + { + "description": "Battery maximum charge power [W].", + "in": "query", + "name": "battery_max_charging_power", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery maximum charge power [W].", + "title": "Battery Max Charging Power" + } + }, + { + "description": "Id of Battery Electric Vehicle simulation provider.", + "in": "query", + "name": "bev_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Id of Battery Electric Vehicle simulation provider.", + "title": "Bev Provider" + } + }, + { + "description": "Battery Electric Vehicle capacity [Wh].", + "in": "query", + "name": "bev_capacity", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle capacity [Wh].", + "title": "Bev Capacity" + } + }, + { + "description": "Battery Electric Vehicle initial state of charge [%].", + "in": "query", + "name": "bev_initial_soc", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle initial state of charge [%].", + "title": "Bev Initial Soc" + } + }, + { + "description": "Battery Electric Vehicle maximum state of charge [%].", + "in": "query", + "name": "bev_soc_max", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle maximum state of charge [%].", + "title": "Bev Soc Max" + } + }, + { + "description": "Battery Electric Vehicle charging efficiency [%].", + "in": "query", + "name": "bev_charging_efficiency", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle charging efficiency [%].", + "title": "Bev Charging Efficiency" + } + }, + { + "description": "Battery Electric Vehicle discharging efficiency [%].", + "in": "query", + "name": "bev_discharging_efficiency", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle discharging efficiency [%].", + "title": "Bev Discharging Efficiency" + } + }, + { + "description": "Battery Electric Vehicle maximum charge power [W].", + "in": "query", + "name": "bev_max_charging_power", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Battery Electric Vehicle maximum charge power [W].", + "title": "Bev Max Charging Power" + } + }, + { + "description": "Id of Dish Washer simulation provider.", + "in": "query", + "name": "dishwasher_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Id of Dish Washer simulation provider.", + "title": "Dishwasher Provider" + } + }, + { + "description": "Dish Washer energy consumption [Wh].", + "in": "query", + "name": "dishwasher_consumption", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Dish Washer energy consumption [Wh].", + "title": "Dishwasher Consumption" + } + }, + { + "description": "Dish Washer usage duration [h].", + "in": "query", + "name": "dishwasher_duration", + "required": false, + "schema": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "description": "Dish Washer usage duration [h].", + "title": "Dishwasher Duration" + } + }, + { + "description": "Id of PV Inverter simulation provider.", + "in": "query", + "name": "inverter_provider", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Id of PV Inverter simulation provider.", + "title": "Inverter Provider" + } + }, + { + "description": "Inverter maximum power [W].", + "in": "query", + "name": "inverter_power_max", + "required": false, + "schema": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "description": "Inverter maximum power [W].", + "title": "Inverter Power Max" + } + }, + { + "description": "EOS default logging level.", + "in": "query", + "name": "logging_level_default", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "description": "EOS default logging level.", + "title": "Logging Level Default" + } + }, + { + "description": "Path to EOS data directory.", + "in": "query", + "name": "data_folder_path", + "required": false, + "schema": { + "anyOf": [ + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "description": "Path to EOS data directory.", + "title": "Data Folder Path" + } + }, + { + "description": "Sub-path for the EOS output data directory.", + "in": "query", + "name": "data_output_subpath", + "required": false, + "schema": { + "anyOf": [ + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "default": "output", + "description": "Sub-path for the EOS output data directory.", + "title": "Data Output Subpath" + } + }, + { + "description": "Sub-path for the EOS cache data directory.", + "in": "query", + "name": "data_cache_subpath", + "required": false, + "schema": { + "anyOf": [ + { + "format": "path", + "type": "string" + }, + { + "type": "null" + } + ], + "default": "cache", + "description": "Sub-path for the EOS cache data directory.", + "title": "Data Cache Subpath" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SettingsEOS" - } - } - }, - "required": true - }, "responses": { "200": { "content": { @@ -5126,6 +8093,113 @@ "summary": "Fastapi Config Put" } }, + "/v1/config/file": { + "get": { + "description": "Get the settings as defined by the EOS configuration file.\n\nReturns:\n settings (SettingsEOS): The settings defined by the EOS configuration file.", + "operationId": "fastapi_config_file_get_v1_config_file_get", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SettingsEOS" + } + } + }, + "description": "Successful Response" + } + }, + "summary": "Fastapi Config File Get" + }, + "put": { + "description": "Save the current configuration to the EOS configuration file.\n\nReturns:\n configuration (ConfigEOS): The current configuration that was saved.", + "operationId": "fastapi_config_file_put_v1_config_file_put", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigEOS" + } + } + }, + "description": "Successful Response" + } + }, + "summary": "Fastapi Config File Put" + } + }, + "/v1/config/update": { + "post": { + "description": "Update the configuration from the EOS configuration file.\n\nReturns:\n configuration (ConfigEOS): The current configuration after update.", + "operationId": "fastapi_config_update_post_v1_config_update_post", + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigEOS" + } + } + }, + "description": "Successful Response" + } + }, + "summary": "Fastapi Config Update Post" + } + }, + "/v1/config/value": { + "put": { + "description": "Set the configuration option in the settings.\n\nArgs:\n key (str): configuration key\n value (Any): configuration value\n\nReturns:\n configuration (ConfigEOS): The current configuration after the write.", + "operationId": "fastapi_config_value_put_v1_config_value_put", + "parameters": [ + { + "description": "configuration key", + "in": "query", + "name": "key", + "required": true, + "schema": { + "description": "configuration key", + "title": "Key", + "type": "string" + } + }, + { + "description": "configuration value", + "in": "query", + "name": "value", + "required": true, + "schema": { + "description": "configuration value", + "title": "Value" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigEOS" + } + } + }, + "description": "Successful Response" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error" + } + }, + "summary": "Fastapi Config Value Put" + } + }, "/v1/measurement/data": { "put": { "description": "Merge the measurement data given as datetime data into EOS measurements.", diff --git a/scripts/generate_config_md.py b/scripts/generate_config_md.py new file mode 100755 index 0000000..4952260 --- /dev/null +++ b/scripts/generate_config_md.py @@ -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() diff --git a/scripts/generate_openapi_md.py b/scripts/generate_openapi_md.py index 2bca16a..04000e7 100755 --- a/scripts/generate_openapi_md.py +++ b/scripts/generate_openapi_md.py @@ -189,8 +189,10 @@ def openapi_to_markdown(openapi_json: dict) -> str: markdown += "**Endpoints**:\n\n" paths = openapi_json.get("paths", {}) - for path, methods in paths.items(): - for method, details in methods.items(): + for path in sorted(paths): + methods = paths[path] + for method in sorted(methods): + details = methods[method] markdown += format_endpoint(path, method, details) # Assure the is no double \n at end of file diff --git a/src/akkudoktoreos/config/config.py b/src/akkudoktoreos/config/config.py index 40f967b..63d9643 100644 --- a/src/akkudoktoreos/config/config.py +++ b/src/akkudoktoreos/config/config.py @@ -20,6 +20,8 @@ from pydantic import Field, ValidationError, computed_field # settings from akkudoktoreos.config.configabc import SettingsBaseModel 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.measurement.measurement import MeasurementCommonSettings 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.weatherimport import WeatherImportCommonSettings from akkudoktoreos.server.server import ServerCommonSettings -from akkudoktoreos.utils.logutil import get_logger from akkudoktoreos.utils.utils import UtilsCommonSettings logger = get_logger(__name__) @@ -90,6 +91,7 @@ class ConfigCommonSettings(SettingsBaseModel): class SettingsEOS( ConfigCommonSettings, + LoggingCommonSettings, DevicesCommonSettings, MeasurementCommonSettings, OptimizationCommonSettings, @@ -188,7 +190,13 @@ class ConfigEOS(SingletonMixin, SettingsEOS): @property def config_default_file_path(self) -> 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_field # type: ignore[prop-decorator] @@ -200,6 +208,15 @@ class ConfigEOS(SingletonMixin, SettingsEOS): key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys())) 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: """Initializes the singleton ConfigEOS instance. @@ -239,7 +256,7 @@ class ConfigEOS(SingletonMixin, SettingsEOS): settings (SettingsEOS): The settings to apply globally. force (Optional[bool]): If True, overwrites the existing settings completely. If False, the new settings are merged to the existing ones with priority for - the new ones. + the new ones. Defaults to False. Raises: ValueError: If settings are already set and `force` is not True or @@ -349,14 +366,23 @@ class ConfigEOS(SingletonMixin, SettingsEOS): return cfile, True return config_dirs[0].joinpath(self.CONFIG_FILE_NAME), False - def from_config_file(self) -> None: - """Loads the configuration file settings for EOS. + def settings_from_config_file(self) -> tuple[SettingsEOS, Path]: + """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: ValueError: If the configuration file is invalid or incomplete. """ config_file, exists = self._get_config_file_path() config_dir = config_file.parent + + # Create config directory and copy default config if file does not exist if not exists: config_dir.mkdir(parents=True, exist_ok=True) try: @@ -366,18 +392,39 @@ class ConfigEOS(SingletonMixin, SettingsEOS): config_file = self.config_default_file_path config_dir = config_file.parent + # Load and validate the configuration file with config_file.open("r", encoding=self.ENCODING) as f_in: try: 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: 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() - # 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 + return ConfigEOS._file_settings, config_file + def to_config_file(self) -> None: """Saves the current configuration to the configuration file. diff --git a/src/akkudoktoreos/core/coreabc.py b/src/akkudoktoreos/core/coreabc.py index af0df32..4b00783 100644 --- a/src/akkudoktoreos/core/coreabc.py +++ b/src/akkudoktoreos/core/coreabc.py @@ -16,7 +16,7 @@ from typing import Any, ClassVar, Dict, Optional, Type from pendulum import DateTime from pydantic import computed_field -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/core/dataabc.py b/src/akkudoktoreos/core/dataabc.py index be2914c..23051fd 100644 --- a/src/akkudoktoreos/core/dataabc.py +++ b/src/akkudoktoreos/core/dataabc.py @@ -31,13 +31,13 @@ from pydantic import ( ) from akkudoktoreos.core.coreabc import ConfigMixin, SingletonMixin, StartMixin +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import ( PydanticBaseModel, PydanticDateTimeData, PydanticDateTimeDataFrame, ) from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) @@ -583,20 +583,48 @@ class DataSequence(DataBase, MutableSequence): # Sort the list by datetime after adding/updating self.sort_by_datetime() - def update_value(self, date: DateTime, key: str, value: Any) -> None: - """Updates a specific value in the data record for a given date. + @overload + 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. - Otherwise, appends a new record with the given value and maintains chronological order. + @overload + 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: - date (datetime): The date for which the weather value is to be added or updated. - key (str): The attribute name to be updated. - value: The new value to set for the specified attribute. + date (datetime): The date for which the values are to be added or updated. + key (str), value (Any): Single key-value pair to update + 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 date = to_datetime(date, to_maxtime=False) + # Check if a record with the given date already exists for record in self.records: 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__}`." ) if compare_datetimes(record.date_time, date).equal: - # Update the DataRecord with the new value for the specified key - setattr(record, key, value) + # Update the DataRecord with all new values + for key, value in values.items(): + setattr(record, key, value) break else: # 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) # Sort the list by datetime after adding/updating self.sort_by_datetime() @@ -841,7 +870,7 @@ class DataSequence(DataBase, MutableSequence): if start_index == 0: # No value before start # Add dummy value - dates.insert(0, dates[0] - interval) + dates.insert(0, start_datetime - interval) values.insert(0, values[0]) elif start_index > 1: # Truncate all values before latest value before start_datetime diff --git a/src/akkudoktoreos/core/ems.py b/src/akkudoktoreos/core/ems.py index 5905e68..5063d13 100644 --- a/src/akkudoktoreos/core/ems.py +++ b/src/akkudoktoreos/core/ems.py @@ -7,12 +7,12 @@ from pydantic import ConfigDict, Field, computed_field, field_validator, model_v from typing_extensions import Self from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.devices.battery import Battery from akkudoktoreos.devices.generic import HomeAppliance from akkudoktoreos.devices.inverter import Inverter from akkudoktoreos.utils.datetimeutil import to_datetime -from akkudoktoreos.utils.logutil import get_logger from akkudoktoreos.utils.utils import NumpyEncoder logger = get_logger(__name__) diff --git a/src/akkudoktoreos/core/logabc.py b/src/akkudoktoreos/core/logabc.py new file mode 100644 index 0000000..6dc4c57 --- /dev/null +++ b/src/akkudoktoreos/core/logabc.py @@ -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 diff --git a/src/akkudoktoreos/utils/logutil.py b/src/akkudoktoreos/core/logging.py similarity index 77% rename from src/akkudoktoreos/utils/logutil.py rename to src/akkudoktoreos/core/logging.py index 39205ba..1cc26a4 100644 --- a/src/akkudoktoreos/utils/logutil.py +++ b/src/akkudoktoreos/core/logging.py @@ -15,19 +15,21 @@ Notes: - The logger supports rotating log files to prevent excessive log file size. """ -import logging +import logging as pylogging import os from logging.handlers import RotatingFileHandler from typing import Optional +from akkudoktoreos.core.logabc import logging_str_to_level + def get_logger( name: str, log_file: Optional[str] = None, - logging_level: Optional[str] = "INFO", + logging_level: Optional[str] = None, max_bytes: int = 5000000, backup_count: int = 5, -) -> logging.Logger: +) -> pylogging.Logger: """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 @@ -48,31 +50,22 @@ def get_logger( logger.info("Application started") """ # Create a logger with the specified name - logger = logging.getLogger(name) + logger = pylogging.getLogger(name) logger.propagate = True - if (env_level := os.getenv("EOS_LOGGING_LEVEL")) is not None: - logging_level = env_level - if logging_level == "DEBUG": - 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) + if logging_level is not None: + level = logging_str_to_level(logging_level) + logger.setLevel(level) # 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 # There may already be a logger from pytest if not logger.handlers: # Create a console handler with a standard output stream - console_handler = logging.StreamHandler() - console_handler.setLevel(level) + console_handler = pylogging.StreamHandler() + if logging_level is not None: + console_handler.setLevel(level) console_handler.setFormatter(formatter) # Add the console handler to the logger @@ -88,7 +81,8 @@ def get_logger( # Create a rotating file handler 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) # Add the file handler to the logger diff --git a/src/akkudoktoreos/core/logsettings.py b/src/akkudoktoreos/core/logsettings.py new file mode 100644 index 0000000..6407329 --- /dev/null +++ b/src/akkudoktoreos/core/logsettings.py @@ -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 diff --git a/src/akkudoktoreos/core/pydantic.py b/src/akkudoktoreos/core/pydantic.py index 5a65ffc..0cebb4c 100644 --- a/src/akkudoktoreos/core/pydantic.py +++ b/src/akkudoktoreos/core/pydantic.py @@ -275,6 +275,7 @@ class PydanticDateTimeDataFrame(PydanticBaseModel): ) @field_validator("tz") + @classmethod def validate_timezone(cls, v: Optional[str]) -> Optional[str]: """Validate that the timezone is valid.""" if v is not None: diff --git a/src/akkudoktoreos/data/default.config.json b/src/akkudoktoreos/data/default.config.json index 29e4a55..3a8dd6f 100644 --- a/src/akkudoktoreos/data/default.config.json +++ b/src/akkudoktoreos/data/default.config.json @@ -14,6 +14,7 @@ "load_name": null, "load_provider": null, "loadakkudoktor_year_energy": null, + "logging_level": "INFO", "longitude": 13.4, "optimization_ev_available_charge_rates_percent": null, "optimization_hours": 48, diff --git a/src/akkudoktoreos/devices/battery.py b/src/akkudoktoreos/devices/battery.py index 0ca5708..6f6fe47 100644 --- a/src/akkudoktoreos/devices/battery.py +++ b/src/akkudoktoreos/devices/battery.py @@ -3,8 +3,8 @@ from typing import Any, Optional import numpy as np from pydantic import BaseModel, Field, field_validator +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.devices.devicesabc import DeviceBase -from akkudoktoreos.utils.logutil import get_logger from akkudoktoreos.utils.utils import NumpyEncoder logger = get_logger(__name__) diff --git a/src/akkudoktoreos/devices/devices.py b/src/akkudoktoreos/devices/devices.py index 87c9629..3dc619b 100644 --- a/src/akkudoktoreos/devices/devices.py +++ b/src/akkudoktoreos/devices/devices.py @@ -6,13 +6,13 @@ from pydantic import Field, computed_field from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.core.coreabc import SingletonMixin +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.devices.battery import Battery from akkudoktoreos.devices.devicesabc import DevicesBase from akkudoktoreos.devices.generic import HomeAppliance from akkudoktoreos.devices.inverter import Inverter from akkudoktoreos.prediction.interpolator import SelfConsumptionPropabilityInterpolator from akkudoktoreos.utils.datetimeutil import to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/devices/devicesabc.py b/src/akkudoktoreos/devices/devicesabc.py index 2874f84..0e64783 100644 --- a/src/akkudoktoreos/devices/devicesabc.py +++ b/src/akkudoktoreos/devices/devicesabc.py @@ -10,9 +10,9 @@ from akkudoktoreos.core.coreabc import ( EnergyManagementSystemMixin, PredictionMixin, ) +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.utils.datetimeutil import to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/devices/generic.py b/src/akkudoktoreos/devices/generic.py index 2fdb79b..6083386 100644 --- a/src/akkudoktoreos/devices/generic.py +++ b/src/akkudoktoreos/devices/generic.py @@ -3,8 +3,8 @@ from typing import Optional import numpy as np from pydantic import BaseModel, Field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.devices.devicesabc import DeviceBase -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/devices/inverter.py b/src/akkudoktoreos/devices/inverter.py index b820beb..8e32b16 100644 --- a/src/akkudoktoreos/devices/inverter.py +++ b/src/akkudoktoreos/devices/inverter.py @@ -3,9 +3,9 @@ from typing import Optional from pydantic import BaseModel, Field from scipy.interpolate import RegularGridInterpolator +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.devices.battery import Battery from akkudoktoreos.devices.devicesabc import DeviceBase -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/measurement/measurement.py b/src/akkudoktoreos/measurement/measurement.py index 3c8b63c..9a70f73 100644 --- a/src/akkudoktoreos/measurement/measurement.py +++ b/src/akkudoktoreos/measurement/measurement.py @@ -16,8 +16,8 @@ from pydantic import Field, computed_field from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.core.coreabc import SingletonMixin 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.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/optimization/optimization.py b/src/akkudoktoreos/optimization/optimization.py index 34e074c..d08c077 100644 --- a/src/akkudoktoreos/optimization/optimization.py +++ b/src/akkudoktoreos/optimization/optimization.py @@ -3,7 +3,7 @@ from typing import List, Optional from pydantic import Field from akkudoktoreos.config.configabc import SettingsBaseModel -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/optimization/optimizationabc.py b/src/akkudoktoreos/optimization/optimizationabc.py index 15bf46a..f82157f 100644 --- a/src/akkudoktoreos/optimization/optimizationabc.py +++ b/src/akkudoktoreos/optimization/optimizationabc.py @@ -3,8 +3,8 @@ from pydantic import ConfigDict from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import PydanticBaseModel -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/elecpriceabc.py b/src/akkudoktoreos/prediction/elecpriceabc.py index e409a51..df7cbb0 100644 --- a/src/akkudoktoreos/prediction/elecpriceabc.py +++ b/src/akkudoktoreos/prediction/elecpriceabc.py @@ -9,8 +9,8 @@ from typing import List, Optional from pydantic import Field, computed_field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/elecpriceakkudoktor.py b/src/akkudoktoreos/prediction/elecpriceakkudoktor.py index 8db35f2..05f4de7 100644 --- a/src/akkudoktoreos/prediction/elecpriceakkudoktor.py +++ b/src/akkudoktoreos/prediction/elecpriceakkudoktor.py @@ -13,11 +13,11 @@ import requests from numpydantic import NDArray, Shape from pydantic import Field, ValidationError +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.prediction.elecpriceabc import ElecPriceDataRecord, ElecPriceProvider from akkudoktoreos.utils.cacheutil import CacheFileStore, cache_in_file from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) @@ -218,17 +218,14 @@ class ElecPriceAkkudoktor(ElecPriceProvider): akkudoktor_value.marketpriceEurocentPerKWh / (100 * 1000) + charges_kwh / 1000 ) - if compare_datetimes(dt, self.start_datetime).lt: - # forecast data is too old + # We provide prediction starting at start of day, to be compatible to old system. + 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 continue self.elecprice_8days[dt.hour, 7] = price_wh - record = ElecPriceDataRecord( - date_time=dt, - elecprice_marketprice_wh=price_wh, - ) - self.append(record) + self.update_value(dt, "elecprice_marketprice_wh", price_wh) # Update 8day cache elecprice_cache_file.seek(0) diff --git a/src/akkudoktoreos/prediction/elecpriceimport.py b/src/akkudoktoreos/prediction/elecpriceimport.py index cc2c823..31c8a2f 100644 --- a/src/akkudoktoreos/prediction/elecpriceimport.py +++ b/src/akkudoktoreos/prediction/elecpriceimport.py @@ -12,9 +12,9 @@ from typing import Optional, Union from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.elecpriceabc import ElecPriceProvider from akkudoktoreos.prediction.predictionabc import PredictionImportProvider -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/load.py b/src/akkudoktoreos/prediction/load.py index 48e9bf7..057c5de 100644 --- a/src/akkudoktoreos/prediction/load.py +++ b/src/akkudoktoreos/prediction/load.py @@ -5,12 +5,14 @@ from typing import Optional from pydantic import Field from akkudoktoreos.config.configabc import SettingsBaseModel -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) class LoadCommonSettings(SettingsBaseModel): + """Common settings for loaod forecast providers.""" + load_provider: Optional[str] = Field( default=None, description="Load provider id of provider to be used." ) diff --git a/src/akkudoktoreos/prediction/loadabc.py b/src/akkudoktoreos/prediction/loadabc.py index 06a6b1d..b7d5295 100644 --- a/src/akkudoktoreos/prediction/loadabc.py +++ b/src/akkudoktoreos/prediction/loadabc.py @@ -9,8 +9,8 @@ from typing import List, Optional from pydantic import Field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/loadakkudoktor.py b/src/akkudoktoreos/prediction/loadakkudoktor.py index a353424..d0db1d9 100644 --- a/src/akkudoktoreos/prediction/loadakkudoktor.py +++ b/src/akkudoktoreos/prediction/loadakkudoktor.py @@ -1,15 +1,14 @@ """Retrieves load forecast data from Akkudoktor load profiles.""" -from pathlib import Path from typing import Optional import numpy as np from pydantic import Field from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.loadabc import LoadProvider from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime, to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) @@ -84,7 +83,7 @@ class LoadAkkudoktor(LoadProvider): def load_data(self) -> np.ndarray: """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 try: file_data = np.load(load_file) @@ -107,23 +106,25 @@ class LoadAkkudoktor(LoadProvider): """Adds the load means and standard deviations.""" data_year_energy = self.load_data() weekday_adjust, weekend_adjust = self._calculate_adjustment(data_year_energy) - date = self.start_datetime - for i in range(self.config.prediction_hours): + # We provide prediction starting at start of day, to be compatible to old system. + # 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 # Day indexing starts at 0, -1 because of that hourly_stats = data_year_energy[date.day_of_year - 1, :, date.hour] - self.update_value(date, "load_mean", hourly_stats[0]) - self.update_value(date, "load_std", hourly_stats[1]) + values = { + "load_mean": hourly_stats[0], + "load_std": hourly_stats[1], + } if date.day_of_week < 5: # Monday to Friday (0..4) - self.update_value( - date, "load_mean_adjusted", hourly_stats[0] + weekday_adjust[date.hour] - ) + values["load_mean_adjusted"] = hourly_stats[0] + weekday_adjust[date.hour] else: # Saturday, Sunday (5, 6) - self.update_value( - date, "load_mean_adjusted", hourly_stats[0] + weekend_adjust[date.hour] - ) + values["load_mean_adjusted"] = hourly_stats[0] + weekend_adjust[date.hour] + self.update_value(date, values) date += to_duration("1 hour") # We are working on fresh data (no cache), report update time self.update_datetime = to_datetime(in_timezone=self.config.timezone) diff --git a/src/akkudoktoreos/prediction/loadimport.py b/src/akkudoktoreos/prediction/loadimport.py index 9a37c9c..39e835b 100644 --- a/src/akkudoktoreos/prediction/loadimport.py +++ b/src/akkudoktoreos/prediction/loadimport.py @@ -12,9 +12,9 @@ from typing import Optional, Union from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.loadabc import LoadProvider from akkudoktoreos.prediction.predictionabc import PredictionImportProvider -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/predictionabc.py b/src/akkudoktoreos/prediction/predictionabc.py index 432df0d..5a6f658 100644 --- a/src/akkudoktoreos/prediction/predictionabc.py +++ b/src/akkudoktoreos/prediction/predictionabc.py @@ -22,8 +22,8 @@ from akkudoktoreos.core.dataabc import ( DataRecord, DataSequence, ) +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.utils.datetimeutil import to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/pvforecast.py b/src/akkudoktoreos/prediction/pvforecast.py index 5b7b0cb..cf261dd 100644 --- a/src/akkudoktoreos/prediction/pvforecast.py +++ b/src/akkudoktoreos/prediction/pvforecast.py @@ -5,7 +5,7 @@ from typing import Any, ClassVar, List, Optional from pydantic import Field, computed_field from akkudoktoreos.config.configabc import SettingsBaseModel -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger 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.", ) 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( default=None, @@ -98,7 +98,9 @@ class PVForecastCommonSettings(SettingsBaseModel): default="free", 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( 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.", @@ -152,7 +154,9 @@ class PVForecastCommonSettings(SettingsBaseModel): default="free", 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( 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.", @@ -206,7 +210,9 @@ class PVForecastCommonSettings(SettingsBaseModel): default="free", 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( 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.", @@ -260,7 +266,9 @@ class PVForecastCommonSettings(SettingsBaseModel): default="free", 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( 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.", @@ -314,7 +322,9 @@ class PVForecastCommonSettings(SettingsBaseModel): default="free", 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( 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.", diff --git a/src/akkudoktoreos/prediction/pvforecastabc.py b/src/akkudoktoreos/prediction/pvforecastabc.py index 98f31db..d9958fe 100644 --- a/src/akkudoktoreos/prediction/pvforecastabc.py +++ b/src/akkudoktoreos/prediction/pvforecastabc.py @@ -9,8 +9,8 @@ from typing import List, Optional from pydantic import Field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/pvforecastakkudoktor.py b/src/akkudoktoreos/prediction/pvforecastakkudoktor.py index 93eb365..ef63c0b 100644 --- a/src/akkudoktoreos/prediction/pvforecastakkudoktor.py +++ b/src/akkudoktoreos/prediction/pvforecastakkudoktor.py @@ -68,6 +68,7 @@ from typing import Any, List, Optional, Union import requests from pydantic import Field, ValidationError, computed_field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.prediction.pvforecastabc import ( PVForecastDataRecord, @@ -75,7 +76,6 @@ from akkudoktoreos.prediction.pvforecastabc import ( ) from akkudoktoreos.utils.cacheutil import cache_in_file from akkudoktoreos.utils.datetimeutil import compare_datetimes, to_datetime -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) @@ -283,27 +283,21 @@ class PVForecastAkkudoktor(PVForecastProvider): original_datetime = akkudoktor_data.values[0][i].datetime dt = to_datetime(original_datetime, in_timezone=self.config.timezone) - # iso_datetime = parser.parse(original_datetime).isoformat() # Konvertiere zu ISO-Format - # print() - # 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: + # We provide prediction starting at start of day, to be compatible to old system. + if compare_datetimes(dt, self.start_datetime.start_of("day")).lt: # forecast data is too old continue 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) - record = PVForecastAkkudoktorDataRecord( - date_time=dt, # Verwende angepassten Zeitstempel - pvforecast_dc_power=sum_dc_power, - pvforecast_ac_power=sum_ac_power, - pvforecastakkudoktor_wind_speed_10m=akkudoktor_data.values[0][i].windspeed_10m, - pvforecastakkudoktor_temp_air=akkudoktor_data.values[0][i].temperature, - ) - self.append(record) + data = { + "pvforecast_dc_power": sum_dc_power, + "pvforecast_ac_power": sum_ac_power, + "pvforecastakkudoktor_wind_speed_10m": akkudoktor_data.values[0][i].windspeed_10m, + "pvforecastakkudoktor_temp_air": akkudoktor_data.values[0][i].temperature, + } + self.update_value(dt, data) if len(self) < self.config.prediction_hours: raise ValueError( diff --git a/src/akkudoktoreos/prediction/pvforecastimport.py b/src/akkudoktoreos/prediction/pvforecastimport.py index a63fa27..083f9d8 100644 --- a/src/akkudoktoreos/prediction/pvforecastimport.py +++ b/src/akkudoktoreos/prediction/pvforecastimport.py @@ -12,9 +12,9 @@ from typing import Optional, Union from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionImportProvider from akkudoktoreos.prediction.pvforecastabc import PVForecastProvider -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/weatherabc.py b/src/akkudoktoreos/prediction/weatherabc.py index b2468a7..49f1fb0 100644 --- a/src/akkudoktoreos/prediction/weatherabc.py +++ b/src/akkudoktoreos/prediction/weatherabc.py @@ -14,8 +14,8 @@ import pandas as pd import pvlib from pydantic import Field +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionProvider, PredictionRecord -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/weatherbrightsky.py b/src/akkudoktoreos/prediction/weatherbrightsky.py index 98176c0..5317023 100644 --- a/src/akkudoktoreos/prediction/weatherbrightsky.py +++ b/src/akkudoktoreos/prediction/weatherbrightsky.py @@ -13,10 +13,10 @@ import pandas as pd import pvlib import requests +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider from akkudoktoreos.utils.cacheutil import cache_in_file from akkudoktoreos.utils.datetimeutil import to_datetime -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/weatherclearoutside.py b/src/akkudoktoreos/prediction/weatherclearoutside.py index f08e909..cd32382 100644 --- a/src/akkudoktoreos/prediction/weatherclearoutside.py +++ b/src/akkudoktoreos/prediction/weatherclearoutside.py @@ -19,10 +19,10 @@ import pandas as pd import requests from bs4 import BeautifulSoup +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.weatherabc import WeatherDataRecord, WeatherProvider from akkudoktoreos.utils.cacheutil import cache_in_file from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration, to_timezone -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/prediction/weatherimport.py b/src/akkudoktoreos/prediction/weatherimport.py index 4cc2071..d781e34 100644 --- a/src/akkudoktoreos/prediction/weatherimport.py +++ b/src/akkudoktoreos/prediction/weatherimport.py @@ -12,9 +12,9 @@ from typing import Optional, Union from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.prediction.predictionabc import PredictionImportProvider from akkudoktoreos.prediction.weatherabc import WeatherProvider -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/server/fastapi_server.py b/src/akkudoktoreos/server/fastapi_server.py index 507bca2..1827ec3 100755 --- a/src/akkudoktoreos/server/fastapi_server.py +++ b/src/akkudoktoreos/server/fastapi_server.py @@ -7,7 +7,6 @@ from pathlib import Path from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union import httpx -import pandas as pd import uvicorn from fastapi import FastAPI, Query, Request 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.core.ems import get_ems +from akkudoktoreos.core.logging import get_logger from akkudoktoreos.core.pydantic import ( PydanticBaseModel, PydanticDateTimeData, @@ -29,7 +29,6 @@ from akkudoktoreos.optimization.genetic import ( ) from akkudoktoreos.prediction.prediction import get_prediction from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration -from akkudoktoreos.utils.logutil import get_logger logger = get_logger(__name__) config_eos = get_config() @@ -182,33 +181,112 @@ class PdfResponse(FileResponse): 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") def fastapi_config_get() -> ConfigEOS: - """Get the current configuration.""" + """Get the current configuration. + + Returns: + configuration (ConfigEOS): The current configuration. + """ return config_eos @app.put("/v1/config") def fastapi_config_put( - settings: SettingsEOS, - save: Optional[bool] = None, + settings: Annotated[SettingsEOS, Query(description="settings")], ) -> 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: - settings (SettingsEOS): The settings to merge into the current configuration. - save (Optional[bool]): Save the resulting configuration to the configuration file. - Defaults to False. + settings (SettingsEOS): The settings to write into the current settings. + + Returns: + configuration (ConfigEOS): The current configuration after the write. """ - config_eos.merge_settings(settings) - if save: - try: - config_eos.to_config_file() - except: - raise HTTPException( - status_code=404, - detail=f"Cannot save configuration to file '{config_eos.config_file_path}'.", - ) + try: + config_eos.merge_settings(settings, force=True) + except Exception as e: + raise HTTPException(status_code=400, detail=f"Error on update of configuration: {e}") 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") if key is None: 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: - 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) 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") if key is None: 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: - 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) pdseries = measurement_eos.key_to_series(key=key) 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") if key is None: 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: - 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 measurement_eos.key_from_series(key=key, series=pdseries) pdseries = measurement_eos.key_to_series(key=key) @@ -277,7 +355,7 @@ def fastapi_measurement_series_get( ) -> PydanticDateTimeSeries: """Get the measurements of given key as series.""" 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) return PydanticDateTimeSeries.from_series(pdseries) @@ -290,7 +368,7 @@ def fastapi_measurement_value_put( ) -> PydanticDateTimeSeries: """Merge the measurement of given key and value into EOS measurements at given datetime.""" 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) pdseries = measurement_eos.key_to_series(key=key) return PydanticDateTimeSeries.from_series(pdseries) @@ -302,7 +380,7 @@ def fastapi_measurement_series_put( ) -> PydanticDateTimeSeries: """Merge measurement given as series into given key.""" 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 measurement_eos.key_from_series(key=key, series=pdseries) pdseries = measurement_eos.key_to_series(key=key) @@ -351,7 +429,7 @@ def fastapi_prediction_series_get( Defaults to end datetime of latest prediction. """ 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: start_datetime = prediction_eos.start_datetime else: @@ -394,7 +472,7 @@ def fastapi_prediction_list_get( Defaults to 1 hour. """ 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: start_datetime = prediction_eos.start_datetime else: @@ -429,7 +507,7 @@ def fastapi_prediction_update(force_update: bool = False, force_enable: bool = F try: prediction_eos.update_data(force_update=force_update, force_enable=force_enable) 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() @@ -453,7 +531,7 @@ def fastapi_prediction_update_provider( try: provider.update_data(force_update=force_update, force_enable=force_enable) 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() @@ -461,6 +539,13 @@ def fastapi_prediction_update_provider( def fastapi_strompreis() -> list[float]: """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' @@ -479,11 +564,21 @@ def fastapi_strompreis() -> list[float]: # Get the current date and the end date based on prediction hours # Fetch prices for the specified date range - return prediction_eos.key_to_array( - key="elecprice_marketprice_wh", - start_datetime=prediction_eos.start_datetime, - end_datetime=prediction_eos.end_datetime, - ).tolist() + start_datetime = to_datetime().start_of("day") + end_datetime = start_datetime.add(days=2) + try: + elecprice = prediction_eos.key_to_array( + 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): @@ -498,6 +593,10 @@ def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]: 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: @@ -534,11 +633,21 @@ def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]: # Create load forecast prediction_eos.update_data(force_update=True) - prediction_list = prediction_eos.key_to_array( - key="load_mean_adjusted", - start_datetime=prediction_eos.start_datetime, - end_datetime=prediction_eos.end_datetime, - ).tolist() + # Get the forcast starting at start of day + start_datetime = to_datetime().start_of("day") + end_datetime = start_datetime.add(days=2) + try: + 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 @@ -548,6 +657,10 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]: 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' @@ -564,11 +677,21 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]: # Create load forecast prediction_eos.update_data(force_update=True) - prediction_list = prediction_eos.key_to_array( - key="load_mean", - start_datetime=prediction_eos.start_datetime, - end_datetime=prediction_eos.end_datetime, - ).tolist() + # Get the forcast starting at start of day + start_datetime = to_datetime().start_of("day") + end_datetime = start_datetime.add(days=2) + try: + 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 @@ -583,6 +706,10 @@ def fastapi_pvforecast() -> ForecastResponse: 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' @@ -590,41 +717,38 @@ def fastapi_pvforecast() -> ForecastResponse: '/v1/prediction/list?key=pvforecast_ac_power' and '/v1/prediction/list?key=pvforecastakkudoktor_temp_air' instead. """ - ############### - # PV Forecast - ############### - prediction_key = "pvforecast_ac_power" - 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}") + settings = SettingsEOS( + elecprice_provider="PVForecastAkkudoktor", + ) + config_eos.merge_settings(settings=settings) - # On empty Series.loc TypeError: Cannot compare tz-naive and tz-aware datetime-like objects - 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 - ] + ems_eos.set_start_datetime() # Set energy management start datetime to current hour. - prediction_key = "pvforecastakkudoktor_temp_air" - pvforecastakkudoktor_temp_air = prediction_eos.get(prediction_key) - if pvforecastakkudoktor_temp_air is None: - raise HTTPException(status_code=404, detail=f"Prediction not available: {prediction_key}") + # Create PV forecast + prediction_eos.update_data(force_update=True) - # On empty Series.loc TypeError: Cannot compare tz-naive and tz-aware datetime-like objects - if len(pvforecastakkudoktor_temp_air) == 0: - pvforecastakkudoktor_temp_air = pd.Series() - else: - # Fetch prices for the specified date range - pvforecastakkudoktor_temp_air = pvforecastakkudoktor_temp_air.loc[ - prediction_eos.start_datetime : prediction_eos.end_datetime - ] + # Get the forcast starting at start of day + start_datetime = to_datetime().start_of("day") + end_datetime = start_datetime.add(days=2) + try: + ac_power = prediction_eos.key_to_array( + key="pvforecast_ac_power", + 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 ForecastResponse( - temperature=pvforecastakkudoktor_temp_air.tolist(), pvpower=pvforecast_ac_power.tolist() - ) + return ForecastResponse(temperature=temp_air, pvpower=ac_power) @app.post("/optimize") diff --git a/src/akkudoktoreos/server/fasthtml_server.py b/src/akkudoktoreos/server/fasthtml_server.py index 6e3607c..5b2656c 100644 --- a/src/akkudoktoreos/server/fasthtml_server.py +++ b/src/akkudoktoreos/server/fasthtml_server.py @@ -2,7 +2,7 @@ import uvicorn from fasthtml.common import H1, FastHTML, Table, Td, Th, Thead, Titled, Tr 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__) diff --git a/src/akkudoktoreos/server/server.py b/src/akkudoktoreos/server/server.py index 0c98bed..a0b0f5a 100644 --- a/src/akkudoktoreos/server/server.py +++ b/src/akkudoktoreos/server/server.py @@ -5,7 +5,7 @@ from typing import Optional from pydantic import Field, IPvAnyAddress, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/utils/cacheutil.py b/src/akkudoktoreos/utils/cacheutil.py index 67a9938..24848af 100644 --- a/src/akkudoktoreos/utils/cacheutil.py +++ b/src/akkudoktoreos/utils/cacheutil.py @@ -48,8 +48,8 @@ from pendulum import DateTime, Duration from pydantic import BaseModel, ConfigDict, Field 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.logutil import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/utils/datetimeutil.py b/src/akkudoktoreos/utils/datetimeutil.py index 5385edc..d22032e 100644 --- a/src/akkudoktoreos/utils/datetimeutil.py +++ b/src/akkudoktoreos/utils/datetimeutil.py @@ -31,7 +31,7 @@ from pendulum import Date, DateTime, Duration from pendulum.tz.timezone import Timezone from timezonefinder import TimezoneFinder -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/src/akkudoktoreos/utils/utils.py b/src/akkudoktoreos/utils/utils.py index 28dabe1..ffd81e8 100644 --- a/src/akkudoktoreos/utils/utils.py +++ b/src/akkudoktoreos/utils/utils.py @@ -4,7 +4,7 @@ from typing import Any import numpy as np from akkudoktoreos.config.configabc import SettingsBaseModel -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/tests/conftest.py b/tests/conftest.py index 1a0b4b8..7f57cad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ import pytest from xprocess import ProcessStarter 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__) @@ -160,7 +160,7 @@ def server(xprocess, config_eos, config_default_dirs): stderr=subprocess.PIPE, ) except subprocess.CalledProcessError: - project_dir = Path(__file__).parent.parent.parent + project_dir = config_eos.package_root_path subprocess.run( [sys.executable, "-m", "pip", "install", "-e", project_dir], check=True, diff --git a/tests/test_config.py b/tests/test_config.py index 552280d..5d1e14e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -5,7 +5,7 @@ from unittest.mock import patch import pytest from akkudoktoreos.config.config import ConfigEOS -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import get_logger logger = get_logger(__name__) diff --git a/tests/test_loadakkudoktor.py b/tests/test_loadakkudoktor.py index fcb6131..d4632fd 100644 --- a/tests/test_loadakkudoktor.py +++ b/tests/test_loadakkudoktor.py @@ -77,13 +77,9 @@ def test_loadakkudoktor_provider_id(load_provider): assert load_provider.provider_id() == "LoadAkkudoktor" -@patch("akkudoktoreos.prediction.loadakkudoktor.Path") @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.""" - # 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_np_load.return_value = { "yearly_profiles": np.ones((365, 24)), diff --git a/tests/test_logutil.py b/tests/test_logging.py similarity index 87% rename from tests/test_logutil.py rename to tests/test_logging.py index 4e789f2..349f7d2 100644 --- a/tests/test_logutil.py +++ b/tests/test_logging.py @@ -1,4 +1,4 @@ -"""Test Module for logutil Module.""" +"""Test Module for logging Module.""" import logging import os @@ -6,7 +6,7 @@ from logging.handlers import RotatingFileHandler import pytest -from akkudoktoreos.utils.logutil import get_logger +from akkudoktoreos.core.logging import 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): """Test logger creation with an invalid logging level.""" - 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 + with pytest.raises(ValueError, match="Unknown loggin level: INVALID"): + logger = get_logger("test_logger", logging_level="INVALID") diff --git a/tests/test_server.py b/tests/test_server.py index d8260ab..81a7bf0 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -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_dir() - result = requests.get(f"{server}/v1/config?") + result = requests.get(f"{server}/v1/config") assert result.status_code == HTTPStatus.OK diff --git a/tests/testdata/openapi.md b/tests/testdata/openapi.md index b581952..3f30f14 100644 --- a/tests/testdata/openapi.md +++ b/tests/testdata/openapi.md @@ -8,6 +8,159 @@ **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 **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. + +Returns: + configuration (ConfigEOS): The current configuration. ``` **Responses**: @@ -31,22 +187,459 @@ Get the current configuration. 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: - settings (SettingsEOS): The settings to merge into the current configuration. - save (Optional[bool]): Save the resulting configuration to the configuration file. - Defaults to False. + settings (SettingsEOS): The settings to write into the current settings. + +Returns: + configuration (ConfigEOS): The current configuration after the write. ``` **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**: - `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**: @@ -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 **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 **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 **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 **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)