import Link from 'next/link'; import { Callout, Code, DocSection, Prose, SubHeading } from '@/components/meta/prose'; import { routes } from '@/lib/site'; /** * Static prose sections of /methodology. Facts come from the backend implementation: * services/resolution.py, orbital/propagate.py, orbital/elements.py, registry/reference.py (SATCAT_STATUS), services/classify.py, * connectors/orbital/celestrak/satcat.py (_ensure_launches), api/common.py (freshness_status), config.py. */ export function PipelineSection() { return (

The site is never coupled to a third-party feed. Every value passes through the same chain, and each stage keeps what the previous one produced:

  1. Connector — a scheduled worker per upstream feed (celestrak_gp, celestrak_groups, celestrak_satcat, derived_analytics). Each run is recorded with duration, record counts, a payload hash and any error; repeated failures open a circuit breaker.
  2. Raw snapshot — the exact upstream payload is stored gzip-compressed on disk and indexed in raw_records before anything is parsed. Only payloads whose hash changed are processed; unchanged responses are logged as such.
  3. Normalize — rows are parsed into a common shape (identifiers, names, dates, element sets). Suspiciously short responses (for example a catalog with far fewer than 50 000 rows) are rejected as truncated rather than treated as “no data”.
  4. Entity resolution — each normalized row is matched to an existing canonical object (next section) or creates a new one. Ambiguous matches go to a manual review queue.
  5. Canonical store — one row per object in satellites, an append-only orbital_elements history with orbital_state pointing at the latest epoch, field-level history and field_provenance (source, observation time, confidence) for every accepted value. Objects missing from an upstream response are never deleted.
  6. Derived — materialized statistics, constellation/operator/country aggregates, the search index and detected events are refreshed by derived_analytics (hourly) using the versioned metric definitions below.
  7. API → web — the public JSON API reads only the canonical and derived layers; the website is a client of that API. Positions are computed at request time (see orbit calculation).
); } export function ResolutionSection() { return (

Matching an incoming row to a canonical object follows a strict priority; the first rule that yields exactly one candidate wins:

  1. NORAD catalog number — if the row has a NORAD id already known, it is the same object.
  2. COSPAR designator — only when the row has no NORAD id, and only if exactly one canonical object carries that designator.
  3. Exact normalized name — only when the row has no NORAD id, against canonical objects that also lack one, and only if the name is unique.
  4. Create — otherwise a new canonical object is created with a ULID and a slug (name-norad, de-duplicated with a numeric suffix).

Anything ambiguous — a COSPAR id that maps to several objects, a name shared by several objects, or a NORAD id that appears alongside a different existing designator — is never merged automatically. It is written to manual_review_queue with both candidates and a confidence; an operator decides merge, keep separate or dismiss. Merges move aliases, identifiers and element history to the kept object and are journaled in entity_merges with a snapshot of the removed row, so they can be audited or reversed.

); } export function OrbitCalcSection() { return (

Positions are propagated with SGP4 (the python-sgp4 implementation of the Vallado et al. reference code, vectorised with SatrecArray) using the WGS-72 gravity constants — the model the element sets were fitted against. Inputs are the OMM element sets from CelesTrak (epoch, mean motion, eccentricity, inclination, RAAN, argument of perigee, mean anomaly, B*, first derivative of mean motion).

SGP4 yields a position vector in the TEME frame. It is rotated to Earth-fixed coordinates (ECEF) about the Z axis by the Greenwich Mean Sidereal Time of the requested instant (UT1 ≈ UTC; polar motion ignored, ≈ 10 m), then converted to geodetic latitude, longitude and altitude on the WGS84 ellipsoid by iteration. Velocity is the norm of the TEME velocity vector.

Derived geometry shown on satellite pages comes from the same element set: semi-major axis a = (μ / n²)^1/3 with μ = 398 600.4418 km³/s², perigee/apogee = a(1 ∓ e) − 6 378.137 km, period = 1440 / n minutes.

Accuracy

SGP4 is an analytical mean-element model: typical along-track error is of the order of a kilometre at epoch and grows by kilometres per day, faster for low, high-drag orbits and after manoeuvres. Positions shown on this site are therefore indicative: good for “where is it over the Earth right now”, not for pointing, conjunction or reentry work. Every position carries the epoch it was propagated from and its age.

Positions are computed on demand and never stored: the propagator keeps the latest element set of every object in memory and answers batch and single-object requests with a 30-second cache. Only element sets are persisted.

); } const OPS_CODES: { code: string; meaning: string; status: string }[] = [ { code: '+', meaning: 'Operational', status: 'ACTIVE' }, { code: 'P', meaning: 'Partially operational', status: 'ACTIVE' }, { code: 'B', meaning: 'Backup / standby', status: 'INACTIVE' }, { code: 'S', meaning: 'Spare', status: 'INACTIVE' }, { code: 'X', meaning: 'Extended mission', status: 'INACTIVE' }, { code: 'D', meaning: 'Decayed', status: 'DECAYED' }, { code: '?', meaning: 'Unknown', status: 'UNKNOWN' }, { code: '(blank)', meaning: 'No code published', status: 'UNKNOWN' }, ]; export function StatusSection({ methodologyText }: { methodologyText: string | null }) { return ( {methodologyText && {methodologyText}}
{OPS_CODES.map((c) => ( ))}
SATCAT code Meaning Canonical status
{c.code} {c.meaning} {c.status}

Two overrides apply after the code lookup, in this order: a decay date always produces DECAYED; debris and rocket bodies with no code are INACTIVE rather than UNKNOWN (they cannot be “operational”). Payloads present in the CelesTrak active GP group but lacking a SATCAT code are considered ACTIVE. Status changes emit decommission / activation events and are kept in the field history.

); } export function OrbitClassSection({ methodologyText }: { methodologyText: string | null }) { return ( {methodologyText && {methodologyText}}

The rule is evaluated on the latest element set (or, for objects without GP data, on the SATCAT apsides with eccentricity estimated from perigee and apogee). Implementation order, as in orbital/elements.py:

{`if apogee or perigee missing → OTHER if |period − 1436.07 min| ≤ 30 and e < 0.05 and i < 20° → GEO if e > 0.25 and apogee > 35 000 km → HEO if apogee < 2 000 km → LEO if perigee ≥ 2 000 km and apogee < 35 786 + 2 000 km: if |period − 1436.07| ≤ 60 → GEO (inclined / drifting geosynchronous) else → MEO if |period − 1436.07| ≤ 60 and e < 0.1 → GEO else → OTHER`}

OTHER therefore collects transfer orbits, highly eccentric non-HEO objects and anything with an apogee below 2 000 km but a perigee above it — impossible by definition, so effectively it is the “does not fit” bucket. Orbit class is a derived label, versioned as metric orbit_class.

); } export function MissionLaunchSection() { return ( <>

Mission type is derived, in this order of preference (services/classify.py):

  1. the service type of the matched constellation (communications, earth-observation, navigation, iot…);
  2. otherwise the object type when it is decisive: rocket bodies → rocket-body, debris → debris, stations → station;
  3. otherwise a documented name pattern from the curated registry (for example weather, GNSS or science families);
  4. otherwise unknown. A later run never downgrades a known mission type to unknown.

No launch feed is ingested yet. Launches are derived from international designators: the first eight characters of a COSPAR id (YYYY-NNN) identify the launch, so every object sharing that prefix is attached to one launch row. The launch date and site are the earliest date and the site reported for those objects in SATCAT; the “primary” payload is the object whose piece letter is A; payload, object and on-orbit counts are recomputed after every catalog run. Objects with no COSPAR id (analyst objects) belong to no launch and are flagged.

); } export function FreshnessSection({ methodologyText }: { methodologyText: string | null }) { return ( {methodologyText && {methodologyText}}

Two scales are in use, and both are shown rather than hidden behind an average:

The health endpoint reports the worst connector freshness as the platform’s data status.

); } export function LimitationsSection() { return ( ); }