use std::collections::HashMap;
use std::time::Duration;
use crate::progress::ExecutionPhase;
use super::model::ExecutionHistory;
const SECONDS_PER_DAY: f64 = 24.0 * 3600.0;
const RECENCY_DECAY_PER_DAY: f64 = 0.9;
pub fn weighted_average_duration(
history: &ExecutionHistory,
runner: &str,
model: &str,
phase_count: u8,
phase: ExecutionPhase,
) -> Option<Duration> {
let relevant_entries: Vec<_> = history
.entries
.iter()
.filter(|entry| {
entry.runner == runner
&& entry.model == model
&& entry.phase_count == phase_count
&& entry.phase_durations.contains_key(&phase)
})
.collect();
if relevant_entries.is_empty() {
return None;
}
let now = current_unix_time_secs();
let mut total_weight = 0.0;
let mut weighted_sum = 0.0;
for entry in relevant_entries {
let entry_secs = parse_timestamp_to_secs(&entry.timestamp).unwrap_or(now as u64) as f64;
let age_days = (now - entry_secs) / SECONDS_PER_DAY;
let weight = RECENCY_DECAY_PER_DAY.powf(age_days);
if let Some(duration) = entry.phase_durations.get(&phase) {
weighted_sum += duration.as_secs_f64() * weight;
total_weight += weight;
}
}
(total_weight > 0.0).then(|| Duration::from_secs_f64(weighted_sum / total_weight))
}
pub fn get_phase_averages(
history: &ExecutionHistory,
runner: &str,
model: &str,
phase_count: u8,
) -> HashMap<ExecutionPhase, Duration> {
let mut averages = HashMap::new();
for phase in [
ExecutionPhase::Planning,
ExecutionPhase::Implementation,
ExecutionPhase::Review,
] {
if let Some(avg) = weighted_average_duration(history, runner, model, phase_count, phase) {
averages.insert(phase, avg);
}
}
averages
}
fn current_unix_time_secs() -> f64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as f64
}
pub(crate) fn parse_timestamp_to_secs(timestamp: &str) -> Option<u64> {
let dt = crate::timeutil::parse_rfc3339_opt(timestamp)?;
let ts = dt.unix_timestamp();
(ts >= 0).then_some(ts as u64)
}