import { WrappedData } from './types.js'; export interface HallOfFameQuestion { id: number; person: string; title: string; emoji: string; category: string; stat: { label: string; value: string | number; }; funFact: string; achievement: string; special?: { type: 'ratio' | 'percentage' | 'comparison' | 'unique'; description: string; }; } export class HallOfFameGenerator { private questions: HallOfFameQuestion[] = []; constructor(private data: WrappedData) { this.generatePersonalizedQuestions(); } private generatePersonalizedQuestions(): void { const authors = Object.keys(this.data.commitsByAuthor); const usedPeople = new Set(); // Find unique achievements for each person const achievements = this.findUniqueAchievements(); // Assign one achievement to each person achievements.forEach((achievement, index) => { if (!usedPeople.has(achievement.person)) { this.questions.push({ id: index + 1, ...achievement }); usedPeople.add(achievement.person); } }); // For anyone without an achievement yet, find something special authors.forEach(author => { if (!usedPeople.has(author)) { const specialAchievement = this.findSpecialAchievement(author); if (specialAchievement) { this.questions.push({ id: this.questions.length + 1, ...specialAchievement }); usedPeople.add(author); } } }); // Shuffle for variety this.questions = this.shuffleArray(this.questions); this.questions.forEach((q, i) => q.id = i + 1); } private shuffleArray(array: T[]): T[] { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } private findUniqueAchievements(): Omit[] { const achievements: Omit[] = []; // Champion des commits const [topCommitter, topCommits] = Object.entries(this.data.commitsByAuthor) .sort((a, b) => b[1] - a[1])[0]; achievements.push({ person: topCommitter, title: 'Champion des Commits', emoji: '🏆', category: 'Productivité', stat: { label: 'Commits cette année', value: topCommits.toLocaleString() }, funFact: `${topCommitter} a contribué ${topCommits} fois au projet!`, achievement: 'Le plus productif de l\'équipe' }); // Le Marathonien (plus longue série) const [streakChamp, longestStreak] = Object.entries(this.data.longestStreakByAuthor) .sort((a, b) => b[1] - a[1])[0]; if (streakChamp && longestStreak > 0) { achievements.push({ person: streakChamp, title: 'Le Marathonien', emoji: '🔥', category: 'Persévérance', stat: { label: 'Série consécutive', value: `${longestStreak} jours` }, funFact: `${longestStreak} jours d'affilée sans interruption!`, achievement: 'La plus longue série de commits' }); } // Le Hibou (plus de commits nocturnes) const nightOwls = Object.entries(this.data.nightOwlCommits) .filter(([_, v]) => v > 0) .sort((a, b) => b[1] - a[1]); if (nightOwls.length > 0) { const [nightOwl, nightCommits] = nightOwls[0]; achievements.push({ person: nightOwl, title: 'Le Hibou Nocturne', emoji: '🦉', category: 'Night Owl', stat: { label: 'Commits après 22h', value: nightCommits }, funFact: 'Les meilleures idées viennent la nuit!', achievement: 'Le codeur de minuit' }); } // Le Lève-Tôt const earlyBirds = Object.entries(this.data.earlyBirdCommits || {}) .filter(([_, v]) => v > 0) .sort((a, b) => b[1] - a[1]); if (earlyBirds.length > 0) { const [earlyBird, earlyCommits] = earlyBirds[0]; achievements.push({ person: earlyBird, title: 'L\'Alouette Matinale', emoji: '🌅', category: 'Early Bird', stat: { label: 'Commits avant 9h', value: earlyCommits }, funFact: 'Le monde appartient à ceux qui se lèvent tôt!', achievement: 'Le premier au bureau (virtuellement)' }); } // Le Friday Deployer const fridayDeployers = Object.entries(this.data.fridayWarriorCommits || {}) .filter(([_, v]) => v > 0) .sort((a, b) => b[1] - a[1]); if (fridayDeployers.length > 0) { const [friday, fridayCommits] = fridayDeployers[0]; achievements.push({ person: friday, title: 'Le Friday Deployer', emoji: '🎉', category: 'Risk Taker', stat: { label: 'Commits le vendredi', value: fridayCommits }, funFact: 'Vendredi deploy, weekend debug!', achievement: 'Le plus courageux (ou fou?)' }); } // Le Plus Régulier const [mostConsistent, activeDays] = Object.entries(this.data.uniqueActiveDaysByAuthor || {}) .sort((a, b) => b[1] - a[1])[0]; if (mostConsistent && activeDays > 0) { const percentage = Math.round((activeDays / 365) * 100); achievements.push({ person: mostConsistent, title: 'Le Plus Régulier', emoji: '📅', category: 'Régularité', stat: { label: 'Jours actifs', value: `${activeDays} jours (${percentage}%)` }, funFact: `Présent ${percentage}% de l'année!`, achievement: 'La constance incarnée' }); } // Le PR Master if (this.data.pullRequests) { const [prMaster, prs] = Object.entries(this.data.pullRequests.prsByAuthor) .map(([name, data]) => [name, data.merged] as [string, number]) .filter(([_, v]) => v > 0) .sort((a, b) => b[1] - a[1])[0] || []; if (prMaster) { achievements.push({ person: prMaster, title: 'Le PR Master', emoji: '✅', category: 'Collaboration', stat: { label: 'PRs mergées', value: prs }, funFact: 'Expert en code review!', achievement: 'Le roi des Pull Requests' }); } } // Le Spécialiste (fichier le plus modifié) let maxFileCommits = 0; let fileSpecialist = ''; let specialFile = ''; Object.entries(this.data.topFilesByAuthor || {}).forEach(([author, files]) => { if (files.length > 0 && files[0].commits > maxFileCommits) { maxFileCommits = files[0].commits; fileSpecialist = author; specialFile = files[0].file.split('/').pop() || files[0].file; } }); if (fileSpecialist) { achievements.push({ person: fileSpecialist, title: 'Le Spécialiste', emoji: '🎯', category: 'Expertise', stat: { label: 'Commits sur un fichier', value: maxFileCommits }, funFact: `Expert de ${specialFile}!`, achievement: 'Le maître d\'un domaine' }); } // L'Équilibreur (meilleur ratio ajout/suppression proche de 1) const balancedCoders = Object.entries(this.data.linesChangedByAuthor) .filter(([_, data]) => data.added > 100 && data.removed > 100) .map(([name, data]) => { const ratio = Math.min(data.added, data.removed) / Math.max(data.added, data.removed); return [name, ratio, data.added, data.removed] as [string, number, number, number]; }) .sort((a, b) => b[1] - a[1]); if (balancedCoders.length > 0) { const [balanced, ratio, added, removed] = balancedCoders[0]; achievements.push({ person: balanced, title: 'L\'Équilibreur', emoji: '⚖️', category: 'Balance', stat: { label: 'Ratio ajout/suppression', value: `${Math.round(ratio * 100)}%` }, funFact: `${added.toLocaleString()} ajoutées, ${removed.toLocaleString()} supprimées!`, achievement: 'L\'équilibre parfait entre création et nettoyage', special: { type: 'ratio', description: 'Le meilleur équilibre entre code ajouté et code nettoyé' } }); } return achievements; } private findSpecialAchievement(author: string): Omit | null { // Try to find ANY stat where this person is notable const commits = this.data.commitsByAuthor[author] || 0; const lines = this.data.linesChangedByAuthor[author]; const activeDays = this.data.uniqueActiveDaysByAuthor?.[author] || 0; // Positive Net Contributor (plus de lignes ajoutées que supprimées) if (lines && lines.net > 0) { return { person: author, title: 'Le Créateur', emoji: '✨', category: 'Innovation', stat: { label: 'Lignes nettes ajoutées', value: `+${lines.net.toLocaleString()}` }, funFact: `Le projet a grandi de ${lines.net.toLocaleString()} lignes grâce à ${author}!`, achievement: 'Contributeur positif net' }; } // Efficiency Master (ratio commits/jours actifs élevé) if (activeDays > 0) { const efficiency = commits / activeDays; if (efficiency >= 2) { return { person: author, title: 'L\'Efficace', emoji: '⚡', category: 'Efficacité', stat: { label: 'Commits par jour actif', value: efficiency.toFixed(1) }, funFact: `Quand ${author} code, ça avance vite!`, achievement: 'Productivité maximale' }; } } // The Consistent (présence régulière même si pas le premier) if (activeDays > 50) { return { person: author, title: 'Le Fiable', emoji: '🎖️', category: 'Fiabilité', stat: { label: 'Présence régulière', value: `${activeDays} jours` }, funFact: `Toujours là quand on a besoin!`, achievement: 'Membre fiable de l\'équipe' }; } // Last resort: Just acknowledge their contribution if (commits > 0) { return { person: author, title: 'Le Contributeur', emoji: '👏', category: 'Contribution', stat: { label: 'Commits', value: commits }, funFact: `Chaque commit compte!`, achievement: 'Membre important de l\'équipe' }; } return null; } getQuestions(): HallOfFameQuestion[] { return this.questions; } getQuestionCount(): number { return this.questions.length; } getPeopleCount(): number { return new Set(this.questions.map(q => q.person)).size; } }