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:
- Identity — designation, name, catalog/orbit id.
- Intrinsic properties — what the object is: orbital elements, absolute magnitude, estimated size. (Estimates are flagged, e.g. diameter from H with an assumed albedo.)
- The array — the time-ordered series the object accreted, with object-level
summary stats (count, extremes, span). For
neothis 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)
- 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-guardedreload(). Bulk-insert DuckDB archives (never row-by-row executemany at scale). A thinscripts/reload_<x>_spine.pywraps it for manual force-reloads. - Object sweep (
src/terrapulse/monitor/<x>_celestialdex.py) — group spine rows by object into one slot per object with its array; write tocelestial_storehouse; rebuild that storehouse's index. - 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:
- 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. - 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.