use crate::error::Result;
use crate::research::experiments::{Experiment, ExperimentResult};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Publication {
pub id: String,
pub title: String,
pub abstracttext: String,
pub authors: Vec<Author>,
pub publication_type: PublicationType,
pub venue: Option<Venue>,
pub status: PublicationStatus,
pub keywords: Vec<String>,
pub sections: Vec<ManuscriptSection>,
pub bibliography: Bibliography,
pub experiment_ids: Vec<String>,
pub submission_history: Vec<SubmissionRecord>,
pub reviews: Vec<Review>,
pub metadata: PublicationMetadata,
pub created_at: DateTime<Utc>,
pub modified_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Author {
pub name: String,
pub email: String,
pub affiliations: Vec<Affiliation>,
pub orcid: Option<String>,
pub position: AuthorPosition,
pub contributions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Affiliation {
pub institution: String,
pub department: Option<String>,
pub address: String,
pub country: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum AuthorPosition {
First,
Corresponding,
Senior,
EqualContribution,
Regular,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum PublicationType {
ConferencePaper,
JournalArticle,
WorkshopPaper,
TechnicalReport,
Preprint,
Thesis,
BookChapter,
Patent,
SoftwarePaper,
DatasetPaper,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Venue {
pub name: String,
pub venue_type: VenueType,
pub abbreviation: Option<String>,
pub publisher: Option<String>,
pub impact_factor: Option<f64>,
pub h_index: Option<u32>,
pub acceptance_rate: Option<f64>,
pub ranking: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum VenueType {
Conference,
Journal,
Workshop,
Symposium,
PreprintServer,
Repository,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum PublicationStatus {
Draft,
ReadyForSubmission,
Submitted,
UnderReview,
RevisionRequested,
Accepted,
Published,
Rejected,
Withdrawn,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ManuscriptSection {
pub title: String,
pub content: String,
pub order: usize,
pub section_type: SectionType,
pub word_count: usize,
pub figures: Vec<Figure>,
pub tables: Vec<Table>,
pub references: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SectionType {
Abstract,
Introduction,
RelatedWork,
Methodology,
Experiments,
Results,
Discussion,
Conclusion,
Acknowledgments,
References,
Appendix,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Figure {
pub caption: String,
pub file_path: PathBuf,
pub figure_type: FigureType,
pub number: usize,
pub width: Option<f64>,
pub height: Option<f64>,
pub experiment_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum FigureType {
Plot,
Diagram,
Flowchart,
Architecture,
Screenshot,
Photo,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Table {
pub caption: String,
pub data: Vec<Vec<String>>,
pub headers: Vec<String>,
pub number: usize,
pub experiment_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bibliography {
pub entries: HashMap<String, BibTeXEntry>,
pub citation_style: CitationStyle,
pub file_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BibTeXEntry {
pub key: String,
pub entry_type: String,
pub fields: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CitationStyle {
APA,
IEEE,
ACM,
Nature,
Science,
Chicago,
MLA,
Harvard,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmissionRecord {
pub submitted_at: DateTime<Utc>,
pub venue: Venue,
pub submission_id: Option<String>,
pub status: SubmissionStatus,
pub decision_date: Option<DateTime<Utc>>,
pub decision: Option<Decision>,
pub editor_comments: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SubmissionStatus {
Submitted,
UnderReview,
Decided,
Withdrawn,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Decision {
Accept,
AcceptMinorRevisions,
AcceptMajorRevisions,
RejectAndResubmit,
Reject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Review {
pub id: String,
pub reviewer: ReviewerInfo,
pub overall_score: Option<f64>,
pub confidence_score: Option<f64>,
pub detailed_scores: HashMap<String, f64>,
pub reviewtext: String,
pub strengths: Vec<String>,
pub weaknesses: Vec<String>,
pub questions: Vec<String>,
pub recommendation: ReviewRecommendation,
pub submitted_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewerInfo {
pub anonymous_id: String,
pub expertise_level: ExpertiseLevel,
pub research_areas: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ExpertiseLevel {
Expert,
Knowledgeable,
SomeKnowledge,
LimitedKnowledge,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReviewRecommendation {
StrongAccept,
Accept,
WeakAccept,
Borderline,
WeakReject,
Reject,
StrongReject,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PublicationMetadata {
pub doi: Option<String>,
pub arxiv_id: Option<String>,
pub pages: Option<String>,
pub volume: Option<String>,
pub issue: Option<String>,
pub year: Option<u32>,
pub month: Option<u32>,
pub isbn_issn: Option<String>,
pub license: Option<String>,
pub open_access: bool,
}
#[derive(Debug)]
pub struct PublicationGenerator {
templates: HashMap<PublicationType, PublicationTemplate>,
default_citation_style: CitationStyle,
output_dir: PathBuf,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicationTemplate {
pub name: String,
pub sections: Vec<SectionTemplate>,
pub formatting: FormattingOptions,
pub venue_constraints: VenueConstraints,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SectionTemplate {
pub section_type: SectionType,
pub template: String,
pub required_fields: Vec<String>,
pub target_word_count: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormattingOptions {
pub format: DocumentFormat,
pub font_size: u32,
pub line_spacing: f64,
pub margins: Margins,
pub citation_format: CitationFormat,
pub figure_numbering: NumberingStyle,
pub table_numbering: NumberingStyle,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DocumentFormat {
LaTeX,
Markdown,
HTML,
Word,
PDF,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Margins {
pub top: f64,
pub bottom: f64,
pub left: f64,
pub right: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum CitationFormat {
Numbered,
AuthorYear,
Superscript,
Footnote,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NumberingStyle {
Arabic,
Roman,
Letters,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VenueConstraints {
pub max_word_count: Option<usize>,
pub max_page_count: Option<usize>,
pub required_sections: Vec<SectionType>,
pub forbidden_sections: Vec<SectionType>,
pub max_figures: Option<usize>,
pub max_tables: Option<usize>,
pub max_references: Option<usize>,
}
impl Publication {
pub fn new(title: &str) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
title: title.to_string(),
abstracttext: String::new(),
authors: Vec::new(),
publication_type: PublicationType::ConferencePaper,
venue: None,
status: PublicationStatus::Draft,
keywords: Vec::new(),
sections: Vec::new(),
bibliography: Bibliography::new(),
experiment_ids: Vec::new(),
submission_history: Vec::new(),
reviews: Vec::new(),
metadata: PublicationMetadata::default(),
created_at: now,
modified_at: now,
}
}
pub fn abstracttext(mut self, abstracttext: &str) -> Self {
self.abstracttext = abstracttext.to_string();
self.modified_at = Utc::now();
self
}
pub fn add_author(mut self, author: Author) -> Self {
self.authors.push(author);
self.modified_at = Utc::now();
self
}
pub fn publication_type(mut self, pubtype: PublicationType) -> Self {
self.publication_type = pubtype;
self.modified_at = Utc::now();
self
}
pub fn venue(mut self, venue: Venue) -> Self {
self.venue = Some(venue);
self.modified_at = Utc::now();
self
}
pub fn keywords(mut self, keywords: Vec<String>) -> Self {
self.keywords = keywords;
self.modified_at = Utc::now();
self
}
pub fn add_experiment(&mut self, experiment_id: &str) {
self.experiment_ids.push(experiment_id.to_string());
self.modified_at = Utc::now();
}
pub fn add_section(&mut self, section: ManuscriptSection) {
self.sections.push(section);
self.modified_at = Utc::now();
}
pub fn generate_latex(&self) -> Result<String> {
let mut latex = String::new();
latex.push_str("\\documentclass[conference]{IEEEtran}\n");
latex.push_str("\\usepackage{graphicx}\n");
latex.push_str("\\usepackage{booktabs}\n");
latex.push_str("\\usepackage{amsmath}\n");
latex.push_str("\\usepackage{url}\n\n");
latex.push_str("\\begin{document}\n\n");
latex.push_str(&format!("\\title{{{}}}\n\n", self.title));
latex.push_str("\\author{\n");
for (i, author) in self.authors.iter().enumerate() {
if i > 0 {
latex.push_str("\\and\n");
}
latex.push_str(&format!("\\IEEEauthorblockN{{{}}}\n", author.name));
if !author.affiliations.is_empty() {
latex.push_str(&format!(
"\\IEEEauthorblockA{{{}}}\n",
author.affiliations[0].institution
));
}
}
latex.push_str("}\n\n");
latex.push_str("\\maketitle\n\n");
if !self.abstracttext.is_empty() {
latex.push_str("\\begin{abstract}\n");
latex.push_str(&self.abstracttext);
latex.push_str("\n\\end{abstract}\n\n");
}
if !self.keywords.is_empty() {
latex.push_str("\\begin{IEEEkeywords}\n");
latex.push_str(&self.keywords.join(", "));
latex.push_str("\n\\end{IEEEkeywords}\n\n");
}
let mut sorted_sections = self.sections.clone();
sorted_sections.sort_by_key(|s| s.order);
for section in sorted_sections {
match section.section_type {
SectionType::Abstract => continue, SectionType::References => continue, _ => {
latex.push_str(&format!("\\section{{{}}}\n", section.title));
latex.push_str(§ion.content);
latex.push_str("\n\n");
}
}
}
if !self.bibliography.entries.is_empty() {
latex.push_str("\\begin{thebibliography}{99}\n");
for entry in self.bibliography.entries.values() {
latex.push_str(&self.format_bibtex_entry_latex(entry));
}
latex.push_str("\\end{thebibliography}\n\n");
}
latex.push_str("\\end{document}\n");
Ok(latex)
}
fn format_bibtex_entry_latex(&self, entry: &BibTeXEntry) -> String {
format!(
"\\bibitem{{{}}}\n{}\n\n",
entry.key,
self.format_bibtex_fields(&entry.fields)
)
}
fn format_bibtex_fields(&self, fields: &HashMap<String, String>) -> String {
let mut result = String::new();
if let Some(author) = fields.get("author") {
result.push_str(author);
}
if let Some(title) = fields.get("title") {
result.push_str(&format!(", ``{}'', ", title));
}
if let Some(journal) = fields.get("journal") {
result.push_str(&format!("\\emph{{{}}}, ", journal));
} else if let Some(booktitle) = fields.get("booktitle") {
result.push_str(&format!("in \\emph{{{}}}, ", booktitle));
}
if let Some(year) = fields.get("year") {
result.push_str(year);
}
result
}
pub fn generate_markdown(&self) -> Result<String> {
let mut markdown = String::new();
markdown.push_str(&format!("# {}\n\n", self.title));
if !self.authors.is_empty() {
markdown.push_str("**Authors**: ");
let author_names: Vec<String> = self.authors.iter().map(|a| a.name.clone()).collect();
markdown.push_str(&author_names.join(", "));
markdown.push_str("\n\n");
}
if !self.abstracttext.is_empty() {
markdown.push_str("## Abstract\n\n");
markdown.push_str(&self.abstracttext);
markdown.push_str("\n\n");
}
if !self.keywords.is_empty() {
markdown.push_str("**Keywords**: ");
markdown.push_str(&self.keywords.join(", "));
markdown.push_str("\n\n");
}
let mut sorted_sections = self.sections.clone();
sorted_sections.sort_by_key(|s| s.order);
for section in sorted_sections {
match section.section_type {
SectionType::Abstract => continue, _ => {
markdown.push_str(&format!("## {}\n\n", section.title));
markdown.push_str(§ion.content);
markdown.push_str("\n\n");
}
}
}
if !self.bibliography.entries.is_empty() {
markdown.push_str("## References\n\n");
for (i, entry) in self.bibliography.entries.values().enumerate() {
markdown.push_str(&format!(
"{}. {}\n",
i + 1,
self.format_bibtex_entry_markdown(entry)
));
}
}
Ok(markdown)
}
fn format_bibtex_entry_markdown(&self, entry: &BibTeXEntry) -> String {
let mut result = String::new();
if let Some(author) = entry.fields.get("author") {
result.push_str(author);
}
if let Some(title) = entry.fields.get("title") {
result.push_str(&format!(". \"{}\". ", title));
}
if let Some(journal) = entry.fields.get("journal") {
result.push_str(&format!("*{}*. ", journal));
} else if let Some(booktitle) = entry.fields.get("booktitle") {
result.push_str(&format!("In *{}*. ", booktitle));
}
if let Some(year) = entry.fields.get("year") {
result.push_str(year);
}
result
}
pub fn submission_statistics(&self) -> SubmissionStatistics {
let total_submissions = self.submission_history.len();
let accepted = self
.submission_history
.iter()
.filter(|s| {
matches!(
s.decision,
Some(Decision::Accept)
| Some(Decision::AcceptMinorRevisions)
| Some(Decision::AcceptMajorRevisions)
)
})
.count();
let rejected = self
.submission_history
.iter()
.filter(|s| matches!(s.decision, Some(Decision::Reject)))
.count();
let avg_review_time = if !self.submission_history.is_empty() {
let total_days: i64 = self
.submission_history
.iter()
.filter_map(|s| {
s.decision_date
.map(|decision_date| (decision_date - s.submitted_at).num_days())
})
.sum();
total_days as f64 / self.submission_history.len() as f64
} else {
0.0
};
SubmissionStatistics {
total_submissions,
accepted,
rejected,
pending: total_submissions - accepted - rejected,
acceptance_rate: if total_submissions > 0 {
accepted as f64 / total_submissions as f64
} else {
0.0
},
avg_review_time_days: avg_review_time,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmissionStatistics {
pub total_submissions: usize,
pub accepted: usize,
pub rejected: usize,
pub pending: usize,
pub acceptance_rate: f64,
pub avg_review_time_days: f64,
}
impl Default for Bibliography {
fn default() -> Self {
Self::new()
}
}
impl Bibliography {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
citation_style: CitationStyle::IEEE,
file_path: None,
}
}
pub fn add_entry(&mut self, entry: BibTeXEntry) {
self.entries.insert(entry.key.clone(), entry);
}
pub fn load_bibtex_file(&mut self, filepath: &PathBuf) -> Result<()> {
let content = std::fs::read_to_string(filepath)?;
self.parse_bibtex(&content)?;
self.file_path = Some(filepath.clone());
Ok(())
}
pub fn parse_bibtex(&mut self, content: &str) -> Result<()> {
let lines: Vec<&str> = content.lines().collect();
let mut current_entry: Option<BibTeXEntry> = None;
for line in lines {
let line = line.trim();
if line.starts_with('@') {
if let Some(entry) = current_entry.take() {
self.entries.insert(entry.key.clone(), entry);
}
if let Some(pos) = line.find('{') {
let entry_type = line[1..pos].to_lowercase();
let key_part = &line[pos + 1..];
if let Some(comma_pos) = key_part.find(',') {
let key = key_part[..comma_pos].trim().to_string();
current_entry = Some(BibTeXEntry {
key,
entry_type,
fields: HashMap::new(),
});
}
}
} else if line.contains('=') && current_entry.is_some() {
if let Some(eq_pos) = line.find('=') {
let field_name = line[..eq_pos].trim().to_lowercase();
let field_value = line[eq_pos + 1..]
.trim()
.trim_start_matches('{')
.trim_end_matches("},")
.trim_start_matches('"')
.trim_end_matches("\",")
.to_string();
if let Some(ref mut entry) = current_entry {
entry.fields.insert(field_name, field_value);
}
}
}
}
if let Some(entry) = current_entry {
self.entries.insert(entry.key.clone(), entry);
}
Ok(())
}
}
impl PublicationGenerator {
pub fn new(_outputdir: PathBuf) -> Self {
Self {
templates: HashMap::new(),
default_citation_style: CitationStyle::IEEE,
output_dir: _outputdir,
}
}
pub fn generate_from_experiments(
&self,
experiments: &[Experiment],
template: &PublicationTemplate,
) -> Result<Publication> {
let mut publication = Publication::new("Generated Publication");
let abstracttext = self.generate_abstract(experiments)?;
publication.abstracttext = abstracttext;
for section_template in &template.sections {
let section = self.generate_section(section_template, experiments)?;
publication.add_section(section);
}
Ok(publication)
}
fn generate_abstract(&self, experiments: &[Experiment]) -> Result<String> {
let mut abstracttext = String::new();
abstracttext.push_str(
"This paper presents experimental results comparing various optimization algorithms. ",
);
if !experiments.is_empty() {
abstracttext.push_str(&format!(
"We conducted {} experiments evaluating the performance of different optimizers. ",
experiments.len()
));
}
abstracttext.push_str("Our results demonstrate significant differences in convergence behavior and final performance across different optimization methods.");
Ok(abstracttext)
}
fn generate_section(
&self,
template: &SectionTemplate,
experiments: &[Experiment],
) -> Result<ManuscriptSection> {
let content = match template.section_type {
SectionType::Introduction => self.generate_introduction(experiments)?,
SectionType::Methodology => self.generate_methodology(experiments)?,
SectionType::Experiments => self.generate_experiments_section(experiments)?,
SectionType::Results => self.generate_results(experiments)?,
SectionType::Conclusion => self.generate_conclusion(experiments)?,
_ => template.template.clone(),
};
Ok(ManuscriptSection {
title: match template.section_type {
SectionType::Abstract => "Abstract".to_string(),
SectionType::Introduction => "Introduction".to_string(),
SectionType::RelatedWork => "Related Work".to_string(),
SectionType::Methodology => "Methodology".to_string(),
SectionType::Experiments => "Experiments".to_string(),
SectionType::Results => "Results".to_string(),
SectionType::Discussion => "Discussion".to_string(),
SectionType::Conclusion => "Conclusion".to_string(),
SectionType::Acknowledgments => "Acknowledgments".to_string(),
SectionType::References => "References".to_string(),
SectionType::Appendix => "Appendix".to_string(),
SectionType::Custom(ref name) => name.clone(),
},
content,
order: 0, section_type: template.section_type.clone(),
word_count: 0, figures: Vec::new(),
tables: Vec::new(),
references: Vec::new(),
})
}
fn generate_introduction(&self, experiments: &[Experiment]) -> Result<String> {
Ok("This section introduces the research problem and motivation for comparing optimization algorithms.".to_string())
}
fn generate_methodology(&self, experiments: &[Experiment]) -> Result<String> {
let mut content = String::new();
content.push_str("We evaluate the following optimization algorithms:\n\n");
for experiment in experiments {
for optimizer_name in experiment.optimizer_configs.keys() {
content.push_str(&format!("- {}\n", optimizer_name));
}
}
Ok(content)
}
fn generate_experiments_section(&self, experiments: &[Experiment]) -> Result<String> {
let mut content = String::new();
content.push_str("We conducted the following experiments:\n\n");
for experiment in experiments {
content.push_str(&format!(
"**{}**: {}\n\n",
experiment.name, experiment.description
));
}
Ok(content)
}
fn generate_results(&self, experiments: &[Experiment]) -> Result<String> {
let mut content = String::new();
content.push_str("The experimental results are summarized below:\n\n");
for experiment in experiments {
if !experiment.results.is_empty() {
content.push_str(&format!("### {}\n\n", experiment.name));
content.push_str(&format!("Number of runs: {}\n\n", experiment.results.len()));
}
}
Ok(content)
}
fn generate_conclusion(&self, experiments: &[Experiment]) -> Result<String> {
Ok("This section summarizes the key findings and implications of the experimental results.".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_publication_creation() {
let publication = Publication::new("Test Publication")
.abstracttext("Test abstract")
.publication_type(PublicationType::ConferencePaper)
.keywords(vec![
"optimization".to_string(),
"machine learning".to_string(),
]);
assert_eq!(publication.title, "Test Publication");
assert_eq!(publication.abstracttext, "Test abstract");
assert_eq!(
publication.publication_type,
PublicationType::ConferencePaper
);
assert_eq!(publication.keywords.len(), 2);
}
#[test]
fn test_bibliography() {
let mut bibliography = Bibliography::new();
let entry = BibTeXEntry {
key: "test2023".to_string(),
entry_type: "article".to_string(),
fields: {
let mut fields = HashMap::new();
fields.insert("author".to_string(), "Test Author".to_string());
fields.insert("title".to_string(), "Test Title".to_string());
fields.insert("year".to_string(), "2023".to_string());
fields
},
};
bibliography.add_entry(entry);
assert_eq!(bibliography.entries.len(), 1);
assert!(bibliography.entries.contains_key("test2023"));
}
#[test]
fn test_markdown_generation() {
let mut publication = Publication::new("Test Paper");
publication.abstracttext = "This is a test abstract.".to_string();
publication.keywords = vec!["test".to_string(), "paper".to_string()];
let markdown = publication.generate_markdown().expect("unwrap failed");
assert!(markdown.contains("# Test Paper"));
assert!(markdown.contains("## Abstract"));
assert!(markdown.contains("This is a test abstract."));
assert!(markdown.contains("**Keywords**: test, paper"));
}
}