Skip to main content

batuta/recipes/
research_artifact.rs

1//! Research artifact recipe implementation.
2
3use crate::experiment::{
4    CitationMetadata, CitationType, CreditRole, ResearchArtifact, ResearchContributor,
5};
6use crate::recipes::RecipeResult;
7
8/// Academic research artifact recipe
9#[derive(Debug)]
10pub struct ResearchArtifactRecipe {
11    artifact: ResearchArtifact,
12}
13
14impl ResearchArtifactRecipe {
15    /// Create a new research artifact recipe
16    pub fn new(title: impl Into<String>, abstract_text: impl Into<String>) -> Self {
17        Self {
18            artifact: ResearchArtifact {
19                title: title.into(),
20                abstract_text: abstract_text.into(),
21                contributors: Vec::new(),
22                keywords: Vec::new(),
23                doi: None,
24                arxiv_id: None,
25                license: "MIT".to_string(),
26                created_at: chrono::Utc::now().to_rfc3339(),
27                datasets: Vec::new(),
28                code_repositories: Vec::new(),
29                pre_registration: None,
30            },
31        }
32    }
33
34    /// Add a contributor
35    pub fn add_contributor(
36        &mut self,
37        name: impl Into<String>,
38        affiliation: impl Into<String>,
39        roles: Vec<CreditRole>,
40    ) {
41        self.artifact.contributors.push(ResearchContributor {
42            name: name.into(),
43            orcid: None,
44            affiliation: affiliation.into(),
45            roles,
46            email: None,
47        });
48    }
49
50    /// Add keywords
51    pub fn add_keywords(&mut self, keywords: Vec<String>) {
52        self.artifact.keywords.extend(keywords);
53    }
54
55    /// Set DOI
56    pub fn set_doi(&mut self, doi: impl Into<String>) {
57        self.artifact.doi = Some(doi.into());
58    }
59
60    /// Add dataset reference
61    pub fn add_dataset(&mut self, dataset: impl Into<String>) {
62        self.artifact.datasets.push(dataset.into());
63    }
64
65    /// Add code repository
66    pub fn add_repository(&mut self, repo: impl Into<String>) {
67        self.artifact.code_repositories.push(repo.into());
68    }
69
70    /// Generate citation metadata
71    pub fn generate_citation(&self) -> CitationMetadata {
72        let authors: Vec<String> =
73            self.artifact.contributors.iter().map(|c| c.name.clone()).collect();
74
75        let now = chrono::Utc::now();
76
77        CitationMetadata {
78            citation_type: CitationType::Software,
79            title: self.artifact.title.clone(),
80            authors,
81            year: now.format("%Y").to_string().parse().unwrap_or(2024),
82            month: Some(now.format("%m").to_string().parse().unwrap_or(1)),
83            doi: self.artifact.doi.clone(),
84            url: self.artifact.code_repositories.first().cloned(),
85            venue: None,
86            volume: None,
87            pages: None,
88            publisher: None,
89            version: Some("1.0.0".to_string()),
90        }
91    }
92
93    /// Build the research artifact
94    pub fn build(&self) -> RecipeResult {
95        let mut result = RecipeResult::success("research-artifact");
96        result = result.with_metric("contributor_count", self.artifact.contributors.len() as f64);
97        result = result.with_metric("keyword_count", self.artifact.keywords.len() as f64);
98        result = result.with_metric("dataset_count", self.artifact.datasets.len() as f64);
99
100        if self.artifact.doi.is_some() {
101            result = result.with_artifact("DOI registered");
102        }
103
104        let citation = self.generate_citation();
105        result = result.with_artifact(format!("BibTeX: {}", citation.to_bibtex("artifact")));
106
107        result
108    }
109
110    /// Get the artifact
111    pub fn artifact(&self) -> &ResearchArtifact {
112        &self.artifact
113    }
114}