research_agent/domain/
knowledge_gap.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4#[serde(rename_all = "snake_case")]
5pub enum GapType {
6 MissingLiterature,
7 UnansweredQuestion,
8 MethodologyGap,
9 ConnectionGap,
10}
11
12impl GapType {
13 pub fn as_str(&self) -> &'static str {
14 match self {
15 Self::MissingLiterature => "missing_literature",
16 Self::UnansweredQuestion => "unanswered_question",
17 Self::MethodologyGap => "methodology_gap",
18 Self::ConnectionGap => "connection_gap",
19 }
20 }
21
22 pub fn from_str_lossy(s: &str) -> Self {
23 match s {
24 "unanswered_question" => Self::UnansweredQuestion,
25 "methodology_gap" => Self::MethodologyGap,
26 "connection_gap" => Self::ConnectionGap,
27 _ => Self::MissingLiterature,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct KnowledgeGap {
34 pub id: String,
35 pub description: String,
36 pub topic_id: String,
37 pub gap_type: GapType,
38 pub priority: f32,
39 pub discovered_at: String,
40}
41
42impl KnowledgeGap {
43 pub fn new(description: String, topic_id: String, gap_type: GapType) -> Self {
44 let now = chrono::Utc::now().to_rfc3339();
45 Self {
46 id: uuid::Uuid::new_v4().to_string(),
47 description,
48 topic_id,
49 gap_type,
50 priority: 0.5,
51 discovered_at: now,
52 }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn gap_type_roundtrip() {
62 let types = [
63 GapType::MissingLiterature,
64 GapType::UnansweredQuestion,
65 GapType::MethodologyGap,
66 GapType::ConnectionGap,
67 ];
68 for t in &types {
69 assert_eq!(GapType::from_str_lossy(t.as_str()), *t);
70 }
71 }
72
73 #[test]
74 fn gap_new() {
75 let g = KnowledgeGap::new(
76 "Missing survey papers".into(),
77 "topic-1".into(),
78 GapType::MissingLiterature,
79 );
80 assert_eq!(g.description, "Missing survey papers");
81 assert_eq!(g.topic_id, "topic-1");
82 assert!(!g.id.is_empty());
83 }
84}