From 27bf4e88f426ad119bbe7c9c2dfc864e7c54e3ad Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 17 Oct 2026 14:09:21 +0200 Subject: [PATCH] fix: retry Open-Meteo on 429/5xx and show why loading failed Co-Authored-By: Claude Opus 5.5 --- .../main/java/app/wetter/data/OpenMeteo.kt | 21 ++++++++++++++++--- .../java/app/wetter/ui/screens/HomeScreen.kt | 18 +++++++++++----- .../app/wetter/ui/screens/PreviewScreen.kt | 8 +++---- wetter/app/src/main/res/values/strings.xml | 2 ++ .../test/java/app/wetter/ScreenshotTest.kt | 2 +- .../src/test/resources/newyork_forecast.json | 1 + .../src/test/resources/paris_forecast.json | 1 + .../test/resources/reykjavik_forecast.json | 1 + 8 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 wetter/app/src/test/resources/newyork_forecast.json create mode 100644 wetter/app/src/test/resources/paris_forecast.json create mode 100644 wetter/app/src/test/resources/reykjavik_forecast.json diff --git a/wetter/app/src/main/java/app/wetter/data/OpenMeteo.kt b/wetter/app/src/main/java/app/wetter/data/OpenMeteo.kt index 4366c6b..91140ba 100644 --- a/wetter/app/src/main/java/app/wetter/data/OpenMeteo.kt +++ b/wetter/app/src/main/java/app/wetter/data/OpenMeteo.kt @@ -17,6 +17,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import java.io.IOException +import kotlinx.coroutines.delay import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId @@ -154,12 +155,24 @@ class OpenMeteo(private val http: OkHttpClient = defaultClient()) { return Air(c.int("european_aqi"), c.num("pm2_5"), c.num("pm10"), pollen) } + /** Open-Meteo answers 429/5xx ("service is overloaded") now and then; retry those twice with backoff. */ private suspend fun get(url: String): String = withContext(Dispatchers.IO) { val request = Request.Builder().url(url).header("User-Agent", USER_AGENT).build() - http.newCall(request).execute().use { response -> - if (!response.isSuccessful) throw IOException("HTTP ${response.code}") - response.body?.string() ?: throw IOException("empty body") + var attempt = 0 + while (true) { + val result = runCatching { + http.newCall(request).execute().use { response -> + if (!response.isSuccessful) throw HttpException(response.code) + response.body?.string() ?: throw IOException("empty body") + } + } + val error = result.exceptionOrNull() ?: return@withContext result.getOrThrow() + val retryable = error is IOException && (error !is HttpException || error.code == 429 || error.code >= 500) + if (!retryable || attempt >= 2) throw error + delay(600L * (attempt + 1) * (attempt + 1)) + attempt++ } + @Suppress("UNREACHABLE_CODE") error("unreachable") } companion object { @@ -189,3 +202,5 @@ private fun JsonObject.int(key: String) = num(key)?.toInt() private fun JsonObject.arr(key: String): JsonArray = this[key]?.jsonArray ?: JsonArray(emptyList()) private fun JsonObject.numAt(key: String, i: Int) = arr(key).getOrNull(i)?.takeUnless { it is JsonNull }?.jsonPrimitive?.doubleOrNull private fun JsonObject.intAt(key: String, i: Int) = numAt(key, i)?.toInt() ?: arr(key).getOrNull(i)?.takeUnless { it is JsonNull }?.jsonPrimitive?.intOrNull + +class HttpException(val code: Int) : IOException("HTTP $code") diff --git a/wetter/app/src/main/java/app/wetter/ui/screens/HomeScreen.kt b/wetter/app/src/main/java/app/wetter/ui/screens/HomeScreen.kt index e744df2..b189eb7 100644 --- a/wetter/app/src/main/java/app/wetter/ui/screens/HomeScreen.kt +++ b/wetter/app/src/main/java/app/wetter/ui/screens/HomeScreen.kt @@ -152,12 +152,12 @@ private fun LocationPage( val repo = Graph.repo val forecast by remember(location.id) { repo.forecast(location.id) }.collectAsState(initial = null) var refreshing by remember { mutableStateOf(false) } - var failed by remember { mutableStateOf(false) } + var error by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() val refresh: (Boolean) -> Unit = { force -> scope.launch { refreshing = force - failed = repo.refresh(location, force) is RefreshResult.Failed + error = (repo.refresh(location, force) as? RefreshResult.Failed)?.error refreshing = false } } @@ -172,7 +172,7 @@ private fun LocationPage( settings = settings, animate = animate, refreshing = refreshing, - failed = failed, + error = error, onRefresh = { refresh(true) }, navigation = { IconButton(onClick = onOpenLocations) { Icon(Icons.Rounded.FormatListBulleted, stringResource(R.string.open_locations)) } @@ -195,7 +195,7 @@ fun ForecastPage( settings: Settings, animate: Boolean, refreshing: Boolean, - failed: Boolean, + error: Throwable?, onRefresh: () -> Unit, navigation: @Composable () -> Unit, actions: @Composable () -> Unit, @@ -208,6 +208,7 @@ fun ForecastPage( ?: SceneSpec(app.wetter.data.Condition.CLOUDY, true) val listState = rememberLazyListState() val fg = spec.onScene + val failed = error != null Box(Modifier.fillMaxSize()) { WeatherScene(spec, animate = animate && settings.animations, scroll = { @@ -237,7 +238,7 @@ fun ForecastPage( item(key = "loading") { Column(Modifier.padding(top = 120.dp), horizontalAlignment = Alignment.CenterHorizontally) { if (failed) { - Text(stringResource(R.string.loading_error), color = fg, style = WType.body) + Text(stringResource(errorText(error)), color = fg, style = WType.body, textAlign = TextAlign.Center) TextButton(onClick = onRefresh) { Text(stringResource(R.string.retry), color = fg) } } else CircularProgressIndicator(color = fg) } @@ -310,3 +311,10 @@ private fun PermissionPrompt(onAllow: () -> Unit, onSearch: () -> Unit, asked: B } } } + +@androidx.annotation.StringRes +private fun errorText(error: Throwable?): Int = when { + error is app.wetter.data.HttpException && (error.code == 429 || error.code >= 500) -> R.string.loading_error_busy + error is java.net.UnknownHostException || error is java.net.ConnectException || error is java.net.SocketTimeoutException -> R.string.loading_error_offline + else -> R.string.loading_error +} diff --git a/wetter/app/src/main/java/app/wetter/ui/screens/PreviewScreen.kt b/wetter/app/src/main/java/app/wetter/ui/screens/PreviewScreen.kt index de63a6c..5dc0fcb 100644 --- a/wetter/app/src/main/java/app/wetter/ui/screens/PreviewScreen.kt +++ b/wetter/app/src/main/java/app/wetter/ui/screens/PreviewScreen.kt @@ -25,12 +25,12 @@ import kotlinx.coroutines.launch @Composable fun PreviewScreen(place: Place, settings: Settings, onBack: () -> Unit, onSaved: (Long) -> Unit) { var forecast by remember { mutableStateOf(null) } - var failed by remember { mutableStateOf(false) } + var error by remember { mutableStateOf(null) } var attempt by remember { mutableStateOf(0) } val scope = rememberCoroutineScope() LaunchedEffect(place, attempt) { - failed = false - runCatching { Graph.repo.preview(place) }.onSuccess { forecast = it }.onFailure { failed = true } + error = null + runCatching { Graph.repo.preview(place) }.onSuccess { forecast = it }.onFailure { error = it } } ForecastPage( title = place.name, @@ -39,7 +39,7 @@ fun PreviewScreen(place: Place, settings: Settings, onBack: () -> Unit, onSaved: settings = settings, animate = true, refreshing = false, - failed = failed, + error = error, onRefresh = { attempt++ }, navigation = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.back)) } }, actions = { diff --git a/wetter/app/src/main/res/values/strings.xml b/wetter/app/src/main/res/values/strings.xml index 0e23a74..d955e98 100644 --- a/wetter/app/src/main/res/values/strings.xml +++ b/wetter/app/src/main/res/values/strings.xml @@ -93,6 +93,8 @@ Standort erlauben Lieber einen Ort suchen Wetterdaten konnten nicht geladen werden + Der Wetterdienst ist gerade überlastet. Bitte gleich noch einmal versuchen. + Keine Verbindung zum Internet Einheiten Temperatur diff --git a/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt b/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt index 7cd9621..ae99030 100644 --- a/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt +++ b/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt @@ -62,7 +62,7 @@ class ScreenshotTest { @Composable private fun Home(f: Forecast, title: String = "Berlin", preview: Boolean = false) = ForecastPage( title = title, isCurrent = !preview, forecast = f, settings = Settings(), animate = false, - refreshing = false, failed = false, onRefresh = {}, + refreshing = false, error = null, onRefresh = {}, navigation = { IconButton(onClick = {}) { Icon(Icons.Rounded.FormatListBulleted, null) } }, actions = { if (preview) FilledTonalButton(onClick = {}) { Text("Speichern") } diff --git a/wetter/app/src/test/resources/newyork_forecast.json b/wetter/app/src/test/resources/newyork_forecast.json new file mode 100644 index 0000000..0508b68 --- /dev/null +++ b/wetter/app/src/test/resources/newyork_forecast.json @@ -0,0 +1 @@ +{"reason":"The service is overloaded","error":true} \ No newline at end of file diff --git a/wetter/app/src/test/resources/paris_forecast.json b/wetter/app/src/test/resources/paris_forecast.json new file mode 100644 index 0000000..3df162a --- /dev/null +++ b/wetter/app/src/test/resources/paris_forecast.json @@ -0,0 +1 @@ +{"latitude":48.853382,"longitude":2.355208,"generationtime_ms":5108.713507652283,"utc_offset_seconds":7200,"timezone":"Europe/Paris","timezone_abbreviation":"GMT+2","elevation":46.0,"current_units":{"time":"iso8601","interval":"seconds","temperature_2m":"°C","apparent_temperature":"°C","weather_code":"wmo code","is_day":"","relative_humidity_2m":"%","dew_point_2m":"°C","wind_speed_10m":"km/h","wind_direction_10m":"°","wind_gusts_10m":"km/h","pressure_msl":"hPa","visibility":"m","uv_index":""},"current":{"time":"2026-10-17T14:00","interval":900,"temperature_2m":15.1,"apparent_temperature":11.2,"weather_code":2,"is_day":1,"relative_humidity_2m":51,"dew_point_2m":5.0,"wind_speed_10m":17.8,"wind_direction_10m":21,"wind_gusts_10m":38.5,"pressure_msl":1028.0,"visibility":40940.00,"uv_index":3.30},"minutely_15_units":{"time":"iso8601","precipitation":"mm"},"minutely_15":{"time":["2026-10-17T14:00","2026-10-17T14:15","2026-10-17T14:30","2026-10-17T14:45","2026-10-17T15:00","2026-10-17T15:15","2026-10-17T15:30","2026-10-17T15:45","2026-10-17T16:00","2026-10-17T16:15","2026-10-17T16:30","2026-10-17T16:45","2026-10-17T17:00","2026-10-17T17:15","2026-10-17T17:30","2026-10-17T17:45"],"precipitation":[0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00]},"hourly_units":{"time":"iso8601","temperature_2m":"°C","weather_code":"wmo code","is_day":"","precipitation_probability":"%","precipitation":"mm","wind_speed_10m":"km/h","relative_humidity_2m":"%"},"hourly":{"time":["2026-10-17T00:00","2026-10-17T01:00","2026-10-17T02:00","2026-10-17T03:00","2026-10-17T04:00","2026-10-17T05:00","2026-10-17T06:00","2026-10-17T07:00","2026-10-17T08:00","2026-10-17T09:00","2026-10-17T10:00","2026-10-17T11:00","2026-10-17T12:00","2026-10-17T13:00","2026-10-17T14:00","2026-10-17T15:00","2026-10-17T16:00","2026-10-17T17:00","2026-10-17T18:00","2026-10-17T19:00","2026-10-17T20:00","2026-10-17T21:00","2026-10-17T22:00","2026-10-17T23:00","2026-10-18T00:00","2026-10-18T01:00","2026-10-18T02:00","2026-10-18T03:00","2026-10-18T04:00","2026-10-18T05:00","2026-10-18T06:00","2026-10-18T07:00","2026-10-18T08:00","2026-10-18T09:00","2026-10-18T10:00","2026-10-18T11:00","2026-10-18T12:00","2026-10-18T13:00","2026-10-18T14:00","2026-10-18T15:00","2026-10-18T16:00","2026-10-18T17:00","2026-10-18T18:00","2026-10-18T19:00","2026-10-18T20:00","2026-10-18T21:00","2026-10-18T22:00","2026-10-18T23:00","2026-10-19T00:00","2026-10-19T01:00","2026-10-19T02:00","2026-10-19T03:00","2026-10-19T04:00","2026-10-19T05:00","2026-10-19T06:00","2026-10-19T07:00","2026-10-19T08:00","2026-10-19T09:00","2026-10-19T10:00","2026-10-19T11:00","2026-10-19T12:00","2026-10-19T13:00","2026-10-19T14:00","2026-10-19T15:00","2026-10-19T16:00","2026-10-19T17:00","2026-10-19T18:00","2026-10-19T19:00","2026-10-19T20:00","2026-10-19T21:00","2026-10-19T22:00","2026-10-19T23:00","2026-10-20T00:00","2026-10-20T01:00","2026-10-20T02:00","2026-10-20T03:00","2026-10-20T04:00","2026-10-20T05:00","2026-10-20T06:00","2026-10-20T07:00","2026-10-20T08:00","2026-10-20T09:00","2026-10-20T10:00","2026-10-20T11:00","2026-10-20T12:00","2026-10-20T13:00","2026-10-20T14:00","2026-10-20T15:00","2026-10-20T16:00","2026-10-20T17:00","2026-10-20T18:00","2026-10-20T19:00","2026-10-20T20:00","2026-10-20T21:00","2026-10-20T22:00","2026-10-20T23:00","2026-10-21T00:00","2026-10-21T01:00","2026-10-21T02:00","2026-10-21T03:00","2026-10-21T04:00","2026-10-21T05:00","2026-10-21T06:00","2026-10-21T07:00","2026-10-21T08:00","2026-10-21T09:00","2026-10-21T10:00","2026-10-21T11:00","2026-10-21T12:00","2026-10-21T13:00","2026-10-21T14:00","2026-10-21T15:00","2026-10-21T16:00","2026-10-21T17:00","2026-10-21T18:00","2026-10-21T19:00","2026-10-21T20:00","2026-10-21T21:00","2026-10-21T22:00","2026-10-21T23:00","2026-10-22T00:00","2026-10-22T01:00","2026-10-22T02:00","2026-10-22T03:00","2026-10-22T04:00","2026-10-22T05:00","2026-10-22T06:00","2026-10-22T07:00","2026-10-22T08:00","2026-10-22T09:00","2026-10-22T10:00","2026-10-22T11:00","2026-10-22T12:00","2026-10-22T13:00","2026-10-22T14:00","2026-10-22T15:00","2026-10-22T16:00","2026-10-22T17:00","2026-10-22T18:00","2026-10-22T19:00","2026-10-22T20:00","2026-10-22T21:00","2026-10-22T22:00","2026-10-22T23:00","2026-10-23T00:00","2026-10-23T01:00","2026-10-23T02:00","2026-10-23T03:00","2026-10-23T04:00","2026-10-23T05:00","2026-10-23T06:00","2026-10-23T07:00","2026-10-23T08:00","2026-10-23T09:00","2026-10-23T10:00","2026-10-23T11:00","2026-10-23T12:00","2026-10-23T13:00","2026-10-23T14:00","2026-10-23T15:00","2026-10-23T16:00","2026-10-23T17:00","2026-10-23T18:00","2026-10-23T19:00","2026-10-23T20:00","2026-10-23T21:00","2026-10-23T22:00","2026-10-23T23:00","2026-10-24T00:00","2026-10-24T01:00","2026-10-24T02:00","2026-10-24T03:00","2026-10-24T04:00","2026-10-24T05:00","2026-10-24T06:00","2026-10-24T07:00","2026-10-24T08:00","2026-10-24T09:00","2026-10-24T10:00","2026-10-24T11:00","2026-10-24T12:00","2026-10-24T13:00","2026-10-24T14:00","2026-10-24T15:00","2026-10-24T16:00","2026-10-24T17:00","2026-10-24T18:00","2026-10-24T19:00","2026-10-24T20:00","2026-10-24T21:00","2026-10-24T22:00","2026-10-24T23:00","2026-10-25T00:00","2026-10-25T01:00","2026-10-25T02:00","2026-10-25T03:00","2026-10-25T04:00","2026-10-25T05:00","2026-10-25T06:00","2026-10-25T07:00","2026-10-25T08:00","2026-10-25T09:00","2026-10-25T10:00","2026-10-25T11:00","2026-10-25T12:00","2026-10-25T13:00","2026-10-25T14:00","2026-10-25T15:00","2026-10-25T16:00","2026-10-25T17:00","2026-10-25T18:00","2026-10-25T19:00","2026-10-25T20:00","2026-10-25T21:00","2026-10-25T22:00","2026-10-25T23:00","2026-10-26T00:00","2026-10-26T01:00","2026-10-26T02:00","2026-10-26T03:00","2026-10-26T04:00","2026-10-26T05:00","2026-10-26T06:00","2026-10-26T07:00","2026-10-26T08:00","2026-10-26T09:00","2026-10-26T10:00","2026-10-26T11:00","2026-10-26T12:00","2026-10-26T13:00","2026-10-26T14:00","2026-10-26T15:00","2026-10-26T16:00","2026-10-26T17:00","2026-10-26T18:00","2026-10-26T19:00","2026-10-26T20:00","2026-10-26T21:00","2026-10-26T22:00","2026-10-26T23:00"],"temperature_2m":[12.9,12.5,12.3,11.4,10.7,10.3,9.9,9.6,9.4,9.4,10.1,12.2,13.5,14.5,15.1,15.4,15.5,15.5,14.8,13.7,12.6,11.6,10.8,10.1,9.4,9.0,8.6,8.4,8.1,8.2,7.9,7.5,7.2,7.0,8.1,9.5,10.9,12.3,13.6,13.9,13.8,13.8,13.6,12.8,12.0,11.2,10.5,9.7,9.3,8.5,8.0,7.5,6.9,6.7,6.6,6.3,6.0,6.0,6.7,8.0,9.7,11.5,12.6,13.6,14.4,14.5,14.4,14.3,13.9,13.7,13.6,13.4,13.3,12.9,12.5,12.1,11.7,11.3,11.0,10.9,10.8,11.0,11.3,12.1,13.1,13.5,13.7,13.8,13.8,13.7,13.7,13.6,13.5,13.2,12.9,12.6,12.4,12.2,12.1,12.0,12.0,11.8,11.4,10.9,10.6,10.7,11.1,11.8,13.3,15.1,16.1,15.5,14.2,13.1,12.6,12.3,11.9,11.2,10.3,9.6,9.1,8.8,8.4,8.1,7.8,7.4,6.8,6.2,5.8,7.6,8.6,9.8,11.5,13.4,14.9,15.6,15.9,15.9,15.5,14.7,13.9,13.2,12.5,11.9,11.4,11.0,10.7,10.8,11.0,11.2,11.4,11.5,11.7,11.9,12.1,12.5,13.2,14.1,15.0,15.9,16.7,17.0,16.2,14.8,13.4,12.4,11.5,10.6,9.9,9.3,8.7,8.1,7.6,7.1,6.6,6.2,6.2,6.9,8.1,9.3,10.9,12.3,13.2,13.5,13.2,12.8,12.3,11.8,11.5,11.4,11.4,11.5,11.6,11.7,11.7,11.7,11.6,11.5,11.5,11.5,11.7,12.1,12.7,13.4,14.1,14.6,14.8,14.6,14.2,13.6,12.9,12.3,11.8,11.4,11.0,10.7,10.4,10.1,9.9,9.7,9.6,9.4,9.4,9.5,9.7,10.3,11.2,12.2,13.2,14.0,14.4,14.5,14.3,14.0,13.5,13.1,12.7,12.5,12.2,11.9],"weather_code":[61,61,0,0,1,1,1,0,2,1,1,1,2,2,2,3,2,2,1,0,0,1,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,2,2,2,2,2,2,1,0,1,0,0,0,0,1,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,61,61,61,61,61,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,1,1,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,51,51,51,51,51,51,53,53,53,53,53,53,80,80,80,80,80,80,3,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,2,2,3,3,3,3,3,3,3,3,51,51,51,51,51,51,1,1,2],"is_day":[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0],"precipitation_probability":[5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,5,13,8,5,3,3,3,5,5,3,8,5,4,3,3,3,3,5,9,14,18,19,20,20,21,22,23,23,23,23,24,24,25,24,25,25,26,27,28,29,30,30,29,27,25,22,21,20,21,22,24,26,28,30,32,34,36,37,38,38,37,34,31,28,25,23,22,21,21,20,20,20,20,20,21,21,21,20,18,16,13,11,12,14,17,16,16,16,16,15,14,13,11,10,8,7,5,4,3,3,2,3,4,6,8,11,14,17,21,24,27,30,33,35,37,39,41,42,44,45,46,47,48,48,48,48,47,45,43,41,38,35,32,29,26,23,22,22,22,23,23,24,24,25,26,27,28,29,31,33,35,38,40,42,44,45,45,45,44,43,42,41,41,40,40,40,40,39,38,37,35,33,31,29,26,22,19,16,12,10,10,10,11,12,13,14,14,15,15,15,16,16,16,16,16],"precipitation":[0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.20,0.10,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.20,0.20,0.20,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.30,0.30,0.30,0.30,0.30,0.30,0.90,0.90,0.90,0.90,0.90,0.90,1.90,1.90,1.90,1.90,1.90,1.90,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.10,0.10,0.10,0.10,0.10,0.00,0.00,0.00],"wind_speed_10m":[11.0,11.3,10.5,10.1,9.9,8.9,9.2,8.8,8.2,9.2,9.8,11.7,15.9,17.1,17.8,17.9,16.9,17.1,17.0,14.2,12.7,11.6,12.0,10.1,10.8,10.1,9.0,7.0,7.5,7.0,5.8,5.3,4.3,4.3,5.2,6.8,8.3,9.3,9.0,8.9,8.4,5.6,8.3,7.1,3.7,3.7,4.1,3.6,2.1,1.0,1.1,2.4,2.2,1.5,1.4,2.0,2.4,3.1,3.2,3.6,5.8,8.4,9.2,7.7,10.3,9.4,9.3,10.1,7.8,7.6,7.2,8.5,7.8,7.9,7.8,7.3,6.2,5.2,5.8,6.2,5.5,5.8,5.8,6.5,7.1,7.7,7.7,6.9,6.8,8.0,8.4,8.4,8.4,8.5,8.9,9.0,8.9,9.0,8.7,8.7,8.9,9.4,8.4,6.8,6.1,6.6,7.5,8.7,10.6,12.6,13.9,14.3,14.6,14.8,15.2,15.7,15.5,13.6,10.5,8.3,7.6,7.8,8.0,7.8,7.4,6.6,5.6,5.1,5.1,7.1,8.2,9.5,11.3,13.5,15.0,15.5,15.9,15.5,14.0,12.3,11.5,11.5,11.9,12.2,12.2,11.9,12.2,13.0,14.2,15.3,16.3,17.0,17.7,18.4,18.8,19.2,20.4,21.6,21.9,19.6,18.2,18.0,15.9,12.3,9.4,8.3,8.0,7.5,6.8,6.0,5.4,4.9,4.3,4.0,4.0,4.1,5.0,6.8,8.9,11.0,13.8,15.5,16.6,17.4,18.0,18.9,19.7,20.8,22.0,23.6,25.5,27.8,29.4,30.5,30.3,28.6,25.8,22.7,20.2,18.8,18.6,19.0,19.5,20.1,20.6,20.9,20.7,20.0,18.8,17.5,16.2,15.2,14.8,15.0,15.5,16.3,17.2,18.1,18.7,18.9,19.1,19.2,19.2,19.1,19.3,19.4,19.6,19.8,19.8,20.0,19.8,19.5,19.2,18.8,18.5,18.4,18.3,18.2,18.2,18.1],"relative_humidity_2m":[77,79,79,79,80,82,83,84,85,85,82,75,66,54,51,47,49,48,52,58,65,73,75,78,82,84,85,85,86,85,84,84,84,85,80,73,69,64,57,56,56,56,54,55,60,64,70,74,76,80,82,86,88,88,88,89,90,91,89,85,80,74,71,68,69,71,73,78,83,86,87,88,86,87,87,87,87,89,90,90,90,88,85,80,77,79,83,83,84,84,85,86,87,88,89,90,91,91,91,90,88,87,86,85,84,82,79,75,68,60,56,61,70,76,76,72,71,75,81,84,82,77,74,74,76,79,83,89,93,81,78,74,68,60,54,51,50,51,54,58,63,68,73,78,82,86,89,91,92,92,92,91,90,88,86,84,81,79,74,66,56,50,52,58,64,70,77,82,85,86,87,89,91,92,93,95,94,90,85,80,75,70,66,66,69,74,79,84,87,88,89,88,88,87,87,88,90,91,93,93,91,86,79,71,65,60,57,56,57,58,60,63,65,66,68,70,72,74,76,78,80,82,83,84,84,82,80,77,73,71,70,71,72,74,76,79,80,82,83,84]},"daily_units":{"time":"iso8601","weather_code":"wmo code","temperature_2m_max":"°C","temperature_2m_min":"°C","precipitation_probability_max":"%","precipitation_sum":"mm","sunrise":"iso8601","sunset":"iso8601","uv_index_max":""},"daily":{"time":["2026-10-17","2026-10-18","2026-10-19","2026-10-20","2026-10-21","2026-10-22","2026-10-23","2026-10-24","2026-10-25","2026-10-26"],"weather_code":[61,2,3,61,3,3,3,53,80,51],"temperature_2m_max":[15.5,13.9,14.5,13.8,16.1,16.0,17.0,13.6,14.9,14.8],"temperature_2m_min":[9.3,7.0,5.8,10.8,9.1,5.8,9.9,6.1,9.9,9.7],"precipitation_probability_max":[5,13,23,30,38,21,48,48,45,33],"precipitation_sum":[0.10,0.00,0.00,0.60,0.00,0.00,1.20,5.40,13.20,0.60],"sunrise":["2026-10-17T08:13","2026-10-18T08:15","2026-10-19T08:16","2026-10-20T08:18","2026-10-21T08:20","2026-10-22T08:21","2026-10-23T08:23","2026-10-24T08:24","2026-10-25T08:26","2026-10-26T08:27"],"sunset":["2026-10-17T18:57","2026-10-18T18:55","2026-10-19T18:53","2026-10-20T18:51","2026-10-21T18:49","2026-10-22T18:47","2026-10-23T18:45","2026-10-24T18:44","2026-10-25T18:42","2026-10-26T18:40"],"uv_index_max":[3.25,3.25,1.85,1.40,2.15,1.60,2.60,2.40,2.40,0.60]}} \ No newline at end of file diff --git a/wetter/app/src/test/resources/reykjavik_forecast.json b/wetter/app/src/test/resources/reykjavik_forecast.json new file mode 100644 index 0000000..790050a --- /dev/null +++ b/wetter/app/src/test/resources/reykjavik_forecast.json @@ -0,0 +1 @@ +{"latitude":64.139565,"longitude":-21.971603,"generationtime_ms":1892.8502798080444,"utc_offset_seconds":0,"timezone":"Atlantic/Reykjavik","timezone_abbreviation":"GMT","elevation":0.0,"current_units":{"time":"iso8601","interval":"seconds","temperature_2m":"°C","apparent_temperature":"°C","weather_code":"wmo code","is_day":"","relative_humidity_2m":"%","dew_point_2m":"°C","wind_speed_10m":"km/h","wind_direction_10m":"°","wind_gusts_10m":"km/h","pressure_msl":"hPa","visibility":"m","uv_index":""},"current":{"time":"2026-10-17T12:00","interval":900,"temperature_2m":8.8,"apparent_temperature":1.5,"weather_code":3,"is_day":1,"relative_humidity_2m":75,"dew_point_2m":4.6,"wind_speed_10m":40.7,"wind_direction_10m":151,"wind_gusts_10m":72.4,"pressure_msl":1015.0,"visibility":50000.00,"uv_index":0.70},"minutely_15_units":{"time":"iso8601","precipitation":"mm"},"minutely_15":{"time":["2026-10-17T12:00","2026-10-17T12:15","2026-10-17T12:30","2026-10-17T12:45","2026-10-17T13:00","2026-10-17T13:15","2026-10-17T13:30","2026-10-17T13:45","2026-10-17T14:00","2026-10-17T14:15","2026-10-17T14:30","2026-10-17T14:45","2026-10-17T15:00","2026-10-17T15:15","2026-10-17T15:30","2026-10-17T15:45"],"precipitation":[0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.10,0.10,0.10,0.50,0.50,0.50]},"hourly_units":{"time":"iso8601","temperature_2m":"°C","weather_code":"wmo code","is_day":"","precipitation_probability":"%","precipitation":"mm","wind_speed_10m":"km/h","relative_humidity_2m":"%"},"hourly":{"time":["2026-10-17T00:00","2026-10-17T01:00","2026-10-17T02:00","2026-10-17T03:00","2026-10-17T04:00","2026-10-17T05:00","2026-10-17T06:00","2026-10-17T07:00","2026-10-17T08:00","2026-10-17T09:00","2026-10-17T10:00","2026-10-17T11:00","2026-10-17T12:00","2026-10-17T13:00","2026-10-17T14:00","2026-10-17T15:00","2026-10-17T16:00","2026-10-17T17:00","2026-10-17T18:00","2026-10-17T19:00","2026-10-17T20:00","2026-10-17T21:00","2026-10-17T22:00","2026-10-17T23:00","2026-10-18T00:00","2026-10-18T01:00","2026-10-18T02:00","2026-10-18T03:00","2026-10-18T04:00","2026-10-18T05:00","2026-10-18T06:00","2026-10-18T07:00","2026-10-18T08:00","2026-10-18T09:00","2026-10-18T10:00","2026-10-18T11:00","2026-10-18T12:00","2026-10-18T13:00","2026-10-18T14:00","2026-10-18T15:00","2026-10-18T16:00","2026-10-18T17:00","2026-10-18T18:00","2026-10-18T19:00","2026-10-18T20:00","2026-10-18T21:00","2026-10-18T22:00","2026-10-18T23:00","2026-10-19T00:00","2026-10-19T01:00","2026-10-19T02:00","2026-10-19T03:00","2026-10-19T04:00","2026-10-19T05:00","2026-10-19T06:00","2026-10-19T07:00","2026-10-19T08:00","2026-10-19T09:00","2026-10-19T10:00","2026-10-19T11:00","2026-10-19T12:00","2026-10-19T13:00","2026-10-19T14:00","2026-10-19T15:00","2026-10-19T16:00","2026-10-19T17:00","2026-10-19T18:00","2026-10-19T19:00","2026-10-19T20:00","2026-10-19T21:00","2026-10-19T22:00","2026-10-19T23:00","2026-10-20T00:00","2026-10-20T01:00","2026-10-20T02:00","2026-10-20T03:00","2026-10-20T04:00","2026-10-20T05:00","2026-10-20T06:00","2026-10-20T07:00","2026-10-20T08:00","2026-10-20T09:00","2026-10-20T10:00","2026-10-20T11:00","2026-10-20T12:00","2026-10-20T13:00","2026-10-20T14:00","2026-10-20T15:00","2026-10-20T16:00","2026-10-20T17:00","2026-10-20T18:00","2026-10-20T19:00","2026-10-20T20:00","2026-10-20T21:00","2026-10-20T22:00","2026-10-20T23:00","2026-10-21T00:00","2026-10-21T01:00","2026-10-21T02:00","2026-10-21T03:00","2026-10-21T04:00","2026-10-21T05:00","2026-10-21T06:00","2026-10-21T07:00","2026-10-21T08:00","2026-10-21T09:00","2026-10-21T10:00","2026-10-21T11:00","2026-10-21T12:00","2026-10-21T13:00","2026-10-21T14:00","2026-10-21T15:00","2026-10-21T16:00","2026-10-21T17:00","2026-10-21T18:00","2026-10-21T19:00","2026-10-21T20:00","2026-10-21T21:00","2026-10-21T22:00","2026-10-21T23:00","2026-10-22T00:00","2026-10-22T01:00","2026-10-22T02:00","2026-10-22T03:00","2026-10-22T04:00","2026-10-22T05:00","2026-10-22T06:00","2026-10-22T07:00","2026-10-22T08:00","2026-10-22T09:00","2026-10-22T10:00","2026-10-22T11:00","2026-10-22T12:00","2026-10-22T13:00","2026-10-22T14:00","2026-10-22T15:00","2026-10-22T16:00","2026-10-22T17:00","2026-10-22T18:00","2026-10-22T19:00","2026-10-22T20:00","2026-10-22T21:00","2026-10-22T22:00","2026-10-22T23:00","2026-10-23T00:00","2026-10-23T01:00","2026-10-23T02:00","2026-10-23T03:00","2026-10-23T04:00","2026-10-23T05:00","2026-10-23T06:00","2026-10-23T07:00","2026-10-23T08:00","2026-10-23T09:00","2026-10-23T10:00","2026-10-23T11:00","2026-10-23T12:00","2026-10-23T13:00","2026-10-23T14:00","2026-10-23T15:00","2026-10-23T16:00","2026-10-23T17:00","2026-10-23T18:00","2026-10-23T19:00","2026-10-23T20:00","2026-10-23T21:00","2026-10-23T22:00","2026-10-23T23:00","2026-10-24T00:00","2026-10-24T01:00","2026-10-24T02:00","2026-10-24T03:00","2026-10-24T04:00","2026-10-24T05:00","2026-10-24T06:00","2026-10-24T07:00","2026-10-24T08:00","2026-10-24T09:00","2026-10-24T10:00","2026-10-24T11:00","2026-10-24T12:00","2026-10-24T13:00","2026-10-24T14:00","2026-10-24T15:00","2026-10-24T16:00","2026-10-24T17:00","2026-10-24T18:00","2026-10-24T19:00","2026-10-24T20:00","2026-10-24T21:00","2026-10-24T22:00","2026-10-24T23:00","2026-10-25T00:00","2026-10-25T01:00","2026-10-25T02:00","2026-10-25T03:00","2026-10-25T04:00","2026-10-25T05:00","2026-10-25T06:00","2026-10-25T07:00","2026-10-25T08:00","2026-10-25T09:00","2026-10-25T10:00","2026-10-25T11:00","2026-10-25T12:00","2026-10-25T13:00","2026-10-25T14:00","2026-10-25T15:00","2026-10-25T16:00","2026-10-25T17:00","2026-10-25T18:00","2026-10-25T19:00","2026-10-25T20:00","2026-10-25T21:00","2026-10-25T22:00","2026-10-25T23:00","2026-10-26T00:00","2026-10-26T01:00","2026-10-26T02:00","2026-10-26T03:00","2026-10-26T04:00","2026-10-26T05:00","2026-10-26T06:00","2026-10-26T07:00","2026-10-26T08:00","2026-10-26T09:00","2026-10-26T10:00","2026-10-26T11:00","2026-10-26T12:00","2026-10-26T13:00","2026-10-26T14:00","2026-10-26T15:00","2026-10-26T16:00","2026-10-26T17:00","2026-10-26T18:00","2026-10-26T19:00","2026-10-26T20:00","2026-10-26T21:00","2026-10-26T22:00","2026-10-26T23:00"],"temperature_2m":[8.1,8.0,7.9,8.1,8.2,8.1,8.3,8.1,8.2,7.9,8.2,8.4,8.8,8.9,9.0,8.8,8.8,9.1,9.4,9.6,9.9,10.1,10.1,10.2,8.8,9.1,8.7,8.4,8.2,8.4,8.0,7.9,8.0,7.7,7.7,6.7,7.1,7.4,7.6,9.1,9.6,9.8,9.2,9.3,8.8,8.7,8.6,8.4,8.1,7.9,7.7,7.7,7.5,7.5,7.5,7.6,6.7,7.5,7.8,7.9,7.8,8.2,8.0,7.7,7.0,6.9,5.6,5.2,4.7,4.5,4.3,3.8,3.4,3.3,3.2,3.5,3.5,2.9,3.1,3.0,2.6,2.3,2.5,3.3,4.4,4.8,4.8,5.1,5.0,3.9,2.4,1.1,-0.4,-1.3,-1.2,-0.7,-0.2,-0.1,-0.2,-0.2,-0.2,-0.2,-0.3,-0.5,-0.7,-0.7,-0.1,0.7,1.5,2.3,3.0,3.2,2.7,1.8,1.1,0.7,0.5,0.5,0.5,0.8,1.0,1.4,1.9,2.2,2.2,2.0,2.0,2.4,2.8,3.3,3.7,4.2,4.5,4.9,5.3,5.3,4.9,4.3,3.7,3.5,3.5,3.3,2.8,2.5,2.2,1.7,1.0,0.4,-0.3,-0.7,-0.9,-0.7,-0.3,0.4,1.1,1.7,2.0,2.1,2.0,1.8,1.6,1.3,1.0,0.6,0.2,-0.3,-0.8,-1.2,-1.4,-1.6,-1.7,-1.7,-1.6,-1.5,-1.3,-1.0,-0.5,0.1,0.6,1.1,1.4,1.6,1.8,1.9,1.9,1.9,1.9,1.9,1.8,1.7,1.5,1.4,1.3,1.3,1.2,1.2,1.2,1.2,1.3,1.4,1.5,1.7,1.9,2.2,2.5,3.0,3.5,4.1,4.7,5.1,5.4,5.5,5.4,5.1,4.9,4.6,4.5,4.4,4.3,4.3,4.3,4.3,4.4,4.6,4.8,5.1,5.3,5.5,5.7,5.7,5.7,5.7,5.6,5.6,5.5,5.4,5.3,5.1,5.0,4.9],"weather_code":[51,3,3,3,3,3,3,3,3,3,51,3,3,3,3,51,61,61,61,61,61,61,55,61,61,61,53,0,51,51,3,3,3,51,51,53,61,53,61,55,51,53,53,3,53,3,3,3,3,3,3,3,3,3,3,3,53,61,3,3,3,3,3,3,51,51,51,3,51,51,51,51,2,2,3,3,3,51,51,3,3,3,3,3,3,3,3,3,3,2,1,1,2,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,3,3,3,3,3,3,51,51,51,85,85,85,53,53,53,51,51,51,51,51,51,51,51,51,51,51,51,0,1,1,2,3,45,45,45,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,3,3,3,2,2,2,1,2,2,2,3,3,3,3,3,3,3,3,3,51,51,51,51,51,51,3,3,3,3,3,3,3,3,3,3,3,3,51,51,51,51,51,51,3,2,2,2,1,1,1,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3],"is_day":[0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0],"precipitation_probability":[0,0,0,0,0,0,0,1,3,6,10,16,27,50,79,100,100,100,100,100,100,100,100,100,100,100,100,100,98,96,94,93,93,94,96,98,100,100,100,100,100,100,100,92,81,71,62,53,49,53,62,69,71,70,69,68,67,67,68,70,73,76,80,82,80,76,73,71,70,69,68,68,67,65,62,59,57,56,53,47,39,35,38,44,51,60,69,73,66,54,43,36,31,27,24,23,25,33,45,51,46,35,27,24,23,24,26,30,33,35,36,37,38,38,37,35,31,29,28,28,29,31,34,37,39,42,43,43,42,41,40,39,39,41,43,45,45,44,43,41,37,35,34,33,33,32,32,31,30,30,29,28,26,24,22,21,20,19,19,19,19,19,20,22,25,28,31,34,35,34,33,31,29,28,27,28,29,31,33,34,35,34,31,29,27,24,22,20,19,18,17,16,16,17,18,20,22,24,25,25,25,24,23,22,22,22,23,23,24,24,24,23,22,20,18,17,16,16,16,17,17,18,18,17,16,15,14,13,12,11,11,11,11,11,12,13,15,16,17,19],"precipitation":[0.30,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.00,0.00,0.00,0.00,0.20,2.00,2.00,2.10,2.00,1.70,1.40,1.20,2.00,1.90,1.30,0.80,0.00,0.40,0.20,0.00,0.00,0.00,0.10,0.10,0.50,1.30,0.90,2.10,1.00,0.40,0.90,0.60,0.00,0.70,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.50,1.50,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.40,0.20,0.15,0.10,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.20,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.10,0.10,0.80,0.80,0.80,0.80,0.80,0.80,0.20,0.20,0.20,0.40,0.40,0.40,0.30,0.30,0.30,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.10,0.10,0.10,0.10,0.10,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.20,0.20,0.20,0.20,0.20,0.20,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00],"wind_speed_10m":[14.4,16.2,14.8,12.2,14.0,16.6,22.3,20.9,28.1,23.8,31.3,37.4,40.7,39.2,50.0,54.4,54.7,53.6,49.7,48.2,45.4,46.8,46.1,41.4,28.4,33.1,33.5,36.7,39.6,46.8,43.2,37.8,39.2,36.4,38.5,34.2,42.1,46.8,52.2,59.8,54.7,60.1,56.5,56.2,56.2,50.0,42.8,40.3,41.0,43.2,45.7,45.7,45.7,45.0,43.9,43.2,41.8,39.6,37.1,32.4,30.2,28.1,25.9,25.6,13.3,18.7,17.5,15.5,15.0,14.0,14.4,12.1,14.1,13.4,13.4,11.5,12.1,12.7,13.5,14.5,11.8,14.7,15.7,13.8,13.9,12.5,10.9,7.8,5.4,1.9,2.4,2.2,2.5,2.9,3.4,4.1,4.4,3.5,2.3,1.7,2.2,3.4,4.5,5.5,6.5,7.0,6.3,5.4,5.2,5.5,6.2,7.1,8.6,10.6,12.2,13.3,14.0,14.6,14.9,15.2,15.7,16.6,17.6,18.4,18.1,17.4,16.9,17.0,17.3,17.7,18.3,19.0,19.3,18.5,17.1,16.4,17.5,19.1,19.9,19.0,17.5,16.0,11.2,9.4,8.5,8.4,9.2,10.4,11.7,13.0,13.6,13.7,13.7,13.5,13.3,13.1,12.7,11.9,11.0,9.7,8.5,7.2,6.2,5.3,4.4,3.6,3.3,3.5,3.7,3.9,4.2,4.5,4.8,4.9,5.0,5.3,6.0,6.9,7.7,8.3,8.5,8.0,7.3,6.9,7.2,7.7,8.4,8.5,8.5,8.0,7.5,7.0,6.9,6.8,6.7,6.7,6.8,7.0,7.2,7.6,8.0,8.5,9.1,9.8,10.4,11.3,12.3,13.5,14.6,15.5,16.2,16.5,16.6,16.5,16.4,16.2,16.2,16.4,16.3,16.3,16.3,16.3,16.6,16.8,17.4,17.9,18.5,19.4,20.2,21.1,22.2,23.4,24.5,25.4,25.9,26.2,26.1,25.9,25.7,25.5],"relative_humidity_2m":[87,87,85,86,87,84,76,75,76,78,80,77,75,77,79,83,86,87,88,90,90,92,93,91,90,90,85,79,81,76,75,75,72,76,78,85,86,87,87,88,88,82,83,79,80,72,76,73,70,68,69,66,63,65,66,66,79,70,68,70,74,69,71,78,83,84,87,85,88,88,87,88,86,82,80,76,72,81,79,78,81,84,87,86,78,77,78,76,77,84,87,87,87,86,86,86,86,86,87,88,88,88,88,89,89,89,91,93,93,90,87,85,86,89,92,92,92,90,88,85,82,80,78,78,81,86,89,90,90,90,89,88,87,85,84,83,85,87,90,91,92,92,91,91,92,92,92,93,93,93,93,93,93,92,91,90,90,89,87,85,83,82,81,81,81,81,82,83,83,82,82,82,81,80,79,78,77,75,74,72,72,72,72,73,73,75,76,78,80,82,85,87,89,90,91,91,92,92,92,92,92,91,91,90,89,88,86,83,82,80,80,80,82,84,86,88,90,91,91,91,91,91,90,89,88,86,84,83,82,81,81,81,81,81,82,82,82,82,82,82]},"daily_units":{"time":"iso8601","weather_code":"wmo code","temperature_2m_max":"°C","temperature_2m_min":"°C","precipitation_probability_max":"%","precipitation_sum":"mm","sunrise":"iso8601","sunset":"iso8601","uv_index_max":""},"daily":{"time":["2026-10-17","2026-10-18","2026-10-19","2026-10-20","2026-10-21","2026-10-22","2026-10-23","2026-10-24","2026-10-25","2026-10-26"],"weather_code":[61,61,61,51,45,85,51,3,51,51],"temperature_2m_max":[10.3,9.8,8.2,5.1,3.2,5.5,3.3,3.0,5.5,6.0],"temperature_2m_min":[7.9,6.7,3.5,-1.4,-0.7,1.0,-1.3,-1.9,-0.2,4.1],"precipitation_probability_max":[100,100,82,73,51,45,34,35,25,19],"precipitation_sum":[16.60,11.30,3.25,0.30,0.00,8.10,0.00,0.00,1.80,0.00],"sunrise":["2026-10-17T08:24","2026-10-18T08:27","2026-10-19T08:30","2026-10-20T08:33","2026-10-21T08:36","2026-10-22T08:39","2026-10-23T08:42","2026-10-24T08:45","2026-10-25T08:48","2026-10-26T08:52"],"sunset":["2026-10-17T18:01","2026-10-18T17:57","2026-10-19T17:54","2026-10-20T17:50","2026-10-21T17:47","2026-10-22T17:43","2026-10-23T17:40","2026-10-24T17:37","2026-10-25T17:33","2026-10-26T17:30"],"uv_index_max":[1.00,0.15,0.60,1.00,0.60,0.85,1.00,1.00,0.20,0.45]}} \ No newline at end of file