Skip to main content

sciforge_hub/engine/query/
mod.rs

1//! Function catalog and introspection.
2//!
3//! The [`Catalog`] registers every available scientific function with its
4//! [`FunctionInfo`] metadata (name, domain, parameter list). Used by the
5//! CLI `list-functions` command and the HTTP introspection endpoint.
6
7#[cfg(feature = "cross_domain")]
8mod astrobiology;
9#[cfg(feature = "cross_domain")]
10mod astrochemistry;
11#[cfg(feature = "astronomy")]
12mod astronomy;
13#[cfg(feature = "cross_domain")]
14mod astrophysics;
15#[cfg(feature = "cross_domain")]
16mod atmospheric_chemistry;
17#[cfg(feature = "cross_domain")]
18mod atmospheric_physics;
19#[cfg(feature = "cross_domain")]
20mod biochemistry;
21#[cfg(feature = "biology")]
22mod biology;
23#[cfg(feature = "cross_domain")]
24mod biomathematics;
25#[cfg(feature = "cross_domain")]
26mod biophysics;
27#[cfg(feature = "chemistry")]
28mod chemistry;
29#[cfg(feature = "cross_domain")]
30mod geochemistry;
31#[cfg(feature = "geology")]
32mod geology;
33#[cfg(feature = "cross_domain")]
34mod geophysics;
35#[cfg(feature = "cross_domain")]
36mod mathematical_physics;
37#[cfg(feature = "maths")]
38mod maths;
39#[cfg(feature = "meteorology")]
40mod meteorology;
41#[cfg(feature = "physics")]
42mod physics;
43#[cfg(feature = "cross_domain")]
44mod planetary_geology;
45
46use crate::engine::experience::experiment::DomainType;
47
48/// Metadata describing a registered scientific function.
49#[derive(Debug, Clone)]
50pub struct FunctionInfo {
51    /// Scientific domain.
52    pub domain: DomainType,
53    /// Function name.
54    pub name: String,
55    /// Expected parameter names.
56    pub param_names: Vec<String>,
57    /// Human-readable description.
58    pub description: String,
59}
60
61/// Registry of all available scientific functions.
62pub struct Catalog {
63    entries: Vec<FunctionInfo>,
64}
65
66impl Catalog {
67    /// Creates a catalog populated with all domain registrations.
68    pub fn new() -> Self {
69        let chunks: Vec<Vec<FunctionInfo>> = vec![
70            #[cfg(feature = "astronomy")]
71            { let mut v = Vec::new(); astronomy::register(&mut v); v },
72            #[cfg(feature = "geology")]
73            { let mut v = Vec::new(); geology::register(&mut v); v },
74            #[cfg(feature = "meteorology")]
75            { let mut v = Vec::new(); meteorology::register(&mut v); v },
76            #[cfg(feature = "physics")]
77            { let mut v = Vec::new(); physics::register(&mut v); v },
78            #[cfg(feature = "chemistry")]
79            { let mut v = Vec::new(); chemistry::register(&mut v); v },
80            #[cfg(feature = "biology")]
81            { let mut v = Vec::new(); biology::register(&mut v); v },
82            #[cfg(feature = "maths")]
83            { let mut v = Vec::new(); maths::register(&mut v); v },
84            #[cfg(feature = "cross_domain")]
85            { let mut v = Vec::new(); astrochemistry::register(&mut v); v },
86            #[cfg(feature = "cross_domain")]
87            { let mut v = Vec::new(); geophysics::register(&mut v); v },
88            #[cfg(feature = "cross_domain")]
89            { let mut v = Vec::new(); astrophysics::register(&mut v); v },
90            #[cfg(feature = "cross_domain")]
91            { let mut v = Vec::new(); biochemistry::register(&mut v); v },
92            #[cfg(feature = "cross_domain")]
93            { let mut v = Vec::new(); biophysics::register(&mut v); v },
94            #[cfg(feature = "cross_domain")]
95            { let mut v = Vec::new(); geochemistry::register(&mut v); v },
96            #[cfg(feature = "cross_domain")]
97            { let mut v = Vec::new(); astrobiology::register(&mut v); v },
98            #[cfg(feature = "cross_domain")]
99            { let mut v = Vec::new(); atmospheric_chemistry::register(&mut v); v },
100            #[cfg(feature = "cross_domain")]
101            { let mut v = Vec::new(); atmospheric_physics::register(&mut v); v },
102            #[cfg(feature = "cross_domain")]
103            { let mut v = Vec::new(); planetary_geology::register(&mut v); v },
104            #[cfg(feature = "cross_domain")]
105            { let mut v = Vec::new(); biomathematics::register(&mut v); v },
106            #[cfg(feature = "cross_domain")]
107            { let mut v = Vec::new(); mathematical_physics::register(&mut v); v },
108        ];
109        let entries: Vec<FunctionInfo> = chunks.into_iter().flatten().collect();
110        Self { entries }
111    }
112
113    /// Total number of registered functions.
114    pub fn len(&self) -> usize {
115        self.entries.len()
116    }
117    /// Returns `true` if the catalog is empty.
118    pub fn is_empty(&self) -> bool {
119        self.entries.is_empty()
120    }
121
122    /// Returns all functions belonging to the given domain.
123    pub fn by_domain(&self, domain: &DomainType) -> Vec<&FunctionInfo> {
124        let d = format!("{:?}", domain);
125        self.entries
126            .iter()
127            .filter(|e| format!("{:?}", e.domain) == d)
128            .collect()
129    }
130
131    /// Searches functions by name substring (case-insensitive).
132    pub fn search(&self, pattern: &str) -> Vec<&FunctionInfo> {
133        let p = pattern.to_lowercase();
134        self.entries
135            .iter()
136            .filter(|e| e.name.to_lowercase().contains(&p))
137            .collect()
138    }
139
140    /// Looks up a function by exact name.
141    pub fn get(&self, name: &str) -> Option<&FunctionInfo> {
142        self.entries.iter().find(|e| e.name == name)
143    }
144
145    /// Returns all registered function names.
146    pub fn names(&self) -> Vec<&str> {
147        self.entries.iter().map(|e| e.name.as_str()).collect()
148    }
149
150    /// Returns (domain_name, function_count) pairs for every domain.
151    pub fn domain_summary(&self) -> Vec<(String, usize)> {
152        let domains = [
153            "Maths",
154            "Physics",
155            "Chemistry",
156            "Biology",
157            "Astronomy",
158            "Geology",
159            "Meteorology",
160            "Astrochemistry",
161            "Geophysics",
162            "Astrophysics",
163            "Biochemistry",
164            "Biophysics",
165            "Geochemistry",
166            "Astrobiology",
167            "AtmosphericChemistry",
168            "AtmosphericPhysics",
169            "PlanetaryGeology",
170            "Biomathematics",
171            "MathematicalPhysics",
172        ];
173        domains
174            .iter()
175            .map(|&d| {
176                let count = self
177                    .entries
178                    .iter()
179                    .filter(|e| format!("{:?}", e.domain) == d)
180                    .count();
181                (d.to_string(), count)
182            })
183            .collect()
184    }
185
186    /// Returns functions that accept the given parameter name.
187    pub fn by_param(&self, param: &str) -> Vec<&FunctionInfo> {
188        self.entries
189            .iter()
190            .filter(|e| e.param_names.iter().any(|p| p == param))
191            .collect()
192    }
193
194    /// Returns functions with exactly `n` parameters.
195    pub fn by_param_count(&self, n: usize) -> Vec<&FunctionInfo> {
196        self.entries
197            .iter()
198            .filter(|e| e.param_names.len() == n)
199            .collect()
200    }
201
202    /// Renders the catalog as a Markdown table.
203    pub fn to_markdown(&self) -> String {
204        let mut out = String::from("# SciForge Function Catalog\n\n");
205        let summary = self.domain_summary();
206        let total: usize = summary.iter().map(|(_, c)| c).sum();
207        out.push_str(&format!("**Total functions: {}**\n\n", total));
208        out.push_str("| Domain | Functions |\n|--------|----------|\n");
209        for (d, c) in &summary {
210            out.push_str(&format!("| {} | {} |\n", d, c));
211        }
212        out
213    }
214}
215
216impl Default for Catalog {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222#[cfg(any(
223    feature = "astronomy",
224    feature = "biology",
225    feature = "chemistry",
226    feature = "geology",
227    feature = "maths",
228    feature = "meteorology",
229    feature = "physics",
230    feature = "cross_domain",
231))]
232pub(super) fn reg(
233    entries: &mut Vec<FunctionInfo>,
234    domain: DomainType,
235    name: &str,
236    params: &[&str],
237    desc: &str,
238) {
239    entries.push(FunctionInfo {
240        domain,
241        name: name.into(),
242        param_names: params.iter().map(|s| s.to_string()).collect(),
243        description: desc.into(),
244    });
245}