Skip to main content

scitadel_core/models/
citation.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use super::{PaperId, QuestionId, SearchId, SnowballRunId};
5
6/// Direction of a citation edge.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum CitationDirection {
10    References,
11    CitedBy,
12}
13
14impl std::fmt::Display for CitationDirection {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            Self::References => write!(f, "references"),
18            Self::CitedBy => write!(f, "cited_by"),
19        }
20    }
21}
22
23impl CitationDirection {
24    /// Parse from string, matching Python's enum values.
25    pub fn from_str_value(s: &str) -> Option<Self> {
26        match s {
27            "references" => Some(Self::References),
28            "cited_by" => Some(Self::CitedBy),
29            _ => None,
30        }
31    }
32}
33
34/// Directed citation edge between two papers.
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct Citation {
37    pub source_paper_id: PaperId,
38    pub target_paper_id: PaperId,
39    pub direction: CitationDirection,
40    #[serde(default)]
41    pub discovered_by: String,
42    #[serde(default)]
43    pub depth: i32,
44    pub snowball_run_id: Option<SnowballRunId>,
45}
46
47/// Record of a snowball (citation chaining) run.
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct SnowballRun {
50    pub id: SnowballRunId,
51    pub search_id: Option<SearchId>,
52    pub question_id: Option<QuestionId>,
53    #[serde(default = "default_direction")]
54    pub direction: String,
55    #[serde(default = "default_max_depth")]
56    pub max_depth: i32,
57    #[serde(default = "default_threshold")]
58    pub threshold: f64,
59    #[serde(default)]
60    pub total_discovered: i32,
61    #[serde(default)]
62    pub total_new_papers: i32,
63    pub created_at: DateTime<Utc>,
64}
65
66fn default_direction() -> String {
67    "both".to_string()
68}
69
70fn default_max_depth() -> i32 {
71    1
72}
73
74fn default_threshold() -> f64 {
75    0.6
76}
77
78impl SnowballRun {
79    #[must_use]
80    pub fn new() -> Self {
81        Self {
82            id: SnowballRunId::new(),
83            search_id: None,
84            question_id: None,
85            direction: "both".to_string(),
86            max_depth: 1,
87            threshold: 0.6,
88            total_discovered: 0,
89            total_new_papers: 0,
90            created_at: Utc::now(),
91        }
92    }
93}
94
95impl Default for SnowballRun {
96    fn default() -> Self {
97        Self::new()
98    }
99}