use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct CycleConfig {
pub deployment_name: String,
pub meta_container: String,
pub safety_lag_minutes: u32,
pub batch_size: usize,
pub poll_interval: Duration,
pub reconcile_every: u64,
pub max_cycle_duration: Duration,
pub companion_containers: HashMap<String, String>,
}
impl CycleConfig {
pub fn from_config(config: &crate::config::Config, deployment_name: impl Into<String>) -> Self {
let ic = &config.ingest;
let cc = &config.cosmos.containers;
let poll_interval =
humantime::parse_duration(&ic.poll_interval).unwrap_or(Duration::from_secs(300));
let max_cycle_duration =
humantime::parse_duration(&ic.max_cycle_duration).unwrap_or(Duration::from_secs(1800));
let mut companion_containers = HashMap::new();
companion_containers.insert("sprints".into(), cc.jira_sprints.clone());
companion_containers.insert("fix_versions".into(), cc.jira_fix_versions.clone());
companion_containers.insert("projects".into(), cc.jira_projects.clone());
companion_containers.insert("spaces".into(), cc.confluence_spaces.clone());
Self {
deployment_name: deployment_name.into(),
meta_container: config.cosmos.meta_container.clone(),
safety_lag_minutes: ic.safety_lag_minutes,
batch_size: ic.batch_size as usize,
poll_interval,
reconcile_every: ic.reconcile_every as u64,
max_cycle_duration,
companion_containers,
}
}
}
impl Default for CycleConfig {
fn default() -> Self {
let mut companion_containers = HashMap::new();
companion_containers.insert("sprints".into(), "jira-sprints".into());
companion_containers.insert("fix_versions".into(), "jira-fix-versions".into());
companion_containers.insert("projects".into(), "jira-projects".into());
companion_containers.insert("spaces".into(), "confluence-spaces".into());
Self {
deployment_name: "test".into(),
meta_container: "quelch-meta".into(),
safety_lag_minutes: 2,
batch_size: 100,
poll_interval: Duration::from_secs(300),
reconcile_every: 12,
max_cycle_duration: Duration::from_secs(1800),
companion_containers,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_has_expected_values() {
let cfg = CycleConfig::default();
assert_eq!(cfg.safety_lag_minutes, 2);
assert_eq!(cfg.batch_size, 100);
assert_eq!(cfg.reconcile_every, 12);
assert_eq!(cfg.meta_container, "quelch-meta");
assert_eq!(cfg.companion_containers["sprints"], "jira-sprints");
assert_eq!(
cfg.companion_containers["fix_versions"],
"jira-fix-versions"
);
assert_eq!(cfg.companion_containers["projects"], "jira-projects");
assert_eq!(cfg.companion_containers["spaces"], "confluence-spaces");
}
#[test]
fn from_config_parses_durations() {
let yaml = r#"
azure:
subscription_id: "sub"
resource_group: "rg"
region: "swedencentral"
cosmos:
database: "quelch"
ai:
provider: azure_openai
endpoint: "https://x.openai.azure.com"
embedding:
deployment: "te"
dimensions: 1536
chat:
deployment: "gpt-5-mini"
model_name: "gpt-5-mini"
ingest:
poll_interval: "120s"
safety_lag_minutes: 5
batch_size: 50
reconcile_every: 6
max_cycle_duration: "15m"
max_concurrent_per_source: 1
max_retries: 3
sources: []
deployments: []
"#;
let config: crate::config::Config = serde_yaml::from_str(yaml).unwrap();
let cfg = CycleConfig::from_config(&config, "prod");
assert_eq!(cfg.deployment_name, "prod");
assert_eq!(cfg.safety_lag_minutes, 5);
assert_eq!(cfg.batch_size, 50);
assert_eq!(cfg.reconcile_every, 6);
assert_eq!(cfg.poll_interval, Duration::from_secs(120));
assert_eq!(cfg.max_cycle_duration, Duration::from_secs(900));
}
#[test]
fn from_config_falls_back_on_bad_duration() {
let yaml = r#"
azure:
subscription_id: "sub"
resource_group: "rg"
region: "swedencentral"
cosmos:
database: "quelch"
ai:
provider: azure_openai
endpoint: "https://x.openai.azure.com"
embedding:
deployment: "te"
dimensions: 1536
chat:
deployment: "gpt-5-mini"
model_name: "gpt-5-mini"
ingest:
poll_interval: "not-a-duration"
max_cycle_duration: "also-bad"
sources: []
deployments: []
"#;
let config: crate::config::Config = serde_yaml::from_str(yaml).unwrap();
let cfg = CycleConfig::from_config(&config, "dev");
assert_eq!(cfg.poll_interval, Duration::from_secs(300));
assert_eq!(cfg.max_cycle_duration, Duration::from_secs(1800));
}
}