forge_workspace/store/
speaking_results.rs1use redb::{ReadableTable, TableDefinition};
9
10use crate::bench::SpeakingBenchResult;
11
12use super::Db;
13
14const RESULTS: TableDefinition<&str, &[u8]> = TableDefinition::new("speaking_bench_results");
16
17pub fn key(result: &SpeakingBenchResult) -> String {
19 format!("{}|{}", result.file, result.corpus.sha256)
20}
21
22pub fn results(db: &Db) -> anyhow::Result<Vec<SpeakingBenchResult>> {
24 let txn = db.database().begin_read()?;
25 let table = match txn.open_table(RESULTS) {
26 Ok(table) => table,
27 Err(redb::TableError::TableDoesNotExist(_)) => return Ok(Vec::new()),
28 Err(error) => return Err(error.into()),
29 };
30 let mut rows = Vec::new();
31 for entry in table.iter()? {
32 let (_, value) = entry?;
33 rows.push(serde_json::from_slice(value.value())?);
34 }
35 rows.sort_by(|a: &SpeakingBenchResult, b: &SpeakingBenchResult| b.at.cmp(&a.at));
36 Ok(rows)
37}
38
39pub fn save(db: &Db, result: &SpeakingBenchResult) -> anyhow::Result<()> {
41 let txn = db.database().begin_write()?;
42 {
43 let mut table = txn.open_table(RESULTS)?;
44 table.insert(key(result).as_str(), serde_json::to_vec(result)?.as_slice())?;
45 }
46 txn.commit()?;
47 Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use forge_dictate::bench::CorpusId;
54 use forge_tts::bench::TtsBenchResult;
55
56 fn result(file: &str, sha: &str, at: &str) -> SpeakingBenchResult {
57 SpeakingBenchResult {
58 file: file.to_owned(),
59 metrics: TtsBenchResult {
60 clips: 3,
61 round_trip_wer: 0.125,
62 first_audio_ms: 3_400,
63 realtime_factor: 0.3,
64 peak_rss_bytes: 417 << 20,
65 },
66 at: at.to_owned(),
67 corpus: CorpusId { clips: 3, audio_seconds: 9, sha256: sha.to_owned() },
68 }
69 }
70
71 fn db(dir: &std::path::Path) -> Db {
72 Db::open(&dir.join("speaking.redb")).expect("a store")
73 }
74
75 #[test]
76 fn a_result_round_trips_and_a_rerun_over_the_same_corpus_replaces_its_row() {
77 let dir = tempfile::tempdir().unwrap();
78 let store = db(dir.path());
79
80 save(&store, &result("qwen.gguf", "aaaa", "2026-11-01T10:00:00Z")).unwrap();
81 save(&store, &result("qwen.gguf", "bbbb", "2026-11-01T11:00:00Z")).unwrap();
82 let rows = results(&store).unwrap();
83 assert_eq!(rows.len(), 2, "two corpora are two results");
84 assert_eq!(rows[0].at, "2026-11-01T11:00:00Z", "newest first");
85 assert_eq!(rows[0].metrics.round_trip_wer, 0.125, "the figures cross whole");
86
87 save(&store, &result("qwen.gguf", "aaaa", "2026-11-01T12:00:00Z")).unwrap();
88 let rows = results(&store).unwrap();
89 assert_eq!(rows.len(), 2, "a re-run replaces its own row");
90 assert_eq!(rows.iter().filter(|row| row.corpus.sha256 == "aaaa").count(), 1);
91 }
92}