use crate::storage::DeveloperProfile;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::info;
pub mod aggregation;
pub mod visualization;
pub mod insights;
pub use aggregation::TeamAggregator;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamConfig {
pub name: String,
pub description: Option<String>,
pub members: Vec<TeamMember>,
pub repositories: Vec<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamMember {
pub github_username: String,
pub role: TeamRole,
pub joined_at: chrono::DateTime<chrono::Utc>,
pub profile_path: Option<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TeamRole {
Lead,
Senior,
Mid,
Junior,
Contributor,
}
impl std::fmt::Display for TeamRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TeamRole::Lead => write!(f, "Lead"),
TeamRole::Senior => write!(f, "Senior"),
TeamRole::Mid => write!(f, "Mid"),
TeamRole::Junior => write!(f, "Junior"),
TeamRole::Contributor => write!(f, "Contributor"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamProfile {
pub config: TeamConfig,
pub aggregated_at: chrono::DateTime<chrono::Utc>,
pub language_coverage: HashMap<String, TeamLanguageCoverage>,
pub technology_coverage: HashMap<String, TechnologyCoverage>,
pub member_stats: Vec<MemberStats>,
pub collaboration_patterns: CollaborationPatterns,
pub knowledge_distribution: KnowledgeDistribution,
pub recommendations: Vec<TeamRecommendation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamLanguageCoverage {
pub language: String,
pub total_experience_years: f64,
pub expert_count: usize,
pub advanced_count: usize,
pub intermediate_count: usize,
pub beginner_count: usize,
pub primary_experts: Vec<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechnologyCoverage {
pub name: String,
pub category: String, pub users: Vec<String>, pub expertise_level: TeamExpertiseLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TeamExpertiseLevel {
Deep, Moderate, Limited, None, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemberStats {
pub github_username: String,
pub role: TeamRole,
pub primary_languages: Vec<String>,
pub contribution_score: f64, pub code_review_activity: CodeReviewStats,
pub knowledge_areas: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewStats {
pub reviews_given: i64,
pub reviews_received: i64,
pub avg_review_time_hours: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollaborationPatterns {
pub most_active_pairs: Vec<(String, String, i64)>, pub code_review_network: Vec<CodeReviewEdge>,
pub knowledge_sharing_score: f64, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewEdge {
pub reviewer: String,
pub reviewee: String,
pub review_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeDistribution {
pub bus_factor: HashMap<String, usize>, pub knowledge_silos: Vec<KnowledgeSilo>,
pub cross_training_opportunities: Vec<CrossTrainingOpportunity>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnowledgeSilo {
pub technology: String,
pub primary_experts: Vec<String>,
pub risk_level: RiskLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RiskLevel {
Critical, High, Medium, Low, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossTrainingOpportunity {
pub technology: String,
pub potential_mentors: Vec<String>,
pub recommended_trainees: Vec<String>,
pub priority: Priority,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Priority {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamRecommendation {
pub category: RecommendationCategory,
pub title: String,
pub description: String,
pub action_items: Vec<String>,
pub priority: Priority,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RecommendationCategory {
SkillGap,
KnowledgeSharing,
Hiring,
Process,
Architecture,
}
pub struct TeamManager {
storage_path: std::path::PathBuf,
}
impl TeamManager {
pub fn new(base_path: &std::path::Path) -> Result<Self> {
let storage_path = base_path.join("teams");
std::fs::create_dir_all(&storage_path)?;
Ok(Self { storage_path })
}
pub fn create_team(&self, name: &str, description: Option<&str>) -> Result<TeamConfig> {
let config = TeamConfig {
name: name.to_string(),
description: description.map(|s| s.to_string()),
members: Vec::new(),
repositories: Vec::new(),
created_at: chrono::Utc::now(),
};
self.save_team(&config)?;
info!("Created team: {}", name);
Ok(config)
}
pub fn load_team(&self, name: &str) -> Result<TeamConfig> {
let path = self.storage_path.join(format!("{}.json", name));
let content = std::fs::read_to_string(&path)?;
let config: TeamConfig = serde_json::from_str(&content)?;
Ok(config)
}
pub fn save_team(&self, config: &TeamConfig) -> Result<()> {
let path = self.storage_path.join(format!("{}.json", config.name));
let content = serde_json::to_string_pretty(config)?;
std::fs::write(&path, content)?;
Ok(())
}
pub fn list_teams(&self) -> Result<Vec<String>> {
let mut teams = Vec::new();
for entry in std::fs::read_dir(&self.storage_path)? {
let entry = entry?;
let path = entry.path();
if path.extension().map(|e| e == "json").unwrap_or(false) {
if let Some(stem) = path.file_stem() {
teams.push(stem.to_string_lossy().to_string());
}
}
}
Ok(teams)
}
pub fn add_member(
&self,
team_name: &str,
github_username: &str,
role: TeamRole,
profile_path: Option<&str>,
) -> Result<()> {
let mut config = self.load_team(team_name)?;
if config.members.iter().any(|m| m.github_username == github_username) {
anyhow::bail!("User {} is already a team member", github_username);
}
config.members.push(TeamMember {
github_username: github_username.to_string(),
role,
joined_at: chrono::Utc::now(),
profile_path: profile_path.map(|s| s.to_string()),
});
self.save_team(&config)?;
info!("Added {} to team {}", github_username, team_name);
Ok(())
}
pub fn remove_member(&self, team_name: &str, github_username: &str) -> Result<()> {
let mut config = self.load_team(team_name)?;
config.members.retain(|m| m.github_username != github_username);
self.save_team(&config)?;
info!("Removed {} from team {}", github_username, team_name);
Ok(())
}
pub async fn load_member_profiles(&self, team_name: &str) -> Result<Vec<DeveloperProfile>> {
let config = self.load_team(team_name)?;
let mut profiles = Vec::new();
for member in &config.members {
if let Some(profile_path) = &member.profile_path {
let content = tokio::fs::read_to_string(profile_path).await?;
let profile: DeveloperProfile = serde_json::from_str(&content)?;
profiles.push(profile);
} else {
let default_path = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?
.join(".i-self")
.join("profile.json");
if default_path.exists() {
let content = tokio::fs::read_to_string(&default_path).await?;
let profile: DeveloperProfile = serde_json::from_str(&content)?;
profiles.push(profile);
}
}
}
Ok(profiles)
}
}