From 912043a0d255acae6b28c5eb3fcb5a41b4296de3 Mon Sep 17 00:00:00 2001 From: axeelz Date: Mon, 12 May 2025 11:44:46 +0200 Subject: [PATCH 01/13] feat: add support for Paris, France --- main.go | 2 ++ web/index.html | 1 + 2 files changed, 3 insertions(+) diff --git a/main.go b/main.go index c5186af..f8d31b3 100644 --- a/main.go +++ b/main.go @@ -115,6 +115,7 @@ var placeholderWeather = map[string]string{ "zurich": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00+02:00\",\"EffectiveEpochDate\":1746943200,\"Severity\":4,\"Text\":\"Pleasant Sunday\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:56:00+02:00\",\"EpochRise\":1746849360,\"Set\":\"2025-05-10T20:49:00+02:00\",\"EpochSet\":1746902940},\"Moon\":{\"Rise\":\"2025-05-10T18:54:00+02:00\",\"EpochRise\":1746896040,\"Set\":\"2025-05-11T04:58:00+02:00\",\"EpochSet\":1746932280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":12.3,\"DegreeDaySummary\":{\"Heating\":{\"Value\":10,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":517,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":8,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and milder\",\"LongPhrase\":\"Mostly sunny and milder\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":62,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":70,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":21,\"Evapotranspiration\":{\"Value\":0.15,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8153.8,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":42,\"Maximum\":70,\"Average\":55},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and chilly\",\"LongPhrase\":\"Clear and chilly\",\"PrecipitationProbability\":3,\"ThunderstormProbability\":0,\"RainProbability\":3,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":6,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":364.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":41,\"Maximum\":94,\"Average\":72},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":42,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":60,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\"}]}", "berlin": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T20:00:00+02:00\",\"EffectiveEpochDate\":1746986400,\"Severity\":7,\"Text\":\"Cool Sunday night\",\"Category\":\"cold\",\"EndDate\":\"2025-05-12T08:00:00+02:00\",\"EndEpochDate\":1747029600,\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:19:00+02:00\",\"EpochRise\":1746847140,\"Set\":\"2025-05-10T20:47:00+02:00\",\"EpochSet\":1746902820},\"Moon\":{\"Rise\":\"2025-05-10T18:46:00+02:00\",\"EpochRise\":1746895560,\"Set\":\"2025-05-11T04:23:00+02:00\",\"EpochSet\":1746930180,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cold\"},\"Maximum\":{\"Value\":65,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":6.4,\"DegreeDaySummary\":{\"Heating\":{\"Value\":9,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":330,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":3,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":4,\"IconPhrase\":\"Intermittent clouds\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Times of clouds and sun\",\"LongPhrase\":\"Times of clouds and sun\",\"PrecipitationProbability\":25,\"ThunderstormProbability\":0,\"RainProbability\":25,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":359,\"Localized\":\"N\",\"English\":\"N\"}},\"WindGust\":{\"Speed\":{\"Value\":23,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":79,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":72,\"Evapotranspiration\":{\"Value\":0.1,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":4596.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":49,\"Maximum\":75,\"Average\":61},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":34,\"IconPhrase\":\"Mostly clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mainly clear\",\"LongPhrase\":\"Mainly clear\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":65,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":90,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":22,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":453.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":60,\"Maximum\":75,\"Average\":67},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\"}]}", "dubai": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T01:00:00+04:00\",\"EffectiveEpochDate\":1746910800,\"Severity\":7,\"Text\":\"Warm late Saturday night\",\"Category\":\"heat\",\"EndDate\":\"2025-05-11T07:00:00+04:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+04:00\",\"EpochDate\":1746846000,\"Sun\":{\"Rise\":\"2025-05-10T05:37:00+04:00\",\"EpochRise\":1746841020,\"Set\":\"2025-05-10T18:54:00+04:00\",\"EpochSet\":1746888840},\"Moon\":{\"Rise\":\"2025-05-10T17:04:00+04:00\",\"EpochRise\":1746882240,\"Set\":\"2025-05-11T04:28:00+04:00\",\"EpochSet\":1746923280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":100,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":108,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Dangerous Heat\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":103,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Hot\"}},\"HoursOfSun\":11.5,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":26,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":12,\"Category\":\"Extreme\",\"CategoryValue\":5}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and very warm\",\"LongPhrase\":\"Mostly sunny and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":29,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":29.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":15,\"Evapotranspiration\":{\"Value\":0.26,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8508.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":40,\"Maximum\":74,\"Average\":52},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":80,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":90,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and very warm\",\"LongPhrase\":\"Clear and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":25,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":0,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":141.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":44,\"Maximum\":74,\"Average\":59},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":73,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":76,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":84,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"}]}", + "paris": "{\"Headline\":{\"EffectiveDate\":\"2025-05-13T14:00:00+02:00\",\"EffectiveEpochDate\":1747137600,\"Severity\":5,\"Text\":\"Expect showers Tuesday afternoon\",\"Category\":\"rain\",\"EndDate\":\"2025-05-13T20:00:00+02:00\",\"EndEpochDate\":1747159200,\"MobileLink\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-12T07:00:00+02:00\",\"EpochDate\":1747026000,\"Sun\":{\"Rise\":\"2025-05-12T06:14:00+02:00\",\"EpochRise\":1747023240,\"Set\":\"2025-05-12T21:21:00+02:00\",\"EpochSet\":1747077660},\"Moon\":{\"Rise\":\"2025-05-12T21:44:00+02:00\",\"EpochRise\":1747079040,\"Set\":\"2025-05-13T06:00:00+02:00\",\"EpochSet\":1747108800,\"Phase\":\"Full\",\"Age\":15},\"Temperature\":{\"Minimum\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":3,\"DegreeDaySummary\":{\"Heating\":{\"Value\":5,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":32767,\"Category\":\"High\",\"CategoryValue\":3},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":3,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":7,\"IconPhrase\":\"Cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Overcast and not as warm\",\"LongPhrase\":\"Overcast and not as warm\",\"PrecipitationProbability\":25,\"ThunderstormProbability\":2,\"RainProbability\":25,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":6.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":182,\"Localized\":\"S\",\"English\":\"S\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":185,\"Localized\":\"S\",\"English\":\"S\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":88,\"Evapotranspiration\":{\"Value\":0.09,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":2002.3,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":53,\"Maximum\":100,\"Average\":66},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly cloudy\",\"LongPhrase\":\"Partly cloudy\",\"PrecipitationProbability\":9,\"ThunderstormProbability\":0,\"RainProbability\":9,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":151,\"Localized\":\"SSE\",\"English\":\"SSE\"}},\"WindGust\":{\"Speed\":{\"Value\":9.2,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":191,\"Localized\":\"S\",\"English\":\"S\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":71,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":289,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":58,\"Maximum\":92,\"Average\":78},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?day=1&lang=en-us\"}]}", } var supportedLocations = map[string]location{ @@ -128,6 +129,7 @@ var supportedLocations = map[string]location{ "zurich": {nil, 47.369019, 8.538030, "Europe/Zurich", "Zurich"}, "berlin": {nil, 52.520008, 13.404954, "Europe/Berlin", "Berlin"}, "dubai": {nil, 25.204849, 55.270782, "Asia/Dubai", "Dubai"}, + "paris": {nil, 48.864716, 2.349014, "Europe/Paris", "Paris"}, } func main() { diff --git a/web/index.html b/web/index.html index 4e81033..b2e599e 100644 --- a/web/index.html +++ b/web/index.html @@ -50,6 +50,7 @@
  • Warsaw
  • Dubai
  • Tokyo
  • +
  • Paris
  • From 75dd44268a484ac70197f54e15c5067717ed3a02 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 12:09:58 +0100 Subject: [PATCH 02/13] only pass time series data to gemini --- main.go | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index c5186af..453bfb5 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "github.com/joho/godotenv" "google.golang.org/genai" "html/template" - "io" "log" "log/slog" "mime" @@ -68,6 +67,12 @@ type webpushNotificationPayload struct { Location string `json:"location"` } +type metAPIData struct { + Properties struct { + TimeSeries []map[string]any `json:"timeseries"` + } `json:"properties"` +} + type state struct { ctx context.Context db *sql.DB @@ -220,7 +225,6 @@ func main() { _, err = s.NewJob( gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(7, 0, 0))), gocron.NewTask(updateSummary, &state, locKey, &loc), - gocron.WithStartAt(gocron.WithStartImmediately()), ) if err != nil { log.Fatal(err) @@ -573,6 +577,8 @@ func deleteSubscription(state *state, regID uuid.UUID) error { func updateSummary(state *state, locKey string, loc *location) { slog.Info("updating weather summary", "location", locKey) + today := time.Now().In(loc.tz) + var weatherJSON string if state.usePlaceholder { weatherJSON = placeholderWeather[locKey] @@ -590,21 +596,41 @@ func updateSummary(state *state, locKey string, loc *location) { return } - b, err := io.ReadAll(resp.Body) defer resp.Body.Close() + + data := metAPIData{} + err = json.NewDecoder(resp.Body).Decode(&data) if err != nil { - slog.Error("failed to query weather data", "location", locKey, "error", err) + slog.Error("failed to decode received weather data", "location", locKey, "error", err) + return + } + + y, m, d := today.Date() + + t := slices.DeleteFunc(data.Properties.TimeSeries, func(series map[string]any) bool { + if ts, ok := series["time"].(string); ok { + t, err := time.Parse(time.RFC3339, ts) + if err != nil { + return false + } + ty, tm, td := t.In(loc.tz).Date() + return !(y == ty && m == tm && d == td) + } + return false + }) + + b, err := json.Marshal(t) + if err != nil { + slog.Error("failed to marshal processed time series data", "location", locKey, "error", err) return } weatherJSON = string(b) } - date := time.Now().In(loc.tz).Format("2006-02-01") - result, err := state.genai.Models.GenerateContent(state.ctx, "gemini-2.0-flash", []*genai.Content{{ Parts: []*genai.Part{ - {Text: fmt.Sprintf(prompt, date, loc.displayName, loc.displayName)}, + {Text: fmt.Sprintf(prompt, today.Format("2006-02-01"), loc.displayName, loc.displayName)}, {Text: weatherJSON}, }, }}, nil) From b6419b8bdc991d95f4eb70512e6a372de3f17329 Mon Sep 17 00:00:00 2001 From: axeelz Date: Mon, 12 May 2025 18:37:26 +0200 Subject: [PATCH 03/13] style: reorder Paris in HTML list --- web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/index.html b/web/index.html index b2e599e..de99890 100644 --- a/web/index.html +++ b/web/index.html @@ -45,12 +45,12 @@
  • San Jose
  • Los Angeles
  • New York City
  • +
  • Paris
  • Berlin
  • Zurich
  • Warsaw
  • Dubai
  • Tokyo
  • -
  • Paris
  • From 00eafedca0285c58accad6c9ed157fdfa6e91cc4 Mon Sep 17 00:00:00 2001 From: axeelz Date: Mon, 12 May 2025 18:38:11 +0200 Subject: [PATCH 04/13] chore: scrap Paris placeholder data --- main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main.go b/main.go index f8d31b3..1e297d5 100644 --- a/main.go +++ b/main.go @@ -115,7 +115,6 @@ var placeholderWeather = map[string]string{ "zurich": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00+02:00\",\"EffectiveEpochDate\":1746943200,\"Severity\":4,\"Text\":\"Pleasant Sunday\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:56:00+02:00\",\"EpochRise\":1746849360,\"Set\":\"2025-05-10T20:49:00+02:00\",\"EpochSet\":1746902940},\"Moon\":{\"Rise\":\"2025-05-10T18:54:00+02:00\",\"EpochRise\":1746896040,\"Set\":\"2025-05-11T04:58:00+02:00\",\"EpochSet\":1746932280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":12.3,\"DegreeDaySummary\":{\"Heating\":{\"Value\":10,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":517,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":8,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and milder\",\"LongPhrase\":\"Mostly sunny and milder\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":62,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":70,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":21,\"Evapotranspiration\":{\"Value\":0.15,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8153.8,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":42,\"Maximum\":70,\"Average\":55},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and chilly\",\"LongPhrase\":\"Clear and chilly\",\"PrecipitationProbability\":3,\"ThunderstormProbability\":0,\"RainProbability\":3,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":6,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":364.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":41,\"Maximum\":94,\"Average\":72},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":42,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":60,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\"}]}", "berlin": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T20:00:00+02:00\",\"EffectiveEpochDate\":1746986400,\"Severity\":7,\"Text\":\"Cool Sunday night\",\"Category\":\"cold\",\"EndDate\":\"2025-05-12T08:00:00+02:00\",\"EndEpochDate\":1747029600,\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:19:00+02:00\",\"EpochRise\":1746847140,\"Set\":\"2025-05-10T20:47:00+02:00\",\"EpochSet\":1746902820},\"Moon\":{\"Rise\":\"2025-05-10T18:46:00+02:00\",\"EpochRise\":1746895560,\"Set\":\"2025-05-11T04:23:00+02:00\",\"EpochSet\":1746930180,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cold\"},\"Maximum\":{\"Value\":65,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":6.4,\"DegreeDaySummary\":{\"Heating\":{\"Value\":9,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":330,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":3,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":4,\"IconPhrase\":\"Intermittent clouds\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Times of clouds and sun\",\"LongPhrase\":\"Times of clouds and sun\",\"PrecipitationProbability\":25,\"ThunderstormProbability\":0,\"RainProbability\":25,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":359,\"Localized\":\"N\",\"English\":\"N\"}},\"WindGust\":{\"Speed\":{\"Value\":23,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":79,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":72,\"Evapotranspiration\":{\"Value\":0.1,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":4596.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":49,\"Maximum\":75,\"Average\":61},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":34,\"IconPhrase\":\"Mostly clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mainly clear\",\"LongPhrase\":\"Mainly clear\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":65,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":90,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":22,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":453.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":60,\"Maximum\":75,\"Average\":67},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\"}]}", "dubai": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T01:00:00+04:00\",\"EffectiveEpochDate\":1746910800,\"Severity\":7,\"Text\":\"Warm late Saturday night\",\"Category\":\"heat\",\"EndDate\":\"2025-05-11T07:00:00+04:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+04:00\",\"EpochDate\":1746846000,\"Sun\":{\"Rise\":\"2025-05-10T05:37:00+04:00\",\"EpochRise\":1746841020,\"Set\":\"2025-05-10T18:54:00+04:00\",\"EpochSet\":1746888840},\"Moon\":{\"Rise\":\"2025-05-10T17:04:00+04:00\",\"EpochRise\":1746882240,\"Set\":\"2025-05-11T04:28:00+04:00\",\"EpochSet\":1746923280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":100,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":108,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Dangerous Heat\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":103,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Hot\"}},\"HoursOfSun\":11.5,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":26,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":12,\"Category\":\"Extreme\",\"CategoryValue\":5}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and very warm\",\"LongPhrase\":\"Mostly sunny and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":29,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":29.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":15,\"Evapotranspiration\":{\"Value\":0.26,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8508.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":40,\"Maximum\":74,\"Average\":52},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":80,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":90,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and very warm\",\"LongPhrase\":\"Clear and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":25,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":0,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":141.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":44,\"Maximum\":74,\"Average\":59},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":73,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":76,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":84,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"}]}", - "paris": "{\"Headline\":{\"EffectiveDate\":\"2025-05-13T14:00:00+02:00\",\"EffectiveEpochDate\":1747137600,\"Severity\":5,\"Text\":\"Expect showers Tuesday afternoon\",\"Category\":\"rain\",\"EndDate\":\"2025-05-13T20:00:00+02:00\",\"EndEpochDate\":1747159200,\"MobileLink\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-12T07:00:00+02:00\",\"EpochDate\":1747026000,\"Sun\":{\"Rise\":\"2025-05-12T06:14:00+02:00\",\"EpochRise\":1747023240,\"Set\":\"2025-05-12T21:21:00+02:00\",\"EpochSet\":1747077660},\"Moon\":{\"Rise\":\"2025-05-12T21:44:00+02:00\",\"EpochRise\":1747079040,\"Set\":\"2025-05-13T06:00:00+02:00\",\"EpochSet\":1747108800,\"Phase\":\"Full\",\"Age\":15},\"Temperature\":{\"Minimum\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":3,\"DegreeDaySummary\":{\"Heating\":{\"Value\":5,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":32767,\"Category\":\"High\",\"CategoryValue\":3},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":3,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":7,\"IconPhrase\":\"Cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Overcast and not as warm\",\"LongPhrase\":\"Overcast and not as warm\",\"PrecipitationProbability\":25,\"ThunderstormProbability\":2,\"RainProbability\":25,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":6.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":182,\"Localized\":\"S\",\"English\":\"S\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":185,\"Localized\":\"S\",\"English\":\"S\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":88,\"Evapotranspiration\":{\"Value\":0.09,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":2002.3,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":53,\"Maximum\":100,\"Average\":66},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly cloudy\",\"LongPhrase\":\"Partly cloudy\",\"PrecipitationProbability\":9,\"ThunderstormProbability\":0,\"RainProbability\":9,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":151,\"Localized\":\"SSE\",\"English\":\"SSE\"}},\"WindGust\":{\"Speed\":{\"Value\":9.2,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":191,\"Localized\":\"S\",\"English\":\"S\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":71,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":289,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":58,\"Maximum\":92,\"Average\":78},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/fr/paris/623/daily-weather-forecast/623?day=1&lang=en-us\"}]}", } var supportedLocations = map[string]location{ From 0a4ec7ddb19b59b2c19e850cbca48d12c5e749f6 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 18:58:31 +0100 Subject: [PATCH 05/13] cache weather summaries to sqlite --- main.go | 141 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/main.go b/main.go index 453bfb5..9ec9307 100644 --- a/main.go +++ b/main.go @@ -73,6 +73,12 @@ type metAPIData struct { } `json:"properties"` } +type updateSummaryOptions struct { + locKey string + location *location + pushUpdate bool +} + type state struct { ctx context.Context db *sql.DB @@ -122,7 +128,7 @@ var placeholderWeather = map[string]string{ "dubai": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T01:00:00+04:00\",\"EffectiveEpochDate\":1746910800,\"Severity\":7,\"Text\":\"Warm late Saturday night\",\"Category\":\"heat\",\"EndDate\":\"2025-05-11T07:00:00+04:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+04:00\",\"EpochDate\":1746846000,\"Sun\":{\"Rise\":\"2025-05-10T05:37:00+04:00\",\"EpochRise\":1746841020,\"Set\":\"2025-05-10T18:54:00+04:00\",\"EpochSet\":1746888840},\"Moon\":{\"Rise\":\"2025-05-10T17:04:00+04:00\",\"EpochRise\":1746882240,\"Set\":\"2025-05-11T04:28:00+04:00\",\"EpochSet\":1746923280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":100,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":108,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Dangerous Heat\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":103,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Hot\"}},\"HoursOfSun\":11.5,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":26,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":12,\"Category\":\"Extreme\",\"CategoryValue\":5}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and very warm\",\"LongPhrase\":\"Mostly sunny and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":29,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":29.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":15,\"Evapotranspiration\":{\"Value\":0.26,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8508.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":40,\"Maximum\":74,\"Average\":52},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":80,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":90,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and very warm\",\"LongPhrase\":\"Clear and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":25,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":0,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":141.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":44,\"Maximum\":74,\"Average\":59},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":73,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":76,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":84,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"}]}", } -var supportedLocations = map[string]location{ +var supportedLocations = map[string]*location{ "london": {nil, 51.507351, -0.127758, "Europe/London", "London"}, "sf": {nil, 37.774929, -122.419418, "America/Los_Angeles", "San Francisco"}, "sj": {nil, 37.338207, -121.886330, "America/Los_Angeles", "San Jose"}, @@ -138,38 +144,45 @@ var supportedLocations = map[string]location{ func main() { port := flag.Int("port", 8080, "the port that the server should listen on") genKeys := flag.Bool("generate-vapid-keys", false, "generate a new vapid key pair, which will be outputted to stdout.") - usePlaceholder := flag.Bool("use-placeholder", false, "use placeholder data instead of real API data.") flag.Parse() if *genKeys { generateKeys() - return + } else if err := startServer(*port); err != nil { + log.Fatal(err) } +} +func startServer(port int) error { slog.Info("starting 7am...") - _ = godotenv.Load() - err := checkEnv() + err := loadTimeZones() if err != nil { - log.Fatal(err) + return err + } + + _ = godotenv.Load() + err = checkEnv() + if err != nil { + return err } wd, err := os.Getwd() if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to get cwd: %w", err) } p := filepath.Join(wd, "data") err = os.MkdirAll(p, os.ModePerm) if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to create data directory at %v: %w", p, err) } slog.Info("data directory created", "path", p) db, err := initDB() if err != nil { - log.Fatalf("failed to initialize db: %e\n", err) + return fmt.Errorf("failed to initialize db: %w", err) } ctx, cancel := context.WithCancel(context.Background()) @@ -180,7 +193,7 @@ func main() { Backend: genai.BackendGeminiAPI, }) if err != nil { - log.Fatalf("failed to initialize gemini client: %e\n", err) + return fmt.Errorf("failed to initialize gemini client: %w\n", err) } summaryHTML, _ := webDir.ReadFile("web/summary.html") @@ -197,7 +210,7 @@ func main() { summaryChans: map[string]chan string{}, genai: genaiClient, - usePlaceholder: *usePlaceholder, + usePlaceholder: false, subscriptions: map[string][]*registeredSubscription{}, @@ -206,28 +219,29 @@ func main() { vapidPrivateKey: os.Getenv("VAPID_PRIVATE_KEY_BASE64"), } + fetchInitialSummaries(&state) + var schedulers []gocron.Scheduler // schedule periodic updates of weather summary for each supported location for locKey, loc := range supportedLocations { - l, err := time.LoadLocation(loc.ianaName) + s, err := gocron.NewScheduler(gocron.WithLocation(loc.tz)) if err != nil { - log.Fatal(err) - } - - loc.tz = l - - s, err := gocron.NewScheduler(gocron.WithLocation(l)) - if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to create gocron scheduler for %v: %w", locKey, err) } _, err = s.NewJob( gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(7, 0, 0))), - gocron.NewTask(updateSummary, &state, locKey, &loc), + gocron.NewTask(func(ctx context.Context) { + updateSummary(ctx, &state, updateSummaryOptions{ + locKey: locKey, + location: loc, + pushUpdate: true, + }) + }), ) if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to scheduel gocron job for %v: %w", locKey, err) } schedulers = append(schedulers, s) @@ -246,16 +260,16 @@ func main() { err = loadSubscriptions(&state) if err != nil { - log.Fatalf("failed to load existing subscriptions: %e\n", err) + return fmt.Errorf("failed to load existing subscriptions: %w", err) } http.HandleFunc("/", handleHTTPRequest(&state)) - slog.Info("server starting", "port", *port) + slog.Info("server starting", "port", port) - err = http.ListenAndServe(fmt.Sprintf(":%d", *port), nil) + err = http.ListenAndServe(fmt.Sprintf(":%d", port), nil) if err != nil { - log.Printf("failed to start http server: %e\n", err) + return fmt.Errorf("failed to start http server: %w", err) } for _, s := range schedulers { @@ -263,6 +277,8 @@ func main() { } slog.Info("7am shut down") + + return nil } func generateKeys() { @@ -426,6 +442,11 @@ func initDB() (*sql.DB, error) { locations TEXT NOT NULL, subscription_json TEXT NOT NULL ); + + CREATE TABLE IF NOT EXISTS summaries( + location TEXT PRIMARY KEY, + summary TEXT NOT NULL + ); `) if err != nil { return nil, err @@ -574,7 +595,55 @@ func deleteSubscription(state *state, regID uuid.UUID) error { return err } -func updateSummary(state *state, locKey string, loc *location) { +func loadTimeZones() error { + for locKey, loc := range supportedLocations { + tz, err := time.LoadLocation(loc.ianaName) + if err != nil { + return fmt.Errorf("failed to load time zone for %v: %w", locKey, err) + } + loc.tz = tz + } + return nil +} + +func fetchInitialSummaries(state *state) { + var wg sync.WaitGroup + for locKey, loc := range supportedLocations { + wg.Add(1) + go func() { + defer wg.Done() + + summary := "" + rows, err := state.db.Query("SELECT summary FROM summaries WHERE location = ?", locKey) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) + } else if err == nil { + rows.Next() + err = rows.Scan(&summary) + if err != nil { + slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) + } + rows.Close() + } + + if summary == "" { + updateSummary(state.ctx, state, updateSummaryOptions{ + locKey: locKey, + location: loc, + pushUpdate: false, + }) + } else { + state.summaries.Store(locKey, summary) + } + }() + } + wg.Wait() +} + +func updateSummary(ctx context.Context, state *state, opts updateSummaryOptions) { + locKey := opts.locKey + loc := opts.location + slog.Info("updating weather summary", "location", locKey) today := time.Now().In(loc.tz) @@ -583,7 +652,7 @@ func updateSummary(state *state, locKey string, loc *location) { if state.usePlaceholder { weatherJSON = placeholderWeather[locKey] } else { - req, err := http.NewRequest("GET", fmt.Sprintf("https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=%v&lon=%v", loc.lat, loc.lon), nil) + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=%v&lon=%v", loc.lat, loc.lon), nil) if err != nil { slog.Error("failed to query weather data", "location", locKey, "error", err) return @@ -628,7 +697,7 @@ func updateSummary(state *state, locKey string, loc *location) { weatherJSON = string(b) } - result, err := state.genai.Models.GenerateContent(state.ctx, "gemini-2.0-flash", []*genai.Content{{ + result, err := state.genai.Models.GenerateContent(ctx, "gemini-2.0-flash", []*genai.Content{{ Parts: []*genai.Part{ {Text: fmt.Sprintf(prompt, today.Format("2006-02-01"), loc.displayName, loc.displayName)}, {Text: weatherJSON}, @@ -640,11 +709,19 @@ func updateSummary(state *state, locKey string, loc *location) { } summary := result.Text() - c := state.summaryChans[locKey] + + _, err = state.db.ExecContext(ctx, "INSERT INTO summaries (location, summary) VALUES (?, ?)", locKey, summary) + if err != nil { + slog.Warn("unable to cache generated weather summary to db", "location", locKey, "error", err) + } state.summaries.Store(locKey, summary) - if len(state.subscriptions[locKey]) > 0 { - c <- summary + + if opts.pushUpdate { + c := state.summaryChans[locKey] + if len(state.subscriptions[locKey]) > 0 { + c <- summary + } } slog.Info("updated weather summary", "location", locKey) From a6fc47fb6f363060f1beb3bc482766a06e39feb6 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 19:07:19 +0100 Subject: [PATCH 06/13] fix data race --- main.go | 113 ++++++++++++++++++++++++-------------------------------- 1 file changed, 48 insertions(+), 65 deletions(-) diff --git a/main.go b/main.go index aba28c7..7a2a7ae 100644 --- a/main.go +++ b/main.go @@ -115,19 +115,6 @@ var envKeys = []string{"GEMINI_API_KEY", "MET_API_USER_AGENT", "VAPID_SUBJECT", //go:embed prompt.txt var prompt string -var placeholderWeather = map[string]string{ - "london": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00+01:00\",\"EffectiveEpochDate\":1746946800,\"Severity\":4,\"Text\":\"Pleasant Sunday\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/gb/london/ec4a-2/daily-weather-forecast/328328?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/gb/london/ec4a-2/daily-weather-forecast/328328?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+01:00\",\"EpochDate\":1746856800,\"Sun\":{\"Rise\":\"2025-05-10T05:17:00+01:00\",\"EpochRise\":1746850620,\"Set\":\"2025-05-10T20:38:00+01:00\",\"EpochSet\":1746905880},\"Moon\":{\"Rise\":\"2025-05-10T18:40:00+01:00\",\"EpochRise\":1746898800,\"Set\":\"2025-05-11T04:21:00+01:00\",\"EpochSet\":1746933660,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":70,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":12.8,\"DegreeDaySummary\":{\"Heating\":{\"Value\":5,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":32767,\"Category\":\"High\",\"CategoryValue\":3},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":7,\"Category\":\"High\",\"CategoryValue\":3}],\"Day\":{\"Icon\":1,\"IconPhrase\":\"Sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Sunshine, breezy and pleasant\",\"LongPhrase\":\"Breezy and pleasant with sunshine\",\"PrecipitationProbability\":1,\"ThunderstormProbability\":0,\"RainProbability\":1,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":13.8,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":97,\"Localized\":\"E\",\"English\":\"E\"}},\"WindGust\":{\"Speed\":{\"Value\":32.2,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":88,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":6,\"Evapotranspiration\":{\"Value\":0.18,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":7999.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":27,\"Maximum\":71,\"Average\":39},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":38,\"IconPhrase\":\"Mostly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Increasing cloudiness\",\"LongPhrase\":\"Increasing cloudiness\",\"PrecipitationProbability\":1,\"ThunderstormProbability\":0,\"RainProbability\":1,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":6.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":69,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":20.7,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":106,\"Localized\":\"ESE\",\"English\":\"ESE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":32,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":155.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":44,\"Maximum\":82,\"Average\":67},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/gb/london/ec4a-2/daily-weather-forecast/328328?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/gb/london/ec4a-2/daily-weather-forecast/328328?day=1&lang=en-us\"}]}", - "sf": "{\"Headline\":{\"EffectiveDate\":\"2025-05-10T08:00:00-07:00\",\"EffectiveEpochDate\":1746889200,\"Severity\":4,\"Text\":\"Pleasant today\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/us/san-francisco-ca/94103/daily-weather-forecast/347629?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/san-francisco-ca/94103/daily-weather-forecast/347629?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00-07:00\",\"EpochDate\":1746885600,\"Sun\":{\"Rise\":\"2025-05-10T06:04:00-07:00\",\"EpochRise\":1746882240,\"Set\":\"2025-05-10T20:08:00-07:00\",\"EpochSet\":1746932880},\"Moon\":{\"Rise\":\"2025-05-10T18:41:00-07:00\",\"EpochRise\":1746927660,\"Set\":\"2025-05-11T05:13:00-07:00\",\"EpochSet\":1746965580,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":71,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":73,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":67,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":10.2,\"DegreeDaySummary\":{\"Heating\":{\"Value\":3,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":49,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Particle Pollution\"},{\"Name\":\"Grass\",\"Value\":12,\"Category\":\"Moderate\",\"CategoryValue\":2},{\"Name\":\"Mold\",\"Value\":3250,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":5,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":7,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":10,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Sunshine and pleasant\",\"LongPhrase\":\"Sunny to partly cloudy and pleasant\",\"PrecipitationProbability\":1,\"ThunderstormProbability\":0,\"RainProbability\":1,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":254,\"Localized\":\"WSW\",\"English\":\"WSW\"}},\"WindGust\":{\"Speed\":{\"Value\":29.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":257,\"Localized\":\"WSW\",\"English\":\"WSW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":28,\"Evapotranspiration\":{\"Value\":0.15,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8489.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":51,\"Maximum\":91,\"Average\":65},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":56,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":62,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly cloudy\",\"LongPhrase\":\"Partly cloudy\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":268,\"Localized\":\"W\",\"English\":\"W\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":264,\"Localized\":\"W\",\"English\":\"W\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":45,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":190.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":57,\"Maximum\":84,\"Average\":73},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/us/san-francisco-ca/94103/daily-weather-forecast/347629?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/san-francisco-ca/94103/daily-weather-forecast/347629?day=1&lang=en-us\"}]}", - "sj": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00-07:00\",\"EffectiveEpochDate\":1746975600,\"Severity\":4,\"Text\":\"Pleasant tomorrow\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/us/san-jose-ca/95110/daily-weather-forecast/347630?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/san-jose-ca/95110/daily-weather-forecast/347630?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00-07:00\",\"EpochDate\":1746885600,\"Sun\":{\"Rise\":\"2025-05-10T06:03:00-07:00\",\"EpochRise\":1746882180,\"Set\":\"2025-05-10T20:05:00-07:00\",\"EpochSet\":1746932700},\"Moon\":{\"Rise\":\"2025-05-10T18:38:00-07:00\",\"EpochRise\":1746927480,\"Set\":\"2025-05-11T05:11:00-07:00\",\"EpochSet\":1746965460,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":84,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":87,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":12.7,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":4,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":70,\"Category\":\"Moderate\",\"CategoryValue\":2,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":12,\"Category\":\"Moderate\",\"CategoryValue\":2},{\"Name\":\"Mold\",\"Value\":3250,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":5,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":7,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":10,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":1,\"IconPhrase\":\"Sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Sunny\",\"LongPhrase\":\"Sunny\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":321,\"Localized\":\"NW\",\"English\":\"NW\"}},\"WindGust\":{\"Speed\":{\"Value\":21.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":321,\"Localized\":\"NW\",\"English\":\"NW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":9,\"Evapotranspiration\":{\"Value\":0.23,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8666.2,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":22,\"Maximum\":74,\"Average\":36},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":60,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":72,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":67,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly cloudy\",\"LongPhrase\":\"Partly cloudy\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":5.8,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":313,\"Localized\":\"NW\",\"English\":\"NW\"}},\"WindGust\":{\"Speed\":{\"Value\":18.4,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":318,\"Localized\":\"NW\",\"English\":\"NW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":30,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":199.8,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":35,\"Maximum\":71,\"Average\":58},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":55,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/us/san-jose-ca/95110/daily-weather-forecast/347630?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/san-jose-ca/95110/daily-weather-forecast/347630?day=1&lang=en-us\"}]}", - "la": "{\"Headline\":{\"EffectiveDate\":\"2025-05-10T08:00:00-07:00\",\"EffectiveEpochDate\":1746889200,\"Severity\":4,\"Text\":\"Record-breaking high temperatures today\",\"Category\":\"record heat\",\"EndDate\":\"2025-05-10T20:00:00-07:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/us/los-angeles-ca/90012/daily-weather-forecast/347625?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/los-angeles-ca/90012/daily-weather-forecast/347625?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00-07:00\",\"EpochDate\":1746885600,\"Sun\":{\"Rise\":\"2025-05-10T05:55:00-07:00\",\"EpochRise\":1746881700,\"Set\":\"2025-05-10T19:44:00-07:00\",\"EpochSet\":1746931440},\"Moon\":{\"Rise\":\"2025-05-10T18:17:00-07:00\",\"EpochRise\":1746926220,\"Set\":\"2025-05-11T05:03:00-07:00\",\"EpochSet\":1746964980,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":100,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"},\"Maximum\":{\"Value\":106,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Hot\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"},\"Maximum\":{\"Value\":98,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Hot\"}},\"HoursOfSun\":12.5,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":18,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":73,\"Category\":\"Moderate\",\"CategoryValue\":2,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":2,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":3250,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":50,\"Category\":\"Moderate\",\"CategoryValue\":2},{\"Name\":\"UVIndex\",\"Value\":11,\"Category\":\"Extreme\",\"CategoryValue\":5}],\"Day\":{\"Icon\":1,\"IconPhrase\":\"Sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Sunny; record-breaking heat\",\"LongPhrase\":\"Sunny and hot with the temperature breaking the record of 95 set in 1934; caution advised if outside for extended periods of time\",\"PrecipitationProbability\":1,\"ThunderstormProbability\":0,\"RainProbability\":1,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":228,\"Localized\":\"SW\",\"English\":\"SW\"}},\"WindGust\":{\"Speed\":{\"Value\":18.4,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":246,\"Localized\":\"WSW\",\"English\":\"WSW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":4,\"Evapotranspiration\":{\"Value\":0.27,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8761.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":18,\"Maximum\":52,\"Average\":26},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":71,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":74,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly cloudy\",\"LongPhrase\":\"Partly cloudy\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":160,\"Localized\":\"SSE\",\"English\":\"SSE\"}},\"WindGust\":{\"Speed\":{\"Value\":13.8,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":233,\"Localized\":\"SW\",\"English\":\"SW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":45,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":174.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":30,\"Maximum\":66,\"Average\":52},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":62,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":65,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":79,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":70,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/us/los-angeles-ca/90012/daily-weather-forecast/347625?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/los-angeles-ca/90012/daily-weather-forecast/347625?day=1&lang=en-us\"}]}", - "nyc": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00-04:00\",\"EffectiveEpochDate\":1746964800,\"Severity\":4,\"Text\":\"Pleasant tomorrow\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/us/new-york-ny/10021/daily-weather-forecast/14-349727_1_al?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/new-york-ny/10021/daily-weather-forecast/14-349727_1_al?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00-04:00\",\"EpochDate\":1746874800,\"Sun\":{\"Rise\":\"2025-05-10T05:44:00-04:00\",\"EpochRise\":1746870240,\"Set\":\"2025-05-10T20:01:00-04:00\",\"EpochSet\":1746921660},\"Moon\":{\"Rise\":\"2025-05-10T18:24:00-04:00\",\"EpochRise\":1746915840,\"Set\":\"2025-05-11T04:49:00-04:00\",\"EpochSet\":1746953340,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":71,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":74,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":67,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":9.8,\"DegreeDaySummary\":{\"Heating\":{\"Value\":1,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":54,\"Category\":\"Moderate\",\"CategoryValue\":2,\"Type\":\"Nitrogen Dioxide\"},{\"Name\":\"Grass\",\"Value\":2,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":5,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":50,\"Category\":\"Moderate\",\"CategoryValue\":2},{\"Name\":\"UVIndex\",\"Value\":9,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":3,\"IconPhrase\":\"Partly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Partly sunny; breezy, warmer\",\"LongPhrase\":\"Partly sunny, breezy and warmer\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":10.4,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":300,\"Localized\":\"WNW\",\"English\":\"WNW\"}},\"WindGust\":{\"Speed\":{\"Value\":24.2,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":296,\"Localized\":\"WNW\",\"English\":\"WNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":37,\"Evapotranspiration\":{\"Value\":0.15,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":7814.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":26,\"Maximum\":63,\"Average\":41},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":47,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":53,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":62,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear\",\"LongPhrase\":\"Clear\",\"PrecipitationProbability\":1,\"ThunderstormProbability\":0,\"RainProbability\":1,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":268,\"Localized\":\"W\",\"English\":\"W\"}},\"WindGust\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":281,\"Localized\":\"W\",\"English\":\"W\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":6,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":240.1,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":34,\"Maximum\":63,\"Average\":48},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":56,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/us/new-york-ny/10021/daily-weather-forecast/14-349727_1_al?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/us/new-york-ny/10021/daily-weather-forecast/14-349727_1_al?day=1&lang=en-us\"}]}", - "tokyo": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T19:00:00+09:00\",\"EffectiveEpochDate\":1746957600,\"Severity\":5,\"Text\":\"Rain Sunday night\",\"Category\":\"rain\",\"EndDate\":\"2025-05-12T07:00:00+09:00\",\"EndEpochDate\":1747000800,\"MobileLink\":\"http://www.accuweather.com/en/jp/tokyo/226396/daily-weather-forecast/226396?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/jp/tokyo/226396/daily-weather-forecast/226396?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-11T07:00:00+09:00\",\"EpochDate\":1746914400,\"Sun\":{\"Rise\":\"2025-05-11T04:40:00+09:00\",\"EpochRise\":1746906000,\"Set\":\"2025-05-11T18:35:00+09:00\",\"EpochSet\":1746956100},\"Moon\":{\"Rise\":\"2025-05-11T17:24:00+09:00\",\"EpochRise\":1746951840,\"Set\":\"2025-05-12T03:55:00+09:00\",\"EpochSet\":1746989700,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":62,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":58,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cool\"},\"Maximum\":{\"Value\":77,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":1,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":6,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":5,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":4,\"IconPhrase\":\"Intermittent clouds\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Breezy in the afternoon\",\"LongPhrase\":\"Sun through high clouds and less humid; breezy in the afternoon\",\"PrecipitationProbability\":9,\"ThunderstormProbability\":0,\"RainProbability\":9,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":10.4,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":261,\"Localized\":\"W\",\"English\":\"W\"}},\"WindGust\":{\"Speed\":{\"Value\":17.3,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":179,\"Localized\":\"S\",\"English\":\"S\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":99,\"Evapotranspiration\":{\"Value\":0.13,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":512,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":42,\"Maximum\":70,\"Average\":51},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":62,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":65,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":71,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":18,\"IconPhrase\":\"Rain\",\"HasPrecipitation\":true,\"PrecipitationType\":\"Rain\",\"PrecipitationIntensity\":\"Light\",\"ShortPhrase\":\"On-and-off rain and drizzle\",\"LongPhrase\":\"On-and-off rain and drizzle\",\"PrecipitationProbability\":84,\"ThunderstormProbability\":0,\"RainProbability\":84,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":9.2,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":233,\"Localized\":\"SW\",\"English\":\"SW\"}},\"WindGust\":{\"Speed\":{\"Value\":13.8,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":334,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0.09,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0.09,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":1.5,\"HoursOfRain\":1.5,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":100,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":9.8,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":58,\"Maximum\":85,\"Average\":73},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":61,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":60,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":67,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":63,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/jp/tokyo/226396/daily-weather-forecast/226396?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/jp/tokyo/226396/daily-weather-forecast/226396?day=1&lang=en-us\"}]}", - "warsaw": "{\"Headline\":{\"EffectiveDate\":\"2025-05-10T23:00:00+02:00\",\"EffectiveEpochDate\":1746910800,\"Severity\":5,\"Text\":\"Expect showers late Saturday evening\",\"Category\":\"rain\",\"EndDate\":\"2025-05-11T05:00:00+02:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/pl/warsaw/274663/daily-weather-forecast/274663?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/pl/warsaw/274663/daily-weather-forecast/274663?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T04:50:00+02:00\",\"EpochRise\":1746845400,\"Set\":\"2025-05-10T20:16:00+02:00\",\"EpochSet\":1746900960},\"Moon\":{\"Rise\":\"2025-05-10T18:14:00+02:00\",\"EpochRise\":1746893640,\"Set\":\"2025-05-11T03:54:00+02:00\",\"EpochSet\":1746928440,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":36,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":34,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cold\"},\"Maximum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":34,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cold\"},\"Maximum\":{\"Value\":45,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"}},\"HoursOfSun\":4.8,\"DegreeDaySummary\":{\"Heating\":{\"Value\":22,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":300,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":2,\"Category\":\"Low\",\"CategoryValue\":1}],\"Day\":{\"Icon\":12,\"IconPhrase\":\"Showers\",\"HasPrecipitation\":true,\"PrecipitationType\":\"Rain\",\"PrecipitationIntensity\":\"Light\",\"ShortPhrase\":\"Cold; spotty morning showers\",\"LongPhrase\":\"A few morning showers; otherwise, low clouds and cold\",\"PrecipitationProbability\":80,\"ThunderstormProbability\":16,\"RainProbability\":80,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":344,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"WindGust\":{\"Speed\":{\"Value\":27.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":350,\"Localized\":\"N\",\"English\":\"N\"}},\"TotalLiquid\":{\"Value\":0.05,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0.05,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":1.5,\"HoursOfRain\":1.5,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":88,\"Evapotranspiration\":{\"Value\":0.06,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":2939.2,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":59,\"Maximum\":94,\"Average\":74},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":47,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":49,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":39,\"IconPhrase\":\"Partly cloudy w/ showers\",\"HasPrecipitation\":true,\"PrecipitationType\":\"Rain\",\"PrecipitationIntensity\":\"Light\",\"ShortPhrase\":\"A shower or two this evening\",\"LongPhrase\":\"A couple of brief showers late this evening; clearing and chilly\",\"PrecipitationProbability\":49,\"ThunderstormProbability\":10,\"RainProbability\":49,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":5.8,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":355,\"Localized\":\"N\",\"English\":\"N\"}},\"WindGust\":{\"Speed\":{\"Value\":16.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":10,\"Localized\":\"N\",\"English\":\"N\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":1,\"HoursOfRain\":1,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":42,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":398.1,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":59,\"Maximum\":95,\"Average\":75},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":36,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":42,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":40,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":36,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/pl/warsaw/274663/daily-weather-forecast/274663?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/pl/warsaw/274663/daily-weather-forecast/274663?day=1&lang=en-us\"}]}", - "zurich": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T08:00:00+02:00\",\"EffectiveEpochDate\":1746943200,\"Severity\":4,\"Text\":\"Pleasant Sunday\",\"Category\":\"mild\",\"EndDate\":null,\"EndEpochDate\":null,\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:56:00+02:00\",\"EpochRise\":1746849360,\"Set\":\"2025-05-10T20:49:00+02:00\",\"EpochSet\":1746902940},\"Moon\":{\"Rise\":\"2025-05-10T18:54:00+02:00\",\"EpochRise\":1746896040,\"Set\":\"2025-05-11T04:58:00+02:00\",\"EpochSet\":1746932280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":66,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":12.3,\"DegreeDaySummary\":{\"Heating\":{\"Value\":10,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":517,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":8,\"Category\":\"Very High\",\"CategoryValue\":4}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and milder\",\"LongPhrase\":\"Mostly sunny and milder\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":62,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":70,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":21,\"Evapotranspiration\":{\"Value\":0.15,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8153.8,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":42,\"Maximum\":70,\"Average\":55},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":52,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":48,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and chilly\",\"LongPhrase\":\"Clear and chilly\",\"PrecipitationProbability\":3,\"ThunderstormProbability\":0,\"RainProbability\":3,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":3.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"WindGust\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":43,\"Localized\":\"NE\",\"English\":\"NE\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":6,\"Evapotranspiration\":{\"Value\":0.01,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":364.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":41,\"Maximum\":94,\"Average\":72},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":42,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":51,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":43,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":60,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ch/zurich/316622/daily-weather-forecast/316622?day=1&lang=en-us\"}]}", - "berlin": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T20:00:00+02:00\",\"EffectiveEpochDate\":1746986400,\"Severity\":7,\"Text\":\"Cool Sunday night\",\"Category\":\"cold\",\"EndDate\":\"2025-05-12T08:00:00+02:00\",\"EndEpochDate\":1747029600,\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+02:00\",\"EpochDate\":1746853200,\"Sun\":{\"Rise\":\"2025-05-10T05:19:00+02:00\",\"EpochRise\":1746847140,\"Set\":\"2025-05-10T20:47:00+02:00\",\"EpochSet\":1746902820},\"Moon\":{\"Rise\":\"2025-05-10T18:46:00+02:00\",\"EpochRise\":1746895560,\"Set\":\"2025-05-11T04:23:00+02:00\",\"EpochSet\":1746930180,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":68,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Chilly\"},\"Maximum\":{\"Value\":69,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Cold\"},\"Maximum\":{\"Value\":65,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Pleasant\"}},\"HoursOfSun\":6.4,\"DegreeDaySummary\":{\"Heating\":{\"Value\":9,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":330,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":3,\"Category\":\"Moderate\",\"CategoryValue\":2}],\"Day\":{\"Icon\":4,\"IconPhrase\":\"Intermittent clouds\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Times of clouds and sun\",\"LongPhrase\":\"Times of clouds and sun\",\"PrecipitationProbability\":25,\"ThunderstormProbability\":0,\"RainProbability\":25,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":359,\"Localized\":\"N\",\"English\":\"N\"}},\"WindGust\":{\"Speed\":{\"Value\":23,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":79,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":72,\"Evapotranspiration\":{\"Value\":0.1,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":4596.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":49,\"Maximum\":75,\"Average\":61},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":57,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":64,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":34,\"IconPhrase\":\"Mostly clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mainly clear\",\"LongPhrase\":\"Mainly clear\",\"PrecipitationProbability\":4,\"ThunderstormProbability\":0,\"RainProbability\":4,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":65,\"Localized\":\"ENE\",\"English\":\"ENE\"}},\"WindGust\":{\"Speed\":{\"Value\":19.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":90,\"Localized\":\"E\",\"English\":\"E\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":22,\"Evapotranspiration\":{\"Value\":0.02,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":453.7,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":60,\"Maximum\":75,\"Average\":67},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":41,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":54,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":46,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":44,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":59,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":50,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\",\"Link\":\"http://www.accuweather.com/en/de/berlin/10178/daily-weather-forecast/178087?day=1&lang=en-us\"}]}", - "dubai": "{\"Headline\":{\"EffectiveDate\":\"2025-05-11T01:00:00+04:00\",\"EffectiveEpochDate\":1746910800,\"Severity\":7,\"Text\":\"Warm late Saturday night\",\"Category\":\"heat\",\"EndDate\":\"2025-05-11T07:00:00+04:00\",\"EndEpochDate\":1746932400,\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"},\"DailyForecasts\":[{\"Date\":\"2025-05-10T07:00:00+04:00\",\"EpochDate\":1746846000,\"Sun\":{\"Rise\":\"2025-05-10T05:37:00+04:00\",\"EpochRise\":1746841020,\"Set\":\"2025-05-10T18:54:00+04:00\",\"EpochSet\":1746888840},\"Moon\":{\"Rise\":\"2025-05-10T17:04:00+04:00\",\"EpochRise\":1746882240,\"Set\":\"2025-05-11T04:28:00+04:00\",\"EpochSet\":1746923280,\"Phase\":\"WaxingGibbous\",\"Age\":13},\"Temperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":100,\"Unit\":\"F\",\"UnitType\":18}},\"RealFeelTemperature\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":108,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Dangerous Heat\"}},\"RealFeelTemperatureShade\":{\"Minimum\":{\"Value\":88,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Warm\"},\"Maximum\":{\"Value\":103,\"Unit\":\"F\",\"UnitType\":18,\"Phrase\":\"Very Hot\"}},\"HoursOfSun\":11.5,\"DegreeDaySummary\":{\"Heating\":{\"Value\":0,\"Unit\":\"F\",\"UnitType\":18},\"Cooling\":{\"Value\":26,\"Unit\":\"F\",\"UnitType\":18}},\"AirAndPollen\":[{\"Name\":\"AirQuality\",\"Value\":0,\"Category\":\"Good\",\"CategoryValue\":1,\"Type\":\"Ozone\"},{\"Name\":\"Grass\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Mold\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Ragweed\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"Tree\",\"Value\":0,\"Category\":\"Low\",\"CategoryValue\":1},{\"Name\":\"UVIndex\",\"Value\":12,\"Category\":\"Extreme\",\"CategoryValue\":5}],\"Day\":{\"Icon\":2,\"IconPhrase\":\"Mostly sunny\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Mostly sunny and very warm\",\"LongPhrase\":\"Mostly sunny and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":8.1,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":29,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":29.9,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":15,\"Evapotranspiration\":{\"Value\":0.26,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":8508.5,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":40,\"Maximum\":74,\"Average\":52},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":75,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":80,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":82,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":90,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18}}},\"Night\":{\"Icon\":33,\"IconPhrase\":\"Clear\",\"HasPrecipitation\":false,\"ShortPhrase\":\"Clear and very warm\",\"LongPhrase\":\"Clear and very warm\",\"PrecipitationProbability\":0,\"ThunderstormProbability\":0,\"RainProbability\":0,\"SnowProbability\":0,\"IceProbability\":0,\"Wind\":{\"Speed\":{\"Value\":4.6,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":25,\"Localized\":\"NNE\",\"English\":\"NNE\"}},\"WindGust\":{\"Speed\":{\"Value\":11.5,\"Unit\":\"mi/h\",\"UnitType\":9},\"Direction\":{\"Degrees\":340,\"Localized\":\"NNW\",\"English\":\"NNW\"}},\"TotalLiquid\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Rain\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Snow\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"Ice\":{\"Value\":0,\"Unit\":\"in\",\"UnitType\":1},\"HoursOfPrecipitation\":0,\"HoursOfRain\":0,\"HoursOfSnow\":0,\"HoursOfIce\":0,\"CloudCover\":0,\"Evapotranspiration\":{\"Value\":0.03,\"Unit\":\"in\",\"UnitType\":1},\"SolarIrradiance\":{\"Value\":141.9,\"Unit\":\"W/m²\",\"UnitType\":33},\"RelativeHumidity\":{\"Minimum\":44,\"Maximum\":74,\"Average\":59},\"WetBulbTemperature\":{\"Minimum\":{\"Value\":73,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":78,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":76,\"Unit\":\"F\",\"UnitType\":18}},\"WetBulbGlobeTemperature\":{\"Minimum\":{\"Value\":81,\"Unit\":\"F\",\"UnitType\":18},\"Maximum\":{\"Value\":86,\"Unit\":\"F\",\"UnitType\":18},\"Average\":{\"Value\":84,\"Unit\":\"F\",\"UnitType\":18}}},\"Sources\":[\"AccuWeather\"],\"MobileLink\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\",\"Link\":\"http://www.accuweather.com/en/ae/dubai/323091/daily-weather-forecast/323091?lang=en-us\"}]}", -} - var supportedLocations = map[string]*location{ "london": {nil, 51.507351, -0.127758, "Europe/London", "London"}, "sf": {nil, 37.774929, -122.419418, "America/Los_Angeles", "San Francisco"}, @@ -252,7 +239,7 @@ func startServer(port int) error { state.summaryChans[locKey] = c // listen for summary updates, and publish updates to all update subscribers via web push - go listenForSummaryUpdates(&state, locKey) + go listenForSummaryUpdates(&state, locKey, c) s.Start() @@ -612,10 +599,13 @@ func fetchInitialSummaries(state *state) { for locKey, loc := range supportedLocations { wg.Add(1) go func() { + ctx, cancel := context.WithCancel(state.ctx) + defer cancel() + defer wg.Done() summary := "" - rows, err := state.db.Query("SELECT summary FROM summaries WHERE location = ?", locKey) + rows, err := state.db.QueryContext(ctx, "SELECT summary FROM summaries WHERE location = ?", locKey) if err != nil && !errors.Is(err, sql.ErrNoRows) { slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) } else if err == nil { @@ -649,54 +639,49 @@ func updateSummary(ctx context.Context, state *state, opts updateSummaryOptions) today := time.Now().In(loc.tz) - var weatherJSON string - if state.usePlaceholder { - weatherJSON = placeholderWeather[locKey] - } else { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=%v&lon=%v", loc.lat, loc.lon), nil) - if err != nil { - slog.Error("failed to query weather data", "location", locKey, "error", err) - return - } - req.Header.Set("User-Agent", state.metAPIUserAgent) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - slog.Error("failed to query weather data", "location", locKey, "error", err) - return - } - - defer resp.Body.Close() - - data := metAPIData{} - err = json.NewDecoder(resp.Body).Decode(&data) - if err != nil { - slog.Error("failed to decode received weather data", "location", locKey, "error", err) - return - } - - y, m, d := today.Date() - - t := slices.DeleteFunc(data.Properties.TimeSeries, func(series map[string]any) bool { - if ts, ok := series["time"].(string); ok { - t, err := time.Parse(time.RFC3339, ts) - if err != nil { - return false - } - ty, tm, td := t.In(loc.tz).Date() - return !(y == ty && m == tm && d == td) - } - return false - }) - - b, err := json.Marshal(t) - if err != nil { - slog.Error("failed to marshal processed time series data", "location", locKey, "error", err) - return - } - - weatherJSON = string(b) + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=%v&lon=%v", loc.lat, loc.lon), nil) + if err != nil { + slog.Error("failed to query weather data", "location", locKey, "error", err) + return } + req.Header.Set("User-Agent", state.metAPIUserAgent) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + slog.Error("failed to query weather data", "location", locKey, "error", err) + return + } + + defer resp.Body.Close() + + data := metAPIData{} + err = json.NewDecoder(resp.Body).Decode(&data) + if err != nil { + slog.Error("failed to decode received weather data", "location", locKey, "error", err) + return + } + + y, m, d := today.Date() + + t := slices.DeleteFunc(data.Properties.TimeSeries, func(series map[string]any) bool { + if ts, ok := series["time"].(string); ok { + t, err := time.Parse(time.RFC3339, ts) + if err != nil { + return false + } + ty, tm, td := t.In(loc.tz).Date() + return !(y == ty && m == tm && d == td) + } + return false + }) + + b, err := json.Marshal(t) + if err != nil { + slog.Error("failed to marshal processed time series data", "location", locKey, "error", err) + return + } + + weatherJSON := string(b) result, err := state.genai.Models.GenerateContent(ctx, "gemini-2.0-flash", []*genai.Content{{ Parts: []*genai.Part{ @@ -728,9 +713,7 @@ func updateSummary(ctx context.Context, state *state, opts updateSummaryOptions) slog.Info("updated weather summary", "location", locKey) } -func listenForSummaryUpdates(state *state, locKey string) { - c := state.summaryChans[locKey] - +func listenForSummaryUpdates(state *state, locKey string, c <-chan string) { opts := webpush.Options{ Subscriber: state.vapidSubject, VAPIDPublicKey: state.vapidPublicKey, From cadeea7b585c6665f32a7146f7210ee5a2a8302e Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 19:14:42 +0100 Subject: [PATCH 07/13] fix rows are closed err on summary cache miss --- main.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 7a2a7ae..9b01d34 100644 --- a/main.go +++ b/main.go @@ -609,12 +609,14 @@ func fetchInitialSummaries(state *state) { if err != nil && !errors.Is(err, sql.ErrNoRows) { slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) } else if err == nil { - rows.Next() - err = rows.Scan(&summary) - if err != nil { - slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) + defer rows.Close() + ok := rows.Next() + if ok { + err = rows.Scan(&summary) + if err != nil { + slog.Warn("unable to get cached weather summary", "location", locKey, "error", err) + } } - rows.Close() } if summary == "" { From 373543834cff8d0ccfb8b4af3c216780661f24e4 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 19:43:24 +0100 Subject: [PATCH 08/13] improve prompt --- prompt.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/prompt.txt b/prompt.txt index bdf94e7..f57d06e 100644 --- a/prompt.txt +++ b/prompt.txt @@ -9,13 +9,13 @@ Here are some examples: Example 1: -JSON: [{"time":"2025-05-11T14:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.4,"air_temperature":17.8,"cloud_area_fraction":0,"relative_humidity":33.5,"wind_from_direction":55.2,"wind_speed":3.1}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T15:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.9,"air_temperature":17.9,"cloud_area_fraction":0,"relative_humidity":31.4,"wind_from_direction":48.3,"wind_speed":3.1}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T16:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.4,"air_temperature":17.6,"cloud_area_fraction":0,"relative_humidity":30.2,"wind_from_direction":43.1,"wind_speed":3.6}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T17:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.3,"air_temperature":16.5,"cloud_area_fraction":0,"relative_humidity":32.2,"wind_from_direction":46.6,"wind_speed":4}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.6,"air_temperature":15.2,"cloud_area_fraction":0,"relative_humidity":35.6,"wind_from_direction":55.1,"wind_speed":4.1}},"next_12_hours":{"summary":{"symbol_code":"fair_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.9,"air_temperature":13.7,"cloud_area_fraction":0,"relative_humidity":41.9,"wind_from_direction":61.1,"wind_speed":3.9}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":12.5,"cloud_area_fraction":0,"relative_humidity":46.9,"wind_from_direction":60.7,"wind_speed":2.9}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.2,"air_temperature":11.5,"cloud_area_fraction":0,"relative_humidity":51.2,"wind_from_direction":49.9,"wind_speed":3.9}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.6,"air_temperature":9.9,"cloud_area_fraction":0,"relative_humidity":57,"wind_from_direction":56.1,"wind_speed":3.9}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}}] -Summary: Weather in Berlin is forecast to be clear, with temperatures ranging from 10-18°C (50-64°F). No rain is expected, so you will not need an umbrella. Dress in layers to accommodate the temperature fluctuations, especially as it cools down in the evening. +JSON: [{"time":"2025-05-12T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1019.9,"air_temperature":11.6,"cloud_area_fraction":0,"relative_humidity":45.3,"wind_from_direction":77,"wind_speed":2.6}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-12T23:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.3,"air_temperature":10.7,"cloud_area_fraction":0,"relative_humidity":49.9,"wind_from_direction":74.4,"wind_speed":2.5}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T00:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.5,"air_temperature":9.7,"cloud_area_fraction":0,"relative_humidity":55.2,"wind_from_direction":59.3,"wind_speed":2.5}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T01:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.6,"air_temperature":8.9,"cloud_area_fraction":0,"relative_humidity":58.8,"wind_from_direction":66.3,"wind_speed":2.3}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T02:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.9,"air_temperature":8.2,"cloud_area_fraction":0,"relative_humidity":62.6,"wind_from_direction":57.9,"wind_speed":2.2}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T03:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021,"air_temperature":7.4,"cloud_area_fraction":0,"relative_humidity":67.2,"wind_from_direction":63.1,"wind_speed":2.1}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T04:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.2,"air_temperature":7.2,"cloud_area_fraction":0,"relative_humidity":69.3,"wind_from_direction":55.2,"wind_speed":1.9}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T05:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.4,"air_temperature":8.3,"cloud_area_fraction":0,"relative_humidity":65.3,"wind_from_direction":45.8,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T06:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.6,"air_temperature":10.6,"cloud_area_fraction":0,"relative_humidity":55.8,"wind_from_direction":56.2,"wind_speed":1.2}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T07:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.6,"air_temperature":13.5,"cloud_area_fraction":0,"relative_humidity":42.7,"wind_from_direction":65.8,"wind_speed":1}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T08:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.6,"air_temperature":15.7,"cloud_area_fraction":0,"relative_humidity":31.5,"wind_from_direction":55.4,"wind_speed":1.3}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T09:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.4,"air_temperature":16.9,"cloud_area_fraction":0,"relative_humidity":28.2,"wind_from_direction":53.5,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T10:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.3,"air_temperature":18,"cloud_area_fraction":0,"relative_humidity":26.5,"wind_from_direction":48.5,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T11:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1021.1,"air_temperature":18.9,"cloud_area_fraction":0,"relative_humidity":24.6,"wind_from_direction":45,"wind_speed":2}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T12:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.6,"air_temperature":19.7,"cloud_area_fraction":0,"relative_humidity":22.4,"wind_from_direction":49.2,"wind_speed":1.9}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T13:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1020.1,"air_temperature":20.2,"cloud_area_fraction":0.8,"relative_humidity":21.6,"wind_from_direction":51.7,"wind_speed":2}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T14:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1019.2,"air_temperature":20.4,"cloud_area_fraction":18,"relative_humidity":22,"wind_from_direction":55.2,"wind_speed":2.1}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T15:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.7,"air_temperature":20.4,"cloud_area_fraction":13.3,"relative_humidity":22.8,"wind_from_direction":54.8,"wind_speed":2.3}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T16:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.4,"air_temperature":20.3,"cloud_area_fraction":38.3,"relative_humidity":24.1,"wind_from_direction":58.5,"wind_speed":2.4}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T17:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":19.7,"cloud_area_fraction":100,"relative_humidity":26.4,"wind_from_direction":58.4,"wind_speed":2.1}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.9,"air_temperature":19,"cloud_area_fraction":100,"relative_humidity":28.4,"wind_from_direction":67.2,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":17.6,"cloud_area_fraction":100,"relative_humidity":33.3,"wind_from_direction":60.9,"wind_speed":1}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.1,"air_temperature":16.7,"cloud_area_fraction":100,"relative_humidity":36.5,"wind_from_direction":334.8,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":15.8,"cloud_area_fraction":57,"relative_humidity":39.8,"wind_from_direction":317.3,"wind_speed":1.3}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}}}}] +Summary: Berlin will have clear skies in the morning, turning partly cloudy in the afternoon. The high will be around 20°C (68°F). Light winds from the east. No rain is expected, so you can leave your umbrella at home. Example 2: -JSON: [{"time":"2025-05-11T14:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015,"air_temperature":11.7,"cloud_area_fraction":26.6,"relative_humidity":84.6,"wind_from_direction":264.7,"wind_speed":6.1}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T15:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.7,"air_temperature":12.2,"cloud_area_fraction":25,"relative_humidity":83.6,"wind_from_direction":260.7,"wind_speed":6.3}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T16:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1016.1,"air_temperature":12.9,"cloud_area_fraction":28.1,"relative_humidity":79.3,"wind_from_direction":264,"wind_speed":7}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T17:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1016.7,"air_temperature":13.4,"cloud_area_fraction":52.3,"relative_humidity":74.8,"wind_from_direction":266.8,"wind_speed":7.1}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.3,"air_temperature":14,"cloud_area_fraction":6.2,"relative_humidity":67.3,"wind_from_direction":259.3,"wind_speed":6.6}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.7,"air_temperature":14.4,"cloud_area_fraction":22.7,"relative_humidity":62.6,"wind_from_direction":257.9,"wind_speed":7.1}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.7,"air_temperature":14.5,"cloud_area_fraction":0,"relative_humidity":62.7,"wind_from_direction":261.9,"wind_speed":7.3}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.5,"air_temperature":14.5,"cloud_area_fraction":0,"relative_humidity":60.6,"wind_from_direction":261.1,"wind_speed":7.1}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-11T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017,"air_temperature":14.4,"cloud_area_fraction":0.8,"relative_humidity":58.4,"wind_from_direction":257.7,"wind_speed":6.8}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}}] -Summary: The weather in San Francisco will be fair with some clouds. Temperatures range from 11.7°C to 14.5°C (53°F to 58°F). No rain is expected, so an umbrella is not needed. Wear layers and bring a light jacket. +JSON: [{"time":"2025-05-13T07:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.7,"air_temperature":11.5,"cloud_area_fraction":37.5,"relative_humidity":85.2,"wind_from_direction":283.6,"wind_speed":6.6}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T08:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.7,"air_temperature":11.4,"cloud_area_fraction":35.2,"relative_humidity":85.1,"wind_from_direction":284.9,"wind_speed":6.8}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T09:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.6,"air_temperature":11.4,"cloud_area_fraction":34.4,"relative_humidity":85.6,"wind_from_direction":285.6,"wind_speed":6.3}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T10:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.5,"air_temperature":11.2,"cloud_area_fraction":23.4,"relative_humidity":86.1,"wind_from_direction":282.5,"wind_speed":6.3}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T11:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.4,"air_temperature":11,"cloud_area_fraction":31.2,"relative_humidity":85.7,"wind_from_direction":284.5,"wind_speed":6.3}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T12:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1015.8,"air_temperature":10.8,"cloud_area_fraction":24.2,"relative_humidity":87.1,"wind_from_direction":293.1,"wind_speed":5.7}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T13:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1016.1,"air_temperature":10.6,"cloud_area_fraction":16.4,"relative_humidity":88.5,"wind_from_direction":297,"wind_speed":4.6}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T14:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1016.8,"air_temperature":10.9,"cloud_area_fraction":21.1,"relative_humidity":86,"wind_from_direction":297.7,"wind_speed":4}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T15:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.4,"air_temperature":11.7,"cloud_area_fraction":35.2,"relative_humidity":79.4,"wind_from_direction":293.7,"wind_speed":3.6}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T16:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.8,"air_temperature":12.9,"cloud_area_fraction":35.9,"relative_humidity":71.4,"wind_from_direction":321.9,"wind_speed":2.8}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T17:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.2,"air_temperature":14.3,"cloud_area_fraction":35.9,"relative_humidity":64.2,"wind_from_direction":166.4,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.5,"air_temperature":15.5,"cloud_area_fraction":15.6,"relative_humidity":59.7,"wind_from_direction":198.8,"wind_speed":2.4}},"next_12_hours":{"summary":{"symbol_code":"fair_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.7,"air_temperature":16.2,"cloud_area_fraction":18.7,"relative_humidity":57.5,"wind_from_direction":231.4,"wind_speed":4.1}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.7,"air_temperature":16,"cloud_area_fraction":15.6,"relative_humidity":59,"wind_from_direction":251.9,"wind_speed":5.1}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.6,"air_temperature":15.9,"cloud_area_fraction":17.2,"relative_humidity":58.9,"wind_from_direction":255.6,"wind_speed":5.5}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.3,"air_temperature":16.7,"cloud_area_fraction":42.2,"relative_humidity":56.7,"wind_from_direction":257.9,"wind_speed":6}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T23:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":17.2,"cloud_area_fraction":28.1,"relative_humidity":54.7,"wind_from_direction":266.4,"wind_speed":6.4}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T00:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.5,"air_temperature":17.1,"cloud_area_fraction":16.4,"relative_humidity":55.1,"wind_from_direction":267.7,"wind_speed":6.3}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T01:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.2,"air_temperature":16.8,"cloud_area_fraction":18,"relative_humidity":58.8,"wind_from_direction":271.9,"wind_speed":6.5}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T02:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.1,"air_temperature":15.4,"cloud_area_fraction":1.6,"relative_humidity":66.6,"wind_from_direction":270.3,"wind_speed":6.6}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T03:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.1,"air_temperature":13.8,"cloud_area_fraction":1.6,"relative_humidity":73.6,"wind_from_direction":263.7,"wind_speed":6.7}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T04:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1017.5,"air_temperature":13.1,"cloud_area_fraction":0,"relative_humidity":76.5,"wind_from_direction":264.6,"wind_speed":6.8}},"next_12_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T05:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018,"air_temperature":13.2,"cloud_area_fraction":0,"relative_humidity":74.3,"wind_from_direction":285.9,"wind_speed":7.4}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-14T06:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":1018.3,"air_temperature":12.9,"cloud_area_fraction":0,"relative_humidity":75.4,"wind_from_direction":286.5,"wind_speed":6.4}},"next_12_hours":{"summary":{"symbol_code":"clearsky_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"clearsky_night"},"details":{"precipitation_amount":0}}}}] +Summary: San Francisco will be mostly fair with some clouds in the morning, becoming clearer later. The high will be around 17°C (63°F). Expect a moderate breeze from the west. No rain is expected, so no umbrella needed. Example 3: @@ -24,5 +24,5 @@ Summary: Expect a very rainy day in Miami. Light rain will quickly intensify to Example 4: -JSON: [{"time":"2025-05-12T00:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996,"air_temperature":-23.3,"cloud_area_fraction":100,"relative_humidity":81.1,"wind_from_direction":12.5,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"fog"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T01:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.4,"air_temperature":-23.6,"cloud_area_fraction":100,"relative_humidity":82.9,"wind_from_direction":32.2,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"fog"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T02:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.6,"air_temperature":-23.9,"cloud_area_fraction":100,"relative_humidity":82.9,"wind_from_direction":56.6,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T03:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.6,"air_temperature":-23.9,"cloud_area_fraction":100,"relative_humidity":82.4,"wind_from_direction":49.6,"wind_speed":1.8}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T04:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.5,"air_temperature":-23.7,"cloud_area_fraction":100,"relative_humidity":80.9,"wind_from_direction":48.1,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T05:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.7,"air_temperature":-23.7,"cloud_area_fraction":100,"relative_humidity":80.4,"wind_from_direction":45.1,"wind_speed":1.8}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T06:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.8,"air_temperature":-24.1,"cloud_area_fraction":100,"relative_humidity":82.9,"wind_from_direction":48.8,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T07:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.8,"air_temperature":-24.4,"cloud_area_fraction":100,"relative_humidity":84.5,"wind_from_direction":86.8,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T08:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.9,"air_temperature":-24.6,"cloud_area_fraction":100,"relative_humidity":84.7,"wind_from_direction":92.5,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T09:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.1,"air_temperature":-24.5,"cloud_area_fraction":100,"relative_humidity":83.3,"wind_from_direction":67.6,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T10:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.2,"air_temperature":-24.5,"cloud_area_fraction":100,"relative_humidity":83.5,"wind_from_direction":45.8,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T11:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.2,"air_temperature":-24.6,"cloud_area_fraction":100,"relative_humidity":83.7,"wind_from_direction":31.1,"wind_speed":1.8}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T12:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.6,"air_temperature":-24.9,"cloud_area_fraction":100,"relative_humidity":84.8,"wind_from_direction":32.9,"wind_speed":1.8}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T13:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.5,"air_temperature":-25.1,"cloud_area_fraction":100,"relative_humidity":84.4,"wind_from_direction":29.4,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T14:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.5,"air_temperature":-25.4,"cloud_area_fraction":100,"relative_humidity":83.8,"wind_from_direction":33,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T15:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.3,"air_temperature":-25.6,"cloud_area_fraction":100,"relative_humidity":82.8,"wind_from_direction":30.7,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T16:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.3,"air_temperature":-26.1,"cloud_area_fraction":100,"relative_humidity":82.2,"wind_from_direction":35.9,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T17:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.5,"air_temperature":-26.3,"cloud_area_fraction":98.4,"relative_humidity":81.3,"wind_from_direction":26.2,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.5,"air_temperature":-26.8,"cloud_area_fraction":99.2,"relative_humidity":81,"wind_from_direction":19.8,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.4,"air_temperature":-26.6,"cloud_area_fraction":100,"relative_humidity":79.2,"wind_from_direction":11.1,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.4,"air_temperature":-26.8,"cloud_area_fraction":98.4,"relative_humidity":78.5,"wind_from_direction":4.2,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-12T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.7,"air_temperature":-27.1,"cloud_area_fraction":99.2,"relative_humidity":77.6,"wind_from_direction":359.3,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-12T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.8,"air_temperature":-27,"cloud_area_fraction":99.2,"relative_humidity":76.5,"wind_from_direction":1.3,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-12T23:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":997.8,"air_temperature":-27.2,"cloud_area_fraction":98.4,"relative_humidity":76.7,"wind_from_direction":358.1,"wind_speed":1.2}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0.1}}}}] -Summary: In Ross Island, the weather is foggy and extremely cold with a temperature of -24.4°C (-11.9°F). Very light precipitation is possible. Dress in heavy, layered winter clothing covering all exposed skin. An umbrella is not needed. +JSON: [{"time":"2025-05-12T18:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.4,"air_temperature":-26.4,"cloud_area_fraction":100,"relative_humidity":79.1,"wind_from_direction":333.7,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-12T19:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.4,"air_temperature":-26.2,"cloud_area_fraction":100,"relative_humidity":78.9,"wind_from_direction":345.8,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-12T20:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.4,"air_temperature":-26.4,"cloud_area_fraction":100,"relative_humidity":78.6,"wind_from_direction":2.5,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-12T21:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.5,"air_temperature":-26.7,"cloud_area_fraction":100,"relative_humidity":78.6,"wind_from_direction":21.3,"wind_speed":1.4}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-12T22:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.7,"air_temperature":-27.2,"cloud_area_fraction":100,"relative_humidity":78,"wind_from_direction":26.1,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-12T23:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.6,"air_temperature":-27.6,"cloud_area_fraction":98.4,"relative_humidity":77.6,"wind_from_direction":34.3,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T00:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.4,"air_temperature":-27.9,"cloud_area_fraction":97.7,"relative_humidity":77.9,"wind_from_direction":36,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T01:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":996.3,"air_temperature":-28,"cloud_area_fraction":96.1,"relative_humidity":77.7,"wind_from_direction":37.2,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T02:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":995.8,"air_temperature":-28.2,"cloud_area_fraction":89.8,"relative_humidity":78.3,"wind_from_direction":41.7,"wind_speed":1.7}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}}}},{"time":"2025-05-13T03:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":995.4,"air_temperature":-28.3,"cloud_area_fraction":82,"relative_humidity":78.9,"wind_from_direction":40.1,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"partlycloudy_day"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0.1}}}},{"time":"2025-05-13T04:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":995.1,"air_temperature":-28.3,"cloud_area_fraction":35.9,"relative_humidity":78.3,"wind_from_direction":42.9,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fair_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.2}}}},{"time":"2025-05-13T05:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":995,"air_temperature":-28.4,"cloud_area_fraction":66.4,"relative_humidity":78,"wind_from_direction":35.1,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"partlycloudy_night"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-13T06:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":994.8,"air_temperature":-28.4,"cloud_area_fraction":96.9,"relative_humidity":76.9,"wind_from_direction":43.2,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.3}}}},{"time":"2025-05-13T07:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":994.8,"air_temperature":-28.2,"cloud_area_fraction":100,"relative_humidity":75.4,"wind_from_direction":36.5,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-13T08:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":994.6,"air_temperature":-27.7,"cloud_area_fraction":100,"relative_humidity":73.6,"wind_from_direction":37,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-13T09:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":994.4,"air_temperature":-27.3,"cloud_area_fraction":100,"relative_humidity":72.2,"wind_from_direction":42.3,"wind_speed":1.5}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-13T10:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":994,"air_temperature":-27.2,"cloud_area_fraction":100,"relative_humidity":72.2,"wind_from_direction":28.2,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"cloudy"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}},{"time":"2025-05-13T11:00:00Z","data":{"instant":{"details":{"air_pressure_at_sea_level":993.9,"air_temperature":-27.4,"cloud_area_fraction":100,"relative_humidity":73.2,"wind_from_direction":26.7,"wind_speed":1.6}},"next_12_hours":{"summary":{"symbol_code":"cloudy"},"details":{}},"next_1_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0}},"next_6_hours":{"summary":{"symbol_code":"fog"},"details":{"precipitation_amount":0.4}}}}] +Summary: In Ross Island, expect cloudy skies and very cold temperatures around -27°C (-17°F). Light winds. There's a chance of very light precipitation. Dress warmly in many layers. An umbrella is not needed. From 49d725a4df71841626d555728cbdaa0db484aeea Mon Sep 17 00:00:00 2001 From: Kenneth Date: Mon, 12 May 2025 20:09:31 +0100 Subject: [PATCH 09/13] enable write ahead log --- main.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 9b01d34..137c1e6 100644 --- a/main.go +++ b/main.go @@ -81,12 +81,12 @@ type updateSummaryOptions struct { type state struct { ctx context.Context - db *sql.DB metAPIUserAgent string genai *genai.Client template pageTemplate - usePlaceholder bool + db *sql.DB + dbMutex sync.Mutex // summaries maps location keys to their latest weather summary summaries sync.Map @@ -189,7 +189,6 @@ func startServer(port int) error { state := state{ ctx: ctx, - db: db, metAPIUserAgent: os.Getenv("MET_API_USER_AGENT"), template: pageTemplate{ summary: summaryPageTemplate, @@ -198,7 +197,8 @@ func startServer(port int) error { summaryChans: map[string]chan string{}, genai: genaiClient, - usePlaceholder: false, + db: db, + dbMutex: sync.Mutex{}, subscriptions: map[string][]*registeredSubscription{}, @@ -425,6 +425,9 @@ func initDB() (*sql.DB, error) { } _, err = db.Exec(` + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + CREATE TABLE IF NOT EXISTS subscriptions( id TEXT PRIMARY KEY, locations TEXT NOT NULL, From 054910811458b62ea9af2292fca1a0d4858156d0 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Tue, 13 May 2025 00:45:26 +0100 Subject: [PATCH 10/13] add support for stockholm and amsterdam --- main.go | 24 +++++++++++++----------- web/index.html | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 137c1e6..880df92 100644 --- a/main.go +++ b/main.go @@ -116,17 +116,19 @@ var envKeys = []string{"GEMINI_API_KEY", "MET_API_USER_AGENT", "VAPID_SUBJECT", var prompt string var supportedLocations = map[string]*location{ - "london": {nil, 51.507351, -0.127758, "Europe/London", "London"}, - "sf": {nil, 37.774929, -122.419418, "America/Los_Angeles", "San Francisco"}, - "sj": {nil, 37.338207, -121.886330, "America/Los_Angeles", "San Jose"}, - "la": {nil, 34.052235, -118.243683, "America/Los_Angeles", "Los Angeles"}, - "nyc": {nil, 40.712776, -74.005974, "America/New_York", "New York City"}, - "tokyo": {nil, 35.689487, 139.691711, "Asia/Tokyo", "Tokyo"}, - "warsaw": {nil, 52.229675, 21.012230, "Europe/Warsaw", "Warsaw"}, - "zurich": {nil, 47.369019, 8.538030, "Europe/Zurich", "Zurich"}, - "berlin": {nil, 52.520008, 13.404954, "Europe/Berlin", "Berlin"}, - "dubai": {nil, 25.204849, 55.270782, "Asia/Dubai", "Dubai"}, - "paris": {nil, 48.864716, 2.349014, "Europe/Paris", "Paris"}, + "london": {nil, 51.507351, -0.127758, "Europe/London", "London"}, + "sf": {nil, 37.774929, -122.419418, "America/Los_Angeles", "San Francisco"}, + "sj": {nil, 37.338207, -121.886330, "America/Los_Angeles", "San Jose"}, + "la": {nil, 34.052235, -118.243683, "America/Los_Angeles", "Los Angeles"}, + "nyc": {nil, 40.712776, -74.005974, "America/New_York", "New York City"}, + "tokyo": {nil, 35.689487, 139.691711, "Asia/Tokyo", "Tokyo"}, + "warsaw": {nil, 52.229675, 21.012230, "Europe/Warsaw", "Warsaw"}, + "zurich": {nil, 47.369019, 8.538030, "Europe/Zurich", "Zurich"}, + "berlin": {nil, 52.520008, 13.404954, "Europe/Berlin", "Berlin"}, + "dubai": {nil, 25.204849, 55.270782, "Asia/Dubai", "Dubai"}, + "paris": {nil, 48.864716, 2.349014, "Europe/Paris", "Paris"}, + "stockholm": {nil, 59.329323, 18.068581, "Europe/Stockholm", "Stockholm"}, + "amsterdam": {nil, 52.370216, 4.895168, "Europe/Amsterdam", "Amsterdam"}, } func main() { diff --git a/web/index.html b/web/index.html index de99890..f337992 100644 --- a/web/index.html +++ b/web/index.html @@ -48,6 +48,8 @@
  • Paris
  • Berlin
  • Zurich
  • +
  • Stockholm
  • +
  • Amsterdam
  • Warsaw
  • Dubai
  • Tokyo
  • From a2a2d008dddc6c526c22d7a7c4ec14dc2f9fca97 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Tue, 13 May 2025 00:51:22 +0100 Subject: [PATCH 11/13] fix sql conflict on summary cache update --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 880df92..14e2090 100644 --- a/main.go +++ b/main.go @@ -703,7 +703,7 @@ func updateSummary(ctx context.Context, state *state, opts updateSummaryOptions) summary := result.Text() - _, err = state.db.ExecContext(ctx, "INSERT INTO summaries (location, summary) VALUES (?, ?)", locKey, summary) + _, err = state.db.ExecContext(ctx, "INSERT OR REPLACE INTO summaries (location, summary) VALUES (?, ?)", locKey, summary) if err != nil { slog.Warn("unable to cache generated weather summary to db", "location", locKey, "error", err) } From 28a33f869b6282e409446f99527b7e2ae5c1594c Mon Sep 17 00:00:00 2001 From: Kenneth Date: Tue, 13 May 2025 00:52:16 +0100 Subject: [PATCH 12/13] use mutex to guard sqlite writes --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 14e2090..eb96e2b 100644 --- a/main.go +++ b/main.go @@ -703,10 +703,12 @@ func updateSummary(ctx context.Context, state *state, opts updateSummaryOptions) summary := result.Text() + state.dbMutex.Lock() _, err = state.db.ExecContext(ctx, "INSERT OR REPLACE INTO summaries (location, summary) VALUES (?, ?)", locKey, summary) if err != nil { slog.Warn("unable to cache generated weather summary to db", "location", locKey, "error", err) } + state.dbMutex.Unlock() state.summaries.Store(locKey, summary) From 7750c0d75e530e5582a9ba767eeff82099254a4a Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 14 May 2025 13:16:31 +0100 Subject: [PATCH 13/13] Add support for Singapore (#2) Co-authored-by: sevensxyt Reviewed-on: https://github.com/kennethnym/7am/pull/2 --- main.go | 1 + web/index.html | 1 + 2 files changed, 2 insertions(+) diff --git a/main.go b/main.go index eb96e2b..cf7dfdf 100644 --- a/main.go +++ b/main.go @@ -122,6 +122,7 @@ var supportedLocations = map[string]*location{ "la": {nil, 34.052235, -118.243683, "America/Los_Angeles", "Los Angeles"}, "nyc": {nil, 40.712776, -74.005974, "America/New_York", "New York City"}, "tokyo": {nil, 35.689487, 139.691711, "Asia/Tokyo", "Tokyo"}, + "singapore": {nil, 1.290270, 103.851959, "Asia/Singapore", "Singapore"}, "warsaw": {nil, 52.229675, 21.012230, "Europe/Warsaw", "Warsaw"}, "zurich": {nil, 47.369019, 8.538030, "Europe/Zurich", "Zurich"}, "berlin": {nil, 52.520008, 13.404954, "Europe/Berlin", "Berlin"}, diff --git a/web/index.html b/web/index.html index f337992..e0ca414 100644 --- a/web/index.html +++ b/web/index.html @@ -53,6 +53,7 @@
  • Warsaw
  • Dubai
  • Tokyo
  • +
  • Singapore