Nova DSO Tracker is a self-hosted Flask web application for astrophotographers to plan deep-sky imaging sessions, track observable objects, log session data, and manage equipment. It uses SQLAlchemy with SQLite for persistence, Jinja2 templates for server-rendered HTML, and background daemon threads for weather data, heatmap caching, and update checks. The app runs as a single-process Flask dev server or behind gunicorn in Docker; all state lives in instance/app.db (plus instance/users.db in multi-user mode). Deployment is a single container with a mounted instance/ volume — no external databases, message queues, or cloud services required.
Eight blueprints, all registered with no URL prefix. Route functions live in nova/blueprints/ (blueprint refactor is complete — routes are no longer in nova/__init__.py).
Dashboard, auth, object search, location management, graph views.
| Method | Path | Purpose |
|---|---|---|
| GET | / |
Main dashboard |
| GET/POST | /config_form |
User configuration |
| GET/POST | /login |
User login |
| POST | /logout |
User logout |
| GET | /sso/login |
SSO login (JWT) |
| POST | /search_object |
SIMBAD/object search |
| POST | /confirm_object |
Confirm and add object |
| POST | /fetch_object_details |
Fetch single object details |
| POST | /fetch_all_details |
Fetch all objects' details |
| GET | /stream_fetch_details |
SSE stream for bulk fetch |
| GET | /graph_dashboard/<name> |
Altitude graph page |
| GET | /get_imaging_opportunities/<name> |
Best imaging dates |
| GET | /get_date_info/<name> |
Date-specific object info |
| GET | /sun_events |
Sun rise/set/transit |
| GET | /get_locations |
User's locations |
| POST | /set_location |
Set active location |
| GET | /get_outlook_data |
Weather outlook |
| POST | /proxy_focus |
Proxy to Stellarium |
| GET | /generate_ics/<name> |
Generate .ics calendar |
| GET | /analytics |
Analytics dashboard |
| GET | /set_language/<lang> |
Set language preference |
| POST | /trigger_update |
Trigger background update |
| POST | /update_project |
Update project fields |
| POST | /update_project_active |
Toggle project active |
| POST | /api/journal/custom-filters |
Add custom journal filter |
| DELETE | /api/journal/custom-filters/<key> |
Delete custom filter |
| GET | /uploads/<user>/<file> |
Serve uploaded images |
| GET | /favicon.ico |
Favicon |
JSON API endpoints consumed by frontend JavaScript.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/get_plot_data/<name> |
Altitude plot data |
| GET | /api/get_monthly_plot_data/<name> |
Monthly plot data |
| GET | /api/get_yearly_heatmap_chunk |
Heatmap tile data |
| GET | /api/get_object_data/<name> |
Single object details |
| GET | /api/get_object_list |
All user objects |
| GET | /api/get_observable_objects |
Currently observable |
| GET | /api/get_framing/<name> |
Saved framing config |
| POST | /api/save_framing |
Save framing config |
| POST | /api/delete_framing |
Delete framing |
| GET | /api/get_saved_views |
List saved graph views |
| POST | /api/save_saved_view |
Save a graph view |
| POST | /api/delete_saved_view |
Delete a saved view |
| GET | /api/get_shared_items |
Community shared items |
| POST | /api/import_item |
Import shared item |
| GET | /api/get_weather_forecast |
Weather forecast JSON |
| GET | /api/get_moon_data |
Moon phase/position |
| GET | /api/calibration_star |
Calibration star data |
| GET | /api/get_desktop_data_batch |
Batch data for desktop |
| GET | /api/mobile_data_chunk |
Paginated mobile data |
| GET | /api/mobile_status |
Mobile status check |
| POST | /api/bulk_fetch_details |
Bulk SIMBAD fetch |
| POST | /api/bulk_update_objects |
Bulk object updates |
| POST | /api/update_object |
Update single object |
| POST | /api/merge_objects |
Merge duplicate objects |
| GET | /api/find_duplicates |
Detect duplicates |
| GET | /api/journal/objects |
Objects for journal dropdown |
| POST | /api/parse_asiair_log |
Parse ASIAir log file |
| GET | /api/session/<id>/log-analysis |
Log analysis results |
| GET | /api/latest_version |
Version check |
| GET | /api/help/<topic> |
Help content |
| GET | /api/help/img/<file> |
Help images |
| POST | /api/internal/provision_user |
SSO user provisioning |
| POST | /api/internal/deprovision_user |
SSO user removal |
| POST | /telemetry/ping |
Telemetry beacon |
| GET | /telemetry/debug |
Telemetry debug |
Session logging with equipment, weather, and image data.
| Method | Path | Purpose |
|---|---|---|
| GET | /journal |
Session list |
| GET/POST | /journal/add |
New session |
| GET/POST | /journal/add_for_target/<name> |
New session for object |
| GET/POST | /journal/edit/<id> |
Edit session |
| POST | /journal/delete/<id> |
Delete session |
| POST | /journal/duplicate/<id> |
Duplicate session |
| POST | /journal/add_project |
Create project from session |
| GET | /journal/report_page/<id> |
Session report |
| GET | /journal/download_csv/<type>/<id> |
CSV export |
Touch-optimized views for phone/tablet use.
| Method | Path | Purpose |
|---|---|---|
| GET | /m |
Mobile dashboard |
| GET | /m/add_object |
Add object |
| GET | /m/object/<name> |
Object detail |
| GET | /m/up_now |
What's up right now |
| GET | /m/outlook |
Weather outlook |
| GET | /m/location |
Location info |
| GET | /m/framing_coords/<name> |
Framing tool |
| GET | /m/mosaic/<name> |
Mosaic planner |
| GET | /m/edit_notes/<name> |
Edit object notes |
| GET/POST | /m/journal/new |
Quick journal entry |
| GET | /sw.js |
Service worker |
Project management tying objects to multi-session imaging efforts.
| Method | Path | Purpose |
|---|---|---|
| GET/POST | /project/<id> |
Project detail/overview |
| POST | /project/delete/<id> |
Delete project |
| GET | /project/report_page/<id> |
Project report |
Equipment CRUD, YAML/CSV import/export, database maintenance.
| Method | Path | Purpose |
|---|---|---|
| POST | /add_component |
Add equipment piece |
| POST | /update_component |
Update equipment |
| POST | /delete_component |
Delete equipment |
| POST | /add_rig |
Create rig (scope+camera) |
| POST | /delete_rig |
Delete rig |
| GET | /get_rig_data |
All rigs/components |
| POST | /set_rig_sort_preference |
Sort preference |
| GET | /download_config |
Export objects as YAML |
| POST | /import_config |
Import objects from YAML |
| GET | /download_rig_config |
Export equipment YAML |
| POST | /import_rig_config |
Import equipment YAML |
| GET | /download_journal |
Export journal CSV |
| POST | /import_journal |
Import journal CSV |
| GET | /download_journal_photos |
Export session photos |
| POST | /import_journal_photos |
Import session photos |
| POST | /import_catalog/<pack> |
Import catalog pack |
| GET | /tools/export/<user> |
Full user YAML export |
| POST | /tools/import |
Full user YAML import |
| POST | /tools/repair_db |
Database repair |
| POST | /upload_editor_image |
Rich-text image upload |
User management (multi-user mode only).
| Method | Path | Purpose |
|---|---|---|
| GET | /admin/users |
User list |
| POST | /admin/users/create |
Create user |
| POST | /admin/users/<id>/toggle |
Activate/deactivate |
| POST | /admin/users/<id>/reset-password |
Reset password |
| POST | /admin/users/<id>/delete |
Delete user |
AI-powered features via OpenAI-compatible API.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/ai/best_objects |
Recommend objects for tonight |
| POST | /api/ai/session-summary |
Summarize a session |
| POST | /api/ai/notes |
Generate DSO notes |
| GET | /api/ai/status |
AI service health |
| GET | /api/ai/prefilter_debug |
Debug AI prefiltering |
All models in nova/models.py. Two SQLite databases: instance/app.db (main) and instance/users.db (multi-user auth only). The User class is conditionally defined in nova/auth.py — as a Flask-SQLAlchemy model backed by users.db in multi-user mode, or as a plain UserMixin in single-user mode.
Central user record. Every user-scoped model has a user_id FK with cascade delete.
| Field | Notes |
|---|---|
username |
String(80), unique |
password_hash |
Nullable (set in multi-user mode) |
active |
Boolean, default True |
Relationships: locations, objects, saved_views, components, rigs, sessions (JournalSession), ui_prefs, projects — all one-to-many, cascade delete.
Groups multiple imaging sessions around one target.
| Field | Notes |
|---|---|
id |
UUID string (not auto-increment) |
name, target_object_name |
|
status |
"In Progress", "Complete", etc. |
description_notes, framing_notes, processing_notes, goals |
Rich text (Trix editor) |
final_image_file |
Path to uploaded image |
Relationships: user (DbUser), sessions (JournalSession).
The fattest model. Stores a single imaging session with weather, equipment, and image metadata.
| Field | Notes |
|---|---|
date_utc |
Session date |
object_name |
Target |
notes |
Free text |
session_image_file |
Uploaded photo |
| Location/weather | location_name, seeing_observed_fwhm, sky_sqm_observed, moon_*, weather_notes |
| Equipment | filter_used_session, guiding_rms_avg_arcsec, gain_setting, offset_setting, camera_temp_*, binning_session |
| Per-filter subs | L, R, G, B, Ha, OIII, SII counts + exposure times |
custom_filter_data |
JSON string for user-defined filters |
| Rig snapshot | rig_*_snapshot fields — denormalized copy of rig at session time |
| Integration logs | asiair_log_content, phd2_log_content, nina_log_content, log_analysis_cache |
draft |
Boolean, for WIP sessions |
Relationships: user (DbUser), project (Project), rig_snapshot (Rig).
A tracked deep-sky object.
| Field | Notes |
|---|---|
object_name |
Catalog designation (e.g. "M 31") |
common_name |
"Andromeda Galaxy" |
ra_hours, dec_deg |
J2000 coordinates |
type, constellation, magnitude, size, sb |
Catalog metadata |
active_project, project_name |
Linked project |
enabled |
Soft-hide toggle |
| Sharing | is_shared, original_user_id, original_item_id |
| Images/descriptions | image_url, image_credit, description_text, etc. |
catalog_sources, catalog_info |
JSON from SIMBAD/VizieR |
Relationships: user (DbUser).
A single piece of equipment (telescope, camera, or reducer/extender).
| Field | Notes |
|---|---|
stable_uid |
UUID, survives import/export |
kind |
"telescope", "camera", or "reducer_extender" |
| Telescope fields | aperture_mm, focal_length_mm |
| Camera fields | sensor_width_mm, sensor_height_mm, pixel_size_um |
| Reducer fields | factor |
| Sharing | is_shared, original_user_id, original_item_id |
Relationships: user (DbUser), rigs_using (Rig).
Combines telescope + camera + optional reducer into an imaging rig. Also supports guide scope/camera.
| Field | Notes |
|---|---|
stable_uid |
UUID |
rig_name |
|
telescope_id, camera_id, reducer_extender_id |
FK → components |
| Computed | effective_focal_length, f_ratio, image_scale, fov_w_arcmin |
| Guide optics | guide_telescope_id, guide_camera_id, guide_is_oag |
Relationships: user (DbUser), telescope/camera/reducer_extender/guide_telescope/guide_camera → Component.
| Field | Notes |
|---|---|
stable_uid |
UUID |
name, lat, lon, timezone |
|
altitude_threshold |
Min altitude for visibility |
bortle_scale |
1–9 sky darkness |
is_default, active |
Relationships: user (DbUser), horizon_points (HorizonPoint).
Azimuth/altitude pairs defining an obstructed horizon for a location.
| Field | Notes |
|---|---|
location_id |
FK → locations |
az_deg, alt_min_deg |
Persisted graph/dashboard configurations.
| Field | Notes |
|---|---|
name, description |
|
settings_json |
Serialized view config |
is_shared |
Community sharing |
Framing tool state per object (survey, rotation, mosaic, image adjustments).
| Field | Notes |
|---|---|
object_name, rig_id |
|
ra, dec, rotation |
|
survey, blend_survey, blend_opacity |
|
| Mosaic | mosaic_cols, mosaic_rows, mosaic_overlap |
| Image adj. | img_brightness, img_contrast, img_gamma, img_saturation |
Single JSON blob per user for UI preferences (theme, sidebar state, etc.).
GDPR-compliant counters (event_name + date, no user identifiers).
User-defined filter bands (e.g. "L-Enhance") for journal session tracking.
| Dependency | Purpose | Used In |
|---|---|---|
| Flask + SQLAlchemy | Web framework + ORM | Everywhere |
| astropy + ephem | Astronomical coordinate transforms, altitude/azimuth, sun/moon events | modules/astro_calculations.py, heatmap worker |
| astroquery (SIMBAD, VizieR) | Object lookup by name, catalog cross-match | modules/nova_data_fetcher.py |
| Open-Meteo API | Free weather forecast (cloud cover, humidity, temp) | nova/workers/weather.py |
| Plotly.js | Altitude/heatmap charts in browser | Frontend (CDN/bundled) |
| Chart.js | Secondary charts | Frontend (bundled) |
| Trix editor | Rich text for project/session notes | Frontend (CDN) |
| OpenAI-compatible API | "Ask Nova" AI features (configurable provider) | nova/ai/service.py |
| Flask-Login + bcrypt | Authentication | nova/auth.py |
| PyJWT | SSO token verification | nova/blueprints/core.py (sso_login) |
| Stellarium API | Planetarium integration (local HTTP) | nova/blueprints/core.py (proxy_focus) |
| Pillow | Image processing (thumbnails, uploads) | nova/helpers.py |
| paho-mqtt | IoT/device integration | nova/ (MQTT client) |
| DeepL API | UI translation | Translation routes |
| gunicorn | Production WSGI server | Docker/production |
Background workers (daemon threads in nova/workers/):
| Worker | Cycle | Purpose |
|---|---|---|
weather.py |
2 hours | Fetch Open-Meteo forecast for all active locations |
heatmap.py |
On-demand / 24h stale | Pre-compute yearly altitude heatmaps (JSON cache in instance/cache/) |
updates.py |
24 hours | Check GitHub for new releases |
iers.py |
24 hours | Refresh Earth rotation data for astropy precision |
Tracing a dashboard page load (GET /):
-
Before-request hooks (
nova/__init__.py):_fix_mode_switch_sessions()— clears stale Flask session data if the app was switched between single/multi-user mode.load_global_request_context()— resolves the current user. In single-user mode, auto-authenticates as "default". Callsget_or_create_db_user()to ensure aDbUserrow exists. Loadsg.user_configfromUiPref. Setsg.sampling_intervalandg.telemetry_enabled.- Telemetry hooks — bootstrap and periodic ping (fire-and-forget).
-
Route handler (
nova/blueprints/core.py:index):load_full_astro_context()— queries all locations (with horizon points eager-loaded) and all objects for the user, setsg.locations,g.objects_list,g.objects_map,g.lat,g.lon,g.tz_name.- Queries
JournalSession(newest first),Project,AstroObject. - Serializes sessions to dicts with computed fields (common names, project names, ISO dates).
- Computes the "observing night" boundary (before noon = previous night).
-
Template render (
templates/index.htmlextendstemplates/base.html):base.htmlprovides: theme toggle script, CSS, nav bar, JS includes.index.htmlinjectswindow.NOVA_INDEXwith session data, config, threshold.- Includes partials:
_heatmap_section.html,_inspiration_section.html,_journal_section.html,_objects_section.html,_project_subtab.html. - Client-side JS (
dashboard.js) fetches plot/heatmap data via API calls and renders Plotly charts.
Data flow: Route queries SQLAlchemy → serializes to dict → Jinja2 renders initial HTML → JS fetches /api/get_plot_data/* and /api/get_yearly_heatmap_chunk for interactive charts.
The largest file in the project. Contains 35 routes spanning object data, framing, heatmap chunks, weather, sharing, journal helpers, log parsing, and internal provisioning. No clear internal grouping — it's a catch-all for anything JSON-shaped. Would benefit from splitting along domain lines (objects, framing, sharing, journal-API, admin-API).
Second largest. Dashboard rendering, auth flows (login/SSO/logout), object search/confirm, graph views, and miscellaneous utilities. The index() function alone does substantial data preparation. Auth logic could live in nova/auth.py.
Still the single biggest file. Contains the app factory, all before-request hooks, context processors, helper functions (get_or_create_db_user, load_full_astro_context, load_effective_settings), in-memory cache management, and the blueprint registration block. Routes have been moved out, but supporting functions remain.
Parsers for ASIAir, PHD2, and NINA log formats. Each parser is substantial and handles fragile real-world log formats. Tightly coupled to JournalSession field names.
The largest template by far. Renders the journal session table, inline edit forms, filter UI, and session detail panels. Significant amounts of inline JavaScript. Mixes presentation with client-side state management.
Database migration logic for schema evolution. Runs imperative ALTER TABLE statements rather than using Alembic. Must be maintained in lockstep with model changes.
Hard-coded prompt templates for AI features. Pure string data, but large and growing.
gobject as shared state: Before-request hooks stuffgwith user, locations, objects, config. Every route and helper accessesgdirectly. This is implicit global state that makes individual functions hard to reason about in isolation.- Dual-mode complexity:
SINGLE_USER_MODEbranches appear in auth, user provisioning, template rendering, and API responses. The conditionalUserclass definition means type-checkers and IDEs struggle. - In-memory caches:
nova/config.pyholds mutable dicts (weather_cache,nightly_curves_cache) shared between threads. No locking; correctness depends on Python's GIL and the append-only nature of most writes.