use pleiades_backend::CelestialBody;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BodyClass {
Luminary,
InnerPlanet,
OuterPlanet,
Asteroid,
}
pub fn body_class(body: &CelestialBody) -> BodyClass {
match body {
CelestialBody::Sun | CelestialBody::Moon => BodyClass::Luminary,
CelestialBody::Mercury | CelestialBody::Venus | CelestialBody::Mars => {
BodyClass::InnerPlanet
}
CelestialBody::Jupiter
| CelestialBody::Saturn
| CelestialBody::Uranus
| CelestialBody::Neptune
| CelestialBody::Pluto => BodyClass::OuterPlanet,
_ => BodyClass::Asteroid,
}
}
#[derive(Clone, Copy, Debug)]
pub struct AccuracyCeiling {
pub lon_arcsec: f64,
pub lat_arcsec: f64,
pub dist_km: f64,
pub lon_speed_arcsec_per_day: f64,
pub lat_speed_arcsec_per_day: f64,
pub radial_speed_au_per_day: f64,
}
pub fn accuracy_ceiling(body: &CelestialBody) -> AccuracyCeiling {
match body_class(body) {
BodyClass::Luminary | BodyClass::InnerPlanet => AccuracyCeiling {
lon_arcsec: 1.0,
lat_arcsec: 1.0,
dist_km: 50.0,
lon_speed_arcsec_per_day: 0.5,
lat_speed_arcsec_per_day: 0.5,
radial_speed_au_per_day: 1.0e-4,
},
BodyClass::OuterPlanet => AccuracyCeiling {
lon_arcsec: 5.0,
lat_arcsec: 5.0,
dist_km: 1_000.0,
lon_speed_arcsec_per_day: 0.05,
lat_speed_arcsec_per_day: 0.05,
radial_speed_au_per_day: 1.0e-4,
},
BodyClass::Asteroid => AccuracyCeiling {
lon_arcsec: 30.0,
lat_arcsec: 30.0,
dist_km: 5_000_000.0,
lon_speed_arcsec_per_day: 120.0,
lat_speed_arcsec_per_day: 120.0,
radial_speed_au_per_day: 1.0e-2,
},
}
}
#[derive(Clone, Copy, Debug)]
pub struct ArtifactBudgets {
pub max_encoded_bytes: usize,
pub decode_latency_target_ms: f64,
pub single_lookup_target_ms: f64,
pub batch_throughput_target_per_s: f64,
pub chart_workload_target_ms: f64,
}
pub const PACKAGED_BUDGETS: ArtifactBudgets = ArtifactBudgets {
max_encoded_bytes: 12_000_000, decode_latency_target_ms: 400.0, single_lookup_target_ms: 6.0, batch_throughput_target_per_s: 1_000.0,
chart_workload_target_ms: 50.0,
};
pub fn packaged_artifact_thresholds_summary_for_report() -> String {
let baseline = crate::accuracy_baseline::packaged_artifact_accuracy_baseline();
let mut lines = Vec::new();
for e in &baseline {
let c = accuracy_ceiling(&e.body);
let lon_status = if e.max_longitude_arcsec <= c.lon_arcsec {
"PASS"
} else {
"FAIL"
};
let lat_status = if e.max_latitude_arcsec <= c.lat_arcsec {
"PASS"
} else {
"FAIL"
};
let dist_status = if e.max_distance_km <= c.dist_km {
"PASS"
} else {
"FAIL"
};
let spd_status = if e.max_lon_speed_arcsec_per_day <= c.lon_speed_arcsec_per_day {
"PASS"
} else {
"FAIL"
};
lines.push(format!(
"{:?}: lon {:.4}\"/{}\"={} lat {:.4}\"/{}\"={} dist {:.1}/{} km={} lon_spd {:.4}/{} \"/d={}",
e.body,
e.max_longitude_arcsec, c.lon_arcsec, lon_status,
e.max_latitude_arcsec, c.lat_arcsec, lat_status,
e.max_distance_km, c.dist_km, dist_status,
e.max_lon_speed_arcsec_per_day, c.lon_speed_arcsec_per_day, spd_status,
));
}
format!(
"Packaged-artifact thresholds ({} bodies); size budget {} bytes:\n{}",
baseline.len(),
PACKAGED_BUDGETS.max_encoded_bytes,
lines.join("\n")
)
}
#[cfg(test)]
mod tests {
use super::*;
use pleiades_backend::CelestialBody;
#[test]
fn classes_map_bodies_correctly() {
assert_eq!(body_class(&CelestialBody::Sun), BodyClass::Luminary);
assert_eq!(body_class(&CelestialBody::Moon), BodyClass::Luminary);
assert_eq!(body_class(&CelestialBody::Mercury), BodyClass::InnerPlanet);
assert_eq!(body_class(&CelestialBody::Pluto), BodyClass::OuterPlanet);
}
#[test]
fn outer_planets_have_looser_longitude_ceiling_than_inner() {
assert!(
accuracy_ceiling(&CelestialBody::Uranus).lon_arcsec
> accuracy_ceiling(&CelestialBody::Mercury).lon_arcsec
);
}
#[test]
fn size_budget_exceeds_current_artifact() {
let max_encoded_bytes = PACKAGED_BUDGETS.max_encoded_bytes;
assert!(max_encoded_bytes >= 10_000_000);
assert!(max_encoded_bytes <= 16_000_000);
}
#[test]
#[ignore = "maintainer helper: prints the thresholds summary to regenerate the golden"]
fn print_packaged_artifact_thresholds_summary() {
eprintln!("{}", packaged_artifact_thresholds_summary_for_report());
}
#[test]
fn packaged_artifact_thresholds_summary_matches_committed_golden() {
let report = packaged_artifact_thresholds_summary_for_report();
assert!(
report.contains("Packaged-artifact thresholds (10 bodies)"),
"thresholds report header drift: {report}"
);
assert!(
report.contains("size budget 12000000 bytes"),
"thresholds report size-budget line drift: {report}"
);
assert!(
report.contains("Moon:") && report.contains("/1\"=PASS"),
"Moon lon/ceiling PASS line drift: {report}"
);
for body_name in &[
"Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune",
"Pluto",
] {
assert!(
report.contains(body_name),
"{body_name} missing from thresholds report: {report}"
);
assert!(
report.contains(&format!("{body_name}:")) && {
report
.lines()
.find(|l| l.starts_with(body_name))
.map(|l| l.contains("PASS"))
.unwrap_or(false)
},
"{body_name} line does not contain PASS: {report}"
);
}
}
}