diff --git a/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt b/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt new file mode 100644 index 0000000..7cd9621 --- /dev/null +++ b/wetter/app/src/test/java/app/wetter/ScreenshotTest.kt @@ -0,0 +1,105 @@ +package app.wetter + +import androidx.compose.foundation.layout.Box +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.FormatListBulleted +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import app.wetter.data.Forecast +import app.wetter.data.OpenMeteo +import app.wetter.data.Quarter +import app.wetter.data.Settings +import app.wetter.data.ThemeMode +import app.wetter.ui.screens.DayContent +import app.wetter.ui.screens.ForecastPage +import app.wetter.ui.screens.SettingsScreen +import app.wetter.ui.theme.WeatherTheme +import com.github.takahirom.roborazzi.captureRoboImage +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode +import java.time.LocalDateTime + +/** Renders the screens on the JVM into replica/clone-screens/ for replica-diff. Data: a real Open-Meteo response. */ +@RunWith(RobolectricTestRunner::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config(sdk = [35], qualifiers = "de-rDE-w412dp-h915dp-xxhdpi", application = android.app.Application::class) +class ScreenshotTest { + @get:Rule val compose = createComposeRule() + + private fun fixture(): Forecast { + val f = javaClass.classLoader!!.getResource("berlin_forecast.json")!!.readText() + val a = javaClass.classLoader!!.getResource("berlin_air.json")!!.readText() + val parsed = OpenMeteo().parse(f, a, System.currentTimeMillis()) + // pin "now" to the response's own current time so the shots never drift + val now = LocalDateTime.parse(Regex("\"current\":\\{\"time\":\"([^\"]+)\"").find(f)!!.groupValues[1]) + return parsed.copy(clock = now) + } + + private fun Forecast.with(code: Int, isDay: Boolean, rainSoon: Boolean = false): Forecast { + val minutely = if (!rainSoon) minutely else List(12) { i -> + Quarter(now.withMinute(0).plusMinutes(15L * i), if (i in 2..7) listOf(0.2, 0.5, 0.9, 1.4, 0.8, 0.3)[i - 2] else 0.0) + } + return copy(current = current.copy(code = code, isDay = isDay), minutely = minutely) + } + + private fun shot(name: String, theme: ThemeMode, content: @Composable () -> Unit) { + compose.setContent { WeatherTheme(theme) { content() } } + compose.onRoot().captureRoboImage("../../replica/clone-screens/$name.png") + } + + @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 = {}, + navigation = { IconButton(onClick = {}) { Icon(Icons.Rounded.FormatListBulleted, null) } }, + actions = { + if (preview) FilledTonalButton(onClick = {}) { Text("Speichern") } + else IconButton(onClick = {}) { Icon(Icons.Rounded.Settings, null) } + }, + ) + + @Test fun parserReadsRealResponse() { + val f = fixture() + assertEquals(10, f.daily.size) + assertTrue(f.hourly.size >= 240) + assertTrue(f.upcomingHours(25).isNotEmpty()) + assertTrue(f.air?.aqi != null) + } + + @Test fun s01_real_light() = shot("S01-real-light", ThemeMode.LIGHT) { Home(fixture()) } + @Test fun s01_clear_day() = shot("S01-clear-day", ThemeMode.LIGHT) { Home(fixture().with(0, true)) } + @Test fun s01_clear_night_dark() = shot("S01-clear-night-dark", ThemeMode.DARK) { Home(fixture().with(0, false)) } + @Test fun s01_rain_nowcast() = shot("S01-rain-nowcast", ThemeMode.LIGHT) { Home(fixture().with(63, true, rainSoon = true)) } + @Test fun s01_snow() = shot("S01-snow", ThemeMode.LIGHT) { Home(fixture().with(73, true)) } + @Test fun s01_storm_dark() = shot("S01-storm-dark", ThemeMode.DARK) { Home(fixture().with(95, false)) } + + @Config(qualifiers = "de-rDE-w412dp-h2600dp-xxhdpi") + @Test fun s01_full_light() = shot("S01-full-light", ThemeMode.LIGHT) { Home(fixture().with(2, true)) } + + @Config(qualifiers = "de-rDE-w412dp-h2600dp-xxhdpi") + @Test fun s01_full_dark() = shot("S01-full-dark", ThemeMode.DARK) { Home(fixture().with(0, false)) } + + @Config(qualifiers = "de-rDE-w412dp-h1800dp-xxhdpi") + @Test fun s04_day_dark() = shot("S04-day-dark", ThemeMode.DARK) { + val f = fixture() + DayContent(f, "Berlin", f.daily[1].date, Settings(), onBack = {}) + } + + @Test fun s15_preview() = shot("S15-preview", ThemeMode.LIGHT) { Home(fixture().with(1, true), "Zwickau", preview = true) } + @Test fun s08_settings() = shot("S08-settings", ThemeMode.LIGHT) { SettingsScreen(Settings(), onBack = {}) } + + @Config(qualifiers = "de-rDE-w840dp-h900dp-xxhdpi") + @Test fun s01_tablet() = shot("S01-tablet", ThemeMode.LIGHT) { Box { Home(fixture().with(2, true)) } } +} diff --git a/wetter/app/src/test/resources/berlin_air.json b/wetter/app/src/test/resources/berlin_air.json new file mode 100644 index 0000000..5393d63 --- /dev/null +++ b/wetter/app/src/test/resources/berlin_air.json @@ -0,0 +1 @@ +{"latitude":52.5,"longitude":13.400002,"generationtime_ms":0.6562471389770508,"utc_offset_seconds":7200,"timezone":"Europe/Berlin","timezone_abbreviation":"GMT+2","elevation":38.0,"current_units":{"time":"iso8601","interval":"seconds","european_aqi":"EAQI","pm2_5":"μg/m³","pm10":"μg/m³","alder_pollen":"grains/m³","birch_pollen":"grains/m³","grass_pollen":"grains/m³","mugwort_pollen":"grains/m³","olive_pollen":"grains/m³","ragweed_pollen":"grains/m³"},"current":{"time":"2026-10-17T13:00","interval":3600,"european_aqi":20,"pm2_5":5.0,"pm10":5.1,"alder_pollen":0.0,"birch_pollen":0.0,"grass_pollen":0.0,"mugwort_pollen":0.0,"olive_pollen":0.0,"ragweed_pollen":0.0}} \ No newline at end of file diff --git a/wetter/app/src/test/resources/berlin_forecast.json b/wetter/app/src/test/resources/berlin_forecast.json new file mode 100644 index 0000000..ebaf502 --- /dev/null +++ b/wetter/app/src/test/resources/berlin_forecast.json @@ -0,0 +1 @@ +{"latitude":52.512604,"longitude":13.419517,"generationtime_ms":5132.478952407837,"utc_offset_seconds":7200,"timezone":"Europe/Berlin","timezone_abbreviation":"GMT+2","elevation":38.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-17T13:45","interval":900,"temperature_2m":12.2,"apparent_temperature":10.4,"weather_code":3,"is_day":1,"relative_humidity_2m":85,"dew_point_2m":9.8,"wind_speed_10m":12.4,"wind_direction_10m":343,"wind_gusts_10m":27.7,"pressure_msl":1018.6,"visibility":23900.00,"uv_index":0.35},"minutely_15_units":{"time":"iso8601","precipitation":"mm"},"minutely_15":{"time":["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","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"],"precipitation":[0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.10,0.30,0.50,0.30,0.10,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":[11.7,11.2,10.6,10.4,10.2,10.0,9.8,9.6,9.4,10.6,12.0,12.1,12.5,12.4,12.1,11.6,11.2,11.2,11.3,11.1,11.2,11.2,11.3,11.3,11.4,11.3,11.5,11.6,11.5,11.6,11.7,11.6,11.7,11.6,11.5,11.7,12.2,13.1,14.0,14.2,14.4,14.6,14.2,13.7,13.0,12.4,12.0,12.0,11.7,11.1,10.1,8.7,7.6,6.9,6.5,6.3,6.1,5.8,6.5,8.0,9.5,10.9,11.7,12.2,12.2,11.5,10.1,8.2,6.5,5.4,4.7,3.9,3.2,2.5,1.8,1.8,2.1,1.9,2.5,3.3,4.0,4.9,5.9,6.3,7.0,8.0,9.2,9.8,10.2,10.4,10.2,9.7,9.2,9.0,8.9,8.8,8.7,8.7,8.6,8.4,8.1,7.9,7.7,7.5,7.6,8.2,9.0,9.8,10.5,11.1,11.7,12.2,12.7,12.8,12.4,11.6,11.0,10.6,10.3,10.0,9.7,9.4,9.0,8.4,7.6,7.2,7.4,7.8,8.1,8.7,9.6,10.6,12.1,13.8,14.9,15.3,15.1,14.7,13.9,12.8,12.0,11.5,11.2,10.9,10.6,10.3,9.9,9.1,8.1,7.3,6.6,5.9,5.9,6.9,8.4,10.0,11.6,13.3,14.4,14.8,14.7,14.0,12.7,10.8,9.3,8.2,7.4,6.8,6.5,6.3,6.3,6.5,6.8,7.0,7.2,7.3,7.6,8.3,9.2,10.1,10.9,11.4,11.4,10.8,10.2,9.5,8.7,8.0,7.4,6.9,6.3,5.8,5.4,5.1,5.0,5.1,5.4,5.9,6.4,6.8,7.1,7.3,7.5,7.6,7.7,7.8,8.0,8.2,8.4,8.6,8.8,8.9,8.9,8.8,8.6,8.3,8.1,7.8,7.7,7.6,7.5,7.5,7.6,7.7,8.0,8.6,9.4,10.3,11.1,11.9,12.3,12.5,12.4,12.2,11.9,11.6,11.3,10.9,10.5,10.0],"weather_code":[1,1,3,3,3,2,3,3,2,3,3,3,3,3,3,3,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,3,3,3,2,3,3,3,2,2,1,1,1,3,3,3,2,1,1,1,1,2,0,2,2,0,0,0,0,0,0,0,0,1,2,2,2,2,3,3,3,3,3,3,3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,61,61,61,3,3,3,3,3,3,3,3,3,61,61,61,61,61,61,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,3,3,3,61,61,61,3,3,3,3,3,3,2,2,2,51,51,51,51,51,51,0,1,1,2,2,3,51,51,51,51,51,51,61,61,61,61,61,61,51,51,51,51,51,51,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,3,3,3,3,3,3,3,3],"is_day":[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,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],"precipitation_probability":[0,0,0,0,0,0,0,0,15,10,8,3,0,0,3,8,10,10,5,3,3,0,3,0,3,3,0,0,0,0,0,0,0,0,3,3,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,2,3,3,3,2,2,1,0,0,0,0,0,0,0,0,0,1,2,2,3,3,3,3,3,3,3,3,2,2,1,0,0,0,1,3,6,8,11,13,15,17,19,21,23,25,27,28,30,27,25,22,19,20,21,22,22,23,24,24,25,26,27,28,28,29,29,29,29,28,27,25,23,21,18,15,13,10,8,6,4,3,2,2,2,3,3,4,5,6,7,8,9,10,11,12,13,13,14,15,16,17,18,17,15,13,11,12,13,14,15,16,16,15,14,13,11,10,9,8,7,6,6,6,6,6,7,8,9,10,12,14,16,18,20,22,23,24,24,24,24,24,25,25,26,26,26,25,23,19,16,12,9,6,5,5,5,5,6,6,6,7,7],"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,0.40,0.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.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.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.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.20,0.20,0.20,0.20,0.20,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.10,0.10,0.10,0.10,0.10,0.10,1.60,1.60,1.60,1.60,1.60,1.60,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.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":[3.6,3.4,4.1,3.0,3.3,2.9,3.0,2.5,1.3,4.3,6.2,8.7,11.9,13.0,12.3,12.9,11.1,11.2,10.9,11.2,11.4,11.0,9.9,8.2,6.6,4.7,4.0,1.8,1.4,3.3,5.1,7.1,10.7,11.2,11.0,10.9,11.1,10.1,11.8,12.0,11.5,11.3,12.4,10.5,11.1,11.1,10.6,12.6,12.9,13.4,12.9,12.6,10.7,10.2,10.3,10.3,10.6,10.9,10.9,10.4,10.5,11.5,11.7,10.5,12.1,10.0,7.7,6.5,6.4,6.8,7.3,7.6,7.3,7.3,7.1,7.4,6.6,6.8,6.6,7.4,7.2,8.0,8.6,9.4,9.5,10.6,11.6,11.9,11.9,11.5,11.2,10.5,10.2,9.7,9.7,9.4,8.7,7.9,7.2,6.8,6.8,6.8,6.9,6.9,7.0,7.4,7.8,8.1,7.7,7.3,6.6,5.9,5.5,4.7,3.8,3.3,2.7,2.2,2.2,2.1,2.3,2.5,2.8,3.1,3.3,3.6,4.2,4.8,5.6,4.5,5.1,6.3,8.0,9.8,10.8,10.1,8.4,7.0,6.8,7.1,7.1,7.1,7.2,7.6,7.6,7.2,6.5,5.5,4.5,4.1,4.2,4.4,4.7,5.4,6.2,6.9,7.6,8.6,9.0,9.0,9.1,8.8,8.7,8.4,8.2,8.2,8.0,7.8,7.9,8.0,7.9,8.0,8.0,7.9,7.9,7.4,7.3,7.2,7.4,8.0,11.7,14.9,17.1,18.0,15.9,13.7,11.7,10.3,9.7,9.7,10.0,10.5,11.2,11.9,12.8,13.9,15.2,16.8,18.4,19.5,20.4,20.8,21.0,21.1,21.1,21.1,20.8,20.0,19.0,17.9,16.7,15.9,15.4,15.1,15.0,15.0,15.2,15.3,15.6,15.8,16.4,16.9,17.5,18.0,18.4,18.8,19.5,19.8,20.4,20.6,20.7,20.6,20.1,19.5,18.9,18.2,17.5,16.8,15.9,15.2],"relative_humidity_2m":[87,89,92,93,92,93,94,94,94,91,86,88,89,84,85,86,88,89,89,90,90,91,91,91,92,92,92,93,92,92,92,92,82,78,77,76,73,70,65,64,62,64,69,73,73,81,84,80,77,75,74,76,79,83,85,84,84,80,73,64,55,49,44,40,36,37,41,48,53,59,61,65,70,76,82,85,88,91,90,88,87,84,80,79,77,73,70,69,68,69,72,76,79,81,83,85,86,87,88,90,92,93,94,95,94,91,86,81,77,74,72,70,69,69,72,77,81,84,85,87,88,90,91,93,95,96,95,94,93,91,91,88,81,72,65,62,62,64,69,75,81,86,90,93,93,91,90,91,93,94,95,95,93,87,79,72,66,60,56,54,53,55,63,73,82,86,88,89,87,84,82,83,86,88,90,92,92,91,88,84,75,66,63,64,66,70,75,79,83,85,87,88,89,90,90,89,88,86,85,83,83,84,86,88,91,93,94,94,95,94,94,93,92,92,91,89,88,87,86,85,85,84,84,83,82,79,76,72,69,67,65,65,65,66,67,69,71,73,75,78]},"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,3,3,3,3,61,3,61,61,3],"temperature_2m_max":[12.6,14.6,12.3,10.4,12.8,15.3,14.9,11.5,9.7,12.7],"temperature_2m_min":[9.4,11.3,3.2,1.8,7.5,7.2,5.8,5.1,4.7,7.4],"precipitation_probability_max":[15,3,0,3,19,30,29,18,24,26],"precipitation_sum":[1.30,0.00,0.00,0.00,0.00,0.90,0.00,1.80,10.80,0.00],"sunrise":["2026-10-17T07:35","2026-10-18T07:36","2026-10-19T07:38","2026-10-20T07:40","2026-10-21T07:42","2026-10-22T07:44","2026-10-23T07:46","2026-10-24T07:47","2026-10-25T07:49","2026-10-26T07:51"],"sunset":["2026-10-17T18:07","2026-10-18T18:05","2026-10-19T18:02","2026-10-20T18:00","2026-10-21T17:58","2026-10-22T17:56","2026-10-23T17:54","2026-10-24T17:52","2026-10-25T17:50","2026-10-26T17:48"],"uv_index_max":[0.40,2.40,2.65,2.55,2.40,1.85,2.10,2.05,1.70,2.15]}} \ No newline at end of file