lean_ctx/core/
evidence.rs1use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
18pub struct Claim {
19 pub text: String,
21 pub confidence: f32,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub source_url: Option<String>,
26}
27
28impl Claim {
29 pub fn new(text: impl Into<String>, confidence: f32) -> Self {
31 Self {
32 text: text.into(),
33 confidence: confidence.clamp(0.0, 1.0),
34 source_url: None,
35 }
36 }
37
38 pub fn with_source(mut self, url: impl Into<String>) -> Self {
40 self.source_url = Some(url.into());
41 self
42 }
43
44 pub fn render(&self) -> String {
46 let mut s = format!("({:.2}) {}", self.confidence, self.text);
47 if let Some(src) = &self.source_url {
48 s.push_str(" — ");
49 s.push_str(src);
50 }
51 s
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn new_clamps_confidence() {
61 assert_eq!(Claim::new("x", 1.7).confidence, 1.0);
62 assert_eq!(Claim::new("x", -0.3).confidence, 0.0);
63 }
64
65 #[test]
66 fn render_with_and_without_source() {
67 assert_eq!(Claim::new("Fact", 0.5).render(), "(0.50) Fact");
68 assert_eq!(
69 Claim::new("Fact", 0.5)
70 .with_source("https://x.com")
71 .render(),
72 "(0.50) Fact — https://x.com"
73 );
74 }
75
76 #[test]
77 fn with_source_sets_field() {
78 let c = Claim::new("t", 0.9).with_source("https://s");
79 assert_eq!(c.source_url.as_deref(), Some("https://s"));
80 }
81
82 #[test]
83 fn serde_skips_empty_source() {
84 let json = serde_json::to_string(&Claim::new("t", 0.5)).unwrap();
85 assert!(!json.contains("source_url"));
86 assert!(json.contains("\"text\":\"t\""));
87 }
88}