ricecoder_completion/
history.rs1use crate::types::*;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::{Arc, RwLock};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct CompletionUsage {
12 pub label: String,
14 pub language: String,
16 pub timestamp: DateTime<Utc>,
18 pub frequency: u32,
20}
21
22impl CompletionUsage {
23 pub fn new(label: String, language: String) -> Self {
24 Self {
25 label,
26 language,
27 timestamp: Utc::now(),
28 frequency: 1,
29 }
30 }
31
32 pub fn record_usage(&mut self) {
34 self.frequency += 1;
35 self.timestamp = Utc::now();
36 }
37}
38
39pub struct CompletionHistory {
41 usage_cache: Arc<RwLock<HashMap<String, CompletionUsage>>>,
43 history_path: Option<PathBuf>,
45}
46
47impl CompletionHistory {
48 pub fn new() -> Self {
50 Self {
51 usage_cache: Arc::new(RwLock::new(HashMap::new())),
52 history_path: None,
53 }
54 }
55
56 pub fn with_path(history_path: PathBuf) -> Self {
58 Self {
59 usage_cache: Arc::new(RwLock::new(HashMap::new())),
60 history_path: Some(history_path),
61 }
62 }
63
64 pub fn record_usage(&self, label: String, language: String) -> CompletionResult<()> {
66 let mut cache = self.usage_cache.write().map_err(|_| {
67 CompletionError::InternalError(
68 "Failed to acquire write lock on history cache".to_string(),
69 )
70 })?;
71
72 let key = format!("{}:{}", language, label);
73 cache
74 .entry(key)
75 .and_modify(|usage| usage.record_usage())
76 .or_insert_with(|| CompletionUsage::new(label, language));
77
78 Ok(())
79 }
80
81 pub fn get_frequency_score(&self, label: &str, language: &str) -> CompletionResult<f32> {
83 let cache = self.usage_cache.read().map_err(|_| {
84 CompletionError::InternalError(
85 "Failed to acquire read lock on history cache".to_string(),
86 )
87 })?;
88
89 let key = format!("{}:{}", language, label);
90 if let Some(usage) = cache.get(&key) {
91 let score = (usage.frequency as f32 / 100.0).min(1.0);
94 Ok(score)
95 } else {
96 Ok(0.0)
97 }
98 }
99
100 pub fn get_recency_score(&self, label: &str, language: &str) -> CompletionResult<f32> {
103 let cache = self.usage_cache.read().map_err(|_| {
104 CompletionError::InternalError(
105 "Failed to acquire read lock on history cache".to_string(),
106 )
107 })?;
108
109 let key = format!("{}:{}", language, label);
110 if let Some(usage) = cache.get(&key) {
111 let now = Utc::now();
113 let duration = now.signed_duration_since(usage.timestamp);
114 let hours_ago = duration.num_hours() as f32;
115
116 let score = if hours_ago <= 0.0 {
118 1.0
119 } else if hours_ago >= 168.0 {
120 0.0
122 } else {
123 1.0 - (hours_ago / 168.0)
124 };
125
126 Ok(score)
127 } else {
128 Ok(0.0)
129 }
130 }
131
132 pub fn get_usage_score(
134 &self,
135 label: &str,
136 language: &str,
137 frequency_weight: f32,
138 recency_weight: f32,
139 ) -> CompletionResult<f32> {
140 let frequency_score = self.get_frequency_score(label, language)?;
141 let recency_score = self.get_recency_score(label, language)?;
142
143 let total_weight = frequency_weight + recency_weight;
144 if total_weight == 0.0 {
145 return Ok(0.0);
146 }
147
148 let combined_score =
149 (frequency_score * frequency_weight + recency_score * recency_weight) / total_weight;
150 Ok(combined_score)
151 }
152
153 pub fn load(&self) -> CompletionResult<()> {
155 if let Some(path) = &self.history_path {
156 if path.exists() {
157 let content = std::fs::read_to_string(path).map_err(CompletionError::IoError)?;
158 let usages: Vec<CompletionUsage> =
159 serde_json::from_str(&content).map_err(CompletionError::SerializationError)?;
160
161 let mut cache = self.usage_cache.write().map_err(|_| {
162 CompletionError::InternalError(
163 "Failed to acquire write lock on history cache".to_string(),
164 )
165 })?;
166
167 for usage in usages {
168 let key = format!("{}:{}", usage.language, usage.label);
169 cache.insert(key, usage);
170 }
171 }
172 }
173
174 Ok(())
175 }
176
177 pub fn save(&self) -> CompletionResult<()> {
179 if let Some(path) = &self.history_path {
180 let cache = self.usage_cache.read().map_err(|_| {
181 CompletionError::InternalError(
182 "Failed to acquire read lock on history cache".to_string(),
183 )
184 })?;
185
186 let usages: Vec<CompletionUsage> = cache.values().cloned().collect();
187 let content = serde_json::to_string_pretty(&usages)
188 .map_err(CompletionError::SerializationError)?;
189
190 std::fs::write(path, content).map_err(CompletionError::IoError)?;
191 }
192
193 Ok(())
194 }
195
196 pub fn clear(&self) -> CompletionResult<()> {
198 let mut cache = self.usage_cache.write().map_err(|_| {
199 CompletionError::InternalError(
200 "Failed to acquire write lock on history cache".to_string(),
201 )
202 })?;
203
204 cache.clear();
205 Ok(())
206 }
207
208 pub fn get_all_usages(&self) -> CompletionResult<Vec<CompletionUsage>> {
210 let cache = self.usage_cache.read().map_err(|_| {
211 CompletionError::InternalError(
212 "Failed to acquire read lock on history cache".to_string(),
213 )
214 })?;
215
216 Ok(cache.values().cloned().collect())
217 }
218}
219
220impl Default for CompletionHistory {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_record_usage() {
232 let history = CompletionHistory::new();
233 assert!(history
234 .record_usage("test".to_string(), "rust".to_string())
235 .is_ok());
236 }
237
238 #[test]
239 fn test_frequency_score_new_completion() {
240 let history = CompletionHistory::new();
241 let score = history.get_frequency_score("test", "rust").unwrap();
242 assert_eq!(score, 0.0);
243 }
244
245 #[test]
246 fn test_frequency_score_after_usage() {
247 let history = CompletionHistory::new();
248 history
249 .record_usage("test".to_string(), "rust".to_string())
250 .unwrap();
251 let score = history.get_frequency_score("test", "rust").unwrap();
252 assert!(score > 0.0);
253 }
254
255 #[test]
256 fn test_frequency_score_multiple_usages() {
257 let history = CompletionHistory::new();
258 for _ in 0..5 {
259 history
260 .record_usage("test".to_string(), "rust".to_string())
261 .unwrap();
262 }
263 let score = history.get_frequency_score("test", "rust").unwrap();
264 assert!(score > 0.0);
265 }
266
267 #[test]
268 fn test_recency_score_new_completion() {
269 let history = CompletionHistory::new();
270 let score = history.get_recency_score("test", "rust").unwrap();
271 assert_eq!(score, 0.0);
272 }
273
274 #[test]
275 fn test_recency_score_after_usage() {
276 let history = CompletionHistory::new();
277 history
278 .record_usage("test".to_string(), "rust".to_string())
279 .unwrap();
280 let score = history.get_recency_score("test", "rust").unwrap();
281 assert!(score > 0.9); }
283
284 #[test]
285 fn test_combined_usage_score() {
286 let history = CompletionHistory::new();
287 history
288 .record_usage("test".to_string(), "rust".to_string())
289 .unwrap();
290 let score = history.get_usage_score("test", "rust", 0.3, 0.2).unwrap();
291 assert!(score > 0.0);
292 }
293
294 #[test]
295 fn test_clear_history() {
296 let history = CompletionHistory::new();
297 history
298 .record_usage("test".to_string(), "rust".to_string())
299 .unwrap();
300 history.clear().unwrap();
301 let score = history.get_frequency_score("test", "rust").unwrap();
302 assert_eq!(score, 0.0);
303 }
304
305 #[test]
306 fn test_get_all_usages() {
307 let history = CompletionHistory::new();
308 history
309 .record_usage("test1".to_string(), "rust".to_string())
310 .unwrap();
311 history
312 .record_usage("test2".to_string(), "rust".to_string())
313 .unwrap();
314 let usages = history.get_all_usages().unwrap();
315 assert_eq!(usages.len(), 2);
316 }
317
318 #[test]
319 fn test_save_and_load() {
320 let temp_dir = tempfile::tempdir().unwrap();
321 let history_path = temp_dir.path().join("history.json");
322
323 let history = CompletionHistory::with_path(history_path.clone());
324 history
325 .record_usage("test".to_string(), "rust".to_string())
326 .unwrap();
327 assert!(history.save().is_ok());
328
329 let history2 = CompletionHistory::with_path(history_path);
330 assert!(history2.load().is_ok());
331 let usages = history2.get_all_usages().unwrap();
332 assert_eq!(usages.len(), 1);
333 assert_eq!(usages[0].label, "test");
334 }
335}