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%
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