Listening for events…

CelestialObjectDex — framework

Coined by Mike, 2026-06-26. The fourth dex family, after Eventdex (slot = one event), Yeardex (slot = one year), and Locationdex (slot = one fixed place). The slot is one celestial object — a persistent body in space that accretes a series of observations or events over time.

The organizing idea

The dex families are distinguished by what a slot IS:

  • Eventdex — a slot is one moment: an earthquake, a tornado, a launch.
  • Yeardex — a slot is one year of an annual subject.
  • Locationdex — a slot is one fixed place: a station, a gauge, an observatory, that accretes a measured series.
  • CelestialObjectDex — a slot is one celestial object: an asteroid, a comet, a transient source, that accretes a series of events (close approaches, detections) and carries intrinsic properties (orbit, size, brightness).

The insight is the same one that produced Locationdex: when the durable identity is the thing and the events are things that happen to it, the object should be the slot, with its events as an array inside, not scattered across many event slots. Apophis is one object that passes Earth repeatedly, not two unrelated flybys.

Slot shape

Each slot dossier carries three layers:

  1. Identity — designation, name, catalog/orbit id.
  2. Intrinsic properties — what the object is: orbital elements, absolute magnitude, estimated size. (Estimates are flagged, e.g. diameter from H with an assumed albedo.)
  3. The array — the time-ordered series the object accreted, with object-level summary stats (count, extremes, span). For neo this is the list of close approaches; for a future transient member it would be the light curve.

A celestial object has no terrestrial position, so the dossier's geographic lat/lon stay NULL; the "position" is in the object's own terms (sky direction, orbit, miss-distance).

Measured reality

CelestialObjectDex lives inside the measured-reality bright line via its celestial-mechanics refinement (feedback_measured_reality_only, Mike 2026-06-26): deterministic ephemeris and orbital propagation of measured orbital elements is admissible. It is the deterministic consequence of real observations (the same mathematics that lands spacecraft), not a chaotic weather/climate forecast. This admits both past and future computed positions; the only caveat is precision (short observation arcs and deep extrapolation carry real uncertainty, which slots preserve rather than hide).

Storage

Its own data/celestial_storehouse/ sibling (mirroring event_storehouse and location_storehouse), reusing the generic event_storehouse write + disk-rebuilt-index machinery via its base_dir argument. A separate storehouse keeps the family's index rebuild scoped to its own object files, off the large shared event index.

Two storage modes by scale (the severe-wx rule, docs/scope-severe-wx-eventdex.md): a small kind is file-per-slot (one JSON dossier per object, listed in the family index) — neo at 18.7k. A large kind is spine-parquet first (one row per object in <kind>/<kind>_spine.parquet, no per-slot files, not in the index) so even the family's own index rebuild stays fast — satcat at 69k. Per-slot dossiers for a spine-parquet kind are deferred until the scalable per-kind index lands. The crossover sits somewhere between 18.7k and 69k; the clean cutoff is an open call tracked in docs/dex-triage-surfaced.md.

Kinds

  • neo (pilot, 2026-06-26) — near-Earth asteroids from CNEOS CAD; slot = one asteroid, array = its close approaches within 0.05 au. ~18.7k objects, file-per-slot. Scope: docs/scope-neo-celestialdex.md.
  • satcat (2026-06-26) — catalogued space objects from CelesTrak SATCAT; slot = one object (satellite / rocket body / debris), array = its launch -> decay lifecycle + measured orbital elements. 69,352 objects. The family's first spine-parquet kind (see Storage). Scope: docs/scope-satcat-celestialdex.md.

Candidate future members

  • comets — periodic/long-period comets, array = apparitions/perihelion passes.
  • transient — already object-shaped (one ZTF source + light-curve array); a natural reclassification from Eventdex, Mike's call.
  • minor bodies — the Solar System movers excluded from transient.

Build pattern (mirrors the other families)

  1. Spine module (src/terrapulse/monitor/<x>_spine.py, importable) — pull the catalog into a dedicated source, one row per observed event/approach, exposing a TTL-guarded reload(). Bulk-insert DuckDB archives (never row-by-row executemany at scale). A thin scripts/reload_<x>_spine.py wraps it for manual force-reloads.
  2. Object sweep (src/terrapulse/monitor/<x>_celestialdex.py) — group spine rows by object into one slot per object with its array; write to celestial_storehouse; rebuild that storehouse's index.
  3. Scope freeze (docs/scope-<x>-celestialdex.md) + unit tests.

Live edge (the family's self-updating contract)

A CelestialObjectDex catalog grows as new objects are discovered, so each kind is self-updating. The contract is a two-step refresh_and_store() on the kind module:

  1. Refresh the spine — the spine module's reload() re-pulls the catalog, guarded by a cache TTL so a frequent tick only re-fetches once the cache is stale. The PG load is an idempotent DELETE + reinsert, so new discoveries flow in and nothing stale lingers.
  2. Rebuild the slots — regroup the refreshed spine into object slots and rebuild the storehouse index.

The scheduler runs each kind's refresh_and_store() on a cadence (ingestion/scheduler.py). For neo this is weekly (Mon 08:00 UTC, neo_refresh job) — the CAD catalog grows slowly and a fresh pull is ~5 MB. New scheduler jobs take effect on the next terrapulse service restart.

Live Feed