use super::{ResultType, SearchResult};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct ExplainedAnswer {
pub answer: String,
pub confidence: f32,
pub sources: Vec<SourceReference>,
pub reasoning_steps: Vec<ReasoningStep>,
pub key_entities: Vec<String>,
pub query_analysis: Option<super::QueryAnalysis>,
}
#[derive(Debug, Clone)]
pub struct SourceReference {
pub id: String,
pub source_type: SourceType,
pub excerpt: String,
pub relevance_score: f32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SourceType {
TextChunk,
Entity,
Relationship,
Summary,
}
#[derive(Debug, Clone)]
pub struct ReasoningStep {
pub step_number: u8,
pub description: String,
pub entities_used: Vec<String>,
pub evidence_snippet: Option<String>,
pub confidence: f32,
}
impl ExplainedAnswer {
pub fn from_results(answer: String, search_results: &[SearchResult], query: &str) -> Self {
let confidence = if search_results.is_empty() {
0.0
} else {
let total_score: f32 = search_results.iter().map(|r| r.score).sum();
let avg_score = total_score / search_results.len() as f32;
(avg_score * 0.7 + 0.3).clamp(0.0, 1.0)
};
let sources: Vec<SourceReference> = search_results
.iter()
.take(5) .map(|r| SourceReference {
id: r.id.clone(),
source_type: match r.result_type {
ResultType::Entity => SourceType::Entity,
ResultType::Chunk => SourceType::TextChunk,
ResultType::GraphPath => SourceType::Relationship,
ResultType::HierarchicalSummary => SourceType::Summary,
ResultType::Hybrid => SourceType::TextChunk,
},
excerpt: if r.content.len() > 200 {
format!("{}...", &r.content[..200])
} else {
r.content.clone()
},
relevance_score: r.score,
})
.collect();
let mut reasoning_steps = Vec::new();
let mut step_num = 1u8;
reasoning_steps.push(ReasoningStep {
step_number: step_num,
description: format!("Analyzed query: \"{}\"", query),
entities_used: vec![],
evidence_snippet: None,
confidence: 0.95,
});
step_num += 1;
let unique_entities: HashSet<_> = search_results
.iter()
.flat_map(|r| r.entities.iter().cloned())
.collect();
if !unique_entities.is_empty() {
reasoning_steps.push(ReasoningStep {
step_number: step_num,
description: format!("Found {} relevant entities", unique_entities.len()),
entities_used: unique_entities.iter().take(5).cloned().collect(),
evidence_snippet: None,
confidence: 0.85,
});
step_num += 1;
}
let chunk_count = search_results
.iter()
.filter(|r| r.result_type == ResultType::Chunk || r.result_type == ResultType::Hybrid)
.count();
if chunk_count > 0 {
reasoning_steps.push(ReasoningStep {
step_number: step_num,
description: format!("Retrieved {} relevant text chunks", chunk_count),
entities_used: vec![],
evidence_snippet: search_results.first().map(|r| {
if r.content.len() > 100 {
format!("{}...", &r.content[..100])
} else {
r.content.clone()
}
}),
confidence,
});
step_num += 1;
}
reasoning_steps.push(ReasoningStep {
step_number: step_num,
description: "Synthesized answer from retrieved information".to_string(),
entities_used: unique_entities.into_iter().take(3).collect(),
evidence_snippet: None,
confidence,
});
let key_entities: Vec<String> = search_results
.iter()
.flat_map(|r| r.entities.iter().cloned())
.take(10)
.collect();
Self {
answer,
confidence,
sources,
reasoning_steps,
key_entities,
query_analysis: None,
}
}
pub fn format_display(&self) -> String {
let mut output = String::new();
output.push_str(&format!("**Answer:** {}\n\n", self.answer));
output.push_str(&format!(
"**Confidence:** {:.0}%\n\n",
self.confidence * 100.0
));
if !self.reasoning_steps.is_empty() {
output.push_str("**Reasoning:**\n");
for step in &self.reasoning_steps {
output.push_str(&format!(
"{}. {} (confidence: {:.0}%)\n",
step.step_number,
step.description,
step.confidence * 100.0
));
if let Some(evidence) = &step.evidence_snippet {
output.push_str(&format!(" Evidence: \"{}\"\n", evidence));
}
}
output.push('\n');
}
if !self.sources.is_empty() {
output.push_str("**Sources:**\n");
for (i, source) in self.sources.iter().enumerate() {
output.push_str(&format!(
"{}. [{:?}] {} (relevance: {:.0}%)\n",
i + 1,
source.source_type,
source.id,
source.relevance_score * 100.0
));
}
}
output
}
}