From 5e040470c7614c2522af4fafe1e01d431ba437a0 Mon Sep 17 00:00:00 2001 From: kenneth Date: Mon, 19 Jan 2026 00:43:38 +0000 Subject: [PATCH] feat: add @aris/source-weatherkit package Implements FeedSource for WeatherKit API. Depends on location source, provides weather context for downstream sources, and produces weather feed items. Co-authored-by: Ona --- bun.lock | 11 + packages/aris-source-weatherkit/README.md | 101 +++++ .../fixtures/san-francisco.json | 1 + packages/aris-source-weatherkit/package.json | 15 + .../aris-source-weatherkit/src/feed-items.ts | 97 +++++ packages/aris-source-weatherkit/src/index.ts | 39 ++ .../src/weather-context.ts | 27 ++ .../src/weather-source.test.ts | 182 +++++++++ .../src/weather-source.ts | 363 +++++++++++++++++ .../aris-source-weatherkit/src/weatherkit.ts | 367 ++++++++++++++++++ 10 files changed, 1203 insertions(+) create mode 100644 packages/aris-source-weatherkit/README.md create mode 100644 packages/aris-source-weatherkit/fixtures/san-francisco.json create mode 100644 packages/aris-source-weatherkit/package.json create mode 100644 packages/aris-source-weatherkit/src/feed-items.ts create mode 100644 packages/aris-source-weatherkit/src/index.ts create mode 100644 packages/aris-source-weatherkit/src/weather-context.ts create mode 100644 packages/aris-source-weatherkit/src/weather-source.test.ts create mode 100644 packages/aris-source-weatherkit/src/weather-source.ts create mode 100644 packages/aris-source-weatherkit/src/weatherkit.ts diff --git a/bun.lock b/bun.lock index a1600f6..2b5c0e7 100644 --- a/bun.lock +++ b/bun.lock @@ -40,6 +40,15 @@ "@aris/core": "workspace:*", }, }, + "packages/aris-source-weatherkit": { + "name": "@aris/source-weatherkit", + "version": "0.0.0", + "dependencies": { + "@aris/core": "workspace:*", + "@aris/source-location": "workspace:*", + "arktype": "^2.1.0", + }, + }, }, "packages": { "@aris/core": ["@aris/core@workspace:packages/aris-core"], @@ -50,6 +59,8 @@ "@aris/source-location": ["@aris/source-location@workspace:packages/aris-source-location"], + "@aris/source-weatherkit": ["@aris/source-weatherkit@workspace:packages/aris-source-weatherkit"], + "@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="], "@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="], diff --git a/packages/aris-source-weatherkit/README.md b/packages/aris-source-weatherkit/README.md new file mode 100644 index 0000000..bd44a95 --- /dev/null +++ b/packages/aris-source-weatherkit/README.md @@ -0,0 +1,101 @@ +# @aris/source-weatherkit + +Weather feed source using Apple WeatherKit API. + +## Usage + +### Basic Setup + +```ts +import { WeatherSource, Units } from "@aris/source-weatherkit" + +const weatherSource = new WeatherSource({ + credentials: { + privateKey: process.env.WEATHERKIT_PRIVATE_KEY!, + keyId: process.env.WEATHERKIT_KEY_ID!, + teamId: process.env.WEATHERKIT_TEAM_ID!, + serviceId: process.env.WEATHERKIT_SERVICE_ID!, + }, + units: Units.metric, +}) +``` + +### With Feed Source Graph + +```ts +import { LocationSource } from "@aris/source-location" +import { WeatherSource } from "@aris/source-weatherkit" + +const locationSource = new LocationSource() +const weatherSource = new WeatherSource({ credentials }) + +// Weather depends on location - graph handles ordering +const sources = [locationSource, weatherSource] +``` + +### Reading Weather Context + +Downstream sources can access weather data: + +```ts +import { contextValue } from "@aris/core" +import { WeatherKey } from "@aris/source-weatherkit" + +async function fetchContext(context: Context) { + const weather = contextValue(context, WeatherKey) + + if (weather?.condition === "Rain") { + // Suggest umbrella, indoor activities, etc. + } + + if (weather && weather.uvIndex > 7) { + // Suggest sunscreen + } +} +``` + +## Exports + +| Export | Description | +| --------------- | --------------------------------------- | +| `WeatherSource` | FeedSource implementation | +| `WeatherKey` | Context key for simplified weather data | +| `Weather` | Type for weather context | +| `Units` | `metric` or `imperial` | + +## Options + +| Option | Default | Description | +| ------------- | -------- | -------------------------- | +| `credentials` | - | WeatherKit API credentials | +| `client` | - | Custom WeatherKit client | +| `hourlyLimit` | `12` | Max hourly forecasts | +| `dailyLimit` | `7` | Max daily forecasts | +| `units` | `metric` | Temperature/speed units | + +## Context + +Provides simplified weather context for downstream sources: + +```ts +interface Weather { + temperature: number + temperatureApparent: number + condition: ConditionCode + humidity: number + uvIndex: number + windSpeed: number + daylight: boolean +} +``` + +## Feed Items + +Produces feed items: + +- `weather-current` - Current conditions +- `weather-hourly` - Hourly forecasts (up to `hourlyLimit`) +- `weather-daily` - Daily forecasts (up to `dailyLimit`) +- `weather-alert` - Weather alerts when present + +Priority is adjusted based on weather severity (storms, extreme temperatures). diff --git a/packages/aris-source-weatherkit/fixtures/san-francisco.json b/packages/aris-source-weatherkit/fixtures/san-francisco.json new file mode 100644 index 0000000..7f28f17 --- /dev/null +++ b/packages/aris-source-weatherkit/fixtures/san-francisco.json @@ -0,0 +1 @@ +{"generatedAt":"2026-01-17T00:37:11.971Z","location":{"lat":37.7749,"lng":-122.4194},"response":{"currentWeather":{"name":"CurrentWeather","metadata":{"attributionURL":"https://developer.apple.com/weatherkit/data-source-attribution/","expireTime":"2026-01-17T00:42:11Z","latitude":37.77,"longitude":-122.42,"readTime":"2026-01-17T00:37:11Z","reportedTime":"2026-01-17T00:37:11Z","units":"m","version":1,"sourceType":"modeled"},"asOf":"2026-01-17T00:37:11Z","cloudCover":0.18,"cloudCoverLowAltPct":0.18,"cloudCoverMidAltPct":0.18,"cloudCoverHighAltPct":0.18,"conditionCode":"MostlyClear","daylight":true,"humidity":0.62,"precipitationIntensity":0,"pressure":1017.74,"pressureTrend":"steady","temperature":15.87,"temperatureApparent":14.43,"temperatureDewPoint":8.6,"uvIndex":0,"visibility":30655.28,"windDirection":352,"windGust":17.67,"windSpeed":11.15},"forecastDaily":{"name":"DailyForecast","metadata":{"attributionURL":"https://developer.apple.com/weatherkit/data-source-attribution/","expireTime":"2026-01-17T01:37:11Z","latitude":37.77,"longitude":-122.42,"readTime":"2026-01-17T00:37:11Z","reportedTime":"2026-01-16T23:05:34Z","units":"m","version":1,"sourceType":"modeled"},"days":[{"forecastStart":"2026-01-16T08:00:00Z","forecastEnd":"2026-01-17T08:00:00Z","conditionCode":"Clear","maxUvIndex":3,"moonPhase":"waningCrescent","moonrise":"2026-01-16T14:09:55Z","moonset":"2026-01-16T23:12:47Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-16T08:19:22Z","solarNoon":"2026-01-16T20:19:43Z","sunrise":"2026-01-16T15:23:30Z","sunriseCivil":"2026-01-16T14:54:53Z","sunriseNautical":"2026-01-16T14:22:43Z","sunriseAstronomical":"2026-01-16T13:51:16Z","sunset":"2026-01-17T01:16:09Z","sunsetCivil":"2026-01-17T01:44:43Z","sunsetNautical":"2026-01-17T02:16:53Z","sunsetAstronomical":"2026-01-17T02:48:20Z","temperatureMax":19.88,"temperatureMin":8.85,"windGustSpeedMax":18.66,"windSpeedAvg":5.57,"windSpeedMax":11.53,"daytimeForecast":{"forecastStart":"2026-01-16T15:00:00Z","forecastEnd":"2026-01-17T03:00:00Z","cloudCover":0.02,"conditionCode":"Clear","humidity":0.63,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":19.88,"temperatureMin":9.02,"windDirection":6,"windGustSpeedMax":18.66,"windSpeed":5.6,"windSpeedMax":11.53},"overnightForecast":{"forecastStart":"2026-01-17T03:00:00Z","forecastEnd":"2026-01-17T15:00:00Z","cloudCover":0.13,"conditionCode":"MostlyClear","humidity":0.84,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":12.51,"temperatureMin":8.03,"windDirection":11,"windGustSpeedMax":18.52,"windSpeed":10.23,"windSpeedMax":11.28},"restOfDayForecast":{"forecastStart":"2026-01-17T00:37:11Z","forecastEnd":"2026-01-17T08:00:00Z","cloudCover":0,"conditionCode":"Clear","humidity":0.77,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":15.87,"temperatureMin":9.84,"windDirection":348,"windGustSpeedMax":18.52,"windSpeed":10.36,"windSpeedMax":11.15}},{"forecastStart":"2026-01-17T08:00:00Z","forecastEnd":"2026-01-18T08:00:00Z","conditionCode":"MostlyCloudy","maxUvIndex":2,"moonPhase":"waningCrescent","moonrise":"2026-01-17T14:56:53Z","moonset":"2026-01-18T00:13:04Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-17T08:19:42Z","solarNoon":"2026-01-17T20:20:03Z","sunrise":"2026-01-17T15:23:07Z","sunriseCivil":"2026-01-17T14:54:34Z","sunriseNautical":"2026-01-17T14:22:26Z","sunriseAstronomical":"2026-01-17T13:51:01Z","sunset":"2026-01-18T01:17:12Z","sunsetCivil":"2026-01-18T01:45:42Z","sunsetNautical":"2026-01-18T02:17:50Z","sunsetAstronomical":"2026-01-18T02:49:14Z","temperatureMax":16.94,"temperatureMin":8.03,"windGustSpeedMax":20.39,"windSpeedAvg":10.06,"windSpeedMax":11.3,"daytimeForecast":{"forecastStart":"2026-01-17T15:00:00Z","forecastEnd":"2026-01-18T03:00:00Z","cloudCover":0.71,"conditionCode":"MostlyCloudy","humidity":0.69,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":16.94,"temperatureMin":8.04,"windDirection":29,"windGustSpeedMax":20.39,"windSpeed":10.63,"windSpeedMax":11.3},"overnightForecast":{"forecastStart":"2026-01-18T03:00:00Z","forecastEnd":"2026-01-18T15:00:00Z","cloudCover":0.84,"conditionCode":"MostlyCloudy","humidity":0.78,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.41,"temperatureMin":9.08,"windDirection":31,"windGustSpeedMax":14.86,"windSpeed":8.91,"windSpeedMax":10.05}},{"forecastStart":"2026-01-18T08:00:00Z","forecastEnd":"2026-01-19T08:00:00Z","conditionCode":"MostlyClear","maxUvIndex":3,"moonPhase":"waningCrescent","moonrise":"2026-01-18T15:37:11Z","moonset":"2026-01-19T01:17:13Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-18T08:20:01Z","solarNoon":"2026-01-18T20:20:23Z","sunrise":"2026-01-18T15:22:41Z","sunriseCivil":"2026-01-18T14:54:12Z","sunriseNautical":"2026-01-18T14:22:08Z","sunriseAstronomical":"2026-01-18T13:50:45Z","sunset":"2026-01-19T01:18:16Z","sunsetCivil":"2026-01-19T01:46:43Z","sunsetNautical":"2026-01-19T02:18:47Z","sunsetAstronomical":"2026-01-19T02:50:09Z","temperatureMax":18.18,"temperatureMin":9.08,"windGustSpeedMax":20.16,"windSpeedAvg":8.09,"windSpeedMax":10.05,"daytimeForecast":{"forecastStart":"2026-01-18T15:00:00Z","forecastEnd":"2026-01-19T03:00:00Z","cloudCover":0.24,"conditionCode":"MostlyClear","humidity":0.63,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":18.18,"temperatureMin":9.19,"windDirection":29,"windGustSpeedMax":20.16,"windSpeed":8.65,"windSpeedMax":9.98},"overnightForecast":{"forecastStart":"2026-01-19T03:00:00Z","forecastEnd":"2026-01-19T15:00:00Z","cloudCover":0.16,"conditionCode":"MostlyClear","humidity":0.8,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":14.04,"temperatureMin":8.35,"windDirection":37,"windGustSpeedMax":13.01,"windSpeed":5.09,"windSpeedMax":6.54}},{"forecastStart":"2026-01-19T08:00:00Z","forecastEnd":"2026-01-20T08:00:00Z","conditionCode":"MostlyClear","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-19T16:11:15Z","moonset":"2026-01-20T02:22:55Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-19T08:20:20Z","solarNoon":"2026-01-19T20:20:41Z","sunrise":"2026-01-19T15:22:14Z","sunriseCivil":"2026-01-19T14:53:48Z","sunriseNautical":"2026-01-19T14:21:47Z","sunriseAstronomical":"2026-01-19T13:50:26Z","sunset":"2026-01-20T01:19:21Z","sunsetCivil":"2026-01-20T01:47:43Z","sunsetNautical":"2026-01-20T02:19:45Z","sunsetAstronomical":"2026-01-20T02:51:04Z","temperatureMax":17.4,"temperatureMin":8.35,"windGustSpeedMax":17.91,"windSpeedAvg":7.28,"windSpeedMax":9.93,"daytimeForecast":{"forecastStart":"2026-01-19T15:00:00Z","forecastEnd":"2026-01-20T03:00:00Z","cloudCover":0.15,"conditionCode":"MostlyClear","humidity":0.63,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":17.4,"temperatureMin":8.46,"windDirection":29,"windGustSpeedMax":17.91,"windSpeed":8.38,"windSpeedMax":9.93},"overnightForecast":{"forecastStart":"2026-01-20T03:00:00Z","forecastEnd":"2026-01-20T15:00:00Z","cloudCover":0.06,"conditionCode":"Clear","humidity":0.78,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.69,"temperatureMin":7.41,"windDirection":35,"windGustSpeedMax":16.49,"windSpeed":8.46,"windSpeedMax":9.54}},{"forecastStart":"2026-01-20T08:00:00Z","forecastEnd":"2026-01-21T08:00:00Z","conditionCode":"Clear","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-20T16:40:40Z","moonset":"2026-01-21T03:28:39Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-20T08:20:37Z","solarNoon":"2026-01-20T20:20:59Z","sunrise":"2026-01-20T15:21:45Z","sunriseCivil":"2026-01-20T14:53:23Z","sunriseNautical":"2026-01-20T14:21:24Z","sunriseAstronomical":"2026-01-20T13:50:06Z","sunset":"2026-01-21T01:20:26Z","sunsetCivil":"2026-01-21T01:48:44Z","sunsetNautical":"2026-01-21T02:20:44Z","sunsetAstronomical":"2026-01-21T02:52:00Z","temperatureMax":17.26,"temperatureMin":7.41,"windGustSpeedMax":18.87,"windSpeedAvg":8.01,"windSpeedMax":9.54,"daytimeForecast":{"forecastStart":"2026-01-20T15:00:00Z","forecastEnd":"2026-01-21T03:00:00Z","cloudCover":0.09,"conditionCode":"Clear","humidity":0.65,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":17.26,"temperatureMin":7.95,"windDirection":35,"windGustSpeedMax":18.87,"windSpeed":8.24,"windSpeedMax":9.48},"overnightForecast":{"forecastStart":"2026-01-21T03:00:00Z","forecastEnd":"2026-01-21T15:00:00Z","cloudCover":0.12,"conditionCode":"Clear","humidity":0.76,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.82,"temperatureMin":6.86,"windDirection":37,"windGustSpeedMax":13.61,"windSpeed":6.29,"windSpeedMax":7.08}},{"forecastStart":"2026-01-21T08:00:00Z","forecastEnd":"2026-01-22T08:00:00Z","conditionCode":"MostlyClear","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-21T17:06:43Z","moonset":"2026-01-22T04:34:00Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-21T08:20:54Z","solarNoon":"2026-01-21T20:21:16Z","sunrise":"2026-01-21T15:21:14Z","sunriseCivil":"2026-01-21T14:52:56Z","sunriseNautical":"2026-01-21T14:21:00Z","sunriseAstronomical":"2026-01-21T13:49:44Z","sunset":"2026-01-22T01:21:31Z","sunsetCivil":"2026-01-22T01:49:45Z","sunsetNautical":"2026-01-22T02:21:42Z","sunsetAstronomical":"2026-01-22T02:52:56Z","temperatureMax":16.24,"temperatureMin":6.86,"windGustSpeedMax":16.74,"windSpeedAvg":5.86,"windSpeedMax":7.11,"daytimeForecast":{"forecastStart":"2026-01-21T15:00:00Z","forecastEnd":"2026-01-22T03:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","humidity":0.69,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":16.24,"temperatureMin":6.96,"windDirection":20,"windGustSpeedMax":16.74,"windSpeed":6.51,"windSpeedMax":7.11},"overnightForecast":{"forecastStart":"2026-01-22T03:00:00Z","forecastEnd":"2026-01-22T15:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","humidity":0.82,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":12.83,"temperatureMin":8.1,"windDirection":46,"windGustSpeedMax":11.04,"windSpeed":4.22,"windSpeedMax":4.9}},{"forecastStart":"2026-01-22T08:00:00Z","forecastEnd":"2026-01-23T08:00:00Z","conditionCode":"PartlyCloudy","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-22T17:31:08Z","moonset":"2026-01-23T05:39:27Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-22T08:21:11Z","solarNoon":"2026-01-22T20:21:32Z","sunrise":"2026-01-22T15:20:40Z","sunriseCivil":"2026-01-22T14:52:26Z","sunriseNautical":"2026-01-22T14:20:34Z","sunriseAstronomical":"2026-01-22T13:49:21Z","sunset":"2026-01-23T01:22:37Z","sunsetCivil":"2026-01-23T01:50:47Z","sunsetNautical":"2026-01-23T02:22:41Z","sunsetAstronomical":"2026-01-23T02:53:52Z","temperatureMax":13.4,"temperatureMin":8.1,"windGustSpeedMax":19.16,"windSpeedAvg":5.72,"windSpeedMax":8.56,"daytimeForecast":{"forecastStart":"2026-01-22T15:00:00Z","forecastEnd":"2026-01-23T03:00:00Z","cloudCover":0.62,"conditionCode":"PartlyCloudy","humidity":0.77,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.4,"temperatureMin":8.26,"windDirection":329,"windGustSpeedMax":19.16,"windSpeed":6.09,"windSpeedMax":8.56},"overnightForecast":{"forecastStart":"2026-01-23T03:00:00Z","forecastEnd":"2026-01-23T15:00:00Z","cloudCover":0.71,"conditionCode":"MostlyCloudy","humidity":0.84,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":11.65,"temperatureMin":8.49,"windDirection":305,"windGustSpeedMax":17.51,"windSpeed":6.27,"windSpeedMax":7.17}},{"forecastStart":"2026-01-23T08:00:00Z","forecastEnd":"2026-01-24T08:00:00Z","conditionCode":"PartlyCloudy","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-23T17:55:04Z","moonset":"2026-01-24T06:45:58Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-23T08:21:26Z","solarNoon":"2026-01-23T20:21:48Z","sunrise":"2026-01-23T15:20:05Z","sunriseCivil":"2026-01-23T14:51:55Z","sunriseNautical":"2026-01-23T14:20:06Z","sunriseAstronomical":"2026-01-23T13:48:55Z","sunset":"2026-01-24T01:23:43Z","sunsetCivil":"2026-01-24T01:51:49Z","sunsetNautical":"2026-01-24T02:23:41Z","sunsetAstronomical":"2026-01-24T02:54:49Z","temperatureMax":13.01,"temperatureMin":8.49,"windGustSpeedMax":13.85,"windSpeedAvg":6.88,"windSpeedMax":10.62,"daytimeForecast":{"forecastStart":"2026-01-23T15:00:00Z","forecastEnd":"2026-01-24T03:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","humidity":0.78,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.01,"temperatureMin":8.74,"windDirection":303,"windGustSpeedMax":13.85,"windSpeed":5.96,"windSpeedMax":9.31},"overnightForecast":{"forecastStart":"2026-01-24T03:00:00Z","forecastEnd":"2026-01-24T15:00:00Z","cloudCover":0.24,"conditionCode":"MostlyClear","humidity":0.85,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":11.59,"temperatureMin":8.31,"windDirection":263,"windGustSpeedMax":14.96,"windSpeed":9.88,"windSpeedMax":10.77}},{"forecastStart":"2026-01-24T08:00:00Z","forecastEnd":"2026-01-25T08:00:00Z","conditionCode":"MostlyClear","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-24T18:20:13Z","moonset":"2026-01-25T07:54:38Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-24T08:21:41Z","solarNoon":"2026-01-24T20:22:02Z","sunrise":"2026-01-24T15:19:29Z","sunriseCivil":"2026-01-24T14:51:22Z","sunriseNautical":"2026-01-24T14:19:36Z","sunriseAstronomical":"2026-01-24T13:48:28Z","sunset":"2026-01-25T01:24:50Z","sunsetCivil":"2026-01-25T01:52:51Z","sunsetNautical":"2026-01-25T02:24:40Z","sunsetAstronomical":"2026-01-25T02:55:45Z","temperatureMax":12.51,"temperatureMin":8.31,"windGustSpeedMax":21.4,"windSpeedAvg":9.99,"windSpeedMax":13.53,"daytimeForecast":{"forecastStart":"2026-01-24T15:00:00Z","forecastEnd":"2026-01-25T03:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","humidity":0.75,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":12.51,"temperatureMin":8.31,"windDirection":318,"windGustSpeedMax":21.4,"windSpeed":11.5,"windSpeedMax":13.53},"overnightForecast":{"forecastStart":"2026-01-25T03:00:00Z","forecastEnd":"2026-01-25T15:00:00Z","cloudCover":0.41,"conditionCode":"PartlyCloudy","humidity":0.87,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":10.99,"temperatureMin":8.32,"windDirection":170,"windGustSpeedMax":16.36,"windSpeed":7.05,"windSpeedMax":8.54}},{"forecastStart":"2026-01-25T08:00:00Z","forecastEnd":"2026-01-26T08:00:00Z","conditionCode":"PartlyCloudy","maxUvIndex":3,"moonPhase":"waxingCrescent","moonrise":"2026-01-25T18:48:10Z","precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"solarMidnight":"2026-01-25T08:21:55Z","solarNoon":"2026-01-25T20:22:16Z","sunrise":"2026-01-25T15:18:50Z","sunriseCivil":"2026-01-25T14:50:48Z","sunriseNautical":"2026-01-25T14:19:04Z","sunriseAstronomical":"2026-01-25T13:47:59Z","sunset":"2026-01-26T01:25:56Z","sunsetCivil":"2026-01-26T01:53:53Z","sunsetNautical":"2026-01-26T02:25:40Z","sunsetAstronomical":"2026-01-26T02:56:42Z","temperatureMax":13.9,"temperatureMin":8.32,"windGustSpeedMax":15.97,"windSpeedAvg":9.09,"windSpeedMax":11.17,"daytimeForecast":{"forecastStart":"2026-01-25T15:00:00Z","forecastEnd":"2026-01-26T03:00:00Z","cloudCover":0.51,"conditionCode":"PartlyCloudy","humidity":0.78,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":13.9,"temperatureMin":8.74,"windDirection":45,"windGustSpeedMax":15.97,"windSpeed":10.2,"windSpeedMax":11.17},"overnightForecast":{"forecastStart":"2026-01-26T03:00:00Z","forecastEnd":"2026-01-26T15:00:00Z","cloudCover":0.56,"conditionCode":"PartlyCloudy","humidity":0.88,"precipitationAmount":0,"precipitationChance":0,"precipitationType":"clear","snowfallAmount":0,"temperatureMax":12.43,"temperatureMin":9.06,"windDirection":57,"windGustSpeedMax":11.61,"windSpeed":7.45,"windSpeedMax":9.93}}]},"forecastHourly":{"name":"HourlyForecast","metadata":{"attributionURL":"https://developer.apple.com/weatherkit/data-source-attribution/","expireTime":"2026-01-17T01:37:11Z","latitude":37.77,"longitude":-122.42,"readTime":"2026-01-17T00:37:11Z","reportedTime":"2026-01-16T23:05:34Z","units":"m","version":1,"sourceType":"modeled"},"hours":[{"forecastStart":"2026-01-16T06:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.96,"temperatureApparent":11.68,"temperatureDewPoint":8.24,"uvIndex":0,"visibility":25281,"windDirection":5,"windGust":2.65,"windSpeed":0.73},{"forecastStart":"2026-01-16T07:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.43,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.37,"temperatureApparent":11.14,"temperatureDewPoint":7.86,"uvIndex":0,"visibility":24048,"windDirection":182,"windGust":2.79,"windSpeed":0.8},{"forecastStart":"2026-01-16T08:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.53,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.62,"temperatureApparent":10.52,"temperatureDewPoint":7.85,"uvIndex":0,"visibility":22707,"windDirection":357,"windGust":3.88,"windSpeed":0.82},{"forecastStart":"2026-01-16T09:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.5,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.39,"temperatureApparent":10.33,"temperatureDewPoint":7.63,"uvIndex":0,"visibility":25066,"windDirection":200,"windGust":2.5,"windSpeed":0.68},{"forecastStart":"2026-01-16T10:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.59,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.56,"temperatureApparent":9.41,"temperatureDewPoint":7.84,"uvIndex":0,"visibility":18724,"windDirection":9,"windGust":4.97,"windSpeed":2.56},{"forecastStart":"2026-01-16T11:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.9,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.67,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.89,"temperatureApparent":8.97,"temperatureDewPoint":7.34,"uvIndex":0,"visibility":16412,"windDirection":338,"windGust":4.78,"windSpeed":1.75},{"forecastStart":"2026-01-16T12:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.91,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.42,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.94,"temperatureApparent":8.37,"temperatureDewPoint":7.55,"uvIndex":0,"visibility":16605,"windDirection":354,"windGust":7.59,"windSpeed":4.32},{"forecastStart":"2026-01-16T13:00:00Z","cloudCover":0.04,"conditionCode":"Clear","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.3,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.08,"temperatureApparent":8.67,"temperatureDewPoint":7.2,"uvIndex":0,"visibility":19567,"windDirection":336,"windGust":6.45,"windSpeed":3.24},{"forecastStart":"2026-01-16T14:00:00Z","cloudCover":0.06,"conditionCode":"Clear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.79,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.24,"temperatureApparent":9.08,"temperatureDewPoint":6.68,"uvIndex":0,"visibility":20343,"windDirection":339,"windGust":5.93,"windSpeed":2.13},{"forecastStart":"2026-01-16T15:00:00Z","cloudCover":0.3,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.1,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.02,"temperatureApparent":9.47,"temperatureDewPoint":6.97,"uvIndex":0,"visibility":15151,"windDirection":279,"windGust":3.14,"windSpeed":0.99},{"forecastStart":"2026-01-16T16:00:00Z","cloudCover":0.03,"conditionCode":"Clear","daylight":true,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.19,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.71,"temperatureApparent":11.3,"temperatureDewPoint":7.14,"uvIndex":0,"visibility":19853,"windDirection":339,"windGust":5.91,"windSpeed":3.46},{"forecastStart":"2026-01-16T17:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":true,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.69,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.86,"temperatureApparent":15.63,"temperatureDewPoint":8.89,"uvIndex":1,"visibility":21686,"windDirection":245,"windGust":4.28,"windSpeed":1.65},{"forecastStart":"2026-01-16T18:00:00Z","cloudCover":0.01,"conditionCode":"Clear","daylight":true,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.12,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.33,"temperatureApparent":18.29,"temperatureDewPoint":9.35,"uvIndex":1,"visibility":25615,"windDirection":44,"windGust":5.63,"windSpeed":1.31},{"forecastStart":"2026-01-16T19:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.59,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.16,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.21,"temperatureApparent":19.27,"temperatureDewPoint":8.19,"uvIndex":2,"visibility":34519,"windDirection":69,"windGust":9.4,"windSpeed":4.72},{"forecastStart":"2026-01-16T20:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.53,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.71,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.61,"temperatureApparent":20.71,"temperatureDewPoint":7.92,"uvIndex":3,"visibility":35618,"windDirection":81,"windGust":8.53,"windSpeed":4.12},{"forecastStart":"2026-01-16T21:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.44,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.86,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":19.46,"temperatureApparent":22.52,"temperatureDewPoint":6.89,"uvIndex":3,"visibility":38272,"windDirection":75,"windGust":5.78,"windSpeed":3.32},{"forecastStart":"2026-01-16T22:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.4,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.42,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":19.87,"temperatureApparent":22.74,"temperatureDewPoint":5.87,"uvIndex":2,"visibility":40905,"windDirection":59,"windGust":8.74,"windSpeed":3.64},{"forecastStart":"2026-01-16T23:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.45,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.42,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":19.25,"temperatureApparent":21.53,"temperatureDewPoint":7.02,"uvIndex":1,"visibility":35011,"windDirection":3,"windGust":9.57,"windSpeed":6.19},{"forecastStart":"2026-01-17T00:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.58,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.63,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.2,"temperatureApparent":17.49,"temperatureDewPoint":8.86,"uvIndex":0,"visibility":32714,"windDirection":2,"windGust":18.66,"windSpeed":11.53},{"forecastStart":"2026-01-17T01:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":true,"humidity":0.65,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.8,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.13,"temperatureApparent":12.79,"temperatureDewPoint":8.6,"uvIndex":0,"visibility":29392,"windDirection":347,"windGust":17.07,"windSpeed":10.92},{"forecastStart":"2026-01-17T02:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.62,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.69,"temperatureApparent":10.87,"temperatureDewPoint":7.89,"uvIndex":0,"visibility":28074,"windDirection":341,"windGust":16.19,"windSpeed":10.59},{"forecastStart":"2026-01-17T03:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.75,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.15,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.51,"temperatureApparent":9.61,"temperatureDewPoint":8.2,"uvIndex":0,"visibility":25398,"windDirection":342,"windGust":16.85,"windSpeed":10.57},{"forecastStart":"2026-01-17T04:00:00Z","cloudCover":0.01,"conditionCode":"Clear","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.61,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.77,"temperatureApparent":8.75,"temperatureDewPoint":8.06,"uvIndex":0,"visibility":23936,"windDirection":347,"windGust":18.52,"windSpeed":10.89},{"forecastStart":"2026-01-17T05:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.12,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.94,"temperatureApparent":8.22,"temperatureDewPoint":7.99,"uvIndex":0,"visibility":23576,"windDirection":344,"windGust":16.32,"windSpeed":9.92},{"forecastStart":"2026-01-17T06:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.26,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.38,"temperatureApparent":7.61,"temperatureDewPoint":7.79,"uvIndex":0,"visibility":22835,"windDirection":348,"windGust":16.17,"windSpeed":10.07},{"forecastStart":"2026-01-17T07:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.23,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.85,"temperatureApparent":7.02,"temperatureDewPoint":7.62,"uvIndex":0,"visibility":21793,"windDirection":351,"windGust":15.88,"windSpeed":10.2},{"forecastStart":"2026-01-17T08:00:00Z","cloudCover":0.04,"conditionCode":"Clear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.15,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.95,"temperatureApparent":7.52,"temperatureDewPoint":7.02,"uvIndex":0,"visibility":22640,"windDirection":17,"windGust":13.96,"windSpeed":9.07},{"forecastStart":"2026-01-17T09:00:00Z","cloudCover":0.05,"conditionCode":"Clear","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.24,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.4,"temperatureApparent":6.98,"temperatureDewPoint":7.01,"uvIndex":0,"visibility":21858,"windDirection":23,"windGust":14.17,"windSpeed":9.08},{"forecastStart":"2026-01-17T10:00:00Z","cloudCover":0.07,"conditionCode":"Clear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.57,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.01,"temperatureApparent":6.5,"temperatureDewPoint":6.79,"uvIndex":0,"visibility":21783,"windDirection":27,"windGust":14.07,"windSpeed":9.38},{"forecastStart":"2026-01-17T11:00:00Z","cloudCover":0.14,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.66,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.72,"temperatureApparent":6.08,"temperatureDewPoint":6.51,"uvIndex":0,"visibility":21016,"windDirection":26,"windGust":14.98,"windSpeed":9.95},{"forecastStart":"2026-01-17T12:00:00Z","cloudCover":0.24,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.7,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.54,"temperatureApparent":5.59,"temperatureDewPoint":6.5,"uvIndex":0,"visibility":20080,"windDirection":24,"windGust":16.9,"windSpeed":11.06},{"forecastStart":"2026-01-17T13:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.31,"temperatureApparent":5.4,"temperatureDewPoint":6.27,"uvIndex":0,"visibility":19963,"windDirection":26,"windGust":16.98,"windSpeed":11.28},{"forecastStart":"2026-01-17T14:00:00Z","cloudCover":0.42,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.34,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.14,"temperatureApparent":5.4,"temperatureDewPoint":6.11,"uvIndex":0,"visibility":20565,"windDirection":27,"windGust":16.86,"windSpeed":11.08},{"forecastStart":"2026-01-17T15:00:00Z","cloudCover":0.56,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.65,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.04,"temperatureApparent":5.55,"temperatureDewPoint":6.01,"uvIndex":0,"visibility":21167,"windDirection":30,"windGust":16.38,"windSpeed":10.9},{"forecastStart":"2026-01-17T16:00:00Z","cloudCover":0.57,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.03,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.36,"temperatureApparent":6.37,"temperatureDewPoint":6.15,"uvIndex":0,"visibility":18945,"windDirection":35,"windGust":16.9,"windSpeed":11.12},{"forecastStart":"2026-01-17T17:00:00Z","cloudCover":0.58,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.58,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.44,"temperatureApparent":7.94,"temperatureDewPoint":6.7,"uvIndex":1,"visibility":19316,"windDirection":36,"windGust":16.93,"windSpeed":11.17},{"forecastStart":"2026-01-17T18:00:00Z","cloudCover":0.77,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.84,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.98,"temperatureApparent":8.8,"temperatureDewPoint":7.29,"uvIndex":1,"visibility":21518,"windDirection":38,"windGust":16.37,"windSpeed":11.3},{"forecastStart":"2026-01-17T19:00:00Z","cloudCover":0.78,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.83,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.44,"temperatureApparent":10.28,"temperatureDewPoint":7.73,"uvIndex":2,"visibility":23392,"windDirection":36,"windGust":16.75,"windSpeed":11.28},{"forecastStart":"2026-01-17T20:00:00Z","cloudCover":0.77,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.67,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.36,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.1,"temperatureApparent":12.23,"temperatureDewPoint":8.06,"uvIndex":2,"visibility":26378,"windDirection":32,"windGust":17.3,"windSpeed":11.03},{"forecastStart":"2026-01-17T21:00:00Z","cloudCover":0.73,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.62,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.86,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.48,"temperatureApparent":13.83,"temperatureDewPoint":8.23,"uvIndex":2,"visibility":27132,"windDirection":27,"windGust":18.8,"windSpeed":11.17},{"forecastStart":"2026-01-17T22:00:00Z","cloudCover":0.78,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.58,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.48,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.42,"temperatureApparent":14.69,"temperatureDewPoint":8.13,"uvIndex":2,"visibility":28404,"windDirection":26,"windGust":20.39,"windSpeed":11.13},{"forecastStart":"2026-01-17T23:00:00Z","cloudCover":0.76,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.56,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.39,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.88,"temperatureApparent":15.38,"temperatureDewPoint":8.05,"uvIndex":1,"visibility":28630,"windDirection":24,"windGust":20.01,"windSpeed":10.68},{"forecastStart":"2026-01-18T00:00:00Z","cloudCover":0.68,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.57,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.42,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.78,"temperatureApparent":15.67,"temperatureDewPoint":8.21,"uvIndex":0,"visibility":28195,"windDirection":21,"windGust":18.22,"windSpeed":9.93},{"forecastStart":"2026-01-18T01:00:00Z","cloudCover":0.79,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.61,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.7,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.47,"temperatureApparent":14.06,"temperatureDewPoint":7.98,"uvIndex":0,"visibility":27557,"windDirection":20,"windGust":17.83,"windSpeed":9.49},{"forecastStart":"2026-01-18T02:00:00Z","cloudCover":0.74,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.65,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.12,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.36,"temperatureApparent":12.8,"temperatureDewPoint":7.87,"uvIndex":0,"visibility":27266,"windDirection":20,"windGust":16.92,"windSpeed":9.45},{"forecastStart":"2026-01-18T03:00:00Z","cloudCover":0.69,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.5,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.41,"temperatureApparent":11.91,"temperatureDewPoint":7.62,"uvIndex":0,"visibility":27301,"windDirection":21,"windGust":14.77,"windSpeed":8.85},{"forecastStart":"2026-01-18T04:00:00Z","cloudCover":0.85,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.76,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.58,"temperatureApparent":11.22,"temperatureDewPoint":7.46,"uvIndex":0,"visibility":27156,"windDirection":26,"windGust":14.62,"windSpeed":8.91},{"forecastStart":"2026-01-18T05:00:00Z","cloudCover":0.87,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.95,"temperatureApparent":10.78,"temperatureDewPoint":7.26,"uvIndex":0,"visibility":26528,"windDirection":26,"windGust":13.29,"windSpeed":8.33},{"forecastStart":"2026-01-18T06:00:00Z","cloudCover":0.89,"conditionCode":"Cloudy","daylight":false,"humidity":0.75,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.25,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.47,"temperatureApparent":10.34,"temperatureDewPoint":7.19,"uvIndex":0,"visibility":26125,"windDirection":29,"windGust":13.6,"windSpeed":8.33},{"forecastStart":"2026-01-18T07:00:00Z","cloudCover":0.87,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.4,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.01,"temperatureApparent":10.01,"temperatureDewPoint":6.94,"uvIndex":0,"visibility":24304,"windDirection":30,"windGust":13.12,"windSpeed":7.95},{"forecastStart":"2026-01-18T08:00:00Z","cloudCover":0.83,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.42,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.59,"temperatureApparent":9.4,"temperatureDewPoint":6.91,"uvIndex":0,"visibility":22807,"windDirection":32,"windGust":14.07,"windSpeed":8.35},{"forecastStart":"2026-01-18T09:00:00Z","cloudCover":0.78,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.42,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.22,"temperatureApparent":8.89,"temperatureDewPoint":6.74,"uvIndex":0,"visibility":22647,"windDirection":32,"windGust":14.04,"windSpeed":8.59},{"forecastStart":"2026-01-18T10:00:00Z","cloudCover":0.82,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.55,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.85,"temperatureApparent":8.61,"temperatureDewPoint":6.56,"uvIndex":0,"visibility":22592,"windDirection":33,"windGust":13.99,"windSpeed":8.49},{"forecastStart":"2026-01-18T11:00:00Z","cloudCover":0.86,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.34,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.54,"temperatureApparent":8.16,"temperatureDewPoint":6.62,"uvIndex":0,"visibility":23167,"windDirection":29,"windGust":13.28,"windSpeed":9.03},{"forecastStart":"2026-01-18T12:00:00Z","cloudCover":0.86,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.05,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.33,"temperatureApparent":7.71,"temperatureDewPoint":6.59,"uvIndex":0,"visibility":22506,"windDirection":33,"windGust":14.22,"windSpeed":9.66},{"forecastStart":"2026-01-18T13:00:00Z","cloudCover":0.84,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.98,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.21,"temperatureApparent":7.5,"temperatureDewPoint":6.65,"uvIndex":0,"visibility":22218,"windDirection":35,"windGust":13.78,"windSpeed":9.83},{"forecastStart":"2026-01-18T14:00:00Z","cloudCover":0.85,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.17,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.08,"temperatureApparent":7.3,"temperatureDewPoint":6.52,"uvIndex":0,"visibility":21996,"windDirection":35,"windGust":14.57,"windSpeed":10.05},{"forecastStart":"2026-01-18T15:00:00Z","cloudCover":0.78,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.39,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.19,"temperatureApparent":7.34,"temperatureDewPoint":6.63,"uvIndex":0,"visibility":22507,"windDirection":35,"windGust":14.86,"windSpeed":9.98},{"forecastStart":"2026-01-18T16:00:00Z","cloudCover":0.6,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.49,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.58,"temperatureApparent":8.1,"temperatureDewPoint":6.66,"uvIndex":0,"visibility":23001,"windDirection":38,"windGust":14.49,"windSpeed":9.73},{"forecastStart":"2026-01-18T17:00:00Z","cloudCover":0.55,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.72,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.57,"temperatureApparent":9.93,"temperatureDewPoint":7.08,"uvIndex":1,"visibility":25449,"windDirection":42,"windGust":15.2,"windSpeed":9.09},{"forecastStart":"2026-01-18T18:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":true,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.81,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.15,"temperatureApparent":12.33,"temperatureDewPoint":7.45,"uvIndex":1,"visibility":26640,"windDirection":42,"windGust":15.01,"windSpeed":9.21},{"forecastStart":"2026-01-18T19:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":true,"humidity":0.66,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.57,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.9,"temperatureApparent":14.42,"temperatureDewPoint":7.65,"uvIndex":2,"visibility":27682,"windDirection":48,"windGust":15.8,"windSpeed":9.11},{"forecastStart":"2026-01-18T20:00:00Z","cloudCover":0.17,"conditionCode":"MostlyClear","daylight":true,"humidity":0.59,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.81,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.65,"temperatureApparent":16.41,"temperatureDewPoint":7.66,"uvIndex":3,"visibility":28854,"windDirection":41,"windGust":17.27,"windSpeed":9.53},{"forecastStart":"2026-01-18T21:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":true,"humidity":0.54,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.03,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.97,"temperatureApparent":18.03,"temperatureDewPoint":7.6,"uvIndex":3,"visibility":29576,"windDirection":32,"windGust":19.32,"windSpeed":9.45},{"forecastStart":"2026-01-18T22:00:00Z","cloudCover":0.18,"conditionCode":"MostlyClear","daylight":true,"humidity":0.51,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.52,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.8,"temperatureApparent":18.7,"temperatureDewPoint":7.53,"uvIndex":2,"visibility":29821,"windDirection":27,"windGust":20.16,"windSpeed":9.03},{"forecastStart":"2026-01-18T23:00:00Z","cloudCover":0.05,"conditionCode":"Clear","daylight":true,"humidity":0.5,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.22,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":18.18,"temperatureApparent":19.54,"temperatureDewPoint":7.59,"uvIndex":1,"visibility":29869,"windDirection":21,"windGust":19.28,"windSpeed":8.49},{"forecastStart":"2026-01-19T00:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":true,"humidity":0.5,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.01,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.86,"temperatureApparent":18.68,"temperatureDewPoint":7.29,"uvIndex":0,"visibility":29687,"windDirection":9,"windGust":18.48,"windSpeed":7.99},{"forecastStart":"2026-01-19T01:00:00Z","cloudCover":0.09,"conditionCode":"Clear","daylight":true,"humidity":0.53,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.94,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.67,"temperatureApparent":15.43,"temperatureDewPoint":7.04,"uvIndex":0,"visibility":29314,"windDirection":0,"windGust":17.16,"windSpeed":7.43},{"forecastStart":"2026-01-19T02:00:00Z","cloudCover":0.03,"conditionCode":"Clear","daylight":false,"humidity":0.59,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.24,"temperatureApparent":13.58,"temperatureDewPoint":7.28,"uvIndex":0,"visibility":28859,"windDirection":0,"windGust":15.11,"windSpeed":6.78},{"forecastStart":"2026-01-19T03:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.64,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.04,"temperatureApparent":12.55,"temperatureDewPoint":7.33,"uvIndex":0,"visibility":28557,"windDirection":3,"windGust":13.01,"windSpeed":6.04},{"forecastStart":"2026-01-19T04:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.96,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.14,"temperatureApparent":11.76,"temperatureDewPoint":7.36,"uvIndex":0,"visibility":27881,"windDirection":11,"windGust":11.88,"windSpeed":5.61},{"forecastStart":"2026-01-19T05:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.97,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.36,"temperatureApparent":11.06,"temperatureDewPoint":7.45,"uvIndex":0,"visibility":27407,"windDirection":17,"windGust":11.11,"windSpeed":5.26},{"forecastStart":"2026-01-19T06:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.01,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.67,"temperatureApparent":10.49,"temperatureDewPoint":7.19,"uvIndex":0,"visibility":27084,"windDirection":26,"windGust":10.39,"windSpeed":4.96},{"forecastStart":"2026-01-19T07:00:00Z","cloudCover":0.08,"conditionCode":"Clear","daylight":false,"humidity":0.77,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.01,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.89,"temperatureApparent":9.97,"temperatureDewPoint":7.02,"uvIndex":0,"visibility":26394,"windDirection":33,"windGust":9.21,"windSpeed":4.52},{"forecastStart":"2026-01-19T08:00:00Z","cloudCover":0.17,"conditionCode":"MostlyClear","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.89,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.28,"temperatureApparent":9.64,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":25832,"windDirection":42,"windGust":8.07,"windSpeed":4.15},{"forecastStart":"2026-01-19T09:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.76,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.76,"temperatureApparent":9.26,"temperatureDewPoint":6.83,"uvIndex":0,"visibility":24877,"windDirection":51,"windGust":7.75,"windSpeed":4.24},{"forecastStart":"2026-01-19T10:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.66,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.32,"temperatureApparent":8.75,"temperatureDewPoint":6.58,"uvIndex":0,"visibility":22331,"windDirection":56,"windGust":7.8,"windSpeed":4.59},{"forecastStart":"2026-01-19T11:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.43,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.97,"temperatureApparent":8.31,"temperatureDewPoint":6.41,"uvIndex":0,"visibility":21652,"windDirection":54,"windGust":7.97,"windSpeed":4.91},{"forecastStart":"2026-01-19T12:00:00Z","cloudCover":0.29,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.13,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.83,"temperatureApparent":8.15,"temperatureDewPoint":6.62,"uvIndex":0,"visibility":20961,"windDirection":51,"windGust":7.97,"windSpeed":5.11},{"forecastStart":"2026-01-19T13:00:00Z","cloudCover":0.23,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.03,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.41,"temperatureApparent":7.53,"temperatureDewPoint":6.2,"uvIndex":0,"visibility":20134,"windDirection":47,"windGust":8.5,"windSpeed":5.49},{"forecastStart":"2026-01-19T14:00:00Z","cloudCover":0.19,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.08,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.36,"temperatureApparent":7.26,"temperatureDewPoint":6.32,"uvIndex":0,"visibility":19096,"windDirection":44,"windGust":9.02,"windSpeed":5.97},{"forecastStart":"2026-01-19T15:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.23,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.46,"temperatureApparent":7.25,"temperatureDewPoint":6.25,"uvIndex":0,"visibility":17581,"windDirection":42,"windGust":9.44,"windSpeed":6.54},{"forecastStart":"2026-01-19T16:00:00Z","cloudCover":0.3,"conditionCode":"MostlyClear","daylight":true,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.46,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.68,"temperatureApparent":8.44,"temperatureDewPoint":6.13,"uvIndex":0,"visibility":18744,"windDirection":42,"windGust":10.29,"windSpeed":7.37},{"forecastStart":"2026-01-19T17:00:00Z","cloudCover":0.31,"conditionCode":"MostlyClear","daylight":true,"humidity":0.81,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.85,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.55,"temperatureApparent":10.22,"temperatureDewPoint":6.45,"uvIndex":1,"visibility":19891,"windDirection":44,"windGust":10.95,"windSpeed":7.71},{"forecastStart":"2026-01-19T18:00:00Z","cloudCover":0.32,"conditionCode":"MostlyClear","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.16,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.87,"temperatureApparent":11.62,"temperatureDewPoint":6.81,"uvIndex":1,"visibility":24708,"windDirection":42,"windGust":11.26,"windSpeed":7.8},{"forecastStart":"2026-01-19T19:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":true,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.06,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.68,"temperatureApparent":13.27,"temperatureDewPoint":6.93,"uvIndex":2,"visibility":27216,"windDirection":39,"windGust":12.84,"windSpeed":8.6},{"forecastStart":"2026-01-19T20:00:00Z","cloudCover":0.11,"conditionCode":"Clear","daylight":true,"humidity":0.6,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.38,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.58,"temperatureApparent":15.49,"temperatureDewPoint":6.9,"uvIndex":3,"visibility":28895,"windDirection":33,"windGust":15.22,"windSpeed":9.46},{"forecastStart":"2026-01-19T21:00:00Z","cloudCover":0.06,"conditionCode":"Clear","daylight":true,"humidity":0.54,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.63,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.06,"temperatureApparent":17.02,"temperatureDewPoint":6.75,"uvIndex":3,"visibility":30281,"windDirection":27,"windGust":16.44,"windSpeed":9.92},{"forecastStart":"2026-01-19T22:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":true,"humidity":0.51,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.1,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.97,"temperatureApparent":17.79,"temperatureDewPoint":6.76,"uvIndex":2,"visibility":30544,"windDirection":24,"windGust":17.27,"windSpeed":9.93},{"forecastStart":"2026-01-19T23:00:00Z","cloudCover":0.05,"conditionCode":"Clear","daylight":true,"humidity":0.48,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.78,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.38,"temperatureApparent":18.35,"temperatureDewPoint":6.26,"uvIndex":1,"visibility":30235,"windDirection":23,"windGust":17.91,"windSpeed":9.57},{"forecastStart":"2026-01-20T00:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":true,"humidity":0.49,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.59,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.22,"temperatureApparent":17.99,"temperatureDewPoint":6.41,"uvIndex":0,"visibility":29939,"windDirection":20,"windGust":16.55,"windSpeed":8.69},{"forecastStart":"2026-01-20T01:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":true,"humidity":0.52,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.62,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.18,"temperatureApparent":14.85,"temperatureDewPoint":6.31,"uvIndex":0,"visibility":29604,"windDirection":15,"windGust":14.34,"windSpeed":7.54},{"forecastStart":"2026-01-20T02:00:00Z","cloudCover":0.03,"conditionCode":"Clear","daylight":false,"humidity":0.56,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.86,"temperatureApparent":13.01,"temperatureDewPoint":6.16,"uvIndex":0,"visibility":28795,"windDirection":12,"windGust":12.98,"windSpeed":7.14},{"forecastStart":"2026-01-20T03:00:00Z","cloudCover":0.03,"conditionCode":"Clear","daylight":false,"humidity":0.6,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.2,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.69,"temperatureApparent":11.8,"temperatureDewPoint":6.06,"uvIndex":0,"visibility":27947,"windDirection":17,"windGust":12.37,"windSpeed":7.12},{"forecastStart":"2026-01-20T04:00:00Z","cloudCover":0.03,"conditionCode":"Clear","daylight":false,"humidity":0.64,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.45,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.71,"temperatureApparent":10.68,"temperatureDewPoint":6.07,"uvIndex":0,"visibility":27449,"windDirection":21,"windGust":13.02,"windSpeed":7.45},{"forecastStart":"2026-01-20T05:00:00Z","cloudCover":0.04,"conditionCode":"Clear","daylight":false,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.69,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.64,"temperatureApparent":9.51,"temperatureDewPoint":5.93,"uvIndex":0,"visibility":27330,"windDirection":26,"windGust":13.61,"windSpeed":7.72},{"forecastStart":"2026-01-20T06:00:00Z","cloudCover":0.04,"conditionCode":"Clear","daylight":false,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.92,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.84,"temperatureApparent":8.69,"temperatureDewPoint":5.99,"uvIndex":0,"visibility":26377,"windDirection":30,"windGust":13.95,"windSpeed":7.92},{"forecastStart":"2026-01-20T07:00:00Z","cloudCover":0.01,"conditionCode":"Clear","daylight":false,"humidity":0.75,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.65,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.99,"temperatureApparent":7.81,"temperatureDewPoint":5.76,"uvIndex":0,"visibility":27658,"windDirection":29,"windGust":14.76,"windSpeed":7.95},{"forecastStart":"2026-01-20T08:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.69,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.39,"temperatureApparent":7.26,"temperatureDewPoint":5.75,"uvIndex":0,"visibility":26837,"windDirection":32,"windGust":14.44,"windSpeed":7.91},{"forecastStart":"2026-01-20T09:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.67,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.86,"temperatureApparent":6.63,"temperatureDewPoint":5.6,"uvIndex":0,"visibility":25655,"windDirection":35,"windGust":14.58,"windSpeed":8.21},{"forecastStart":"2026-01-20T10:00:00Z","cloudCover":0.05,"conditionCode":"Clear","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.64,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.39,"temperatureApparent":6.09,"temperatureDewPoint":5.67,"uvIndex":0,"visibility":23659,"windDirection":36,"windGust":15.14,"windSpeed":8.54},{"forecastStart":"2026-01-20T11:00:00Z","cloudCover":0.08,"conditionCode":"Clear","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.54,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.01,"temperatureApparent":5.55,"temperatureDewPoint":5.64,"uvIndex":0,"visibility":21303,"windDirection":38,"windGust":16.04,"windSpeed":9.07},{"forecastStart":"2026-01-20T12:00:00Z","cloudCover":0.11,"conditionCode":"Clear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.5,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.74,"temperatureApparent":5.2,"temperatureDewPoint":5.55,"uvIndex":0,"visibility":19499,"windDirection":41,"windGust":16.49,"windSpeed":9.39},{"forecastStart":"2026-01-20T13:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.58,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.54,"temperatureApparent":4.96,"temperatureDewPoint":5.52,"uvIndex":0,"visibility":18464,"windDirection":44,"windGust":16.21,"windSpeed":9.52},{"forecastStart":"2026-01-20T14:00:00Z","cloudCover":0.13,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.77,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.43,"temperatureApparent":4.85,"temperatureDewPoint":5.41,"uvIndex":0,"visibility":17980,"windDirection":47,"windGust":15.64,"windSpeed":9.54},{"forecastStart":"2026-01-20T15:00:00Z","cloudCover":0.14,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.06,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.95,"temperatureApparent":5.41,"temperatureDewPoint":5.75,"uvIndex":0,"visibility":18313,"windDirection":50,"windGust":15.26,"windSpeed":9.45},{"forecastStart":"2026-01-20T16:00:00Z","cloudCover":0.16,"conditionCode":"MostlyClear","daylight":true,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.45,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.37,"temperatureApparent":7.44,"temperatureDewPoint":5.82,"uvIndex":0,"visibility":19824,"windDirection":51,"windGust":15.29,"windSpeed":9.48},{"forecastStart":"2026-01-20T17:00:00Z","cloudCover":0.17,"conditionCode":"MostlyClear","daylight":true,"humidity":0.81,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.85,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.12,"temperatureApparent":9.63,"temperatureDewPoint":6.03,"uvIndex":1,"visibility":22154,"windDirection":51,"windGust":15.42,"windSpeed":9.37},{"forecastStart":"2026-01-20T18:00:00Z","cloudCover":0.17,"conditionCode":"MostlyClear","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.03,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.19,"temperatureApparent":11.03,"temperatureDewPoint":6.15,"uvIndex":1,"visibility":24487,"windDirection":50,"windGust":15.53,"windSpeed":9.15},{"forecastStart":"2026-01-20T19:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.78,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.79,"temperatureApparent":12.7,"temperatureDewPoint":6.7,"uvIndex":2,"visibility":26742,"windDirection":47,"windGust":16.28,"windSpeed":9.03},{"forecastStart":"2026-01-20T20:00:00Z","cloudCover":0.06,"conditionCode":"Clear","daylight":true,"humidity":0.65,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.15,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.68,"temperatureApparent":14.93,"temperatureDewPoint":7.22,"uvIndex":3,"visibility":28998,"windDirection":41,"windGust":17.42,"windSpeed":8.85},{"forecastStart":"2026-01-20T21:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":true,"humidity":0.6,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.54,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.23,"temperatureApparent":16.75,"temperatureDewPoint":7.51,"uvIndex":3,"visibility":30680,"windDirection":35,"windGust":18.28,"windSpeed":8.56},{"forecastStart":"2026-01-20T22:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":true,"humidity":0.56,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.15,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.31,"temperatureApparent":18.02,"temperatureDewPoint":7.51,"uvIndex":2,"visibility":31487,"windDirection":27,"windGust":18.66,"windSpeed":7.94},{"forecastStart":"2026-01-20T23:00:00Z","cloudCover":0.04,"conditionCode":"Clear","daylight":true,"humidity":0.52,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.86,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.06,"temperatureApparent":18.71,"temperatureDewPoint":7.13,"uvIndex":1,"visibility":31721,"windDirection":20,"windGust":18.87,"windSpeed":7.46},{"forecastStart":"2026-01-21T00:00:00Z","cloudCover":0.06,"conditionCode":"Clear","daylight":true,"humidity":0.51,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.71,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":17.21,"temperatureApparent":18.42,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":31712,"windDirection":14,"windGust":18.44,"windSpeed":6.98},{"forecastStart":"2026-01-21T01:00:00Z","cloudCover":0.08,"conditionCode":"Clear","daylight":true,"humidity":0.52,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.76,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.38,"temperatureApparent":15.33,"temperatureDewPoint":6.5,"uvIndex":0,"visibility":31503,"windDirection":11,"windGust":17.25,"windSpeed":6.99},{"forecastStart":"2026-01-21T02:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":false,"humidity":0.55,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.04,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.05,"temperatureApparent":13.35,"temperatureDewPoint":6.08,"uvIndex":0,"visibility":31051,"windDirection":14,"windGust":15.12,"windSpeed":6.9},{"forecastStart":"2026-01-21T03:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.58,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.31,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.82,"temperatureApparent":12.11,"temperatureDewPoint":5.7,"uvIndex":0,"visibility":30557,"windDirection":17,"windGust":13.61,"windSpeed":6.78},{"forecastStart":"2026-01-21T04:00:00Z","cloudCover":0.12,"conditionCode":"Clear","daylight":false,"humidity":0.61,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.52,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.85,"temperatureApparent":11.23,"temperatureDewPoint":5.51,"uvIndex":0,"visibility":30195,"windDirection":21,"windGust":12.2,"windSpeed":6.38},{"forecastStart":"2026-01-21T05:00:00Z","cloudCover":0.11,"conditionCode":"Clear","daylight":false,"humidity":0.65,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.69,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.95,"temperatureApparent":10.43,"temperatureDewPoint":5.57,"uvIndex":0,"visibility":29791,"windDirection":29,"windGust":10.98,"windSpeed":5.99},{"forecastStart":"2026-01-21T06:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":false,"humidity":0.69,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.81,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.14,"temperatureApparent":9.68,"temperatureDewPoint":5.66,"uvIndex":0,"visibility":29023,"windDirection":33,"windGust":10.43,"windSpeed":5.95},{"forecastStart":"2026-01-21T07:00:00Z","cloudCover":0.09,"conditionCode":"Clear","daylight":false,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.9,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.11,"temperatureApparent":8.74,"temperatureDewPoint":5.29,"uvIndex":0,"visibility":27595,"windDirection":39,"windGust":10.03,"windSpeed":5.79},{"forecastStart":"2026-01-21T08:00:00Z","cloudCover":0.07,"conditionCode":"Clear","daylight":false,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.95,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.46,"temperatureApparent":8.11,"temperatureDewPoint":5.06,"uvIndex":0,"visibility":25805,"windDirection":42,"windGust":9.88,"windSpeed":5.77},{"forecastStart":"2026-01-21T09:00:00Z","cloudCover":0.06,"conditionCode":"Clear","daylight":false,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.87,"temperatureApparent":7.46,"temperatureDewPoint":4.87,"uvIndex":0,"visibility":24225,"windDirection":42,"windGust":10.22,"windSpeed":6},{"forecastStart":"2026-01-21T10:00:00Z","cloudCover":0.07,"conditionCode":"Clear","daylight":false,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.89,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.29,"temperatureApparent":6.87,"temperatureDewPoint":4.86,"uvIndex":0,"visibility":23260,"windDirection":42,"windGust":10.7,"windSpeed":6.13},{"forecastStart":"2026-01-21T11:00:00Z","cloudCover":0.08,"conditionCode":"Clear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.81,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.77,"temperatureApparent":6.32,"temperatureDewPoint":4.89,"uvIndex":0,"visibility":22502,"windDirection":41,"windGust":11.53,"windSpeed":6.35},{"forecastStart":"2026-01-21T12:00:00Z","cloudCover":0.1,"conditionCode":"Clear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.79,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.37,"temperatureApparent":5.86,"temperatureDewPoint":4.84,"uvIndex":0,"visibility":21302,"windDirection":41,"windGust":12.22,"windSpeed":6.63},{"forecastStart":"2026-01-21T13:00:00Z","cloudCover":0.16,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.9,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.06,"temperatureApparent":5.63,"temperatureDewPoint":4.88,"uvIndex":0,"visibility":18790,"windDirection":41,"windGust":12.1,"windSpeed":6.68},{"forecastStart":"2026-01-21T14:00:00Z","cloudCover":0.25,"conditionCode":"MostlyClear","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.11,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":6.87,"temperatureApparent":5.49,"temperatureDewPoint":5.02,"uvIndex":0,"visibility":15835,"windDirection":41,"windGust":11.92,"windSpeed":6.88},{"forecastStart":"2026-01-21T15:00:00Z","cloudCover":0.28,"conditionCode":"MostlyClear","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.35,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":6.96,"temperatureApparent":5.55,"temperatureDewPoint":5.11,"uvIndex":0,"visibility":14394,"windDirection":42,"windGust":12.16,"windSpeed":7.08},{"forecastStart":"2026-01-21T16:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":true,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.69,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":7.37,"temperatureApparent":7.36,"temperatureDewPoint":5.18,"uvIndex":0,"visibility":15702,"windDirection":42,"windGust":12.72,"windSpeed":7.03},{"forecastStart":"2026-01-21T17:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":true,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.04,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.05,"temperatureApparent":9.18,"temperatureDewPoint":5.34,"uvIndex":1,"visibility":18530,"windDirection":42,"windGust":13.77,"windSpeed":7.07},{"forecastStart":"2026-01-21T18:00:00Z","cloudCover":0.31,"conditionCode":"MostlyClear","daylight":true,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.18,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.06,"temperatureApparent":10.2,"temperatureDewPoint":5.61,"uvIndex":2,"visibility":21149,"windDirection":41,"windGust":14.67,"windSpeed":7.11},{"forecastStart":"2026-01-21T19:00:00Z","cloudCover":0.28,"conditionCode":"MostlyClear","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.89,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.94,"temperatureApparent":12.09,"temperatureDewPoint":6.49,"uvIndex":2,"visibility":22907,"windDirection":36,"windGust":15.53,"windSpeed":7.08},{"forecastStart":"2026-01-21T20:00:00Z","cloudCover":0.23,"conditionCode":"MostlyClear","daylight":true,"humidity":0.68,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.36,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.7,"temperatureApparent":13.95,"temperatureDewPoint":6.94,"uvIndex":3,"visibility":24459,"windDirection":30,"windGust":16.34,"windSpeed":6.99},{"forecastStart":"2026-01-21T21:00:00Z","cloudCover":0.2,"conditionCode":"MostlyClear","daylight":true,"humidity":0.64,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.86,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.15,"temperatureApparent":15.56,"temperatureDewPoint":7.44,"uvIndex":3,"visibility":26033,"windDirection":23,"windGust":16.74,"windSpeed":6.85},{"forecastStart":"2026-01-21T22:00:00Z","cloudCover":0.21,"conditionCode":"MostlyClear","daylight":true,"humidity":0.6,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.43,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.23,"temperatureApparent":16.67,"temperatureDewPoint":7.51,"uvIndex":2,"visibility":27965,"windDirection":14,"windGust":16.53,"windSpeed":6.64},{"forecastStart":"2026-01-21T23:00:00Z","cloudCover":0.22,"conditionCode":"MostlyClear","daylight":true,"humidity":0.58,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.04,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.01,"temperatureApparent":17.41,"temperatureDewPoint":7.75,"uvIndex":1,"visibility":29914,"windDirection":3,"windGust":15.91,"windSpeed":6.38},{"forecastStart":"2026-01-22T00:00:00Z","cloudCover":0.25,"conditionCode":"MostlyClear","daylight":true,"humidity":0.57,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.83,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":16.21,"temperatureApparent":17.25,"temperatureDewPoint":7.68,"uvIndex":0,"visibility":31104,"windDirection":354,"windGust":15.03,"windSpeed":6.06},{"forecastStart":"2026-01-22T01:00:00Z","cloudCover":0.3,"conditionCode":"MostlyClear","daylight":true,"humidity":0.59,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":15.44,"temperatureApparent":14.91,"temperatureDewPoint":7.46,"uvIndex":0,"visibility":31045,"windDirection":348,"windGust":13.77,"windSpeed":5.71},{"forecastStart":"2026-01-22T02:00:00Z","cloudCover":0.35,"conditionCode":"MostlyClear","daylight":false,"humidity":0.63,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.18,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":14.07,"temperatureApparent":13.18,"temperatureDewPoint":7.13,"uvIndex":0,"visibility":30229,"windDirection":342,"windGust":12.24,"windSpeed":5.3},{"forecastStart":"2026-01-22T03:00:00Z","cloudCover":0.38,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.67,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.42,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.83,"temperatureApparent":12.09,"temperatureDewPoint":6.85,"uvIndex":0,"visibility":29353,"windDirection":339,"windGust":11.04,"windSpeed":4.8},{"forecastStart":"2026-01-22T04:00:00Z","cloudCover":0.38,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.7,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.59,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.95,"temperatureApparent":11.43,"temperatureDewPoint":6.65,"uvIndex":0,"visibility":28661,"windDirection":347,"windGust":10.44,"windSpeed":4.02},{"forecastStart":"2026-01-22T05:00:00Z","cloudCover":0.36,"conditionCode":"MostlyClear","daylight":false,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.75,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.19,"temperatureApparent":10.96,"temperatureDewPoint":6.53,"uvIndex":0,"visibility":27909,"windDirection":11,"windGust":10.16,"windSpeed":3.15},{"forecastStart":"2026-01-22T06:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":false,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.83,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.27,"temperatureApparent":10.2,"temperatureDewPoint":6.22,"uvIndex":0,"visibility":27054,"windDirection":39,"windGust":9.96,"windSpeed":2.73},{"forecastStart":"2026-01-22T07:00:00Z","cloudCover":0.39,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.82,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.68,"temperatureApparent":9.64,"temperatureDewPoint":6.4,"uvIndex":0,"visibility":26223,"windDirection":50,"windGust":9.8,"windSpeed":3.15},{"forecastStart":"2026-01-22T08:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.73,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.19,"temperatureApparent":9.05,"temperatureDewPoint":6.45,"uvIndex":0,"visibility":25289,"windDirection":50,"windGust":9.71,"windSpeed":4.02},{"forecastStart":"2026-01-22T09:00:00Z","cloudCover":0.5,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.59,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.06,"temperatureApparent":8.8,"temperatureDewPoint":6.67,"uvIndex":0,"visibility":23825,"windDirection":51,"windGust":9.67,"windSpeed":4.68},{"forecastStart":"2026-01-22T10:00:00Z","cloudCover":0.51,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.36,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.69,"temperatureApparent":8.4,"temperatureDewPoint":6.65,"uvIndex":0,"visibility":21214,"windDirection":53,"windGust":9.63,"windSpeed":4.89},{"forecastStart":"2026-01-22T11:00:00Z","cloudCover":0.52,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.08,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.41,"temperatureApparent":8.15,"temperatureDewPoint":6.7,"uvIndex":0,"visibility":18067,"windDirection":54,"windGust":9.62,"windSpeed":4.9},{"forecastStart":"2026-01-22T12:00:00Z","cloudCover":0.52,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.93,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.25,"temperatureApparent":8.01,"temperatureDewPoint":6.54,"uvIndex":0,"visibility":15812,"windDirection":57,"windGust":9.72,"windSpeed":4.87},{"forecastStart":"2026-01-22T13:00:00Z","cloudCover":0.52,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.97,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.15,"temperatureApparent":7.93,"temperatureDewPoint":6.45,"uvIndex":0,"visibility":14785,"windDirection":65,"windGust":9.87,"windSpeed":4.83},{"forecastStart":"2026-01-22T14:00:00Z","cloudCover":0.52,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.14,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.11,"temperatureApparent":7.92,"temperatureDewPoint":6.24,"uvIndex":0,"visibility":14306,"windDirection":72,"windGust":10.02,"windSpeed":4.72},{"forecastStart":"2026-01-22T15:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.38,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.26,"temperatureApparent":8.13,"temperatureDewPoint":6.22,"uvIndex":0,"visibility":14279,"windDirection":75,"windGust":10.3,"windSpeed":4.54},{"forecastStart":"2026-01-22T16:00:00Z","cloudCover":0.55,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.71,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.6,"temperatureApparent":9.36,"temperatureDewPoint":6.39,"uvIndex":0,"visibility":14610,"windDirection":71,"windGust":10.77,"windSpeed":4.13},{"forecastStart":"2026-01-22T17:00:00Z","cloudCover":0.58,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.02,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.09,"temperatureApparent":10.43,"temperatureDewPoint":6.35,"uvIndex":1,"visibility":15201,"windDirection":62,"windGust":11.39,"windSpeed":3.65},{"forecastStart":"2026-01-22T18:00:00Z","cloudCover":0.6,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.81,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.09,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.65,"temperatureApparent":10.96,"temperatureDewPoint":6.55,"uvIndex":1,"visibility":15950,"windDirection":51,"windGust":12.09,"windSpeed":3.57},{"forecastStart":"2026-01-22T19:00:00Z","cloudCover":0.62,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.79,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.39,"temperatureApparent":11.33,"temperatureDewPoint":6.72,"uvIndex":2,"visibility":17166,"windDirection":35,"windGust":12.96,"windSpeed":4.21},{"forecastStart":"2026-01-22T20:00:00Z","cloudCover":0.63,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.24,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.32,"temperatureApparent":11.77,"temperatureDewPoint":7.24,"uvIndex":3,"visibility":18988,"windDirection":6,"windGust":14.12,"windSpeed":5.28},{"forecastStart":"2026-01-22T21:00:00Z","cloudCover":0.64,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.68,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.84,"temperatureApparent":11.84,"temperatureDewPoint":7.35,"uvIndex":2,"visibility":21062,"windDirection":338,"windGust":15.52,"windSpeed":6.32},{"forecastStart":"2026-01-22T22:00:00Z","cloudCover":0.63,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.19,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.82,"temperatureApparent":12.58,"temperatureDewPoint":7.9,"uvIndex":2,"visibility":23035,"windDirection":318,"windGust":17.06,"windSpeed":7.24},{"forecastStart":"2026-01-22T23:00:00Z","cloudCover":0.62,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.76,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.26,"temperatureApparent":12.77,"temperatureDewPoint":8.11,"uvIndex":1,"visibility":24557,"windDirection":303,"windGust":18.41,"windSpeed":8.1},{"forecastStart":"2026-01-23T00:00:00Z","cloudCover":0.63,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.47,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.39,"temperatureApparent":12.62,"temperatureDewPoint":8.24,"uvIndex":0,"visibility":25276,"windDirection":296,"windGust":19.16,"windSpeed":8.56},{"forecastStart":"2026-01-23T01:00:00Z","cloudCover":0.65,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.38,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.02,"temperatureApparent":11.78,"temperatureDewPoint":8.09,"uvIndex":0,"visibility":25093,"windDirection":293,"windGust":19.16,"windSpeed":8.38},{"forecastStart":"2026-01-23T02:00:00Z","cloudCover":0.68,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.75,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.4,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.36,"temperatureApparent":11.14,"temperatureDewPoint":8.05,"uvIndex":0,"visibility":24270,"windDirection":293,"windGust":18.59,"windSpeed":7.79},{"forecastStart":"2026-01-23T03:00:00Z","cloudCover":0.7,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.77,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.45,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.65,"temperatureApparent":10.67,"temperatureDewPoint":7.75,"uvIndex":0,"visibility":22996,"windDirection":291,"windGust":17.51,"windSpeed":7.17},{"forecastStart":"2026-01-23T04:00:00Z","cloudCover":0.72,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.47,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.03,"temperatureApparent":10.3,"temperatureDewPoint":7.53,"uvIndex":0,"visibility":21461,"windDirection":287,"windGust":16,"windSpeed":6.62},{"forecastStart":"2026-01-23T05:00:00Z","cloudCover":0.72,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.81,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.45,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.47,"temperatureApparent":9.94,"temperatureDewPoint":7.35,"uvIndex":0,"visibility":19858,"windDirection":282,"windGust":14.37,"windSpeed":6.09},{"forecastStart":"2026-01-23T06:00:00Z","cloudCover":0.71,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.37,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.4,"temperatureApparent":9.98,"temperatureDewPoint":7.46,"uvIndex":0,"visibility":18376,"windDirection":278,"windGust":13.1,"windSpeed":5.8},{"forecastStart":"2026-01-23T07:00:00Z","cloudCover":0.69,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.21,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.81,"temperatureApparent":9.35,"temperatureDewPoint":7.06,"uvIndex":0,"visibility":16860,"windDirection":278,"windGust":12.27,"windSpeed":5.89},{"forecastStart":"2026-01-23T08:00:00Z","cloudCover":0.66,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.55,"temperatureApparent":8.95,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":15128,"windDirection":281,"windGust":11.62,"windSpeed":6.2},{"forecastStart":"2026-01-23T09:00:00Z","cloudCover":0.66,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.78,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.31,"temperatureApparent":8.63,"temperatureDewPoint":6.92,"uvIndex":0,"visibility":13333,"windDirection":288,"windGust":11.11,"windSpeed":6.48},{"forecastStart":"2026-01-23T10:00:00Z","cloudCover":0.68,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.56,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.04,"temperatureApparent":8.33,"temperatureDewPoint":6.82,"uvIndex":0,"visibility":11627,"windDirection":302,"windGust":10.69,"windSpeed":6.66},{"forecastStart":"2026-01-23T11:00:00Z","cloudCover":0.73,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.37,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.74,"temperatureApparent":8.06,"temperatureDewPoint":6.7,"uvIndex":0,"visibility":10162,"windDirection":318,"windGust":10.35,"windSpeed":6.77},{"forecastStart":"2026-01-23T12:00:00Z","cloudCover":0.77,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.24,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.49,"temperatureApparent":7.91,"temperatureDewPoint":6.62,"uvIndex":0,"visibility":9086,"windDirection":330,"windGust":10.01,"windSpeed":6.68},{"forecastStart":"2026-01-23T13:00:00Z","cloudCover":0.7,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.25,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.78,"temperatureApparent":8.26,"temperatureDewPoint":6.9,"uvIndex":0,"visibility":10809,"windDirection":342,"windGust":7.91,"windSpeed":6.27},{"forecastStart":"2026-01-23T14:00:00Z","cloudCover":0.72,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.44,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.67,"temperatureApparent":8.41,"temperatureDewPoint":6.79,"uvIndex":0,"visibility":10815,"windDirection":356,"windGust":8.44,"windSpeed":5.65},{"forecastStart":"2026-01-23T15:00:00Z","cloudCover":0.72,"conditionCode":"MostlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.67,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.74,"temperatureApparent":8.69,"temperatureDewPoint":6.86,"uvIndex":0,"visibility":10822,"windDirection":11,"windGust":8.81,"windSpeed":5.06},{"forecastStart":"2026-01-23T16:00:00Z","cloudCover":0.7,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.99,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.06,"temperatureApparent":9.49,"temperatureDewPoint":6.84,"uvIndex":0,"visibility":10829,"windDirection":27,"windGust":8.93,"windSpeed":4.56},{"forecastStart":"2026-01-23T17:00:00Z","cloudCover":0.66,"conditionCode":"MostlyCloudy","daylight":true,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.32,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.34,"temperatureApparent":10.23,"temperatureDewPoint":6.77,"uvIndex":1,"visibility":11555,"windDirection":41,"windGust":8.95,"windSpeed":4.15},{"forecastStart":"2026-01-23T18:00:00Z","cloudCover":0.61,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.42,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.95,"temperatureApparent":11.09,"temperatureDewPoint":7.02,"uvIndex":1,"visibility":12422,"windDirection":44,"windGust":9.04,"windSpeed":3.95},{"forecastStart":"2026-01-23T19:00:00Z","cloudCover":0.56,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.15,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.71,"temperatureApparent":11.94,"temperatureDewPoint":7.22,"uvIndex":2,"visibility":13549,"windDirection":30,"windGust":9.24,"windSpeed":3.97},{"forecastStart":"2026-01-23T20:00:00Z","cloudCover":0.49,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.66,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.59,"temperatureApparent":12.88,"temperatureDewPoint":7.5,"uvIndex":3,"visibility":15058,"windDirection":3,"windGust":9.57,"windSpeed":4.22},{"forecastStart":"2026-01-23T21:00:00Z","cloudCover":0.42,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.16,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12,"temperatureApparent":13.32,"temperatureDewPoint":7.51,"uvIndex":3,"visibility":16789,"windDirection":332,"windGust":10.16,"windSpeed":4.76},{"forecastStart":"2026-01-23T22:00:00Z","cloudCover":0.38,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.74,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.52,"temperatureApparent":13.68,"temperatureDewPoint":7.61,"uvIndex":2,"visibility":18577,"windDirection":294,"windGust":10.99,"windSpeed":5.73},{"forecastStart":"2026-01-23T23:00:00Z","cloudCover":0.36,"conditionCode":"MostlyClear","daylight":true,"humidity":0.72,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.37,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.9,"temperatureApparent":13.72,"temperatureDewPoint":7.97,"uvIndex":1,"visibility":20262,"windDirection":270,"windGust":11.84,"windSpeed":6.91},{"forecastStart":"2026-01-24T00:00:00Z","cloudCover":0.32,"conditionCode":"MostlyClear","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.12,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13,"temperatureApparent":13.37,"temperatureDewPoint":7.86,"uvIndex":0,"visibility":21684,"windDirection":263,"windGust":12.56,"windSpeed":7.83},{"forecastStart":"2026-01-24T01:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1013.45,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.71,"temperatureApparent":11.24,"temperatureDewPoint":8.59,"uvIndex":0,"visibility":23024,"windDirection":258,"windGust":13.2,"windSpeed":9.03},{"forecastStart":"2026-01-24T02:00:00Z","cloudCover":0.11,"conditionCode":"Clear","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1013.63,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.16,"temperatureApparent":9.78,"temperatureDewPoint":8.44,"uvIndex":0,"visibility":24450,"windDirection":261,"windGust":13.73,"windSpeed":9.18},{"forecastStart":"2026-01-24T03:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1013.8,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.59,"temperatureApparent":9.03,"temperatureDewPoint":8.26,"uvIndex":0,"visibility":25772,"windDirection":264,"windGust":13.85,"windSpeed":9.31},{"forecastStart":"2026-01-24T04:00:00Z","cloudCover":0,"conditionCode":"Clear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1013.91,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.12,"temperatureApparent":8.49,"temperatureDewPoint":8.16,"uvIndex":0,"visibility":26805,"windDirection":264,"windGust":13.44,"windSpeed":9.51},{"forecastStart":"2026-01-24T05:00:00Z","cloudCover":0.02,"conditionCode":"Clear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1013.99,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.71,"temperatureApparent":8.02,"temperatureDewPoint":8.12,"uvIndex":0,"visibility":27362,"windDirection":263,"windGust":12.79,"windSpeed":9.74},{"forecastStart":"2026-01-24T06:00:00Z","cloudCover":0.08,"conditionCode":"Clear","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.04,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.39,"temperatureApparent":7.68,"temperatureDewPoint":7.98,"uvIndex":0,"visibility":27258,"windDirection":263,"windGust":12.25,"windSpeed":10},{"forecastStart":"2026-01-24T07:00:00Z","cloudCover":0.2,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.07,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.13,"temperatureApparent":7.46,"temperatureDewPoint":7.89,"uvIndex":0,"visibility":26118,"windDirection":264,"windGust":11.76,"windSpeed":10.32},{"forecastStart":"2026-01-24T08:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.08,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.91,"temperatureApparent":7.3,"temperatureDewPoint":7.85,"uvIndex":0,"visibility":24022,"windDirection":266,"windGust":11.23,"windSpeed":10.62},{"forecastStart":"2026-01-24T09:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.09,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.72,"temperatureApparent":7.18,"temperatureDewPoint":7.66,"uvIndex":0,"visibility":21462,"windDirection":267,"windGust":10.98,"windSpeed":10.77},{"forecastStart":"2026-01-24T10:00:00Z","cloudCover":0.41,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.06,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.62,"temperatureApparent":7.03,"temperatureDewPoint":7.39,"uvIndex":0,"visibility":18928,"windDirection":267,"windGust":11.25,"windSpeed":10.75},{"forecastStart":"2026-01-24T11:00:00Z","cloudCover":0.32,"conditionCode":"MostlyClear","daylight":false,"humidity":0.85,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.06,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.45,"temperatureApparent":6.78,"temperatureDewPoint":7.05,"uvIndex":0,"visibility":16908,"windDirection":266,"windGust":11.89,"windSpeed":10.6},{"forecastStart":"2026-01-24T12:00:00Z","cloudCover":0.25,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.2,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.31,"temperatureApparent":6.67,"temperatureDewPoint":6.74,"uvIndex":0,"visibility":15886,"windDirection":264,"windGust":12.6,"windSpeed":10.24},{"forecastStart":"2026-01-24T13:00:00Z","cloudCover":0.28,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1014.55,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.95,"temperatureApparent":6.69,"temperatureDewPoint":6.39,"uvIndex":0,"visibility":15944,"windDirection":261,"windGust":13.24,"windSpeed":9.29},{"forecastStart":"2026-01-24T14:00:00Z","cloudCover":0.31,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.05,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.53,"temperatureApparent":6.75,"temperatureDewPoint":5.98,"uvIndex":0,"visibility":16708,"windDirection":255,"windGust":13.99,"windSpeed":8.12},{"forecastStart":"2026-01-24T15:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1015.65,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.31,"temperatureApparent":6.69,"temperatureDewPoint":5.76,"uvIndex":0,"visibility":17971,"windDirection":243,"windGust":14.96,"windSpeed":7.8},{"forecastStart":"2026-01-24T16:00:00Z","cloudCover":0.39,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1016.41,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.64,"temperatureApparent":7.67,"temperatureDewPoint":5.74,"uvIndex":0,"visibility":19529,"windDirection":264,"windGust":16.23,"windSpeed":9.16},{"forecastStart":"2026-01-24T17:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.81,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.22,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.13,"temperatureApparent":7.96,"temperatureDewPoint":6.04,"uvIndex":1,"visibility":21178,"windDirection":27,"windGust":17.54,"windSpeed":11.37},{"forecastStart":"2026-01-24T18:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.76,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.67,"temperatureApparent":7.92,"temperatureDewPoint":6.2,"uvIndex":2,"visibility":22718,"windDirection":26,"windGust":18.53,"windSpeed":13.01},{"forecastStart":"2026-01-24T19:00:00Z","cloudCover":0.43,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.82,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.29,"temperatureApparent":8.47,"temperatureDewPoint":6.24,"uvIndex":2,"visibility":24368,"windDirection":18,"windGust":19.1,"windSpeed":13.53},{"forecastStart":"2026-01-24T20:00:00Z","cloudCover":0.37,"conditionCode":"MostlyClear","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.61,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.11,"temperatureApparent":9.49,"temperatureDewPoint":6.65,"uvIndex":3,"visibility":26330,"windDirection":351,"windGust":19.55,"windSpeed":13.52},{"forecastStart":"2026-01-24T21:00:00Z","cloudCover":0.32,"conditionCode":"MostlyClear","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.45,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.69,"temperatureApparent":10.3,"temperatureDewPoint":6.61,"uvIndex":3,"visibility":28380,"windDirection":314,"windGust":20.08,"windSpeed":13.35},{"forecastStart":"2026-01-24T22:00:00Z","cloudCover":0.29,"conditionCode":"MostlyClear","daylight":true,"humidity":0.7,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.47,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.12,"temperatureApparent":10.94,"temperatureDewPoint":6.81,"uvIndex":2,"visibility":30296,"windDirection":299,"windGust":20.81,"windSpeed":13.08},{"forecastStart":"2026-01-24T23:00:00Z","cloudCover":0.27,"conditionCode":"MostlyClear","daylight":true,"humidity":0.7,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.6,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.42,"temperatureApparent":11.47,"temperatureDewPoint":7.1,"uvIndex":1,"visibility":31858,"windDirection":293,"windGust":21.4,"windSpeed":12.56},{"forecastStart":"2026-01-25T00:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":true,"humidity":0.7,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1017.83,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.49,"temperatureApparent":11.55,"temperatureDewPoint":7.17,"uvIndex":0,"visibility":32848,"windDirection":290,"windGust":21.37,"windSpeed":11.74},{"forecastStart":"2026-01-25T01:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":true,"humidity":0.71,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.2,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.17,"temperatureApparent":10.15,"temperatureDewPoint":7.07,"uvIndex":0,"visibility":33155,"windDirection":284,"windGust":20.32,"windSpeed":10.4},{"forecastStart":"2026-01-25T02:00:00Z","cloudCover":0.26,"conditionCode":"MostlyClear","daylight":false,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1018.67,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.59,"temperatureApparent":9.46,"temperatureDewPoint":7.11,"uvIndex":0,"visibility":32934,"windDirection":278,"windGust":18.49,"windSpeed":8.74},{"forecastStart":"2026-01-25T03:00:00Z","cloudCover":0.28,"conditionCode":"MostlyClear","daylight":false,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.17,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.99,"temperatureApparent":9.39,"temperatureDewPoint":6.92,"uvIndex":0,"visibility":32373,"windDirection":273,"windGust":16.36,"windSpeed":7.37},{"forecastStart":"2026-01-25T04:00:00Z","cloudCover":0.28,"conditionCode":"MostlyClear","daylight":false,"humidity":0.79,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1019.69,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.47,"temperatureApparent":9.15,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":31653,"windDirection":275,"windGust":14.27,"windSpeed":6.67},{"forecastStart":"2026-01-25T05:00:00Z","cloudCover":0.3,"conditionCode":"MostlyClear","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.21,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.98,"temperatureApparent":8.8,"temperatureDewPoint":7.05,"uvIndex":0,"visibility":30958,"windDirection":279,"windGust":12.45,"windSpeed":6.44},{"forecastStart":"2026-01-25T06:00:00Z","cloudCover":0.31,"conditionCode":"MostlyClear","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.65,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.58,"temperatureApparent":8.47,"temperatureDewPoint":7.01,"uvIndex":0,"visibility":30466,"windDirection":281,"windGust":11.29,"windSpeed":6.37},{"forecastStart":"2026-01-25T07:00:00Z","cloudCover":0.34,"conditionCode":"MostlyClear","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1020.96,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.25,"temperatureApparent":8.21,"temperatureDewPoint":7.03,"uvIndex":0,"visibility":30192,"windDirection":248,"windGust":10.89,"windSpeed":6.33},{"forecastStart":"2026-01-25T08:00:00Z","cloudCover":0.36,"conditionCode":"MostlyClear","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.2,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.93,"temperatureApparent":7.91,"temperatureDewPoint":7.05,"uvIndex":0,"visibility":29994,"windDirection":192,"windGust":10.95,"windSpeed":6.39},{"forecastStart":"2026-01-25T09:00:00Z","cloudCover":0.4,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.41,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.71,"temperatureApparent":7.69,"temperatureDewPoint":6.83,"uvIndex":0,"visibility":29816,"windDirection":173,"windGust":11.23,"windSpeed":6.57},{"forecastStart":"2026-01-25T10:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.9,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.6,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.53,"temperatureApparent":7.45,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":29606,"windDirection":159,"windGust":11.48,"windSpeed":6.9},{"forecastStart":"2026-01-25T11:00:00Z","cloudCover":0.48,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.9,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1021.79,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.4,"temperatureApparent":7.23,"temperatureDewPoint":6.85,"uvIndex":0,"visibility":29310,"windDirection":135,"windGust":11.71,"windSpeed":7.29},{"forecastStart":"2026-01-25T12:00:00Z","cloudCover":0.51,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.91,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.01,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.32,"temperatureApparent":7.06,"temperatureDewPoint":6.94,"uvIndex":0,"visibility":28876,"windDirection":108,"windGust":12.05,"windSpeed":7.63},{"forecastStart":"2026-01-25T13:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.91,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.25,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.36,"temperatureApparent":7.02,"temperatureDewPoint":6.98,"uvIndex":0,"visibility":28170,"windDirection":101,"windGust":12.83,"windSpeed":7.9},{"forecastStart":"2026-01-25T14:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.91,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.52,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.47,"temperatureApparent":7.03,"temperatureDewPoint":7.08,"uvIndex":0,"visibility":27211,"windDirection":101,"windGust":13.98,"windSpeed":8.18},{"forecastStart":"2026-01-25T15:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.9,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1022.85,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":8.74,"temperatureApparent":7.15,"temperatureDewPoint":7.19,"uvIndex":0,"visibility":26171,"windDirection":101,"windGust":15.04,"windSpeed":8.54},{"forecastStart":"2026-01-25T16:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.34,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.24,"temperatureApparent":8.25,"temperatureDewPoint":7.35,"uvIndex":0,"visibility":25222,"windDirection":98,"windGust":15.62,"windSpeed":9.01},{"forecastStart":"2026-01-25T17:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.87,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.95,"temperatureApparent":9.3,"temperatureDewPoint":7.72,"uvIndex":1,"visibility":24531,"windDirection":95,"windGust":15.8,"windSpeed":9.5},{"forecastStart":"2026-01-25T18:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.83,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.18,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.66,"temperatureApparent":9.94,"temperatureDewPoint":7.89,"uvIndex":1,"visibility":24266,"windDirection":90,"windGust":15.87,"windSpeed":9.89},{"forecastStart":"2026-01-25T19:00:00Z","cloudCover":0.54,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.12,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.5,"temperatureApparent":10.65,"temperatureDewPoint":8.17,"uvIndex":2,"visibility":24553,"windDirection":84,"windGust":15.97,"windSpeed":10.1},{"forecastStart":"2026-01-25T20:00:00Z","cloudCover":0.55,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.77,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.84,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.43,"temperatureApparent":11.52,"temperatureDewPoint":8.51,"uvIndex":3,"visibility":25278,"windDirection":72,"windGust":15.97,"windSpeed":10.2},{"forecastStart":"2026-01-25T21:00:00Z","cloudCover":0.56,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.59,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.16,"temperatureApparent":12.23,"temperatureDewPoint":8.63,"uvIndex":3,"visibility":26249,"windDirection":48,"windGust":15.73,"windSpeed":10.31},{"forecastStart":"2026-01-25T22:00:00Z","cloudCover":0.53,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.44,"pressureTrend":"falling","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.61,"temperatureApparent":12.72,"temperatureDewPoint":8.86,"uvIndex":2,"visibility":27275,"windDirection":12,"windGust":15.49,"windSpeed":10.61},{"forecastStart":"2026-01-25T23:00:00Z","cloudCover":0.5,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.33,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.86,"temperatureApparent":12.94,"temperatureDewPoint":9.1,"uvIndex":1,"visibility":28169,"windDirection":350,"windGust":15.35,"windSpeed":11},{"forecastStart":"2026-01-26T00:00:00Z","cloudCover":0.47,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.73,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.3,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.87,"temperatureApparent":12.82,"temperatureDewPoint":9.11,"uvIndex":0,"visibility":28745,"windDirection":347,"windGust":14.99,"windSpeed":11.17},{"forecastStart":"2026-01-26T01:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","daylight":true,"humidity":0.74,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.38,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.56,"temperatureApparent":11.66,"temperatureDewPoint":9.01,"uvIndex":0,"visibility":28971,"windDirection":2,"windGust":14.01,"windSpeed":10.93},{"forecastStart":"2026-01-26T02:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.76,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.53,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":13.01,"temperatureApparent":10.72,"temperatureDewPoint":8.88,"uvIndex":0,"visibility":28991,"windDirection":23,"windGust":12.67,"windSpeed":10.43},{"forecastStart":"2026-01-26T03:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.78,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.7,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":12.43,"temperatureApparent":10.26,"temperatureDewPoint":8.7,"uvIndex":0,"visibility":28887,"windDirection":36,"windGust":11.61,"windSpeed":9.93},{"forecastStart":"2026-01-26T04:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.8,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1023.86,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.91,"temperatureApparent":9.86,"temperatureDewPoint":8.57,"uvIndex":0,"visibility":28739,"windDirection":45,"windGust":11.15,"windSpeed":9.48},{"forecastStart":"2026-01-26T05:00:00Z","cloudCover":0.45,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.82,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.01,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":11.38,"temperatureApparent":9.52,"temperatureDewPoint":8.42,"uvIndex":0,"visibility":28627,"windDirection":53,"windGust":10.95,"windSpeed":9},{"forecastStart":"2026-01-26T06:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.84,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.14,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.91,"temperatureApparent":9.23,"temperatureDewPoint":8.31,"uvIndex":0,"visibility":28630,"windDirection":59,"windGust":10.63,"windSpeed":8.56},{"forecastStart":"2026-01-26T07:00:00Z","cloudCover":0.46,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.86,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.25,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.48,"temperatureApparent":8.98,"temperatureDewPoint":8.24,"uvIndex":0,"visibility":28747,"windDirection":66,"windGust":9.99,"windSpeed":8.09},{"forecastStart":"2026-01-26T08:00:00Z","cloudCover":0.45,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.87,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.34,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":10.08,"temperatureApparent":8.73,"temperatureDewPoint":8.01,"uvIndex":0,"visibility":28916,"windDirection":72,"windGust":9.27,"windSpeed":7.66},{"forecastStart":"2026-01-26T09:00:00Z","cloudCover":0.45,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.38,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.78,"temperatureApparent":8.48,"temperatureDewPoint":7.89,"uvIndex":0,"visibility":29117,"windDirection":77,"windGust":8.83,"windSpeed":7.52},{"forecastStart":"2026-01-26T10:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.88,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.32,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.64,"temperatureApparent":8.22,"temperatureDewPoint":7.75,"uvIndex":0,"visibility":29334,"windDirection":75,"windGust":8.8,"windSpeed":7.84},{"forecastStart":"2026-01-26T11:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.22,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.6,"temperatureApparent":7.96,"temperatureDewPoint":7.88,"uvIndex":0,"visibility":29549,"windDirection":72,"windGust":9.01,"windSpeed":8.44},{"forecastStart":"2026-01-26T12:00:00Z","cloudCover":0.44,"conditionCode":"PartlyCloudy","daylight":false,"humidity":0.89,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.16,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.57,"temperatureApparent":7.67,"temperatureDewPoint":7.85,"uvIndex":0,"visibility":29744,"windDirection":69,"windGust":9.35,"windSpeed":9.14},{"forecastStart":"2026-01-26T13:00:00Z","cloudCover":1,"conditionCode":"Cloudy","daylight":false,"humidity":0.98,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1024.99,"pressureTrend":"steady","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.09,"temperatureApparent":10.32,"temperatureDewPoint":8.79,"uvIndex":0,"visibility":24135,"windDirection":344,"windGust":3.34,"windSpeed":2.57},{"forecastStart":"2026-01-26T14:00:00Z","cloudCover":1,"conditionCode":"Cloudy","daylight":false,"humidity":0.97,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1025.49,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.15,"temperatureApparent":10,"temperatureDewPoint":8.7,"uvIndex":0,"visibility":24135,"windDirection":329,"windGust":4.36,"windSpeed":3.8},{"forecastStart":"2026-01-26T15:00:00Z","cloudCover":1,"conditionCode":"Cloudy","daylight":false,"humidity":0.94,"precipitationAmount":0,"precipitationIntensity":0,"precipitationChance":0,"precipitationType":"clear","pressure":1025.91,"pressureTrend":"rising","snowfallIntensity":0,"snowfallAmount":0,"temperature":9.38,"temperatureApparent":9.92,"temperatureDewPoint":8.46,"uvIndex":0,"visibility":24135,"windDirection":327,"windGust":4.98,"windSpeed":4.64}]}}} diff --git a/packages/aris-source-weatherkit/package.json b/packages/aris-source-weatherkit/package.json new file mode 100644 index 0000000..56b7b80 --- /dev/null +++ b/packages/aris-source-weatherkit/package.json @@ -0,0 +1,15 @@ +{ + "name": "@aris/source-weatherkit", + "version": "0.0.0", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "test": "bun test ." + }, + "dependencies": { + "@aris/core": "workspace:*", + "@aris/source-location": "workspace:*", + "arktype": "^2.1.0" + } +} diff --git a/packages/aris-source-weatherkit/src/feed-items.ts b/packages/aris-source-weatherkit/src/feed-items.ts new file mode 100644 index 0000000..0005846 --- /dev/null +++ b/packages/aris-source-weatherkit/src/feed-items.ts @@ -0,0 +1,97 @@ +import type { FeedItem } from "@aris/core" + +import type { Certainty, ConditionCode, PrecipitationType, Severity, Urgency } from "./weatherkit" + +export const WeatherFeedItemType = { + current: "weather-current", + hourly: "weather-hourly", + daily: "weather-daily", + alert: "weather-alert", +} as const + +export type WeatherFeedItemType = (typeof WeatherFeedItemType)[keyof typeof WeatherFeedItemType] + +export type CurrentWeatherData = { + conditionCode: ConditionCode + daylight: boolean + humidity: number + precipitationIntensity: number + pressure: number + pressureTrend: "rising" | "falling" | "steady" + temperature: number + temperatureApparent: number + uvIndex: number + visibility: number + windDirection: number + windGust: number + windSpeed: number +} + +export interface CurrentWeatherFeedItem extends FeedItem< + typeof WeatherFeedItemType.current, + CurrentWeatherData +> {} + +export type HourlyWeatherData = { + forecastTime: Date + conditionCode: ConditionCode + daylight: boolean + humidity: number + precipitationAmount: number + precipitationChance: number + precipitationType: PrecipitationType + temperature: number + temperatureApparent: number + uvIndex: number + windDirection: number + windGust: number + windSpeed: number +} + +export interface HourlyWeatherFeedItem extends FeedItem< + typeof WeatherFeedItemType.hourly, + HourlyWeatherData +> {} + +export type DailyWeatherData = { + forecastDate: Date + conditionCode: ConditionCode + maxUvIndex: number + precipitationAmount: number + precipitationChance: number + precipitationType: PrecipitationType + snowfallAmount: number + sunrise: Date + sunset: Date + temperatureMax: number + temperatureMin: number +} + +export interface DailyWeatherFeedItem extends FeedItem< + typeof WeatherFeedItemType.daily, + DailyWeatherData +> {} + +export type WeatherAlertData = { + alertId: string + areaName: string + certainty: Certainty + description: string + detailsUrl: string + effectiveTime: Date + expireTime: Date + severity: Severity + source: string + urgency: Urgency +} + +export interface WeatherAlertFeedItem extends FeedItem< + typeof WeatherFeedItemType.alert, + WeatherAlertData +> {} + +export type WeatherFeedItem = + | CurrentWeatherFeedItem + | HourlyWeatherFeedItem + | DailyWeatherFeedItem + | WeatherAlertFeedItem diff --git a/packages/aris-source-weatherkit/src/index.ts b/packages/aris-source-weatherkit/src/index.ts new file mode 100644 index 0000000..f950694 --- /dev/null +++ b/packages/aris-source-weatherkit/src/index.ts @@ -0,0 +1,39 @@ +export { WeatherKey, type Weather } from "./weather-context" +export { + WeatherSource, + Units, + type Units as UnitsType, + type WeatherSourceOptions, +} from "./weather-source" + +export { + WeatherFeedItemType, + type WeatherFeedItemType as WeatherFeedItemTypeType, + type WeatherFeedItem, + type CurrentWeatherFeedItem, + type CurrentWeatherData, + type HourlyWeatherFeedItem, + type HourlyWeatherData, + type DailyWeatherFeedItem, + type DailyWeatherData, + type WeatherAlertFeedItem, + type WeatherAlertData, +} from "./feed-items" + +export { + ConditionCode, + Severity, + Urgency, + Certainty, + PrecipitationType, + DefaultWeatherKitClient, + type ConditionCode as ConditionCodeType, + type Severity as SeverityType, + type Urgency as UrgencyType, + type Certainty as CertaintyType, + type PrecipitationType as PrecipitationTypeType, + type WeatherKitClient, + type WeatherKitCredentials, + type WeatherKitQueryOptions, + type WeatherKitResponse, +} from "./weatherkit" diff --git a/packages/aris-source-weatherkit/src/weather-context.ts b/packages/aris-source-weatherkit/src/weather-context.ts new file mode 100644 index 0000000..11a027e --- /dev/null +++ b/packages/aris-source-weatherkit/src/weather-context.ts @@ -0,0 +1,27 @@ +import type { ContextKey } from "@aris/core" + +import { contextKey } from "@aris/core" + +import type { ConditionCode } from "./weatherkit" + +/** + * Simplified weather context for downstream sources. + */ +export interface Weather { + /** Current temperature */ + temperature: number + /** Feels-like temperature */ + temperatureApparent: number + /** Weather condition */ + condition: ConditionCode + /** Relative humidity (0-1) */ + humidity: number + /** UV index */ + uvIndex: number + /** Wind speed */ + windSpeed: number + /** Is it currently daytime */ + daylight: boolean +} + +export const WeatherKey: ContextKey = contextKey("weather") diff --git a/packages/aris-source-weatherkit/src/weather-source.test.ts b/packages/aris-source-weatherkit/src/weather-source.test.ts new file mode 100644 index 0000000..6d0ac65 --- /dev/null +++ b/packages/aris-source-weatherkit/src/weather-source.test.ts @@ -0,0 +1,182 @@ +import { contextValue, type Context } from "@aris/core" +import { LocationKey } from "@aris/source-location" +import { describe, expect, test } from "bun:test" + +import type { WeatherKitClient, WeatherKitResponse } from "./weatherkit" + +import fixture from "../fixtures/san-francisco.json" +import { WeatherFeedItemType } from "./feed-items" +import { WeatherKey } from "./weather-context" +import { WeatherSource, Units } from "./weather-source" + +const mockCredentials = { + privateKey: "mock", + keyId: "mock", + teamId: "mock", + serviceId: "mock", +} + +function createMockClient(response: WeatherKitResponse): WeatherKitClient { + return { + fetch: async () => response, + } +} + +function createMockContext(location?: { lat: number; lng: number }): Context { + const ctx: Context = { time: new Date("2026-01-17T00:00:00Z") } + if (location) { + ctx[LocationKey] = { ...location, accuracy: 10, timestamp: new Date() } + } + return ctx +} + +describe("WeatherSource", () => { + describe("properties", () => { + test("has correct id", () => { + const source = new WeatherSource({ credentials: mockCredentials }) + expect(source.id).toBe("weather") + }) + + test("depends on location", () => { + const source = new WeatherSource({ credentials: mockCredentials }) + expect(source.dependencies).toEqual(["location"]) + }) + + test("throws error if neither client nor credentials provided", () => { + expect(() => new WeatherSource({} as never)).toThrow( + "Either client or credentials must be provided", + ) + }) + }) + + describe("fetchContext", () => { + const mockClient = createMockClient(fixture.response as WeatherKitResponse) + + test("returns empty when no location", async () => { + const source = new WeatherSource({ client: mockClient }) + const result = await source.fetchContext(createMockContext()) + + expect(result).toEqual({}) + }) + + test("returns simplified weather context", async () => { + const source = new WeatherSource({ client: mockClient }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const result = await source.fetchContext(context) + const weather = contextValue(result, WeatherKey) + + expect(weather).toBeDefined() + expect(typeof weather!.temperature).toBe("number") + expect(typeof weather!.temperatureApparent).toBe("number") + expect(typeof weather!.condition).toBe("string") + expect(typeof weather!.humidity).toBe("number") + expect(typeof weather!.uvIndex).toBe("number") + expect(typeof weather!.windSpeed).toBe("number") + expect(typeof weather!.daylight).toBe("boolean") + }) + + test("converts temperature to imperial", async () => { + const source = new WeatherSource({ client: mockClient, units: Units.imperial }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const result = await source.fetchContext(context) + const weather = contextValue(result, WeatherKey) + + // Fixture has temperature around 10°C, imperial should be around 50°F + expect(weather!.temperature).toBeGreaterThan(40) + }) + }) + + describe("fetchItems", () => { + const mockClient = createMockClient(fixture.response as WeatherKitResponse) + + test("returns empty array when no location", async () => { + const source = new WeatherSource({ client: mockClient }) + const items = await source.fetchItems(createMockContext()) + + expect(items).toEqual([]) + }) + + test("returns feed items with all types", async () => { + const source = new WeatherSource({ client: mockClient }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const items = await source.fetchItems(context) + + expect(items.length).toBeGreaterThan(0) + expect(items.some((i) => i.type === WeatherFeedItemType.current)).toBe(true) + expect(items.some((i) => i.type === WeatherFeedItemType.hourly)).toBe(true) + expect(items.some((i) => i.type === WeatherFeedItemType.daily)).toBe(true) + }) + + test("applies hourly and daily limits", async () => { + const source = new WeatherSource({ + client: mockClient, + hourlyLimit: 3, + dailyLimit: 2, + }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const items = await source.fetchItems(context) + + const hourlyItems = items.filter((i) => i.type === WeatherFeedItemType.hourly) + const dailyItems = items.filter((i) => i.type === WeatherFeedItemType.daily) + + expect(hourlyItems.length).toBe(3) + expect(dailyItems.length).toBe(2) + }) + + test("sets timestamp from context.time", async () => { + const source = new WeatherSource({ client: mockClient }) + const queryTime = new Date("2026-01-17T12:00:00Z") + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + context.time = queryTime + + const items = await source.fetchItems(context) + + for (const item of items) { + expect(item.timestamp).toEqual(queryTime) + } + }) + + test("assigns priority based on weather conditions", async () => { + const source = new WeatherSource({ client: mockClient }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const items = await source.fetchItems(context) + + for (const item of items) { + expect(item.priority).toBeGreaterThanOrEqual(0) + expect(item.priority).toBeLessThanOrEqual(1) + } + + const currentItem = items.find((i) => i.type === WeatherFeedItemType.current) + expect(currentItem).toBeDefined() + expect(currentItem!.priority).toBeGreaterThanOrEqual(0.5) + }) + + test("generates unique IDs for each item", async () => { + const source = new WeatherSource({ client: mockClient }) + const context = createMockContext({ lat: 37.7749, lng: -122.4194 }) + + const items = await source.fetchItems(context) + const ids = items.map((i) => i.id) + const uniqueIds = new Set(ids) + + expect(uniqueIds.size).toBe(ids.length) + }) + }) + + describe("no reactive methods", () => { + test("does not implement onContextUpdate", () => { + const source = new WeatherSource({ credentials: mockCredentials }) + expect(source.onContextUpdate).toBeUndefined() + }) + + test("does not implement onItemsUpdate", () => { + const source = new WeatherSource({ credentials: mockCredentials }) + expect(source.onItemsUpdate).toBeUndefined() + }) + }) +}) diff --git a/packages/aris-source-weatherkit/src/weather-source.ts b/packages/aris-source-weatherkit/src/weather-source.ts new file mode 100644 index 0000000..bee6f00 --- /dev/null +++ b/packages/aris-source-weatherkit/src/weather-source.ts @@ -0,0 +1,363 @@ +import type { Context, FeedSource } from "@aris/core" + +import { contextValue } from "@aris/core" +import { LocationKey } from "@aris/source-location" + +import { WeatherFeedItemType, type WeatherFeedItem } from "./feed-items" +import { WeatherKey, type Weather } from "./weather-context" +import { + DefaultWeatherKitClient, + type ConditionCode, + type CurrentWeather, + type DailyForecast, + type HourlyForecast, + type Severity, + type WeatherAlert, + type WeatherKitClient, + type WeatherKitCredentials, +} from "./weatherkit" + +export const Units = { + metric: "metric", + imperial: "imperial", +} as const + +export type Units = (typeof Units)[keyof typeof Units] + +export interface WeatherSourceOptions { + credentials?: WeatherKitCredentials + client?: WeatherKitClient + /** Number of hourly forecasts to include (default: 12) */ + hourlyLimit?: number + /** Number of daily forecasts to include (default: 7) */ + dailyLimit?: number + /** Units for temperature and measurements (default: metric) */ + units?: Units +} + +const DEFAULT_HOURLY_LIMIT = 12 +const DEFAULT_DAILY_LIMIT = 7 + +const BASE_PRIORITY = { + current: 0.5, + hourly: 0.3, + daily: 0.2, + alert: 0.7, +} as const + +const SEVERE_CONDITIONS = new Set([ + "SevereThunderstorm", + "Hurricane", + "Tornado", + "TropicalStorm", + "Blizzard", + "FreezingRain", + "Hail", + "Frigid", + "Hot", +]) + +const MODERATE_CONDITIONS = new Set([ + "Thunderstorm", + "IsolatedThunderstorms", + "ScatteredThunderstorms", + "HeavyRain", + "HeavySnow", + "FreezingDrizzle", + "BlowingSnow", +]) + +/** + * A FeedSource that provides weather context and feed items using Apple WeatherKit. + * + * Depends on location source for coordinates. Provides simplified weather context + * for downstream sources and produces weather feed items (current, hourly, daily, alerts). + * + * @example + * ```ts + * const weatherSource = new WeatherSource({ + * credentials: { + * privateKey: process.env.WEATHERKIT_PRIVATE_KEY!, + * keyId: process.env.WEATHERKIT_KEY_ID!, + * teamId: process.env.WEATHERKIT_TEAM_ID!, + * serviceId: process.env.WEATHERKIT_SERVICE_ID!, + * }, + * units: Units.metric, + * }) + * + * // Access weather context in downstream sources + * const weather = contextValue(context, WeatherKey) + * if (weather?.condition === "Rain") { + * // suggest umbrella + * } + * ``` + */ +export class WeatherSource implements FeedSource { + readonly id = "weather" + readonly dependencies = ["location"] + + private readonly client: WeatherKitClient + private readonly hourlyLimit: number + private readonly dailyLimit: number + private readonly units: Units + + constructor(options: WeatherSourceOptions) { + if (!options.client && !options.credentials) { + throw new Error("Either client or credentials must be provided") + } + this.client = options.client ?? new DefaultWeatherKitClient(options.credentials!) + this.hourlyLimit = options.hourlyLimit ?? DEFAULT_HOURLY_LIMIT + this.dailyLimit = options.dailyLimit ?? DEFAULT_DAILY_LIMIT + this.units = options.units ?? Units.metric + } + + async fetchContext(context: Context): Promise> { + const location = contextValue(context, LocationKey) + if (!location) { + return {} + } + + const response = await this.client.fetch({ + lat: location.lat, + lng: location.lng, + }) + + if (!response.currentWeather) { + return {} + } + + const weather: Weather = { + temperature: convertTemperature(response.currentWeather.temperature, this.units), + temperatureApparent: convertTemperature( + response.currentWeather.temperatureApparent, + this.units, + ), + condition: response.currentWeather.conditionCode, + humidity: response.currentWeather.humidity, + uvIndex: response.currentWeather.uvIndex, + windSpeed: convertSpeed(response.currentWeather.windSpeed, this.units), + daylight: response.currentWeather.daylight, + } + + return { [WeatherKey]: weather } + } + + async fetchItems(context: Context): Promise { + const location = contextValue(context, LocationKey) + if (!location) { + return [] + } + + const timestamp = context.time + + const response = await this.client.fetch({ + lat: location.lat, + lng: location.lng, + }) + + const items: WeatherFeedItem[] = [] + + if (response.currentWeather) { + items.push(createCurrentWeatherFeedItem(response.currentWeather, timestamp, this.units)) + } + + if (response.forecastHourly?.hours) { + const hours = response.forecastHourly.hours.slice(0, this.hourlyLimit) + for (let i = 0; i < hours.length; i++) { + const hour = hours[i] + if (hour) { + items.push(createHourlyWeatherFeedItem(hour, i, timestamp, this.units)) + } + } + } + + if (response.forecastDaily?.days) { + const days = response.forecastDaily.days.slice(0, this.dailyLimit) + for (let i = 0; i < days.length; i++) { + const day = days[i] + if (day) { + items.push(createDailyWeatherFeedItem(day, i, timestamp, this.units)) + } + } + } + + if (response.weatherAlerts?.alerts) { + for (const alert of response.weatherAlerts.alerts) { + items.push(createWeatherAlertFeedItem(alert, timestamp)) + } + } + + return items + } +} + +function adjustPriorityForCondition(basePriority: number, conditionCode: ConditionCode): number { + if (SEVERE_CONDITIONS.has(conditionCode)) { + return Math.min(1, basePriority + 0.3) + } + if (MODERATE_CONDITIONS.has(conditionCode)) { + return Math.min(1, basePriority + 0.15) + } + return basePriority +} + +function adjustPriorityForAlertSeverity(severity: Severity): number { + switch (severity) { + case "extreme": + return 1 + case "severe": + return 0.9 + case "moderate": + return 0.75 + case "minor": + return BASE_PRIORITY.alert + } +} + +function convertTemperature(celsius: number, units: Units): number { + if (units === Units.imperial) { + return (celsius * 9) / 5 + 32 + } + return celsius +} + +function convertSpeed(kmh: number, units: Units): number { + if (units === Units.imperial) { + return kmh * 0.621371 + } + return kmh +} + +function convertDistance(km: number, units: Units): number { + if (units === Units.imperial) { + return km * 0.621371 + } + return km +} + +function convertPrecipitation(mm: number, units: Units): number { + if (units === Units.imperial) { + return mm * 0.0393701 + } + return mm +} + +function convertPressure(mb: number, units: Units): number { + if (units === Units.imperial) { + return mb * 0.02953 + } + return mb +} + +function createCurrentWeatherFeedItem( + current: CurrentWeather, + timestamp: Date, + units: Units, +): WeatherFeedItem { + const priority = adjustPriorityForCondition(BASE_PRIORITY.current, current.conditionCode) + + return { + id: `weather-current-${timestamp.getTime()}`, + type: WeatherFeedItemType.current, + priority, + timestamp, + data: { + conditionCode: current.conditionCode, + daylight: current.daylight, + humidity: current.humidity, + precipitationIntensity: convertPrecipitation(current.precipitationIntensity, units), + pressure: convertPressure(current.pressure, units), + pressureTrend: current.pressureTrend, + temperature: convertTemperature(current.temperature, units), + temperatureApparent: convertTemperature(current.temperatureApparent, units), + uvIndex: current.uvIndex, + visibility: convertDistance(current.visibility, units), + windDirection: current.windDirection, + windGust: convertSpeed(current.windGust, units), + windSpeed: convertSpeed(current.windSpeed, units), + }, + } +} + +function createHourlyWeatherFeedItem( + hourly: HourlyForecast, + index: number, + timestamp: Date, + units: Units, +): WeatherFeedItem { + const priority = adjustPriorityForCondition(BASE_PRIORITY.hourly, hourly.conditionCode) + + return { + id: `weather-hourly-${timestamp.getTime()}-${index}`, + type: WeatherFeedItemType.hourly, + priority, + timestamp, + data: { + forecastTime: new Date(hourly.forecastStart), + conditionCode: hourly.conditionCode, + daylight: hourly.daylight, + humidity: hourly.humidity, + precipitationAmount: convertPrecipitation(hourly.precipitationAmount, units), + precipitationChance: hourly.precipitationChance, + precipitationType: hourly.precipitationType, + temperature: convertTemperature(hourly.temperature, units), + temperatureApparent: convertTemperature(hourly.temperatureApparent, units), + uvIndex: hourly.uvIndex, + windDirection: hourly.windDirection, + windGust: convertSpeed(hourly.windGust, units), + windSpeed: convertSpeed(hourly.windSpeed, units), + }, + } +} + +function createDailyWeatherFeedItem( + daily: DailyForecast, + index: number, + timestamp: Date, + units: Units, +): WeatherFeedItem { + const priority = adjustPriorityForCondition(BASE_PRIORITY.daily, daily.conditionCode) + + return { + id: `weather-daily-${timestamp.getTime()}-${index}`, + type: WeatherFeedItemType.daily, + priority, + timestamp, + data: { + forecastDate: new Date(daily.forecastStart), + conditionCode: daily.conditionCode, + maxUvIndex: daily.maxUvIndex, + precipitationAmount: convertPrecipitation(daily.precipitationAmount, units), + precipitationChance: daily.precipitationChance, + precipitationType: daily.precipitationType, + snowfallAmount: convertPrecipitation(daily.snowfallAmount, units), + sunrise: new Date(daily.sunrise), + sunset: new Date(daily.sunset), + temperatureMax: convertTemperature(daily.temperatureMax, units), + temperatureMin: convertTemperature(daily.temperatureMin, units), + }, + } +} + +function createWeatherAlertFeedItem(alert: WeatherAlert, timestamp: Date): WeatherFeedItem { + const priority = adjustPriorityForAlertSeverity(alert.severity) + + return { + id: `weather-alert-${alert.id}`, + type: WeatherFeedItemType.alert, + priority, + timestamp, + data: { + alertId: alert.id, + areaName: alert.areaName, + certainty: alert.certainty, + description: alert.description, + detailsUrl: alert.detailsUrl, + effectiveTime: new Date(alert.effectiveTime), + expireTime: new Date(alert.expireTime), + severity: alert.severity, + source: alert.source, + urgency: alert.urgency, + }, + } +} diff --git a/packages/aris-source-weatherkit/src/weatherkit.ts b/packages/aris-source-weatherkit/src/weatherkit.ts new file mode 100644 index 0000000..455e465 --- /dev/null +++ b/packages/aris-source-weatherkit/src/weatherkit.ts @@ -0,0 +1,367 @@ +// WeatherKit REST API client and response types +// https://developer.apple.com/documentation/weatherkitrestapi + +import { type } from "arktype" + +export interface WeatherKitCredentials { + privateKey: string + keyId: string + teamId: string + serviceId: string +} + +export interface WeatherKitQueryOptions { + lat: number + lng: number + language?: string + timezone?: string +} + +export interface WeatherKitClient { + fetch(query: WeatherKitQueryOptions): Promise +} + +export class DefaultWeatherKitClient implements WeatherKitClient { + private readonly credentials: WeatherKitCredentials + + constructor(credentials: WeatherKitCredentials) { + this.credentials = credentials + } + + async fetch(query: WeatherKitQueryOptions): Promise { + const token = await generateJwt(this.credentials) + + const dataSets = ["currentWeather", "forecastHourly", "forecastDaily", "weatherAlerts"].join( + ",", + ) + + const url = new URL( + `${WEATHERKIT_API_BASE}/weather/${query.language ?? "en"}/${query.lat}/${query.lng}`, + ) + url.searchParams.set("dataSets", dataSets) + if (query.timezone) { + url.searchParams.set("timezone", query.timezone) + } + + const response = await fetch(url.toString(), { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + + if (!response.ok) { + const body = await response.text() + throw new Error(`WeatherKit API error: ${response.status} ${response.statusText}: ${body}`) + } + + const json = await response.json() + const result = weatherKitResponseSchema(json) + + if (result instanceof type.errors) { + throw new Error(`WeatherKit API response validation failed: ${result.summary}`) + } + + return result + } +} + +export const Severity = { + Minor: "minor", + Moderate: "moderate", + Severe: "severe", + Extreme: "extreme", +} as const + +export type Severity = (typeof Severity)[keyof typeof Severity] + +export const Urgency = { + Immediate: "immediate", + Expected: "expected", + Future: "future", + Past: "past", + Unknown: "unknown", +} as const + +export type Urgency = (typeof Urgency)[keyof typeof Urgency] + +export const Certainty = { + Observed: "observed", + Likely: "likely", + Possible: "possible", + Unlikely: "unlikely", + Unknown: "unknown", +} as const + +export type Certainty = (typeof Certainty)[keyof typeof Certainty] + +export const PrecipitationType = { + Clear: "clear", + Precipitation: "precipitation", + Rain: "rain", + Snow: "snow", + Sleet: "sleet", + Hail: "hail", + Mixed: "mixed", +} as const + +export type PrecipitationType = (typeof PrecipitationType)[keyof typeof PrecipitationType] + +export const ConditionCode = { + Clear: "Clear", + Cloudy: "Cloudy", + Dust: "Dust", + Fog: "Fog", + Haze: "Haze", + MostlyClear: "MostlyClear", + MostlyCloudy: "MostlyCloudy", + PartlyCloudy: "PartlyCloudy", + ScatteredThunderstorms: "ScatteredThunderstorms", + Smoke: "Smoke", + Breezy: "Breezy", + Windy: "Windy", + Drizzle: "Drizzle", + HeavyRain: "HeavyRain", + Rain: "Rain", + Showers: "Showers", + Flurries: "Flurries", + HeavySnow: "HeavySnow", + MixedRainAndSleet: "MixedRainAndSleet", + MixedRainAndSnow: "MixedRainAndSnow", + MixedRainfall: "MixedRainfall", + MixedSnowAndSleet: "MixedSnowAndSleet", + ScatteredShowers: "ScatteredShowers", + ScatteredSnowShowers: "ScatteredSnowShowers", + Sleet: "Sleet", + Snow: "Snow", + SnowShowers: "SnowShowers", + Blizzard: "Blizzard", + BlowingSnow: "BlowingSnow", + FreezingDrizzle: "FreezingDrizzle", + FreezingRain: "FreezingRain", + Frigid: "Frigid", + Hail: "Hail", + Hot: "Hot", + Hurricane: "Hurricane", + IsolatedThunderstorms: "IsolatedThunderstorms", + SevereThunderstorm: "SevereThunderstorm", + Thunderstorm: "Thunderstorm", + Tornado: "Tornado", + TropicalStorm: "TropicalStorm", +} as const + +export type ConditionCode = (typeof ConditionCode)[keyof typeof ConditionCode] + +const WEATHERKIT_API_BASE = "https://weatherkit.apple.com/api/v1" + +const severitySchema = type.enumerated( + Severity.Minor, + Severity.Moderate, + Severity.Severe, + Severity.Extreme, +) + +const urgencySchema = type.enumerated( + Urgency.Immediate, + Urgency.Expected, + Urgency.Future, + Urgency.Past, + Urgency.Unknown, +) + +const certaintySchema = type.enumerated( + Certainty.Observed, + Certainty.Likely, + Certainty.Possible, + Certainty.Unlikely, + Certainty.Unknown, +) + +const precipitationTypeSchema = type.enumerated( + PrecipitationType.Clear, + PrecipitationType.Precipitation, + PrecipitationType.Rain, + PrecipitationType.Snow, + PrecipitationType.Sleet, + PrecipitationType.Hail, + PrecipitationType.Mixed, +) + +const conditionCodeSchema = type.enumerated(...Object.values(ConditionCode)) + +const pressureTrendSchema = type.enumerated("rising", "falling", "steady") + +const currentWeatherSchema = type({ + asOf: "string", + conditionCode: conditionCodeSchema, + daylight: "boolean", + humidity: "number", + precipitationIntensity: "number", + pressure: "number", + pressureTrend: pressureTrendSchema, + temperature: "number", + temperatureApparent: "number", + temperatureDewPoint: "number", + uvIndex: "number", + visibility: "number", + windDirection: "number", + windGust: "number", + windSpeed: "number", +}) + +export type CurrentWeather = typeof currentWeatherSchema.infer + +const hourlyForecastSchema = type({ + forecastStart: "string", + conditionCode: conditionCodeSchema, + daylight: "boolean", + humidity: "number", + precipitationAmount: "number", + precipitationChance: "number", + precipitationType: precipitationTypeSchema, + pressure: "number", + snowfallIntensity: "number", + temperature: "number", + temperatureApparent: "number", + temperatureDewPoint: "number", + uvIndex: "number", + visibility: "number", + windDirection: "number", + windGust: "number", + windSpeed: "number", +}) + +export type HourlyForecast = typeof hourlyForecastSchema.infer + +const dayWeatherConditionsSchema = type({ + conditionCode: conditionCodeSchema, + humidity: "number", + precipitationAmount: "number", + precipitationChance: "number", + precipitationType: precipitationTypeSchema, + snowfallAmount: "number", + temperatureMax: "number", + temperatureMin: "number", + windDirection: "number", + "windGust?": "number", + windSpeed: "number", +}) + +export type DayWeatherConditions = typeof dayWeatherConditionsSchema.infer + +const dailyForecastSchema = type({ + forecastStart: "string", + forecastEnd: "string", + conditionCode: conditionCodeSchema, + maxUvIndex: "number", + moonPhase: "string", + "moonrise?": "string", + "moonset?": "string", + precipitationAmount: "number", + precipitationChance: "number", + precipitationType: precipitationTypeSchema, + snowfallAmount: "number", + sunrise: "string", + sunriseCivil: "string", + sunriseNautical: "string", + sunriseAstronomical: "string", + sunset: "string", + sunsetCivil: "string", + sunsetNautical: "string", + sunsetAstronomical: "string", + temperatureMax: "number", + temperatureMin: "number", + "daytimeForecast?": dayWeatherConditionsSchema, + "overnightForecast?": dayWeatherConditionsSchema, +}) + +export type DailyForecast = typeof dailyForecastSchema.infer + +const weatherAlertSchema = type({ + id: "string", + areaId: "string", + areaName: "string", + certainty: certaintySchema, + countryCode: "string", + description: "string", + detailsUrl: "string", + effectiveTime: "string", + expireTime: "string", + issuedTime: "string", + responses: "string[]", + severity: severitySchema, + source: "string", + urgency: urgencySchema, +}) + +export type WeatherAlert = typeof weatherAlertSchema.infer + +const weatherKitResponseSchema = type({ + "currentWeather?": currentWeatherSchema, + "forecastHourly?": type({ + hours: hourlyForecastSchema.array(), + }), + "forecastDaily?": type({ + days: dailyForecastSchema.array(), + }), + "weatherAlerts?": type({ + alerts: weatherAlertSchema.array(), + }), +}) + +export type WeatherKitResponse = typeof weatherKitResponseSchema.infer + +async function generateJwt(credentials: WeatherKitCredentials): Promise { + const header = { + alg: "ES256", + kid: credentials.keyId, + id: `${credentials.teamId}.${credentials.serviceId}`, + } + + const now = Math.floor(Date.now() / 1000) + const payload = { + iss: credentials.teamId, + iat: now, + exp: now + 3600, + sub: credentials.serviceId, + } + + const encoder = new TextEncoder() + const headerB64 = btoa(JSON.stringify(header)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "") + const payloadB64 = btoa(JSON.stringify(payload)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "") + + const signingInput = `${headerB64}.${payloadB64}` + + const pemContents = credentials.privateKey + .replace(/-----BEGIN PRIVATE KEY-----/, "") + .replace(/-----END PRIVATE KEY-----/, "") + .replace(/\s/g, "") + + const binaryKey = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0)) + + const cryptoKey = await crypto.subtle.importKey( + "pkcs8", + binaryKey, + { name: "ECDSA", namedCurve: "P-256" }, + false, + ["sign"], + ) + + const signature = await crypto.subtle.sign( + { name: "ECDSA", hash: "SHA-256" }, + cryptoKey, + encoder.encode(signingInput), + ) + + const signatureB64 = btoa(String.fromCharCode(...new Uint8Array(signature))) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, "") + + return `${signingInput}.${signatureB64}` +}