nsb 0.1.1

Night-sky background evaluation and observation planning for ground-based astronomy.
Documentation
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Vallés Puig, Ramon
//! Point NSB evaluation and component composition.

use super::metadata::{
    airglow_metadata, moonlight_metadata, starlight_metadata, zodiacal_metadata,
};
use super::types::{ComponentMask, NsbComponent, NsbResult, PreparedPointQuery};
use super::NsbEvaluator;
use crate::error::Result;
use crate::units::photometry::s10_to_surface_brightness;
use crate::units::radiometry::{
    PhotonsPerSquareCentimeterNanosecondSteradian as BandPhotonRadiance, S10s as S10,
};
use crate::NSB_S10_ZP;
use tempoch::{Time, UTC};

pub(crate) fn evaluate(
    evaluator: &NsbEvaluator,
    query: &PreparedPointQuery,
    time: Time<UTC>,
) -> Result<NsbResult> {
    let mut components = Vec::new();
    let mut total = BandPhotonRadiance::new(0.0);
    let (mut b_total, mut v_total) = (S10::new(0.0), S10::new(0.0));

    if query.components.contains(ComponentMask::ZODIACAL) {
        let out = evaluator
            .zodiacal()
            .compute(time, query.observer, query.target)?;
        total += out.integrated;
        b_total += out.b_flux_s10;
        v_total += out.v_flux_s10;
        components.push(NsbComponent {
            name: "zodiacal",
            integrated: out.integrated,
            b_flux_s10: out.b_flux_s10,
            v_flux_s10: out.v_flux_s10,
            relative_uncertainty: None,
            statistical_uncertainty: None,
            systematic_uncertainty: None,
            total_uncertainty: None,
            metadata: zodiacal_metadata(
                evaluator.model_config().zodiacal_model(),
                evaluator.model_config().zodiacal_extinction(),
            ),
        });
    }
    if query.components.contains(ComponentMask::STARLIGHT) {
        let out = evaluator.evaluate_starlight(query.target)?;
        total += out.integrated;
        b_total += out.b_flux_s10;
        v_total += out.v_flux_s10;
        components.push(NsbComponent {
            name: "starlight",
            integrated: out.integrated,
            b_flux_s10: out.b_flux_s10,
            v_flux_s10: out.v_flux_s10,
            relative_uncertainty: out.relative_uncertainty(),
            statistical_uncertainty: out.statistical_uncertainty,
            systematic_uncertainty: out.systematic_uncertainty,
            total_uncertainty: out.total_uncertainty,
            metadata: starlight_metadata(
                evaluator.model_config().starlight_product(),
                evaluator.starlight().map(|model| model.map().provenance()),
            ),
        });
    }
    if query.components.contains(ComponentMask::AIRGLOW) {
        let (out, solar) =
            evaluator.evaluate_airglow_resolved(query.observer, time, query.target)?;
        total += out.integrated;
        b_total += out.b_flux_s10;
        v_total += out.v_flux_s10;
        components.push(NsbComponent {
            name: "airglow",
            integrated: out.integrated,
            b_flux_s10: out.b_flux_s10,
            v_flux_s10: out.v_flux_s10,
            relative_uncertainty: out.relative_uncertainty,
            statistical_uncertainty: None,
            systematic_uncertainty: None,
            total_uncertainty: None,
            metadata: airglow_metadata(
                evaluator.airglow_resolved(),
                evaluator.model_config().site_profile(),
                query.observer,
                Some(&solar),
                evaluator.model_config().airglow_geometry(),
                out.physical_outcome,
                out.physical_zero_reason,
            ),
        });
    }
    if query.components.contains(ComponentMask::MOON) {
        let out = evaluator.evaluate_moonlight(query.observer, time, query.target)?;
        total += out.integrated;
        b_total += out.b_flux_s10;
        v_total += out.v_flux_s10;
        components.push(NsbComponent {
            name: "moon",
            integrated: out.integrated,
            b_flux_s10: out.b_flux_s10,
            v_flux_s10: out.v_flux_s10,
            relative_uncertainty: None,
            statistical_uncertainty: None,
            systematic_uncertainty: None,
            total_uncertainty: None,
            metadata: moonlight_metadata(
                evaluator.model_config().moonlight_model(),
                evaluator.model_config().site_profile(),
                query.observer,
            ),
        });
    }

    Ok(NsbResult {
        integrated: total,
        b_mag: s10_to_surface_brightness(
            b_total.max(S10::new(f64::MIN_POSITIVE)),
            NSB_S10_ZP.value(),
        ),
        v_mag: s10_to_surface_brightness(
            v_total.max(S10::new(f64::MIN_POSITIVE)),
            NSB_S10_ZP.value(),
        ),
        components,
        band_diagnostic: super::BandDiagnostic::MONOCHROMATIC_S10_PROXY,
    })
}