import { eq, sql, and, gte, lte } from "drizzle-orm"; import { getDb } from "./db"; import { questionnaires, envoisQuestionnaires, reponsesQuestionnaires, reponsesQuestions, questions, sequences, formations, formateurs } from "../drizzle/schema"; /** * Statistiques de taux de réponse par formation */ export async function getStatsByFormation() { const db = await getDb(); if (!db) throw new Error("Database not available"); const results = await db .select({ formationId: formations.id, formationNom: formations.nom, totalEnvois: sql`COUNT(DISTINCT ${envoisQuestionnaires.id})`, totalReponses: sql`SUM(CASE WHEN ${envoisQuestionnaires.dateReponse} IS NOT NULL THEN 1 ELSE 0 END)`, }) .from(envoisQuestionnaires) .innerJoin(sequences, eq(envoisQuestionnaires.sequenceId, sequences.id)) .innerJoin(formations, eq(sequences.formationId, formations.id)) .groupBy(formations.id, formations.nom); return results.map(r => ({ formationId: r.formationId, formationNom: r.formationNom, totalEnvois: Number(r.totalEnvois), totalReponses: Number(r.totalReponses), tauxReponse: Number(r.totalEnvois) > 0 ? Math.round((Number(r.totalReponses) / Number(r.totalEnvois)) * 100) : 0 })); } /** * Évolution temporelle des réponses (par mois) */ export async function getEvolutionTemporelle(startDate?: Date, endDate?: Date) { const db = await getDb(); if (!db) throw new Error("Database not available"); const conditions = []; if (startDate) { conditions.push(gte(envoisQuestionnaires.dateEnvoi, startDate)); } if (endDate) { conditions.push(lte(envoisQuestionnaires.dateEnvoi, endDate)); } const results = await db .select({ mois: sql`DATE_FORMAT(${envoisQuestionnaires.dateEnvoi}, '%Y-%m')`, totalEnvois: sql`COUNT(DISTINCT ${envoisQuestionnaires.id})`, totalReponses: sql`SUM(CASE WHEN ${envoisQuestionnaires.dateReponse} IS NOT NULL THEN 1 ELSE 0 END)`, }) .from(envoisQuestionnaires) .where(conditions.length > 0 ? and(...conditions) : undefined) .groupBy(sql`DATE_FORMAT(${envoisQuestionnaires.dateEnvoi}, '%Y-%m')`) .orderBy(sql`DATE_FORMAT(${envoisQuestionnaires.dateEnvoi}, '%Y-%m')`); return results.map(r => ({ mois: r.mois, totalEnvois: Number(r.totalEnvois), totalReponses: Number(r.totalReponses), tauxReponse: Number(r.totalEnvois) > 0 ? Math.round((Number(r.totalReponses) / Number(r.totalEnvois)) * 100) : 0 })); } /** * Comparaison par formateur */ export async function getStatsByFormateur() { const db = await getDb(); if (!db) throw new Error("Database not available"); // Première requête : statistiques d'envois et réponses par formateur const statsEnvois = await db .select({ formateurId: formateurs.id, formateurNom: formateurs.nom, totalEnvois: sql`COUNT(DISTINCT ${envoisQuestionnaires.id})`, totalReponses: sql`SUM(CASE WHEN ${envoisQuestionnaires.dateReponse} IS NOT NULL THEN 1 ELSE 0 END)`, }) .from(envoisQuestionnaires) .innerJoin(sequences, eq(envoisQuestionnaires.sequenceId, sequences.id)) .innerJoin(formateurs, eq(sequences.formateurId, formateurs.id)) .groupBy(formateurs.id, formateurs.nom); // Pour chaque formateur, calculer la moyenne de satisfaction const results = await Promise.all(statsEnvois.map(async (stat) => { // Calculer la moyenne des réponses de type échelle pour ce formateur const satisfactionResult = await db .select({ moyenne: sql`AVG(CAST(${reponsesQuestions.reponseNumerique} AS DECIMAL(10,2)))`, }) .from(reponsesQuestionnaires) .innerJoin(sequences, eq(reponsesQuestionnaires.sequenceId, sequences.id)) .innerJoin(reponsesQuestions, eq(reponsesQuestionnaires.id, reponsesQuestions.reponseQuestionnaireId)) .innerJoin(questions, eq(reponsesQuestions.questionId, questions.id)) .where( and( eq(sequences.formateurId, stat.formateurId), eq(reponsesQuestionnaires.complete, true), eq(questions.typeQuestion, 'echelle') ) ); const moyenneSatisfaction = satisfactionResult[0]?.moyenne ? Number(satisfactionResult[0].moyenne).toFixed(1) : null; return { formateurId: stat.formateurId, formateurNom: stat.formateurNom || "Non spécifié", totalEnvois: Number(stat.totalEnvois), totalReponses: Number(stat.totalReponses), tauxReponse: Number(stat.totalEnvois) > 0 ? Math.round((Number(stat.totalReponses) / Number(stat.totalEnvois)) * 100) : 0, moyenneSatisfaction }; })); return results; } /** * Statistiques globales pour le tableau de bord */ export async function getStatsGlobales() { const db = await getDb(); if (!db) throw new Error("Database not available"); const results = await db .select({ totalQuestionnaires: sql`COUNT(DISTINCT ${questionnaires.id})`, totalEnvois: sql`COUNT(DISTINCT ${envoisQuestionnaires.id})`, totalReponses: sql`SUM(CASE WHEN ${envoisQuestionnaires.dateReponse} IS NOT NULL THEN 1 ELSE 0 END)`, }) .from(questionnaires) .leftJoin(envoisQuestionnaires, eq(questionnaires.id, envoisQuestionnaires.questionnaireId)); const stats = results[0]; const totalEnvois = Number(stats.totalEnvois) || 0; const totalReponses = Number(stats.totalReponses) || 0; return { totalQuestionnaires: Number(stats.totalQuestionnaires) || 0, totalEnvois, totalReponses, tauxReponseGlobal: totalEnvois > 0 ? Number(((totalReponses / totalEnvois) * 100).toFixed(1)) : 0 }; }