use crate::error::{OptimError, Result};
use chrono::{DateTime, Datelike, TimeZone, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConferenceManager {
pub conferences: HashMap<String, Conference>,
pub submissions: Vec<Submission>,
pub alerts: Vec<DeadlineAlert>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Conference {
pub id: String,
pub name: String,
pub abbreviation: String,
pub description: String,
pub url: String,
pub ranking: ConferenceRanking,
pub research_areas: Vec<String>,
pub annual: bool,
pub series_info: SeriesInfo,
pub dates: ConferenceDates,
pub requirements: SubmissionRequirements,
pub review_process: ReviewProcess,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ConferenceRanking {
TopTier,
HighQuality,
Good,
Acceptable,
Emerging,
Workshop,
Unranked,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SeriesInfo {
pub series_number: u32,
pub year: u32,
pub location: String,
pub country: String,
pub format: ConferenceFormat,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ConferenceFormat {
InPerson,
Virtual,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConferenceDates {
pub abstract_deadline: Option<DateTime<Utc>>,
pub paper_deadline: DateTime<Utc>,
pub notification_date: DateTime<Utc>,
pub camera_ready_deadline: DateTime<Utc>,
pub conference_start: DateTime<Utc>,
pub conference_end: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmissionRequirements {
pub page_limit: u32,
pub word_limit: Option<u32>,
pub format: FormatRequirements,
pub required_sections: Vec<String>,
pub supplementary_allowed: bool,
pub anonymous_submission: bool,
pub double_blind: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormatRequirements {
pub template: String,
pub font_size: u32,
pub line_spacing: f64,
pub margins: String,
pub citation_style: String,
pub file_format: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewProcess {
pub reviewers_per_paper: u32,
pub review_criteria: Vec<String>,
pub rebuttal_allowed: bool,
pub acceptance_rate: Option<f64>,
pub review_format: ReviewFormat,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReviewFormat {
NumericalScores,
WrittenOnly,
Mixed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Submission {
pub id: String,
pub conference_id: String,
pub paper_id: String,
pub status: SubmissionStatus,
pub submitted_at: DateTime<Utc>,
pub track: Option<String>,
pub materials: SubmissionMaterials,
pub reviews: Vec<Review>,
pub decision: Option<Decision>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum SubmissionStatus {
Draft,
Submitted,
UnderReview,
Rebuttal,
Decided,
CameraReady,
Withdrawn,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubmissionMaterials {
pub paper_file: String,
pub supplementary_files: Vec<String>,
pub abstracttext: String,
pub keywords: Vec<String>,
pub authors: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Review {
pub id: String,
pub reviewer: String,
pub overall_score: Option<f64>,
pub detailed_scores: HashMap<String, f64>,
pub reviewtext: String,
pub recommendation: ReviewRecommendation,
pub reviewed_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReviewRecommendation {
StrongAccept,
Accept,
WeakAccept,
Borderline,
WeakReject,
Reject,
StrongReject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Decision {
pub outcome: DecisionOutcome,
pub decided_at: DateTime<Utc>,
pub editor_comments: Option<String>,
pub required_revisions: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DecisionOutcome {
Accept,
AcceptMinorRevisions,
AcceptMajorRevisions,
ConditionalAccept,
Reject,
RejectAndResubmit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeadlineAlert {
pub id: String,
pub conference_id: String,
pub deadline_type: DeadlineType,
pub alert_date: DateTime<Utc>,
pub days_before: u32,
pub message: String,
pub sent: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DeadlineType {
AbstractSubmission,
PaperSubmission,
Notification,
CameraReady,
ConferenceStart,
}
impl Default for ConferenceManager {
fn default() -> Self {
Self::new()
}
}
impl ConferenceManager {
pub fn new() -> Self {
Self {
conferences: HashMap::new(),
submissions: Vec::new(),
alerts: Vec::new(),
}
}
pub fn add_conference(&mut self, conference: Conference) {
self.conferences.insert(conference.id.clone(), conference);
}
pub fn submit_paper(
&mut self,
conference_id: &str,
paper_id: &str,
materials: SubmissionMaterials,
) -> Result<String> {
if !self.conferences.contains_key(conference_id) {
return Err(OptimError::InvalidConfig(format!(
"Conference '{}' not found",
conference_id
)));
}
let submission_id = uuid::Uuid::new_v4().to_string();
let submission = Submission {
id: submission_id.clone(),
conference_id: conference_id.to_string(),
paper_id: paper_id.to_string(),
status: SubmissionStatus::Submitted,
submitted_at: Utc::now(),
track: None,
materials,
reviews: Vec::new(),
decision: None,
};
self.submissions.push(submission);
Ok(submission_id)
}
pub fn get_upcoming_deadlines(
&self,
days_ahead: u32,
) -> Vec<(&Conference, DeadlineType, DateTime<Utc>)> {
let mut deadlines = Vec::new();
let now = Utc::now();
let future_limit = now + chrono::Duration::days(days_ahead as i64);
for conference in self.conferences.values() {
let dates = &conference.dates;
if let Some(abstract_deadline) = dates.abstract_deadline {
if abstract_deadline > now && abstract_deadline <= future_limit {
deadlines.push((
conference,
DeadlineType::AbstractSubmission,
abstract_deadline,
));
}
}
if dates.paper_deadline > now && dates.paper_deadline <= future_limit {
deadlines.push((
conference,
DeadlineType::PaperSubmission,
dates.paper_deadline,
));
}
if dates.notification_date > now && dates.notification_date <= future_limit {
deadlines.push((
conference,
DeadlineType::Notification,
dates.notification_date,
));
}
if dates.camera_ready_deadline > now && dates.camera_ready_deadline <= future_limit {
deadlines.push((
conference,
DeadlineType::CameraReady,
dates.camera_ready_deadline,
));
}
if dates.conference_start > now && dates.conference_start <= future_limit {
deadlines.push((
conference,
DeadlineType::ConferenceStart,
dates.conference_start,
));
}
}
deadlines.sort_by_key(|a| a.2);
deadlines
}
pub fn generate_deadline_alerts(&mut self, days_before: &[u32]) -> Vec<DeadlineAlert> {
let now = Utc::now();
let mut deadline_entries: Vec<(String, DeadlineType, DateTime<Utc>)> = Vec::new();
for conference in self.conferences.values() {
let dates = &conference.dates;
if let Some(abstract_deadline) = dates.abstract_deadline {
deadline_entries.push((
conference.id.clone(),
DeadlineType::AbstractSubmission,
abstract_deadline,
));
}
deadline_entries.push((
conference.id.clone(),
DeadlineType::PaperSubmission,
dates.paper_deadline,
));
deadline_entries.push((
conference.id.clone(),
DeadlineType::Notification,
dates.notification_date,
));
deadline_entries.push((
conference.id.clone(),
DeadlineType::CameraReady,
dates.camera_ready_deadline,
));
deadline_entries.push((
conference.id.clone(),
DeadlineType::ConferenceStart,
dates.conference_start,
));
}
let mut new_alerts = Vec::new();
for (conference_id, deadline_type, deadline) in deadline_entries {
if deadline <= now {
continue;
}
let days_remaining = (deadline - now).num_days().max(0) as u32;
for &threshold in days_before {
if days_remaining > threshold {
continue;
}
let already_exists = self.alerts.iter().any(|alert| {
alert.conference_id == conference_id
&& alert.deadline_type == deadline_type
&& alert.days_before == threshold
});
if already_exists {
continue;
}
let alert = DeadlineAlert {
id: uuid::Uuid::new_v4().to_string(),
conference_id: conference_id.clone(),
deadline_type: deadline_type.clone(),
alert_date: now,
days_before: threshold,
message: format!(
"{deadline_type:?} deadline for conference '{conference_id}' is in \
{days_remaining} day(s) ({deadline})"
),
sent: false,
};
self.alerts.push(alert.clone());
new_alerts.push(alert);
}
}
new_alerts
}
pub fn pending_alerts(&self) -> Vec<&DeadlineAlert> {
self.alerts.iter().filter(|alert| !alert.sent).collect()
}
pub fn mark_alert_sent(&mut self, alert_id: &str) -> Result<()> {
let alert = self
.alerts
.iter_mut()
.find(|alert| alert.id == alert_id)
.ok_or_else(|| OptimError::InvalidConfig(format!("alert '{alert_id}' not found")))?;
alert.sent = true;
Ok(())
}
pub fn search_conferences(&self, research_area: &str) -> Vec<&Conference> {
self.conferences
.values()
.filter(|conf| {
conf.research_areas
.iter()
.any(|area| area.to_lowercase().contains(&research_area.to_lowercase()))
})
.collect()
}
pub fn get_conferences_by_ranking(&self, ranking: ConferenceRanking) -> Vec<&Conference> {
self.conferences
.values()
.filter(|conf| conf.ranking == ranking)
.collect()
}
pub fn load_standard_conferences(&mut self) {
self.add_conference(Self::next_upcoming_edition(Self::create_neurips_conference));
self.add_conference(Self::next_upcoming_edition(Self::create_icml_conference));
self.add_conference(Self::next_upcoming_edition(Self::create_iclr_conference));
self.add_conference(Self::next_upcoming_edition(Self::create_aaai_conference));
self.add_conference(Self::next_upcoming_edition(Self::create_ijcai_conference));
}
fn next_upcoming_edition(build: fn(i32) -> Conference) -> Conference {
let now = Utc::now();
let start_year = now.year();
let last_candidate = start_year + 3;
for candidate_year in start_year..=last_candidate {
let conference = build(candidate_year);
if conference.dates.paper_deadline > now {
return conference;
}
}
build(last_candidate)
}
fn create_neurips_conference(edition_year: i32) -> Conference {
Conference {
id: format!("neurips{edition_year}"),
name: "Conference on Neural Information Processing Systems".to_string(),
abbreviation: "NeurIPS".to_string(),
description: "Premier conference on neural information processing systems".to_string(),
url: "https://neurips.cc/".to_string(),
ranking: ConferenceRanking::TopTier,
research_areas: vec![
"Machine Learning".to_string(),
"Deep Learning".to_string(),
"Neural Networks".to_string(),
"Optimization".to_string(),
],
annual: true,
series_info: SeriesInfo {
series_number: 38,
year: edition_year as u32,
location: "Vancouver".to_string(),
country: "Canada".to_string(),
format: ConferenceFormat::Hybrid,
},
dates: ConferenceDates {
abstract_deadline: Some(
chrono::Utc
.with_ymd_and_hms(edition_year, 5, 15, 23, 59, 59)
.single()
.expect("invalid datetime"),
),
paper_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 5, 22, 23, 59, 59)
.single()
.expect("invalid datetime"),
notification_date: chrono::Utc
.with_ymd_and_hms(edition_year, 9, 25, 12, 0, 0)
.single()
.expect("invalid datetime"),
camera_ready_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 10, 30, 23, 59, 59)
.single()
.expect("invalid datetime"),
conference_start: chrono::Utc
.with_ymd_and_hms(edition_year, 12, 10, 9, 0, 0)
.single()
.expect("invalid datetime"),
conference_end: chrono::Utc
.with_ymd_and_hms(edition_year, 12, 16, 18, 0, 0)
.single()
.expect("invalid datetime"),
},
requirements: SubmissionRequirements {
page_limit: 9,
word_limit: None,
format: FormatRequirements {
template: format!("NeurIPS {edition_year} LaTeX template"),
font_size: 10,
line_spacing: 1.0,
margins: "1 inch".to_string(),
citation_style: "NeurIPS".to_string(),
file_format: vec!["PDF".to_string()],
},
required_sections: vec![
"Abstract".to_string(),
"Introduction".to_string(),
"Related Work".to_string(),
"Method".to_string(),
"Experiments".to_string(),
"Conclusion".to_string(),
],
supplementary_allowed: true,
anonymous_submission: true,
double_blind: true,
},
review_process: ReviewProcess {
reviewers_per_paper: 3,
review_criteria: vec![
"Technical Quality".to_string(),
"Novelty".to_string(),
"Significance".to_string(),
"Clarity".to_string(),
],
rebuttal_allowed: true,
acceptance_rate: Some(0.26), review_format: ReviewFormat::Mixed,
},
}
}
fn create_icml_conference(edition_year: i32) -> Conference {
Conference {
id: format!("icml{edition_year}"),
name: "International Conference on Machine Learning".to_string(),
abbreviation: "ICML".to_string(),
description: "Premier international conference on machine learning".to_string(),
url: "https://icml.cc/".to_string(),
ranking: ConferenceRanking::TopTier,
research_areas: vec![
"Machine Learning".to_string(),
"Optimization".to_string(),
"Statistical Learning".to_string(),
"Deep Learning".to_string(),
],
annual: true,
series_info: SeriesInfo {
series_number: 41,
year: edition_year as u32,
location: "Vienna".to_string(),
country: "Austria".to_string(),
format: ConferenceFormat::Hybrid,
},
dates: ConferenceDates {
abstract_deadline: None,
paper_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 2, 1, 23, 59, 59)
.single()
.expect("invalid datetime"),
notification_date: chrono::Utc
.with_ymd_and_hms(edition_year, 5, 1, 12, 0, 0)
.single()
.expect("invalid datetime"),
camera_ready_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 6, 1, 23, 59, 59)
.single()
.expect("invalid datetime"),
conference_start: chrono::Utc
.with_ymd_and_hms(edition_year, 7, 21, 9, 0, 0)
.single()
.expect("invalid datetime"),
conference_end: chrono::Utc
.with_ymd_and_hms(edition_year, 7, 27, 18, 0, 0)
.single()
.expect("invalid datetime"),
},
requirements: SubmissionRequirements {
page_limit: 8,
word_limit: None,
format: FormatRequirements {
template: format!("ICML {edition_year} LaTeX template"),
font_size: 10,
line_spacing: 1.0,
margins: "1 inch".to_string(),
citation_style: "ICML".to_string(),
file_format: vec!["PDF".to_string()],
},
required_sections: vec![
"Abstract".to_string(),
"Introduction".to_string(),
"Methods".to_string(),
"Results".to_string(),
"Conclusion".to_string(),
],
supplementary_allowed: true,
anonymous_submission: true,
double_blind: true,
},
review_process: ReviewProcess {
reviewers_per_paper: 3,
review_criteria: vec![
"Technical Quality".to_string(),
"Clarity".to_string(),
"Originality".to_string(),
"Significance".to_string(),
],
rebuttal_allowed: true,
acceptance_rate: Some(0.23), review_format: ReviewFormat::Mixed,
},
}
}
fn create_iclr_conference(edition_year: i32) -> Conference {
let prior_year = edition_year - 1;
Conference {
id: format!("iclr{edition_year}"),
name: "International Conference on Learning Representations".to_string(),
abbreviation: "ICLR".to_string(),
description: "Conference focused on learning representations".to_string(),
url: "https://iclr.cc/".to_string(),
ranking: ConferenceRanking::TopTier,
research_areas: vec![
"Deep Learning".to_string(),
"Representation Learning".to_string(),
"Neural Networks".to_string(),
"Optimization".to_string(),
],
annual: true,
series_info: SeriesInfo {
series_number: 12,
year: edition_year as u32,
location: "Vienna".to_string(),
country: "Austria".to_string(),
format: ConferenceFormat::Hybrid,
},
dates: ConferenceDates {
abstract_deadline: Some(
chrono::Utc
.with_ymd_and_hms(prior_year, 9, 28, 23, 59, 59)
.single()
.expect("invalid datetime"),
),
paper_deadline: chrono::Utc
.with_ymd_and_hms(prior_year, 10, 2, 23, 59, 59)
.single()
.expect("invalid datetime"),
notification_date: chrono::Utc
.with_ymd_and_hms(edition_year, 1, 15, 12, 0, 0)
.single()
.expect("invalid datetime"),
camera_ready_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 2, 28, 23, 59, 59)
.single()
.expect("invalid datetime"),
conference_start: chrono::Utc
.with_ymd_and_hms(edition_year, 5, 7, 9, 0, 0)
.single()
.expect("invalid datetime"),
conference_end: chrono::Utc
.with_ymd_and_hms(edition_year, 5, 11, 18, 0, 0)
.single()
.expect("invalid datetime"),
},
requirements: SubmissionRequirements {
page_limit: 9,
word_limit: None,
format: FormatRequirements {
template: format!("ICLR {edition_year} LaTeX template"),
font_size: 10,
line_spacing: 1.0,
margins: "1 inch".to_string(),
citation_style: "ICLR".to_string(),
file_format: vec!["PDF".to_string()],
},
required_sections: vec![
"Abstract".to_string(),
"Introduction".to_string(),
"Related Work".to_string(),
"Method".to_string(),
"Experiments".to_string(),
"Conclusion".to_string(),
],
supplementary_allowed: true,
anonymous_submission: true,
double_blind: true,
},
review_process: ReviewProcess {
reviewers_per_paper: 3,
review_criteria: vec![
"Technical Quality".to_string(),
"Clarity".to_string(),
"Originality".to_string(),
"Significance".to_string(),
],
rebuttal_allowed: true,
acceptance_rate: Some(0.31), review_format: ReviewFormat::Mixed,
},
}
}
fn create_aaai_conference(edition_year: i32) -> Conference {
let prior_year = edition_year - 1;
Conference {
id: format!("aaai{edition_year}"),
name: "AAAI Conference on Artificial Intelligence".to_string(),
abbreviation: "AAAI".to_string(),
description: "Conference on artificial intelligence".to_string(),
url: "https://aaai.org/".to_string(),
ranking: ConferenceRanking::TopTier,
research_areas: vec![
"Artificial Intelligence".to_string(),
"Machine Learning".to_string(),
"Knowledge Representation".to_string(),
"Planning".to_string(),
],
annual: true,
series_info: SeriesInfo {
series_number: 38,
year: edition_year as u32,
location: "Vancouver".to_string(),
country: "Canada".to_string(),
format: ConferenceFormat::Hybrid,
},
dates: ConferenceDates {
abstract_deadline: Some(
chrono::Utc
.with_ymd_and_hms(prior_year, 8, 15, 23, 59, 59)
.single()
.expect("invalid datetime"),
),
paper_deadline: chrono::Utc
.with_ymd_and_hms(prior_year, 8, 19, 23, 59, 59)
.single()
.expect("invalid datetime"),
notification_date: chrono::Utc
.with_ymd_and_hms(prior_year, 12, 9, 12, 0, 0)
.single()
.expect("invalid datetime"),
camera_ready_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 1, 15, 23, 59, 59)
.single()
.expect("invalid datetime"),
conference_start: chrono::Utc
.with_ymd_and_hms(edition_year, 2, 20, 9, 0, 0)
.single()
.expect("invalid datetime"),
conference_end: chrono::Utc
.with_ymd_and_hms(edition_year, 2, 27, 18, 0, 0)
.single()
.expect("invalid datetime"),
},
requirements: SubmissionRequirements {
page_limit: 7,
word_limit: None,
format: FormatRequirements {
template: format!("AAAI {edition_year} LaTeX template"),
font_size: 10,
line_spacing: 1.0,
margins: "0.75 inch".to_string(),
citation_style: "AAAI".to_string(),
file_format: vec!["PDF".to_string()],
},
required_sections: vec![
"Abstract".to_string(),
"Introduction".to_string(),
"Related Work".to_string(),
"Approach".to_string(),
"Experiments".to_string(),
"Conclusion".to_string(),
],
supplementary_allowed: false,
anonymous_submission: true,
double_blind: true,
},
review_process: ReviewProcess {
reviewers_per_paper: 3,
review_criteria: vec![
"Technical Quality".to_string(),
"Novelty".to_string(),
"Significance".to_string(),
"Clarity".to_string(),
],
rebuttal_allowed: false,
acceptance_rate: Some(0.23), review_format: ReviewFormat::NumericalScores,
},
}
}
fn create_ijcai_conference(edition_year: i32) -> Conference {
Conference {
id: format!("ijcai{edition_year}"),
name: "International Joint Conference on Artificial Intelligence".to_string(),
abbreviation: "IJCAI".to_string(),
description: "International conference on artificial intelligence".to_string(),
url: "https://ijcai.org/".to_string(),
ranking: ConferenceRanking::TopTier,
research_areas: vec![
"Artificial Intelligence".to_string(),
"Machine Learning".to_string(),
"Automated Reasoning".to_string(),
"Multi-agent Systems".to_string(),
],
annual: true,
series_info: SeriesInfo {
series_number: 33,
year: edition_year as u32,
location: "Jeju".to_string(),
country: "South Korea".to_string(),
format: ConferenceFormat::Hybrid,
},
dates: ConferenceDates {
abstract_deadline: Some(
chrono::Utc
.with_ymd_and_hms(edition_year, 1, 17, 23, 59, 59)
.single()
.expect("invalid datetime"),
),
paper_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 1, 24, 23, 59, 59)
.single()
.expect("invalid datetime"),
notification_date: chrono::Utc
.with_ymd_and_hms(edition_year, 4, 16, 12, 0, 0)
.single()
.expect("invalid datetime"),
camera_ready_deadline: chrono::Utc
.with_ymd_and_hms(edition_year, 5, 15, 23, 59, 59)
.single()
.expect("invalid datetime"),
conference_start: chrono::Utc
.with_ymd_and_hms(edition_year, 8, 3, 9, 0, 0)
.single()
.expect("invalid datetime"),
conference_end: chrono::Utc
.with_ymd_and_hms(edition_year, 8, 9, 18, 0, 0)
.single()
.expect("invalid datetime"),
},
requirements: SubmissionRequirements {
page_limit: 7,
word_limit: None,
format: FormatRequirements {
template: format!("IJCAI {edition_year} LaTeX template"),
font_size: 10,
line_spacing: 1.0,
margins: "0.75 inch".to_string(),
citation_style: "IJCAI".to_string(),
file_format: vec!["PDF".to_string()],
},
required_sections: vec![
"Abstract".to_string(),
"Introduction".to_string(),
"Background".to_string(),
"Approach".to_string(),
"Experiments".to_string(),
"Conclusion".to_string(),
],
supplementary_allowed: false,
anonymous_submission: true,
double_blind: true,
},
review_process: ReviewProcess {
reviewers_per_paper: 3,
review_criteria: vec![
"Technical Quality".to_string(),
"Novelty".to_string(),
"Significance".to_string(),
"Clarity".to_string(),
],
rebuttal_allowed: true,
acceptance_rate: Some(0.15), review_format: ReviewFormat::Mixed,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conference_manager_creation() {
let manager = ConferenceManager::new();
assert!(manager.conferences.is_empty());
assert!(manager.submissions.is_empty());
}
#[test]
fn test_load_standard_conferences() {
let mut manager = ConferenceManager::new();
manager.load_standard_conferences();
assert_eq!(manager.conferences.len(), 5);
let abbreviations: Vec<&str> = manager
.conferences
.values()
.map(|c| c.abbreviation.as_str())
.collect();
for expected in ["NeurIPS", "ICML", "ICLR", "AAAI", "IJCAI"] {
assert!(
abbreviations.contains(&expected),
"missing {expected} in {abbreviations:?}"
);
}
}
#[test]
fn test_load_standard_conferences_deadlines_are_in_the_future() {
let mut manager = ConferenceManager::new();
manager.load_standard_conferences();
let now = Utc::now();
for conference in manager.conferences.values() {
assert!(
conference.dates.paper_deadline > now,
"{}'s paper deadline {} is not in the future (now = {now})",
conference.abbreviation,
conference.dates.paper_deadline
);
}
let upcoming = manager.get_upcoming_deadlines(400);
assert!(
!upcoming.is_empty(),
"expected at least one upcoming deadline within 400 days"
);
}
#[test]
fn test_generate_deadline_alerts_populates_alerts() {
let mut manager = ConferenceManager::new();
manager.load_standard_conferences();
assert!(manager.alerts.is_empty());
let created = manager.generate_deadline_alerts(&[400]);
assert!(!created.is_empty());
assert_eq!(manager.alerts.len(), created.len());
assert!(manager.alerts.iter().all(|a| !a.sent));
assert_eq!(manager.pending_alerts().len(), manager.alerts.len());
let created_again = manager.generate_deadline_alerts(&[400]);
assert!(created_again.is_empty());
assert_eq!(manager.alerts.len(), created.len());
let alert_id = manager.alerts[0].id.clone();
manager
.mark_alert_sent(&alert_id)
.expect("mark should succeed");
assert!(
manager
.alerts
.iter()
.find(|a| a.id == alert_id)
.unwrap()
.sent
);
assert_eq!(manager.pending_alerts().len(), manager.alerts.len() - 1);
}
#[test]
fn test_search_conferences() {
let mut manager = ConferenceManager::new();
manager.load_standard_conferences();
let ml_conferences = manager.search_conferences("Machine Learning");
assert!(!ml_conferences.is_empty());
let top_tier = manager.get_conferences_by_ranking(ConferenceRanking::TopTier);
assert!(!top_tier.is_empty());
}
}