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, and the days the fill logged that still lack a measure (weatherToRefill), as runs of up to a year, one request each (weatherGaps).
  • 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.
  • Measures that come later: POWER fills its measures at different lags. At Toronto on 2026-10-22 temperature was 3 days behind, sunshine 5, and cloud cover and UV index 84, so a day is logged as soon as it has any measure and the rest are added to its entry as they arrive (weatherRefilled), keeping every measure it had. That stops POWER_SETTLES_DAYS (120) after the day (powerSettled in packages/core/src/power.ts): a measure missing by then never comes, as cloud cover before 1984 does not.
  • 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 you logged, or whose entry you changed, is never touched; the fill only adds measures to an entry still as it wrote it, logged_by NASA POWER (Logged for You), and never changes one it has. 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.
  • Every day is kept, and an unfinished one refreshed: a day POWER has not filled every measure of (powerDayComplete) is served from the cache for a day (REFRESH_MS), then asked for again, until POWER has filled it or it has settled (powerSettled). So the newest days, which every person in a city asks for every few hours, reach POWER once a day per grid point.
  • 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