8000 GitHub - ManiFed/TTN · GitHub
[go: up one dir, main page]

Skip to content
ManiFedPublic

About

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

690 Commits< 8BD0 /a>

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

The Telescope Net — Node Software & Cloud

The Telescope Net is the natural language interface for telescope control — so you can point it at real research, not a driver, an app, or a config file. Say "connect my telescope" or "what's the plan tonight" and the network handles the rest.

That research program is the point. Seestar owners donate their telescope's nights. The pipeline schedules observations of scientifically valuable targets, processes the data into calibrated photometry, and submits it to AAVSO under the contributor's name. Members do real astronomy — no manual intervention required.

The Telescope Net is an astronomy charity that gives anyone access to real telescope time through natural language. Automated software, with AI support, schedules variable-star observations, processes photometry, and submits to AAVSO on each member's behalf.


Fleet Digital Twin

The network can be simulated offline as an evolving instrument: synthetic fleets (5–1,000 nodes), correlated weather, class-specific target physics, and the production CHORUS solver executed unchanged, compared against baseline schedulers on identical nights. Deterministic per seed, no DB, no live APIs.

python -m sim list                          # scenario library
python -m sim run --scenario beta_5_nodes   # one command, full comparison
python -m pytest tests/test_sim.py          # CI-safe smoke tests (~8 s)

See SIMULATION.md (model + assumptions + how to run) and docs/FLEET_DIGITAL_TWIN_REPORT.md (partner-facing results).

Beta-node readiness

make preflight        # read-only node/config/cloud/storage readiness check
make preflight-active # same, but actually authenticates to the cloud, writes
                       # to each runtime dir, and launches the solver binary
                       # — run this before each observing session
make beta-init         # create the C1–C7 real-data capture corpus
make beta-audit        # verify captured FITS checksums and campaign coverage

Add a frame with python3 scripts/beta_capture.py add C1 frame.fits --node-id node042. See docs/validation/FIXTURE_MANIFEST.md for campaign-specific collection instructions.

Automatic commissioning after signup

When a node receives its cloud credentials, commissioning starts without another member action. It persists in data/commissioning.json, checks location, disk, plate solving, telescope/camera connectivity and FITS ingest, and resumes after restarts or temporarily disconnected hardware. The first ordinary science FITS adds checksum-pinned timing, WCS, image-shape, and detector-range evidence. Progress appears in the local web UI and travels in cloud heartbeat conditions for remote support.

Commissioning is deliberately non-invasive: it never slews or exposes a telescope outside the scheduler and safety system. POST /api/commissioning/restart re-runs it after hardware changes.

Repository Contents

node_v1-main/
├── main.py               Dev-mode watchdog (auto-restarts src/dashboard.py)
├── config.yaml           Node configuration (edit before first run)
│
├── src/                  Node Agent source (moved for clean separation)
│   ├── dashboard.py          Flask dashboard + control loop
│   ├── main_service.py       Production entry point (used by installers, no subprocess)
│   ├── sleep_prevention.py   Cross-platform OS sleep prevention
│   ├── cloud_communicator.py Node → Cloud API client (heartbeat, plan, measurements)
│   ├── photometry.py         Aperture photometry pipeline (ASTAP → comp stars → mag)
│   ├── stacking.py           RANSAC live sub-pixel stacking
│   ├── image_watcher.py      FSEvents/inotify watcher on the Seestar SMB share
│   ├── aavso_submission.py   AAVSO Extended File Format + WebObs API (node-side)
│   ├── fits_export.py        Enhanced FITS header writer
│   ├── geolocation.py        IP-based location detection for auto-registration
│   ├── shared_models.py      NodeInfo, TargetInfo, PlanItem, ObservationPlan, Measurement
│   └── __init__.py           Package marker
│
├── alpaca/               ALPACA protocol abstraction layer
│   ├── telescope.py      Slew, track, park, RA/Dec query
│   ├── camera.py         Expose, status, FITS export
│   ├── focuser.py        Move, halt, position
│   ├── autofocus.py      V-curve sweep → parabolic minimum
│   ├── filterwheel.py    Filter position management
│   ├── platesolve.py     ASTAP → WCS → closed-loop centering
│   ├── safety_manager.py Altitude limits, auto-park, dawn detection
│   └── device_manager.py Connect / disconnect all devices
│
├── cloud/                Cloud server (runs on a VPS, not on the telescope host)
│   ├── main.py           Entry point — Flask API + background loops
│   ├── server.py         All API routes (nodes, members, public, admin)
│   ├── db.py             SQLite schema + migration system
│   ├── registry.py       Node registration, auth, heartbeat, performance refresh
│   ├── auth.py           Member registration, login, bearer token auth
│   ├── alerts.py         Alert ingestion (ALeRCE, Gaia, TNS, ATLAS, ASAS-SN, AAVSO)
│   ├── scoring.py        Composite (target × node) scoring engine
│   ├── scheduler.py      Nightly plan generation
│   ├── data_pipeline.py  Measurement ingestion, cross-validation, AAVSO batch
│   ├── nights.py         Night summary generation, notification dispatch
│   ├── conditions.py     Weather, moon, airmass utilities
│   ├── tuning.py         Claude-powered weight tuning monitor (observability scoring)
│   └── config.yaml       Cloud configuration (fill in AAVSO credentials here)
│
├── cloud_data/          Data files (database, batches, exports)
│   ├── cloud.db          SQLite database
│   └── aavso_batches/    Generated AAVSO submission batches
│
├── website/             Marketing site
│   ├── tour.html         Interactive founding network experience (main site)
│   └── README.md         Website documentation and API integration guide
│
├── app/                 Flutter desktop member app — the only member app (macOS, Windows, Linux, Raspberry Pi OS)
│   ├── lib/              Screens (dashboard, observations, help/AI chat, nodes, notifications, more), API client, state, Aladin widgets
│   ├── pubspec.yaml
│   └── README.md         App build and accessibility notes
│
├── scripts/
│   ├── manage.py         Admin CLI (status, ingest, batch, submit, check-aavso, generate-code)
│   ├── seed_demo.py      Demo data generator for testing
│   └── validate_photometry.py  Photometry validation engine (offline; `make validate`)
│
├── docs/validation/     Scientific credibility layer
│   ├── VALIDATION_REPORT.md  Partner-facing photometry validation report (AAVSO)
│   ├── RISK_MAP.md           Ranked photometric-error risk analysis
│   ├── FIXTURE_MANIFEST.md   Real-data corpus spec for beta nodes
│   └── evidence/             Latest validation run (report, results.json, figures)
│
├── build/                Installer build system
│   ├── node_agent.spec   PyInstaller spec
│   ├── build.py          Cross-platform build orchestration
│   ├── config.template.yaml  Config template written by installers
│   ├── windows/install.nsi   NSIS Windows installer
│   ├── macos/            macOS .pkg + launchd plist
│   └── linux/            systemd unit + install.sh
│
└── archive/              Deactivated code — never built, shipped, or tested
    └── ios/              Retired iOS member apps (see archive/README.md)

Phase Status

Phase Status Goal
0 — Proof of Concept ✅ Code complete First AAVSO-accepted automated observation
1 — Core System 🚀 In progress Installers shipped, member accounts live, founding network site live, Flutter app in development, 3–5 beta nodes
2 — Launch 🔜 Next 50 nodes, signed desktop app distribution, first ATel, first grant application
3 — Growth Not started 200 nodes, 25 countries, 10,000+ AAVSO submissions

Recent Changes (June–July 2026)

Network Science Expansion

The network now has a safe, staged path to three new operational capabilities:

  • a self-calibrating photometric mesh that learns response-specific zero-point, color, extinction, and drift corrections from overlapping stable stars, validates them in shadow, and preserves immutable raw magnitudes;
  • global event tiling for GCN transient notices, including event revisions, retractions, probability-map tiling, depth-aware assignment, and event coverage accounting; and
  • bounded disconnected-night autonomy, where a qualified node can execute only a signed, expiring UTC plan with durable restart recovery and idempotent reconciliation.

All three ship behind conservative defaults: calibration application is off, autonomy is disabled pending signed-key canaries, and the dedicated GCN worker does not live-dispatch to telescopes. The occultation array is explicitly a documentation-only future program. See docs/NETWORK_SCIENCE_EXPANSION.md for the architecture, safeguards, deployment sequence, and operational boundaries.

Live Fleet State + Universal Frame Ingestion (New Programs)

Two structural additions on top of CHORUS and Open Aperture — see LIVE_FLEET.md for full design and status.

  • Live fleet state — the network keeps a live, second-scale picture of the fleet (cloud/live.py + a dedicated realtime/ SSE gateway service) instead of learning what happened the next morning. Mid-night reflow (cloud/chorus/reflow.py) re-values a clouded-out node's remaining targets on other dark nodes by calling CHORUS's own marginal-value function — the optimizer itself is untouched. Reflex confirmation (cloud/reflex.py) tasks other dark nodes to confirm a just-promoted discovery candidate within seconds instead of waiting for the next replan.
  • Universal frame ingestion — any FITS frame, from any camera, live or archival, can become survey science. The cloud now plate-solves contributed frames that arrive without a WCS (src/plate_solve.py, cloud/solver.py), runs a staged triage → solve → extract → ingest pipeline (cloud/ingest_worker.py), and a lightweight companion watcher mode (src/companion.py) turns any astrophotographer's output folder into a feed. Archive frames route through a retrospective path (retro_discoveries) that enriches baselines without ever firing a live alert.
  • Reflow ships disabled (scheduler.reflow: false); reflex confirmation is enabled by default. Both are fully tested — see tests/test_live_fleet_cloud.py, tests/test_reflow_reflex.py, tests/test_ingest_worker.py, tests/test_triage.py, tests/test_historical.py.

Member App (Flutter) — Major Progress

  • App redesign wired to real backend data
    • Observation history tab (observations_tab.dart): recent photometric measurements + per-night summaries
    • Help tab (help_tab.dart): AI assistant chat (OpenRouter + Claude Haiku 4.5, 5 messages/week), node diagnostics, and safe config.yaml patch queue for node agents
    • Science program suggestions (suggest_program_screen.dart): members can propose new targets/programs
    • More menu, Me tab (moved to sidebar), target detail screens
    • Real data only mode; members can name their telescopes
    • Push notifications (Firebase) + night summary alerts
    • Aladin Lite sky visualization integrated into the operational dashboard
  • Dashboard iterations: institutional/no-scroll layouts, workbench-style IDE UI, mission control previews (see app/archive/)
  • Deployment: Railway one-command API deploys (scripts/deploy-api.sh) and GitHub Actions desktop builds
  • iOS apps deactivated (2026-07-19): the desktop app is the only member application. Both the standalone SwiftUI app and the Flutter iOS build target moved to archive/ios/ — not built, shipped, or tested. See archive/README.md.

Marketing Site Launch (website/tour.html)

  • Interactive founding network experience — single-page scrollytelling site
    • Beautiful, responsive design with glassmorphism + dark theme
    • Animated sky visualization using Aladin Lite (live star field background)
    • Real-time stats pulled from the cloud API (/api/v1/network/status, /api/v1/lightcurves/SS_Cyg)
    • Seven narrative sections: why it matters, the network idea, how to join, real example (SS Cygni), science impact, accessibility, founding seats
    • Email signup + funding donation flow (JavaScript form handling)
    • Fully responsive — mobile, tablet, desktop
    • No build step, no dependencies — runs as static HTML in any browser or on the cloud server

Directory Reorganization

  • Node Agent source files moved to src/ — cleaner separation of concerns

    • src/dashboard.py, src/cloud_communicator.py, src/photometry.py, etc.
    • Main entry point main.py and config.yaml remain at project root
    • Installer and launch configurations updated
  • Website structure (website/)

    • website/tour.html — Main public site (replaces index.html)
    • website/future/ — Phase 2+ member portal scaffolding
    • website/README.md — Site setup and API integration docs
  • Database and demo data organized into cloud_data/

    • cloud_data/cloud.db — SQLite database
    • cloud_data/aavso_batches/ — Generated submission files
    • scripts/seed_demo.py — Demo data generator for testing (23 nodes across 14 countries)

Claude Weight-Tuning Monitor (New Feature)

  • cloud/tuning.py — Nightly advisory monitor that reads observation outcomes and proposes adjustments to observability scoring weights
    • Hot path unchanged — alert ingestion, scoring, and planning remain 100% procedural
    • Opt-in — controlled by tuning.enabled in cloud/config.yaml
    • Audited — every weight change is logged with reasoning and can be rolled back
    • Requires ANTHROPIC_API_KEY environment variable when enabled
    • See Weight Tuning Monitor section for full documentation

Database Schema Extensions

  • New tuning_state table — stores live observability weights
  • New weight_history table — audit trail of all scoring weight changes
  • Fixed missing filters column in nodes table
  • All migrations applied automatically on cloud server startup

Alert Integration Improvements

  • ATLAS — fixed token-based authentication endpoint
  • All alert sources now use modern auth patterns

Other Updates

  • requirements.txt — added anthropic>=0.92 (optional, lazy-loaded)
  • Admin CLI (scripts/manage.py) — added tuning monitoring support
  • API server (cloud/server.py) — new tuning endpoints (GET /api/v1/admin/tuning, POST /api/v1/admin/tuning/rollback)

Node Data Model

This is the most important architectural document in the repo. The nodes table is the single source of truth the AI scheduler reads when deciding which telescope points where. Every column exists for a reason.

Location (observability)

Column Type Used for
latitude, longitude REAL Night window, altitude curve, airmass, moon angle
elevation REAL Pressure correction for airmass calculation
utc_offset_hours REAL Twilight times in local clock; auto-updated from heartbeat
city, country TEXT Display, geographic diversity tracking
light_pollution_mpsas REAL Sky background noise; faint-target magnitude limit adjustment
bortle INT Human-readable sky quality label; auto-fetched on registration
horizon_mask JSON [[alt,az],…] Local obstructions — the scheduler won't assign targets behind trees or buildings

Hardware: Telescope (capability)

Column Type Used for
tier INT 1–3 1=Seestar broadband, 2=Filtered BVRI, 3=Spectroscopy; controls which target classes get assigned
telescope_model TEXT Display; future per-model aperture lookup
aperture_mm REAL Faint magnitude limit, integration time scaling
focal_length_mm, fov_deg, pixel_scale_arcsec REAL Field-of-view matching for targets with extended structure
mount_type TEXT alt_az|equatorial Equatorial mounts can take longer sub-frames; alt-az has field rotation
max_exposure_s REAL Sub-frame cap, primarily field-rotation limited for alt-az mounts
mag_bright_limit, mag_faint_limit REAL Direct inputs to the brightness_match() scorer
min_altitude_deg REAL Hard floor — targets below this altitude are never scheduled for this node

Hardware: Camera & Filters (photometry quality)

Column Type Used for
camera_model TEXT Display; future per-camera calibration frames
cooled_camera BOOL TEC cooling lowers read noise → better faint limit; captured but not yet scored
filter_set JSON ["CV","B","V","R","I"] Multi-band targets require a matching filter; broadband-only Seestars get ["CV"]
filters TEXT Legacy comma-separated; kept for compatibility with older node agents

Hardware: Autonomy (unattended operation reliability)

These four flags determine whether a node can run an entire night without human intervention. The scheduler does not assign a numeric penalty for missing them today, but nodes with all four tend to produce better clear_nights_30d and higher aavso_accepted counts — which flows directly into their reliability_score over time.

Column Why it matters for the scheduler
has_dew_heater Prevents lens fogging in humid conditions. Without one, a node fails silently — images blur, plate-solving fails, the whole night is wasted with no measurement data.
has_power_mgmt Smart power box lets the node agent remotely cycle a hung Seestar. Without it, a crashed scope means a missed assignment with no recovery until a human intervenes.
has_enclosure Dome or minidome — the node can observe through light rain, wind, and heavy dew. Dramatically improves clear_nights_30d which feeds the reliability score.
has_ups Brief power cuts don't kill the night. Especially relevant for nodes in regions with unstable grid power — protects both clear_nights_30d and aavso_acceptance_rate.

Scheduler Hints (operator-provided)

Column Example
scheduling_notes "south horizon blocked past az 195", "WiFi drops after midnight", "struggles above airmass 2.5" — free text surfaced to the scheduler and admin
preferred_targets ["SN","nova"] — scheduler gives this node a soft preference for these target types when scores are close

Performance Metrics (recomputed nightly)

These columns are never set by the node agent. The nightly maintenance loop calls registry.refresh_all_performance() which recomputes them from the measurements table. They represent ground truth — what a node has actually delivered, not what it claims it can deliver.

Column Formula What it tells the scheduler
total_observations COUNT(*) in measurements Activity level; nodes with < 10 observations stay at neutral 0.50 reliability
aavso_accepted SUM(aavso_submitted = 1) How much science has been delivered — the ultimate output metric
aavso_rejected Outlier count with good quality flag How often this node produces data the network can't corroborate
mean_uncertainty AVG(uncertainty) for non-poor measurements Typical photometric precision; AAVSO quality ceiling is 0.30 mag
mean_fwhm AVG(fwhm) for non-poor measurements Typical image quality / seeing; proxy for focus and atmospheric stability
clear_nights_30d COUNT(DISTINCT date) in last 30 days How often the node actually observes; captures weather + hardware reliability combined
outlier_rate outliers / total Cross-validation disagreement rate; high values signal that data can't be trusted

Reliability Score

reliability_score is a composite 0..1 value stored per-node and applied as a multiplier on every scheduler score for that node:

total_score = theoretical_score × (0.5 + 0.5 × reliability_score)
reliability multiplier meaning
1.0 ×1.00 proven node — no penalty
0.5 ×0.75 new or data-sparse node — slight preference for proven peers
0.0 ×0.50 persistently poor data — still gets some assignments (floor prevents starvation)

Formula (computed in cloud/registry.py → refresh_node_performance()):

reliability = 0.40 × aavso_acceptance_rate
            + 0.25 × (1 − outlier_rate)
            + 0.20 × min(1, clear_nights_30d / 30)
            + 0.15 × precision_factor

precision_factor = max(0, 1 − mean_uncertainty / 0.30)

New nodes (< 10 observations) start at 0.50 and converge toward their true value over roughly 20–30 observations. The formula is intentionally multi-dimensional: a node can't inflate its score by gaming one metric — it has to deliver across all four simultaneously.


Linking a Telescope

There are no activation codes. They were retired in July 2026. Members link a telescope from the desktop app — nothing to request, nothing to copy into a config file. POST /api/v1/me/activation-code and POST /api/v1/admin/activation-codes still exist only to return a 410 pointing at the current flow, and POST /api/v1/nodes/register rejects any request that carries an activation_code.

Linking is what creates the nodes row — every field group described above gets populated from the attach payload.

Member creates an account and signs in to the desktop app
         ↓
Member installs the Node Agent and taps "Connect telescope"
         ↓
App → POST /api/v1/me/nodes/attach   (member bearer token)
      {latitude, longitude, location_name, telescope_model,
       telescope_display_name, telescope_specs, portable}
         ↓
Cloud creates the nodes row, generates node_id + api_key,
and links it to the member (node_members)
  · an identical telescope of this member's that has never
    heartbeated is reused instead of minting a duplicate
  · passing an existing {node_id, api_key} claims an
    already-registered agent instead of creating a new one
         ↓
App installs the credentials on the local agent:
      POST http://localhost:5173/api/cloud/credentials
         ↓
Agent saves node_id + api_key to data/cloud_state.json
and every later call authenticates with those two headers

If the app can't reach the agent (different machine, firewall, headless box), the agent falls back to a pairing token. It prints a short token, shows it on its setup page at http://localhost:5173, and polls GET /api/v1/nodes/pair/<token>; the app pushes the credentials to that token with POST /api/v1/nodes/pair. Tokens are short-lived, and failed claims are rate-limited per IP because the token space is small.


Quick Start — Node Agent (Development)

Low-cost cloud deployment (small fleets)

The API now serves the live event stream at https://api.thetelescope.net/api/v1/stream. For a single telescope, deploy the API with EMBED_REALTIME=1 (the default), point the node's cloud.realtime_url at the API URL, and then stop or remove the separate Railway realtime service. Update any already-installed node configurations before removing that service; old installations may still point at https://realtime.thetelescope.net. The node's regular plan polling remains available if a stream disconnects. Set EMBED_REALTIME=0 if using a separate realtime service for a larger fleet.

The default active heartbeat is now 30 seconds instead of 5 seconds, reducing active heartbeat requests and database writes by about 83% per node. Existing node configurations need the same cloud.heartbeat_fast_interval: 30 change. This reduces live status update frequency to every 30 seconds; plan and interrupt signals still arrive over the event stream.

Check Railway's per-service usage before changing the other services. The GCN consumer and solver worker are separate always-on processes; stop either only if its corresponding alert or frame-solving feature is not needed. The API and database are still required.

# 1. Clone and set up
git clone https://github.com/telescopenet/node_v1 && cd node_v1-main
python3 -m venv venv && source venv/bin/activate
pip install flask pyyaml numpy astropy pillow requests watchdog photutils astroquery pyongc

# 2. Install ASTAP (required for plate-solving and photometry)
#    https://www.hnsky.org/astap.htm — install the binary, then a star
#    database: D20 (~435 MB, recommended default) is a good general-purpose
#    choice; D05 (~140 MB) works for very wide fields, D50 (~940 MB) or D80
#    (~1.3 GB) for narrow-field/long-focal-length setups. See "Plate Solving"
#    in the Node Agent's Settings panel for the tradeoffs.

# 3. Configure (for a dry run, only these are required)
nano config.yaml
#    observatory.latitude / longitude
#    image_watcher.watch_path (Seestar SMB share mount point)
#    cloud.url  (credentials arrive from the app — there is no code to enter)

# 4. Run (dev mode — auto-restarts src/dashboard.py on file changes)
python3 main.py
# Dashboard: http://localhost:5173

First-Time Dry Run (no hardware)

Set these in config.yaml to test the dashboard without a Seestar:

cloud:
  enabled: false

image_watcher:
  enabled: false

photometry:
  enabled: false

aavso:
  dry_run: true

Then python3 main.py. Everything in the dashboard works except hardware control. Object catalog, config editor, logs, and API endpoints are all live.


Quick Start — Cloud Server (Development)

cd cloud/
pip install flask pyyaml requests

# Optional: enable Claude weight tuning (requires ANTHROPIC_API_KEY env var)
export ANTHROPIC_API_KEY="sk-..."
pip install anthropic

# Edit cloud/config.yaml:
#   aavso.observer_code: MXXX
#   aavso.username / aavso.password
#   server.admin_key
#   tuning.enabled: true  (optional, defaults to false)

python3 -m cloud.main
# API: http://localhost:8800
# Health check: http://localhost:8800/api/v1/health

Marketing Site (website/tour.html) — Running the Founding Network Experience

The tour.html is a standalone, zero-dependency HTML+CSS+JS site that pitches The Telescope Net as the natural language interface for telescope control, backed by a real research program, and drives member acquisition. It pulls live data from the cloud API but renders gracefully even when the API is offline.

Quick Start — Website Dev

# Terminal 1: Cloud API server (provides data)
cd cloud/
PYTHONPATH="$PWD:$PWD/src" python3 -m cloud.main
# → serves on http://localhost:8800

# Terminal 2: Website (static file server)
cd website/
python3 -m http.server 4180 --directory .
# → open http://localhost:4180/tour.html
#   or just http://localhost:4180 (if index.html points to tour.html)

What the Site Shows

Section Data source Fallback
Founding seats (7 / 100) /api/v1/network/status → founded_member_count Static "07 / 100"
Online nodes /api/v1/network/status → nodes_online "7 online"
AAVSO submissions /api/v1/network/status → aavso_submitted "—"
Network reliability /api/v1/network/status → highest reliability_score Sensible defaults
Live example (SS Cygni) /api/v1/lightcurves/SS_Cyg → recent observations Empty chart
Network map dots /api/v1/network/status → node locations No pins shown
Sky background Aladin Lite + RA/Dec per section Star field rendered

Architecture

tour.html
├─ Embedded CSS (glassmorphism, dark theme, responsive)
├─ Aladin Lite integration (CDN, sky visualization per scroll section)
├─ API calls (fetch from cloud server, graceful fallback if offline)
├─ Canvas visualization (Earth globe rotation, observer animations)
└─ Email signup form + donation flow (JavaScript state, no backend)

Browser compatibility: Modern browsers (Chrome 90+, Firefox 88+, Safari 14+). Fallback text renders on older clients.

Seeding Demo Data

The site looks better with realistic stats. Populate the database once:

python3 scripts/seed_demo.py --wipe
# Creates 23 nodes across 14 countries + light curve for SS Cyg
# Nodes are "online" for 15 minutes after seeding — re-run to refresh

Then refresh your browser and watch the stats, map, and light curve populate.


Member App (app/) — Flutter Desktop Member Experience

The Flutter desktop app is the place members control a telescope — the only member application, on macOS, Windows, and Linux/Raspberry Pi OS. It provides telescope status, observation history, night summaries, an AI help assistant, science program suggestions, and push notifications. Designed with strong accessibility principles (large text, haptics, audio descriptions, screen reader support).

It runs on the same computer as the node agent and talks to it directly on http://127.0.0.1:5173 to install credentials and connect the telescope, which is why linking is a desktop operation. The node's own setup page at that address shows the pairing code and link state.

iOS is deactivated — see archive/ios/. There is no mobile member app.

Current Screens / Tabs

Tab/Screen Purpose
Dashboard Live node status, tonight's plan, network activity, Aladin sky view
Nodes List registered telescopes, claim nodes, name/rename
Observations Photometric history + night-by-night summaries; drill into targets
Notifications Alerts, night summaries, system messages (mark read)
Help Contact links + AI chat assistant (Claude Haiku via OpenRouter, weekly limit) + diagnostics + config patch requests
More Science program suggestions, settings, about

The app uses the member-authenticated cloud endpoints (/api/v1/me/*, /api/v1/plan, /api/v1/observations, /api/v1/help/*, etc.). It supports push via Firebase.

Quick Start — App (against local cloud)

# 1. Ensure cloud is running (see Quick Start — Cloud Server)
cd cloud/
PYTHONPATH="$PWD:$PWD/src" python3 -m cloud.main

# 2. In another terminal, run the Flutter app
cd app
# (first time: flutter create . ; flutter pub get)
flutter run --dart-define=BS_API_BASE=http://localhost:8800
# For a real device on same LAN: use your host IP instead of localhost

Desktop builds: flutter build macos, flutter build windows, or flutter build linux.

See app/README.md for full setup, accessibility notes, and API contract details.

Key Features (Phase 1)

  • Real-time data from live cloud (no mock data in current main flows)
  • AI-powered help that can suggest and queue safe configuration patches for your node agent
  • Per-member stats, observation lists, target light curves
  • Notifications for completed nights and important events
  • Science program suggestion flow (sends to cloud for review)

First AAVSO Submission — Step by Step

# 1. Set AAVSO credentials in cloud/config.yaml
#    aavso.observer_code: MXXX
#    aavso.username: ...
#    aavso.password: ...

# 2. Verify the Extended File Format looks correct (no live POST)
python3 scripts/manage.py check-aavso
# Prints a formatted test record with your observer code filled in

# 3. Check that the network has active nodes
python3 scripts/manage.py status

# 4. Trigger alert ingestion (or wait for the hourly background loop)
python3 scripts/manage.py ingest

# 5. Run a night's observations with a connected node

# 6. Preview the AAVSO batch before committing (dry run)
python3 scripts/manage.py batch
# Prints the exact Extended File Format that would be POSTed to WebObs

# 7. Submit live (calls AAVSO WebObs API)
python3 scripts/manage.py submit

# 8. Verify
python3 scripts/manage.py status

Success criterion (Phase 0): One observation accepted by AAVSO with magnitude agreeing within 0.15 mag of the known value for the target.


Weight Tuning Monitor — Claude-Powered Observability Optimization

The scoring engine is built on six observability sub-weights that balance how heavily each factor influences target assignment:

{
  "light_pollution":  0.20,   Weight of sky brightness on assignment
  "weather":          0.25,   Clearness likelihood (historical)
  "moon":             0.15,   Lunar illumination + position
  "airmass":          0.15,   Atmospheric extinction + target altitude
  "window":           0.15,   Hours the target is visible each night
  "telescope":        0.10,   Hardware capability match to target
}

By default these are static. When tuning.enabled: true in cloud/config.yaml, a nightly monitor wakes after maintenance and:

  1. Gathers evidence from the last N nights of observations

    • Measures ingested, quality flags, AAVSO acceptance rate
    • Correlates outcomes with the factors above
    • Identifies which weights may be misaligned
  2. Proposes adjustments (Claude API call)

    • Reads the evidence brief + current weights
    • Returns proposed values for each sub-weight
    • Reasoning is logged but not applied automatically
  3. Applies with safeguards (procedural)

    • Clamps each weight to ±5% change per night (trust-region limit)
    • Renormalizes to sum 1.0
    • Persists to the database with full audit trail
    • Notifies admins via the API if changes exceed 2% on any weight

The hot path (alert ingestion, scoring, planning) remains 100% procedural — no Claude calls, no latency added. Scoring reads live weights from tuning_state table on every run via scoring.score_all(), so a change takes effect on the next rescore with no restart.

Key points:

  • Only the advisory monitor calls Claude; schedulers and scorers use fixed formulas
  • Tuning is opt-in and ships disabled; behavior is identical to static weights when disabled
  • All weight changes are audited in weight_history table — rollback is a single API call
  • Configuration lives in both config.yaml (seed values) and the database (live values)

Tuning Configuration

tuning:
  enabled: false                    # set true to enable Claude-powered tuning
  model: claude-opus-4-8            # Claude model for proposals (impacts cost/latency)
  lookback_days: 14                 # analyze the last N nights for evidence
  max_delta_per_night: 0.05         # trust-region clamp (±5% per weight per night)
  min_nights_for_tuning: 7          # don't tune until ≥ 7 nights of data exist
  min_measurements: 30              # require ≥ 30 measurements to tune
  min_weight_change: 0.005          # skip notify if no weight moves > 0.5%

# Seed values — used as defaults if tuning is disabled or on fresh install
scoring:
  observability_weights:
    light_pollution: 0.20
    weather: 0.25
    moon: 0.15
    airmass: 0.15
    window: 0.15
    telescope: 0.10

Monitoring the Tuning Monitor

# Check current live weights
curl -H "X-Admin-Key: your-admin-key" \
     https://api.thetelescope.net/api/v1/admin/tuning

# Response:
# {
#   "active_weights": { … },
#   "last_tuning_run": "2026-06-15T02:30:00Z",
#   "weight_history": [ … ],
#   "recent_changes": [ … ]
# }

# Rollback to a prior weight set (manual safety valve)
curl -X POST -H "X-Admin-Key: your-admin-key" \
     -H "Content-Type: application/json" \
     -d '{"rollback_to_id": 5}' \
     https://api.thetelescope.net/api/v1/admin/tuning/rollback

Admin CLI (scripts/manage.py)

python3 scripts/manage.py [--config PATH] <command>

status          Node count, measurement queue, AAVSO batch history, config check
ingest          Trigger alert ingestion + scoring + plan regeneration
batch           Dry-run preview of the pending AAVSO batch (does not POST)
submit          Live AAVSO submission (ignores config dry_run setting)
check-aavso     Verify credentials config, print a formatted test observation
nights          Generate/backfill night summaries for all active nodes

Building Installers

pip install pyinstaller

# Auto-detect platform and build installer
python3 build/build.py

# Bundle only (PyInstaller one-file, no OS installer wrapper)
python3 build/build.py --bundle-only

# Outputs:
#   Windows  → dist/TelescopeNetNode-Setup.exe   (requires NSIS + NSSM on PATH)
#   macOS    → dist/TelescopeNetNode-X.Y.Z.pkg
#   Linux    → dist/TelescopeNetNode-linux-x86_64

Windows (NSIS + NSSM)

  • Writes config.yaml from the template; no code to enter — the member links the telescope from the desktop app after install
  • Registers the agent as a Windows service via NSSM; auto-starts at boot
  • Calls powercfg /change standby-timeout-ac 0 to disable AC idle sleep

macOS (.pkg)

  • Installs to /Applications/TelescopeNetNode/
  • Installs a launchd plist → /Library/LaunchDaemons/ (auto-start at boot)
  • Calls pmset -c sleep 0 to disable AC idle sleep
  • Data: /Library/Application Support/TelescopeNet/NodeAgent/

Linux (systemd)

# One-line install
curl -sSL https://telescopenet.org/install.sh | sudo bash --code BS-2026-XXXXXXXX

# Or manually
sudo bash build/linux/install.sh --code BS-2026-XXXXXXXX
  • Creates telescopenet service user; installs systemd unit; enables at boot
  • Masks sleep.target, suspend.target, hibernate.target, hybrid-sleep.target

Cloud API Reference

Node-authenticated (headers: X-Node-Id + X-Api-Key)

Method Path Description
POST /api/v1/nodes/register First-boot self-registration (no member link). Rejects any activation_code with 410
POST /api/v1/nodes/heartbeat 60-second keepalive with optional conditions JSON
GET /api/v1/nodes/me Own registry entry (all columns including performance metrics)
GET /api/v1/plan Tonight's observation plan
POST /api/v1/measurements Upload a photometry result
POST /api/v1/images Upload raw FITS (retained 14 days)
GET /api/v1/interrupts High-priority target alerts

Member-authenticated (header: Authorization: Bearer <token>)

Method Path Description
POST /api/v1/auth/register Create member account
POST /api/v1/auth/login Authenticate; returns bearer token
GET /api/v1/me Member profile
GET /api/v1/me/nodes My registered nodes
POST /api/v1/me/nodes/attach Link a telescope to this account; returns node_id + api_key
POST /api/v1/me/nodes/<id> Claim an existing node by api_key
POST /api/v1/nodes/pair Push credentials to a pairing token the local agent is polling
GET /api/v1/me/observations Observation history
GET /api/v1/me/stats Cumulative totals (observations, AAVSO accepted, targets covered)
GET /api/v1/me/nights Night-by-night summaries
GET /api/v1/me/notifications Notification inbox
POST /api/v1/me/notifications/<id>/read Mark a notification read
PUT /api/v1/me/notifications/prefs Update notification preferences

Public (no auth required)

Method Path Description
GET /api/v1/targets Active target queue with current composite scores
GET /api/v1/lightcurves/<name> Historical photometry for a named target
GET /api/v1/network/status Live node count, submission totals
GET /api/v1/health Uptime check

Admin (header: X-Admin-Key)

Method Path Description
POST /api/v1/admin/ingest Trigger alert ingestion + scoring
POST /api/v1/admin/replan Trigger rescoring + plan regeneration
POST /api/v1/interrupts Broadcast a high-priority target interrupt to nodes
GET /api/v1/admin/tuning Get current active weights, history, and last run timestamp
POST /api/v1/admin/tuning/rollback Rollback to a previous weight set by history ID

Database Schema Updates

New Tables (Phase 0.5+)

Table Purpose Columns
tuning_state Active observability sub-weights id, obs_weights (JSON), updated_at
weight_history Audit trail of all weight changes id, timestamp, old_weights, new_weights, reason, claude_reasoning

Schema Changes to Existing Tables

Table Change Notes
nodes Added filters column Previously missing, needed for multi-band target assignment

Migration: All schema updates are applied automatically on cloud server startup via db.py migration system.


Photometry Pipeline (src/photometry.py)

Runs automatically on each new FITS file when photometry.enabled: true.

FITS file
  → 1. Ensure WCS         (check header; run ASTAP plate solve if absent)
  → 2. Locate target      (world_to_pixel; reject if too close to edge)
  → 3. Estimate FWHM      (DAOStarFinder + second-moment Gaussian stamps)
  → 4. Comparison stars   (AAVSO VSP API → Gaia DR3 fallback; merge, deduplicate)
  → 5. Aperture photometry (CircularAperture + sigma-clipped annulus background)
  → 6. Differential photometry (weighted zero-point ensemble; Poisson + ZP scatter)
  → 7. Ancillary data     (BJD_TCB via astropy, airmass from Alt/Az or header)
  → 8. Quality flag       (good / acceptable / poor based on SNR, uncertainty, comp stars)
  → result dict

Output:

{
    "target_name":      "SS Cyg",
    "bjd":              2460500.123456,
    "magnitude":        12.341,
    "uncertainty":      0.031,
    "filter":           "CV",
    "airmass":          1.24,
    "fwhm":             3.8,
    "snr":              52.0,
    "comparison_stars": 9,
    "quality_flag":     "good",
    "node_id":          "node_001",
    "zero_point":       22.413,
    "zp_scatter":       0.028,
    "fits_file":        "seestar_image.fits",
}

Architecture Overview

src/dashboard.py (Flask, port 5173)
  │
  ├─ API endpoints (telescope, camera, schedule, photometry, config, logs, …)
  │
  ├─ SafetyManager (daemon thread)
  │   heartbeat monitor, reconnect, dawn parking, horizon mask, SIGTERM handler
  │
  ├─ ImageWatcher (daemon thread, when enabled)
  │   OS filesystem events → debounce → photometry pipeline
  │
  ├─ Photometry pipeline (photometry.py)
  │   WCS → comp stars → aperture phot → differential → AAVSO submission
  │
  ├─ CloudCommunicator (daemon threads, when cloud.enabled)
  │   auto-register → heartbeats → plan polling → measurement upload (retry queue)
  │
  └─ DeviceManager → AlpacaClient (HTTP/JSON)
       Telescope, Camera, Focuser, FilterWheel

cloud/ (Flask, port 8800)
  │
  ├─ Background loops
  │   alert-ingest → scoring → (optionally) replan  every 60 min
  │   replan                                         every 120 min
  │   aavso-batch                                    every 360 min
  │   weight-tuning (Claude advisory monitor)        daily
  │   maintenance (image pruning, night summaries,   daily
  │                performance refresh, LP refresh)
  │
  ├─ nodes table — the AI scheduler's view of each telescope
  │   location → observability windows, airmass, moon
  │   hardware → what the scope can see and how long it can expose
  │   autonomy → likelihood of completing a night unattended
  │   performance → what it has actually delivered to AAVSO
  │   reliability_score → multiplier on every (target, node) score pair
  │
  └─ tuning_state + weight_history tables (when tuning enabled)
      active observability sub-weights, audit trail of all changes

Configuration Reference (config.yaml)

Cloud Configuration (when enabled: true)

cloud:
  enabled: true
  url: https://api.thetelescope.net
  # node_id / api_key are written by the app when you link the telescope.
  # There is no activation code — do not add one.
  auto_run_plans: true     # automatically execute observation plans from the cloud

Tuning (Claude weight monitor — requires Anthropic API key in environment)

Note: See Weight Tuning Monitor section above for detailed documentation.

tuning:
  enabled: false                    # set true to enable Claude-powered observability weight tuning
  model: claude-opus-4-8            # Claude model for advisory proposals
  lookback_days: 14                 # analyze the last N nights of observations
  max_delta_per_night: 0.05         # clamp changes to ±5% per weight per night
  min_nights_for_tuning: 7          # require ≥ 7 nights of historical data
  min_measurements: 30              # require ≥ 30 measurements to enable tuning
  min_weight_change: 0.005          # skip notifications if max change < 0.5%

Environment variable: ANTHROPIC_API_KEY must be set for tuning to function.

Photometry

photometry:
  enabled: false           # set true to auto-run on each new FITS file
  node_id: "node_001"
  filter_name: "CV"
  gain: 1.0                # e-/ADU
  read_noise: 5.0
  target:
    name: ""               # leave blank to use FITS header values
    ra_deg: ~
    dec_deg: ~
  astap_path: "astap"
  astap_search_radius: 10
  aperture_factor: 2.5
  annulus_inner: 4.0
  annulus_outer: 6.0
  field_radius: 0.5
  mag_limit: 15.0
  min_comparison_stars: 3
  snr_threshold: 20
  max_uncertainty: 0.3
  max_airmass: 3.0
  fits_export:
    enabled: true
    export_dir: "fits_export"

AAVSO

aavso:
  observer_code: ""        # AAVSO OBSCODE  ← required
  username: ""             # AAVSO login    ← required to POST
  password: ""
  audit_dir: "aavso_submissions"
  dry_run: false
  submit_poor_quality: false
  chart_id: ""

Observatory

observatory:
  name: ""
  latitude: ~              # decimal degrees N
  longitude: ~             # decimal degrees E (negative = West)
  elevation: 0.0           # metres
  telescope: "ZWO Seestar S50"
  instrument: "ZWO Seestar S50 IMX462"
  observer: ""

Cloud connection

cloud:
  enabled: true
  url: https://api.thetelescope.net
  # node_id / api_key are written by the app when you link the telescope.
  # There is no activation code — do not add one.
  auto_run_plans: true

Safety

safety:
  enabled: true
  disconnect_timeout: 600
  heartbeat_interval: 30
  reconnect_attempts: 3
  reconnect_delay: 10
  park_at_dawn: true
  dawn_type: astronomical  # astronomical (-18°), nautical (-12°), civil (-6°)
  observer:
    latitude: 0.0
    longitude: 0.0

Image Watcher

image_watcher:
  enabled: false
  watch_path: ""   # blank = auto-mount the Seestar's SMB share under the node's data dir
  debounce_delay: 2.0

Troubleshooting

"No ALPACA servers found"

  • Verify the Seestar is in Station mode (joined to your home WiFi), not its own AP/hotspot. The native ALPACA server only comes up in Station mode; the Seestar app shows "connected" in either mode, so that alone doesn't tell you which one you're in.
  • Verify the Seestar's firmware is up to date — native ALPACA support requires a recent firmware version.
  • Verify the Seestar is powered on and on the same subnet as the node machine
  • Bump alpaca.discovery_timeout in config.yaml to 8–10s if discovery is intermittent — the native firmware responder can be slower to answer than third-party ALPACA servers
  • Some routers block UDP broadcast; check with tcpdump -i en0 udp port 32227
  • macOS may require allowing Python in System Preferences → Privacy & Security

"ErrorNumber 1: Device not connected"

The ALPACA server responded but the device isn't initialised inside the Seestar app yet. Open the Seestar app first.

Plate solve fails

  • ASTAP not in PATH — set photometry.astap_path to the full binary path
  • Star database not installed — download D05/D20/D50/D80 from the ASTAP site (see the "Plate Solving" section of Settings for which to pick)
  • Increase astap_search_radius if the image is very wide field

AAVSO submission rejected

Check aavso_submissions/YYYY-MM-DD/*_response.txt for the raw WebObs error message. Common causes: invalid observer code, target name not in AAVSO VSX, date out of range. Use dry_run: true to validate format before a live submission.

Photometry quality always "poor"

  • Low SNR: increase exposure time or check sky conditions
  • Too few comparison stars: Gaia DR3 fallback should help for targets outside AAVSO VSX coverage
  • Large ZP scatter: poor seeing or clouds; scatter contributes directly to uncertainty

"Host is down" despite Seestar app showing connected

The ALPACA HTTP server has wedged while the core firmware remains operational. The Seestar app talks to the firmware directly; ALPACA is a separate HTTP service that can fail independently. Recovery: Settings → Restart in the Seestar app (a full firmware reboot, not just app relaunch).

SafetyManager stuck in unsafe state after reconnect

The SafetyManager lost contact for longer than disconnect_timeout and triggered emergency park. The unsafe state does not self-clear. Recovery: restart dashboard.py. The manager initialises safe on startup and re-attaches cleanly.

Root causes: Seestar WiFi drop, Seestar app crash, host machine sleep. Prevention: increase disconnect_timeout, fix WiFi signal quality, ensure sleep_prevention is active.

"ErrorNumber 1024: Method Unpark is not implemented"

The Seestar S50 ALPACA driver does not implement Unpark. Unpark from within the Seestar app. The dashboard Unpark button has no effect on this device.

"CoverCalibrator connect failed: 400 Client Error"

Non-fatal — no cover calibrator device is configured at ALPACA index 0. Suppress with devices.covercalibrator.enabled: false in config.yaml.

Dawn parking not triggering

  • observer.latitude and observer.longitude must be non-zero
  • At mid-latitudes in summer the sun may not reach −18° — try dawn_type: nautical

Weight-tuning monitor not running

  • Check that ANTHROPIC_API_KEY is set in the environment: echo $ANTHROPIC_API_KEY
  • Verify tuning.enabled: true in cloud/config.yaml
  • Tuning is a daily task — runs during the maintenance window, typically 2–3 AM UTC
  • Check cloud server logs: cloud.tuning logger will show evidence gathered and Claude responses
  • Insufficient data: tuning requires at least min_measurements (default 30) and min_nights_for_tuning (default 7) of observations

Weight changes seem to have no effect on plan

  • Plans are built during the replan loop (every 120 min); a weight change takes effect on the next replan, not retroactively
  • To see weights take effect immediately, trigger a manual rescore: curl -X POST -H "X-Admin-Key: ..." http://localhost:8800/api/v1/admin/replan
  • Check the GET /api/v1/admin/tuning response to confirm weights were actually updated

Claude API errors during tuning

  • 429 (rate limited): reduce frequency by increasing tuning.lookback_days or temporarily disabling
  • 401 (auth failed): verify ANTHROPIC_API_KEY is correct and has quota remaining
  • Content policy violations: evidence brief triggers a refusal (rare); tuning skips apply and notifies in logs

Technology Stack

Layer Technology Notes
Node Agent Python 3.10+ / Flask Runs Windows, macOS, Linux; pip only
Telescope control ALPACA REST — Seestar's native server (Station mode) or seestar_alp Vendor-neutral; node agent is a pure ALPACA client either way
Plate solving ASTAP + D05/D20/D50/D80 catalogue Fast, offline, accurate on Seestar images
Image stacking NumPy + RANSAC Sub-pixel alignment, no external binaries
Photometry astropy + custom aperture code Differential against AAVSO/Gaia comp stars
Cloud server Python / Flask Upgrade to FastAPI + async at Phase 2 scale
Scoring weights Claude API (opt-in) Advisory monitor for observability tuning; hot path stays procedural
Database SQLite (WAL mode) Zero-config; migrate to Postgres at ~50 nodes
Member auth PBKDF2-SHA256 + bearer tokens 260K rounds, per-user salt, hashed token storage
Sleep prevention OS-native APIs SetThreadExecutionState (Win), caffeinate (Mac), systemd-inhibit (Linux)
Packaging PyInstaller one-file NSIS (Win), pkgbuild/productbuild (Mac), systemd install.sh (Linux)
Marketing site HTML/CSS/JavaScript (static) tour.html — interactive scrollytelling, Aladin Lite sky vis, live API data
Member app Flutter / Dart (macOS, Windows, Linux, Raspberry Pi OS) Real backend integration, AI Help assistant, TTS, and strong accessibility focus
AI member support OpenRouter (Claude Haiku) Rate-limited chat in-app; queues safe config patches for node agents

Further Reading


The Telescope Net — The night sky belongs to everyone. Founded 2025.

About

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages

0