yksa-orbital/yksa_orbital/README.md
2026-08-18 22:01:53 +08:00

86 lines
3.4 KiB
Markdown

# yksa_orbital
Propagation for Django services: a backend registry, and the client to the
Orekit sidecar.
```python
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.
```python
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. |