SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
4.1 KB · 100 lines typescript
Raw Blame History
1// Labels and helpers for Source Mesh v2: observation types, comparability classes, roles, coverage tiers.2import type { Comparability, CoverageStatus, CoverageTier, ObservationType } from "./types";34export const OBSERVATION_TYPE_LABEL: Record<ObservationType, string> = {5  TRADE: "Trade",6  MID: "Mid",7  QUOTE: "Quote",8  INDEX_VALUE: "Index value",9  INDICATIVE: "Indicative",10  STABLECOIN_PROXY: "Stablecoin proxy",11  DERIVED: "Derived",12  OFFICIAL_FIX: "Official fix",13  REFERENCE_RATE: "Reference rate",14  SETTLEMENT: "Settlement",15  EOD_CLOSE: "EOD close",16  NAV: "NAV",17};1819export const COMPARABILITY_LABEL: Record<Comparability, string> = {20  LIVE: "live market consensus",21  FIX: "official fixing",22  EOD: "end-of-day close",23};2425export const COMPARABILITY_OF: Record<ObservationType, Comparability> = {26  TRADE: "LIVE",27  MID: "LIVE",28  QUOTE: "LIVE",29  INDEX_VALUE: "LIVE",30  INDICATIVE: "LIVE",31  STABLECOIN_PROXY: "LIVE",32  DERIVED: "LIVE",33  OFFICIAL_FIX: "FIX",34  REFERENCE_RATE: "FIX",35  SETTLEMENT: "EOD",36  EOD_CLOSE: "EOD",37  NAV: "EOD",38};3940export const PROXY_TYPES = new Set<ObservationType>(["STABLECOIN_PROXY", "DERIVED", "INDICATIVE"]);4142/** Readable copy for exclusion reasons. `canonical` is the comparability class of the canonical value. */43export function reasonLabel(reason: string | null | undefined, type?: ObservationType, canonical?: Comparability | null): string {44  switch (reason) {45    case "stale":46      return "stale — outside its freshness window";47    case "not_comparable": {48      const own = type ? COMPARABILITY_LABEL[COMPARABILITY_OF[type]] : null;49      const can = canonical ? COMPARABILITY_LABEL[canonical] : null;50      return own && can ? `not comparable: ${own} vs ${can}` : "not comparable with the canonical class";51    }52    case "temporal_mismatch":53      return "temporal mismatch — too far from the freshest observation";54    case "validation_only":55      return "validation only — restricted rights, never votes";56    case "validator_disagrees":57      return "validator disagrees with the consensus";58    case "outlier":59      return "outlier — more than 2 % from the median";60    case "superseded_by_live":61      return "superseded by a live source";62    default:63      return reason ?? "excluded";64  }65}6667export type Role = "votes" | "confirms" | "validates" | "excluded";6869export function roleOf(c: { included: boolean; reason: string | null; observation_type?: ObservationType; rights_status: string }): Role {70  if (c.included) return c.observation_type && PROXY_TYPES.has(c.observation_type) ? "confirms" : "votes";71  if (c.reason === "validation_only" || c.reason === "validator_disagrees") return "validates";72  return "excluded";73}7475export const ROLE_LABEL: Record<Role, string> = { votes: "votes", confirms: "confirms (proxy)", validates: "validates", excluded: "excluded" };7677export const TIER_LABEL: Record<CoverageTier, string> = { A: "Tier A · major", B: "Tier B · liquid", C: "Tier C · long tail", D: "Tier D · official reference" };78export const TIER_TARGET: Record<CoverageTier, string> = { A: "≥ 3 independent families, 5 observations", B: "≥ 2 families, 3 observations", C: "1 family, 2 observations", D: "one official source is the truth" };7980export const COVERAGE_STATUS_LABEL: Record<CoverageStatus, string> = { COVERED: "covered", UNDERCOVERED: "under-covered", SINGLE_SOURCE: "single source", NO_DATA: "no data" };81export const COVERAGE_STATUS_TONE: Record<CoverageStatus, string> = {82  COVERED: "bg-positive-soft text-positive",83  UNDERCOVERED: "bg-warning-soft text-warning",84  SINGLE_SOURCE: "bg-warning-soft text-warning",85  NO_DATA: "bg-stale-soft text-stale",86};8788export function formatDeltaBps(v: number | null | undefined): string {89  if (v == null || !Number.isFinite(v)) return "—";90  const a = Math.abs(v);91  const s = a < 10 ? a.toFixed(2) : a < 100 ? a.toFixed(1) : Math.round(a).toString();92  return `${v > 0 ? "+" : v < 0 ? "−" : ""}${s} bp`;93}9495export function deltaTone(v: number | null | undefined): string {96  if (v == null) return "text-ink-3";97  const a = Math.abs(v);98  return a <= 5 ? "text-positive" : a <= 25 ? "text-ink-2" : a <= 100 ? "text-warning" : "text-negative";99}100