An overview of observation-log ingestion, analysis pipelines, scheduling, and the closed-loop results workflow.
graph TD
%% Data Ingestion Stage
A["Raw FITS Files\n(MUSCAT_DATA_DIR)"]:::default -->|1. scan| B["Observation CSV Logs\n($MUSCAT_OBSLOG_DIR)"]:::default
B -->|2. build-db| C["SQLite Database\n(muscat.db)"]:::db
%% Web UI
C -->|Reads DB| D["Web GUI Dashboard"]:::default
D -->|Explore Targets, Logs, Exposures| D
%% Multi-user auth
AUTH["nginx Reverse Proxy\n(HTTP Basic Auth)"]:::pipeline -->|X-Forwarded-User| D
D -->|Per-user config| SET["Settings Page\n(/settings, encrypted LCO token)"]:::default
%% Target page integrations
D -->|Search papers| ADSN["NASA ADS Publications\n(target page)"]:::pipeline
D -->|Browse catalogs| CAT["TOI / NExScI Catalog Pages\n(/toi, /nexsci)"]:::pipeline
CAT -->|Link to target| D
%% Photometry Stage
D -->|Configure & Launch| E["Photometry Job Queue"]:::pipeline
E -->|Runs calibrated prose| F["Photometry Reduction"]:::default
F -->|CSVs & diagnostics| G["Photometry Results\n($MUSCAT_PROSE_DIR/inst/date/_runs/target/run/)"]:::default
%% Transit Fit Stage
G -->|Inputs lightcurves| H["Transit Fit Job Queue"]:::pipeline
D -->|Configure & Launch| H
H -->|Runs timer MCMC sampler| I["Transit Fitting"]:::default
I -->|Posteriors & diagnostics| J["Transit Results\n($MUSCAT_TIMER_DIR/inst/date/target/run/)"]:::default
%% Ephemeris Analysis
J -->|Transit centers Tc| L["Ephemeris O-C Analysis"]:::pipeline
L -->|Interactive fit| M["O-C Plot & Ephemeris\n(saved views in DB)"]:::default
%% TTV Fit Stage
L -->|Transit times| R1["TTV Fit Job Queue"]:::pipeline
R1 -->|Runs harmonic emcee sampler| R2["Multi-Harmonic TTV Fit"]:::default
R2 -->|Posteriors & plots| R3["TTV Results\n($MUSCAT_TTV_DIR/target/_runs/run/)"]:::default
%% Exposure Calculator & Targets API
D -->|Exposure times| N["Exposure Calculator"]:::pipeline
N -->|Calibration coefficients| C
D -->|Query past obs| N1["Exposure Target API\n(/api/exposure/target/{target})"]:::pipeline
N1 -->|Retrieves summary| D
%% FOV Optimizer
D -->|Optimize FOV| N2["FOV Optimizer\n(/fov)"]:::pipeline
N2 -->|Visualizes field & comps| D
%% LCO Integration
D -->|LCO Token & Config| O["LCO Observation Portal"]:::default
SET -->|Per-user token overrides global| O
O -->|Schedule observations| P["Submit Requestgroups"]:::pipeline
O -->|Download archive data| Q["Reduce & Ingest FITS"]:::pipeline
O -->|Optimize pointing| N2
O -->|Estimate exposures| N
%% Jobs monitoring & Results loop
E -->|Track execution| K["Jobs Page\n(per-user attribution)"]:::default
H -->|Track execution| K
R1 -->|Track execution| K
G -->|Display preview & plots| D
J -->|Display parameters & plots| D
M -->|Inspect TTVs, update| D
R3 -->|Display posteriors & plots| D
P -->|Track submission| K
Q -->|Add to muscat-db| B
Detailed step-by-step reduction flow of the calibrated science frames within the prose photometry engine.
Repository: github.com/jpdeleon/prose2
graph TD
%% Input
A["Science FITS Files\n(raw MuSCAT/MuSCAT2; BANZAI e91 for MuSCAT3/4/Sinistro)"]:::default --> B["Calibration when required\nthen band grouping from FILTER headers"]:::default
%% Setup & Reference Frame
B --> C["Reference Construction\n(position: first/middle frame; quality: clean local anchor\n+ registered top-K temporal persistence)"]:::default
C --> D["Point Source Detection\n(Persistent stars on the quality anchor; single-frame detections in position mode)"]:::default
D --> E["Celestial Target Identification\n(WCS matching / MAST coordinate resolution)"]:::default
E --> F["Gaia Contamination Sizing\n(Sizes sky annulus RIN & ROUT to exclude contaminants)"]:::default
F --> G["Aperture Sizing Grid\n(Multiple radii defined from FWHM up to RIN)"]:::default
%% Frame-by-Frame Parallel Photometry
B --> H["Parallel Frame Reduction\n(prose.SequenceParallel)"]:::pipeline
G --> H
H --> H1["CCD Trim\n(Trim borders/edge noise)"]:::default
H1 --> H2["Gaussian Modeling\n(Gaussian2D fit for FWHM)"]:::default
H2 --> H3["Twirl Alignment\n(ComputeTransformTwirl & AlignReferenceSources)"]:::default
H3 --> H4["Centroid Tuning\n(CentroidQuadratic optimization)"]:::default
H4 --> H5["Flux Extraction\n(AperturePhotometry & AnnulusBackground)"]:::default
%% Post-processing
H5 --> I["Differential Photometry\n(autodiff - Broeg et al. 2005)"]:::default
I --> J["BJD Time Correction\n(Converts JD-UTC to BJD-TDB via barycorrpy)"]:::default
J --> K["Export Results\n(CSV files, apertures.png, alignment.png, gif)"]:::default
Transit lightcurve modeling, parameter estimation, and posterior sampling steps executed in the timer MCMC engine.
Repository: github.com/john-livingston/timer
graph TD
%% Configuration & Input
A["Photometry CSV Lightcurves"]:::default --> B["YAML Configuration files\n(fit.yaml & sys.yaml)"]:::default
B --> C["Input Validation\n(Parameter conflicts & duplicate configurations)"]:::default
C --> D["Data Loading\n(Read lightcurves & detrending covariates)"]:::default
%% Model Building
D --> E["Prior Initialization\n(Stellar params, planet priors, limb darkening)"]:::default
E --> F["Transit Model Assembly\n(PyMC model definition)"]:::pipeline
%% Optimization & Sampling
F --> G["Run Mode Check\n(New Fit vs Continue Sampling)"]:::default
G -->|New Fit| H["Mean Fitting / Pre-optimization\n(L-BFGS MAP optimization)"]:::default
G -->|Continue Sampling| I["Load Previous Trace\n(Resume sampling from saved trace)"]:::default
H --> J["MCMC Sampling\n(PyMC NUTS sampler)"]:::pipeline
I --> J
%% Post-processing
J --> K["Outlier Clipping & Detrending\n(Sigma-clip residual outliers)"]:::default
K --> L["Compute Information Criteria\n(Calculate BIC, AIC)"]:::default
L --> M["Save Outputs\n(Posterior stats, fit.yaml configs)"]:::default
L --> N["Generate Visualizations\n(Corner plot, trace plot, LD profile, fit plots)"]:::default
Interactive observed-minus-calculated analysis built from completed transit-fit runs and catalog reference ephemerides.
graph TD
%% Target discovery
A["Completed Transit Fit Jobs\n(DB jobs table + orphan fit discovery)"]:::db --> B["Target List\n/api/ephemeris/targets"]:::default
B --> C["Select One or More Targets\n(ephemeris.html UI)"]:::default
%% Dataset assembly
C --> D["Load Target Info\n/api/ephemeris/target-info"]:::pipeline
D --> E["Completed Production-Fit Datasets\ninstrument, date, run_id, transit coverage"]:::default
D --> F["Fitted Transit Centers\nTc ± σ per planet per dataset"]:::default
D --> G["Reference Ephemerides\nNASA Exoplanet Archive, TOI, fallback"]:::default
D --> H["Planet Cards\nT0, period, source override"]:::default
%% Interactive controls
E --> I["Dataset Selection\n(Checkboxes per run)"]:::default
F --> J["Automatic Recalculate\n/api/ephemeris/calculate"]:::pipeline
G --> J
H --> J
I --> J
X["External Transit CSV\nplanet, epoch, tc, tc_unc"] --> Y["Import Preview\nheader mapping, validation, time-scale confirmation"]:::default
Y --> J
K["Fitting Method\nUnweighted or Weighted"]:::default --> J
%% Fit math
J --> L["Epoch Assignment\nround((Tc - T0) / P)"]:::default
L --> M["Linear Ephemeris Fit\nTcalc = T0fit + E * Pfit"]:::default
M --> N["O-C Residuals\n(Tc - Tcalc) in minutes"]:::default
%% Output & persistence
N --> O["Observed minus Calculated Plot\nBJD or epoch x-axis"]:::default
M --> P["Best-Fit Ephemeris Summary\nT0, period, uncertainties"]:::default
O --> Q["Interactive Controls\nDate axis toggle, point exclusion, styles, PNG export"]:::default
%% Saved views
C --> R["Save View State\n/api/ephemeris/view POST"]:::pipeline
R --> S["Ephemeris Views Table\n(ephemeris_views DB)"]:::db
S --> T["Shareable View Links\n/api/ephemeris/view/{slug}"]:::default
T --> U["Resume Analysis\nRestore all settings & selections"]:::default
%% Handoff to TTV fitting
L --> V["TTV Fit Inputs\ndata.csv: planet, epoch, tc, tc_unc"]:::default
V --> W["harmonic TTV Fit\n(see next section)"]:::pipeline
Use Import transit CSV beside the dataset table to add external timing measurements.
Enter the instrument name once in the import dialog; it is applied to every accepted CSV row.
Imported rows are stored in the shareable saved-view URL, including CSV-only analyses with no completed
transit-fit dataset, so reopening the URL in a fresh browser restores the timing table.
Headers such as tc(BJD) and tc_unc(BJD) are mapped to transit center and
uncertainty automatically; numeric planet indices use 0=b, 1=c, and so on.
The preview retains invalid rows with a specific note, rejects uncertainties below one minute, lists extra
columns that will be dropped, and compares a supplied CSV epoch with the new page epoch. A constant
per-planet offset is a safe change of epoch zero-point; inconsistent offsets are blocked. Files that say
only BJD require confirmation that their time scale is BJD_TDB.
Enabling Show date (UTC) expands the figure to accommodate the second x-axis while
preserving the original O-C plotting-area aspect ratio.
Multi-harmonic transit timing variation modeling run from the Ephemeris page, sampled with emcee in the harmonic engine.
Repository: github.com/john-livingston/harmonic
graph TD
%% Inputs assembled on the ephemeris page
A["Transit Centers from O-C Plot\nTc, tc_unc, epoch per planet"]:::default --> B["Dataset Selection & Point Exclusions\n(shared with the linear fit)"]:::default
B --> C["Planet Letters to Indices\nb,c,d to 0,1,2"]:::default
%% Configuration
C --> D["data.csv\nplanet, epoch, tc, tc_unc"]:::default
E["Model Options\nstellar mass, non-transiting outer, phase offsets"]:::default --> F["config.ini"]:::default
G["INIT section, per adjacent pair\na_bc, a_cb, per_bc, t_bc"]:::default --> F
H["Optional sections\nT14 transit durations\nOUTER non-transiting planet"]:::default --> F
%% Validation & launch
D --> I["Validate Options & INI\n/api/ttv-fit/start"]:::pipeline
F --> I
I --> J["Write Run Directory\n$MUSCAT_TTV_DIR/target/_runs/run_name"]:::default
J --> K["Launch harmonic CLI\n(conda harmonic env, background job)"]:::pipeline
%% Sampling
K --> L["Multi-Harmonic TTV Model\nLithwick, Xie and Wu 2012 near-resonant formalism"]:::default
L --> M["MCMC Sampling\n(emcee: walkers, steps, burn-in, thinning)"]:::pipeline
M --> N["Live Log Streaming\nharmonic.log to run panel"]:::default
%% Outputs
M --> O["Posterior Samples\nsamples.csv.gz"]:::default
M --> P["Diagnostic Plots\ncorner.png, trace.png, fit.png, init.png"]:::default
J --> Q["Run Provenance\nmeta.yaml: muscat-db & harmonic versions, options"]:::default
%% Results loop
O --> R["TTV Fit Results Panel\nrun chips switch between stored runs"]:::default
P --> R
Q --> R
R --> S["Download All\nzipped run directory"]:::default
N --> T["Jobs Page\n(per-user attribution, cancel)"]:::db
Maximize comparison star coverage by optimizing telescope pointing offsets and position angles (PA).
graph TD
A["Target Input\n(Resolved Name or RA/Dec)"]:::default --> B["Instrument Selector\n(Footprint size & observatory latitude)"]:::default
B --> C["Load Footprint Geometry\n(XML votable or computed fallback)"]:::default
A --> D["Resolve Coordinates\n(Query MAST/SIMBAD)"]:::pipeline
D --> E["Gaia DR3 Catalog Query\n(Vizier cone-search around target)"]:::pipeline
E --> F["Star Scoring Heuristics\n(Mag offset, color similarity, blending)"]:::default
C --> G["Grid-Search Optimization\n(Vary center offset & position angle)"]:::pipeline
F --> G
G --> H["Select Optimal Pointing\n(Target within margin, maximize weights)"]:::default
H --> I["Display Results\n(Offsets, PA, comps table, Aladin Lite overlay)"]:::default
The FOV Optimizer finds the optimal pointing center offset (east, north) and position angle (PA) that keep the science target in the field of view (with a safe edge margin) while maximizing the number of high-quality comparison stars.
End-to-end LCO Observation Portal submission: from ephemeris to dry-run IPP check to live observation request.
graph TD
%% Configuration & Input
A["Authenticated User\n(per-user token or LCO_API_TOKEN fallback)"]:::default --> B["Load Proposals\n/api/lco/proposals"]:::pipeline
B --> C["Select Project/Proposal"]:::default
C --> D["Choose Saved Ephemeris View\n(from /lco?view=slug or direct link)"]:::default
D --> E["Extract Target & Ephemeris\nT0, period, target name"]:::default
E --> E1["Show FOV Button\n(Optimize pointing offset/PA)"]:::default
E --> E2["Show Exp Button\n(Check target exposure history)"]:::default
%% Window Generation
E --> F["Configure Search Window\nUTC start date, end date, duration"]:::default
E1 --> F
E2 --> F
F --> G["Configure Pre/Post Padding\n(minutes before/after transit)"]:::default
G --> H["Generate Transit Windows\n/api/lco/windows"]:::pipeline
H --> I["Display Candidate Windows\nCandidate start/end times"]:::default
%% Imaging Configuration
I --> J["Select Imaging Mode"]:::default
J -->|MUSCAT| K["Configure MUSCAT Imaging\nFilters: g/r/i/z exposure times\nExposure mode: sync/async\nNarrowband in/out positions"]:::default
J -->|Sinistro| L["Configure Sinistro Imaging\nFilter, exposure time, count"]:::default
%% Payload & Validation
K --> M["Build Request Payload\nConstraints, configurations, windows"]:::default
L --> M
M --> N["Dry-Run IPP Check\n/api/lco/ipp"]:::pipeline
%% Feedback & Submission
N -->|IPP Valid| O["Show Payload & IPP Cost\nUser reviews request details"]:::default
N -->|IPP Invalid| P["Display Error Message\nUser modifies and retries"]:::default
P --> M
O --> Q["Submit Button Enabled?"]:::default
Q -->|No changes since dry-run| R["User Confirms Submission\n(MUSCAT_LCO_ALLOW_SUBMIT=1)"]:::default
Q -->|Changes detected| P
R -->|Submit| S["Post to Observation Portal\n/api/lco/submit"]:::pipeline
%% Tracking
S -->|Success| T["Persist request group + child Request IDs\nObservation scheduled"]:::default
S -->|Error| U["Show Error Details\nAsk user to retry"]:::default
U --> M
T --> V["Wait until observing window\nPersistent DB monitor + worker lease"]:::pipeline
V --> W["Poll request-scoped raw + level-91 metadata\n5 min initially, bounded backoff"]:::pipeline
W -->|New level-91 frames| X["Download + funpack immediately\nAtomic files, bounded parallelism"]:::pipeline
X --> W
W -->|Terminal and every raw has level 91| Y["Scan affected instrument/date\nIngest obslogs into SQLite"]:::pipeline
Y --> Z["Complete\nVisible on scheduler monitoring table"]:::default
Generated transit windows are invalidated whenever a target, ephemeris, date range, padding, site, or observability constraint changes. The old table and visibility plot are hidden until Generate windows is clicked again, so stale predictions cannot be submitted.
Search LCO data archive and download reduced frames to the local server.
graph TD
%% User input and filtering
A["Archive Search Interface"]:::default --> B["Set Search Filters"]:::default
B --> B1["Project/Proposal ID\n(optional constraint)"]:::default
B --> B2["Target Object Name\n(optional substring)"]:::default
B --> B3["Instrument\n(1M0-SCICAM-SINISTRO or 2M0-SCICAM-MUSCAT)"]:::default
B --> B4["Site / Telescope\n(e.g. ogg, coj, cpt, lsc, elp)"]:::default
B --> B5["Reduction Level\n(raw, processed, fits)"]:::default
B --> B6["Date Range\n(start and end UTC dates)"]:::default
B --> B7["Result Limit\n(max frames to return)"]:::default
%% API call
B1 --> C["Query Archive\n/api/lco/archive/frames"]:::pipeline
B2 --> C
B3 --> C
B4 --> C
B5 --> C
B6 --> C
B7 --> C
%% Results display
C -->|Success| D["Display Frames Table\nColumns: filename, site, instrument, date, filter"]:::default
C -->|No results| E["Show 'No frames found'\nSuggest different filters"]:::default
%% Selection and preparation
D --> F["User Selects Frames\n(Checkboxes for multiple selection)"]:::default
F --> G["Review Selection\nFilename, size, reduction level"]:::default
G --> H["Confirm Download"]:::default
%% Server-side download
H --> I["Send Selected Frame URLs\n/api/lco/archive/download"]:::pipeline
I --> J["Server Validates Paths\n(No overwrite by default)"]:::default
J -->|Overwrite Disabled| K["Check if files exist locally"]:::default
K -->|Files exist| L["Skip existing files\nShow 'already exists' status"]:::default
K -->|New files| M["Download from archive-api.lco.global"]:::default
J -->|Overwrite Enabled| M
%% Path resolution
M --> N["Determine Save Location"]:::default
N --> N1["MUSCAT_LCO_DIR set?\nYes: MUSCAT_LCO_DIR/inst/date/"]:::default
N --> N2["No: MUSCAT_DATA_DIR/inst/date/"]:::default
%% Results
N1 --> O["Save Downloaded Files"]:::default
N2 --> O
O -->|Success| P["Display Results\nFilename, size, path"]:::default
O -->|Error| Q["Show Error Details\nPermission or disk issues"]:::default
%% Final status
P --> R["Download Complete\nFiles ready in muscat-db data tree"]:::default
Calculate recommended exposure times or expected signal-to-noise ratios (S/N) and manage instrument sensitivity coefficients.
graph TD
A["Target Parameters\n(Vmag, Filter/Band, Focus mm)"]:::default --> B["Calculation Mode"]:::default
B -->|Time-to-S/N| C["Estimate Exposure Time\n(Computes time needed to hit target S/N)"]:::pipeline
B -->|S/N-at-Time| D["Estimate Expected S/N\n(Computes S/N for a given exposure time)"]:::pipeline
C --> E["Full-Well & Saturation Check\n(Alerts user if exposure time risks saturation)"]:::default
D --> E
F["Calibration Data\n(Standard stars & target observations)"]:::default --> G["Fit Coefficients Job\n(/exposure/calibrate)"]:::pipeline
G --> H["exposure_coeffs Table\n(Update instrument band coefficients in DB)"]:::db
H -->|Provides sensitivity parameters| C
H -->|Provides sensitivity parameters| D
The Exposure Calculator helps observers optimize their sequences to maximize the S/N ratio of their photometry while keeping the peak pixel values safely below full-well saturation.
Explore TESS Objects of Interest and the NASA Exoplanet Archive database with interactive charts and Boyle+2026 rotation catalogs.
graph TD
A["Catalog Files\n(TOIs.csv & nexsci_pscomppars.csv)"]:::default --> B["Interactive Plotly Plots\n(Configurable X/Y axes & color dimensions)"]:::pipeline
C["Boyle+2026 Catalog\n(TIC ID matching)"]:::default -->|Left-join rotation stats| D["TOI Table Browse\n(Includes period Prot, RUWE, binarity)"]:::default
E["SQLite muscat.db\n(targets table)"]:::db -->|Query presence| F["Highlight Targets\n(Targets in DB are drawn as stars ★)"]:::pipeline
F --> B
D --> G["Filter Chips & Search\n(Save filter state to URL hash fragment)"]:::default
B --> H["CSV Export & Navigation\n(Links to LCO Scheduler or Exoplanet Archive)"]:::default
The catalog browsers allow users to filter targets based on planet size, orbital period, transit depth, host star characteristics, and rotation periods.
muscat-db are highlighted differently so users can quickly see which exoplanets have local observation logs.HTTP Basic Auth via nginx, per-user job attribution, and encrypted per-user LCO tokens for shared-server deployments.
graph TD
%% Login
A["Browser Request"]:::default --> B["nginx :8000\n(HTTP Basic Auth, deploy/nginx.conf)"]:::pipeline
B -->|"Checks credential"| C["htpasswd file\n(0640 root:www-data)"]:::db
C -->|Valid| D["nginx sets X-Forwarded-User"]:::pipeline
C -->|Invalid| E["401 Challenge"]:::default
%% Proxy trust boundary
D --> F["uvicorn 127.0.0.1:8001\n(muscat-db serve --nginx)"]:::default
F --> G["Auth Middleware"]:::pipeline
G -->|"Loopback peer + proxy secret valid?"| H{Trust header?}:::default
H -->|Yes| I["request.state.user = X-Forwarded-User"]:::default
H -->|No| J["Reject protected request"]:::default
%% Per-user features
I --> K["Photometry / Transit-Fit Launch"]:::default
K -->|Tag job with user_name| L["Jobs Page\nUser column"]:::db
I --> M["Settings Page (/settings)"]:::default
M -->|"POST /api/settings/lco-token\n(Origin/Referer same-host check)"| N["Fernet-encrypt token\n(key derived from MUSCAT_DB_SECRET)"]:::pipeline
N --> O["users table\n(settings JSON column)"]:::db
O -->|Overrides global LCO_API_TOKEN| P["LCO API calls (lco.py)"]:::default
X-Forwarded-User together with a private proxy secret. In --nginx mode, FastAPI fails closed unless the immediate peer is loopback and both values are valid, preventing another local account from bypassing nginx through port 8001. Ordinary serve/restart defaults to loopback and requires an explicit --host to expose a development deployment.muscat-db htpasswd add/delete/list is the only supported way to edit the auth file. Passwords are piped over stdin to openssl passwd -apr1 (never as a CLI argument, which would leak via ps//proc), and the file is written 0640 root:<nginx group> so other local accounts on the shared server cannot read and offline-crack the hashes.phot.start_run(), fit.start_fit(), and the job store, so the Jobs page's User column shows who launched each photometry or transit-fit run./settings. Tokens are encrypted at rest with Fernet symmetric encryption keyed from MUSCAT_DB_SECRET, stored in a users table, and override the server-wide LCO_API_TOKEN fallback when present. All unsafe HTTP methods centrally check Origin/Referer against the request host as a CSRF defense, since FastAPI can parse JSON from a browser simple request without a CORS preflight.A read-only Gemma assistant embedded in team chat that answers questions about this codebase, grounded in the project docs and the relevant pipeline repositories.
graph TD
%% Trigger & routing
A["Team Chat Message\n(@bot your question)"]:::default --> B["Mention Parser\n(addressed / extract_question)"]:::pipeline
B -->|"@bot /reset or /clear"| RS["Post Reset Divider\n(system message = hard context cutoff)"]:::db
B -->|question| C["Concurrency Guard\n(MUSCAT_OLLAMA_MAX_CONCURRENT, default 2)"]:::default
C -->|at capacity| BUSY["Ephemeral busy note\n(ask again shortly)"]:::default
%% Context assembly (read-only grounding)
C -->|slot reserved| D["Assemble Context"]:::pipeline
D --> E["System Prompt\n(CLAUDE.md + module map, read-only rules)"]:::default
D --> F["Topic-Gated Grounding\nphotometry to prose2, transit to timer, TTV to harmonic\n(README + CLAUDE.md; EN + Japanese keywords)"]:::default
D --> G["Shared Room History\n(last 8 turns; TTL auto-clear + reset cutoff)"]:::db
%% Model call over the tunnel
E --> H["POST /api/chat\n(non-streaming, no tools, num_ctx = MUSCAT_OLLAMA_NUM_CTX)"]:::pipeline
F --> H
G --> H
H --> I["SSH Tunnel\nlocalhost:11434 to muscat-ut4 (scripts/ollama_tunnel.sh)"]:::pipeline
I --> J["ollama / gemma4\n(replies in the asker's language)"]:::default
J --> K["Post Reply to Chat\n(persisted agent message)"]:::default
Address the assistant in team chat with @bot <question> (the name is configurable via MUSCAT_CHAT_AGENT_NAME). It is a grounding-only advisor — it is never given tools or file access, and its reply is inert chat text that is never executed.
tools/functions field, so the model cannot act on the system. It can explain what a human would change and where, but cannot edit files, run jobs, or touch the database.CLAUDE.md + module map), a photometry question folds in prose2's docs, a transit-fit question timer's, and a TTV question harmonic's (each repo's README + CLAUDE.md). Routing keywords cover both English and Japanese phrasing. Repo paths resolve from MUSCAT_PROSE_PROJECT / MUSCAT_TIMER_PROJECT / MUSCAT_HARMONIC_PROJECT.MUSCAT_OLLAMA_MAX_CONCURRENT (default 2) questions run at once on the single-worker server; a further concurrent request receives an ephemeral “busy” note rather than queueing.MUSCAT_AGENT_HISTORY_TTL_S (default 900 s); posting @bot /reset (or /clear) drops it immediately via a persisted system divider.MUSCAT_OLLAMA_NUM_CTX (default 8192).muscat-ut4 binds 127.0.0.1 (no auth), so scripts/ollama_tunnel.sh forwards it over SSH to a local port; set MUSCAT_OLLAMA_URL to the local tunnel end (http://127.0.0.1:11434). The model tag is MUSCAT_OLLAMA_MODEL (default gemma4:latest).
Entity-relationship diagram and core column definitions for muscat.db.
erDiagram
frames {
int id PK
text instrument
text obsdate
int ccd
text filename
text object
real jd_start
text ut_start
real exptime
text read_mode
text filter
text ra
text declination
real airmass
real focus
real pa
}
summaries {
int id PK
text instrument
text obsdate
int ccd
text object
real exptime
text read_mode
text telescope
text frame_start
text frame_end
text ut_start
text ut_end
int nframes
text filter
text ra
text declination
real airmass_min
real airmass_max
}
targets {
text object PK
int n_dates
int n_frames
text instruments
text dates
text inst_dates
text filters
real total_exptime
text ra
text declination
real airmass_min
real airmass_max
int is_identified
text phot_status
text fit_status
}
target_notes {
text object PK
text note
text updated_at
}
target_overrides {
text object PK
int is_identified
text updated_at
}
jobs {
text key PK
text type
text instrument
text obsdate
text target
text state
int returncode
int elapsed
real started_at
text error_desc
text run_type
text params
text run_id
text run_name
text user_name
}
users {
text username PK
text password_hash
text display_name
int is_admin
text created_at
text last_login
text settings
}
db_meta {
text key PK
text value
}
exposure_coeffs {
text instrument PK
text band PK
real focus_mm PK
real coef
real fwhm_pix
int n_frames
text updated_at
}
exposure_jobs {
text id PK
text instrument
text state
text progress
real started_at
real updated_at
}
ephemeris_views {
text slug PK
text state_hash
text state_json
text targets_json
text created_at
text updated_at
}
test_observations {
text id PK
text target
text instrument
text site
text state
text plan_json
text result_json
text request_ids_json
}
lco_observation_requests {
int request_id PK
int requestgroup_id
text target
text instrument
text user_name
text request_state
text monitor_state
text result_json
}
lco_observation_frames {
int request_id PK
text frame_id PK
text observation_id
text filename
text instrument
text obsdate
text state
text error
text metadata_json
real updated_at
}
lco_monitor_leases {
text name PK
text owner
real expires_at
}
exofop_cache {
text tic_id PK
int has_confirmed_planets
text confirmed_planets
text updated_at
}
job_concurrency_slots {
text pipeline PK
text holder_key PK
real claimed_at
}
frames ||--o{ summaries : "grouped into"
targets ||--o{ frames : "contains"
targets ||--o{ summaries : "contains"
targets ||--|| target_notes : "annotates"
targets ||--|| target_overrides : "overrides"
users ||--o{ jobs : "launches"
users ||--o{ lco_observation_requests : "submits"
lco_observation_requests ||--o{ lco_observation_frames : "produces"
| Table | Description | Rows | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| frames | Per-FITS-row metadata: instrument, date, CCD, target name, JD, filter, coordinates, airmass | One per science frame | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| summaries | Per-observation rollup: date range, frame count, airmass range, filter | One per (inst, date, CCD, object) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targets | Aggregate target stats: observation count, instrument list, date coverage, total exposure time | One per unique object name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_notes | User-defined notes attached to a target | Zero or one per target | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target_overrides | Manual override for the is_identified flag on a target |
Zero or one per target | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs | Pipeline job tracking: photometry, transit-fit, and TTV-fit state, run identity, user attribution, elapsed time, and serialized parameters | One per pipeline launch | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| users | Authenticated-user metadata and encrypted per-user settings such as LCO and ADS tokens | One per configured user | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db_meta | Key-value metadata store (build version, last rebuild timestamp, etc.) | Few rows | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exposure_coeffs | Exposure-time calibration coefficients per (instrument, band, focus) | One per calibration point | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exposure_jobs | Exposure calculator job tracking: state, progress, timestamps | One per exposure calc run | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| …7 more rows not shown in static snapshot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The build-db command writes a new database to a temporary file first, then atomically replaces muscat.db. Before replacement it preserves app-owned tables: jobs, users, ephemeris_views, target_notes, target_overrides, exposure_coeffs, test_observations, lco_observation_requests, and lco_observation_frames. The observation-derived frames, summaries, and targets tables are rebuilt from the CSV obslogs. Transient exposure-job rows, monitor leases, and concurrency-slot claims are intentionally recreated instead of copied into the new database.
Sequential stages of the MuSCAT-db pipeline from data ingestion through analysis.
prose pipeline to align frames, build PSF models, and extract raw lightcurves.
PyMC MCMC sampler to model the transit curve, compute planetary/stellar parameters, and output high-quality fit plots.
Detailed explanations of pipeline internals, configuration options, and common workflows.
The database is a SQLite file (muscat.db) that stores observation metadata extracted from FITS headers. The pipeline has two stages:
muscat-db scan <inst> <yymmdd> reads every FITS file in the raw data directory, extracts HEADER keywords (OBJECT, FILTER, RA, DEC, EXPTIME, etc.), and writes a CSV log per CCD under $MUSCAT_OBSLOG_DIR (default $HOME/muscat/obslog). Calibration frames (bias, dark, flat) are excluded; only science frames are recorded.muscat-db build-db ingests all scanned CSV logs into SQLite with indexes on instrument, date, object, and filter. The resulting DB powers the web GUI's browse, search, and target discovery features.The DB also persists background job state (photometry and transit fit runs), submitted LCO request IDs and monitoring progress, and guarded test-observation plans. The LCO monitor resumes after a server restart and uses a database lease so multiple web workers do not duplicate API polling, downloads, or ingestion.
The database is rebuilt automatically once per day at 5:00 PM local time via a cronjob:
30 17 * * * cd /path/to/muscat-db && uv run muscat-db build-db >> logs/build-db.log 2>&1
This re-scans all CSV observation logs under $MUSCAT_OBSLOG_DIR and rebuilds the summaries and targets tables to reflect any new or corrected scans. Log output is written to logs/build-db.log for troubleshooting.
To trigger a manual rebuild at any time, run muscat-db build-db from the repo root.
The prose pipeline processes calibrated FITS frames through a sequence of steps:
--ref_band); with --ref_select quality, the pipeline selects a clean anchor and uses registered catalogs from nearby candidates to reject transient detections (see FAQ below)By default (position) the reference frame is chosen positionally — the first frame for self-reference, or the middle frame of the reference band when --ref_band is set — with no check on whether that frame is actually any good (clouds, poor focus, and high airmass can all silently become the reference). --ref_select quality replaces this with a two-tier pre-check that runs before the full reduction:
L1FWHM), airmass, focus position, humidity, and background level. Frames with physically broken values (e.g. airmass > 3.5, humidity > 95%) are excluded, and a composite score dominated by FWHM identifies the best-quality anchor. The candidate window is then formed from viable frames nearest that anchor in time, limiting telescope drift.--ref_select_top_k, default 5) are run through the normal source-detection pipeline in parallel and ranked using target match, non-outlier star count, measured FWHM, and target saturation. Their catalogs are registered to the selected anchor, and a detection is retained only when it occurs in at least 60% of the registered catalogs (3 of 5 by default). This removes one-frame cosmic-ray and hot-pixel detections while preserving the WCS-selected or explicit target. MuSCAT/MuSCAT2 use a local window around the positional midpoint because they lack the BANZAI header proxy.An explicit Reference frame ID always overrides quality selection. If fewer than two candidate catalogs can be registered, the pipeline safely keeps the anchor catalog instead of applying an unreliable persistence filter. The chosen frame, candidate comparison, registration status, and persistence counts are written to a *_ref_selection.txt sidecar (linked from the results page as "view ref selection") so the decision can be audited without re-running anything. Quality mode adds work roughly proportional to top-K, so it remains opt-in.
The automatic differential photometry algorithm (implemented in prose) optimally selects and weights comparison stars to minimize the scatter in the target's differential lightcurve:
The key advantage is that no manual selection of comparison stars is needed — the algorithm handles both the choice and the relative weighting, and it can exclude bad comparison stars that would otherwise introduce noise.
The NaN Limitation: The matrix-based implementation of autodiff in prose requires a dense matrix with no NaNs. Because of this, we cannot easily mask out localized outliers (like a cosmic ray hitting a single comparison star on a single frame) by setting them to NaN without propagating that NaN to all other stars through the matrix multiplications.
Proposed Fix (Imputation): Instead of dropping the entire frame (which is wasteful) or using NaNs (which breaks the algebra), we can apply star-level sigma clipping on the flux matrix and impute/replace individual outlier points with the median flux of that star before running the autodiff calculation.
Implications:
The comparison stars used to compute the differential lightcurve of the target are determined as follows:
max_num_stars (default 10) of the brightest point sources across all frames. All comparison star candidates are chosen from this subset of tracked stars.--cID) are provided by the user, the pipeline validates that they are in-range and do not belong to the avoided/excluded list before using them directly.autodiff() algorithm. This algorithm dynamically determines and weights comparison stars from the candidate pool (excluding the target and any avoided stars) to minimize the scatter in the target's differential lightcurve.--avoid_cids. Additionally, the pipeline automatically detects stars near the CCD edge (edge_cids) and adds them to the avoid pool to prevent edge-effect systematics from contaminating the comparison pool.Optimal Comparison Stars:
max_num_stars), excluding the target and avoided stars.auto_diff_1d) starts with the highest-weighted star, iteratively adding the next-highest, and computes the binned white noise (binned_nanstd) of the target's resulting differential lightcurve. It retains the subset of comparison stars that minimizes this white noise.Optimal Aperture Size:
--test_run_frames). The pipeline runs all stages (alignment, source detection, centroiding, photometry) but produces a short lightcurve. Used to verify settings, check alignment quality, and preview results before committing to a full reduction.You can always re-run with --overwrite to replace a test run with a full run later.
*_ref.png) — the stacked reference frame with detected sources circled and the target marked. Verifies that sources are correctly identified and the target is at the right position.*_apertures.png) — overplot of aperture radii and sky annuli on the reference. Check that the inner annulus excludes nearby stars and the outer annulus is within clean sky.*_alignment.png) — shows the shift (dx, dy) computed for each frame relative to the reference. Large or discontinuous shifts may indicate guiding problems or a bad reference frame.lightcurves.png) — multi-band raw and differential lightcurves side by side. The raw flux should be stable; the differential should be flat for non-variable targets. Trends in the differential suggest poor comparison star selection.covariates.png) — systematics summary: FWHM, airmass, background, and centroid positions across the sequence. Correlations between these and the lightcurve can guide detrending choices.stacks.png) — grouped thumbnail grid of one cutout per frame per band to quickly flag outliers (cosmic rays, clouds, tracking losses).The pipeline queries the Gaia DR3 catalog around the target position to identify contaminating sources:
When to override: for crowded fields where Gaia sources are too numerous (the annulus becomes unusably small), or for isolated bright targets where the heuristic is overly conservative. Use --aper_radii MIN,MAX,DR and --annulus RIN,ROUT to set custom values.
WCS (World Coordinate System) maps pixel coordinates to sky coordinates (RA, Dec). The pipeline needs it for two things:
If WCS is missing from calibrated headers, --target_coord RA Dec can be passed instead — but source identification then relies on source ordering (brightest = target), which is unreliable for crowded fields.
The pipeline converts observation times from JD-UTC to BJD-TDB to remove the light-travel time variation caused by Earth's orbital motion around the Solar System barycenter:
light_travel_time function computes the barycentric correction using the target's sky coordinates and the observatory geocentric position (derived from the FITS header's SITE keyword or instrument defaults like Teide Observatory for MuSCAT2).--use_barycorrpy is enabled, the barycorrpy package (a Python wrapper for Jason Eastman's barycorr) is used instead. It accounts for additional effects including Shapiro delay and Earth's precession/nutation.The difference between the two methods is typically <0.1 seconds for MuSCAT observing cadences. barycorrpy is recommended for sub-minute transit timing measurements.
The pipeline identifies the target star among all detected sources by matching the resolved target coordinates (from MAST) against the WCS-transformed positions of every detected source, selecting the nearest match within a tolerance.
When to override — if the WCS solution is poor, if the field contains multiple stars at similar coordinates, or if MAST resolution failed and the fallback assumed the brightest star is the target. Set --tID N where N is the 0-indexed source number from the reference image (circled labels on the *_ref.png plot).
The pipeline auto-detects narrow-band observations by inspecting the obslog CSVs for known narrow-band filter names (g_narrow, Na_D, i_narrow, z_narrow). When narrow bands are present:
To override the auto-detection, specify bands explicitly with --bands gp rp Na_D.
Give the reduction a distinct Run name to isolate it under $MUSCAT_PROSE_DIR/<instrument>/<date>/_runs/<target>/<run_id>/. Run chips on the results page switch between saved reductions. The --overwrite option (enabled by default in the GUI) only replaces products in the selected run, which is useful when retrying the same configuration with changed apertures, bands, or reference settings. Use a new run name whenever the previous products must remain intact.
Both the photometry and transit-fit result pages have a Download all output button. It calls a server-side endpoint (/api/photometry/download-all/... or /api/transit-fit/download-all/...) that zips every product file for that run into a single archive and streams it back — no need to download plots and CSVs one at a time. For run-scoped reductions the zip walks the run directory recursively; legacy (non run-scoped) reductions zip the top-level files plus the out/ subdirectory. The temporary zip is written under the pipeline's tmpdir and deleted immediately after the response finishes streaming.
Default parameters are defined in src/muscat_db/photometry.py in the RUN_DEFAULTS dictionary. Key defaults:
These can be overridden individually in the GUI form or via command-line flags.
Use pix when the PSF is stable and you want a fixed physical aperture. Use fwhm when seeing varies significantly across the sequence — it produces more consistent photometry at the cost of slightly more complex interpretation.
The pipeline reads the saturation level from the FITS header (SATURATE keyword or BSCALE/DATAMAX). During source detection, pixels above a configurable fraction of the saturation level are flagged. However:
For heavily saturated targets, use shorter exposure sequences or apply a non-linearity correction externally before running the pipeline.
The --ref_band flag controls whether all bands are aligned to a single reference band or each band self-references its own first frame:
--ref_band not set) — each band creates its own reference frame from its first science frame. Alignment is within-camera only. Recommended for multi-camera instruments (MuSCAT3/4) where each band uses a separate CCD, because cross-camera registration would introduce interpolation artifacts from slightly different pointing and distortion.--ref_band gp for example) — all bands align to the reference band's middle frame. This can introduce systematic offsets between bands because different cameras have different pixel scales, rotations, and distortions. The resulting lightcurves may show band-dependent systematics.For MuSCAT2 (single CCD with filter wheel), both strategies work because all bands share the same detector. Per-band self-reference is still the default and generally recommended. For Sinistro, --ref_band is disabled in the GUI because simultaneous bands can be observed by different LCO telescopes with large pointing offsets, so mapping one band's reference catalog onto another can fail or produce invalid alignment.
The timer transit fitting pipeline models the transit lightcurve using PyMC's NUTS sampler:
fit.yaml, sys.yaml) specify stellar and planetary parameters, priors, detrending covariates, and sampler settings.The transit fitting pipeline supports two run modes to handle existing reduction results:
clobber: true in the fit configuration. It discards any previous traces, runs L-BFGS MAP pre-optimization to find an initial solution, and starts MCMC sampling from scratch.clobber: false. It loads the previous MCMC trace from saved pickle files and resumes sampling, appending new draws to the existing chain. This option is only available when a previous fit run exists.Check the Secondary eclipse box in the Fitting Pipeline Options section before launching a fit. This sets secondary_eclipse: true in the fit.yaml configuration, which places the modeled event at orbital phase 0.5 rather than phase 0 (the primary transit). It is off by default and is independent of the New Fit / Continue Sampling run mode — combine it with a fresh set of lightcurves centered on the expected eclipse time.
The transit fit page has a Query Archive button that fetches stellar and planetary parameters from two sources:
source=toi) — queries the TESS Objects of Interest table via the Exoplanet Archive TAP service. The target name is cleaned by removing the TOI prefix, leading zeros, and .01 suffix (e.g. TOI07475.01 → 7475), then matched against the toi column with fallback queries on toidisplay and substring matching. Returns Teff, logg, orbital period, transit midpoint, and transit duration.source=nasa) — queries the pscomppars (Planetary Systems Composite Parameters) table by pl_name or hostname. Returns the same parameters plus metallicity, radius ratio, and impact parameter, with literature references.The fetched values are used to pre-populate the priors in the YAML configuration, which can be edited before running the fit.
The timer transit fitting pipeline used here is distinct from the MuSCAT2 transit pipeline (Parvi et al.). Key differences:
| Aspect | timer (MuSCAT-db) | MuSCAT2 Pipeline |
|---|---|---|
| Sampling engine | PyMC (v3 or v4) with NUTS sampler | emcee ensemble sampler |
| Transit model | batman or starry (flexible) | Custom analytical model |
| Limb darkening | Quadratic, nonlinear, fixed, or priors | Fixed from stellar parameters |
| Systematics model | Linear regression or Gaussian process | Airmass + centroid polynomial |
| Configuration | YAML files (fit.yaml, sys.yaml) |
Python scripts |
| Web integration | Native — runs via web UI, persists results in DB | Standalone — independent command-line tool |
| Archive integration | Loads photometry CSVs from prose pipeline | Works with raw MuSCAT2 observations |
| Output | Posterior samples, corner/trace/LD/fit plots, O-C analysis | Posterior samples and publication-quality plots |
When to use each:
Both pipelines produce scientifically rigorous results; the choice depends on your data source and workflow integration needs.
There is no separate TTV page. TTV fitting lives on the Ephemeris page, in the Configure & Run TTV Fitting section below the O-C plot. A TTV signal is only meaningful relative to a reference linear ephemeris, so the fit is the last step of the O-C workflow rather than its own destination. The old /ttv-fit URL redirects to /ephemeris.
The harmonic engine fits the multi-harmonic near-resonant TTV model of Lithwick, Xie & Wu (2012), sampled with emcee:
Tc, tc_unc, epoch per planet) are written to data.csv, with planet letters remapped to the integer indices harmonic expects. The dataset checkboxes and point exclusions that shape the linear fit shape the TTV fit too.config.ini carries an [INIT] section with the TTV amplitude, super-period, and phase reference (a_bc, a_cb, per_bc, t_bc) for each adjacent pair, plus optional [T14] (transit durations) and [OUTER] (non-transiting outer planet) sections.harmonic environment. The exact command is shown in the run panel so it can be reproduced in a terminal.emcee runs with the configured walkers, steps, burn-in, thinning, processes, and seed, streaming harmonic.log live to the page. Completed results include a fit run log link that opens the full log in a new tab.samples.csv.gz), diagnostic plots (corner.png, trace.png, fit.png, init.png), and meta.yaml recording the muscat-db and harmonic versions plus the full option set.TTV results are keyed on target alone — not instrument and date, unlike photometry — because a TTV fit spans every transit of that system regardless of which night or instrument produced each transit center. They live under $MUSCAT_TTV_DIR/<target>/_runs/<run_name>/ (default $HOME/ql/harmonic), mirroring the photometry _runs/ convention.
Naming a run keeps it isolated, so you can compare, say, a two-planet fit against a fit with a non-transiting outer planet. The run chips above the results switch between stored runs, and Delete Results is scoped to the run currently on screen rather than the name typed in the box. A run directory without samples.csv.gz is treated as empty and hidden from the chip list.
[OUTER] section in the INI.The stellar mass field feeds the mass and eccentricity constraints derived from the fitted TTV amplitudes.
Run muscat-db htpasswd add <username> (it prompts for a password) or muscat-db htpasswd add <username> --password-stdin for scripting — never edit the htpasswd file by hand. muscat-db htpasswd list shows current users and muscat-db htpasswd delete <username> removes one. The file lives at /etc/nginx/.htpasswd-muscatdb by default (falling back to data/.htpasswd-muscatdb when nginx isn't installed, e.g. in dev) and is written 0640 root:www-data so only root and nginx's worker group can read the password hashes.
nginx sets X-Forwarded-User after a successful HTTP Basic Auth login and adds a private X-MuSCAT-Proxy-Secret header. In --nginx mode, the middleware requires a loopback peer, a valid username, and the matching proxy secret; protected requests fail with 401 otherwise. This prevents both remote header spoofing and direct local calls to uvicorn from impersonating nginx.
Per-user LCO tokens saved on /settings are encrypted at rest with Fernet symmetric encryption, keyed from MUSCAT_DB_SECRET. If that variable is unset (or changes), saving or reading a token fails with a 503 ("could not save LCO token" / "stored LCO token cannot be read") rather than silently storing or returning it in cleartext. Set a long, random MUSCAT_DB_SECRET before onboarding real users, and keep it stable across restarts (e.g. export it from the service environment or a `.env` file, not an ephemeral interactive shell) — rotating it makes previously-encrypted tokens permanently undecryptable, so affected users must re-save their token.
The FOV Optimizer evaluates possible telescope pointings using a grid-search algorithm over center offsets and position angles (PA). It queries the Gaia DR3 catalog and assigns utility weights to candidate comparison stars based on:
The optimizer selects the center offset and rotation angle that maximize the cumulative weight of the captured stars while keeping the target safely inside the instrument's footprint edge (margin of 30 arcseconds).
An optional avoid brighter than Gmag threshold rejects any candidate pointing whose full footprint contains a star brighter than that limit (the science target itself is exempt). This is useful for steering the field away from a nearby saturating star that would otherwise bleed into the frame or degrade guiding.
These planning shortcut buttons prefill target and instrument parameters, linking directly to the respective planning tool:
/api/exposure/target/{target}) to retrieve unique filters and exposure times previously used.The exported Observed-minus-Calculated (O-C) summary CSV text now starts with descriptive comment lines (prefixed by #) explaining that the transit center times (TCs) are in Barycentric Dynamical Time (BJD_TDB). This makes it easy for automated scripts and external fitting tools to parse column formats (planet, epoch, tc, tc_unc) and track time-standard coordinates correctly.
Each target-scoped ephemeris field (RA/Dec, T0, period, duration) on the LCO Schedule page shows a small badge indicating where its value came from: NASA or TOI for a catalog lookup, AUTO for a fallback default, LINEAR/FIT for a value derived from a saved ephemeris view's linear fit, or manual once you've hand-edited it.
Switching targets blanks all four fields first — an empty field honestly means "no data for this target" rather than a stale value left over from whatever you looked at previously. Re-querying the same target preserves any field you've tagged manual instead of overwriting it, and a catalog miss no longer blanks coordinates you entered by hand.
LCO validates a request's configuration duration against target visibility across the entire request window, including pre/post padding — not just the bare transit shown in the window generator. Before a dry-run IPP check or live submission, the backend re-checks observability at the selected site over the full padded window and rejects the request with a specific reason (which window index, whether it is partially or fully unobservable, the request span in hours) instead of letting it fail opaquely at LCO.
If a window is flagged, regenerate windows with "Include padding in obs check" enabled, reduce the pre/post padding, pick a different window or site, or loosen the airmass/moon-separation constraints.
The LCO Schedule form and the /toi//nexsci catalog pages encode their current filter/form state into the URL hash fragment as you interact with them (e.g. /lco/schedule#proposal=TOM00001234&target=TOI-2000&kind=muscat3 or /toi#q=TOI-2000&planet=SuperEarth&star=M&sort=period:1), updated in place via history.replaceState() so it never adds browser-history entries.
On load, state is restored with this priority: URL hash > localStorage > built-in defaults. That means bookmarking or sharing the URL reproduces the exact view regardless of what is saved in the visiting browser's localStorage.
The target page has a collapsible NASA ADS Publications panel that queries the ADS search API for papers matching a text query (pre-filled with the target name). It requires an ADS API token — set ADS_API_TOKEN (or ADS_DEV_KEY/ADS_TOKEN) in .env, generated from your ADS token settings. A status dot on the panel shows whether the server has a token configured before you search; results link out to each paper's ADS abstract page by bibcode. No token is ever sent to the browser — the server holds it and proxies the query.
The /toi page left-joins the Boyle+2026 stellar-rotation catalog (final_catalog.feather, matched by TIC ID) onto the TOI table, adding rotation period (Prot ± uncertainty), RUWE, contaminant/sector stats, and binarity flag columns. The Fast rotator (Prot < 10 d) chip in the Rotation chip group filters to TOI hosts with a measured period below 10 days — fast-rotating host stars are more likely to show spot-crossing anomalies and higher photometric variability during a transit, which is useful context before scheduling. A dynamic note near the chip reports how many of the currently filtered targets have a measured Prot, with a link to the source paper (arXiv:2603.05586).
The exposure calculator fits empirical coefficients (using muscat-db build-db or calibration runs) to predict how many stellar photoelectrons will hit the CCD per second for a star of a given magnitude. It then applies standard CCD noise models (Poisson noise of the source, sky background noise, dark current, and read noise) to compute either the exposure time to reach a target S/N or the estimated S/N for a specified exposure time. Key parameters include the instrument aperture, focus offset, and filter transmission band.
The catalog browsers query the SQLite database's targets table on load to compile a list of all objects that have already been ingested into muscat-db. In the Plotly scatter plots, these matched targets are rendered as prominent ★ star markers instead of standard circular dots. In the searchable tables, clicking these rows opens the local target details page, while unmatched catalog rows link out directly to their NASA Exoplanet Archive overview page.
FastAPI automatically generates interactive API documentation for all JSON endpoints:
/docs. This interface allows you to interactively test endpoints directly from the browser./redoc. This provides a clean, read-only layout of the API specifications.Additionally, modern documentation engines like Scalar can be served side-by-side or mounted to override /docs by returning a custom HTMLResponse that fetches the Scalar web components from its official CDN pointing to the /openapi.json schema.
@here to share this page, @name to notify someone, or @test for a message that isn't saved.