mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-09-13 07:21:16 +00:00
PVForecast: planes as nested config (list)
This commit is contained in:
@@ -1,82 +1,75 @@
|
||||
import pytest
|
||||
|
||||
from akkudoktoreos.prediction.pvforecast import PVForecastCommonSettings
|
||||
from akkudoktoreos.prediction.pvforecast import (
|
||||
PVForecastCommonSettings,
|
||||
PVForecastPlaneSetting,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def settings():
|
||||
"""Fixture that creates an empty PVForecastSettings."""
|
||||
settings = PVForecastCommonSettings()
|
||||
|
||||
# Check default values for plane 0
|
||||
assert settings.pvforecast0_surface_tilt is None
|
||||
assert settings.pvforecast0_surface_azimuth is None
|
||||
assert settings.pvforecast0_pvtechchoice == "crystSi"
|
||||
assert settings.pvforecast0_mountingplace == "free"
|
||||
assert settings.pvforecast0_trackingtype is None
|
||||
assert settings.pvforecast0_optimal_surface_tilt is False
|
||||
assert settings.pvforecast0_optimalangles is False
|
||||
# Check default values for plane 1
|
||||
assert settings.pvforecast1_surface_azimuth is None
|
||||
assert settings.pvforecast1_pvtechchoice == "crystSi"
|
||||
assert settings.pvforecast1_mountingplace == "free"
|
||||
assert settings.pvforecast1_trackingtype is None
|
||||
assert settings.pvforecast1_optimal_surface_tilt is False
|
||||
assert settings.pvforecast1_optimalangles is False
|
||||
|
||||
expected_planes: list[str] = []
|
||||
assert settings.pvforecast_planes == expected_planes
|
||||
|
||||
assert settings.planes is None
|
||||
return settings
|
||||
|
||||
|
||||
def test_active_planes_detection(settings):
|
||||
"""Test that active planes are correctly detected based on tilt and azimuth."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
|
||||
expected_planes = ["pvforecast1", "pvforecast2"]
|
||||
assert settings.pvforecast_planes == expected_planes
|
||||
|
||||
|
||||
def test_planes_peakpower_computation(settings):
|
||||
"""Test computation of peak power for active planes."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast1_peakpower = 5.0
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.pvforecast2_peakpower = 3.5
|
||||
settings.pvforecast3_surface_tilt = 30.0
|
||||
settings.pvforecast3_surface_azimuth = 30.0
|
||||
settings.pvforecast3_modules_per_string = 20 # Should use default 5000W
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
peakpower=5.0,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
peakpower=3.5,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=30.0,
|
||||
surface_azimuth=30.0,
|
||||
modules_per_string=20, # Should use default 5000W
|
||||
),
|
||||
]
|
||||
|
||||
expected_peakpower = [5.0, 3.5, 5000.0]
|
||||
assert settings.pvforecast_planes_peakpower == expected_peakpower
|
||||
assert settings.planes_peakpower == expected_peakpower
|
||||
|
||||
|
||||
def test_planes_azimuth_computation(settings):
|
||||
"""Test computation of azimuth values for active planes."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
),
|
||||
]
|
||||
|
||||
expected_azimuths = [10.0, 20.0]
|
||||
assert settings.pvforecast_planes_azimuth == expected_azimuths
|
||||
assert settings.planes_azimuth == expected_azimuths
|
||||
|
||||
|
||||
def test_planes_tilt_computation(settings):
|
||||
"""Test computation of tilt values for active planes."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
),
|
||||
]
|
||||
|
||||
expected_tilts = [10.0, 20.0]
|
||||
assert settings.pvforecast_planes_tilt == expected_tilts
|
||||
assert settings.planes_tilt == expected_tilts
|
||||
|
||||
|
||||
def test_planes_userhorizon_computation(settings):
|
||||
@@ -84,116 +77,84 @@ def test_planes_userhorizon_computation(settings):
|
||||
horizon1 = [10.0, 20.0, 30.0]
|
||||
horizon2 = [5.0, 15.0, 25.0]
|
||||
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast1_userhorizon = horizon1
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.pvforecast2_userhorizon = horizon2
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
userhorizon=horizon1,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
userhorizon=horizon2,
|
||||
),
|
||||
]
|
||||
|
||||
expected_horizons = [horizon1, horizon2]
|
||||
assert settings.pvforecast_planes_userhorizon == expected_horizons
|
||||
assert settings.planes_userhorizon == expected_horizons
|
||||
|
||||
|
||||
def test_planes_inverter_paco_computation(settings):
|
||||
"""Test computation of inverter power rating for active planes."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast1_inverter_paco = 6000
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.pvforecast2_inverter_paco = 4000
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
inverter_paco=6000,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
inverter_paco=4000,
|
||||
),
|
||||
]
|
||||
|
||||
expected_paco = [6000, 4000]
|
||||
assert settings.pvforecast_planes_inverter_paco == expected_paco
|
||||
|
||||
|
||||
def test_non_sequential_plane_numbers(settings):
|
||||
"""Test that non-sequential plane numbers are handled correctly."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast1_peakpower = 5.0
|
||||
settings.pvforecast3_surface_tilt = 30.0
|
||||
settings.pvforecast3_surface_azimuth = 30.0
|
||||
settings.pvforecast3_peakpower = 3.5
|
||||
settings.pvforecast5_surface_tilt = 50.0
|
||||
settings.pvforecast5_surface_azimuth = 50.0
|
||||
settings.pvforecast5_peakpower = 2.0
|
||||
|
||||
expected_planes = ["pvforecast1", "pvforecast3", "pvforecast5"]
|
||||
assert settings.pvforecast_planes == expected_planes
|
||||
assert settings.pvforecast_planes_peakpower == [5.0, 3.5, 2.0]
|
||||
assert settings.planes_inverter_paco == expected_paco
|
||||
|
||||
|
||||
def test_mixed_plane_configuration(settings):
|
||||
"""Test mixed configuration with some planes having peak power and others having modules."""
|
||||
settings.pvforecast1_surface_tilt = 10.0
|
||||
settings.pvforecast1_surface_azimuth = 10.0
|
||||
settings.pvforecast1_peakpower = 5.0
|
||||
settings.pvforecast2_surface_tilt = 20.0
|
||||
settings.pvforecast2_surface_azimuth = 20.0
|
||||
settings.pvforecast2_modules_per_string = 20
|
||||
settings.pvforecast2_strings_per_inverter = 2
|
||||
settings.pvforecast4_surface_tilt = 40.0
|
||||
settings.pvforecast4_surface_azimuth = 40.0
|
||||
settings.pvforecast4_peakpower = 3.0
|
||||
settings.planes = [
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=10.0,
|
||||
surface_azimuth=10.0,
|
||||
peakpower=5.0,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=20.0,
|
||||
surface_azimuth=20.0,
|
||||
modules_per_string=20,
|
||||
strings_per_inverter=2,
|
||||
),
|
||||
PVForecastPlaneSetting(
|
||||
surface_tilt=40.0,
|
||||
surface_azimuth=40.0,
|
||||
peakpower=3.0,
|
||||
),
|
||||
]
|
||||
|
||||
expected_planes = ["pvforecast1", "pvforecast2", "pvforecast4"]
|
||||
assert settings.pvforecast_planes == expected_planes
|
||||
# First plane uses specified peak power, second uses default, third uses specified
|
||||
assert settings.pvforecast_planes_peakpower == [5.0, 5000.0, 3.0]
|
||||
assert settings.planes_peakpower == [5.0, 5000.0, 3.0]
|
||||
|
||||
|
||||
def test_max_planes_limit(settings):
|
||||
"""Test that the maximum number of planes is enforced."""
|
||||
assert settings.pvforecast_max_planes == 6
|
||||
assert settings.max_planes == 6
|
||||
|
||||
# Create settings with more planes than allowed (should only recognize up to max)
|
||||
plane_settings = {}
|
||||
for i in range(1, 8): # Try to set up 7 planes, skipping plane 0
|
||||
plane_settings[f"pvforecast{i}_peakpower"] = 5.0
|
||||
plane_settings = [{"peakpower": 5.0} for _ in range(8)]
|
||||
|
||||
settings = PVForecastCommonSettings(**plane_settings)
|
||||
assert len(settings.pvforecast_planes) <= settings.pvforecast_max_planes
|
||||
with pytest.raises(ValueError):
|
||||
PVForecastCommonSettings(planes=plane_settings)
|
||||
|
||||
|
||||
def test_optional_parameters_non_zero_plane(settings):
|
||||
def test_invalid_plane_settings():
|
||||
"""Test that optional parameters can be None for non-zero planes."""
|
||||
settings.pvforecast1_peakpower = 5.0
|
||||
settings.pvforecast1_albedo = None
|
||||
settings.pvforecast1_module_model = None
|
||||
settings.pvforecast1_userhorizon = None
|
||||
|
||||
assert settings.pvforecast1_albedo is None
|
||||
assert settings.pvforecast1_module_model is None
|
||||
assert settings.pvforecast1_userhorizon is None
|
||||
|
||||
|
||||
def test_tracking_type_values_non_zero_plane(settings):
|
||||
"""Test valid tracking type values for non-zero planes."""
|
||||
valid_types = [0, 1, 2, 3, 4, 5]
|
||||
|
||||
for tracking_type in valid_types:
|
||||
settings.pvforecast1_peakpower = 5.0
|
||||
settings.pvforecast1_trackingtype = tracking_type
|
||||
assert settings.pvforecast1_trackingtype == tracking_type
|
||||
|
||||
|
||||
def test_pv_technology_values_non_zero_plane(settings):
|
||||
"""Test valid PV technology values for non-zero planes."""
|
||||
valid_technologies = ["crystSi", "CIS", "CdTe", "Unknown"]
|
||||
|
||||
for tech in valid_technologies:
|
||||
settings.pvforecast2_peakpower = 5.0
|
||||
settings.pvforecast2_pvtechchoice = tech
|
||||
assert settings.pvforecast2_pvtechchoice == tech
|
||||
|
||||
|
||||
def test_mounting_place_values_non_zero_plane(settings):
|
||||
"""Test valid mounting place values for non-zero planes."""
|
||||
valid_mounting = ["free", "building"]
|
||||
|
||||
for mounting in valid_mounting:
|
||||
settings.pvforecast3_peakpower = 5.0
|
||||
settings.pvforecast3_mountingplace = mounting
|
||||
assert settings.pvforecast3_mountingplace == mounting
|
||||
with pytest.raises(ValueError):
|
||||
PVForecastPlaneSetting(
|
||||
peakpower=5.0,
|
||||
albedo=None,
|
||||
module_model=None,
|
||||
userhorizon=None,
|
||||
)
|
||||
|
@@ -33,27 +33,36 @@ def sample_settings(config_eos):
|
||||
},
|
||||
"pvforecast": {
|
||||
"provider": "PVForecastAkkudoktor",
|
||||
"pvforecast0_peakpower": 5.0,
|
||||
"pvforecast0_surface_azimuth": -10,
|
||||
"pvforecast0_surface_tilt": 7,
|
||||
"pvforecast0_userhorizon": [20, 27, 22, 20],
|
||||
"pvforecast0_inverter_paco": 10000,
|
||||
"pvforecast1_peakpower": 4.8,
|
||||
"pvforecast1_surface_azimuth": -90,
|
||||
"pvforecast1_surface_tilt": 7,
|
||||
"pvforecast1_userhorizon": [30, 30, 30, 50],
|
||||
"pvforecast1_inverter_paco": 10000,
|
||||
"pvforecast2_peakpower": 1.4,
|
||||
"pvforecast2_surface_azimuth": -40,
|
||||
"pvforecast2_surface_tilt": 60,
|
||||
"pvforecast2_userhorizon": [60, 30, 0, 30],
|
||||
"pvforecast2_inverter_paco": 2000,
|
||||
"pvforecast3_peakpower": 1.6,
|
||||
"pvforecast3_surface_azimuth": 5,
|
||||
"pvforecast3_surface_tilt": 45,
|
||||
"pvforecast3_userhorizon": [45, 25, 30, 60],
|
||||
"pvforecast3_inverter_paco": 1400,
|
||||
"pvforecast4_peakpower": None,
|
||||
"planes": [
|
||||
{
|
||||
"peakpower": 5.0,
|
||||
"surface_azimuth": -10,
|
||||
"surface_tilt": 7,
|
||||
"userhorizon": [20, 27, 22, 20],
|
||||
"inverter_paco": 10000,
|
||||
},
|
||||
{
|
||||
"peakpower": 4.8,
|
||||
"surface_azimuth": -90,
|
||||
"surface_tilt": 7,
|
||||
"userhorizon": [30, 30, 30, 50],
|
||||
"inverter_paco": 10000,
|
||||
},
|
||||
{
|
||||
"peakpower": 1.4,
|
||||
"surface_azimuth": -40,
|
||||
"surface_tilt": 60,
|
||||
"userhorizon": [60, 30, 0, 30],
|
||||
"inverter_paco": 2000,
|
||||
},
|
||||
{
|
||||
"peakpower": 1.6,
|
||||
"surface_azimuth": 5,
|
||||
"surface_tilt": 45,
|
||||
"userhorizon": [45, 25, 30, 60],
|
||||
"inverter_paco": 1400,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -19,8 +19,8 @@ def provider(sample_import_1_json, config_eos):
|
||||
"pvforecast": {
|
||||
"provider": "PVForecastImport",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"pvforecastimport_json": json.dumps(sample_import_1_json),
|
||||
"import_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"import_json": json.dumps(sample_import_1_json),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ def test_invalid_provider(provider, config_eos):
|
||||
"pvforecast": {
|
||||
"provider": "<invalid>",
|
||||
"provider_settings": {
|
||||
"pvforecastimport_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
"import_file_path": str(FILE_TESTDATA_PVFORECASTIMPORT_1_JSON),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -86,11 +86,11 @@ def test_import(provider, sample_import_1_json, start_datetime, from_file, confi
|
||||
ems_eos = get_ems()
|
||||
ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin"))
|
||||
if from_file:
|
||||
config_eos.pvforecast.provider_settings.pvforecastimport_json = None
|
||||
assert config_eos.pvforecast.provider_settings.pvforecastimport_json is None
|
||||
config_eos.pvforecast.provider_settings.import_json = None
|
||||
assert config_eos.pvforecast.provider_settings.import_json is None
|
||||
else:
|
||||
config_eos.pvforecast.provider_settings.pvforecastimport_file_path = None
|
||||
assert config_eos.pvforecast.provider_settings.pvforecastimport_file_path is None
|
||||
config_eos.pvforecast.provider_settings.import_file_path = None
|
||||
assert config_eos.pvforecast.provider_settings.import_file_path is None
|
||||
provider.clear()
|
||||
|
||||
# Call the method
|
||||
|
Reference in New Issue
Block a user