SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
yesterdaylast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
1.7 KB · 43 lines typescript
Raw Blame History
1import type { Ranking, RankingRow } from "@dci/core";2import { DASH, fmtCompact, fmtInt, fmtMw } from "@/lib/format";3import { entityHref } from "@/lib/routes";45export const SCOPE_LABEL: Record<Ranking["scope"], string> = {6  countries: "Countries",7  metros: "Markets",8  operators: "Operators",9  facilities: "Facilities",10};1112export const SCOPE_ORDER: Ranking["scope"][] = ["countries", "metros", "operators", "facilities"];1314/** Format a ranking value according to the ranking unit. */15export function fmtRankValue(v: number | null | undefined, unit: string): string {16  if (v === null || v === undefined || !Number.isFinite(v)) return DASH;17  const u = unit.toLowerCase();18  if (u === "mw") return fmtMw(v);19  if (u.includes("per") || u.includes("kw") || u.includes("$")) return `${fmtCompact(v, 2)} ${unit}`;20  return fmtInt(v);21}2223/** First sentence of the methodology text (for excerpts). */24export function firstSentence(text: string): string {25  const m = text.match(/^.*?[.!?](\s|$)/);26  return (m ? m[0] : text).trim();27}2829/** "Secondary: known MW." → "Known MW" */30export function secondaryLabel(methodology: string): string {31  const m = methodology.match(/Secondary:\s*([^.]+)\./i);32  if (!m) return "Secondary";33  const s = m[1]!.trim();34  return s.charAt(0).toUpperCase() + s.slice(1);35}3637/** Site href for a ranking row — the API already returns a site path; fall back to entityHref. */38export function rowHref(row: RankingRow, scope: Ranking["scope"]): string {39  if (row.href && row.href.startsWith("/")) return row.href;40  const type = scope === "countries" ? "country" : scope === "metros" ? "metro" : scope === "operators" ? "operator" : "facility";41  return entityHref(type, row.slug, row.name);42}43