optirs_core/research/
publications.rs

1// Publication generation and management for academic research
2//
3// This module provides tools for generating academic publications from experimental
4// results, managing bibliographies, and formatting papers for various venues.
5
6use crate::error::Result;
7use crate::research::experiments::{Experiment, ExperimentResult};
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::path::PathBuf;
12
13/// Academic publication representation
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Publication {
16    /// Publication identifier
17    pub id: String,
18    /// Publication title
19    pub title: String,
20    /// Publication abstract
21    pub abstracttext: String,
22    /// Authors
23    pub authors: Vec<Author>,
24    /// Publication type
25    pub publication_type: PublicationType,
26    /// Venue information
27    pub venue: Option<Venue>,
28    /// Publication status
29    pub status: PublicationStatus,
30    /// Keywords
31    pub keywords: Vec<String>,
32    /// Manuscript sections
33    pub sections: Vec<ManuscriptSection>,
34    /// Bibliography
35    pub bibliography: Bibliography,
36    /// Associated experiments
37    pub experiment_ids: Vec<String>,
38    /// Submission history
39    pub submission_history: Vec<SubmissionRecord>,
40    /// Review information
41    pub reviews: Vec<Review>,
42    /// Publication metadata
43    pub metadata: PublicationMetadata,
44    /// Creation timestamp
45    pub created_at: DateTime<Utc>,
46    /// Last modified timestamp
47    pub modified_at: DateTime<Utc>,
48}
49
50/// Author information
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Author {
53    /// Full name
54    pub name: String,
55    /// Email address
56    pub email: String,
57    /// Affiliations
58    pub affiliations: Vec<Affiliation>,
59    /// ORCID identifier
60    pub orcid: Option<String>,
61    /// Author position (first, corresponding, etc.)
62    pub position: AuthorPosition,
63    /// Contribution description
64    pub contributions: Vec<String>,
65}
66
67/// Author affiliation
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Affiliation {
70    /// Institution name
71    pub institution: String,
72    /// Department
73    pub department: Option<String>,
74    /// Address
75    pub address: String,
76    /// Country
77    pub country: String,
78}
79
80/// Author position/role
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82pub enum AuthorPosition {
83    /// First author
84    First,
85    /// Corresponding author
86    Corresponding,
87    /// Senior author
88    Senior,
89    /// Equal contribution
90    EqualContribution,
91    /// Regular author
92    Regular,
93}
94
95/// Publication types
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
97pub enum PublicationType {
98    /// Conference paper
99    ConferencePaper,
100    /// Journal article
101    JournalArticle,
102    /// Workshop paper
103    WorkshopPaper,
104    /// Technical report
105    TechnicalReport,
106    /// Preprint
107    Preprint,
108    /// Thesis
109    Thesis,
110    /// Book chapter
111    BookChapter,
112    /// Patent
113    Patent,
114    /// Software paper
115    SoftwarePaper,
116    /// Dataset paper
117    DatasetPaper,
118}
119
120/// Publication venue
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct Venue {
123    /// Venue name
124    pub name: String,
125    /// Venue type
126    pub venue_type: VenueType,
127    /// Abbreviation
128    pub abbreviation: Option<String>,
129    /// Publisher
130    pub publisher: Option<String>,
131    /// Impact factor
132    pub impact_factor: Option<f64>,
133    /// H-index
134    pub h_index: Option<u32>,
135    /// Acceptance rate
136    pub acceptance_rate: Option<f64>,
137    /// Ranking (A*, A, B, C)
138    pub ranking: Option<String>,
139    /// Venue URL
140    pub url: Option<String>,
141}
142
143/// Venue types
144#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
145pub enum VenueType {
146    /// Academic conference
147    Conference,
148    /// Academic journal
149    Journal,
150    /// Workshop
151    Workshop,
152    /// Symposium
153    Symposium,
154    /// Preprint server
155    PreprintServer,
156    /// Repository
157    Repository,
158}
159
160/// Publication status
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
162pub enum PublicationStatus {
163    /// Draft in preparation
164    Draft,
165    /// Ready for submission
166    ReadyForSubmission,
167    /// Submitted
168    Submitted,
169    /// Under review
170    UnderReview,
171    /// Revision requested
172    RevisionRequested,
173    /// Accepted
174    Accepted,
175    /// Published
176    Published,
177    /// Rejected
178    Rejected,
179    /// Withdrawn
180    Withdrawn,
181}
182
183/// Manuscript section
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ManuscriptSection {
186    /// Section title
187    pub title: String,
188    /// Section content
189    pub content: String,
190    /// Section order
191    pub order: usize,
192    /// Section type
193    pub section_type: SectionType,
194    /// Word count
195    pub word_count: usize,
196    /// Figures and tables
197    pub figures: Vec<Figure>,
198    pub tables: Vec<Table>,
199    /// References in this section
200    pub references: Vec<String>,
201}
202
203/// Section types
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
205pub enum SectionType {
206    /// Abstract
207    Abstract,
208    /// Introduction
209    Introduction,
210    /// Background/Related Work
211    RelatedWork,
212    /// Methodology
213    Methodology,
214    /// Experiments
215    Experiments,
216    /// Results
217    Results,
218    /// Discussion
219    Discussion,
220    /// Conclusion
221    Conclusion,
222    /// Acknowledgments
223    Acknowledgments,
224    /// References
225    References,
226    /// Appendix
227    Appendix,
228    /// Custom section
229    Custom(String),
230}
231
232/// Figure information
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct Figure {
235    /// Figure caption
236    pub caption: String,
237    /// Figure file path
238    pub file_path: PathBuf,
239    /// Figure type
240    pub figure_type: FigureType,
241    /// Figure number
242    pub number: usize,
243    /// Width (in publication units)
244    pub width: Option<f64>,
245    /// Height (in publication units)
246    pub height: Option<f64>,
247    /// Associated experiment ID
248    pub experiment_id: Option<String>,
249}
250
251/// Figure types
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
253pub enum FigureType {
254    /// Plot/graph
255    Plot,
256    /// Diagram
257    Diagram,
258    /// Algorithm flowchart
259    Flowchart,
260    /// Architecture diagram
261    Architecture,
262    /// Screenshot
263    Screenshot,
264    /// Photo
265    Photo,
266    /// Other
267    Other,
268}
269
270/// Table information
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct Table {
273    /// Table caption
274    pub caption: String,
275    /// Table data
276    pub data: Vec<Vec<String>>,
277    /// Column headers
278    pub headers: Vec<String>,
279    /// Table number
280    pub number: usize,
281    /// Associated experiment ID
282    pub experiment_id: Option<String>,
283}
284
285/// Bibliography management
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct Bibliography {
288    /// BibTeX entries
289    pub entries: HashMap<String, BibTeXEntry>,
290    /// Citation style
291    pub citation_style: CitationStyle,
292    /// Bibliography file path
293    pub file_path: Option<PathBuf>,
294}
295
296/// BibTeX entry
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct BibTeXEntry {
299    /// Entry key
300    pub key: String,
301    /// Entry type (article, inproceedings, etc.)
302    pub entry_type: String,
303    /// Fields (title, author, year, etc.)
304    pub fields: HashMap<String, String>,
305}
306
307/// Citation styles
308#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
309pub enum CitationStyle {
310    /// APA style
311    APA,
312    /// IEEE style
313    IEEE,
314    /// ACM style
315    ACM,
316    /// Nature style
317    Nature,
318    /// Science style
319    Science,
320    /// Chicago style
321    Chicago,
322    /// MLA style
323    MLA,
324    /// Harvard style
325    Harvard,
326    /// Custom style
327    Custom(String),
328}
329
330/// Submission record
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct SubmissionRecord {
333    /// Submission timestamp
334    pub submitted_at: DateTime<Utc>,
335    /// Venue submitted to
336    pub venue: Venue,
337    /// Submission ID
338    pub submission_id: Option<String>,
339    /// Submission status
340    pub status: SubmissionStatus,
341    /// Decision date
342    pub decision_date: Option<DateTime<Utc>>,
343    /// Decision outcome
344    pub decision: Option<Decision>,
345    /// Comments from editors
346    pub editor_comments: Option<String>,
347}
348
349/// Submission status
350#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
351pub enum SubmissionStatus {
352    /// Submitted
353    Submitted,
354    /// Under review
355    UnderReview,
356    /// Decision made
357    Decided,
358    /// Withdrawn
359    Withdrawn,
360}
361
362/// Review decision
363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
364pub enum Decision {
365    /// Accept
366    Accept,
367    /// Accept with minor revisions
368    AcceptMinorRevisions,
369    /// Accept with major revisions
370    AcceptMajorRevisions,
371    /// Reject and resubmit
372    RejectAndResubmit,
373    /// Reject
374    Reject,
375}
376
377/// Review information
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct Review {
380    /// Review ID
381    pub id: String,
382    /// Reviewer information (anonymous)
383    pub reviewer: ReviewerInfo,
384    /// Overall score
385    pub overall_score: Option<f64>,
386    /// Confidence score
387    pub confidence_score: Option<f64>,
388    /// Detailed scores
389    pub detailed_scores: HashMap<String, f64>,
390    /// Written review
391    pub reviewtext: String,
392    /// Strengths
393    pub strengths: Vec<String>,
394    /// Weaknesses
395    pub weaknesses: Vec<String>,
396    /// Questions for authors
397    pub questions: Vec<String>,
398    /// Recommendation
399    pub recommendation: ReviewRecommendation,
400    /// Review timestamp
401    pub submitted_at: DateTime<Utc>,
402}
403
404/// Reviewer information (anonymized)
405#[derive(Debug, Clone, Serialize, Deserialize)]
406pub struct ReviewerInfo {
407    /// Anonymous reviewer ID
408    pub anonymous_id: String,
409    /// Expertise level
410    pub expertise_level: ExpertiseLevel,
411    /// Research areas
412    pub research_areas: Vec<String>,
413}
414
415/// Expertise levels
416#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
417pub enum ExpertiseLevel {
418    /// Expert in the field
419    Expert,
420    /// Knowledgeable
421    Knowledgeable,
422    /// Some knowledge
423    SomeKnowledge,
424    /// Limited knowledge
425    LimitedKnowledge,
426}
427
428/// Review recommendation
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
430pub enum ReviewRecommendation {
431    /// Strong accept
432    StrongAccept,
433    /// Accept
434    Accept,
435    /// Weak accept
436    WeakAccept,
437    /// Borderline
438    Borderline,
439    /// Weak reject
440    WeakReject,
441    /// Reject
442    Reject,
443    /// Strong reject
444    StrongReject,
445}
446
447/// Publication metadata
448#[derive(Debug, Clone, Serialize, Deserialize, Default)]
449pub struct PublicationMetadata {
450    /// DOI
451    pub doi: Option<String>,
452    /// ArXiv ID
453    pub arxiv_id: Option<String>,
454    /// Page numbers
455    pub pages: Option<String>,
456    /// Volume
457    pub volume: Option<String>,
458    /// Issue/Number
459    pub issue: Option<String>,
460    /// Publication year
461    pub year: Option<u32>,
462    /// Publication month
463    pub month: Option<u32>,
464    /// ISBN/ISSN
465    pub isbn_issn: Option<String>,
466    /// License
467    pub license: Option<String>,
468    /// Open access status
469    pub open_access: bool,
470}
471
472/// Publication generator for creating publications from experiments
473#[derive(Debug)]
474pub struct PublicationGenerator {
475    /// Template repository
476    templates: HashMap<PublicationType, PublicationTemplate>,
477    /// Default citation style
478    default_citation_style: CitationStyle,
479    /// Output directory
480    output_dir: PathBuf,
481}
482
483/// Publication template
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct PublicationTemplate {
486    /// Template name
487    pub name: String,
488    /// Template sections
489    pub sections: Vec<SectionTemplate>,
490    /// Default formatting options
491    pub formatting: FormattingOptions,
492    /// Target venue constraints
493    pub venue_constraints: VenueConstraints,
494}
495
496/// Section template
497#[derive(Debug, Clone, Serialize, Deserialize)]
498pub struct SectionTemplate {
499    /// Section type
500    pub section_type: SectionType,
501    /// Template content
502    pub template: String,
503    /// Required fields
504    pub required_fields: Vec<String>,
505    /// Word count target
506    pub target_word_count: Option<usize>,
507}
508
509/// Formatting options
510#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct FormattingOptions {
512    /// Document format
513    pub format: DocumentFormat,
514    /// Font size
515    pub font_size: u32,
516    /// Line spacing
517    pub line_spacing: f64,
518    /// Margins (in cm)
519    pub margins: Margins,
520    /// Citation format
521    pub citation_format: CitationFormat,
522    /// Figure numbering
523    pub figure_numbering: NumberingStyle,
524    /// Table numbering
525    pub table_numbering: NumberingStyle,
526}
527
528/// Document formats
529#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
530pub enum DocumentFormat {
531    /// LaTeX
532    LaTeX,
533    /// Markdown
534    Markdown,
535    /// HTML
536    HTML,
537    /// Microsoft Word
538    Word,
539    /// PDF
540    PDF,
541}
542
543/// Page margins
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct Margins {
546    /// Top margin
547    pub top: f64,
548    /// Bottom margin
549    pub bottom: f64,
550    /// Left margin
551    pub left: f64,
552    /// Right margin
553    pub right: f64,
554}
555
556/// Citation format
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558pub enum CitationFormat {
559    /// Numbered citations \[1\]
560    Numbered,
561    /// Author-year citations (Author, 2023)
562    AuthorYear,
563    /// Superscript citations¹
564    Superscript,
565    /// Footnote citations
566    Footnote,
567}
568
569/// Numbering styles
570#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
571pub enum NumberingStyle {
572    /// Arabic numerals (1, 2, 3)
573    Arabic,
574    /// Roman numerals (I, II, III)
575    Roman,
576    /// Letters (a, b, c)
577    Letters,
578    /// No numbering
579    None,
580}
581
582/// Venue constraints
583#[derive(Debug, Clone, Serialize, Deserialize)]
584pub struct VenueConstraints {
585    /// Maximum word count
586    pub max_word_count: Option<usize>,
587    /// Maximum page count
588    pub max_page_count: Option<usize>,
589    /// Required sections
590    pub required_sections: Vec<SectionType>,
591    /// Forbidden sections
592    pub forbidden_sections: Vec<SectionType>,
593    /// Figure limits
594    pub max_figures: Option<usize>,
595    /// Table limits
596    pub max_tables: Option<usize>,
597    /// Reference limits
598    pub max_references: Option<usize>,
599}
600
601impl Publication {
602    /// Create a new publication
603    pub fn new(title: &str) -> Self {
604        let now = Utc::now();
605        Self {
606            id: uuid::Uuid::new_v4().to_string(),
607            title: title.to_string(),
608            abstracttext: String::new(),
609            authors: Vec::new(),
610            publication_type: PublicationType::ConferencePaper,
611            venue: None,
612            status: PublicationStatus::Draft,
613            keywords: Vec::new(),
614            sections: Vec::new(),
615            bibliography: Bibliography::new(),
616            experiment_ids: Vec::new(),
617            submission_history: Vec::new(),
618            reviews: Vec::new(),
619            metadata: PublicationMetadata::default(),
620            created_at: now,
621            modified_at: now,
622        }
623    }
624
625    /// Set publication abstract
626    pub fn abstracttext(mut self, abstracttext: &str) -> Self {
627        self.abstracttext = abstracttext.to_string();
628        self.modified_at = Utc::now();
629        self
630    }
631
632    /// Add an author
633    pub fn add_author(mut self, author: Author) -> Self {
634        self.authors.push(author);
635        self.modified_at = Utc::now();
636        self
637    }
638
639    /// Set publication type
640    pub fn publication_type(mut self, pubtype: PublicationType) -> Self {
641        self.publication_type = pubtype;
642        self.modified_at = Utc::now();
643        self
644    }
645
646    /// Set target venue
647    pub fn venue(mut self, venue: Venue) -> Self {
648        self.venue = Some(venue);
649        self.modified_at = Utc::now();
650        self
651    }
652
653    /// Add keywords
654    pub fn keywords(mut self, keywords: Vec<String>) -> Self {
655        self.keywords = keywords;
656        self.modified_at = Utc::now();
657        self
658    }
659
660    /// Associate with experiment
661    pub fn add_experiment(&mut self, experiment_id: &str) {
662        self.experiment_ids.push(experiment_id.to_string());
663        self.modified_at = Utc::now();
664    }
665
666    /// Add a manuscript section
667    pub fn add_section(&mut self, section: ManuscriptSection) {
668        self.sections.push(section);
669        self.modified_at = Utc::now();
670    }
671
672    /// Generate LaTeX document
673    pub fn generate_latex(&self) -> Result<String> {
674        let mut latex = String::new();
675
676        // Document class and packages
677        latex.push_str("\\documentclass[conference]{IEEEtran}\n");
678        latex.push_str("\\usepackage{graphicx}\n");
679        latex.push_str("\\usepackage{booktabs}\n");
680        latex.push_str("\\usepackage{amsmath}\n");
681        latex.push_str("\\usepackage{url}\n\n");
682
683        latex.push_str("\\begin{document}\n\n");
684
685        // Title and authors
686        latex.push_str(&format!("\\title{{{}}}\n\n", self.title));
687
688        latex.push_str("\\author{\n");
689        for (i, author) in self.authors.iter().enumerate() {
690            if i > 0 {
691                latex.push_str("\\and\n");
692            }
693            latex.push_str(&format!("\\IEEEauthorblockN{{{}}}\n", author.name));
694            if !author.affiliations.is_empty() {
695                latex.push_str(&format!(
696                    "\\IEEEauthorblockA{{{}}}\n",
697                    author.affiliations[0].institution
698                ));
699            }
700        }
701        latex.push_str("}\n\n");
702
703        latex.push_str("\\maketitle\n\n");
704
705        // Abstract
706        if !self.abstracttext.is_empty() {
707            latex.push_str("\\begin{abstract}\n");
708            latex.push_str(&self.abstracttext);
709            latex.push_str("\n\\end{abstract}\n\n");
710        }
711
712        // Keywords
713        if !self.keywords.is_empty() {
714            latex.push_str("\\begin{IEEEkeywords}\n");
715            latex.push_str(&self.keywords.join(", "));
716            latex.push_str("\n\\end{IEEEkeywords}\n\n");
717        }
718
719        // Sections
720        let mut sorted_sections = self.sections.clone();
721        sorted_sections.sort_by_key(|s| s.order);
722
723        for section in sorted_sections {
724            match section.section_type {
725                SectionType::Abstract => continue,   // Already handled
726                SectionType::References => continue, // Handle at end
727                _ => {
728                    latex.push_str(&format!("\\section{{{}}}\n", section.title));
729                    latex.push_str(&section.content);
730                    latex.push_str("\n\n");
731                }
732            }
733        }
734
735        // Bibliography
736        if !self.bibliography.entries.is_empty() {
737            latex.push_str("\\begin{thebibliography}{99}\n");
738            for entry in self.bibliography.entries.values() {
739                latex.push_str(&self.format_bibtex_entry_latex(entry));
740            }
741            latex.push_str("\\end{thebibliography}\n\n");
742        }
743
744        latex.push_str("\\end{document}\n");
745
746        Ok(latex)
747    }
748
749    fn format_bibtex_entry_latex(&self, entry: &BibTeXEntry) -> String {
750        format!(
751            "\\bibitem{{{}}}\n{}\n\n",
752            entry.key,
753            self.format_bibtex_fields(&entry.fields)
754        )
755    }
756
757    fn format_bibtex_fields(&self, fields: &HashMap<String, String>) -> String {
758        let mut result = String::new();
759
760        if let Some(author) = fields.get("author") {
761            result.push_str(author);
762        }
763
764        if let Some(title) = fields.get("title") {
765            result.push_str(&format!(", ``{}'', ", title));
766        }
767
768        if let Some(journal) = fields.get("journal") {
769            result.push_str(&format!("\\emph{{{}}}, ", journal));
770        } else if let Some(booktitle) = fields.get("booktitle") {
771            result.push_str(&format!("in \\emph{{{}}}, ", booktitle));
772        }
773
774        if let Some(year) = fields.get("year") {
775            result.push_str(year);
776        }
777
778        result
779    }
780
781    /// Generate markdown document
782    pub fn generate_markdown(&self) -> Result<String> {
783        let mut markdown = String::new();
784
785        // Title
786        markdown.push_str(&format!("# {}\n\n", self.title));
787
788        // Authors
789        if !self.authors.is_empty() {
790            markdown.push_str("**Authors**: ");
791            let author_names: Vec<String> = self.authors.iter().map(|a| a.name.clone()).collect();
792            markdown.push_str(&author_names.join(", "));
793            markdown.push_str("\n\n");
794        }
795
796        // Abstract
797        if !self.abstracttext.is_empty() {
798            markdown.push_str("## Abstract\n\n");
799            markdown.push_str(&self.abstracttext);
800            markdown.push_str("\n\n");
801        }
802
803        // Keywords
804        if !self.keywords.is_empty() {
805            markdown.push_str("**Keywords**: ");
806            markdown.push_str(&self.keywords.join(", "));
807            markdown.push_str("\n\n");
808        }
809
810        // Sections
811        let mut sorted_sections = self.sections.clone();
812        sorted_sections.sort_by_key(|s| s.order);
813
814        for section in sorted_sections {
815            match section.section_type {
816                SectionType::Abstract => continue, // Already handled
817                _ => {
818                    markdown.push_str(&format!("## {}\n\n", section.title));
819                    markdown.push_str(&section.content);
820                    markdown.push_str("\n\n");
821                }
822            }
823        }
824
825        // References
826        if !self.bibliography.entries.is_empty() {
827            markdown.push_str("## References\n\n");
828            for (i, entry) in self.bibliography.entries.values().enumerate() {
829                markdown.push_str(&format!(
830                    "{}. {}\n",
831                    i + 1,
832                    self.format_bibtex_entry_markdown(entry)
833                ));
834            }
835        }
836
837        Ok(markdown)
838    }
839
840    fn format_bibtex_entry_markdown(&self, entry: &BibTeXEntry) -> String {
841        let mut result = String::new();
842
843        if let Some(author) = entry.fields.get("author") {
844            result.push_str(author);
845        }
846
847        if let Some(title) = entry.fields.get("title") {
848            result.push_str(&format!(". \"{}\". ", title));
849        }
850
851        if let Some(journal) = entry.fields.get("journal") {
852            result.push_str(&format!("*{}*. ", journal));
853        } else if let Some(booktitle) = entry.fields.get("booktitle") {
854            result.push_str(&format!("In *{}*. ", booktitle));
855        }
856
857        if let Some(year) = entry.fields.get("year") {
858            result.push_str(year);
859        }
860
861        result
862    }
863
864    /// Generate submission statistics
865    pub fn submission_statistics(&self) -> SubmissionStatistics {
866        let total_submissions = self.submission_history.len();
867        let accepted = self
868            .submission_history
869            .iter()
870            .filter(|s| {
871                matches!(
872                    s.decision,
873                    Some(Decision::Accept)
874                        | Some(Decision::AcceptMinorRevisions)
875                        | Some(Decision::AcceptMajorRevisions)
876                )
877            })
878            .count();
879        let rejected = self
880            .submission_history
881            .iter()
882            .filter(|s| matches!(s.decision, Some(Decision::Reject)))
883            .count();
884
885        let avg_review_time = if !self.submission_history.is_empty() {
886            let total_days: i64 = self
887                .submission_history
888                .iter()
889                .filter_map(|s| {
890                    s.decision_date
891                        .map(|decision_date| (decision_date - s.submitted_at).num_days())
892                })
893                .sum();
894            total_days as f64 / self.submission_history.len() as f64
895        } else {
896            0.0
897        };
898
899        SubmissionStatistics {
900            total_submissions,
901            accepted,
902            rejected,
903            pending: total_submissions - accepted - rejected,
904            acceptance_rate: if total_submissions > 0 {
905                accepted as f64 / total_submissions as f64
906            } else {
907                0.0
908            },
909            avg_review_time_days: avg_review_time,
910        }
911    }
912}
913
914/// Submission statistics
915#[derive(Debug, Clone, Serialize, Deserialize)]
916pub struct SubmissionStatistics {
917    /// Total number of submissions
918    pub total_submissions: usize,
919    /// Number of accepted submissions
920    pub accepted: usize,
921    /// Number of rejected submissions
922    pub rejected: usize,
923    /// Number of pending submissions
924    pub pending: usize,
925    /// Acceptance rate (0.0 to 1.0)
926    pub acceptance_rate: f64,
927    /// Average review time in days
928    pub avg_review_time_days: f64,
929}
930
931impl Default for Bibliography {
932    fn default() -> Self {
933        Self::new()
934    }
935}
936
937impl Bibliography {
938    /// Create a new bibliography
939    pub fn new() -> Self {
940        Self {
941            entries: HashMap::new(),
942            citation_style: CitationStyle::IEEE,
943            file_path: None,
944        }
945    }
946
947    /// Add a BibTeX entry
948    pub fn add_entry(&mut self, entry: BibTeXEntry) {
949        self.entries.insert(entry.key.clone(), entry);
950    }
951
952    /// Load from BibTeX file
953    pub fn load_bibtex_file(&mut self, filepath: &PathBuf) -> Result<()> {
954        let content = std::fs::read_to_string(filepath)?;
955        self.parse_bibtex(&content)?;
956        self.file_path = Some(filepath.clone());
957        Ok(())
958    }
959
960    /// Parse BibTeX content
961    pub fn parse_bibtex(&mut self, content: &str) -> Result<()> {
962        // Simplified BibTeX parser
963        // In a real implementation, you'd want a proper BibTeX parser
964        let lines: Vec<&str> = content.lines().collect();
965        let mut current_entry: Option<BibTeXEntry> = None;
966
967        for line in lines {
968            let line = line.trim();
969            if line.starts_with('@') {
970                // Save previous entry
971                if let Some(entry) = current_entry.take() {
972                    self.entries.insert(entry.key.clone(), entry);
973                }
974
975                // Start new entry
976                if let Some(pos) = line.find('{') {
977                    let entry_type = line[1..pos].to_lowercase();
978                    let key_part = &line[pos + 1..];
979                    if let Some(comma_pos) = key_part.find(',') {
980                        let key = key_part[..comma_pos].trim().to_string();
981                        current_entry = Some(BibTeXEntry {
982                            key,
983                            entry_type,
984                            fields: HashMap::new(),
985                        });
986                    }
987                }
988            } else if line.contains('=') && current_entry.is_some() {
989                // Parse field
990                if let Some(eq_pos) = line.find('=') {
991                    let field_name = line[..eq_pos].trim().to_lowercase();
992                    let field_value = line[eq_pos + 1..]
993                        .trim()
994                        .trim_start_matches('{')
995                        .trim_end_matches("},")
996                        .trim_start_matches('"')
997                        .trim_end_matches("\",")
998                        .to_string();
999
1000                    if let Some(ref mut entry) = current_entry {
1001                        entry.fields.insert(field_name, field_value);
1002                    }
1003                }
1004            }
1005        }
1006
1007        // Save last entry
1008        if let Some(entry) = current_entry {
1009            self.entries.insert(entry.key.clone(), entry);
1010        }
1011
1012        Ok(())
1013    }
1014}
1015
1016impl PublicationGenerator {
1017    /// Create a new publication generator
1018    pub fn new(_outputdir: PathBuf) -> Self {
1019        Self {
1020            templates: HashMap::new(),
1021            default_citation_style: CitationStyle::IEEE,
1022            output_dir: _outputdir,
1023        }
1024    }
1025
1026    /// Generate publication from experiments
1027    pub fn generate_from_experiments(
1028        &self,
1029        experiments: &[Experiment],
1030        template: &PublicationTemplate,
1031    ) -> Result<Publication> {
1032        let mut publication = Publication::new("Generated Publication");
1033
1034        // Generate abstract from experiments
1035        let abstracttext = self.generate_abstract(experiments)?;
1036        publication.abstracttext = abstracttext;
1037
1038        // Generate sections
1039        for section_template in &template.sections {
1040            let section = self.generate_section(section_template, experiments)?;
1041            publication.add_section(section);
1042        }
1043
1044        Ok(publication)
1045    }
1046
1047    fn generate_abstract(&self, experiments: &[Experiment]) -> Result<String> {
1048        // Generate abstract based on experiments
1049        let mut abstracttext = String::new();
1050
1051        abstracttext.push_str(
1052            "This paper presents experimental results comparing various optimization algorithms. ",
1053        );
1054
1055        if !experiments.is_empty() {
1056            abstracttext.push_str(&format!(
1057                "We conducted {} experiments evaluating the performance of different optimizers. ",
1058                experiments.len()
1059            ));
1060        }
1061
1062        abstracttext.push_str("Our results demonstrate significant differences in convergence behavior and final performance across different optimization methods.");
1063
1064        Ok(abstracttext)
1065    }
1066
1067    fn generate_section(
1068        &self,
1069        template: &SectionTemplate,
1070        experiments: &[Experiment],
1071    ) -> Result<ManuscriptSection> {
1072        let content = match template.section_type {
1073            SectionType::Introduction => self.generate_introduction(experiments)?,
1074            SectionType::Methodology => self.generate_methodology(experiments)?,
1075            SectionType::Experiments => self.generate_experiments_section(experiments)?,
1076            SectionType::Results => self.generate_results(experiments)?,
1077            SectionType::Conclusion => self.generate_conclusion(experiments)?,
1078            _ => template.template.clone(),
1079        };
1080
1081        Ok(ManuscriptSection {
1082            title: match template.section_type {
1083                SectionType::Abstract => "Abstract".to_string(),
1084                SectionType::Introduction => "Introduction".to_string(),
1085                SectionType::RelatedWork => "Related Work".to_string(),
1086                SectionType::Methodology => "Methodology".to_string(),
1087                SectionType::Experiments => "Experiments".to_string(),
1088                SectionType::Results => "Results".to_string(),
1089                SectionType::Discussion => "Discussion".to_string(),
1090                SectionType::Conclusion => "Conclusion".to_string(),
1091                SectionType::Acknowledgments => "Acknowledgments".to_string(),
1092                SectionType::References => "References".to_string(),
1093                SectionType::Appendix => "Appendix".to_string(),
1094                SectionType::Custom(ref name) => name.clone(),
1095            },
1096            content,
1097            order: 0, // Will be set based on section type
1098            section_type: template.section_type.clone(),
1099            word_count: 0, // Will be calculated
1100            figures: Vec::new(),
1101            tables: Vec::new(),
1102            references: Vec::new(),
1103        })
1104    }
1105
1106    fn generate_introduction(&self, experiments: &[Experiment]) -> Result<String> {
1107        Ok("This section introduces the research problem and motivation for comparing optimization algorithms.".to_string())
1108    }
1109
1110    fn generate_methodology(&self, experiments: &[Experiment]) -> Result<String> {
1111        let mut content = String::new();
1112        content.push_str("We evaluate the following optimization algorithms:\n\n");
1113
1114        for experiment in experiments {
1115            for optimizer_name in experiment.optimizer_configs.keys() {
1116                content.push_str(&format!("- {}\n", optimizer_name));
1117            }
1118        }
1119
1120        Ok(content)
1121    }
1122
1123    fn generate_experiments_section(&self, experiments: &[Experiment]) -> Result<String> {
1124        let mut content = String::new();
1125        content.push_str("We conducted the following experiments:\n\n");
1126
1127        for experiment in experiments {
1128            content.push_str(&format!(
1129                "**{}**: {}\n\n",
1130                experiment.name, experiment.description
1131            ));
1132        }
1133
1134        Ok(content)
1135    }
1136
1137    fn generate_results(&self, experiments: &[Experiment]) -> Result<String> {
1138        let mut content = String::new();
1139        content.push_str("The experimental results are summarized below:\n\n");
1140
1141        for experiment in experiments {
1142            if !experiment.results.is_empty() {
1143                content.push_str(&format!("### {}\n\n", experiment.name));
1144                content.push_str(&format!("Number of runs: {}\n\n", experiment.results.len()));
1145            }
1146        }
1147
1148        Ok(content)
1149    }
1150
1151    fn generate_conclusion(&self, experiments: &[Experiment]) -> Result<String> {
1152        Ok("This section summarizes the key findings and implications of the experimental results.".to_string())
1153    }
1154}
1155
1156#[cfg(test)]
1157mod tests {
1158    use super::*;
1159
1160    #[test]
1161    fn test_publication_creation() {
1162        let publication = Publication::new("Test Publication")
1163            .abstracttext("Test abstract")
1164            .publication_type(PublicationType::ConferencePaper)
1165            .keywords(vec![
1166                "optimization".to_string(),
1167                "machine learning".to_string(),
1168            ]);
1169
1170        assert_eq!(publication.title, "Test Publication");
1171        assert_eq!(publication.abstracttext, "Test abstract");
1172        assert_eq!(
1173            publication.publication_type,
1174            PublicationType::ConferencePaper
1175        );
1176        assert_eq!(publication.keywords.len(), 2);
1177    }
1178
1179    #[test]
1180    fn test_bibliography() {
1181        let mut bibliography = Bibliography::new();
1182
1183        let entry = BibTeXEntry {
1184            key: "test2023".to_string(),
1185            entry_type: "article".to_string(),
1186            fields: {
1187                let mut fields = HashMap::new();
1188                fields.insert("author".to_string(), "Test Author".to_string());
1189                fields.insert("title".to_string(), "Test Title".to_string());
1190                fields.insert("year".to_string(), "2023".to_string());
1191                fields
1192            },
1193        };
1194
1195        bibliography.add_entry(entry);
1196        assert_eq!(bibliography.entries.len(), 1);
1197        assert!(bibliography.entries.contains_key("test2023"));
1198    }
1199
1200    #[test]
1201    fn test_markdown_generation() {
1202        let mut publication = Publication::new("Test Paper");
1203        publication.abstracttext = "This is a test abstract.".to_string();
1204        publication.keywords = vec!["test".to_string(), "paper".to_string()];
1205
1206        let markdown = publication.generate_markdown().unwrap();
1207        assert!(markdown.contains("# Test Paper"));
1208        assert!(markdown.contains("## Abstract"));
1209        assert!(markdown.contains("This is a test abstract."));
1210        assert!(markdown.contains("**Keywords**: test, paper"));
1211    }
1212}