spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { ATTRIBUTION, DISCLAIMER, FIELD_PRIORITY } from '@/components/meta/legal';4import { FreshnessSection, LimitationsSection, MissionLaunchSection, OrbitCalcSection, OrbitClassSection, PipelineSection, ResolutionSection, StatusSection } from '@/components/meta/methodology-sections';5import { ConstellationRulesSection, MetricsSection, OwnerCodesSection } from '@/components/meta/methodology-tables';6import { Callout, DocLayout, DocSection, Prose } from '@/components/meta/prose';7import { Container, PageHeader } from '@/components/ui/section';8import { api, safe } from '@/lib/api';9import { fmtDateTime } from '@/lib/format';10import { SITE_URL, routes } from '@/lib/site';1112export const metadata: Metadata = {13 title: 'Methodology — how SatelliteIndex builds and derives its data',14 description: 'Ingestion pipeline, source priorities, entity resolution, SGP4 orbit calculation, status and orbit-class rules, constellation membership, versioned derived metrics, freshness thresholds and known limitations.',15 alternates: { canonical: `${SITE_URL}/methodology` },16 openGraph: { title: 'Methodology | SatelliteIndex', description: 'How every value on SatelliteIndex is sourced, resolved, computed and labelled.', url: `${SITE_URL}/methodology` },17 twitter: { card: 'summary', title: 'Methodology | SatelliteIndex', description: 'How every value on SatelliteIndex is sourced, resolved, computed and labelled.' },18};1920const TOC = [21 { id: 'pipeline', label: 'Data pipeline' },22 { id: 'source-priority', label: 'Source priorities' },23 { id: 'entity-resolution', label: 'Entity resolution' },24 { id: 'orbit-calculation', label: 'Orbit calculation' },25 { id: 'status', label: 'Status classification' },26 { id: 'orbit-class', label: 'Orbit class' },27 { id: 'constellations', label: 'Constellation membership' },28 { id: 'mission-type', label: 'Mission type' },29 { id: 'launches', label: 'Launches' },30 { id: 'owner-codes', label: 'Owner codes' },31 { id: 'metrics', label: 'Derived metrics' },32 { id: 'freshness', label: 'Freshness' },33 { id: 'limitations', label: 'Limitations' },34 { id: 'disclaimer', label: 'Disclaimer' },35];3637export default async function MethodologyPage() {38 const res = await safe(api.methodology());39 const payload = res?.data ?? null;40 const metric = (key: string) => payload?.metrics.find((m) => m.key === key)?.methodology ?? null;41 return (42 <Container>43 <PageHeader eyebrow="Methodology" title="How the index is built" lede="Every value on SatelliteIndex is either a sourced fact with provenance or a derived label with a versioned rule. This page documents the rules — from the raw upstream payload to the position drawn on the globe — so you can decide how much to trust each number.">44 <p className="mt-4 text-sm text-ink-3">45 Sources and licenses: <Link href={routes.sources()} className="link">/sources</Link> · Live freshness: <Link href={routes.statusData()} className="link">/status/data</Link> · Machine-readable: <a href="/api/v1/methodology" className="link mono">/api/v1/methodology</a>46 {res && <span className="ml-2">· definitions fetched {fmtDateTime(res.meta.generated_at)}</span>}47 </p>48 </PageHeader>4950 <DocLayout toc={TOC}>51 <div>52 <PipelineSection />5354 <DocSection id="source-priority" eyebrow="02" title="Source priorities">55 <Prose>56 <p>57 Sources carry a numeric priority; when two sources describe the same field the higher-priority value is kept and the other is still recorded in provenance. In practice each field family has one primary source today:58 </p>59 </Prose>60 <div className="mt-4 overflow-x-auto">61 <table className="data-table stack md:min-w-[640px]">62 <thead>63 <tr>64 <th>Field</th>65 <th>Primary source</th>66 <th>Note</th>67 </tr>68 </thead>69 <tbody>70 {FIELD_PRIORITY.map((f) => (71 <tr key={f.field}>72 <td className="primary text-sm text-ink">{f.field}</td>73 <td data-label="Primary source" className="text-sm">74 <Link href={`${routes.sources()}#${f.sourceId}`} className="link">75 {f.source}76 </Link>77 </td>78 <td data-label="Note" className="text-sm text-ink-2">79 {f.note}80 </td>81 </tr>82 ))}83 </tbody>84 </table>85 </div>86 </DocSection>8788 <ResolutionSection />89 <OrbitCalcSection />90 <StatusSection methodologyText={metric('status')} />91 <OrbitClassSection methodologyText={metric('orbit_class')} />92 <ConstellationRulesSection rules={payload?.constellation_rules ?? null} />93 <MissionLaunchSection />94 <OwnerCodesSection owners={payload?.owner_codes ?? null} />95 <MetricsSection metrics={payload?.metrics ?? null} />96 <FreshnessSection methodologyText={metric('freshness')} />97 <LimitationsSection />9899 <DocSection id="disclaimer" eyebrow="14" title="Disclaimer">100 <Callout tone="warn" className="mt-0">101 {DISCLAIMER}102 </Callout>103 <Prose className="mt-4">104 <p>{ATTRIBUTION}</p>105 <p className="text-ink-3">106 See the <Link href={routes.terms()} className="link">terms of use</Link>. Corrections and questions: <Link href={routes.about()} className="link">about</Link>.107 </p>108 </Prose>109 </DocSection>110 </div>111 </DocLayout>112 </Container>113 );114}115