yksa-orbital/yksa_orbital
2026-08-18 22:01:53 +08:00
..
__pycache__ Initial commit 2026-08-18 22:01:53 +08:00
backends Initial commit 2026-08-18 22:01:53 +08:00
tests Initial commit 2026-08-18 22:01:53 +08:00
__init__.py Initial commit 2026-08-18 22:01:53 +08:00
apps.py Initial commit 2026-08-18 22:01:53 +08:00
model_config.py Initial commit 2026-08-18 22:01:53 +08:00
README.md Initial commit 2026-08-18 22:01:53 +08:00
registry.py Initial commit 2026-08-18 22:01:53 +08:00
wire.py Initial commit 2026-08-18 22:01:53 +08:00

yksa_orbital

Propagation for Django services: a backend registry, and the client to the Orekit sidecar.

from yksa_orbital import get_backend

state = get_backend().state_at(omm, at)
weather = get_backend("orekit").space_weather(start, stop)

Why it is its own app

ODMS and track.tmtc.yksa.space both propagate, both against the same sidecar. The alternative to sharing this is two HTTP clients that drift apart on timeouts, on 503 handling, and on what they put in a request — and since the sidecar's whole purpose is that two services fly the same model, two clients that send different things quietly defeat it.

Install

Add "yksa_orbital" to INSTALLED_APPS and set:

ORBITAL_PROPAGATOR_BACKEND=sgp4        # or "orekit"
OREKIT_SERVICE_URL=http://orekit:5000

It depends on odm and on httpx. Nothing else — in particular, nothing from the host service.

Backends

Name What it can do
sgp4 Pure Python. TEME plus a GMST Earth-fixed frame. Analytical theory only: no drag integration, so it refuses decay, drag fitting and space weather rather than returning a number nobody should trust.
orekit HTTP client to the Orekit sidecar (tle/services/orekit). Rigorous frames, numerical and semi-analytical propagation, TLE fitting, drag fitting, decay forecasts, solar-activity ensembles, space weather.

Name the backend when you need the sidecar

get_backend() returns the deployment's default, which is sgp4. Anything needing physics sgp4 does not have must ask for "orekit" explicitly.

This is not hypothetical. A view that forgot to, and swallowed the resulting PropagationError, rendered an empty space-weather chart for weeks with nothing logging a complaint. If your call needs the sidecar, say so.

Capacity

The sidecar keeps a worker free for the interactive queries a page load waits on, and turns heavy work away rather than queueing it. A 503 arrives here as BackendBusy — a subclass of PropagationError, so existing handlers still work, but a Celery task should catch it first and retry. Nothing about the request needs to change for it to succeed later; recording it as a failed forecast is wrong.

The model config

yksa_orbital.model_config reads the sidecar's GET /config — the force model, propagator, decay altitude, drag-fit thresholds and weather sources it is actually flying — with a cache and a vendored fallback.

Use it for values a page genuinely needs locally: the ensemble's display floor, the decay altitude in a caption. Do not use it to build a request. Omitting a parameter already gets the sidecar's value; echoing it back only adds a way for the two to disagree.

from yksa_orbital.model_config import model_config, setting

floor_km = setting("ensemble", "display_floor_km", 150.0)

setting() applies this deployment's DECAY_* override when there is one, so a caption describes the run on screen rather than the model in general.

Layout

File What
registry.py get_backend(), and register() for a service with its own propagator.
backends/sgp4.py The pure-Python backend.
backends/orekit.py The sidecar HTTP client.
wire.py Stored OMM dict → the CCSDS message the sidecar consumes. The only place that conversion happens.
model_config.py Cached access to the sidecar's model definition.