toolhub_storage/
scores.rs1use rusqlite::Connection;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ScoreRow {
11 pub tool_id: String,
12 pub success_rate: Option<f64>,
13 pub sample_size: Option<i64>,
14 pub avg_cost_usd: Option<f64>,
15 pub median_duration_ms: Option<i64>,
16 pub score_updated_at: Option<String>,
17}
18
19pub fn list(conn: &Connection, tool_id: Option<&str>) -> anyhow::Result<Vec<ScoreRow>> {
20 let (sql, params) = match tool_id {
21 Some(id) => (
22 "SELECT tool_id, success_rate, sample_size, avg_cost_usd,
23 median_duration_ms, score_updated_at
24 FROM tool_scores WHERE tool_id = ?",
25 vec![id.to_string()],
26 ),
27 None => (
28 "SELECT tool_id, success_rate, sample_size, avg_cost_usd,
29 median_duration_ms, score_updated_at
30 FROM tool_scores ORDER BY tool_id",
31 vec![],
32 ),
33 };
34 let mut stmt = conn.prepare(sql)?;
35 let param_refs: Vec<&dyn rusqlite::ToSql> =
36 params.iter().map(|s| s as &dyn rusqlite::ToSql).collect();
37 let rows = stmt
38 .query_map(param_refs.as_slice(), |row| {
39 Ok(ScoreRow {
40 tool_id: row.get(0)?,
41 success_rate: row.get(1)?,
42 sample_size: row.get(2)?,
43 avg_cost_usd: row.get(3)?,
44 median_duration_ms: row.get(4)?,
45 score_updated_at: row.get(5)?,
46 })
47 })?
48 .collect::<Result<Vec<_>, _>>()?;
49 Ok(rows)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::open;
56 use rusqlite::params;
57
58 #[test]
59 fn list_empty_table_returns_empty() {
60 let dir = tempfile::tempdir().unwrap();
61 let conn = open(&dir.path().join("t.sqlite")).unwrap();
62 let rows = list(&conn, None).unwrap();
63 assert!(rows.is_empty());
64 let rows = list(&conn, Some("skill:nonexistent")).unwrap();
65 assert!(rows.is_empty());
66 }
67
68 #[test]
69 fn list_returns_inserted_rows_filtered_by_id() {
70 let dir = tempfile::tempdir().unwrap();
71 let conn = open(&dir.path().join("t.sqlite")).unwrap();
72 conn.execute(
74 "INSERT INTO tools (id, type, name, triggers, examples, requires,
75 enabled, added_at, last_seen_at)
76 VALUES (?, 'skill', 'X', '[]', '[]', '[]', 1,
77 '2026-05-03T00:00:00+00:00', '2026-05-03T00:00:00+00:00')",
78 params!["skill:x"],
79 )
80 .unwrap();
81 conn.execute(
82 "INSERT INTO tool_scores
83 (tool_id, success_rate, sample_size, avg_cost_usd,
84 median_duration_ms, score_updated_at)
85 VALUES (?, 0.8, 10, 0.04, 250, '2026-05-03T00:00:00+00:00')",
86 params!["skill:x"],
87 )
88 .unwrap();
89 let rows = list(&conn, Some("skill:x")).unwrap();
90 assert_eq!(rows.len(), 1);
91 assert_eq!(rows[0].sample_size, Some(10));
92 }
93}