Initial commit
This commit is contained in:
commit
5fb00f30d1
22 changed files with 1128 additions and 0 deletions
108
yksa_orbital/model_config.py
Normal file
108
yksa_orbital/model_config.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
"""What simulation model the sidecar is flying, asked rather than assumed.
|
||||
|
||||
The model -- force model, propagator, decay altitude, drag-fit thresholds,
|
||||
space-weather sources -- is defined once, in the sidecar's ``config.py``, because
|
||||
more than one service flies it and two forecasts are only comparable if the model
|
||||
behind them is the same. This is how a Django service reads that definition
|
||||
instead of keeping a second copy that drifts.
|
||||
|
||||
Use it for the values a page or a plot genuinely needs locally: the ensemble's
|
||||
display floor, the drag-fit window count, the decay altitude in a caption. Do
|
||||
*not* use it to build a request -- omitting a parameter already gets the
|
||||
sidecar's value, and echoing it back would only add a way for the two to
|
||||
disagree.
|
||||
|
||||
Cached, because it changes on deploy and not otherwise, and because a page that
|
||||
wants one number out of it must not pay a round trip. Falls back to a vendored
|
||||
copy of the defaults when the sidecar is unreachable, so a caption never takes a
|
||||
page down; the fallback is flagged so a caller can tell.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from odm import PropagationError
|
||||
|
||||
from .registry import get_backend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CACHE_KEY = "yksa_orbital:model_config"
|
||||
CACHE_TTL_S = 3600
|
||||
#: A failure is cached too, briefly. Without it a sidecar that is down costs a
|
||||
#: fresh connection attempt on every page render -- and against an unresolvable
|
||||
#: hostname that is a DNS timeout each time, which is how a caption took a page
|
||||
#: down rather than the other way round.
|
||||
FAILURE_TTL_S = 60
|
||||
|
||||
#: The sidecar's defaults as of this release. Only ever used when the sidecar
|
||||
#: cannot be reached, and only for values that are read for display -- so a stale
|
||||
#: entry here shows a slightly wrong caption rather than flying a wrong model.
|
||||
#: Keep it in step with ``services/orekit/config.py`` when a default changes.
|
||||
FALLBACK = {
|
||||
"force_model": {
|
||||
"gravity_degree": 4, "gravity_order": 4,
|
||||
"srp": True, "third_bodies": True, "tesseral": False,
|
||||
"atmosphere": "NRLMSISE00",
|
||||
},
|
||||
"decay": {
|
||||
"method": "auto", "strengths": ["AVERAGE"], "altitude_km": 105.0,
|
||||
"max_years": 25.0, "dsst_handover_altitude_km": 180.0,
|
||||
},
|
||||
"drag_fit": {"window_days": 90, "min_elements": 30, "windows": 5},
|
||||
"ensemble": {
|
||||
"realizations": 30, "percentiles": [10, 25, 50, 75, 90],
|
||||
"display_floor_km": 150.0,
|
||||
},
|
||||
"unavailable": True,
|
||||
}
|
||||
|
||||
|
||||
def model_config(*, refresh: bool = False) -> dict:
|
||||
"""The sidecar's resolved model. Never raises."""
|
||||
if not refresh:
|
||||
cached = cache.get(CACHE_KEY)
|
||||
if cached is not None:
|
||||
return cached
|
||||
try:
|
||||
config = get_backend("orekit").model_config()
|
||||
except (PropagationError, ValueError) as exc:
|
||||
logger.warning("could not read the sidecar's model config: %s", exc)
|
||||
cache.set(CACHE_KEY, FALLBACK, FAILURE_TTL_S)
|
||||
return FALLBACK
|
||||
cache.set(CACHE_KEY, config, CACHE_TTL_S)
|
||||
return config
|
||||
|
||||
|
||||
def setting(section: str, key: str, default=None):
|
||||
"""One value out of the model, with the local override applied if there is one.
|
||||
|
||||
Django's ``DECAY_*`` settings are overrides: when one is set, this deployment
|
||||
really is flying something other than the sidecar's model, and a caption
|
||||
reading the sidecar's value would be describing a different run than the one
|
||||
on screen.
|
||||
"""
|
||||
override = getattr(settings, _OVERRIDES.get((section, key), ""), None)
|
||||
if override not in (None, ""):
|
||||
return override
|
||||
return model_config().get(section, {}).get(key, default)
|
||||
|
||||
|
||||
#: (section, key) -> the Django setting that overrides it. Mirrors
|
||||
#: ``yksa_tle.predictions.service._DECAY_OVERRIDES``.
|
||||
_OVERRIDES = {
|
||||
("decay", "altitude_km"): "DECAY_ALTITUDE_KM",
|
||||
("decay", "strengths"): "DECAY_STRENGTHS",
|
||||
("decay", "method"): "DECAY_METHOD",
|
||||
("decay", "max_years"): "DECAY_MAX_YEARS",
|
||||
("force_model", "gravity_degree"): "DECAY_GRAVITY_DEGREE",
|
||||
("force_model", "gravity_order"): "DECAY_GRAVITY_ORDER",
|
||||
("force_model", "srp"): "DECAY_SRP",
|
||||
("force_model", "third_bodies"): "DECAY_THIRD_BODIES",
|
||||
("drag_fit", "windows"): "DECAY_DRAG_FIT_WINDOWS",
|
||||
("ensemble", "realizations"): "DECAY_ENSEMBLE_REALIZATIONS",
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue