How fresh the data is
Nothing in Sunset Beach is fetched live when you open the app. A cron job on
the Worker runs every 5 minutes, refreshes every upstream whose cache has
expired, and publishes one assembled /api/conditions payload that every phone
and browser then reads. So a reading is at most its source’s cache lifetime
plus up to two cron ticks old — for the buoys that means roughly 5–15 minutes,
for tides up to 6 hours (they are predictions, so it doesn’t matter), and for
a fishing report up to an hour. When an upstream is down, the Worker serves the
last copy that worked and labels it — “Cached” on the Sources screen,
“Serving last known good” in the row’s detail — rather than showing nothing
or passing old data off as live.
The pipeline, end to end
Section titled “The pipeline, end to end”In prose: the cron fetches each upstream through the same code path a reader
would, honouring that source’s cache lifetime; a successful fetch is stored in
the edge cache and also written to KV as the last-known-good copy; the cron
then assembles the whole conditions payload once and publishes it to KV, and
every request for /api/conditions serves that copy verbatim. If an upstream
fails or returns something unparseable, the KV copy is served in its place with
stale: true and a reason. The widget reads the smaller /api/summary, built
from the same cached sources.
How often each source is refreshed
Section titled “How often each source is refreshed”The lifetime is set by what the upstream actually does. The full per-source table is on Where every number comes from; this is the shape of it.
| Source | Refresh | Why |
|---|---|---|
| NWS active alerts | 2 min | safety feed |
| NDBC buoys 41024, 41013, 41110 | 5 min | stations post every ~10 min |
| NWS gridpoint forecast | 10 min | amended forecasts show up quickly |
| Springmaid Pier surge, BirdCast bucket listing | 10 min | 6-minute and 10-minute upstreams |
| Open-Meteo sky, marine; surf zone forecast index; coastal waters index | 15 min | hourly models, products issued a few times a day |
| Surf zone forecast product, air quality | 30 min | |
| eBird, pier fishing report, tropical outlook, sewer spills, coastal waters product | 1 h | |
| Town calendar ICS, shellfish closures, right whale slow zones | 3 h | |
| Launch Library 2 | 4 h, or 5 min while a visible launch is within T−75 to T+20 min | rate-limited upstream; the short window is what lets a slip reach a Live Activity |
| Tides, turtle counts, water quality, iNaturalist recent, proclamations, nest-database canary | 6 h | deterministic or slow-moving |
| Pier concert page, seasonal whale zones | 12 h | |
| Sound Waves page, iNaturalist species counts | 24 h | |
| OBIS, iNaturalist megafauna, BirdCast objects, shellfish classification | 7 d | historical, immutable, or a standing category |
| Right whale critical habitat | 30 d | a 2016 regulatory boundary |
The published conditions copy
Section titled “The published conditions copy”/api/conditions is not rebuilt per request. The cron assembles it once per
tick and publishes it to KV; readers serve that copy until it is 10 minutes
old (twice the cron cadence, so an ordinary KV lag never forces a rebuild),
after which a request recomputes it. Every JSON response carries
Cache-Control: public, max-age=60 and an ETag over the exact bytes, so a
refresh more than a minute after the last one revalidates against the copy the
phone already holds and, most of the time, gets a 304 with no body. The practical effect: the interpolated tide height, countdowns and the
sun’s position in the payload can lag by up to two ticks, and generatedAt
tells you exactly when the copy was built.
What “Cached” and “last known good” mean
Section titled “What “Cached” and “last known good” mean”Every fetched panel carries a meta block with ok, fetchedAt, stale and
staleReason. The app turns that into one of three states:
| Status | Sources row badge | Row detail | Meaning |
|---|---|---|---|
| Live | (no badge, shows how long ago it was fetched) | “Live” | The upstream answered inside its refresh window. |
| Cached | ”Cached" | "Serving last known good”, with a “Fell back because” line quoting the error | The upstream failed, timed out, rate-limited, or returned something unparseable, so the previous good copy is shown. fetchedAt is when that copy was fetched. |
| Down | ”Unavailable" | "Unavailable”, with an “Error” line | No usable copy exists — the panel shows nothing. |
The Sources screen opens with a strip counting Live / Cached / Down out of the total, and its footer says: “Cached” means the upstream failed and the Worker served its last good copy rather than nothing. The Beach info tab carries the same tally in one row (“N live”, with ”· N cached” and ”· N down” added only when non-zero) that opens the Sources screen.
How long a fallback may be shown depends on the source: 1 day unless the adapter says otherwise, and longer where a stale answer still beats none — 30 days for water quality, the pier report and the proclamations (the pier shuts for the winter, so a months-old report is the truthful answer), 7 days for launches and eBird, 3 hours for the surge anomaly (a day-old anomaly describes a different weather system). Past that window the panel goes to Down rather than showing something misleading.
Reading /api/health
Section titled “Reading /api/health”https://sunsetbeach.neuralconfig.com/api/health fans out to all 28 upstreams
and returns:
ok—trueonly when every row isok.degraded—truewhen any row isokbutstale; serving a last-known-good copy counts as degraded, not healthy.sources— the 28 rows, each withkey,label,source,url,fetchedAt,ok,stale,staleReason, a humannote, and where the feed carries its own timestamp anobservedAt(and for the three buoys anobservationAgeMin).generatedAtandelapsedMs.
The health report is memoised at the edge for 60 seconds and deliberately has no last-known-good tier of its own — a stale copy of the thing that reports staleness would be self-defeating.
The app’s memory
Section titled “The app’s memory”Separately from caching, the cron keeps a small diary: once an hour it writes a
~1 KB projection of the published payload (the verdict, the sunset rating and
the cloud profile it came from, the rip level, water temperatures, the surge
anomaly, the next tide, turtle counts, AQI, the pier’s catch list) to KV for
90 days, and once a day rolls the hours up into a record kept for two
years. /api/calibration reports the last 30 and 90 days of what the app
said against what the sky then did. Nothing on the phone reads this; it exists
so the sunset and score weights can one day be tuned against outcomes.