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%
5.4 KB · 137 lines tsx
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    apps/web/app/methodology/page.tsx6 * Purpose: Public methodology page (product commitment) — per-metric source, license, cadence, model family/version, last observation, uncertainty7 */89import Link from "next/link";10import { formatValue } from "@earth-now/counter";11import { fetchMetrics, type MetricSummary } from "@/lib/api";12import {13  DEFAULT_LOCALE,14  formatDateUtc,15  isLocale,16  translate,17  type MessageKey,18} from "@/lib/messages";1920export const dynamic = "force-dynamic";2122const PERCENT_HINTS = { decimals: 0, unit: "%" } as const;2324function levelLabel(metric: MetricSummary): string {25  const level = typeof metric.level === "number" ? `L${metric.level}` : metric.level.toUpperCase();26  const version = metric.modelVersion !== undefined ? ` · ${metric.modelVersion}` : "";27  return `${level} · ${metric.model}${version}`;28}2930export default async function MethodologyPage({31  searchParams,32}: {33  searchParams?: { lang?: string };34}) {35  const langParam = searchParams?.lang;36  const locale = isLocale(langParam) ? langParam : DEFAULT_LOCALE;37  const t = (key: MessageKey) => translate(locale, key);3839  let metrics: MetricSummary[] = [];40  let offline = false;41  try {42    metrics = await fetchMetrics();43  } catch {44    offline = true;45  }4647  return (48    <div className="mx-auto max-w-6xl px-4 py-10">49      <Link href="/" className="text-xs text-accent hover:opacity-80">50        {t("methodology.back")}51      </Link>52      <h1 className="mt-3 text-2xl font-semibold tracking-tight text-ink">53        {t("methodology.title")}54      </h1>55      <p className="mt-3 max-w-3xl text-sm leading-relaxed text-ink2">56        {t("methodology.intro")}57      </p>5859      {offline ? (60        <p className="mt-8 text-sm text-ink2">{t("offline.body")}</p>61      ) : (62        <div className="mt-8 overflow-x-auto">63          <table className="w-full min-w-[900px] border-collapse text-left text-xs">64            <thead>65              <tr className="border-b border-line text-ink2">66                <th className="py-2 pr-3 font-medium">{t("methodology.metric")}</th>67                <th className="py-2 pr-3 font-medium">{t("methodology.domain")}</th>68                <th className="py-2 pr-3 font-medium">{t("methodology.kind")}</th>69                <th className="py-2 pr-3 font-medium">{t("methodology.model")}</th>70                <th className="py-2 pr-3 font-medium">{t("methodology.source")}</th>71                <th className="py-2 pr-3 font-medium">{t("methodology.license")}</th>72                <th className="py-2 pr-3 font-medium">{t("methodology.cadence")}</th>73                <th className="py-2 pr-3 font-medium">{t("methodology.observed")}</th>74                <th className="py-2 pr-3 font-medium">{t("methodology.uncertainty")}</th>75                <th className="py-2 font-medium">{t("methodology.note")}</th>76              </tr>77            </thead>78            <tbody>79              {metrics.map((metric) => {80                const source = metric.sources[0];81                return (82                  <tr key={metric.id} className="border-b border-line align-top text-ink2">83                    <td className="py-2 pr-3 font-medium text-ink">84                      {metric.name[locale]}85                      {metric.stale && (86                        <span className="ml-2 rounded border border-warn/50 px-1.5 py-0.5 text-[10px] uppercase text-ink2">87                          {t("chip.stale")}88                        </span>89                      )}90                    </td>91                    <td className="py-2 pr-3">92                      {t(`domain.${metric.domain}` as MessageKey)}93                    </td>94                    <td className="py-2 pr-3">{t(`kind.${metric.kind}` as MessageKey)}</td>95                    <td className="py-2 pr-3 tabular-nums">{levelLabel(metric)}</td>96                    <td className="py-2 pr-3">97                      {source !== undefined ? (98                        <a99                          href={source.url}100                          target="_blank"101                          rel="noreferrer"102                          className="text-accent underline decoration-accent/40 hover:opacity-80"103                        >104                          {source.name}105                        </a>106                      ) : (107                        "—"108                      )}109                    </td>110                    <td className="py-2 pr-3">{source?.license ?? "—"}</td>111                    <td className="py-2 pr-3">{source?.cadence ?? "—"}</td>112                    <td className="py-2 pr-3">113                      {metric.observedAt !== undefined114                        ? formatDateUtc(metric.observedAt, locale)115                        : "—"}116                    </td>117                    <td className="py-2 pr-3 tabular-nums">118                      {metric.uncertaintyFraction !== undefined119                        ? `± ${formatValue(metric.uncertaintyFraction * 100, PERCENT_HINTS, {120                            locale,121                          })} % (${t("chip.estimate")})`122                        : "—"}123                    </td>124                    <td className="py-2 text-ink2">125                      {metric.editorialNote !== undefined ? metric.editorialNote[locale] : ""}126                    </td>127                  </tr>128                );129              })}130            </tbody>131          </table>132        </div>133      )}134    </div>135  );136}137