Fix SNR calculator, add score weight controls, highlight selected filter
SNR calculator: signalFromSB calibration was 4750× too small (0.001 vs ~4.75 at SB=21). Calibration is now derived consistently from the sky background constants: C[filter] = sky_e_s[filter] / 10^((21 - 21.5) / 2.5). Also made it filter-aware so narrowband filters use their own reference. Replaced the broken 500+/billions display with a proper per-filter result or a 'too faint for this setup' message when signal ≈ 0. Score weights: 'Best score tonight' sort now accepts score_alt/fov/time/moon query params (0.0–1.0, server-side normalised to sum=1). Frontend adds a ⚙ weights button next to the sort dropdown that reveals 4 sliders showing effective %, persisted to localStorage. Weights default to 40/30/20/10. Selected filter: clicking a filter pill in the Filters tab now highlights the row (bg + amber outline on the pill + ▶ marker) so it's clear which filter the SNR calculator and workflow card are showing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,11 @@ pub struct TargetsQuery {
|
||||
pub mosaic_only: Option<bool>,
|
||||
pub not_imaged: Option<bool>,
|
||||
pub show_custom: Option<bool>,
|
||||
// Score weights for "best tonight" sort (0.0–1.0 each, auto-normalised server-side)
|
||||
pub score_alt: Option<f64>,
|
||||
pub score_fov: Option<f64>,
|
||||
pub score_time: Option<f64>,
|
||||
pub score_moon: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::FromRow)]
|
||||
@@ -233,18 +238,32 @@ pub async fn list_targets(
|
||||
.unwrap_or(1.0);
|
||||
|
||||
// "best" sort: composite score balancing altitude, FOV fill, usable time, moon separation.
|
||||
// Weights are normalised to sum=1.0 so changing one factor doesn't need all others to change.
|
||||
let raw_alt = params.score_alt.unwrap_or(0.40).clamp(0.0, 1.0);
|
||||
let raw_fov = params.score_fov.unwrap_or(0.30).clamp(0.0, 1.0);
|
||||
let raw_time = params.score_time.unwrap_or(0.20).clamp(0.0, 1.0);
|
||||
let raw_moon = params.score_moon.unwrap_or(0.10).clamp(0.0, 1.0);
|
||||
let weight_sum = raw_alt + raw_fov + raw_time + raw_moon;
|
||||
let (w_alt, w_fov, w_time, w_moon) = if weight_sum > 0.0 {
|
||||
(raw_alt / weight_sum, raw_fov / weight_sum, raw_time / weight_sum, raw_moon / weight_sum)
|
||||
} else {
|
||||
(0.4, 0.3, 0.2, 0.1)
|
||||
};
|
||||
// Multiplied by weather_weight so cloudy nights rank all targets lower.
|
||||
let best_score_expr = format!(r#"(
|
||||
COALESCE(nc.max_alt_deg, 0) / 90.0 * 0.40
|
||||
COALESCE(nc.max_alt_deg, 0) / 90.0 * {w_alt:.4}
|
||||
+ CASE
|
||||
WHEN c.fov_fill_pct IS NULL THEN 0.15
|
||||
WHEN c.fov_fill_pct BETWEEN 20 AND 80 THEN (1.0 - ABS(c.fov_fill_pct - 50) / 50.0) * 0.30
|
||||
WHEN c.fov_fill_pct > 80 THEN 0.10
|
||||
ELSE 0.05
|
||||
WHEN c.fov_fill_pct IS NULL THEN {w_fov:.4} * 0.5
|
||||
WHEN c.fov_fill_pct BETWEEN 20 AND 80 THEN (1.0 - ABS(c.fov_fill_pct - 50) / 50.0) * {w_fov:.4}
|
||||
WHEN c.fov_fill_pct > 80 THEN {w_fov:.4} * 0.33
|
||||
ELSE {w_fov:.4} * 0.17
|
||||
END
|
||||
+ MIN(COALESCE(nc.usable_min, 0), 300) / 300.0 * 0.20
|
||||
+ COALESCE(nc.moon_sep_deg, 90) / 180.0 * 0.10
|
||||
) * {weather_weight:.2} DESC"#, weather_weight = weather_weight);
|
||||
+ MIN(COALESCE(nc.usable_min, 0), 300) / 300.0 * {w_time:.4}
|
||||
+ COALESCE(nc.moon_sep_deg, 90) / 180.0 * {w_moon:.4}
|
||||
) * {weather_weight:.2} DESC"#,
|
||||
w_alt = w_alt, w_fov = w_fov, w_time = w_time, w_moon = w_moon,
|
||||
weather_weight = weather_weight
|
||||
);
|
||||
let sort_col_owned: String = match params.sort.as_deref() {
|
||||
Some("transit") => "nc.transit_utc".to_string(),
|
||||
Some("size") => "c.size_arcmin_maj DESC".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user