diff --git a/applications/modelling_e2e/handler.py b/applications/modelling_e2e/handler.py index cf1d1564..da47719d 100644 --- a/applications/modelling_e2e/handler.py +++ b/applications/modelling_e2e/handler.py @@ -284,6 +284,21 @@ def _spatial_for( return None +# The 32-wide fallback gap between this container's Solar calls: 0.8 (safety +# headroom) × 600 QPM ÷ 60 ÷ 32 containers ≈ one call every 4s. Used when the +# env var is unset so the Lambda self-protects even if terraform wiring is missed. +_DEFAULT_SOLAR_MIN_REQUEST_INTERVAL_SECONDS: float = 4.0 + + +def _solar_min_request_interval_seconds() -> float: + """Per-container minimum gap (seconds) between Google Solar API calls, read + from ``SOLAR_MIN_REQUEST_INTERVAL_SECONDS``. Terraform derives the value from + the queue's ``maximum_concurrency`` (0.8 × 10 QPS ÷ N) so the up-to-32-wide + fleet stays under the hard 600 QPM Solar ceiling. Falls back to the 32-wide + default when unset or unparseable.""" + raise NotImplementedError + + def _solar_insights_for( solar_client: GoogleSolarApiClient, spatial: Optional[SpatialReference] ) -> Optional[dict[str, Any]]: diff --git a/tests/applications/modelling_e2e/test_handler.py b/tests/applications/modelling_e2e/test_handler.py index 860e5313..7f11c7d7 100644 --- a/tests/applications/modelling_e2e/test_handler.py +++ b/tests/applications/modelling_e2e/test_handler.py @@ -123,6 +123,43 @@ def _baseline_orchestrator() -> Iterator[MagicMock]: yield orchestrator +# --------------------------------------------------------------------------- +# Solar throttle: per-container call gap resolved from env (quota/N safety) +# --------------------------------------------------------------------------- + + +def test_solar_min_request_interval_falls_back_to_32_wide_default() -> None: + # Arrange — env var absent: the Lambda must still self-protect at the 32-wide + # gap so a missed terraform wiring can't reopen the 600 QPM over-quota storm. + from applications.modelling_e2e.handler import ( + _DEFAULT_SOLAR_MIN_REQUEST_INTERVAL_SECONDS, + _solar_min_request_interval_seconds, + ) + + with patch.dict("os.environ", {}, clear=True): + # Act + result = _solar_min_request_interval_seconds() + + # Assert + assert result == _DEFAULT_SOLAR_MIN_REQUEST_INTERVAL_SECONDS + + +def test_solar_min_request_interval_reads_env_when_set() -> None: + # Arrange — terraform injects the computed gap (e.g. a smaller N → smaller gap). + from applications.modelling_e2e.handler import ( + _solar_min_request_interval_seconds, + ) + + with patch.dict( + "os.environ", {"SOLAR_MIN_REQUEST_INTERVAL_SECONDS": "2.0"}, clear=True + ): + # Act + result = _solar_min_request_interval_seconds() + + # Assert + assert result == 2.0 + + # --------------------------------------------------------------------------- # Trigger body validation # ---------------------------------------------------------------------------