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%
4.4 KB · 137 lines typescript
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    packages/registry/src/schema.ts6 * Purpose: Zod schemas for the metric registry YAML and the data fixtures — a metric does not exist until it validates here7 */89import { z } from "zod";1011export const sourceSchema = z.object({12  id: z.string().min(1),13  name: z.string().min(1),14  url: z.string().url(),15  license: z.string().min(1),16  /** Set when the license needs a human re-check before public launch. */17  licenseNote: z.string().optional(),18  cadence: z.string().min(1),19  variants: z.array(z.string()).optional(),20});2122export const displaySchema = z.object({23  decimals: z.number().int().min(0).max(6),24  sigFigs: z.number().int().min(1).max(15).optional(),25  /** Display conversion factor (t → Gt = 1e-9). NEVER applied in the pipeline. */26  scale: z.number().positive().optional(),27  unit: z.object({ fr: z.string(), en: z.string() }),28});2930export const derivedSchema = z.object({31  op: z.enum(["linear-combination", "window", "rate-of", "depletion-countdown"]),32  inputs: z.array(z.object({ id: z.string(), weight: z.number().default(1) })).min(1),33  constant: z.number().optional(),34  /** For op=window: which window is this metric's primary reading. */35  window: z.enum(["today", "ytd", "session"]).optional(),36});3738export const metricSchema = z.object({39  id: z.string().regex(/^[a-z0-9_]+$/),40  name: z.object({ fr: z.string().min(1), en: z.string().min(1) }),41  domain: z.enum([42    "population",43    "climate",44    "emissions",45    "forest",46    "ocean",47    "energy",48    "society",49    "health",50    "economy",51    "tech",52    "realtime",53    "space",54  ]),55  priority: z.enum(["mvp", "v1", "v2"]),56  kind: z.enum(["stock", "cumulative", "event", "derived"]),57  /** Model level from the catalog: 0/1/2 statistical, "rt" true event-driven, "derived". */58  level: z.union([z.literal(0), z.literal(1), z.literal(2), z.literal("rt"), z.literal("derived")]),59  /** Model family (must match a builder in packages/models), or "derived"/"rt". */60  model: z.string().min(1),61  /** Data-layer unit (SI or domain standard); display conversions live in display. */62  unit: z.string().min(1),63  sources: z.array(sourceSchema).min(1),64  refresh: z.string().min(1),65  display: displaySchema,66  constraints: z67    .object({68      maxAbsRatePerSec: z.number().positive().optional(),69      maxJumpOnRefit: z.number().positive().optional(),70    })71    .default({}),72  windows: z.array(z.enum(["total", "today", "ytd", "session"])).default(["total"]),73  derived: derivedSchema.optional(),74  /** Relative 90 % CI to surface in the UI ("estimation" label mandatory when set). */75  uncertaintyFraction: z.number().positive().max(1).optional(),76  editorialNote: z.object({ fr: z.string(), en: z.string() }).optional(),77});7879export const metricsFileSchema = z.object({ metrics: z.array(metricSchema).min(1) });8081export type MetricEntry = z.infer<typeof metricSchema>;82export type SourceEntry = z.infer<typeof sourceSchema>;8384const timeValueSchema = z.object({ time: z.string(), value: z.number() });8586export const fixtureSchema = z.discriminatedUnion("family", [87  z.object({88    family: z.literal("seasonal-spline-v2"),89    observations: z.array(timeValueSchema).min(1),90    forecasts: z.array(timeValueSchema).min(1),91    uncertainty: z.object({ low: z.number(), high: z.number() }).optional(),92    observedAt: z.string(),93  }),94  z.object({95    family: z.literal("keeling-fusion-v1"),96    observations: z.array(timeValueSchema).min(6),97    observedAt: z.string(),98  }),99  z.object({100    family: z.literal("seasonal-ytd-v1"),101    year: z.number().int(),102    annualTotal: z.number().nonnegative(),103    shape: z.array(104      z.object({105        period: z.enum(["year", "week", "day"]),106        order: z.number().int().min(1),107        relativeAmplitude: z.number(),108        phase: z.number(),109      }),110    ),111    observedAt: z.string(),112  }),113  z.object({114    family: z.literal("linear-ytd-v1"),115    year: z.number().int(),116    annualTotal: z.number().nonnegative(),117    observedAt: z.string(),118  }),119  z.object({120    family: z.literal("linear-stock-v1"),121    at: z.string(),122    value: z.number(),123    perSecond: z.number(),124    observedAt: z.string(),125  }),126  z.object({127    family: z.literal("static-rt-v1"),128    at: z.string(),129    value: z.number(),130    observedAt: z.string(),131  }),132]);133134export type MetricFixture = z.infer<typeof fixtureSchema>;135136export const fixturesFileSchema = z.record(z.string(), fixtureSchema);137