Skip to main content

pleiades_data/
thresholds.rs

1//! Published per-body-class accuracy ceilings and size/latency budgets (the
2//! public contract). The hold-out gate (accuracy_baseline.rs) asserts measured
3//! <= ceiling; the tight golden drift test stays as the regression catcher.
4
5use pleiades_backend::CelestialBody;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum BodyClass {
9    Luminary,
10    InnerPlanet,
11    OuterPlanet,
12    Asteroid,
13}
14
15pub fn body_class(body: &CelestialBody) -> BodyClass {
16    match body {
17        CelestialBody::Sun | CelestialBody::Moon => BodyClass::Luminary,
18        CelestialBody::Mercury | CelestialBody::Venus | CelestialBody::Mars => {
19            BodyClass::InnerPlanet
20        }
21        CelestialBody::Jupiter
22        | CelestialBody::Saturn
23        | CelestialBody::Uranus
24        | CelestialBody::Neptune
25        | CelestialBody::Pluto => BodyClass::OuterPlanet,
26        _ => BodyClass::Asteroid,
27    }
28}
29
30#[derive(Clone, Copy, Debug)]
31pub struct AccuracyCeiling {
32    pub lon_arcsec: f64,
33    pub lat_arcsec: f64,
34    pub dist_km: f64,
35    pub lon_speed_arcsec_per_day: f64,
36    pub lat_speed_arcsec_per_day: f64,
37    pub radial_speed_au_per_day: f64,
38}
39
40/// Finalized speed and distance ceilings (Task 11, 2026-06-20).
41///
42/// Speed ceilings are set to round numbers ≥ ~10× the measured maxima from the
43/// committed packaged artifact baseline:
44///
45/// | class           | measured lon_speed | measured lat_speed | measured dist_km |
46/// |-----------------|--------------------|--------------------|------------------|
47/// | Luminary/Inner  | 0.0303 arcsec/day  | 0.0231 arcsec/day  | 0.654 km (Venus) |
48/// | Outer           | 0.0013 arcsec/day  | 0.0013 arcsec/day  | 54.828 km (Uranus)|
49///
50/// Chosen ceilings:
51/// - Luminary/Inner: lon/lat speed → 0.5 arcsec/day (~16× headroom on Moon's 0.0303);
52///   dist_km → 50 km (~76× headroom on Venus's 0.654 km).
53/// - Outer: lon/lat speed → 0.05 arcsec/day (~38× headroom on worst 0.0013);
54///   dist_km → 1_000 km (~18× headroom on Uranus's 54.828 km).
55/// - Radial speed: all bodies show < 1e-7 AU/day measured; ceiling 1e-4 AU/day gives
56///   >1000× headroom; kept tighter than the original placeholder (1e-3) to signal intent.
57pub fn accuracy_ceiling(body: &CelestialBody) -> AccuracyCeiling {
58    match body_class(body) {
59        BodyClass::Luminary | BodyClass::InnerPlanet => AccuracyCeiling {
60            lon_arcsec: 1.0,
61            lat_arcsec: 1.0,
62            dist_km: 50.0,
63            lon_speed_arcsec_per_day: 0.5,
64            lat_speed_arcsec_per_day: 0.5,
65            radial_speed_au_per_day: 1.0e-4,
66        },
67        BodyClass::OuterPlanet => AccuracyCeiling {
68            lon_arcsec: 5.0,
69            lat_arcsec: 5.0,
70            dist_km: 1_000.0,
71            lon_speed_arcsec_per_day: 0.05,
72            lat_speed_arcsec_per_day: 0.05,
73            radial_speed_au_per_day: 1.0e-4,
74        },
75        BodyClass::Asteroid => AccuracyCeiling {
76            lon_arcsec: 30.0,
77            lat_arcsec: 30.0,
78            dist_km: 5_000_000.0,
79            lon_speed_arcsec_per_day: 120.0,
80            lat_speed_arcsec_per_day: 120.0,
81            radial_speed_au_per_day: 1.0e-2,
82        },
83    }
84}
85
86#[derive(Clone, Copy, Debug)]
87pub struct ArtifactBudgets {
88    pub max_encoded_bytes: usize,
89    pub decode_latency_target_ms: f64,
90    pub single_lookup_target_ms: f64,
91    pub batch_throughput_target_per_s: f64,
92    pub chart_workload_target_ms: f64,
93}
94
95pub const PACKAGED_BUDGETS: ArtifactBudgets = ArtifactBudgets {
96    max_encoded_bytes: 12_000_000,   // ~10.0 MB measured + headroom
97    decode_latency_target_ms: 400.0, // ~260 ms measured
98    single_lookup_target_ms: 6.0,    // ~3.3 ms measured
99    batch_throughput_target_per_s: 1_000.0,
100    chart_workload_target_ms: 50.0,
101};
102
103/// Returns a deterministic summary comparing published accuracy ceilings against the live
104/// measured baseline (from the committed packaged artifact vs hold-out corpus), plus the
105/// size budget. One line per body class × channel showing measured/ceiling with PASS/FAIL.
106///
107/// The function reads [`accuracy_ceiling`] and [`PACKAGED_BUDGETS`] (the SSOT) plus the
108/// live baseline — deterministic, kernel-free.  PASS/FAIL is computed from measured vs
109/// ceiling, so the summary stays truthful if a value ever regresses.
110pub fn packaged_artifact_thresholds_summary_for_report() -> String {
111    let baseline = crate::accuracy_baseline::packaged_artifact_accuracy_baseline();
112    let mut lines = Vec::new();
113    for e in &baseline {
114        let c = accuracy_ceiling(&e.body);
115        let lon_status = if e.max_longitude_arcsec <= c.lon_arcsec {
116            "PASS"
117        } else {
118            "FAIL"
119        };
120        let lat_status = if e.max_latitude_arcsec <= c.lat_arcsec {
121            "PASS"
122        } else {
123            "FAIL"
124        };
125        let dist_status = if e.max_distance_km <= c.dist_km {
126            "PASS"
127        } else {
128            "FAIL"
129        };
130        let spd_status = if e.max_lon_speed_arcsec_per_day <= c.lon_speed_arcsec_per_day {
131            "PASS"
132        } else {
133            "FAIL"
134        };
135        lines.push(format!(
136            "{:?}: lon {:.4}\"/{}\"={} lat {:.4}\"/{}\"={} dist {:.1}/{} km={} lon_spd {:.4}/{} \"/d={}",
137            e.body,
138            e.max_longitude_arcsec, c.lon_arcsec, lon_status,
139            e.max_latitude_arcsec, c.lat_arcsec, lat_status,
140            e.max_distance_km, c.dist_km, dist_status,
141            e.max_lon_speed_arcsec_per_day, c.lon_speed_arcsec_per_day, spd_status,
142        ));
143    }
144    format!(
145        "Packaged-artifact thresholds ({} bodies); size budget {} bytes:\n{}",
146        baseline.len(),
147        PACKAGED_BUDGETS.max_encoded_bytes,
148        lines.join("\n")
149    )
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use pleiades_backend::CelestialBody;
156
157    #[test]
158    fn classes_map_bodies_correctly() {
159        assert_eq!(body_class(&CelestialBody::Sun), BodyClass::Luminary);
160        assert_eq!(body_class(&CelestialBody::Moon), BodyClass::Luminary);
161        assert_eq!(body_class(&CelestialBody::Mercury), BodyClass::InnerPlanet);
162        assert_eq!(body_class(&CelestialBody::Pluto), BodyClass::OuterPlanet);
163    }
164
165    #[test]
166    fn outer_planets_have_looser_longitude_ceiling_than_inner() {
167        assert!(
168            accuracy_ceiling(&CelestialBody::Uranus).lon_arcsec
169                > accuracy_ceiling(&CelestialBody::Mercury).lon_arcsec
170        );
171    }
172
173    #[test]
174    fn size_budget_exceeds_current_artifact() {
175        // current ~10 MB; budget has headroom but is finite. Bind to a local so
176        // the bounds check is a runtime assertion over the published budget
177        // rather than a const-folded tautology (clippy::assertions_on_constants).
178        let max_encoded_bytes = PACKAGED_BUDGETS.max_encoded_bytes;
179        assert!(max_encoded_bytes >= 10_000_000);
180        assert!(max_encoded_bytes <= 16_000_000);
181    }
182
183    #[test]
184    #[ignore = "maintainer helper: prints the thresholds summary to regenerate the golden"]
185    fn print_packaged_artifact_thresholds_summary() {
186        eprintln!("{}", packaged_artifact_thresholds_summary_for_report());
187    }
188
189    // Drift gate: committed thresholds summary must match the live baseline.
190    // Generated from actual output (2026-06-20, SP3 heliocentric-planet artifact).
191    // Anchors header, one representative body line, and the size-budget line.
192    // PASS/FAIL is computed — fails if any channel regresses past its ceiling.
193    #[test]
194    fn packaged_artifact_thresholds_summary_matches_committed_golden() {
195        let report = packaged_artifact_thresholds_summary_for_report();
196
197        // Header: 10 bodies and the size budget.
198        assert!(
199            report.contains("Packaged-artifact thresholds (10 bodies)"),
200            "thresholds report header drift: {report}"
201        );
202        assert!(
203            report.contains("size budget 12000000 bytes"),
204            "thresholds report size-budget line drift: {report}"
205        );
206
207        // Representative body line: Moon (Luminary class, ceiling 1.0"/1.0"/50 km/0.5"/d).
208        // All channels must PASS given the current sub-ceiling measurements.
209        assert!(
210            report.contains("Moon:") && report.contains("/1\"=PASS"),
211            "Moon lon/ceiling PASS line drift: {report}"
212        );
213
214        // Size budget line is in the header (checked above).
215        // Verify each body appears and shows PASS for longitude.
216        for body_name in &[
217            "Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune",
218            "Pluto",
219        ] {
220            assert!(
221                report.contains(body_name),
222                "{body_name} missing from thresholds report: {report}"
223            );
224            // Each body should show PASS (all measured values are well within ceilings).
225            assert!(
226                report.contains(&format!("{body_name}:")) && {
227                    // find this body's line and check it has PASS
228                    report
229                        .lines()
230                        .find(|l| l.starts_with(body_name))
231                        .map(|l| l.contains("PASS"))
232                        .unwrap_or(false)
233                },
234                "{body_name} line does not contain PASS: {report}"
235            );
236        }
237    }
238}