Skip to main content

scone_core/
profile.rs

1//! Two-tier profiles (spec §7 via memory/lessons.md L-11): the predecessor
2//! validated the shape — stable identity facts plus recent activity, one
3//! cheap call (their "~50ms profiles"; ours is a pair of indexed queries).
4
5use crate::auth::ScopedSpace;
6use crate::error::Result;
7use crate::{Engine, FactItem};
8
9#[derive(Debug)]
10pub struct Profile {
11    /// Durable identity: active facts, strongest first.
12    pub static_facts: Vec<FactItem>,
13    /// Recent activity: newest episode excerpts, newest first.
14    pub dynamic: Vec<String>,
15}
16
17impl Engine {
18    pub fn profile(&mut self, space: &ScopedSpace, limit: usize) -> Result<Profile> {
19        let limit = limit.clamp(1, 50) as i64;
20        let static_facts = {
21            let mut stmt = self.conn.prepare(
22                "SELECT f.id, en.canonical, f.predicate, f.object, f.confidence,
23                        f.valid_from, f.valid_until, f.status
24                 FROM facts f JOIN entities en ON en.id = f.subject_entity
25                 WHERE f.space_id = ?1 AND f.status = 'active'
26                 ORDER BY f.access_count DESC, f.confidence DESC, f.id
27                 LIMIT ?2",
28            )?;
29            let rows = stmt.query_map(rusqlite::params![space.id(), limit], |r| {
30                Ok(FactItem {
31                    fact_id: r.get(0)?,
32                    subject: r.get(1)?,
33                    predicate: r.get(2)?,
34                    object: r.get(3)?,
35                    confidence: r.get(4)?,
36                    valid_from: r.get(5)?,
37                    valid_until: r.get(6)?,
38                    status: r.get(7)?,
39                })
40            })?;
41            rows.collect::<std::result::Result<Vec<_>, _>>()?
42        };
43        let dynamic = {
44            let mut stmt = self.conn.prepare(
45                "SELECT substr(content, 1, 200) FROM episodes
46                 WHERE space_id = ?1 ORDER BY id DESC LIMIT ?2",
47            )?;
48            let rows = stmt.query_map(rusqlite::params![space.id(), limit], |r| {
49                r.get::<_, String>(0)
50            })?;
51            rows.collect::<std::result::Result<Vec<_>, _>>()?
52        };
53        Ok(Profile {
54            static_facts,
55            dynamic,
56        })
57    }
58}