Skip to main content

locus_core_rs/application/services/
context_query_service.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use chrono::{DateTime, Utc};
5
6use crate::domain::contracts::NodeStore;
7use crate::domain::models::{AvecState, ListNodesResult, PsiRange, RetrieveResult, SttpNode};
8
9pub struct ContextQueryService {
10    store: Arc<dyn NodeStore>,
11}
12
13impl ContextQueryService {
14    /// Create a context query service over the shared node store.
15    pub fn new(store: Arc<dyn NodeStore>) -> Self {
16        Self { store }
17    }
18
19    pub async fn get_context_async(
20        &self,
21        session_id: &str,
22        stability: f32,
23        friction: f32,
24        logic: f32,
25        autonomy: f32,
26        limit: usize,
27    ) -> RetrieveResult {
28        self.get_context_scoped_filtered_async(
29            Some(session_id),
30            stability,
31            friction,
32            logic,
33            autonomy,
34            None,
35            None,
36            None,
37            limit,
38        )
39        .await
40    }
41
42    /// Retrieve context across all sessions (global memory mode).
43    pub async fn get_context_global_async(
44        &self,
45        stability: f32,
46        friction: f32,
47        logic: f32,
48        autonomy: f32,
49        limit: usize,
50    ) -> RetrieveResult {
51        self.get_context_scoped_filtered_async(
52            None, stability, friction, logic, autonomy, None, None, None, limit,
53        )
54        .await
55    }
56
57    pub async fn get_context_global_filtered_async(
58        &self,
59        stability: f32,
60        friction: f32,
61        logic: f32,
62        autonomy: f32,
63        from_utc: Option<DateTime<Utc>>,
64        to_utc: Option<DateTime<Utc>>,
65        tiers: Option<&[String]>,
66        limit: usize,
67    ) -> RetrieveResult {
68        self.get_context_scoped_filtered_async(
69            None, stability, friction, logic, autonomy, from_utc, to_utc, tiers, limit,
70        )
71        .await
72    }
73
74    /// Retrieve context with an optional session scope.
75    pub async fn get_context_scoped_async(
76        &self,
77        session_id: Option<&str>,
78        stability: f32,
79        friction: f32,
80        logic: f32,
81        autonomy: f32,
82        limit: usize,
83    ) -> RetrieveResult {
84        self.get_context_scoped_filtered_async(
85            session_id, stability, friction, logic, autonomy, None, None, None, limit,
86        )
87        .await
88    }
89
90    /// Retrieve resonance-ranked context with optional scope and tier/date filters.
91    pub async fn get_context_scoped_filtered_async(
92        &self,
93        session_id: Option<&str>,
94        stability: f32,
95        friction: f32,
96        logic: f32,
97        autonomy: f32,
98        from_utc: Option<DateTime<Utc>>,
99        to_utc: Option<DateTime<Utc>>,
100        tiers: Option<&[String]>,
101        limit: usize,
102    ) -> RetrieveResult {
103        let current = AvecState {
104            stability,
105            friction,
106            logic,
107            autonomy,
108        };
109
110        let nodes = match session_id {
111            Some(session_id) => match self
112                .store
113                .get_by_resonance_async(session_id, current, from_utc, to_utc, tiers, limit)
114                .await
115            {
116                Ok(nodes) => nodes,
117                Err(_) => return empty_retrieve_result(),
118            },
119            None => match self
120                .store
121                .get_by_resonance_global_async(current, from_utc, to_utc, tiers, limit)
122                .await
123            {
124                Ok(nodes) => nodes,
125                Err(_) => return empty_retrieve_result(),
126            },
127        };
128
129        to_retrieve_result(nodes)
130    }
131
132    pub async fn get_context_hybrid_async(
133        &self,
134        session_id: &str,
135        stability: f32,
136        friction: f32,
137        logic: f32,
138        autonomy: f32,
139        query_embedding: Option<&[f32]>,
140        alpha: f32,
141        beta: f32,
142        limit: usize,
143    ) -> RetrieveResult {
144        self.get_context_hybrid_scoped_filtered_async(
145            Some(session_id),
146            stability,
147            friction,
148            logic,
149            autonomy,
150            None,
151            None,
152            None,
153            query_embedding,
154            alpha,
155            beta,
156            limit,
157        )
158        .await
159    }
160
161    /// Retrieve hybrid context across all sessions (global memory mode).
162    pub async fn get_context_hybrid_global_async(
163        &self,
164        stability: f32,
165        friction: f32,
166        logic: f32,
167        autonomy: f32,
168        query_embedding: Option<&[f32]>,
169        alpha: f32,
170        beta: f32,
171        limit: usize,
172    ) -> RetrieveResult {
173        self.get_context_hybrid_scoped_filtered_async(
174            None,
175            stability,
176            friction,
177            logic,
178            autonomy,
179            None,
180            None,
181            None,
182            query_embedding,
183            alpha,
184            beta,
185            limit,
186        )
187        .await
188    }
189
190    pub async fn get_context_hybrid_global_filtered_async(
191        &self,
192        stability: f32,
193        friction: f32,
194        logic: f32,
195        autonomy: f32,
196        from_utc: Option<DateTime<Utc>>,
197        to_utc: Option<DateTime<Utc>>,
198        tiers: Option<&[String]>,
199        query_embedding: Option<&[f32]>,
200        alpha: f32,
201        beta: f32,
202        limit: usize,
203    ) -> RetrieveResult {
204        self.get_context_hybrid_scoped_filtered_async(
205            None,
206            stability,
207            friction,
208            logic,
209            autonomy,
210            from_utc,
211            to_utc,
212            tiers,
213            query_embedding,
214            alpha,
215            beta,
216            limit,
217        )
218        .await
219    }
220
221    /// Retrieve hybrid context with an optional session scope.
222    pub async fn get_context_hybrid_scoped_async(
223        &self,
224        session_id: Option<&str>,
225        stability: f32,
226        friction: f32,
227        logic: f32,
228        autonomy: f32,
229        query_embedding: Option<&[f32]>,
230        alpha: f32,
231        beta: f32,
232        limit: usize,
233    ) -> RetrieveResult {
234        self.get_context_hybrid_scoped_filtered_async(
235            session_id,
236            stability,
237            friction,
238            logic,
239            autonomy,
240            None,
241            None,
242            None,
243            query_embedding,
244            alpha,
245            beta,
246            limit,
247        )
248        .await
249    }
250
251    /// Retrieve hybrid-ranked context with optional scope and tier/date filters.
252    pub async fn get_context_hybrid_scoped_filtered_async(
253        &self,
254        session_id: Option<&str>,
255        stability: f32,
256        friction: f32,
257        logic: f32,
258        autonomy: f32,
259        from_utc: Option<DateTime<Utc>>,
260        to_utc: Option<DateTime<Utc>>,
261        tiers: Option<&[String]>,
262        query_embedding: Option<&[f32]>,
263        alpha: f32,
264        beta: f32,
265        limit: usize,
266    ) -> RetrieveResult {
267        let current = AvecState {
268            stability,
269            friction,
270            logic,
271            autonomy,
272        };
273
274        let nodes = match session_id {
275            Some(session_id) => match self
276                .store
277                .get_by_hybrid_async(
278                    session_id,
279                    current,
280                    from_utc,
281                    to_utc,
282                    tiers,
283                    query_embedding,
284                    alpha,
285                    beta,
286                    limit,
287                )
288                .await
289            {
290                Ok(nodes) => nodes,
291                Err(_) => return empty_retrieve_result(),
292            },
293            None => match self
294                .store
295                .get_by_hybrid_global_async(
296                    current,
297                    from_utc,
298                    to_utc,
299                    tiers,
300                    query_embedding,
301                    alpha,
302                    beta,
303                    limit,
304                )
305                .await
306            {
307                Ok(nodes) => nodes,
308                Err(_) => return empty_retrieve_result(),
309            },
310        };
311
312        to_retrieve_result(nodes)
313    }
314
315    pub async fn list_nodes_async(
316        &self,
317        limit: usize,
318        session_id: Option<&str>,
319    ) -> Result<ListNodesResult> {
320        let capped_limit = limit.clamp(1, 200);
321        let nodes = self
322            .store
323            .list_nodes_async(capped_limit, session_id)
324            .await?;
325        Ok(ListNodesResult {
326            retrieved: nodes.len(),
327            nodes,
328        })
329    }
330}
331
332fn empty_retrieve_result() -> RetrieveResult {
333    RetrieveResult {
334        nodes: Vec::new(),
335        retrieved: 0,
336        psi_range: PsiRange {
337            min: 0.0,
338            max: 0.0,
339            average: 0.0,
340        },
341    }
342}
343
344fn to_retrieve_result(nodes: Vec<SttpNode>) -> RetrieveResult {
345    if nodes.is_empty() {
346        return empty_retrieve_result();
347    }
348
349    let mut min = f32::INFINITY;
350    let mut max = f32::NEG_INFINITY;
351    let mut sum = 0.0f32;
352
353    for node in &nodes {
354        min = min.min(node.psi);
355        max = max.max(node.psi);
356        sum += node.psi;
357    }
358
359    let retrieved = nodes.len();
360
361    RetrieveResult {
362        retrieved,
363        nodes,
364        psi_range: PsiRange {
365            min,
366            max,
367            average: sum / (retrieved as f32),
368        },
369    }
370}