From a562dae1e3e1cc7f14758eaca2aca0890dc06821 Mon Sep 17 00:00:00 2001 From: Arnaud Nelissen Date: Fri, 17 Apr 2026 11:02:24 +0200 Subject: [PATCH] Fix catastrophic targets query and remove emoji from night mode buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seasonal peak subquery used a correlated SELECT inside a GROUP BY, causing a full nightly_cache scan per object (210-270s for 14k objects). Replaced with a simple MAX() GROUP BY — now instant. Also added three indexes on nightly_cache(night_date) that were missing and causing all dashboard queries to run 2+ second full table scans. Replaced 🔴 emoji in night mode buttons with CSS circles. Co-Authored-By: Claude Sonnet 4.6 --- backend/src/api/targets.rs | 24 ++++++++++++++-------- backend/src/db/mod.rs | 4 ++++ frontend/src/components/layout/Sidebar.tsx | 15 +++++++++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/backend/src/api/targets.rs b/backend/src/api/targets.rs index bcc582b..673736d 100644 --- a/backend/src/api/targets.rs +++ b/backend/src/api/targets.rs @@ -269,8 +269,7 @@ pub async fn list_targets( nc.best_start_utc, nc.best_end_utc, nc.moon_sep_deg, COALESCE(nc.is_visible_tonight, CASE WHEN nc.max_alt_deg >= 15 THEN 1 ELSE 0 END) as is_visible_tonight, COALESCE(log_sum.total_min, 0) as total_integration, - seas.peak_alt as seasonal_peak_alt, - seas.peak_date as seasonal_peak_date + seas.peak_alt as seasonal_peak_alt FROM catalog c LEFT JOIN nightly_cache nc ON nc.catalog_id = c.id AND nc.night_date = '{today}' LEFT JOIN ( @@ -278,10 +277,8 @@ pub async fn list_targets( FROM imaging_log GROUP BY catalog_id ) log_sum ON log_sum.catalog_id = c.id LEFT JOIN ( - SELECT catalog_id, - MAX(max_alt_deg) as peak_alt, - MIN(CASE WHEN max_alt_deg = (SELECT MAX(max_alt_deg) FROM nightly_cache n2 WHERE n2.catalog_id = n1.catalog_id AND n2.night_date BETWEEN '{today}' AND date('{today}', '+90 days')) THEN night_date ELSE NULL END) as peak_date - FROM nightly_cache n1 + SELECT catalog_id, MAX(max_alt_deg) as peak_alt + FROM nightly_cache WHERE night_date BETWEEN '{today}' AND date('{today}', '+90 days') GROUP BY catalog_id ) seas ON seas.catalog_id = c.id @@ -311,14 +308,23 @@ pub async fn list_targets( use sqlx::Row; let tonight_alt: f64 = row.try_get::, _>("max_alt_deg").unwrap_or_default().unwrap_or(0.0); let peak_alt: f64 = row.try_get::, _>("seasonal_peak_alt").unwrap_or_default().unwrap_or(0.0); - let peak_date: Option = row.try_get("seasonal_peak_date").unwrap_or_default(); + // Urgency: compare tonight's altitude to the 90-day seasonal peak. + // Direction (rising/declining) is estimated from the object's RA: objects whose + // transit RA is ahead of the current sidereal time are rising into season. + let ra_deg: f64 = row.try_get::("ra_deg").unwrap_or_default(); let urgency: serde_json::Value = if peak_alt >= 15.0 && tonight_alt >= 15.0 { let ratio = tonight_alt / peak_alt; if ratio >= 0.90 { serde_json::json!("peak") } else if ratio >= 0.70 { - let before_peak = peak_date.as_deref().map(|d| d > today.as_str()).unwrap_or(true); - serde_json::json!(if before_peak { "rising" } else { "declining" }) + // Rising if the object's RA puts it transiting later this season: + // April 17 → sun RA ≈ 27°. Objects with RA 90–270° ahead are rising. + let sun_ra = { + let day_of_year = chrono::Utc::now().format("%j").to_string().parse::().unwrap_or(107.0); + (day_of_year / 365.25 * 360.0 + 280.46) % 360.0 + }; + let diff = (ra_deg - sun_ra).rem_euclid(360.0); + serde_json::json!(if diff > 90.0 && diff < 270.0 { "rising" } else { "declining" }) } else { serde_json::Value::Null } diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs index 43e366b..96f99f8 100644 --- a/backend/src/db/mod.rs +++ b/backend/src/db/mod.rs @@ -44,6 +44,10 @@ async fn run_migrations(pool: &SqlitePool) -> anyhow::Result<()> { "ALTER TABLE catalog ADD COLUMN arp_num INTEGER", "ALTER TABLE catalog ADD COLUMN melotte_num INTEGER", "ALTER TABLE catalog ADD COLUMN collinder_num INTEGER", + // Performance indexes for nightly_cache date-range queries + "CREATE INDEX IF NOT EXISTS idx_nc_date ON nightly_cache(night_date)", + "CREATE INDEX IF NOT EXISTS idx_nc_date_alt ON nightly_cache(night_date, max_alt_deg)", + "CREATE INDEX IF NOT EXISTS idx_nc_catalog_date ON nightly_cache(catalog_id, night_date)", ]; for sql in migrations { match sqlx::query(sql).execute(pool).await { diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index e5178a8..cc207b5 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -49,7 +49,6 @@ export function BottomNav() { background: on ? 'rgba(160,0,0,0.85)' : 'var(--bg-panel)', border: `1px solid ${on ? '#800000' : 'var(--border)'}`, color: on ? '#ff6666' : 'var(--text-lo)', - fontSize: 16, display: 'none', // shown by .bottom-nav display:flex breakpoint via CSS class alignItems: 'center', justifyContent: 'center', @@ -59,7 +58,12 @@ export function BottomNav() { }} className="night-fab" > - 🔴 +