Skip to main content

nms_query/
stats.rs

1//! Aggregate statistics queries.
2
3use std::collections::HashMap;
4
5use nms_core::biome::Biome;
6use nms_graph::GalaxyModel;
7
8/// What statistics to compute.
9#[derive(Debug, Clone, Default)]
10pub struct StatsQuery {
11    /// Show biome distribution.
12    pub biomes: bool,
13    /// Show discovery counts by type.
14    pub discoveries: bool,
15}
16
17/// Aggregate statistics result.
18#[derive(Debug, Clone)]
19pub struct StatsResult {
20    /// Total systems in model.
21    pub system_count: usize,
22    /// Total planets in model.
23    pub planet_count: usize,
24    /// Total bases.
25    pub base_count: usize,
26    /// Biome distribution: biome -> count of planets.
27    pub biome_counts: HashMap<Biome, usize>,
28    /// Planets with no biome assigned.
29    pub unknown_biome_count: usize,
30    /// Named vs unnamed planets.
31    pub named_planet_count: usize,
32    /// Named vs unnamed systems.
33    pub named_system_count: usize,
34    /// Infested planet count.
35    pub infested_count: usize,
36}
37
38/// Execute a stats query.
39pub fn execute_stats(model: &GalaxyModel, _query: &StatsQuery) -> StatsResult {
40    let mut biome_counts: HashMap<Biome, usize> = HashMap::new();
41    let mut unknown_biome_count = 0;
42    let mut named_planet_count = 0;
43    let mut infested_count = 0;
44
45    for planet in model.planets.values() {
46        match planet.biome {
47            Some(biome) => *biome_counts.entry(biome).or_default() += 1,
48            None => unknown_biome_count += 1,
49        }
50        if planet.name.is_some() {
51            named_planet_count += 1;
52        }
53        if planet.infested {
54            infested_count += 1;
55        }
56    }
57
58    let named_system_count = model.systems.values().filter(|s| s.name.is_some()).count();
59
60    StatsResult {
61        system_count: model.system_count(),
62        planet_count: model.planet_count(),
63        base_count: model.base_count(),
64        biome_counts,
65        unknown_biome_count,
66        named_planet_count,
67        named_system_count,
68        infested_count,
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    fn test_model() -> GalaxyModel {
77        let json = r#"{
78            "Version": 4720, "Platform": "Mac|Final", "ActiveContext": "Main",
79            "CommonStateData": {"SaveName": "Test", "TotalPlayTime": 100},
80            "BaseContext": {"GameMode": 1, "PlayerStateData": {"UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 1, "PlanetIndex": 0}}, "Units": 0, "Nanites": 0, "Specials": 0, "PersistentPlayerBases": []}},
81            "ExpeditionContext": {"GameMode": 6, "PlayerStateData": {"UniverseAddress": {"RealityIndex": 0, "GalacticAddress": {"VoxelX": 0, "VoxelY": 0, "VoxelZ": 0, "SolarSystemIndex": 0, "PlanetIndex": 0}}, "Units": 0, "Nanites": 0, "Specials": 0, "PersistentPlayerBases": []}},
82            "DiscoveryManagerData": {"DiscoveryData-v1": {"ReserveStore": 0, "ReserveManaged": 0, "Store": {"Record": [
83                {"DD": {"UA": "0x001000000064", "DT": "SolarSystem", "VP": []}, "DM": {}, "OWS": {"LID":"","UID":"1","USN":"A","PTK":"ST","TS":0}, "FL": {"U": 1}},
84                {"DD": {"UA": "0x101000000064", "DT": "Planet", "VP": ["0xAB", 0]}, "DM": {}, "OWS": {"LID":"","UID":"1","USN":"A","PTK":"ST","TS":0}, "FL": {"U": 1}},
85                {"DD": {"UA": "0x201000000064", "DT": "Planet", "VP": ["0xCD", 0]}, "DM": {}, "OWS": {"LID":"","UID":"1","USN":"A","PTK":"ST","TS":0}, "FL": {"U": 1}}
86            ]}}}
87        }"#;
88        nms_save::parse_save(json.as_bytes())
89            .map(|save| GalaxyModel::from_save(&save))
90            .unwrap()
91    }
92
93    #[test]
94    fn test_stats_basic_counts() {
95        let model = test_model();
96        let query = StatsQuery {
97            biomes: true,
98            discoveries: true,
99        };
100        let stats = execute_stats(&model, &query);
101        assert_eq!(stats.system_count, 1);
102        assert_eq!(stats.planet_count, 2);
103    }
104
105    #[test]
106    fn test_stats_biome_counts_sum() {
107        let model = test_model();
108        let stats = execute_stats(&model, &StatsQuery::default());
109        let total: usize = stats.biome_counts.values().sum::<usize>() + stats.unknown_biome_count;
110        assert_eq!(total, stats.planet_count);
111    }
112}