1use serde::{Deserialize, Serialize};
11use std::time::Instant;
12use tracing::{debug, error, info, warn};
13
14use super::{extract_json_from_completion, serialize_for_log, ModeCore};
15use crate::config::Config;
16use crate::error::{AppResult, ToolError};
17use crate::langbase::{LangbaseClient, Message, PipeRequest};
18use crate::prompts::{BAYESIAN_UPDATER_PROMPT, EVIDENCE_ASSESSOR_PROMPT};
19use crate::storage::{
20 EvidenceAssessment as StoredEvidence, Invocation, ProbabilityUpdate as StoredProbability,
21 SqliteStorage, Storage,
22};
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct EvidenceParams {
31 pub claim: String,
33 pub evidence: Vec<EvidenceInput>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub session_id: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub context: Option<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct EvidenceInput {
46 pub content: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub source: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub source_type: Option<SourceType>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub date: Option<String>,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum SourceType {
63 Primary,
65 Secondary,
67 Anecdotal,
69 Expert,
71 Statistical,
73}
74
75impl std::fmt::Display for SourceType {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 SourceType::Primary => write!(f, "primary"),
79 SourceType::Secondary => write!(f, "secondary"),
80 SourceType::Anecdotal => write!(f, "anecdotal"),
81 SourceType::Expert => write!(f, "expert"),
82 SourceType::Statistical => write!(f, "statistical"),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ProbabilisticParams {
90 pub hypothesis: String,
92 pub prior: f64,
94 pub evidence: Vec<BayesianEvidence>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub session_id: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct BayesianEvidence {
104 pub description: String,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub likelihood_if_true: Option<f64>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub likelihood_if_false: Option<f64>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
120struct EvidenceResponse {
121 overall_support: OverallSupport,
122 evidence_analysis: Vec<EvidenceAnalysisItem>,
123 #[serde(default)]
124 chain_analysis: Option<ChainAnalysis>,
125 #[serde(default)]
126 contradictions: Vec<Contradiction>,
127 #[serde(default)]
128 gaps: Vec<EvidenceGap>,
129 #[serde(default)]
130 recommendations: Vec<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134struct OverallSupport {
135 level: String,
136 confidence: f64,
137 explanation: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141struct EvidenceAnalysisItem {
142 #[serde(default)]
143 evidence_id: String,
144 content_summary: String,
145 relevance: RelevanceScore,
146 credibility: CredibilityScore,
147 weight: f64,
148 supports_claim: bool,
149 #[serde(default)]
150 inferential_distance: i32,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154struct RelevanceScore {
155 score: f64,
156 #[serde(default)]
157 relevance_type: String,
158 explanation: String,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162struct CredibilityScore {
163 score: f64,
164 #[serde(default)]
165 factors: CredibilityFactors,
166 #[serde(default)]
167 concerns: Vec<String>,
168}
169
170#[derive(Debug, Clone, Default, Serialize, Deserialize)]
171struct CredibilityFactors {
172 #[serde(default)]
173 source_reliability: f64,
174 #[serde(default)]
175 methodology: f64,
176 #[serde(default)]
177 recency: f64,
178 #[serde(default)]
179 corroboration: f64,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183struct ChainAnalysis {
184 #[serde(default)]
185 primary_chain: Vec<String>,
186 #[serde(default)]
187 weak_links: Vec<WeakLink>,
188 #[serde(default)]
189 redundancy: Vec<String>,
190 #[serde(default)]
191 synergies: Vec<Synergy>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195struct WeakLink {
196 from: String,
197 to: String,
198 weakness: String,
199 impact: f64,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203struct Synergy {
204 evidence_ids: Vec<String>,
205 combined_strength: f64,
206 explanation: String,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210struct Contradiction {
211 evidence_a: String,
212 evidence_b: String,
213 nature: String,
214 resolution: String,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218struct EvidenceGap {
219 gap: String,
220 importance: f64,
221 suggested_evidence: String,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226struct BayesianResponse {
227 prior: f64,
228 posterior: f64,
229 #[serde(default)]
230 confidence_interval: Option<ConfidenceInterval>,
231 update_steps: Vec<UpdateStepResponse>,
232 #[serde(default)]
233 uncertainty_analysis: Option<UncertaintyAnalysis>,
234 #[serde(default)]
235 sensitivity: Option<SensitivityAnalysis>,
236 interpretation: Interpretation,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
240struct ConfidenceInterval {
241 lower: f64,
242 upper: f64,
243 level: f64,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247struct UpdateStepResponse {
248 evidence: String,
249 prior_before: f64,
250 likelihood_ratio: f64,
251 posterior_after: f64,
252 #[serde(default)]
253 explanation: String,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
257struct UncertaintyAnalysis {
258 entropy_before: f64,
259 entropy_after: f64,
260 information_gained: f64,
261 remaining_uncertainty: String,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
265struct SensitivityAnalysis {
266 most_influential_evidence: String,
267 robustness: f64,
268 #[serde(default)]
269 critical_assumptions: Vec<String>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273struct Interpretation {
274 verbal_probability: String,
275 recommendation: String,
276 #[serde(default)]
277 caveats: Vec<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct EvidenceResult {
287 pub assessment_id: String,
289 pub session_id: String,
291 pub claim: String,
293 pub overall_support: SupportLevel,
295 pub evidence_analyses: Vec<EvidenceAnalysis>,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub chain_analysis: Option<InferentialChain>,
300 pub contradictions: Vec<EvidenceContradiction>,
302 pub gaps: Vec<Gap>,
304 pub recommendations: Vec<String>,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct SupportLevel {
311 pub level: String,
313 pub confidence: f64,
315 pub explanation: String,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct EvidenceAnalysis {
322 pub evidence_id: String,
324 pub content_summary: String,
326 pub relevance: f64,
328 pub credibility: f64,
330 pub weight: f64,
332 pub supports_claim: bool,
334 pub notes: String,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct InferentialChain {
341 pub primary_chain: Vec<String>,
343 pub weak_links: Vec<ChainWeakness>,
345 pub redundant_evidence: Vec<String>,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct ChainWeakness {
352 pub from: String,
354 pub to: String,
356 pub weakness: String,
358 pub impact: f64,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct EvidenceContradiction {
365 pub evidence_a: String,
367 pub evidence_b: String,
369 pub nature: String,
371 pub resolution: String,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct Gap {
378 pub gap: String,
380 pub importance: f64,
382 pub suggested_evidence: String,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ProbabilisticResult {
389 pub update_id: String,
391 pub session_id: String,
393 pub hypothesis: String,
395 pub prior: f64,
397 pub posterior: f64,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub confidence_interval: Option<ProbabilityInterval>,
402 pub update_steps: Vec<BayesianUpdateStep>,
404 pub uncertainty: UncertaintyMetrics,
406 pub interpretation: ProbabilityInterpretation,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ProbabilityInterval {
413 pub lower: f64,
415 pub upper: f64,
417 pub level: f64,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct BayesianUpdateStep {
424 pub evidence: String,
426 pub prior: f64,
428 pub posterior: f64,
430 pub likelihood_ratio: f64,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct UncertaintyMetrics {
437 pub entropy_before: f64,
439 pub entropy_after: f64,
441 pub information_gained: f64,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct ProbabilityInterpretation {
448 pub verbal: String,
450 pub recommendation: String,
452 pub caveats: Vec<String>,
454}
455
456#[derive(Clone)]
462pub struct EvidenceMode {
463 core: ModeCore,
465 decision_framework_pipe: String,
467}
468
469impl EvidenceMode {
470 pub fn new(storage: SqliteStorage, langbase: LangbaseClient, config: &Config) -> Self {
472 Self {
473 core: ModeCore::new(storage, langbase),
474 decision_framework_pipe: config
475 .pipes
476 .evidence
477 .as_ref()
478 .and_then(|e| e.pipe.clone())
479 .unwrap_or_else(|| "decision-framework-v1".to_string()),
480 }
481 }
482
483 pub async fn assess_evidence(&self, params: EvidenceParams) -> AppResult<EvidenceResult> {
485 let start = Instant::now();
486
487 self.validate_evidence_params(¶ms)?;
489
490 let session = self
492 .core
493 .storage()
494 .get_or_create_session(¶ms.session_id, "evidence")
495 .await?;
496 debug!(session_id = %session.id, "Processing evidence assessment");
497
498 let messages = self.build_evidence_messages(¶ms);
500
501 let mut invocation = Invocation::new(
503 "reasoning.assess_evidence",
504 serialize_for_log(¶ms, "reasoning.assess_evidence input"),
505 )
506 .with_session(&session.id)
507 .with_pipe(&self.decision_framework_pipe);
508
509 let request = PipeRequest::new(&self.decision_framework_pipe, messages);
511 let response = match self.core.langbase().call_pipe(request).await {
512 Ok(resp) => resp,
513 Err(e) => {
514 let latency = start.elapsed().as_millis() as i64;
515 invocation = invocation.failure(e.to_string(), latency);
516 self.core.storage().log_invocation(&invocation).await?;
517 return Err(e.into());
518 }
519 };
520
521 let evidence_response = self.parse_evidence_response(&response.completion)?;
523
524 let assessment_id = uuid::Uuid::new_v4().to_string();
526
527 let result = EvidenceResult {
529 assessment_id: assessment_id.clone(),
530 session_id: session.id.clone(),
531 claim: params.claim.clone(),
532 overall_support: SupportLevel {
533 level: evidence_response.overall_support.level,
534 confidence: evidence_response.overall_support.confidence,
535 explanation: evidence_response.overall_support.explanation,
536 },
537 evidence_analyses: evidence_response
538 .evidence_analysis
539 .into_iter()
540 .map(|ea| EvidenceAnalysis {
541 evidence_id: ea.evidence_id,
542 content_summary: ea.content_summary,
543 relevance: ea.relevance.score,
544 credibility: ea.credibility.score,
545 weight: ea.weight,
546 supports_claim: ea.supports_claim,
547 notes: ea.relevance.explanation,
548 })
549 .collect(),
550 chain_analysis: evidence_response.chain_analysis.map(|ca| InferentialChain {
551 primary_chain: ca.primary_chain,
552 weak_links: ca
553 .weak_links
554 .into_iter()
555 .map(|wl| ChainWeakness {
556 from: wl.from,
557 to: wl.to,
558 weakness: wl.weakness,
559 impact: wl.impact,
560 })
561 .collect(),
562 redundant_evidence: ca.redundancy,
563 }),
564 contradictions: evidence_response
565 .contradictions
566 .into_iter()
567 .map(|c| EvidenceContradiction {
568 evidence_a: c.evidence_a,
569 evidence_b: c.evidence_b,
570 nature: c.nature,
571 resolution: c.resolution,
572 })
573 .collect(),
574 gaps: evidence_response
575 .gaps
576 .into_iter()
577 .map(|g| Gap {
578 gap: g.gap,
579 importance: g.importance,
580 suggested_evidence: g.suggested_evidence,
581 })
582 .collect(),
583 recommendations: evidence_response.recommendations,
584 };
585
586 let mut stored_evidence = StoredEvidence::new(
588 &session.id,
589 ¶ms.claim,
590 serde_json::to_value(¶ms.evidence).unwrap_or_default(),
591 serde_json::to_value(&result.overall_support).unwrap_or_default(),
592 serde_json::to_value(&result.evidence_analyses).unwrap_or_default(),
593 )
594 .with_contradictions(serde_json::to_value(&result.contradictions).unwrap_or_default())
595 .with_gaps(serde_json::to_value(&result.gaps).unwrap_or_default())
596 .with_recommendations(serde_json::to_value(&result.recommendations).unwrap_or_default());
597
598 if let Some(chain) = &result.chain_analysis {
599 stored_evidence = stored_evidence
600 .with_chain_analysis(serde_json::to_value(chain).unwrap_or_default());
601 }
602
603 self.core
604 .storage()
605 .create_evidence_assessment(&stored_evidence)
606 .await
607 .map_err(|e| {
608 error!(
609 error = %e,
610 assessment_id = %assessment_id,
611 "Failed to persist evidence assessment - operation failed"
612 );
613 e
614 })?;
615
616 let latency = start.elapsed().as_millis() as i64;
618 invocation = invocation.success(
619 serialize_for_log(&result, "reasoning.assess_evidence output"),
620 latency,
621 );
622 self.core.storage().log_invocation(&invocation).await?;
623
624 info!(
625 assessment_id = %assessment_id,
626 support_level = %result.overall_support.level,
627 latency_ms = latency,
628 "Evidence assessment completed"
629 );
630
631 Ok(result)
632 }
633
634 pub async fn update_probability(
636 &self,
637 params: ProbabilisticParams,
638 ) -> AppResult<ProbabilisticResult> {
639 let start = Instant::now();
640
641 self.validate_probabilistic_params(¶ms)?;
643
644 let session = self
646 .core
647 .storage()
648 .get_or_create_session(¶ms.session_id, "evidence")
649 .await?;
650 debug!(session_id = %session.id, "Processing probabilistic update");
651
652 let messages = self.build_probabilistic_messages(¶ms);
654
655 let mut invocation = Invocation::new(
657 "reasoning.probabilistic",
658 serialize_for_log(¶ms, "reasoning.probabilistic input"),
659 )
660 .with_session(&session.id)
661 .with_pipe(&self.decision_framework_pipe);
662
663 let request = PipeRequest::new(&self.decision_framework_pipe, messages);
665 let response = match self.core.langbase().call_pipe(request).await {
666 Ok(resp) => resp,
667 Err(e) => {
668 let latency = start.elapsed().as_millis() as i64;
669 invocation = invocation.failure(e.to_string(), latency);
670 self.core.storage().log_invocation(&invocation).await?;
671
672 error!(error = %e, "Langbase call failed - propagating error");
673 return Err(ToolError::PipeUnavailable {
674 pipe: self.decision_framework_pipe.clone(),
675 reason: e.to_string(),
676 }
677 .into());
678 }
679 };
680
681 let bayesian_response = self.parse_bayesian_response(&response.completion, ¶ms)?;
683
684 let update_id = uuid::Uuid::new_v4().to_string();
686
687 let result = ProbabilisticResult {
689 update_id: update_id.clone(),
690 session_id: session.id.clone(),
691 hypothesis: params.hypothesis.clone(),
692 prior: params.prior,
693 posterior: bayesian_response.posterior,
694 confidence_interval: bayesian_response.confidence_interval.map(|ci| {
695 ProbabilityInterval {
696 lower: ci.lower,
697 upper: ci.upper,
698 level: ci.level,
699 }
700 }),
701 update_steps: bayesian_response
702 .update_steps
703 .into_iter()
704 .map(|us| BayesianUpdateStep {
705 evidence: us.evidence,
706 prior: us.prior_before,
707 posterior: us.posterior_after,
708 likelihood_ratio: us.likelihood_ratio,
709 })
710 .collect(),
711 uncertainty: bayesian_response
712 .uncertainty_analysis
713 .map(|ua| UncertaintyMetrics {
714 entropy_before: ua.entropy_before,
715 entropy_after: ua.entropy_after,
716 information_gained: ua.information_gained,
717 })
718 .unwrap_or_else(|| {
719 let entropy_before = self.calculate_entropy(params.prior);
720 let entropy_after = self.calculate_entropy(bayesian_response.posterior);
721 UncertaintyMetrics {
722 entropy_before,
723 entropy_after,
724 information_gained: entropy_before - entropy_after,
725 }
726 }),
727 interpretation: ProbabilityInterpretation {
728 verbal: bayesian_response.interpretation.verbal_probability,
729 recommendation: bayesian_response.interpretation.recommendation,
730 caveats: bayesian_response.interpretation.caveats,
731 },
732 };
733
734 let mut stored_probability = StoredProbability::new(
736 &session.id,
737 ¶ms.hypothesis,
738 result.prior,
739 result.posterior,
740 serde_json::to_value(&result.update_steps).unwrap_or_default(),
741 serde_json::to_value(&result.interpretation).unwrap_or_default(),
742 )
743 .with_uncertainty(serde_json::to_value(&result.uncertainty).unwrap_or_default());
744
745 if let Some(ci) = &result.confidence_interval {
746 stored_probability = stored_probability.with_confidence_interval(
747 Some(ci.lower),
748 Some(ci.upper),
749 Some(ci.level),
750 );
751 }
752
753 self.core
754 .storage()
755 .create_probability_update(&stored_probability)
756 .await
757 .map_err(|e| {
758 error!(
759 error = %e,
760 update_id = %update_id,
761 "Failed to persist probability update - operation failed"
762 );
763 e
764 })?;
765
766 let latency = start.elapsed().as_millis() as i64;
768 invocation = invocation.success(
769 serialize_for_log(&result, "reasoning.probabilistic output"),
770 latency,
771 );
772 self.core.storage().log_invocation(&invocation).await?;
773
774 info!(
775 update_id = %update_id,
776 prior = %params.prior,
777 posterior = %result.posterior,
778 latency_ms = latency,
779 "Probabilistic update completed"
780 );
781
782 Ok(result)
783 }
784
785 fn validate_evidence_params(&self, params: &EvidenceParams) -> AppResult<()> {
790 if params.claim.trim().is_empty() {
791 return Err(ToolError::Validation {
792 field: "claim".to_string(),
793 reason: "Claim cannot be empty".to_string(),
794 }
795 .into());
796 }
797
798 if params.evidence.is_empty() {
799 return Err(ToolError::Validation {
800 field: "evidence".to_string(),
801 reason: "At least one evidence item is required".to_string(),
802 }
803 .into());
804 }
805
806 Ok(())
807 }
808
809 fn validate_probabilistic_params(&self, params: &ProbabilisticParams) -> AppResult<()> {
810 if params.hypothesis.trim().is_empty() {
811 return Err(ToolError::Validation {
812 field: "hypothesis".to_string(),
813 reason: "Hypothesis cannot be empty".to_string(),
814 }
815 .into());
816 }
817
818 if !(0.0..=1.0).contains(¶ms.prior) {
819 return Err(ToolError::Validation {
820 field: "prior".to_string(),
821 reason: "Prior probability must be between 0 and 1".to_string(),
822 }
823 .into());
824 }
825
826 Ok(())
827 }
828
829 fn build_evidence_messages(&self, params: &EvidenceParams) -> Vec<Message> {
830 let mut messages = Vec::new();
831 messages.push(Message::system(EVIDENCE_ASSESSOR_PROMPT.to_string()));
832
833 let evidence_json = serde_json::to_string_pretty(¶ms.evidence).unwrap_or_default();
835 let user_content = if let Some(ref context) = params.context {
836 format!(
837 "Assess the following evidence for this claim:\n\nClaim: {}\n\nContext: {}\n\nEvidence:\n{}",
838 params.claim, context, evidence_json
839 )
840 } else {
841 format!(
842 "Assess the following evidence for this claim:\n\nClaim: {}\n\nEvidence:\n{}",
843 params.claim, evidence_json
844 )
845 };
846
847 messages.push(Message::user(user_content));
848 messages
849 }
850
851 fn build_probabilistic_messages(&self, params: &ProbabilisticParams) -> Vec<Message> {
852 let mut messages = Vec::new();
853 messages.push(Message::system(BAYESIAN_UPDATER_PROMPT.to_string()));
854
855 let evidence_json = serde_json::to_string_pretty(¶ms.evidence).unwrap_or_default();
857 let user_content = format!(
858 "Perform Bayesian probability updates for this hypothesis:\n\nHypothesis: {}\n\nPrior probability: {}\n\nEvidence:\n{}",
859 params.hypothesis, params.prior, evidence_json
860 );
861
862 messages.push(Message::user(user_content));
863 messages
864 }
865
866 fn parse_evidence_response(&self, completion: &str) -> AppResult<EvidenceResponse> {
867 let json_str = extract_json_from_completion(completion).map_err(|e| {
868 warn!(
869 error = %e,
870 completion_preview = %completion.chars().take(200).collect::<String>(),
871 "Failed to extract JSON from evidence response"
872 );
873 ToolError::Reasoning {
874 message: format!("Evidence response extraction failed: {}", e),
875 }
876 })?;
877
878 serde_json::from_str::<EvidenceResponse>(json_str).map_err(|e| {
879 ToolError::Reasoning {
880 message: format!("Failed to parse evidence response: {}", e),
881 }
882 .into()
883 })
884 }
885
886 fn parse_bayesian_response(
887 &self,
888 completion: &str,
889 _params: &ProbabilisticParams,
890 ) -> AppResult<BayesianResponse> {
891 let json_str = extract_json_from_completion(completion).map_err(|e| {
892 warn!(
893 error = %e,
894 completion_preview = %completion.chars().take(200).collect::<String>(),
895 "Failed to extract JSON from Bayesian response"
896 );
897 ToolError::Reasoning {
898 message: format!("Bayesian response extraction failed: {}", e),
899 }
900 })?;
901
902 serde_json::from_str::<BayesianResponse>(json_str).map_err(|e| {
904 let preview: String = json_str.chars().take(200).collect();
905 ToolError::ParseFailed {
906 mode: "evidence.probabilistic".to_string(),
907 message: format!("JSON parse error: {} | Response preview: {}", e, preview),
908 }
909 .into()
910 })
911 }
912
913 fn calculate_entropy(&self, p: f64) -> f64 {
915 if p <= 0.0 || p >= 1.0 {
916 return 0.0;
917 }
918 let q = 1.0 - p;
919 -(p * p.log2() + q * q.log2())
920 }
921}
922
923impl EvidenceParams {
928 pub fn new(claim: impl Into<String>) -> Self {
930 Self {
931 claim: claim.into(),
932 evidence: Vec::new(),
933 session_id: None,
934 context: None,
935 }
936 }
937
938 pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
940 self.session_id = Some(session_id.into());
941 self
942 }
943
944 pub fn with_evidence(mut self, content: impl Into<String>) -> Self {
946 self.evidence.push(EvidenceInput {
947 content: content.into(),
948 source: None,
949 source_type: None,
950 date: None,
951 });
952 self
953 }
954
955 pub fn with_sourced_evidence(
957 mut self,
958 content: impl Into<String>,
959 source: impl Into<String>,
960 source_type: SourceType,
961 ) -> Self {
962 self.evidence.push(EvidenceInput {
963 content: content.into(),
964 source: Some(source.into()),
965 source_type: Some(source_type),
966 date: None,
967 });
968 self
969 }
970
971 pub fn with_context(mut self, context: impl Into<String>) -> Self {
973 self.context = Some(context.into());
974 self
975 }
976}
977
978impl ProbabilisticParams {
979 pub fn new(hypothesis: impl Into<String>, prior: f64) -> Self {
981 Self {
982 hypothesis: hypothesis.into(),
983 prior,
984 evidence: Vec::new(),
985 session_id: None,
986 }
987 }
988
989 pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
991 self.session_id = Some(session_id.into());
992 self
993 }
994
995 pub fn with_evidence(
997 mut self,
998 description: impl Into<String>,
999 likelihood_true: f64,
1000 likelihood_false: f64,
1001 ) -> Self {
1002 self.evidence.push(BayesianEvidence {
1003 description: description.into(),
1004 likelihood_if_true: Some(likelihood_true),
1005 likelihood_if_false: Some(likelihood_false),
1006 });
1007 self
1008 }
1009}
1010
1011#[cfg(test)]
1016mod tests {
1017 use super::*;
1018
1019 #[test]
1024 fn test_evidence_params_new() {
1025 let params = EvidenceParams::new("Test claim");
1026 assert_eq!(params.claim, "Test claim");
1027 assert!(params.evidence.is_empty());
1028 assert!(params.session_id.is_none());
1029 assert!(params.context.is_none());
1030 }
1031
1032 #[test]
1033 fn test_evidence_params_with_session() {
1034 let params = EvidenceParams::new("Claim").with_session("sess-123");
1035 assert_eq!(params.session_id, Some("sess-123".to_string()));
1036 }
1037
1038 #[test]
1039 fn test_evidence_params_with_evidence() {
1040 let params = EvidenceParams::new("Claim")
1041 .with_evidence("Evidence 1")
1042 .with_evidence("Evidence 2");
1043 assert_eq!(params.evidence.len(), 2);
1044 assert_eq!(params.evidence[0].content, "Evidence 1");
1045 }
1046
1047 #[test]
1048 fn test_evidence_params_with_sourced_evidence() {
1049 let params = EvidenceParams::new("Claim").with_sourced_evidence(
1050 "Statistical data",
1051 "Research paper",
1052 SourceType::Primary,
1053 );
1054 assert_eq!(params.evidence.len(), 1);
1055 assert_eq!(
1056 params.evidence[0].source,
1057 Some("Research paper".to_string())
1058 );
1059 assert_eq!(params.evidence[0].source_type, Some(SourceType::Primary));
1060 }
1061
1062 #[test]
1063 fn test_evidence_params_with_context() {
1064 let params = EvidenceParams::new("Claim").with_context("Additional context");
1065 assert_eq!(params.context, Some("Additional context".to_string()));
1066 }
1067
1068 #[test]
1073 fn test_probabilistic_params_new() {
1074 let params = ProbabilisticParams::new("Hypothesis", 0.5);
1075 assert_eq!(params.hypothesis, "Hypothesis");
1076 assert_eq!(params.prior, 0.5);
1077 assert!(params.evidence.is_empty());
1078 assert!(params.session_id.is_none());
1079 }
1080
1081 #[test]
1082 fn test_probabilistic_params_with_session() {
1083 let params = ProbabilisticParams::new("H", 0.5).with_session("sess-456");
1084 assert_eq!(params.session_id, Some("sess-456".to_string()));
1085 }
1086
1087 #[test]
1088 fn test_probabilistic_params_with_evidence() {
1089 let params = ProbabilisticParams::new("H", 0.5).with_evidence("Evidence", 0.8, 0.2);
1090 assert_eq!(params.evidence.len(), 1);
1091 assert_eq!(params.evidence[0].description, "Evidence");
1092 assert_eq!(params.evidence[0].likelihood_if_true, Some(0.8));
1093 assert_eq!(params.evidence[0].likelihood_if_false, Some(0.2));
1094 }
1095
1096 #[test]
1101 fn test_source_type_display() {
1102 assert_eq!(format!("{}", SourceType::Primary), "primary");
1103 assert_eq!(format!("{}", SourceType::Secondary), "secondary");
1104 assert_eq!(format!("{}", SourceType::Anecdotal), "anecdotal");
1105 assert_eq!(format!("{}", SourceType::Expert), "expert");
1106 assert_eq!(format!("{}", SourceType::Statistical), "statistical");
1107 }
1108
1109 #[test]
1110 fn test_source_type_serialize() {
1111 let json = serde_json::to_string(&SourceType::Primary).unwrap();
1112 assert_eq!(json, "\"primary\"");
1113 }
1114
1115 #[test]
1116 fn test_source_type_deserialize() {
1117 let st: SourceType = serde_json::from_str("\"expert\"").unwrap();
1118 assert_eq!(st, SourceType::Expert);
1119 }
1120
1121 #[test]
1126 fn test_support_level_serialize() {
1127 let sl = SupportLevel {
1128 level: "strong".to_string(),
1129 confidence: 0.85,
1130 explanation: "Well supported".to_string(),
1131 };
1132 let json = serde_json::to_string(&sl).unwrap();
1133 assert!(json.contains("strong"));
1134 assert!(json.contains("0.85"));
1135 }
1136
1137 #[test]
1138 fn test_evidence_analysis_serialize() {
1139 let ea = EvidenceAnalysis {
1140 evidence_id: "e1".to_string(),
1141 content_summary: "Summary".to_string(),
1142 relevance: 0.9,
1143 credibility: 0.8,
1144 weight: 0.72,
1145 supports_claim: true,
1146 notes: "Good evidence".to_string(),
1147 };
1148 let json = serde_json::to_string(&ea).unwrap();
1149 assert!(json.contains("e1"));
1150 assert!(json.contains("0.72"));
1151 }
1152
1153 #[test]
1154 fn test_bayesian_update_step_serialize() {
1155 let step = BayesianUpdateStep {
1156 evidence: "Test".to_string(),
1157 prior: 0.5,
1158 posterior: 0.7,
1159 likelihood_ratio: 2.33,
1160 };
1161 let json = serde_json::to_string(&step).unwrap();
1162 assert!(json.contains("Test"));
1163 assert!(json.contains("2.33"));
1164 }
1165
1166 #[test]
1167 fn test_uncertainty_metrics_serialize() {
1168 let um = UncertaintyMetrics {
1169 entropy_before: 1.0,
1170 entropy_after: 0.88,
1171 information_gained: 0.12,
1172 };
1173 let json = serde_json::to_string(&um).unwrap();
1174 assert!(json.contains("0.88"));
1175 assert!(json.contains("0.12"));
1176 }
1177
1178 #[test]
1179 fn test_probability_interpretation_serialize() {
1180 let pi = ProbabilityInterpretation {
1181 verbal: "likely".to_string(),
1182 recommendation: "Proceed".to_string(),
1183 caveats: vec!["Limited data".to_string()],
1184 };
1185 let json = serde_json::to_string(&pi).unwrap();
1186 assert!(json.contains("likely"));
1187 assert!(json.contains("Limited data"));
1188 }
1189
1190 #[test]
1195 fn test_entropy_calculation() {
1196 fn entropy(p: f64) -> f64 {
1202 if p <= 0.0 || p >= 1.0 {
1203 0.0
1204 } else {
1205 -(p * p.log2() + (1.0 - p) * (1.0 - p).log2())
1206 }
1207 }
1208
1209 assert_eq!(entropy(0.0), 0.0);
1211 assert_eq!(entropy(1.0), 0.0);
1212
1213 let entropy_half = entropy(0.5);
1215 assert!((entropy_half - 1.0).abs() < 0.0001); }
1217
1218 #[test]
1223 fn test_evidence_input_serialize() {
1224 let ei = EvidenceInput {
1225 content: "Data shows X".to_string(),
1226 source: Some("Journal".to_string()),
1227 source_type: Some(SourceType::Statistical),
1228 date: Some("2023-01-15".to_string()),
1229 };
1230 let json = serde_json::to_string(&ei).unwrap();
1231 assert!(json.contains("Data shows X"));
1232 assert!(json.contains("Journal"));
1233 assert!(json.contains("statistical"));
1234 }
1235
1236 #[test]
1237 fn test_evidence_input_deserialize() {
1238 let json = r#"{"content":"Test content","source":"Source","source_type":"primary","date":"2023-01-01"}"#;
1239 let ei: EvidenceInput = serde_json::from_str(json).unwrap();
1240 assert_eq!(ei.content, "Test content");
1241 assert_eq!(ei.source, Some("Source".to_string()));
1242 assert_eq!(ei.source_type, Some(SourceType::Primary));
1243 }
1244
1245 #[test]
1246 fn test_evidence_input_minimal() {
1247 let ei = EvidenceInput {
1248 content: "Minimal".to_string(),
1249 source: None,
1250 source_type: None,
1251 date: None,
1252 };
1253 let json = serde_json::to_string(&ei).unwrap();
1254 assert!(json.contains("Minimal"));
1255 assert!(!json.contains("source"));
1257 }
1258
1259 #[test]
1264 fn test_evidence_params_serialize() {
1265 let params = EvidenceParams::new("Claim")
1266 .with_evidence("E1")
1267 .with_session("s1")
1268 .with_context("ctx");
1269 let json = serde_json::to_string(¶ms).unwrap();
1270 assert!(json.contains("Claim"));
1271 assert!(json.contains("E1"));
1272 assert!(json.contains("s1"));
1273 assert!(json.contains("ctx"));
1274 }
1275
1276 #[test]
1277 fn test_evidence_params_deserialize() {
1278 let json = r#"{"claim":"Test","evidence":[{"content":"E1"}]}"#;
1279 let params: EvidenceParams = serde_json::from_str(json).unwrap();
1280 assert_eq!(params.claim, "Test");
1281 assert_eq!(params.evidence.len(), 1);
1282 assert_eq!(params.evidence[0].content, "E1");
1283 }
1284
1285 #[test]
1286 fn test_evidence_params_round_trip() {
1287 let original = EvidenceParams::new("Round trip test")
1288 .with_evidence("Evidence 1")
1289 .with_evidence("Evidence 2")
1290 .with_session("session-123");
1291
1292 let json = serde_json::to_string(&original).unwrap();
1293 let deserialized: EvidenceParams = serde_json::from_str(&json).unwrap();
1294
1295 assert_eq!(original.claim, deserialized.claim);
1296 assert_eq!(original.evidence.len(), deserialized.evidence.len());
1297 assert_eq!(original.session_id, deserialized.session_id);
1298 }
1299
1300 #[test]
1305 fn test_bayesian_evidence_serialize() {
1306 let be = BayesianEvidence {
1307 description: "Test evidence".to_string(),
1308 likelihood_if_true: Some(0.8),
1309 likelihood_if_false: Some(0.2),
1310 };
1311 let json = serde_json::to_string(&be).unwrap();
1312 assert!(json.contains("Test evidence"));
1313 assert!(json.contains("0.8"));
1314 assert!(json.contains("0.2"));
1315 }
1316
1317 #[test]
1318 fn test_bayesian_evidence_deserialize() {
1319 let json = r#"{"description":"E","likelihood_if_true":0.9,"likelihood_if_false":0.1}"#;
1320 let be: BayesianEvidence = serde_json::from_str(json).unwrap();
1321 assert_eq!(be.description, "E");
1322 assert_eq!(be.likelihood_if_true, Some(0.9));
1323 assert_eq!(be.likelihood_if_false, Some(0.1));
1324 }
1325
1326 #[test]
1327 fn test_bayesian_evidence_optional_likelihoods() {
1328 let be = BayesianEvidence {
1329 description: "Incomplete".to_string(),
1330 likelihood_if_true: None,
1331 likelihood_if_false: None,
1332 };
1333 let json = serde_json::to_string(&be).unwrap();
1334 assert!(json.contains("Incomplete"));
1335 }
1336
1337 #[test]
1338 fn test_bayesian_evidence_round_trip() {
1339 let original = BayesianEvidence {
1340 description: "Round trip evidence".to_string(),
1341 likelihood_if_true: Some(0.75),
1342 likelihood_if_false: Some(0.25),
1343 };
1344 let json = serde_json::to_string(&original).unwrap();
1345 let deserialized: BayesianEvidence = serde_json::from_str(&json).unwrap();
1346
1347 assert_eq!(original.description, deserialized.description);
1348 assert_eq!(original.likelihood_if_true, deserialized.likelihood_if_true);
1349 assert_eq!(
1350 original.likelihood_if_false,
1351 deserialized.likelihood_if_false
1352 );
1353 }
1354
1355 #[test]
1360 fn test_probabilistic_params_serialize() {
1361 let params = ProbabilisticParams::new("H", 0.6)
1362 .with_evidence("E1", 0.8, 0.2)
1363 .with_session("s2");
1364 let json = serde_json::to_string(¶ms).unwrap();
1365 assert!(json.contains("\"H\""));
1366 assert!(json.contains("0.6"));
1367 assert!(json.contains("E1"));
1368 }
1369
1370 #[test]
1371 fn test_probabilistic_params_deserialize() {
1372 let json = r#"{"hypothesis":"Test H","prior":0.5,"evidence":[]}"#;
1373 let params: ProbabilisticParams = serde_json::from_str(json).unwrap();
1374 assert_eq!(params.hypothesis, "Test H");
1375 assert_eq!(params.prior, 0.5);
1376 }
1377
1378 #[test]
1379 fn test_probabilistic_params_round_trip() {
1380 let original = ProbabilisticParams::new("Round trip hypothesis", 0.4)
1381 .with_evidence("E1", 0.9, 0.1)
1382 .with_evidence("E2", 0.7, 0.3);
1383
1384 let json = serde_json::to_string(&original).unwrap();
1385 let deserialized: ProbabilisticParams = serde_json::from_str(&json).unwrap();
1386
1387 assert_eq!(original.hypothesis, deserialized.hypothesis);
1388 assert_eq!(original.prior, deserialized.prior);
1389 assert_eq!(original.evidence.len(), deserialized.evidence.len());
1390 }
1391
1392 #[test]
1397 fn test_probability_interval_serialize() {
1398 let pi = ProbabilityInterval {
1399 lower: 0.3,
1400 upper: 0.7,
1401 level: 0.95,
1402 };
1403 let json = serde_json::to_string(&pi).unwrap();
1404 assert!(json.contains("0.3"));
1405 assert!(json.contains("0.7"));
1406 assert!(json.contains("0.95"));
1407 }
1408
1409 #[test]
1410 fn test_chain_weakness_serialize() {
1411 let cw = ChainWeakness {
1412 from: "Node A".to_string(),
1413 to: "Node B".to_string(),
1414 weakness: "Weak assumption".to_string(),
1415 impact: 0.6,
1416 };
1417 let json = serde_json::to_string(&cw).unwrap();
1418 assert!(json.contains("Node A"));
1419 assert!(json.contains("Weak assumption"));
1420 }
1421
1422 #[test]
1423 fn test_evidence_contradiction_serialize() {
1424 let ec = EvidenceContradiction {
1425 evidence_a: "E1".to_string(),
1426 evidence_b: "E2".to_string(),
1427 nature: "Direct conflict".to_string(),
1428 resolution: "Consider E1 more credible".to_string(),
1429 };
1430 let json = serde_json::to_string(&ec).unwrap();
1431 assert!(json.contains("E1"));
1432 assert!(json.contains("Direct conflict"));
1433 }
1434
1435 #[test]
1436 fn test_gap_serialize() {
1437 let gap = Gap {
1438 gap: "Missing baseline data".to_string(),
1439 importance: 0.8,
1440 suggested_evidence: "Collect historical data".to_string(),
1441 };
1442 let json = serde_json::to_string(&gap).unwrap();
1443 assert!(json.contains("Missing baseline"));
1444 assert!(json.contains("0.8"));
1445 }
1446
1447 #[test]
1452 fn test_credibility_factors_default() {
1453 let cf = CredibilityFactors::default();
1454 assert_eq!(cf.source_reliability, 0.0);
1455 assert_eq!(cf.methodology, 0.0);
1456 assert_eq!(cf.recency, 0.0);
1457 assert_eq!(cf.corroboration, 0.0);
1458 }
1459
1460 #[test]
1461 fn test_source_type_equality() {
1462 assert_eq!(SourceType::Primary, SourceType::Primary);
1463 assert_ne!(SourceType::Primary, SourceType::Secondary);
1464 assert_ne!(SourceType::Expert, SourceType::Anecdotal);
1465 }
1466
1467 #[test]
1468 fn test_all_source_types_display() {
1469 let types = vec![
1470 SourceType::Primary,
1471 SourceType::Secondary,
1472 SourceType::Anecdotal,
1473 SourceType::Expert,
1474 SourceType::Statistical,
1475 ];
1476 for t in types {
1477 let display = format!("{}", t);
1478 assert!(!display.is_empty());
1479 }
1480 }
1481
1482 #[test]
1487 fn test_evidence_result_serialize() {
1488 let result = EvidenceResult {
1489 assessment_id: "assess-1".to_string(),
1490 session_id: "sess-1".to_string(),
1491 claim: "Test claim".to_string(),
1492 overall_support: SupportLevel {
1493 level: "strong".to_string(),
1494 confidence: 0.9,
1495 explanation: "Well supported".to_string(),
1496 },
1497 evidence_analyses: vec![],
1498 chain_analysis: None,
1499 contradictions: vec![],
1500 gaps: vec![],
1501 recommendations: vec![],
1502 };
1503 let json = serde_json::to_string(&result).unwrap();
1504 assert!(json.contains("assess-1"));
1505 assert!(json.contains("Test claim"));
1506 assert!(json.contains("strong"));
1507 }
1508
1509 #[test]
1510 fn test_evidence_result_with_analyses() {
1511 let result = EvidenceResult {
1512 assessment_id: "assess-2".to_string(),
1513 session_id: "sess-2".to_string(),
1514 claim: "Claim".to_string(),
1515 overall_support: SupportLevel {
1516 level: "moderate".to_string(),
1517 confidence: 0.7,
1518 explanation: "Some support".to_string(),
1519 },
1520 evidence_analyses: vec![EvidenceAnalysis {
1521 evidence_id: "e1".to_string(),
1522 content_summary: "Summary".to_string(),
1523 relevance: 0.8,
1524 credibility: 0.9,
1525 weight: 0.72,
1526 supports_claim: true,
1527 notes: "Notes".to_string(),
1528 }],
1529 chain_analysis: None,
1530 contradictions: vec![],
1531 gaps: vec![],
1532 recommendations: vec!["Get more evidence".to_string()],
1533 };
1534
1535 assert_eq!(result.evidence_analyses.len(), 1);
1536 assert_eq!(result.recommendations.len(), 1);
1537 assert_eq!(result.evidence_analyses[0].weight, 0.72);
1538 }
1539
1540 #[test]
1541 fn test_evidence_result_deserialize() {
1542 let json = r#"{
1543 "assessment_id": "a1",
1544 "session_id": "s1",
1545 "claim": "Claim",
1546 "overall_support": {
1547 "level": "weak",
1548 "confidence": 0.4,
1549 "explanation": "Limited support"
1550 },
1551 "evidence_analyses": [],
1552 "contradictions": [],
1553 "gaps": [],
1554 "recommendations": []
1555 }"#;
1556 let result: EvidenceResult = serde_json::from_str(json).unwrap();
1557 assert_eq!(result.assessment_id, "a1");
1558 assert_eq!(result.overall_support.level, "weak");
1559 assert_eq!(result.overall_support.confidence, 0.4);
1560 }
1561
1562 #[test]
1563 fn test_evidence_result_round_trip() {
1564 let original = EvidenceResult {
1565 assessment_id: "round-trip".to_string(),
1566 session_id: "sess".to_string(),
1567 claim: "Test".to_string(),
1568 overall_support: SupportLevel {
1569 level: "strong".to_string(),
1570 confidence: 0.95,
1571 explanation: "Clear support".to_string(),
1572 },
1573 evidence_analyses: vec![],
1574 chain_analysis: None,
1575 contradictions: vec![],
1576 gaps: vec![],
1577 recommendations: vec![],
1578 };
1579
1580 let json = serde_json::to_string(&original).unwrap();
1581 let deserialized: EvidenceResult = serde_json::from_str(&json).unwrap();
1582
1583 assert_eq!(original.assessment_id, deserialized.assessment_id);
1584 assert_eq!(original.claim, deserialized.claim);
1585 assert_eq!(
1586 original.overall_support.confidence,
1587 deserialized.overall_support.confidence
1588 );
1589 }
1590
1591 #[test]
1596 fn test_probabilistic_result_serialize() {
1597 let result = ProbabilisticResult {
1598 update_id: "update-1".to_string(),
1599 session_id: "sess-1".to_string(),
1600 hypothesis: "H".to_string(),
1601 prior: 0.5,
1602 posterior: 0.7,
1603 confidence_interval: None,
1604 update_steps: vec![],
1605 uncertainty: UncertaintyMetrics {
1606 entropy_before: 1.0,
1607 entropy_after: 0.88,
1608 information_gained: 0.12,
1609 },
1610 interpretation: ProbabilityInterpretation {
1611 verbal: "likely".to_string(),
1612 recommendation: "Proceed".to_string(),
1613 caveats: vec![],
1614 },
1615 };
1616 let json = serde_json::to_string(&result).unwrap();
1617 assert!(json.contains("update-1"));
1618 assert!(json.contains("0.5"));
1619 assert!(json.contains("0.7"));
1620 assert!(json.contains("likely"));
1621 }
1622
1623 #[test]
1624 fn test_probabilistic_result_with_interval() {
1625 let result = ProbabilisticResult {
1626 update_id: "update-2".to_string(),
1627 session_id: "sess-2".to_string(),
1628 hypothesis: "Test H".to_string(),
1629 prior: 0.3,
1630 posterior: 0.6,
1631 confidence_interval: Some(ProbabilityInterval {
1632 lower: 0.5,
1633 upper: 0.7,
1634 level: 0.95,
1635 }),
1636 update_steps: vec![BayesianUpdateStep {
1637 evidence: "E".to_string(),
1638 prior: 0.3,
1639 posterior: 0.6,
1640 likelihood_ratio: 3.0,
1641 }],
1642 uncertainty: UncertaintyMetrics {
1643 entropy_before: 0.88,
1644 entropy_after: 0.97,
1645 information_gained: -0.09,
1646 },
1647 interpretation: ProbabilityInterpretation {
1648 verbal: "possible".to_string(),
1649 recommendation: "Gather more evidence".to_string(),
1650 caveats: vec!["Limited data".to_string()],
1651 },
1652 };
1653
1654 assert!(result.confidence_interval.is_some());
1655 let ci = result.confidence_interval.unwrap();
1656 assert_eq!(ci.lower, 0.5);
1657 assert_eq!(ci.upper, 0.7);
1658 assert_eq!(ci.level, 0.95);
1659 }
1660
1661 #[test]
1662 fn test_probabilistic_result_deserialize() {
1663 let json = r#"{
1664 "update_id": "u1",
1665 "session_id": "s1",
1666 "hypothesis": "H",
1667 "prior": 0.5,
1668 "posterior": 0.8,
1669 "update_steps": [],
1670 "uncertainty": {
1671 "entropy_before": 1.0,
1672 "entropy_after": 0.72,
1673 "information_gained": 0.28
1674 },
1675 "interpretation": {
1676 "verbal": "highly_likely",
1677 "recommendation": "Act",
1678 "caveats": []
1679 }
1680 }"#;
1681 let result: ProbabilisticResult = serde_json::from_str(json).unwrap();
1682 assert_eq!(result.update_id, "u1");
1683 assert_eq!(result.prior, 0.5);
1684 assert_eq!(result.posterior, 0.8);
1685 assert_eq!(result.interpretation.verbal, "highly_likely");
1686 }
1687
1688 #[test]
1689 fn test_probabilistic_result_round_trip() {
1690 let original = ProbabilisticResult {
1691 update_id: "round".to_string(),
1692 session_id: "s".to_string(),
1693 hypothesis: "Test hypothesis".to_string(),
1694 prior: 0.4,
1695 posterior: 0.75,
1696 confidence_interval: Some(ProbabilityInterval {
1697 lower: 0.65,
1698 upper: 0.85,
1699 level: 0.95,
1700 }),
1701 update_steps: vec![],
1702 uncertainty: UncertaintyMetrics {
1703 entropy_before: 0.97,
1704 entropy_after: 0.81,
1705 information_gained: 0.16,
1706 },
1707 interpretation: ProbabilityInterpretation {
1708 verbal: "likely".to_string(),
1709 recommendation: "Test".to_string(),
1710 caveats: vec![],
1711 },
1712 };
1713
1714 let json = serde_json::to_string(&original).unwrap();
1715 let deserialized: ProbabilisticResult = serde_json::from_str(&json).unwrap();
1716
1717 assert_eq!(original.hypothesis, deserialized.hypothesis);
1718 assert_eq!(original.prior, deserialized.prior);
1719 assert_eq!(original.posterior, deserialized.posterior);
1720 }
1721
1722 #[test]
1727 fn test_inferential_chain_serialize() {
1728 let chain = InferentialChain {
1729 primary_chain: vec!["A".to_string(), "B".to_string(), "C".to_string()],
1730 weak_links: vec![ChainWeakness {
1731 from: "A".to_string(),
1732 to: "B".to_string(),
1733 weakness: "Assumption".to_string(),
1734 impact: 0.5,
1735 }],
1736 redundant_evidence: vec!["E1".to_string(), "E2".to_string()],
1737 };
1738 let json = serde_json::to_string(&chain).unwrap();
1739 assert!(json.contains("\"A\""));
1740 assert!(json.contains("Assumption"));
1741 assert!(json.contains("E1"));
1742 }
1743
1744 #[test]
1745 fn test_inferential_chain_deserialize() {
1746 let json = r#"{
1747 "primary_chain": ["X", "Y", "Z"],
1748 "weak_links": [],
1749 "redundant_evidence": []
1750 }"#;
1751 let chain: InferentialChain = serde_json::from_str(json).unwrap();
1752 assert_eq!(chain.primary_chain.len(), 3);
1753 assert_eq!(chain.primary_chain[0], "X");
1754 }
1755
1756 #[test]
1757 fn test_inferential_chain_round_trip() {
1758 let original = InferentialChain {
1759 primary_chain: vec!["Step1".to_string(), "Step2".to_string()],
1760 weak_links: vec![],
1761 redundant_evidence: vec!["Evidence".to_string()],
1762 };
1763
1764 let json = serde_json::to_string(&original).unwrap();
1765 let deserialized: InferentialChain = serde_json::from_str(&json).unwrap();
1766
1767 assert_eq!(original.primary_chain, deserialized.primary_chain);
1768 assert_eq!(original.redundant_evidence, deserialized.redundant_evidence);
1769 }
1770
1771 #[test]
1776 fn test_evidence_params_empty_claim() {
1777 let params = EvidenceParams::new("");
1778 assert_eq!(params.claim, "");
1779 }
1780
1781 #[test]
1782 fn test_probabilistic_params_boundary_priors() {
1783 let params_zero = ProbabilisticParams::new("H", 0.0);
1784 assert_eq!(params_zero.prior, 0.0);
1785
1786 let params_one = ProbabilisticParams::new("H", 1.0);
1787 assert_eq!(params_one.prior, 1.0);
1788 }
1789
1790 #[test]
1791 fn test_support_level_round_trip() {
1792 let original = SupportLevel {
1793 level: "contradictory".to_string(),
1794 confidence: 0.65,
1795 explanation: "Mixed evidence".to_string(),
1796 };
1797
1798 let json = serde_json::to_string(&original).unwrap();
1799 let deserialized: SupportLevel = serde_json::from_str(&json).unwrap();
1800
1801 assert_eq!(original.level, deserialized.level);
1802 assert_eq!(original.confidence, deserialized.confidence);
1803 assert_eq!(original.explanation, deserialized.explanation);
1804 }
1805
1806 #[test]
1807 fn test_evidence_analysis_round_trip() {
1808 let original = EvidenceAnalysis {
1809 evidence_id: "id-123".to_string(),
1810 content_summary: "Summary text".to_string(),
1811 relevance: 0.95,
1812 credibility: 0.88,
1813 weight: 0.8360,
1814 supports_claim: false,
1815 notes: "Detailed notes".to_string(),
1816 };
1817
1818 let json = serde_json::to_string(&original).unwrap();
1819 let deserialized: EvidenceAnalysis = serde_json::from_str(&json).unwrap();
1820
1821 assert_eq!(original.evidence_id, deserialized.evidence_id);
1822 assert_eq!(original.relevance, deserialized.relevance);
1823 assert_eq!(original.supports_claim, deserialized.supports_claim);
1824 }
1825
1826 #[test]
1827 fn test_bayesian_update_step_round_trip() {
1828 let original = BayesianUpdateStep {
1829 evidence: "Strong evidence".to_string(),
1830 prior: 0.25,
1831 posterior: 0.67,
1832 likelihood_ratio: 5.33,
1833 };
1834
1835 let json = serde_json::to_string(&original).unwrap();
1836 let deserialized: BayesianUpdateStep = serde_json::from_str(&json).unwrap();
1837
1838 assert_eq!(original.evidence, deserialized.evidence);
1839 assert_eq!(original.prior, deserialized.prior);
1840 assert_eq!(original.posterior, deserialized.posterior);
1841 assert_eq!(original.likelihood_ratio, deserialized.likelihood_ratio);
1842 }
1843
1844 #[test]
1845 fn test_uncertainty_metrics_round_trip() {
1846 let original = UncertaintyMetrics {
1847 entropy_before: 0.95,
1848 entropy_after: 0.72,
1849 information_gained: 0.23,
1850 };
1851
1852 let json = serde_json::to_string(&original).unwrap();
1853 let deserialized: UncertaintyMetrics = serde_json::from_str(&json).unwrap();
1854
1855 assert_eq!(original.entropy_before, deserialized.entropy_before);
1856 assert_eq!(original.entropy_after, deserialized.entropy_after);
1857 assert_eq!(original.information_gained, deserialized.information_gained);
1858 }
1859
1860 #[test]
1861 fn test_probability_interpretation_round_trip() {
1862 let original = ProbabilityInterpretation {
1863 verbal: "almost_certain".to_string(),
1864 recommendation: "Execute plan".to_string(),
1865 caveats: vec!["Caveat 1".to_string(), "Caveat 2".to_string()],
1866 };
1867
1868 let json = serde_json::to_string(&original).unwrap();
1869 let deserialized: ProbabilityInterpretation = serde_json::from_str(&json).unwrap();
1870
1871 assert_eq!(original.verbal, deserialized.verbal);
1872 assert_eq!(original.recommendation, deserialized.recommendation);
1873 assert_eq!(original.caveats.len(), deserialized.caveats.len());
1874 }
1875
1876 #[test]
1877 fn test_probability_interval_round_trip() {
1878 let original = ProbabilityInterval {
1879 lower: 0.45,
1880 upper: 0.65,
1881 level: 0.90,
1882 };
1883
1884 let json = serde_json::to_string(&original).unwrap();
1885 let deserialized: ProbabilityInterval = serde_json::from_str(&json).unwrap();
1886
1887 assert_eq!(original.lower, deserialized.lower);
1888 assert_eq!(original.upper, deserialized.upper);
1889 assert_eq!(original.level, deserialized.level);
1890 }
1891
1892 #[test]
1893 fn test_chain_weakness_round_trip() {
1894 let original = ChainWeakness {
1895 from: "Premise A".to_string(),
1896 to: "Conclusion B".to_string(),
1897 weakness: "Logical leap".to_string(),
1898 impact: 0.75,
1899 };
1900
1901 let json = serde_json::to_string(&original).unwrap();
1902 let deserialized: ChainWeakness = serde_json::from_str(&json).unwrap();
1903
1904 assert_eq!(original.from, deserialized.from);
1905 assert_eq!(original.to, deserialized.to);
1906 assert_eq!(original.weakness, deserialized.weakness);
1907 assert_eq!(original.impact, deserialized.impact);
1908 }
1909
1910 #[test]
1911 fn test_evidence_contradiction_round_trip() {
1912 let original = EvidenceContradiction {
1913 evidence_a: "Source 1".to_string(),
1914 evidence_b: "Source 2".to_string(),
1915 nature: "Conflicting dates".to_string(),
1916 resolution: "Use more recent".to_string(),
1917 };
1918
1919 let json = serde_json::to_string(&original).unwrap();
1920 let deserialized: EvidenceContradiction = serde_json::from_str(&json).unwrap();
1921
1922 assert_eq!(original.evidence_a, deserialized.evidence_a);
1923 assert_eq!(original.evidence_b, deserialized.evidence_b);
1924 assert_eq!(original.nature, deserialized.nature);
1925 assert_eq!(original.resolution, deserialized.resolution);
1926 }
1927
1928 #[test]
1929 fn test_gap_round_trip() {
1930 let original = Gap {
1931 gap: "Missing control group data".to_string(),
1932 importance: 0.92,
1933 suggested_evidence: "Conduct controlled study".to_string(),
1934 };
1935
1936 let json = serde_json::to_string(&original).unwrap();
1937 let deserialized: Gap = serde_json::from_str(&json).unwrap();
1938
1939 assert_eq!(original.gap, deserialized.gap);
1940 assert_eq!(original.importance, deserialized.importance);
1941 assert_eq!(original.suggested_evidence, deserialized.suggested_evidence);
1942 }
1943
1944 #[test]
1949 fn test_evidence_params_builder_chain() {
1950 let params = EvidenceParams::new("Complex claim")
1951 .with_session("s1")
1952 .with_context("Context info")
1953 .with_evidence("E1")
1954 .with_sourced_evidence("E2", "Source", SourceType::Expert)
1955 .with_evidence("E3");
1956
1957 assert_eq!(params.claim, "Complex claim");
1958 assert_eq!(params.session_id, Some("s1".to_string()));
1959 assert_eq!(params.context, Some("Context info".to_string()));
1960 assert_eq!(params.evidence.len(), 3);
1961 assert_eq!(params.evidence[1].source_type, Some(SourceType::Expert));
1962 }
1963
1964 #[test]
1965 fn test_probabilistic_params_builder_chain() {
1966 let params = ProbabilisticParams::new("H", 0.5)
1967 .with_session("sess")
1968 .with_evidence("E1", 0.9, 0.1)
1969 .with_evidence("E2", 0.8, 0.2)
1970 .with_evidence("E3", 0.7, 0.3);
1971
1972 assert_eq!(params.hypothesis, "H");
1973 assert_eq!(params.prior, 0.5);
1974 assert_eq!(params.session_id, Some("sess".to_string()));
1975 assert_eq!(params.evidence.len(), 3);
1976 assert_eq!(params.evidence[0].likelihood_if_true, Some(0.9));
1977 assert_eq!(params.evidence[2].likelihood_if_false, Some(0.3));
1978 }
1979
1980 #[test]
1985 fn test_source_type_copy() {
1986 let st1 = SourceType::Primary;
1987 let st2 = st1; assert_eq!(st1, st2);
1989 }
1990
1991 #[test]
1992 fn test_evidence_params_clone() {
1993 let params1 = EvidenceParams::new("Claim").with_evidence("E1");
1994 let params2 = params1.clone();
1995 assert_eq!(params1.claim, params2.claim);
1996 assert_eq!(params1.evidence.len(), params2.evidence.len());
1997 }
1998
1999 #[test]
2000 fn test_probabilistic_params_clone() {
2001 let params1 = ProbabilisticParams::new("H", 0.6).with_evidence("E", 0.8, 0.2);
2002 let params2 = params1.clone();
2003 assert_eq!(params1.hypothesis, params2.hypothesis);
2004 assert_eq!(params1.prior, params2.prior);
2005 assert_eq!(params1.evidence.len(), params2.evidence.len());
2006 }
2007
2008 #[test]
2009 fn test_evidence_result_debug() {
2010 let result = EvidenceResult {
2011 assessment_id: "a1".to_string(),
2012 session_id: "s1".to_string(),
2013 claim: "Claim".to_string(),
2014 overall_support: SupportLevel {
2015 level: "strong".to_string(),
2016 confidence: 0.9,
2017 explanation: "Good".to_string(),
2018 },
2019 evidence_analyses: vec![],
2020 chain_analysis: None,
2021 contradictions: vec![],
2022 gaps: vec![],
2023 recommendations: vec![],
2024 };
2025 let debug_str = format!("{:?}", result);
2026 assert!(debug_str.contains("a1"));
2027 assert!(debug_str.contains("Claim"));
2028 }
2029
2030 #[test]
2035 fn test_evidence_params_multiple_evidence_types() {
2036 let params = EvidenceParams::new("Test")
2037 .with_evidence("Simple evidence")
2038 .with_sourced_evidence("Expert opinion", "Dr. Smith", SourceType::Expert)
2039 .with_sourced_evidence("Data", "Study", SourceType::Statistical);
2040
2041 assert_eq!(params.evidence.len(), 3);
2042 assert!(params.evidence[0].source.is_none());
2043 assert_eq!(params.evidence[1].source, Some("Dr. Smith".to_string()));
2044 assert_eq!(
2045 params.evidence[2].source_type,
2046 Some(SourceType::Statistical)
2047 );
2048 }
2049
2050 #[test]
2051 fn test_probabilistic_params_multiple_evidence() {
2052 let params = ProbabilisticParams::new("Hypothesis", 0.3)
2053 .with_evidence("Strong evidence", 0.95, 0.05)
2054 .with_evidence("Moderate evidence", 0.7, 0.3)
2055 .with_evidence("Weak evidence", 0.6, 0.4);
2056
2057 assert_eq!(params.evidence.len(), 3);
2058 assert_eq!(params.evidence[0].likelihood_if_true, Some(0.95));
2059 assert_eq!(params.evidence[1].likelihood_if_true, Some(0.7));
2060 assert_eq!(params.evidence[2].likelihood_if_false, Some(0.4));
2061 }
2062
2063 #[test]
2068 fn test_evidence_params_all_none() {
2069 let params = EvidenceParams {
2070 claim: "Claim".to_string(),
2071 evidence: vec![],
2072 session_id: None,
2073 context: None,
2074 };
2075 assert!(params.session_id.is_none());
2076 assert!(params.context.is_none());
2077 assert!(params.evidence.is_empty());
2078 }
2079
2080 #[test]
2081 fn test_bayesian_evidence_all_none() {
2082 let be = BayesianEvidence {
2083 description: "Test".to_string(),
2084 likelihood_if_true: None,
2085 likelihood_if_false: None,
2086 };
2087 assert!(be.likelihood_if_true.is_none());
2088 assert!(be.likelihood_if_false.is_none());
2089 }
2090
2091 #[test]
2092 fn test_evidence_result_empty_collections() {
2093 let result = EvidenceResult {
2094 assessment_id: "a".to_string(),
2095 session_id: "s".to_string(),
2096 claim: "c".to_string(),
2097 overall_support: SupportLevel {
2098 level: "insufficient".to_string(),
2099 confidence: 0.1,
2100 explanation: "No evidence".to_string(),
2101 },
2102 evidence_analyses: vec![],
2103 chain_analysis: None,
2104 contradictions: vec![],
2105 gaps: vec![],
2106 recommendations: vec![],
2107 };
2108 assert!(result.evidence_analyses.is_empty());
2109 assert!(result.contradictions.is_empty());
2110 assert!(result.gaps.is_empty());
2111 assert!(result.recommendations.is_empty());
2112 }
2113
2114 #[test]
2115 fn test_probabilistic_result_no_confidence_interval() {
2116 let result = ProbabilisticResult {
2117 update_id: "u".to_string(),
2118 session_id: "s".to_string(),
2119 hypothesis: "h".to_string(),
2120 prior: 0.5,
2121 posterior: 0.6,
2122 confidence_interval: None,
2123 update_steps: vec![],
2124 uncertainty: UncertaintyMetrics {
2125 entropy_before: 1.0,
2126 entropy_after: 0.97,
2127 information_gained: 0.03,
2128 },
2129 interpretation: ProbabilityInterpretation {
2130 verbal: "possible".to_string(),
2131 recommendation: "wait".to_string(),
2132 caveats: vec![],
2133 },
2134 };
2135 assert!(result.confidence_interval.is_none());
2136 assert!(result.update_steps.is_empty());
2137 }
2138
2139 #[test]
2144 fn test_entropy_boundary_values() {
2145 fn entropy(p: f64) -> f64 {
2146 if p <= 0.0 || p >= 1.0 {
2147 0.0
2148 } else {
2149 -(p * p.log2() + (1.0 - p) * (1.0 - p).log2())
2150 }
2151 }
2152
2153 assert_eq!(entropy(0.0), 0.0);
2155 assert_eq!(entropy(1.0), 0.0);
2156
2157 let near_zero = entropy(0.001);
2159 assert!(near_zero > 0.0 && near_zero < 0.1);
2160
2161 let near_one = entropy(0.999);
2162 assert!(near_one > 0.0 && near_one < 0.1);
2163
2164 let e25 = entropy(0.25);
2166 let e50 = entropy(0.5);
2167 let e75 = entropy(0.75);
2168
2169 assert!((e50 - 1.0).abs() < 0.0001);
2171 assert!((e25 - e75).abs() < 0.0001);
2173 }
2174
2175 #[test]
2180 fn test_source_type_all_values_serialize_deserialize() {
2181 let types = vec![
2182 SourceType::Primary,
2183 SourceType::Secondary,
2184 SourceType::Anecdotal,
2185 SourceType::Expert,
2186 SourceType::Statistical,
2187 ];
2188
2189 for source_type in types {
2190 let json = serde_json::to_string(&source_type).unwrap();
2191 let deserialized: SourceType = serde_json::from_str(&json).unwrap();
2192 assert_eq!(source_type, deserialized);
2193 }
2194 }
2195
2196 #[test]
2201 fn test_evidence_result_complex_structure() {
2202 let result = EvidenceResult {
2203 assessment_id: "complex-1".to_string(),
2204 session_id: "sess-complex".to_string(),
2205 claim: "Complex claim".to_string(),
2206 overall_support: SupportLevel {
2207 level: "moderate".to_string(),
2208 confidence: 0.75,
2209 explanation: "Mixed evidence".to_string(),
2210 },
2211 evidence_analyses: vec![
2212 EvidenceAnalysis {
2213 evidence_id: "e1".to_string(),
2214 content_summary: "First".to_string(),
2215 relevance: 0.9,
2216 credibility: 0.8,
2217 weight: 0.72,
2218 supports_claim: true,
2219 notes: "Strong".to_string(),
2220 },
2221 EvidenceAnalysis {
2222 evidence_id: "e2".to_string(),
2223 content_summary: "Second".to_string(),
2224 relevance: 0.7,
2225 credibility: 0.6,
2226 weight: 0.42,
2227 supports_claim: false,
2228 notes: "Weak".to_string(),
2229 },
2230 ],
2231 chain_analysis: Some(InferentialChain {
2232 primary_chain: vec!["A".to_string(), "B".to_string(), "C".to_string()],
2233 weak_links: vec![ChainWeakness {
2234 from: "B".to_string(),
2235 to: "C".to_string(),
2236 weakness: "Assumption".to_string(),
2237 impact: 0.4,
2238 }],
2239 redundant_evidence: vec!["e3".to_string()],
2240 }),
2241 contradictions: vec![EvidenceContradiction {
2242 evidence_a: "e1".to_string(),
2243 evidence_b: "e2".to_string(),
2244 nature: "Conflict".to_string(),
2245 resolution: "Prefer e1".to_string(),
2246 }],
2247 gaps: vec![Gap {
2248 gap: "Missing baseline".to_string(),
2249 importance: 0.8,
2250 suggested_evidence: "Get baseline".to_string(),
2251 }],
2252 recommendations: vec!["Rec 1".to_string(), "Rec 2".to_string()],
2253 };
2254
2255 let json = serde_json::to_string(&result).unwrap();
2256 let deserialized: EvidenceResult = serde_json::from_str(&json).unwrap();
2257
2258 assert_eq!(result.evidence_analyses.len(), 2);
2259 assert_eq!(deserialized.evidence_analyses.len(), 2);
2260 assert!(result.chain_analysis.is_some());
2261 assert!(deserialized.chain_analysis.is_some());
2262 assert_eq!(result.contradictions.len(), 1);
2263 assert_eq!(result.gaps.len(), 1);
2264 assert_eq!(result.recommendations.len(), 2);
2265 }
2266
2267 #[test]
2268 fn test_probabilistic_result_complex_structure() {
2269 let result = ProbabilisticResult {
2270 update_id: "complex-update".to_string(),
2271 session_id: "sess".to_string(),
2272 hypothesis: "Complex hypothesis".to_string(),
2273 prior: 0.3,
2274 posterior: 0.8,
2275 confidence_interval: Some(ProbabilityInterval {
2276 lower: 0.7,
2277 upper: 0.9,
2278 level: 0.95,
2279 }),
2280 update_steps: vec![
2281 BayesianUpdateStep {
2282 evidence: "E1".to_string(),
2283 prior: 0.3,
2284 posterior: 0.5,
2285 likelihood_ratio: 2.33,
2286 },
2287 BayesianUpdateStep {
2288 evidence: "E2".to_string(),
2289 prior: 0.5,
2290 posterior: 0.8,
2291 likelihood_ratio: 6.0,
2292 },
2293 ],
2294 uncertainty: UncertaintyMetrics {
2295 entropy_before: 0.88,
2296 entropy_after: 0.72,
2297 information_gained: 0.16,
2298 },
2299 interpretation: ProbabilityInterpretation {
2300 verbal: "highly_likely".to_string(),
2301 recommendation: "Act on hypothesis".to_string(),
2302 caveats: vec!["Caveat 1".to_string(), "Caveat 2".to_string()],
2303 },
2304 };
2305
2306 let json = serde_json::to_string(&result).unwrap();
2307 let deserialized: ProbabilisticResult = serde_json::from_str(&json).unwrap();
2308
2309 assert_eq!(result.update_steps.len(), 2);
2310 assert_eq!(deserialized.update_steps.len(), 2);
2311 assert!(result.confidence_interval.is_some());
2312 assert_eq!(result.interpretation.caveats.len(), 2);
2313 }
2314}