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.4 KB · 70 lines typescript
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    apps/web/lib/tiers.ts6 * Purpose: Pure display-tier classification — metrics whose last visible digit ticks fast lead the page; slow ones go to the reference strip7 */89import { rateAt, type CounterModel, type DisplayHints } from "@earth-now/counter";10import type { MetricSummary } from "./api";1112export type DisplayTier = "hero" | "live" | "country" | "realtime" | "reference";1314export const HERO_METRIC_ID = "world_population";15export const COUNTRY_PREFIX = "country_population_";16export const CONTINENT_PREFIX = "continent_population_";1718/**19 * Seconds between visible changes of the LAST displayed digit:20 * (10^-decimals) / (|rate| × displayScale). Infinity when the value doesn't move.21 * Pure — time is a parameter.22 */23export function secondsPerVisibleTick(24  model: CounterModel,25  hints: Pick<DisplayHints, "decimals" | "scale">,26  tMs: number,27): number {28  const rate = Math.abs(rateAt(model, tMs)) * (hints.scale ?? 1);29  if (rate === 0 || !Number.isFinite(rate)) return Infinity;30  return 10 ** -hints.decimals / rate;31}3233/** A counter reads as "live" when its last digit changes at least every ~2.5 s. */34export const LIVE_TICK_THRESHOLD_S = 2.5;3536export function tierFor(37  metric: MetricSummary,38  model: CounterModel | undefined,39  tMs: number,40): DisplayTier {41  if (metric.id === HERO_METRIC_ID) return "hero";42  if (metric.id.startsWith(COUNTRY_PREFIX) || metric.id.startsWith(CONTINENT_PREFIX)) return "country";43  if (metric.level === "rt") return "realtime";44  if (model === undefined) return "reference";45  // rate-of / depletion displays are static text (the rate itself moves slowly).46  if (metric.derived?.op === "rate-of" || metric.derived?.op === "depletion-countdown") {47    return "reference";48  }49  const spv = secondsPerVisibleTick(50    model,51    { decimals: metric.display.decimals, ...(metric.display.scale !== undefined ? { scale: metric.display.scale } : {}) },52    tMs,53  );54  return spv <= LIVE_TICK_THRESHOLD_S ? "live" : "reference";55}5657/** Sort key for the live grid: fastest visible tick first. */58export function liveSortKey(59  metric: MetricSummary,60  model: CounterModel | undefined,61  tMs: number,62): number {63  if (model === undefined) return Infinity;64  return secondsPerVisibleTick(65    model,66    { decimals: metric.display.decimals, ...(metric.display.scale !== undefined ? { scale: metric.display.scale } : {}) },67    tMs,68  );69}70