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(
42 &self,
43 namespace: &str,
44 strategy: &str,
45 input: usize,
46 inferred: usize,
47 skipped: usize,
48 samples: Vec<(String, String, String)>,
49 ) {
50 let record = InferenceRecord {
51 timestamp: Utc::now(),
52 namespace: namespace.to_string(),
53 strategy: strategy.to_string(),
54 input_triples: input,
55 inferred_triples: inferred,
56 duplicates_skipped: skipped,
57 sample_inferences: samples.into_iter().take(10).collect(),
58 };
59
60 let mut records = self.records.write().unwrap();
61 let ns_records = records.entry(namespace.to_string()).or_default();
62
63 ns_records.push(record);
64
65 if ns_records.len() > self.max_records {
67 ns_records.remove(0);
68 }
69 }
70
71 pub fn get_history(&self, namespace: &str) -> Vec<InferenceRecord> {
73 let records = self.records.read().unwrap();
74 records.get(namespace).cloned().unwrap_or_default()
75 }
76
77 pub fn get_last(&self, namespace: &str) -> Option<InferenceRecord> {
79 let records = self.records.read().unwrap();
80 records.get(namespace).and_then(|r| r.last().cloned())
81 }
82
83 pub fn export_json(&self) -> String {
85 let records = self.records.read().unwrap();
86 serde_json::to_string_pretty(&*records).unwrap_or_default()
87 }
88}