1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::sync::RwLock;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct InferenceRecord {
9 pub timestamp: DateTime<Utc>,
10 pub namespace: String,
11 pub strategy: String,
12 pub input_triples: usize,
13 pub inferred_triples: usize,
14 pub duplicates_skipped: usize,
15 pub sample_inferences: Vec<(String, String, String)>,
16}
17
18pub struct InferenceAudit {
20 records: RwLock<HashMap<String, Vec<InferenceRecord>>>,
22 max_records: usize,
24}
25
26impl Default for InferenceAudit {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl InferenceAudit {
33 pub fn new() -> Self {
34 Self {
35 records: RwLock::new(HashMap::new()),
36 max_records: 100,
37 }
38 }
39
40 pub fn log(&self, namespace: &str, strategy: &str, input: usize, inferred: usize, skipped: usize, samples: Vec<(String, String, String)>) {
42 let record = InferenceRecord {
43 timestamp: Utc::now(),
44 namespace: namespace.to_string(),
45 strategy: strategy.to_string(),
46 input_triples: input,
47 inferred_triples: inferred,
48 duplicates_skipped: skipped,
49 sample_inferences: samples.into_iter().take(10).collect(),
50 };
51
52 let mut records = self.records.write().unwrap();
53 let ns_records = records.entry(namespace.to_string()).or_insert_with(Vec::new);
54
55 ns_records.push(record);
56
57 if ns_records.len() > self.max_records {
59 ns_records.remove(0);
60 }
61 }
62
63 pub fn get_history(&self, namespace: &str) -> Vec<InferenceRecord> {
65 let records = self.records.read().unwrap();
66 records.get(namespace).cloned().unwrap_or_default()
67 }
68
69 pub fn get_last(&self, namespace: &str) -> Option<InferenceRecord> {
71 let records = self.records.read().unwrap();
72 records.get(namespace).and_then(|r| r.last().cloned())
73 }
74
75 pub fn export_json(&self) -> String {
77 let records = self.records.read().unwrap();
78 serde_json::to_string_pretty(&*records).unwrap_or_default()
79 }
80}