Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
Some checks failed
CI / test (push) Has been cancelled
CI / release (push) Has been cancelled

This commit is contained in:
2026-02-04 15:45:38 +00:00
parent 546523fc45
commit 8f834f9fbf

159
src/models/time_series.rs Normal file
View File

@@ -0,0 +1,159 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum TimeBucket {
Hour,
Day,
Week,
Month,
}
impl TimeBucket {
pub fn as_str(&self) -> &str {
match self {
TimeBucket::Hour => "hour",
TimeBucket::Day => "day",
TimeBucket::Week => "week",
TimeBucket::Month => "month",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeSeriesData {
pub bucket: String,
pub bucket_type: String,
pub commits: usize,
pub lines_added: usize,
pub lines_removed: usize,
pub authors: usize,
}
impl Default for TimeSeriesData {
fn default() -> Self {
Self {
bucket: String::new(),
bucket_type: String::new(),
commits: 0,
lines_added: 0,
lines_removed: 0,
authors: 0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MovingAverage {
pub period: usize,
pub values: Vec<f64>,
}
impl MovingAverage {
pub fn new(period: usize) -> Self {
Self {
period,
values: Vec::new(),
}
}
pub fn calculate(&mut self, value: f64) -> f64 {
self.values.push(value);
if self.values.len() > self.period {
self.values.remove(0);
}
let sum: f64 = self.values.iter().sum();
sum / self.values.len() as f64
}
pub fn is_ready(&self) -> bool {
self.values.len() >= self.period
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendAnalysis {
pub slope: f64,
pub direction: TrendDirection,
pub strength: f64,
pub prediction: Vec<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TrendDirection {
Increasing,
Decreasing,
Stable,
Volatile,
}
impl Default for TrendAnalysis {
fn default() -> Self {
Self {
slope: 0.0,
direction: TrendDirection::Stable,
strength: 0.0,
prediction: Vec::new(),
}
}
}
pub fn calculate_trend(values: &[f64]) -> TrendAnalysis {
if values.len() < 2 {
return TrendAnalysis::default();
}
let n = values.len() as f64;
let sum_x = (0..values.len()).map(|x| x as f64).sum::<f64>();
let sum_y = values.iter().sum::<f64>();
let sum_xy = values
.iter()
.enumerate()
.map(|(x, y)| x as f64 * y)
.sum::<f64>();
let sum_x2 = values
.iter()
.enumerate()
.map(|(x, _)| (x as f64).powi(2))
.sum::<f64>();
let slope = if n * sum_x2 - sum_x * sum_x != 0.0 {
(n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x)
} else {
0.0
};
let avg_y = sum_y / n;
let direction = if slope > 0.01 {
TrendDirection::Increasing
} else if slope < -0.01 {
TrendDirection::Decreasing
} else {
TrendDirection::Stable
};
let variance: f64 = values
.iter()
.map(|y| (y - avg_y).powi(2))
.sum::<f64>()
/ n;
let std_dev = variance.sqrt();
let strength = if std_dev > 0.0 {
(slope.abs() / std_dev).min(1.0)
} else {
0.0
};
let prediction: Vec<f64> = (0..3)
.map(|i| {
let x = values.len() as f64 + i as f64;
slope * (x - sum_x / n) + avg_y
})
.collect();
TrendAnalysis {
slope,
direction,
strength,
prediction,
}
}