Backend system for tracking and analyzing Near-Earth Objects using real NASA data. Ingests NEO data, processes it through a Rust risk-analysis engine, persists results to MongoDB, and exposes a documented REST API consumed by a Textual terminal dashboard.
NASA NeoWS API
│
▼
┌─────────────────────────────────┐
│ Python API (Flask · port 5001) │ ← proxy, cache, chunking, filtering
│ core/nasa_client (TTLCache) │
│ routes/nasa GET /asteroids │
└───────────┬─────────────────────┘
│ pipeline trigger
▼
┌─────────────────────────────────┐
│ Rust Engine (Axum · port 8080) │ ← impact energy, risk scoring
│ /api/process/batch │
└───────────┬─────────────────────┘
│ persist
▼
┌─────────────────────────────────┐
│ MongoDB (port 27017) │
│ asteroids_raw │
│ asteroid_analyses │
│ nasa_feeds │
└───────────┬─────────────────────┘
│ read
▼
┌─────────────────────────────────┐
│ Textual Dashboard (local TUI) │
│ Home · Asteroids · Charts │
│ Pipeline · Logs │
└─────────────────────────────────┘
# 1 — create env file
cp infra/env/python.env.example infra/env/python.env
# → set NASA_API_KEY inside python.env
# 2 — build and start all services
cd infra
docker compose up --buildServices started:
| Service | URL |
|---|---|
| Python API | http://localhost:5001 |
| Rust Engine | http://localhost:8080 |
| MongoDB | localhost:27018 (mapped from the container's 27017, to avoid clashing with a local mongod) |
# Env file (local dev reads the root .env, not infra/env/python.env)
cp .env.example .env
# → set NASA_API_KEY inside .env
# MongoDB
mongod --dbpath /tmp/mongodb --logpath /tmp/mongodb.log --fork
# Rust Engine
cd services/rust-engine && cargo run
# Python API
cd services/python-api
source venv/bin/activate
python -m app.main
# Dashboard (separate terminal)
cd services/dashboard
source venv/bin/activate
python -m app.maincd services/python-api && python -m venv venv && source venv/bin/activate && pip install -r requirements-dev.txt
cd ../dashboard && python -m venv venv && source venv/bin/activate && pip install -r requirements.txtOr use the one-command launcher:
./infra/scripts/start_dev.shFull request/response examples live in docs/:
Python API spec · Rust Engine spec
Python API — port 5001
| Endpoint | Description |
|---|---|
GET /nasa/asteroids |
Filterable, normalized NEO list (hazard, distance, name, sort) |
GET /nasa/asteroids/<id> |
Full asteroid record — close approaches + orbital data |
GET /nasa/neo/feed |
Raw NASA NeoWS feed proxy (max 7-day window) |
POST /nasa/neo/save |
Fetch + persist a feed window to MongoDB (deduplicated) |
POST /pipeline/neo/analyze |
Run Rust risk analysis on all unprocessed asteroids |
GET /pipeline/status |
Health check — MongoDB + Rust Engine connectivity |
GET /pipeline/stats |
Pipeline statistics (unprocessed, analyzed, high-risk counts) |
GET /pipeline/analysis/asteroids |
List analyzed asteroids, sorted by risk/energy/date |
GET /pipeline/close-approaches |
NEOs sorted by miss distance, enriched with risk data |
GET /logs |
Recent structured log entries |
Rust Engine — port 8080
| Endpoint | Description |
|---|---|
GET /api/health |
Liveness check |
POST /api/process/batch |
Impact-physics risk analysis for 1–500 asteroids |
| Key | Screen |
|---|---|
h |
Home — system status + quick actions |
a |
Asteroids — analyzed risk table |
c |
Charts — miss distance scatter + size distribution bar chart |
p |
Pipeline — trigger analysis, live stats |
l |
Logs — real-time log viewer |
q |
Quit |
The dashboard reads API_BASE_URL (default http://localhost:5001) — point it at a deployed
instance by exporting the variable before launch:
API_BASE_URL=https://your-deployed-api.fly.dev python -m app.mainservices/
├── python-api/ Flask API — NASA proxy, caching, pipeline orchestration
│ ├── app/
│ │ ├── core/ nasa_client, cache, mongodb, rust_client, pipeline, dto_mapper, config
│ │ ├── routes/ nasa, orchestration, logs
│ │ ├── models/ Asteroid domain model
│ │ └── utils/ Structured logger, centralized error handlers
│ ├── Dockerfile
│ ├── requirements.txt
│ └── requirements-dev.txt pytest, flake8, black (not in the Docker image)
│
├── rust-engine/ Axum microservice — impact physics + risk scoring
│ ├── src/
│ │ ├── api/ HTTP handlers
│ │ ├── domain/ Asteroid, RiskResult, errors
│ │ ├── dto/ Validated request/response types
│ │ └── logic/ Impact energy, risk algorithm
│ └── Dockerfile
│
└── dashboard/ Textual TUI — local visualization
└── app/
├── screens/ Home, Asteroids, Charts, Pipeline, Logs
├── widgets/ StatsPanel, AsteroidTable, LogViewer
└── client/ API client with retry strategy
infra/
├── docker-compose.yml Full stack orchestration
├── env/ Environment variable templates
└── scripts/ Local dev launcher, Docker Hub image publisher
All NASA API calls are cached in-memory with a 5-minute TTL (thread-safe cachetools.TTLCache).
Cache keys include the full parameter set so different date ranges are cached independently.
The cache is intentionally in-process — no Redis dependency for the challenge scope.
A threat index from 0 to 100, not an impact probability (that would need NASA's Sentry impact-monitoring data). It answers "how bad would this one be, and how close does it pass?":
- Energy — kinetic energy from diameter (silicaceous density, 2700 kg/m³) and velocity, mapped on a log scale: ~10¹² J → 0, ~10²³ J → 100.
- Proximity — ×1.0 within 1 lunar distance (LD), sliding on a log scale to ×0.5 at ~195 LD (0.5 AU, the far edge of what NeoWS returns).
- Hazard flag — +10 if NASA marks it potentially hazardous.
Levels: Low < 25 ≤ Medium < 50 ≤ High < 75 ≤ Critical.
Every analysis stores the model_version that produced it (RISK_MODEL_VERSION
in services/rust-engine/src/domain/risk.rs). Analyses are fully derived from
asteroids_raw, so after a formula change they can simply be recomputed:
- Bump
RISK_MODEL_VERSIONand restart the engine (Docker:docker compose up --build rust-engine). - Delete the old analyses:
mongosh astroforge_db --eval 'db.asteroid_analyses.deleteMany({})' # Docker: docker exec astroforge-mongodb mongosh astroforge_db --eval 'db.asteroid_analyses.deleteMany({})'
- Run the pipeline (dashboard → Run Pipeline). It processes up to 1000 asteroids per run.
To check for stale analyses: db.asteroid_analyses.countDocuments({"risk_data.model_version": {$ne: 3}}).
Python API
| Variable | Required | Description |
|---|---|---|
NASA_API_KEY |
Yes | NASA API key (api.nasa.gov) — no default, raises on startup if unset |
MONGO_URI |
No | MongoDB connection string (default: mongodb://localhost:27017) |
MONGO_DB |
No | Database name (default: astroforge_db) |
RUST_ENGINE_URL |
Yes | Rust engine base URL |
LOG_DIRECTORY |
No | Log file path (default: ./logs; Docker Compose overrides this to /app/storage/logs) |
REQUEST_TIMEOUT |
No | NASA HTTP timeout in seconds (default: 30) |
DEBUG |
No | Flask debug mode with auto-reload, bound to 127.0.0.1 only (default: false) |
Where they come from:
- Docker — copy
infra/env/python.env.example→infra/env/python.env. - Local dev — copy
.env.example→.envin the project root.
Rust Engine
| Variable | Required | Description |
|---|---|---|
RUST_PORT |
No | Listen port (default: 8080) |
Copy infra/env/rust.env.example → infra/env/rust.env before starting.
Licensed under the MIT License — see LICENSE for details





