refactor: rule of coverage are in one place

This commit is contained in:
Julien Froidefond
2025-08-27 14:31:05 +02:00
parent a5bcdd34fb
commit a8cad0b2ec
16 changed files with 430 additions and 133 deletions

View File

@@ -7,7 +7,12 @@ import {
TeamMember,
TeamMemberSkill,
} from "@/lib/team-review-types";
import { SkillLevel } from "@/lib/types";
import { SkillLevel, SKILL_LEVEL_VALUES } from "@/lib/types";
import {
COVERAGE_OBJECTIVES,
isCoverageBelowObjective,
calculateSkillCoverage,
} from "@/lib/evaluation-utils";
export class TeamReviewService {
static async getTeamReviewData(teamId: string): Promise<TeamReviewData> {
@@ -100,7 +105,11 @@ export class TeamReviewService {
skillName: row.skill_name,
category: row.category,
importance: row.importance || "standard",
level: row.level as SkillLevel,
level: row.level as
| "never"
| "not-autonomous"
| "autonomous"
| "expert",
canMentor: row.can_mentor || false,
wantsToLearn: row.wants_to_learn || false,
};
@@ -140,8 +149,14 @@ export class TeamReviewService {
const teamMembers = evaluations.filter((e) => e.level).length;
const totalTeamMembers = membersMap.size;
const coverage =
totalTeamMembers > 0 ? (teamMembers / totalTeamMembers) * 100 : 0;
// Calculer la couverture en fonction des niveaux
const levels = evaluations.map((e) =>
e.level
? SKILL_LEVEL_VALUES[e.level as keyof typeof SKILL_LEVEL_VALUES]
: 0
);
const coverage = calculateSkillCoverage(levels, totalTeamMembers);
// Déterminer le niveau de risque en fonction de l'importance
const risk =
@@ -159,7 +174,7 @@ export class TeamReviewService {
category: skill.category,
icon: skill.icon,
importance: skill.importance || "standard",
team_members: teamMembers,
teamMembers,
experts,
mentors,
learners,
@@ -175,8 +190,8 @@ export class TeamReviewService {
if (!categoriesMap.has(skill.category)) {
categoriesMap.set(skill.category, {
category: skill.category,
total_skills: 0,
covered_skills: 0,
totalSkills: 0,
coveredSkills: 0,
experts: 0,
mentors: 0,
learners: 0,
@@ -189,13 +204,13 @@ export class TeamReviewService {
}
const categoryStats = categoriesMap.get(skill.category)!;
categoryStats.total_skills++;
categoryStats.totalSkills++;
const skillGap = skillGaps.find(
(gap) => gap.skillId === skill.skill_id
);
if (skillGap) {
if (skillGap.team_members > 0) categoryStats.covered_skills++;
if (skillGap.teamMembers > 0) categoryStats.coveredSkills++;
categoryStats.experts += skillGap.experts;
categoryStats.mentors += skillGap.mentors;
categoryStats.learners += skillGap.learners;
@@ -203,11 +218,14 @@ export class TeamReviewService {
// Calculer la couverture des compétences critiques
if (
skillGap.importance === "incontournable" &&
skillGap.coverage > 50
!isCoverageBelowObjective(skillGap.coverage, skillGap.importance)
) {
categoryStats.criticalSkillsCoverage.incontournable++;
}
if (skillGap.importance === "majeure" && skillGap.coverage > 50) {
if (
skillGap.importance === "majeure" &&
!isCoverageBelowObjective(skillGap.coverage, skillGap.importance)
) {
categoryStats.criticalSkillsCoverage.majeure++;
}
}
@@ -219,8 +237,8 @@ export class TeamReviewService {
).map((category) => ({
...category,
coverage:
category.total_skills > 0
? (category.covered_skills / category.total_skills) * 100
category.totalSkills > 0
? (category.coveredSkills / category.totalSkills) * 100
: 0,
}));
@@ -236,7 +254,14 @@ export class TeamReviewService {
incontournable:
(skillGaps
.filter((gap) => gap.importance === "incontournable")
.reduce((acc, gap) => acc + (gap.coverage > 50 ? 1 : 0), 0) /
.reduce(
(acc, gap) =>
acc +
(!isCoverageBelowObjective(gap.coverage, gap.importance)
? 1
: 0),
0
) /
Math.max(
1,
skillGaps.filter((gap) => gap.importance === "incontournable")
@@ -246,7 +271,14 @@ export class TeamReviewService {
majeure:
(skillGaps
.filter((gap) => gap.importance === "majeure")
.reduce((acc, gap) => acc + (gap.coverage > 50 ? 1 : 0), 0) /
.reduce(
(acc, gap) =>
acc +
(!isCoverageBelowObjective(gap.coverage, gap.importance)
? 1
: 0),
0
) /
Math.max(
1,
skillGaps.filter((gap) => gap.importance === "majeure").length
@@ -298,7 +330,9 @@ export class TeamReviewService {
// Analyser les gaps critiques par importance
const uncoveredIncontournables = skillGaps.filter(
(gap) => gap.importance === "incontournable" && gap.coverage < 50
(gap) =>
gap.importance === "incontournable" &&
isCoverageBelowObjective(gap.coverage, gap.importance)
);
if (uncoveredIncontournables.length > 0) {
recommendations.push(
@@ -311,7 +345,9 @@ export class TeamReviewService {
}
const uncoveredMajeures = skillGaps.filter(
(gap) => gap.importance === "majeure" && gap.coverage < 30
(gap) =>
gap.importance === "majeure" &&
isCoverageBelowObjective(gap.coverage, gap.importance)
);
if (uncoveredMajeures.length > 0) {
recommendations.push(
@@ -338,7 +374,7 @@ export class TeamReviewService {
// Analyser la couverture des catégories
const lowCoverageCategories = categoryCoverage
.filter((cat) => cat.coverage < 50)
.filter((cat) => cat.coverage < COVERAGE_OBJECTIVES.majeure)
.map((cat) => cat.category);
if (lowCoverageCategories.length > 0) {
recommendations.push(