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( userId: string, sources?: string[] ): 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: { ownerId: userId, dueDate: { not: null, }, status: { notIn: ['done', 'cancelled', 'archived'], }, }, include: { taskTags: { include: { tag: true, }, }, }, orderBy: { dueDate: 'asc', }, }); // Convertir et analyser les tâches let 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 par sources si spécifié if (sources && sources.length > 0) { deadlineTasks = deadlineTasks.filter((task) => sources.includes(task.source) ); } // 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( userId: string, sources?: string[] ): Promise { const metrics = await this.getDeadlineMetrics(userId, sources); 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 }; } }