mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-08 23:46:38 +00:00
feat(pvforecast): send plane horizon to Forecast.Solar
A plane's userhorizon uses the same convention as the Forecast.Solar horizon query parameter (evenly distributed heights in degrees, starting north, clockwise), so pass it through instead of dropping it. Without it a shaded plane is forecast as if it had a free horizon. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,9 @@ Note on conventions:
|
|||||||
converts via ``az = surface_azimuth - 180``.
|
converts via ``az = surface_azimuth - 180``.
|
||||||
- Response timestamps are local wall-clock; ``message.info.timezone`` is used
|
- Response timestamps are local wall-clock; ``message.info.timezone`` is used
|
||||||
to resolve them to absolute instants before EOS resamples them.
|
to resolve them to absolute instants before EOS resamples them.
|
||||||
|
- A plane's ``userhorizon`` (PVGIS convention: evenly distributed horizon
|
||||||
|
heights in degrees, starting at north and going clockwise) is passed on as
|
||||||
|
the ``horizon`` query parameter, which uses the same convention.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
@@ -94,7 +97,13 @@ class PVForecastForecastSolar(PVForecastProvider):
|
|||||||
api_key = self._api_key
|
api_key = self._api_key
|
||||||
if api_key:
|
if api_key:
|
||||||
base = f"{base}/{api_key}"
|
base = f"{base}/{api_key}"
|
||||||
return f"{base}/estimate/{latitude}/{longitude}/{float(tilt)}/{fs_az}/{float(peakpower)}"
|
url = f"{base}/estimate/{latitude}/{longitude}/{float(tilt)}/{fs_az}/{float(peakpower)}"
|
||||||
|
# Horizon shading: same convention on both sides (evenly distributed
|
||||||
|
# heights in degrees, starting north, clockwise), so pass it through.
|
||||||
|
horizon = getattr(plane, "userhorizon", None)
|
||||||
|
if horizon:
|
||||||
|
url = f"{url}?horizon={','.join(str(float(value)) for value in horizon)}"
|
||||||
|
return url
|
||||||
|
|
||||||
@cache_in_file(with_ttl="1 hour")
|
@cache_in_file(with_ttl="1 hour")
|
||||||
def _request_forecast(self) -> dict:
|
def _request_forecast(self) -> dict:
|
||||||
|
|||||||
@@ -127,3 +127,25 @@ def test_request_forecast_raises_on_http_error(pvforecast_instance):
|
|||||||
with pytest.raises(RuntimeError) as exc_info:
|
with pytest.raises(RuntimeError) as exc_info:
|
||||||
pvforecast_instance._request_forecast(force_update=True)
|
pvforecast_instance._request_forecast(force_update=True)
|
||||||
assert "Failed to fetch pvforecast from Forecast.Solar" in str(exc_info.value)
|
assert "Failed to fetch pvforecast from Forecast.Solar" in str(exc_info.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plane_url_passes_userhorizon(config_eos):
|
||||||
|
"""A plane's userhorizon is forwarded as the Forecast.Solar horizon parameter."""
|
||||||
|
_config(
|
||||||
|
config_eos,
|
||||||
|
planes=[
|
||||||
|
{
|
||||||
|
"surface_tilt": 87.9,
|
||||||
|
"surface_azimuth": 175.0,
|
||||||
|
"peakpower": 13.11,
|
||||||
|
"userhorizon": [28.0, 34.0, 32.0, 60.0],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
pv = PVForecastForecastSolar(
|
||||||
|
config=config_eos.load, start_datetime=pendulum.datetime(2025, 1, 1, tz="UTC")
|
||||||
|
)
|
||||||
|
with patch("requests.get", return_value=_http({})) as mock_get:
|
||||||
|
pv._request_forecast(force_update=True) # type: ignore
|
||||||
|
url = mock_get.call_args[0][0]
|
||||||
|
assert url.endswith("/estimate/52.5/13.4/87.9/-5.0/13.11?horizon=28.0,34.0,32.0,60.0")
|
||||||
|
|||||||
Reference in New Issue
Block a user