use crate::unified_quality::{QualityMode, QualityPhilosophy};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[allow(dead_code)]
pub struct TeamOnboarding {
sessions: HashMap<TeamId, OnboardingSession>,
tutorials: TutorialLibrary,
config: OnboardingConfig,
}
pub type TeamId = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnboardingSession {
pub team_id: TeamId,
pub current_phase: OnboardingPhase,
pub completed_tutorials: Vec<String>,
pub quality_mode: QualityMode,
pub started_at: std::time::SystemTime,
pub progress: OnboardingProgress,
pub preferences: TeamPreferences,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum OnboardingPhase {
Introduction,
MonitoringSetup,
MetricsLearning,
EnforcementConfig,
AutomationSetup,
AdvancedFeatures,
ProductionReady,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnboardingProgress {
pub tutorials_completed: u32,
pub tutorials_total: u32,
pub exercises_completed: u32,
pub quality_improvements: u32,
pub days_active: u32,
pub engagement_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamPreferences {
pub languages: Vec<String>,
pub learning_style: LearningStyle,
pub notifications: NotificationPreference,
pub philosophy: QualityPhilosophy,
pub team_info: TeamInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LearningStyle {
Practical,
Theoretical,
Balanced,
Exploratory,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationPreference {
pub daily_updates: bool,
pub weekly_summaries: bool,
pub achievements: bool,
pub celebrations: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamInfo {
pub size: u32,
pub experience_level: ExperienceLevel,
pub project_type: ProjectType,
pub quality_maturity: QualityMaturity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExperienceLevel {
Junior,
Intermediate,
Senior,
Mixed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProjectType {
WebApplication,
SystemsSoftware,
DataScience,
Mobile,
Library,
Microservices,
Monolith,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QualityMaturity {
None,
Basic,
Intermediate,
Advanced,
}
#[derive(Debug, Clone)]
pub struct TutorialLibrary {
tutorials: HashMap<OnboardingPhase, Vec<Tutorial>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tutorial {
pub id: String,
pub name: String,
pub description: String,
pub duration_minutes: u32,
pub prerequisites: Vec<String>,
pub objectives: Vec<String>,
pub content: TutorialContent,
pub exercises: Vec<Exercise>,
pub success_criteria: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TutorialContent {
Demo {
title: String,
description: String,
commands: Vec<String>,
expected_outputs: Vec<String>,
},
Walkthrough { steps: Vec<WalkthroughStep> },
Video {
title: String,
url: String,
transcript: Option<String>,
},
Documentation {
title: String,
content: String,
examples: Vec<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WalkthroughStep {
pub step: u32,
pub title: String,
pub instructions: String,
pub commands: Vec<String>,
pub expected_results: String,
pub tips: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exercise {
pub name: String,
pub instructions: String,
pub starting_files: HashMap<String, String>,
pub expected_outcome: String,
pub validation: Vec<String>,
pub hints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnboardingConfig {
pub interactive_mode: bool,
pub personalization: bool,
pub track_progress: bool,
pub gamification: GamificationConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GamificationConfig {
pub achievements: bool,
pub badges: bool,
pub leaderboards: bool,
pub points: bool,
}
impl TeamOnboarding {
#[must_use]
pub fn new(config: OnboardingConfig) -> Self {
Self {
sessions: HashMap::new(),
tutorials: TutorialLibrary::new(),
config,
}
}
pub fn start_onboarding(
&mut self,
team_id: TeamId,
preferences: TeamPreferences,
) -> Result<OnboardingSession> {
let session = OnboardingSession {
team_id: team_id.clone(),
current_phase: OnboardingPhase::Introduction,
completed_tutorials: Vec::new(),
quality_mode: QualityMode::Observe,
started_at: std::time::SystemTime::now(),
progress: OnboardingProgress {
tutorials_completed: 0,
tutorials_total: self.tutorials.count_tutorials(),
exercises_completed: 0,
quality_improvements: 0,
days_active: 0,
engagement_score: 0.0,
},
preferences,
};
self.sessions.insert(team_id, session.clone());
Ok(session)
}
pub fn get_recommendations(&self, team_id: &TeamId) -> Result<Vec<Tutorial>> {
let session = self
.sessions
.get(team_id)
.ok_or_else(|| anyhow::anyhow!("Team not found: {team_id}"))?;
let phase_tutorials = self
.tutorials
.get_tutorials_for_phase(&session.current_phase);
let mut recommendations = Vec::new();
for tutorial in phase_tutorials {
if !session.completed_tutorials.contains(&tutorial.id) {
let prerequisites_met = tutorial
.prerequisites
.iter()
.all(|prereq| session.completed_tutorials.contains(prereq));
if prerequisites_met {
recommendations.push(tutorial);
}
}
}
recommendations.sort_by(|a, b| {
let relevance_a = self.calculate_relevance(a, &session.preferences);
let relevance_b = self.calculate_relevance(b, &session.preferences);
relevance_b
.partial_cmp(&relevance_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(recommendations)
}
pub fn complete_tutorial(
&mut self,
team_id: &TeamId,
tutorial_id: String,
exercises_completed: u32,
) -> Result<()> {
let needs_update = {
let session = self
.sessions
.get(team_id)
.ok_or_else(|| anyhow::anyhow!("Team not found: {team_id}"))?;
!session.completed_tutorials.contains(&tutorial_id)
};
if needs_update {
let engagement_score = {
let session = self
.sessions
.get(team_id)
.ok_or_else(|| anyhow::anyhow!("Team not found: {team_id}"))?;
let mut temp_session = session.clone();
temp_session.progress.tutorials_completed += 1;
temp_session.progress.exercises_completed += exercises_completed;
self.calculate_engagement_score(&temp_session)
};
let session = self
.sessions
.get_mut(team_id)
.ok_or_else(|| anyhow::anyhow!("Team not found: {team_id}"))?;
session.completed_tutorials.push(tutorial_id);
session.progress.tutorials_completed += 1;
session.progress.exercises_completed += exercises_completed;
session.progress.engagement_score = engagement_score;
let current_phase = session.current_phase.clone();
let tutorials_completed = session.progress.tutorials_completed;
let should_advance = match current_phase {
OnboardingPhase::Introduction if tutorials_completed >= 2 => true,
OnboardingPhase::MonitoringSetup if tutorials_completed >= 4 => true,
OnboardingPhase::MetricsLearning if tutorials_completed >= 6 => true,
OnboardingPhase::EnforcementConfig if tutorials_completed >= 8 => true,
OnboardingPhase::AutomationSetup if tutorials_completed >= 10 => true,
_ => false,
};
if should_advance {
let next_phase = match current_phase {
OnboardingPhase::Introduction => OnboardingPhase::MonitoringSetup,
OnboardingPhase::MonitoringSetup => OnboardingPhase::MetricsLearning,
OnboardingPhase::MetricsLearning => OnboardingPhase::EnforcementConfig,
OnboardingPhase::EnforcementConfig => OnboardingPhase::AutomationSetup,
OnboardingPhase::AutomationSetup => OnboardingPhase::AdvancedFeatures,
OnboardingPhase::AdvancedFeatures => OnboardingPhase::ProductionReady,
OnboardingPhase::ProductionReady => OnboardingPhase::ProductionReady, };
session.current_phase = next_phase;
}
}
Ok(())
}
pub fn generate_progress_report(&self, team_id: &TeamId) -> Result<OnboardingReport> {
let session = self
.sessions
.get(team_id)
.ok_or_else(|| anyhow::anyhow!("Team not found: {team_id}"))?;
let completion_percentage = (f64::from(session.progress.tutorials_completed)
/ f64::from(session.progress.tutorials_total))
* 100.0;
let current_phase_progress = self.calculate_phase_progress(session);
let recommended_next_steps = self.get_recommendations(team_id)?;
let achievements = self.calculate_achievements(session);
Ok(OnboardingReport {
team_id: team_id.clone(),
current_phase: session.current_phase.clone(),
overall_completion: completion_percentage,
phase_progress: current_phase_progress,
days_active: session.progress.days_active,
engagement_score: session.progress.engagement_score,
achievements,
recommended_next_steps: recommended_next_steps.into_iter().take(3).collect(),
quality_mode: session.quality_mode,
})
}
fn calculate_relevance(&self, tutorial: &Tutorial, preferences: &TeamPreferences) -> f64 {
let mut relevance: f64 = 0.0;
if preferences.languages.iter().any(|lang| {
tutorial
.description
.to_lowercase()
.contains(&lang.to_lowercase())
}) {
relevance += 0.3;
}
match preferences.learning_style {
LearningStyle::Practical => {
if tutorial.exercises.len() > 2 {
relevance += 0.2;
}
}
LearningStyle::Theoretical => {
if matches!(tutorial.content, TutorialContent::Documentation { .. }) {
relevance += 0.2;
}
}
_ => relevance += 0.1,
}
match preferences.team_info.experience_level {
ExperienceLevel::Junior => {
if tutorial.duration_minutes <= 30 {
relevance += 0.2;
}
}
ExperienceLevel::Senior => {
if tutorial.exercises.len() > 1 {
relevance += 0.2;
}
}
_ => relevance += 0.1,
}
relevance.min(1.0_f64)
}
fn calculate_engagement_score(&self, session: &OnboardingSession) -> f64 {
let tutorial_ratio =
f64::from(session.progress.tutorials_completed) / f64::from(session.progress.tutorials_total);
let exercise_bonus = (f64::from(session.progress.exercises_completed) * 0.1).min(0.3);
let improvement_bonus = (f64::from(session.progress.quality_improvements) * 0.05).min(0.2);
((tutorial_ratio + exercise_bonus + improvement_bonus) * 100.0).min(100.0)
}
fn calculate_phase_progress(&self, session: &OnboardingSession) -> f64 {
let phase_tutorials = self
.tutorials
.get_tutorials_for_phase(&session.current_phase);
let completed_in_phase = phase_tutorials
.iter()
.filter(|t| session.completed_tutorials.contains(&t.id))
.count();
(completed_in_phase as f64 / phase_tutorials.len() as f64) * 100.0
}
fn calculate_achievements(&self, session: &OnboardingSession) -> Vec<Achievement> {
let mut achievements = Vec::new();
if session.progress.tutorials_completed >= 5 {
achievements.push(Achievement {
id: "tutorial_enthusiast".to_string(),
name: "Tutorial Enthusiast".to_string(),
description: "Completed 5 tutorials".to_string(),
earned_at: session.started_at,
});
}
if session.progress.exercises_completed >= 10 {
achievements.push(Achievement {
id: "hands_on_learner".to_string(),
name: "Hands-on Learner".to_string(),
description: "Completed 10 exercises".to_string(),
earned_at: session.started_at,
});
}
if session.progress.engagement_score >= 90.0 {
achievements.push(Achievement {
id: "quality_champion".to_string(),
name: "Quality Champion".to_string(),
description: "Achieved 90% engagement score".to_string(),
earned_at: session.started_at,
});
}
achievements
}
#[allow(dead_code)]
fn next_phase(&self, current: &OnboardingPhase) -> OnboardingPhase {
match current {
OnboardingPhase::Introduction => OnboardingPhase::MonitoringSetup,
OnboardingPhase::MonitoringSetup => OnboardingPhase::MetricsLearning,
OnboardingPhase::MetricsLearning => OnboardingPhase::EnforcementConfig,
OnboardingPhase::EnforcementConfig => OnboardingPhase::AutomationSetup,
OnboardingPhase::AutomationSetup => OnboardingPhase::AdvancedFeatures,
OnboardingPhase::AdvancedFeatures => OnboardingPhase::ProductionReady,
OnboardingPhase::ProductionReady => OnboardingPhase::ProductionReady, }
}
#[allow(dead_code)]
fn recommended_quality_mode(&self, phase: &OnboardingPhase) -> QualityMode {
match phase {
OnboardingPhase::Introduction => QualityMode::Observe,
OnboardingPhase::MonitoringSetup => QualityMode::Observe,
OnboardingPhase::MetricsLearning => QualityMode::Advise,
OnboardingPhase::EnforcementConfig => QualityMode::Guide,
OnboardingPhase::AutomationSetup => QualityMode::Enforce,
OnboardingPhase::AdvancedFeatures => QualityMode::Enforce,
OnboardingPhase::ProductionReady => QualityMode::Enforce,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnboardingReport {
pub team_id: TeamId,
pub current_phase: OnboardingPhase,
pub overall_completion: f64,
pub phase_progress: f64,
pub days_active: u32,
pub engagement_score: f64,
pub achievements: Vec<Achievement>,
pub recommended_next_steps: Vec<Tutorial>,
pub quality_mode: QualityMode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Achievement {
pub id: String,
pub name: String,
pub description: String,
pub earned_at: std::time::SystemTime,
}
impl Default for TutorialLibrary {
fn default() -> Self {
Self::new()
}
}
impl TutorialLibrary {
#[must_use]
pub fn new() -> Self {
let mut tutorials = HashMap::new();
tutorials.insert(OnboardingPhase::Introduction, vec![
Tutorial {
id: "quality_philosophy".to_string(),
name: "Quality Philosophy and Benefits".to_string(),
description: "Learn about the unified quality approach and its benefits".to_string(),
duration_minutes: 15,
prerequisites: vec![],
objectives: vec![
"Understand quality-driven development".to_string(),
"Learn about error budgets".to_string(),
"See ROI of quality investment".to_string(),
],
content: TutorialContent::Documentation {
title: "Quality Philosophy".to_string(),
content: "Quality is not just about catching bugs - it's about building sustainable, maintainable software that delivers business value.".to_string(),
examples: vec!["Case studies from high-performing teams".to_string()],
},
exercises: vec![],
success_criteria: vec!["Complete reading".to_string(), "Pass knowledge check".to_string()],
},
]);
tutorials.insert(
OnboardingPhase::MonitoringSetup,
vec![Tutorial {
id: "setup_monitoring".to_string(),
name: "Setting Up Quality Monitoring".to_string(),
description: "Configure real-time quality monitoring for your project".to_string(),
duration_minutes: 30,
prerequisites: vec!["quality_philosophy".to_string()],
objectives: vec![
"Install and configure PMAT".to_string(),
"Set up file watching".to_string(),
"Understand monitoring outputs".to_string(),
],
content: TutorialContent::Walkthrough {
steps: vec![WalkthroughStep {
step: 1,
title: "Install PMAT".to_string(),
instructions: "Install PMAT using cargo".to_string(),
commands: vec!["cargo install pmat".to_string()],
expected_results: "PMAT installed successfully".to_string(),
tips: vec!["Use --force to update existing installation".to_string()],
}],
},
exercises: vec![Exercise {
name: "Monitor Your Project".to_string(),
instructions: "Set up monitoring for your current project".to_string(),
starting_files: HashMap::new(),
expected_outcome: "Quality metrics displayed".to_string(),
validation: vec!["pmat analyze complexity".to_string()],
hints: vec!["Start with a small test file".to_string()],
}],
success_criteria: vec![
"Successfully run pmat commands".to_string(),
"See quality metrics".to_string(),
],
}],
);
Self { tutorials }
}
#[must_use]
pub fn get_tutorials_for_phase(&self, phase: &OnboardingPhase) -> Vec<Tutorial> {
self.tutorials.get(phase).cloned().unwrap_or_default()
}
#[must_use]
pub fn count_tutorials(&self) -> u32 {
self.tutorials.values().map(|v| v.len() as u32).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_onboarding_session_creation() {
let preferences = TeamPreferences {
languages: vec!["rust".to_string()],
learning_style: LearningStyle::Practical,
notifications: NotificationPreference {
daily_updates: true,
weekly_summaries: true,
achievements: true,
celebrations: true,
},
philosophy: QualityPhilosophy::default(),
team_info: TeamInfo {
size: 5,
experience_level: ExperienceLevel::Intermediate,
project_type: ProjectType::WebApplication,
quality_maturity: QualityMaturity::Basic,
},
};
let config = OnboardingConfig {
interactive_mode: true,
personalization: true,
track_progress: true,
gamification: GamificationConfig {
achievements: true,
badges: true,
leaderboards: false,
points: true,
},
};
let mut onboarding = TeamOnboarding::new(config);
let session = onboarding
.start_onboarding("team-1".to_string(), preferences)
.unwrap();
assert_eq!(session.team_id, "team-1");
assert_eq!(session.current_phase, OnboardingPhase::Introduction);
assert_eq!(session.quality_mode, QualityMode::Observe);
assert!(session.completed_tutorials.is_empty());
}
#[test]
fn test_tutorial_library() {
let library = TutorialLibrary::new();
let intro_tutorials = library.get_tutorials_for_phase(&OnboardingPhase::Introduction);
assert!(!intro_tutorials.is_empty());
let total_count = library.count_tutorials();
assert!(total_count > 0);
}
#[test]
fn test_phase_progression() {
let config = OnboardingConfig {
interactive_mode: true,
personalization: true,
track_progress: true,
gamification: GamificationConfig {
achievements: true,
badges: true,
leaderboards: false,
points: true,
},
};
let onboarding = TeamOnboarding::new(config);
assert_eq!(
onboarding.next_phase(&OnboardingPhase::Introduction),
OnboardingPhase::MonitoringSetup
);
assert_eq!(
onboarding.next_phase(&OnboardingPhase::ProductionReady),
OnboardingPhase::ProductionReady
);
}
#[test]
fn test_quality_mode_progression() {
let config = OnboardingConfig {
interactive_mode: true,
personalization: true,
track_progress: true,
gamification: GamificationConfig {
achievements: true,
badges: true,
leaderboards: false,
points: true,
},
};
let onboarding = TeamOnboarding::new(config);
assert_eq!(
onboarding.recommended_quality_mode(&OnboardingPhase::Introduction),
QualityMode::Observe
);
assert_eq!(
onboarding.recommended_quality_mode(&OnboardingPhase::EnforcementConfig),
QualityMode::Guide
);
assert_eq!(
onboarding.recommended_quality_mode(&OnboardingPhase::ProductionReady),
QualityMode::Enforce
);
}
#[test]
fn test_learning_styles() {
let styles = vec![
LearningStyle::Practical,
LearningStyle::Theoretical,
LearningStyle::Balanced,
LearningStyle::Exploratory,
];
assert_eq!(styles.len(), 4);
let serialized = serde_json::to_string(&styles[0]).unwrap();
assert!(serialized.contains("Practical"));
}
#[test]
fn test_gamification_config() {
let config = GamificationConfig {
achievements: true,
badges: false,
leaderboards: true,
points: false,
};
assert!(config.achievements);
assert!(!config.badges);
assert!(config.leaderboards);
assert!(!config.points);
}
}