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 async fn save_experiment(
17 &self,
18 tenant_id: &str,
19 experiment: &GenomeExperiment,
20 ) -> Result<(), EvolutionStoreError>;
21
22 async fn load_recent(
24 &self,
25 tenant_id: &str,
26 limit: usize,
27 ) -> Result<Vec<GenomeExperiment>, EvolutionStoreError>;
28
29 async fn save_rule(
31 &self,
32 tenant_id: &str,
33 rule: &OptimizationRule,
34 ) -> Result<(), EvolutionStoreError>;
35
36 async fn load_rules(
38 &self,
39 tenant_id: &str,
40 ) -> Result<Vec<OptimizationRule>, EvolutionStoreError>;
41}
42
43#[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 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(), 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(), };
199 rules.push(rules_obj);
200 }
201
202 Ok(rules)
203 }
204}