#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Skill {
pub name: String,
pub level: SkillLevel,
pub years_experience: Option<f32>,
pub last_used: Option<String>,
pub projects_count: usize,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum SkillLevel {
Beginner,
Intermediate,
Advanced,
Expert,
Master,
}
impl SkillLevel {
pub fn from_score(score: f64) -> Self {
match score as u32 {
0..=20 => SkillLevel::Beginner,
21..=40 => SkillLevel::Intermediate,
41..=60 => SkillLevel::Advanced,
61..=80 => SkillLevel::Expert,
_ => SkillLevel::Master,
}
}
pub fn to_score(&self) -> u32 {
match self {
SkillLevel::Beginner => 10,
SkillLevel::Intermediate => 30,
SkillLevel::Advanced => 50,
SkillLevel::Expert => 70,
SkillLevel::Master => 90,
}
}
pub fn label(&self) -> &'static str {
match self {
SkillLevel::Beginner => "Beginner",
SkillLevel::Intermediate => "Intermediate",
SkillLevel::Advanced => "Advanced",
SkillLevel::Expert => "Expert",
SkillLevel::Master => "Master",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
pub struct SkillRequirement {
pub skill_name: String,
pub required_level: SkillLevel,
pub is_mandatory: bool,
pub priority: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobProfile {
pub title: String,
pub required_skills: Vec<SkillRequirement>,
pub preferred_skills: Vec<SkillRequirement>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillGap {
pub skill_name: String,
pub current_level: SkillLevel,
pub required_level: SkillLevel,
pub gap_score: i32,
pub is_critical: bool,
pub recommendation: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GapAnalysis {
pub job_title: String,
pub overall_match_percentage: f64,
pub gaps: Vec<SkillGap>,
pub strengths: Vec<String>,
pub missing_critical_skills: Vec<String>,
pub recommendations: Vec<String>,
}
pub struct SkillGapAnalyzer {
pub current_skills: HashMap<String, Skill>,
}
impl SkillGapAnalyzer {
pub fn new() -> Self {
Self {
current_skills: HashMap::new(),
}
}
pub fn load_from_profile(&mut self, skills: Vec<Skill>) {
for skill in skills {
self.current_skills.insert(skill.name.to_lowercase(), skill);
}
}
pub fn analyze_job(&self, job: &JobProfile) -> GapAnalysis {
let mut gaps = Vec::new();
let mut strengths = Vec::new();
let mut missing_critical = Vec::new();
let mut total_required = 0;
let mut met_requirements = 0;
for req in &job.required_skills {
total_required += 1;
let skill_key = req.skill_name.to_lowercase();
if let Some(current) = self.current_skills.get(&skill_key) {
let current_score = current.level.to_score();
let required_score = req.required_level.to_score();
let gap = required_score as i32 - current_score as i32;
if gap <= 0 {
met_requirements += 1;
strengths.push(req.skill_name.clone());
} else {
gaps.push(SkillGap {
skill_name: req.skill_name.clone(),
current_level: current.level,
required_level: req.required_level,
gap_score: gap,
is_critical: req.is_mandatory,
recommendation: Self::generate_recommendation(&req.skill_name, gap),
});
if req.is_mandatory {
missing_critical.push(req.skill_name.clone());
}
}
} else {
gaps.push(SkillGap {
skill_name: req.skill_name.clone(),
current_level: SkillLevel::Beginner,
required_level: req.required_level,
gap_score: req.required_level.to_score() as i32,
is_critical: req.is_mandatory,
recommendation: format!("Learn {} from scratch", req.skill_name),
});
if req.is_mandatory {
missing_critical.push(req.skill_name.clone());
}
}
}
for pref in &job.preferred_skills {
let skill_key = pref.skill_name.to_lowercase();
if let Some(current) = self.current_skills.get(&skill_key) {
if current.level >= pref.required_level {
strengths.push(pref.skill_name.clone());
}
}
}
let match_percentage = if total_required > 0 {
(met_requirements as f64 / total_required as f64) * 100.0
} else {
0.0
};
let recommendations = Self::generate_recommendations(&gaps);
GapAnalysis {
job_title: job.title.clone(),
overall_match_percentage: match_percentage,
gaps,
strengths,
missing_critical_skills: missing_critical,
recommendations,
}
}
fn generate_recommendation(skill: &str, gap: i32) -> String {
let _skill_lower = skill.to_lowercase();
if gap <= 10 {
format!("Practice {} in real projects to reach required level", skill)
} else if gap <= 30 {
format!("Take an intermediate {} course and build projects", skill)
} else {
format!("Start with {} fundamentals course, then progress to advanced topics", skill)
}
}
fn generate_recommendations(gaps: &[SkillGap]) -> Vec<String> {
let mut recs = Vec::new();
let critical: Vec<_> = gaps.iter().filter(|g| g.is_critical).collect();
if !critical.is_empty() {
recs.push(format!(
"Focus on {} critical skill(s) first: {}",
critical.len(),
critical.iter().map(|g| g.skill_name.as_str()).collect::<Vec<_>>().join(", ")
));
}
let largest_gap = gaps.iter().max_by_key(|g| g.gap_score);
if let Some(gap) = largest_gap {
recs.push(format!(
"Priority: {} (gap of {})",
gap.skill_name, gap.gap_score
));
}
recs
}
pub fn compare_skills(&self, other: &SkillGapAnalyzer) -> HashMap<String, (SkillLevel, SkillLevel)> {
let mut comparison = HashMap::new();
for (name, skill) in &self.current_skills {
if let Some(other_skill) = other.current_skills.get(name) {
comparison.insert(
name.clone(),
(skill.level, other_skill.level),
);
}
}
comparison
}
pub fn get_skill_summary(&self) -> Vec<(&String, &Skill)> {
let mut skills: Vec<_> = self.current_skills.iter().collect();
skills.sort_by(|a, b| b.1.level.cmp(&a.1.level));
skills
}
}
impl Default for SkillGapAnalyzer {
fn default() -> Self {
Self::new()
}
}
pub fn get_common_job_profiles() -> Vec<JobProfile> {
vec![
JobProfile {
title: "Senior Software Engineer".to_string(),
required_skills: vec![
SkillRequirement { skill_name: "Rust".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Python".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "JavaScript".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Git".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 2 },
SkillRequirement { skill_name: "Docker".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: true, priority: 2 },
],
preferred_skills: vec![
SkillRequirement { skill_name: "Kubernetes".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: false, priority: 3 },
SkillRequirement { skill_name: "AWS".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: false, priority: 3 },
],
},
JobProfile {
title: "Full Stack Developer".to_string(),
required_skills: vec![
SkillRequirement { skill_name: "JavaScript".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "TypeScript".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "React".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Node.js".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "SQL".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 2 },
],
preferred_skills: vec![
SkillRequirement { skill_name: "GraphQL".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: false, priority: 3 },
SkillRequirement { skill_name: "PostgreSQL".to_string(), required_level: SkillLevel::Advanced, is_mandatory: false, priority: 3 },
],
},
JobProfile {
title: "DevOps Engineer".to_string(),
required_skills: vec![
SkillRequirement { skill_name: "Docker".to_string(), required_level: SkillLevel::Expert, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Kubernetes".to_string(), required_level: SkillLevel::Expert, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "AWS".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Linux".to_string(), required_level: SkillLevel::Expert, is_mandatory: true, priority: 1 },
SkillRequirement { skill_name: "Terraform".to_string(), required_level: SkillLevel::Advanced, is_mandatory: true, priority: 2 },
],
preferred_skills: vec![
SkillRequirement { skill_name: "GCP".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: false, priority: 3 },
SkillRequirement { skill_name: "Ansible".to_string(), required_level: SkillLevel::Intermediate, is_mandatory: false, priority: 3 },
],
},
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_skill_level_from_score() {
assert_eq!(SkillLevel::from_score(10.0), SkillLevel::Beginner);
assert_eq!(SkillLevel::from_score(30.0), SkillLevel::Intermediate);
assert_eq!(SkillLevel::from_score(50.0), SkillLevel::Advanced);
assert_eq!(SkillLevel::from_score(70.0), SkillLevel::Expert);
assert_eq!(SkillLevel::from_score(90.0), SkillLevel::Master);
}
#[test]
fn test_skill_level_to_score() {
assert_eq!(SkillLevel::Beginner.to_score(), 10);
assert_eq!(SkillLevel::Intermediate.to_score(), 30);
assert_eq!(SkillLevel::Advanced.to_score(), 50);
assert_eq!(SkillLevel::Expert.to_score(), 70);
assert_eq!(SkillLevel::Master.to_score(), 90);
}
#[test]
fn test_skill_level_label() {
assert_eq!(SkillLevel::Beginner.label(), "Beginner");
assert_eq!(SkillLevel::Intermediate.label(), "Intermediate");
assert_eq!(SkillLevel::Advanced.label(), "Advanced");
assert_eq!(SkillLevel::Expert.label(), "Expert");
assert_eq!(SkillLevel::Master.label(), "Master");
}
#[test]
fn test_skill_level_ordering() {
assert!(SkillLevel::Beginner < SkillLevel::Intermediate);
assert!(SkillLevel::Intermediate < SkillLevel::Advanced);
assert!(SkillLevel::Advanced < SkillLevel::Expert);
assert!(SkillLevel::Expert < SkillLevel::Master);
}
#[test]
fn test_skill_requirement_ordering() {
let req1 = SkillRequirement {
skill_name: "Rust".to_string(),
required_level: SkillLevel::Expert,
is_mandatory: true,
priority: 1,
};
let req2 = SkillRequirement {
skill_name: "Python".to_string(),
required_level: SkillLevel::Intermediate,
is_mandatory: false,
priority: 2,
};
assert!(req1 > req2);
}
#[test]
fn test_get_common_job_profiles() {
let profiles = get_common_job_profiles();
assert!(!profiles.is_empty());
assert!(profiles.iter().any(|p| p.title.contains("Full Stack")));
}
#[test]
fn test_skill_gap_analyzer() {
let mut analyzer = SkillGapAnalyzer::new();
analyzer.load_from_profile(vec![
Skill {
name: "Rust".to_string(),
level: SkillLevel::Expert,
years_experience: Some(5.0),
last_used: Some("2024".to_string()),
projects_count: 20,
},
]);
let profiles = get_common_job_profiles();
if let Some(profile) = profiles.first() {
let analysis = analyzer.analyze_job(profile);
assert!(!analysis.missing_critical_skills.is_empty());
}
}
#[test]
fn test_skill_serialization() {
let skill = Skill {
name: "Rust".to_string(),
level: SkillLevel::Expert,
years_experience: Some(5.0),
last_used: Some("2024".to_string()),
projects_count: 20,
};
let serialized = serde_json::to_string(&skill).unwrap();
let deserialized: Skill = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.name, "Rust");
assert_eq!(deserialized.level, SkillLevel::Expert);
}
#[test]
fn test_skill_requirement_ordering_by_priority() {
let req1 = SkillRequirement {
skill_name: "Rust".to_string(),
required_level: SkillLevel::Expert,
is_mandatory: true,
priority: 1,
};
let req2 = SkillRequirement {
skill_name: "Python".to_string(),
required_level: SkillLevel::Intermediate,
is_mandatory: false,
priority: 2,
};
assert!(req1.priority < req2.priority);
}
}