Skip to main content

scitadel_core/models/
paper.rs

1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::PaperId;
7
8/// Canonical, deduplicated paper record.
9///
10/// A paper exists once regardless of how many searches found it.
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct Paper {
13    pub id: PaperId,
14    pub title: String,
15    pub authors: Vec<String>,
16    #[serde(default)]
17    pub r#abstract: String,
18    pub full_text: Option<String>,
19    pub summary: Option<String>,
20    pub doi: Option<String>,
21    pub arxiv_id: Option<String>,
22    pub pubmed_id: Option<String>,
23    pub inspire_id: Option<String>,
24    pub openalex_id: Option<String>,
25    pub year: Option<i32>,
26    pub journal: Option<String>,
27    pub url: Option<String>,
28    #[serde(default)]
29    pub source_urls: HashMap<String, String>,
30    pub created_at: DateTime<Utc>,
31    pub updated_at: DateTime<Utc>,
32}
33
34impl Paper {
35    #[must_use]
36    pub fn new(title: impl Into<String>) -> Self {
37        let now = Utc::now();
38        Self {
39            id: PaperId::new(),
40            title: title.into(),
41            authors: Vec::new(),
42            r#abstract: String::new(),
43            full_text: None,
44            summary: None,
45            doi: None,
46            arxiv_id: None,
47            pubmed_id: None,
48            inspire_id: None,
49            openalex_id: None,
50            year: None,
51            journal: None,
52            url: None,
53            source_urls: HashMap::new(),
54            created_at: now,
55            updated_at: now,
56        }
57    }
58}
59
60/// Un-deduplicated paper record from a single source adapter.
61///
62/// Adapters produce candidates; the dedup engine merges them into Papers.
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct CandidatePaper {
65    pub source: String,
66    pub source_id: String,
67    pub title: String,
68    #[serde(default)]
69    pub authors: Vec<String>,
70    #[serde(default)]
71    pub r#abstract: String,
72    pub doi: Option<String>,
73    pub arxiv_id: Option<String>,
74    pub pubmed_id: Option<String>,
75    pub inspire_id: Option<String>,
76    pub openalex_id: Option<String>,
77    pub year: Option<i32>,
78    pub journal: Option<String>,
79    pub url: Option<String>,
80    pub rank: Option<i32>,
81    pub score: Option<f64>,
82    #[serde(default)]
83    pub raw_data: serde_json::Value,
84}
85
86impl CandidatePaper {
87    #[must_use]
88    pub fn new(
89        source: impl Into<String>,
90        source_id: impl Into<String>,
91        title: impl Into<String>,
92    ) -> Self {
93        Self {
94            source: source.into(),
95            source_id: source_id.into(),
96            title: title.into(),
97            authors: Vec::new(),
98            r#abstract: String::new(),
99            doi: None,
100            arxiv_id: None,
101            pubmed_id: None,
102            inspire_id: None,
103            openalex_id: None,
104            year: None,
105            journal: None,
106            url: None,
107            rank: None,
108            score: None,
109            raw_data: serde_json::Value::Null,
110        }
111    }
112}