UNPKG

@sparring/tech-roles-library

Version:

Comprehensive tech roles and competencies library for 78 technical roles with 9 career levels each. Includes detailed competencies and career progression paths with complete bilingual support (EN/ES).

263 lines (223 loc) 7.92 kB
/** * Comparison API - Role and level comparison utilities * * Provides methods for comparing roles, analyzing competency gaps, and finding similar roles. * Used internally by the main library for career progression analysis. * * @module api/comparisons * @author 686f6c61 * @license MIT */ const { Validator } = require('../core/validator'); class ComparisonAPI { constructor(database, queryAPI) { this.db = database; this.queryAPI = queryAPI; } compareRoles(role1, role2, level) { const entry1 = this.queryAPI.getRoleByNameAndLevel(role1, level); const entry2 = this.queryAPI.getRoleByNameAndLevel(role2, level); const comp1Set = new Set([ ...entry1.coreCompetencies, ...entry1.complementaryCompetencies ]); const comp2Set = new Set([ ...entry2.coreCompetencies, ...entry2.complementaryCompetencies ]); const common = Array.from(comp1Set).filter(c => comp2Set.has(c)); const unique1 = Array.from(comp1Set).filter(c => !comp2Set.has(c)); const unique2 = Array.from(comp2Set).filter(c => !comp1Set.has(c)); const unionSize = comp1Set.size + comp2Set.size - common.length; const similarity = unionSize > 0 ? common.length / unionSize : 0; return { role1: { name: entry1.role, level: entry1.level, code: entry1.code }, role2: { name: entry2.role, level: entry2.level, code: entry2.code }, common: common, unique1: unique1, unique2: unique2, similarity: Math.round(similarity * 1000) / 1000, statistics: { totalCompetencies1: comp1Set.size, totalCompetencies2: comp2Set.size, commonCount: common.length, unique1Count: unique1.length, unique2Count: unique2.length } }; } compareLevels(roleName, fromLevel, toLevel) { const from = this.queryAPI.getRoleByNameAndLevel(roleName, fromLevel); const to = this.queryAPI.getRoleByNameAndLevel(roleName, toLevel); const fromSet = new Set([ ...from.coreCompetencies, ...from.complementaryCompetencies ]); const toSet = new Set([ ...to.coreCompetencies, ...to.complementaryCompetencies ]); const maintained = Array.from(fromSet).filter(c => toSet.has(c)); const newCompetencies = Array.from(toSet).filter(c => !fromSet.has(c)); const deprecated = Array.from(fromSet).filter(c => !toSet.has(c)); return { role: roleName, fromLevel: { level: from.level, code: from.code, yearsRange: from.yearsRange }, toLevel: { level: to.level, code: to.code, yearsRange: to.yearsRange }, maintained: maintained, new: newCompetencies, deprecated: deprecated, statistics: { maintainedCount: maintained.length, newCount: newCompetencies.length, deprecatedCount: deprecated.length, growthRate: fromSet.size > 0 ? Math.round((newCompetencies.length / fromSet.size) * 100) : 0 } }; } findSimilarRoles(roleName, threshold = 0.3) { Validator.validateRoleName(roleName); const targetLevels = this.queryAPI.getAllLevelsForRole(roleName); const targetCompetencies = new Set(); targetLevels.forEach(level => { level.coreCompetencies.forEach(c => targetCompetencies.add(c)); level.complementaryCompetencies.forEach(c => targetCompetencies.add(c)); }); const allRoles = this.db.getAllRoles(); const similarities = []; allRoles.forEach(role => { if (role === roleName) return; const roleLevels = this.queryAPI.getAllLevelsForRole(role); const roleCompetencies = new Set(); roleLevels.forEach(level => { level.coreCompetencies.forEach(c => roleCompetencies.add(c)); level.complementaryCompetencies.forEach(c => roleCompetencies.add(c)); }); const intersection = new Set( [...targetCompetencies].filter(c => roleCompetencies.has(c)) ); const union = new Set([...targetCompetencies, ...roleCompetencies]); const similarity = intersection.size / union.size; if (similarity >= threshold) { const commonCompetencies = Array.from(intersection); similarities.push({ role: role, category: roleLevels[0].category, similarity: Math.round(similarity * 1000) / 1000, commonCompetencies: commonCompetencies.slice(0, 10), totalCommon: commonCompetencies.length }); } }); return similarities.sort((a, b) => b.similarity - a.similarity); } getCompetencyGaps(roleName, fromLevel, toLevel) { const comparison = this.compareLevels(roleName, fromLevel, toLevel); const estimatedWeeks = comparison.new.length * 2; const estimatedMonths = Math.ceil(estimatedWeeks / 4); return { role: roleName, from: comparison.fromLevel, to: comparison.toLevel, gaps: comparison.new, gapCount: comparison.new.length, estimatedLearningTime: { weeks: estimatedWeeks, months: estimatedMonths }, recommendations: this.generateRecommendations(comparison.new) }; } generateRecommendations(newCompetencies) { const categories = { technical: [], leadership: [], architecture: [], processes: [] }; const leadershipKeywords = ['liderazgo', 'leadership', 'gestión', 'management', 'mentor']; const architectureKeywords = ['arquitectura', 'architecture', 'diseño', 'design', 'estrategia', 'strategy']; const processKeywords = ['proceso', 'process', 'metodología', 'methodology', 'governance']; newCompetencies.forEach(comp => { const compLower = comp.toLowerCase(); let categorized = false; if (leadershipKeywords.some(k => compLower.includes(k))) { categories.leadership.push(comp); categorized = true; } if (architectureKeywords.some(k => compLower.includes(k))) { categories.architecture.push(comp); categorized = true; } if (processKeywords.some(k => compLower.includes(k))) { categories.processes.push(comp); categorized = true; } if (!categorized) { categories.technical.push(comp); } }); return categories; } getCareerPath(roleName, fromLevel, toLevel) { Validator.validateRoleName(roleName); const allLevels = this.queryAPI.getAllLevelsForRole(roleName); const normalizedFrom = Validator.normalizeLevel(fromLevel); const normalizedTo = Validator.normalizeLevel(toLevel); const fromNum = parseInt(normalizedFrom.match(/\d/)[0]); const toNum = parseInt(normalizedTo.match(/\d/)[0]); if (fromNum >= toNum) { throw new Error('Target level must be higher than starting level'); } const path = []; for (let i = fromNum; i <= toNum; i++) { const level = allLevels.find(l => l.levelNumber === i); if (level) { const stepData = { level: level.level, code: level.code, yearsRange: level.yearsRange, indicators: level.indicators }; if (i > fromNum) { const prevLevel = allLevels.find(l => l.levelNumber === i - 1); if (prevLevel) { const comparison = this.compareLevels(roleName, prevLevel.level, level.level); stepData.newCompetencies = comparison.new; stepData.newCompetenciesCount = comparison.new.length; } } path.push(stepData); } } return { role: roleName, from: normalizedFrom, to: normalizedTo, steps: path, totalSteps: path.length, estimatedYears: path.length > 0 ? (path[path.length - 1].yearsRange.min - (path[0].yearsRange.min || 0)) : 0 }; } } module.exports = ComparisonAPI;