import { TaskStatus } from '@/lib/types'; import { prisma } from '@/services/core/database'; import { getToday } from '@/lib/date-utils'; export interface DeadlineTask { id: string; title: string; status: TaskStatus; priority: string; dueDate: Date; daysRemaining: number; urgencyLevel: 'overdue' | 'critical' | 'warning' | 'normal'; source: string; tags: string[]; jiraKey?: string; } export interface DeadlineMetrics { overdue: DeadlineTask[]; critical: DeadlineTask[]; // 0-2 jours warning: DeadlineTask[]; // 3-7 jours upcoming: DeadlineTask[]; // 8-14 jours summary: { overdueCount: number; criticalCount: number; warningCount: number; upcomingCount: number; totalWithDeadlines: number; }; } export class DeadlineAnalyticsService { /** * Analyse les tâches selon leurs échéances */ static async getDeadlineMetrics(): Promise { try { const now = getToday(); // Récupérer toutes les tâches non terminées avec échéance const dbTasks = await prisma.task.findMany({ where: { dueDate: { not: null }, status: { notIn: ['done', 'cancelled', 'archived'] } }, include: { taskTags: { include: { tag: true } } }, orderBy: { dueDate: 'asc' } }); // Convertir et analyser les tâches const deadlineTasks: DeadlineTask[] = dbTasks.map(task => { const dueDate = task.dueDate!; const daysRemaining = Math.ceil((dueDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); let urgencyLevel: DeadlineTask['urgencyLevel']; if (daysRemaining < 0) { urgencyLevel = 'overdue'; } else if (daysRemaining <= 2) { urgencyLevel = 'critical'; } else if (daysRemaining <= 7) { urgencyLevel = 'warning'; } else { urgencyLevel = 'normal'; } return { id: task.id, title: task.title, status: task.status as TaskStatus, priority: task.priority, dueDate, daysRemaining, urgencyLevel, source: task.source, tags: task.taskTags.map(tt => tt.tag.name), jiraKey: task.jiraKey || undefined }; }); // Filtrer les tâches dans les 2 prochaines semaines const relevantTasks = deadlineTasks.filter(task => task.daysRemaining <= 14 || task.urgencyLevel === 'overdue' ); const overdue = relevantTasks.filter(t => t.urgencyLevel === 'overdue'); const critical = relevantTasks.filter(t => t.urgencyLevel === 'critical'); const warning = relevantTasks.filter(t => t.urgencyLevel === 'warning'); const upcoming = relevantTasks.filter(t => t.urgencyLevel === 'normal' && t.daysRemaining <= 14); return { overdue, critical, warning, upcoming, summary: { overdueCount: overdue.length, criticalCount: critical.length, warningCount: warning.length, upcomingCount: upcoming.length, totalWithDeadlines: deadlineTasks.length } }; } catch (error) { console.error('Erreur lors de l\'analyse des échéances:', error); throw new Error('Impossible d\'analyser les échéances'); } } /** * Retourne les tâches les plus critiques (en retard + échéance dans 48h) */ static async getCriticalDeadlines(): Promise { const metrics = await this.getDeadlineMetrics(); return [ ...metrics.overdue, ...metrics.critical ].slice(0, 10); // Limite à 10 tâches les plus critiques } /** * Analyse l'impact des échéances par priorité */ static analyzeImpactByPriority(tasks: DeadlineTask[]): Array<{ priority: string; count: number; overdueCount: number; criticalCount: number; }> { const priorityGroups = new Map(); tasks.forEach(task => { const priority = task.priority || 'medium'; if (!priorityGroups.has(priority)) { priorityGroups.set(priority, []); } priorityGroups.get(priority)!.push(task); }); return Array.from(priorityGroups.entries()).map(([priority, tasks]) => ({ priority, count: tasks.length, overdueCount: tasks.filter(t => t.urgencyLevel === 'overdue').length, criticalCount: tasks.filter(t => t.urgencyLevel === 'critical').length })).sort((a, b) => { // Trier par impact (retard + critique) puis par priorité const aImpact = a.overdueCount + a.criticalCount; const bImpact = b.overdueCount + b.criticalCount; if (aImpact !== bImpact) return bImpact - aImpact; const priorityOrder: Record = { urgent: 4, high: 3, medium: 2, low: 1 }; return (priorityOrder[b.priority] || 2) - (priorityOrder[a.priority] || 2); }); } /** * Calcule les métriques de risque */ static calculateRiskMetrics(metrics: DeadlineMetrics): { riskScore: number; // 0-100 riskLevel: 'low' | 'medium' | 'high' | 'critical'; recommendation: string; } { const { summary } = metrics; // Calcul du score de risque basé sur les échéances let riskScore = 0; riskScore += summary.overdueCount * 25; // Retard = très grave riskScore += summary.criticalCount * 15; // Critique = grave riskScore += summary.warningCount * 5; // Avertissement = attention riskScore += summary.upcomingCount * 1; // À venir = surveillance // Limiter à 100 riskScore = Math.min(riskScore, 100); let riskLevel: 'low' | 'medium' | 'high' | 'critical'; let recommendation: string; if (riskScore >= 75) { riskLevel = 'critical'; recommendation = 'Action immédiate requise ! Plusieurs tâches en retard ou critiques.'; } else if (riskScore >= 50) { riskLevel = 'high'; recommendation = 'Attention : échéances critiques approchent, planifier les priorités.'; } else if (riskScore >= 25) { riskLevel = 'medium'; recommendation = 'Surveillance nécessaire, quelques échéances à surveiller.'; } else { riskLevel = 'low'; recommendation = 'Situation stable, échéances sous contrôle.'; } return { riskScore, riskLevel, recommendation }; } }