use crate::domain::model::issue::Issue;
use crate::domain::model::status::{StatusCategory, StatusesConfig};
use crate::domain::model::temporal::iso_date::IsoDate;
pub(crate) fn is_terminal(statuses: &StatusesConfig) -> impl Fn(&str) -> bool + '_ {
move |s| statuses.resolve(s).map(|st| st.terminal).unwrap_or(false)
}
pub(crate) fn is_ongoing(statuses: &StatusesConfig) -> impl Fn(&str) -> bool + '_ {
move |s| {
statuses
.resolve(s)
.map(|st| st.category == StatusCategory::Active)
.unwrap_or(false)
}
}
pub(crate) fn is_stalled(statuses: &StatusesConfig) -> impl Fn(&str) -> bool + '_ {
move |s| {
statuses
.resolve(s)
.map(|st| st.category == StatusCategory::Stalled)
.unwrap_or(false)
}
}
pub(super) fn creation_date(issue: &Issue) -> IsoDate {
issue.events.creation_date(&issue.date)
}
pub(crate) fn close_date(issue: &Issue, statuses: &StatusesConfig) -> Option<IsoDate> {
issue.events.close_date(is_terminal(statuses))
}
pub(super) fn percentiles(sorted: &[f64]) -> Option<super::Percentiles> {
if sorted.is_empty() {
return None;
}
let p = |pct: f64| -> f64 {
let idx = (pct / 100.0 * (sorted.len() - 1) as f64).round() as usize;
sorted[idx.min(sorted.len() - 1)]
};
Some(super::Percentiles {
p50: p(50.0),
p85: p(85.0),
p95: p(95.0),
})
}
pub(crate) fn coeff_of_variation(values: &[f64]) -> Option<f64> {
if values.len() < 2 {
return None;
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
if mean == 0.0 {
return None;
}
let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
Some(variance.sqrt() / mean * 100.0)
}