/** * earth-now.co * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: apps/web/lib/tiers.ts * Purpose: Pure display-tier classification — metrics whose last visible digit ticks fast lead the page; slow ones go to the reference strip */ import { rateAt, type CounterModel, type DisplayHints } from "@earth-now/counter"; import type { MetricSummary } from "./api"; export type DisplayTier = "hero" | "live" | "country" | "realtime" | "reference"; export const HERO_METRIC_ID = "world_population"; export const COUNTRY_PREFIX = "country_population_"; export const CONTINENT_PREFIX = "continent_population_"; /** * Seconds between visible changes of the LAST displayed digit: * (10^-decimals) / (|rate| × displayScale). Infinity when the value doesn't move. * Pure — time is a parameter. */ export function secondsPerVisibleTick( model: CounterModel, hints: Pick, tMs: number, ): number { const rate = Math.abs(rateAt(model, tMs)) * (hints.scale ?? 1); if (rate === 0 || !Number.isFinite(rate)) return Infinity; return 10 ** -hints.decimals / rate; } /** A counter reads as "live" when its last digit changes at least every ~2.5 s. */ export const LIVE_TICK_THRESHOLD_S = 2.5; export function tierFor( metric: MetricSummary, model: CounterModel | undefined, tMs: number, ): DisplayTier { if (metric.id === HERO_METRIC_ID) return "hero"; if (metric.id.startsWith(COUNTRY_PREFIX) || metric.id.startsWith(CONTINENT_PREFIX)) return "country"; if (metric.level === "rt") return "realtime"; if (model === undefined) return "reference"; // rate-of / depletion displays are static text (the rate itself moves slowly). if (metric.derived?.op === "rate-of" || metric.derived?.op === "depletion-countdown") { return "reference"; } const spv = secondsPerVisibleTick( model, { decimals: metric.display.decimals, ...(metric.display.scale !== undefined ? { scale: metric.display.scale } : {}) }, tMs, ); return spv <= LIVE_TICK_THRESHOLD_S ? "live" : "reference"; } /** Sort key for the live grid: fastest visible tick first. */ export function liveSortKey( metric: MetricSummary, model: CounterModel | undefined, tMs: number, ): number { if (model === undefined) return Infinity; return secondsPerVisibleTick( model, { decimals: metric.display.decimals, ...(metric.display.scale !== undefined ? { scale: metric.display.scale } : {}) }, tMs, ); }