Weather is logged for you by a source rather than by a person (Logged for You). Once a place is set, each day’s weather arrives as an ordinary Weather entry, so it can be charted beside sleep or mood and read against anything else, works offline once filled, and syncs like the rest of the profile. It is filed under External, the first-party tag for what is not about you (Modules).

The source is NASA POWER, a free daily archive with no key: powerUrl in packages/core/src/power.ts builds its daily point request, https://power.larc.nasa.gov/api/temporal/daily/point, for a latitude and longitude, every measure at once, from 1981 on, in local solar time. It is POWER rather than Open-Meteo because Open-Meteo’s free API is for non-commercial use only.

The Weather trackable

The Weather module makes one trackable, Weather (weatherTrackable): a record of every measure POWER gives, each an optional number field, since POWER fills some later than others. POWER gives measures rather than a condition, so there is no “sunny” or “rainy” field.

FieldPOWER parameterStored as
Temperature, High, Low, Dew pointT2M, T2M_MAX, T2M_MIN, T2MDEWa temperature
PrecipitationPRECTOTCORRa distance, shown in mm
HumidityRH2M%
Wind, Strongest windWS2M, WS2M_MAXa speed, km/h or mph by preference
SunshineALLSKY_SFC_SW_DWNMJ/m²
Cloud coverCLOUD_AMT%
PressurePSa pressure, hPa
Snow depthSNODPa distance, shown in cm
UV indexALLSKY_SFC_UV_INDEXa number

Speed and pressure are dimensions for this (Value Types). The trackable is asked nowhere, since nobody logs it, and its own Reading is a Count. Its capture names its source, { kind: "nasa-power", place }.

The place

A place is picked from a list of cities bundled with the app and loaded only while a place is being picked: GeoNames’ cities of over 15,000 people, trimmed to name, country, coordinates, population, time zone and region, the first-level division from GeoNames’ admin1 codes (pnpm run db:gen-cities writes it, about 2.1 MB, with the region names in a 40 KB list beside it that each city points into). A place shows as its name, region and country (“London, Ontario, Canada”), and a search narrows by region or country after a comma (“london, ont”); components/weather/cities.ts holds the search. Two cities of one name in one region are told apart by time zone and size. No location permission is asked. The trackable’s page shows the place, how far the weather is filled, and the credits both sources ask for. The credit shows wherever their data does too, which is every screen that shows a Weather value: under Weather’s card and charts (one drawing for a card, the editor and its page), under a Visualization that draws one of its series, on an entry it logged, under the Table and Home’s Recents when they list one of its entries, and in the Calendar, under a day’s entries and under the Week, 3-day and Day grid that draws them at their times. A trackable worked out from Weather credits it too, at any depth of formulas (WeatherCredit and fromWeather in apps/renderer/src/components/weather/WeatherCredit.tsx, the one wording).

The place is where new days are filled for. Changing it, after a move or for a trip, fills the days not yet filled for the new place; a day already filled keeps the weather it was filled with, and the page says so rather than naming every day the new place’s. A day does not yet record its own place, so it cannot be fetched again for another one.

The fill

While the app is open, useWeatherFill fills every trackable that has a place: when the app opens, and every three hours after.

  • Which days: from the profile’s first entry to today, the days with no entry of that trackable and not deleted (weatherGaps), as runs of up to a year, one request each.
  • What a day becomes: POWER’s values for that day in the fields’ base units (powerDays); a measure it has not got yet (−999) is left out, and a day with none at all is left for a later fill, since POWER lags a few days behind and some measures lag more than others.
  • The entry: one per day at midday, since it is about the whole day, with the id nasa-power:<trackable>:<day> and logged_by NASA POWER (weatherEntry), so two devices filling one day write one row.
  • Yours wins: a day already logged, by hand or by the fill, is never overwritten, and a day whose entry was deleted is not fetched again.
  • Where it asks: a device signed in to the sync service asks the service’s shared cache (below); one that is not asks POWER directly. Either answer has POWER’s own shape, so the rest of the fill reads both the same (askWeather in apps/renderer/src/hooks/useWeatherFill.ts).

The shared cache

The sync service keeps the weather it has fetched, so many people in one city share one request to POWER. GET /v1/weather?lat&lon&from&to is for a signed-in device only (401 otherwise), at most 366 days a request, and answers in POWER’s own shape (weatherFor in apps/sync/src/weather.ts).

  • Keyed by grid point: a place is moved to the nearest point of POWER’s half-degree grid (powerCell), and the cache keeps each day of each point as a weather_day row in D1.
  • One request for what is missing: the service reads the days it has, then asks POWER once, at the grid point, for the run from the first day it lacks to the last.
  • Only complete days are kept: a day POWER has not filled every measure of (powerDayComplete) is passed on but not kept, so the next request asks for it again until POWER has it all.
  • Without POWER: a service with no route out answers 503, and the app fills nothing that run; the next run tries again.

The parameters both sides ask for are one list, POWER_PARAMETERS in packages/core/src/power.ts, which the Weather fields read from and the Worker imports on its own (@datler/core/power).

In the demo profiles

Chad’s Weather is set to Toronto when his profile is built, and the app fills his year from POWER the first time it opens with a connection; offline he simply has none.

Logged for You · Schedules · Modules · Value Types