SPB Git

spb/earth-now Public License

earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.

TypeScript 93% Shell 2.3% SQL 1.4% JavaScript 1.3% Dockerfile 1.2% CSS 0.8%
2.3 KB · 68 lines typescript
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    apps/api/src/pollers/open-notify.ts6 * Purpose: Open Notify poller (1 h) for humans_in_space — extremely failure-tolerant, fixture value stays as fallback7 */89import { buildStaticRtModel } from "@earth-now/models";10import { type ModelStore, displayHintsOf } from "../store.js";11import { type PollerHandle, NOOP_POLLER, fetchJson, pollersEnabled } from "./poller.js";1213const METRIC_ID = "humans_in_space";14const SOURCE_ID = "open_notify";15const ASTROS_URL = "http://api.open-notify.org/astros.json";16// The API is known to be flaky: only flag the metric stale after repeated failures.17const MAX_CONSECUTIVE_FAILURES = 3;1819export const OPEN_NOTIFY_POLL_INTERVAL_MS = 60 * 60_000;2021async function pollOnce(store: ModelStore): Promise<void> {22  const data = await fetchJson(ASTROS_URL);23  const count = (data as { number?: unknown }).number;24  if (typeof count !== "number" || !Number.isInteger(count) || count < 0 || count > 100) {25    throw new Error("astros.json malformed or implausible 'number' field");26  }27  const entry = store.registry.byId.get(METRIC_ID);28  if (!entry) throw new Error(`registry has no '${METRIC_ID}' entry`);29  const nowIso = new Date().toISOString();30  store.updateRtModel(31    METRIC_ID,32    buildStaticRtModel(33      {34        metricId: METRIC_ID,35        sourceId: SOURCE_ID,36        observedAt: nowIso,37        displayHints: displayHintsOf(entry),38      },39      { value: count, at: nowIso },40    ),41  );42}4344export function startOpenNotifyPoller(45  store: ModelStore,46  intervalMs: number = OPEN_NOTIFY_POLL_INTERVAL_MS,47): PollerHandle {48  if (!pollersEnabled()) return NOOP_POLLER;49  let consecutiveFailures = 0;50  const tick = (): void => {51    void pollOnce(store)52      .then(() => {53        consecutiveFailures = 0;54      })55      .catch((err: unknown) => {56        consecutiveFailures += 1;57        if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) store.markStale(METRIC_ID);58        console.error(59          `[open-notify] poll failed ${consecutiveFailures}× (fixture/previous value keeps serving): ${String(err)}`,60        );61      });62  };63  tick();64  const timer = setInterval(tick, intervalMs);65  timer.unref();66  return { stop: () => clearInterval(timer) };67}68