mcp_langbase_reasoning/modes/
evidence.rs

1//! Evidence assessment reasoning mode - evidence evaluation and Bayesian reasoning.
2//!
3//! This module provides evidence assessment capabilities:
4//! - Evidence quality and credibility evaluation
5//! - Source reliability analysis
6//! - Corroboration tracking
7//! - Bayesian probability updates
8//! - Uncertainty quantification with entropy
9
10use 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// ============================================================================
25// Evidence Assessment Parameters
26// ============================================================================
27
28/// Input parameters for evidence assessment.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct EvidenceParams {
31    /// The claim to assess evidence for.
32    pub claim: String,
33    /// Evidence items to assess.
34    pub evidence: Vec<EvidenceInput>,
35    /// Optional session ID (creates new if not provided).
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub session_id: Option<String>,
38    /// Additional context for the assessment.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub context: Option<String>,
41}
42
43/// Evidence input item for assessment.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct EvidenceInput {
46    /// Evidence content or description.
47    pub content: String,
48    /// Source of the evidence.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub source: Option<String>,
51    /// Type of source (primary, secondary, anecdotal, expert, statistical).
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub source_type: Option<SourceType>,
54    /// Date of evidence (ISO format).
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub date: Option<String>,
57}
58
59/// Type of evidence source.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case")]
62pub enum SourceType {
63    /// Primary/direct source.
64    Primary,
65    /// Secondary/derived source.
66    Secondary,
67    /// Anecdotal evidence.
68    Anecdotal,
69    /// Expert opinion.
70    Expert,
71    /// Statistical data.
72    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/// Input parameters for Bayesian probability updates.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ProbabilisticParams {
90    /// The hypothesis to evaluate.
91    pub hypothesis: String,
92    /// Prior probability (0-1).
93    pub prior: f64,
94    /// Evidence items with likelihood ratios.
95    pub evidence: Vec<BayesianEvidence>,
96    /// Optional session ID for context persistence.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub session_id: Option<String>,
99}
100
101/// Evidence item for Bayesian updating.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct BayesianEvidence {
104    /// Evidence description.
105    pub description: String,
106    /// P(evidence|hypothesis true).
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub likelihood_if_true: Option<f64>,
109    /// P(evidence|hypothesis false).
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub likelihood_if_false: Option<f64>,
112}
113
114// ============================================================================
115// Langbase Response Types
116// ============================================================================
117
118/// Response from evidence assessor Langbase pipe.
119#[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/// Response from Bayesian updater Langbase pipe.
225#[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// ============================================================================
281// Result Types
282// ============================================================================
283
284/// Result of evidence assessment.
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct EvidenceResult {
287    /// Unique assessment ID.
288    pub assessment_id: String,
289    /// Session ID.
290    pub session_id: String,
291    /// The claim being assessed.
292    pub claim: String,
293    /// Overall support level and confidence.
294    pub overall_support: SupportLevel,
295    /// Individual evidence analyses.
296    pub evidence_analyses: Vec<EvidenceAnalysis>,
297    /// Chain of reasoning analysis.
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub chain_analysis: Option<InferentialChain>,
300    /// Detected contradictions.
301    pub contradictions: Vec<EvidenceContradiction>,
302    /// Identified gaps.
303    pub gaps: Vec<Gap>,
304    /// Recommendations.
305    pub recommendations: Vec<String>,
306}
307
308/// Overall support level for a claim.
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct SupportLevel {
311    /// Support level (strong, moderate, weak, insufficient, contradictory).
312    pub level: String,
313    /// Confidence in assessment (0.0-1.0).
314    pub confidence: f64,
315    /// Explanation of the assessment.
316    pub explanation: String,
317}
318
319/// Analysis of a single piece of evidence.
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct EvidenceAnalysis {
322    /// Evidence identifier.
323    pub evidence_id: String,
324    /// Summary of content.
325    pub content_summary: String,
326    /// Relevance to claim (0.0-1.0).
327    pub relevance: f64,
328    /// Source credibility (0.0-1.0).
329    pub credibility: f64,
330    /// Combined weight (0.0-1.0).
331    pub weight: f64,
332    /// Whether it supports the claim.
333    pub supports_claim: bool,
334    /// Assessment notes.
335    pub notes: String,
336}
337
338/// Inferential chain from evidence to claim.
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct InferentialChain {
341    /// Primary reasoning chain.
342    pub primary_chain: Vec<String>,
343    /// Weak links in the chain.
344    pub weak_links: Vec<ChainWeakness>,
345    /// Evidence providing redundancy.
346    pub redundant_evidence: Vec<String>,
347}
348
349/// Weakness in an inferential chain.
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct ChainWeakness {
352    /// From node.
353    pub from: String,
354    /// To node.
355    pub to: String,
356    /// Description of weakness.
357    pub weakness: String,
358    /// Impact on conclusion (0.0-1.0).
359    pub impact: f64,
360}
361
362/// Contradiction between evidence items.
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct EvidenceContradiction {
365    /// First evidence item.
366    pub evidence_a: String,
367    /// Second evidence item.
368    pub evidence_b: String,
369    /// Nature of contradiction.
370    pub nature: String,
371    /// Resolution approach.
372    pub resolution: String,
373}
374
375/// Gap in evidence coverage.
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct Gap {
378    /// Description of what's missing.
379    pub gap: String,
380    /// Importance of filling this gap (0.0-1.0).
381    pub importance: f64,
382    /// Suggested evidence to gather.
383    pub suggested_evidence: String,
384}
385
386/// Result of Bayesian probability update.
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ProbabilisticResult {
389    /// Unique update ID.
390    pub update_id: String,
391    /// Session ID.
392    pub session_id: String,
393    /// The hypothesis evaluated.
394    pub hypothesis: String,
395    /// Prior probability.
396    pub prior: f64,
397    /// Posterior probability after all evidence.
398    pub posterior: f64,
399    /// Confidence interval.
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub confidence_interval: Option<ProbabilityInterval>,
402    /// Update steps for each evidence.
403    pub update_steps: Vec<BayesianUpdateStep>,
404    /// Uncertainty metrics.
405    pub uncertainty: UncertaintyMetrics,
406    /// Human interpretation.
407    pub interpretation: ProbabilityInterpretation,
408}
409
410/// Confidence interval for probability.
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct ProbabilityInterval {
413    /// Lower bound.
414    pub lower: f64,
415    /// Upper bound.
416    pub upper: f64,
417    /// Confidence level (e.g., 0.95).
418    pub level: f64,
419}
420
421/// Single Bayesian update step.
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct BayesianUpdateStep {
424    /// Evidence description.
425    pub evidence: String,
426    /// Prior before this evidence.
427    pub prior: f64,
428    /// Posterior after this evidence.
429    pub posterior: f64,
430    /// Likelihood ratio used.
431    pub likelihood_ratio: f64,
432}
433
434/// Uncertainty metrics for probability assessment.
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct UncertaintyMetrics {
437    /// Shannon entropy before updates.
438    pub entropy_before: f64,
439    /// Shannon entropy after updates.
440    pub entropy_after: f64,
441    /// Information gained.
442    pub information_gained: f64,
443}
444
445/// Human interpretation of probability.
446#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct ProbabilityInterpretation {
448    /// Verbal probability (almost_certain, highly_likely, likely, possible, unlikely, etc.).
449    pub verbal: String,
450    /// Recommendation based on probability.
451    pub recommendation: String,
452    /// Important caveats.
453    pub caveats: Vec<String>,
454}
455
456// ============================================================================
457// Mode Handler
458// ============================================================================
459
460/// Evidence assessment mode handler.
461#[derive(Clone)]
462pub struct EvidenceMode {
463    /// Core infrastructure (storage and langbase client).
464    core: ModeCore,
465    /// Consolidated pipe name for decision framework operations (prompts passed dynamically).
466    decision_framework_pipe: String,
467}
468
469impl EvidenceMode {
470    /// Create a new evidence mode handler.
471    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    /// Assess evidence for a claim.
484    pub async fn assess_evidence(&self, params: EvidenceParams) -> AppResult<EvidenceResult> {
485        let start = Instant::now();
486
487        // Validate input
488        self.validate_evidence_params(&params)?;
489
490        // Get or create session
491        let session = self
492            .core
493            .storage()
494            .get_or_create_session(&params.session_id, "evidence")
495            .await?;
496        debug!(session_id = %session.id, "Processing evidence assessment");
497
498        // Build messages for Langbase
499        let messages = self.build_evidence_messages(&params);
500
501        // Create invocation log
502        let mut invocation = Invocation::new(
503            "reasoning.assess_evidence",
504            serialize_for_log(&params, "reasoning.assess_evidence input"),
505        )
506        .with_session(&session.id)
507        .with_pipe(&self.decision_framework_pipe);
508
509        // Call Langbase pipe
510        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        // Parse response
522        let evidence_response = self.parse_evidence_response(&response.completion)?;
523
524        // Generate assessment ID
525        let assessment_id = uuid::Uuid::new_v4().to_string();
526
527        // Build result
528        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        // Persist to storage
587        let mut stored_evidence = StoredEvidence::new(
588            &session.id,
589            &params.claim,
590            serde_json::to_value(&params.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        // Log successful invocation
617        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    /// Perform Bayesian probability update.
635    pub async fn update_probability(
636        &self,
637        params: ProbabilisticParams,
638    ) -> AppResult<ProbabilisticResult> {
639        let start = Instant::now();
640
641        // Validate input
642        self.validate_probabilistic_params(&params)?;
643
644        // Get or create session
645        let session = self
646            .core
647            .storage()
648            .get_or_create_session(&params.session_id, "evidence")
649            .await?;
650        debug!(session_id = %session.id, "Processing probabilistic update");
651
652        // Build messages for Langbase
653        let messages = self.build_probabilistic_messages(&params);
654
655        // Create invocation log
656        let mut invocation = Invocation::new(
657            "reasoning.probabilistic",
658            serialize_for_log(&params, "reasoning.probabilistic input"),
659        )
660        .with_session(&session.id)
661        .with_pipe(&self.decision_framework_pipe);
662
663        // Call Langbase pipe
664        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        // Parse response
682        let bayesian_response = self.parse_bayesian_response(&response.completion, &params)?;
683
684        // Generate update ID
685        let update_id = uuid::Uuid::new_v4().to_string();
686
687        // Build result
688        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        // Persist to storage
735        let mut stored_probability = StoredProbability::new(
736            &session.id,
737            &params.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        // Log successful invocation
767        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    // ========================================================================
786    // Private Helper Methods
787    // ========================================================================
788
789    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(&params.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        // Build user message with claim and evidence
834        let evidence_json = serde_json::to_string_pretty(&params.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        // Build user message
856        let evidence_json = serde_json::to_string_pretty(&params.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        // Parse response - returns error on failure (no fallbacks)
903        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    /// Calculate Shannon entropy for a probability.
914    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
923// ============================================================================
924// Builder Methods
925// ============================================================================
926
927impl EvidenceParams {
928    /// Create new evidence params with claim.
929    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    /// Set the session ID.
939    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    /// Add evidence.
945    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    /// Add evidence with source.
956    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    /// Set context.
972    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    /// Create new probabilistic params.
980    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    /// Set the session ID.
990    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    /// Add evidence with likelihoods.
996    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// ============================================================================
1012// Tests
1013// ============================================================================
1014
1015#[cfg(test)]
1016mod tests {
1017    use super::*;
1018
1019    // ========================================================================
1020    // EvidenceParams Tests
1021    // ========================================================================
1022
1023    #[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    // ========================================================================
1069    // ProbabilisticParams Tests
1070    // ========================================================================
1071
1072    #[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    // ========================================================================
1097    // SourceType Tests
1098    // ========================================================================
1099
1100    #[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    // ========================================================================
1122    // Result Type Tests
1123    // ========================================================================
1124
1125    #[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    // ========================================================================
1191    // Entropy Calculation Tests
1192    // ========================================================================
1193
1194    #[test]
1195    fn test_entropy_calculation() {
1196        // Create a minimal EvidenceMode for testing
1197        // Note: In actual use, these would use the mode's calculate_entropy method
1198        // Here we test the formula directly
1199
1200        // Helper function for entropy calculation
1201        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        // Entropy should be 0 at extremes
1210        assert_eq!(entropy(0.0), 0.0);
1211        assert_eq!(entropy(1.0), 0.0);
1212
1213        // Maximum entropy at p=0.5
1214        let entropy_half = entropy(0.5);
1215        assert!((entropy_half - 1.0).abs() < 0.0001); // Should be approximately 1 bit
1216    }
1217
1218    // ========================================================================
1219    // EvidenceInput Tests
1220    // ========================================================================
1221
1222    #[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        // Optional fields should be skipped when None
1256        assert!(!json.contains("source"));
1257    }
1258
1259    // ========================================================================
1260    // EvidenceParams Serialization Tests
1261    // ========================================================================
1262
1263    #[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(&params).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    // ========================================================================
1301    // BayesianEvidence Tests
1302    // ========================================================================
1303
1304    #[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    // ========================================================================
1356    // ProbabilisticParams Serialization Tests
1357    // ========================================================================
1358
1359    #[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(&params).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    // ========================================================================
1393    // Response Type Tests
1394    // ========================================================================
1395
1396    #[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    // ========================================================================
1448    // Default and Edge Cases
1449    // ========================================================================
1450
1451    #[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    // ========================================================================
1483    // EvidenceResult Tests
1484    // ========================================================================
1485
1486    #[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    // ========================================================================
1592    // ProbabilisticResult Tests
1593    // ========================================================================
1594
1595    #[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    // ========================================================================
1723    // InferentialChain Tests
1724    // ========================================================================
1725
1726    #[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    // ========================================================================
1772    // Edge Cases and Boundary Values
1773    // ========================================================================
1774
1775    #[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    // ========================================================================
1945    // Builder Chain Tests
1946    // ========================================================================
1947
1948    #[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    // ========================================================================
1981    // Clone and Debug Trait Tests
1982    // ========================================================================
1983
1984    #[test]
1985    fn test_source_type_copy() {
1986        let st1 = SourceType::Primary;
1987        let st2 = st1; // Copy trait - no clone needed
1988        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    // ========================================================================
2031    // Multiple Evidence Items Tests
2032    // ========================================================================
2033
2034    #[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    // ========================================================================
2064    // Empty and None Value Tests
2065    // ========================================================================
2066
2067    #[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    // ========================================================================
2140    // Entropy Edge Cases
2141    // ========================================================================
2142
2143    #[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        // Test exact boundaries
2154        assert_eq!(entropy(0.0), 0.0);
2155        assert_eq!(entropy(1.0), 0.0);
2156
2157        // Test near boundaries
2158        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        // Test various probabilities
2165        let e25 = entropy(0.25);
2166        let e50 = entropy(0.5);
2167        let e75 = entropy(0.75);
2168
2169        // Entropy at 0.5 should be maximum (1.0)
2170        assert!((e50 - 1.0).abs() < 0.0001);
2171        // Entropy should be symmetric around 0.5
2172        assert!((e25 - e75).abs() < 0.0001);
2173    }
2174
2175    // ========================================================================
2176    // Source Type Serialization Edge Cases
2177    // ========================================================================
2178
2179    #[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    // ========================================================================
2197    // Complex Nested Structure Tests
2198    // ========================================================================
2199
2200    #[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}