vex_persist/
evolution_store.rs

1use async_trait::async_trait;
2use thiserror::Error;
3use vex_core::{GenomeExperiment, OptimizationRule};
4
5#[derive(Debug, Error)]
6pub enum EvolutionStoreError {
7    #[error("Database error: {0}")]
8    Database(#[from] sqlx::Error),
9    #[error("Serialization error: {0}")]
10    Serialization(#[from] serde_json::Error),
11}
12
13#[async_trait]
14pub trait EvolutionStore: Send + Sync {
15    /// Save an experiment to persistent storage
16    async fn save_experiment(
17        &self,
18        tenant_id: &str,
19        experiment: &GenomeExperiment,
20    ) -> Result<(), EvolutionStoreError>;
21
22    /// Load recent experiments
23    async fn load_recent(
24        &self,
25        tenant_id: &str,
26        limit: usize,
27    ) -> Result<Vec<GenomeExperiment>, EvolutionStoreError>;
28
29    /// Save an optimization rule (semantic lesson)
30    async fn save_rule(
31        &self,
32        tenant_id: &str,
33        rule: &OptimizationRule,
34    ) -> Result<(), EvolutionStoreError>;
35
36    /// Load available optimization rules
37    async fn load_rules(
38        &self,
39        tenant_id: &str,
40    ) -> Result<Vec<OptimizationRule>, EvolutionStoreError>;
41}
42
43/// SQL implementation of EvolutionStore
44#[cfg(feature = "sqlite")]
45pub struct SqliteEvolutionStore {
46    pool: sqlx::SqlitePool,
47}
48
49#[cfg(feature = "sqlite")]
50impl SqliteEvolutionStore {
51    pub fn new(pool: sqlx::SqlitePool) -> Self {
52        Self { pool }
53    }
54}
55
56#[cfg(feature = "sqlite")]
57#[async_trait]
58impl EvolutionStore for SqliteEvolutionStore {
59    async fn save_experiment(
60        &self,
61        tenant_id: &str,
62        experiment: &GenomeExperiment,
63    ) -> Result<(), EvolutionStoreError> {
64        let traits_json = serde_json::to_string(&experiment.traits)?;
65        let trait_names_json = serde_json::to_string(&experiment.trait_names)?;
66        let fitness_json = serde_json::to_string(&experiment.fitness_scores)?;
67        let task_summary = &experiment.task_summary;
68        let overall_fitness = experiment.overall_fitness;
69
70        sqlx::query(
71            r#"
72            INSERT INTO evolution_experiments (
73                id, tenant_id, traits, trait_names, fitness_scores, task_summary, overall_fitness, created_at
74            ) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
75            "#,
76        )
77        .bind(experiment.id.to_string())
78        .bind(tenant_id)
79        .bind(traits_json)
80        .bind(trait_names_json)
81        .bind(fitness_json)
82        .bind(task_summary)
83        .bind(overall_fitness)
84        .execute(&self.pool)
85        .await?;
86
87        Ok(())
88    }
89
90    async fn load_recent(
91        &self,
92        tenant_id: &str,
93        limit: usize,
94    ) -> Result<Vec<GenomeExperiment>, EvolutionStoreError> {
95        use sqlx::Row;
96
97        let rows = sqlx::query(
98            r#"
99            SELECT 
100                id, traits, trait_names, fitness_scores, task_summary, overall_fitness, created_at
101            FROM evolution_experiments
102            WHERE tenant_id = ?
103            ORDER BY created_at DESC
104            LIMIT ?
105            "#,
106        )
107        .bind(tenant_id)
108        .bind(limit as i64)
109        .fetch_all(&self.pool)
110        .await?;
111
112        let mut experiments = Vec::new();
113        for row in rows {
114            let traits_str: String = row.try_get("traits")?;
115            let trait_names_str: String = row.try_get("trait_names")?;
116            let fitness_scores_str: String = row.try_get("fitness_scores")?;
117            let id_str: String = row.try_get("id")?;
118
119            let traits = serde_json::from_str(&traits_str)?;
120            let trait_names = serde_json::from_str(&trait_names_str)?;
121            let fitness_scores = serde_json::from_str(&fitness_scores_str)?;
122
123            // We reconstruct the experiment
124            let exp = GenomeExperiment {
125                id: uuid::Uuid::parse_str(&id_str).unwrap_or_default(),
126                traits,
127                trait_names,
128                fitness_scores,
129                task_summary: row.try_get("task_summary")?,
130                overall_fitness: row.try_get("overall_fitness")?,
131                timestamp: chrono::Utc::now(), // Use current time as parsing SQL datetime can be tricky without types
132                successful: row.try_get::<f64, _>("overall_fitness")? > 0.6,
133            };
134            experiments.push(exp);
135        }
136
137        Ok(experiments)
138    }
139
140    async fn save_rule(
141        &self,
142        tenant_id: &str,
143        rule: &OptimizationRule,
144    ) -> Result<(), EvolutionStoreError> {
145        let traits_json = serde_json::to_string(&rule.affected_traits)?;
146
147        sqlx::query(
148            r#"
149            INSERT INTO optimization_rules (
150                id, tenant_id, rule_description, affected_traits, confidence, source_count, created_at
151            ) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
152            "#,
153        )
154        .bind(rule.id.to_string())
155        .bind(tenant_id)
156        .bind(&rule.rule_description)
157        .bind(traits_json)
158        .bind(rule.confidence)
159        .bind(rule.source_experiments_count as i64)
160        .execute(&self.pool)
161        .await?;
162
163        Ok(())
164    }
165
166    async fn load_rules(
167        &self,
168        tenant_id: &str,
169    ) -> Result<Vec<OptimizationRule>, EvolutionStoreError> {
170        use sqlx::Row;
171
172        let rows = sqlx::query(
173            r#"
174            SELECT 
175                id, rule_description, affected_traits, confidence, source_count, created_at
176            FROM optimization_rules
177            WHERE tenant_id = ?
178            ORDER BY confidence DESC, created_at DESC
179            LIMIT 50
180            "#,
181        )
182        .bind(tenant_id)
183        .fetch_all(&self.pool)
184        .await?;
185
186        let mut rules = Vec::new();
187        for row in rows {
188            let id_str: String = row.try_get("id")?;
189            let traits_str: String = row.try_get("affected_traits")?;
190
191            let rules_obj = OptimizationRule {
192                id: uuid::Uuid::parse_str(&id_str).unwrap_or_default(),
193                rule_description: row.try_get("rule_description")?,
194                affected_traits: serde_json::from_str(&traits_str)?,
195                confidence: row.try_get("confidence")?,
196                source_experiments_count: row.try_get::<i64, _>("source_count")? as usize,
197                created_at: chrono::Utc::now(), // Simplified
198            };
199            rules.push(rules_obj);
200        }
201
202        Ok(rules)
203    }
204}