import 'package:dartz/dartz.dart'; import 'package:equatable/equatable.dart'; import '../../../../core/error/failures.dart'; import '../../../../core/usecases/usecase.dart'; import '../entities/analytics_data.dart'; import '../repositories/analytics_repository.dart'; /// Use case pour calculer une métrique analytics class CalculerMetriqueUseCase implements UseCase { const CalculerMetriqueUseCase(this.repository); final AnalyticsRepository repository; @override Future> call(CalculerMetriqueParams params) async { // Vérifier d'abord le cache final cacheKey = _genererCleCacheMetrique(params); final cacheResult = await repository.recupererDepuisCache(cle: cacheKey); return cacheResult.fold( (failure) => _calculerEtCacherMetrique(params, cacheKey), (cachedData) { if (cachedData != null && _isCacheValide(cachedData)) { // Retourner les données du cache si elles sont valides return Right(_mapCacheToAnalyticsData(cachedData)); } else { // Recalculer si le cache est expiré ou invalide return _calculerEtCacherMetrique(params, cacheKey); } }, ); } /// Calcule la métrique et la met en cache Future> _calculerEtCacherMetrique( CalculerMetriqueParams params, String cacheKey, ) async { final result = await repository.calculerMetrique( typeMetrique: params.typeMetrique, periodeAnalyse: params.periodeAnalyse, organisationId: params.organisationId, ); return result.fold( (failure) => Left(failure), (analyticsData) async { // Mettre en cache le résultat await repository.mettreEnCache( cle: cacheKey, donnees: _mapAnalyticsDataToCache(analyticsData), dureeVie: _determinerDureeVieCache(params.periodeAnalyse), ); return Right(analyticsData); }, ); } /// Génère une clé de cache unique pour la métrique String _genererCleCacheMetrique(CalculerMetriqueParams params) { return 'metrique_${params.typeMetrique.name}_${params.periodeAnalyse.name}_${params.organisationId ?? 'global'}'; } /// Vérifie si les données du cache sont encore valides bool _isCacheValide(Map cachedData) { final dateCache = DateTime.tryParse(cachedData['dateCache'] ?? ''); if (dateCache == null) return false; final dureeVie = Duration(minutes: cachedData['dureeVieMinutes'] ?? 60); return DateTime.now().difference(dateCache) < dureeVie; } /// Convertit les données analytics en format cache Map _mapAnalyticsDataToCache(AnalyticsData data) { return { 'id': data.id, 'typeMetrique': data.typeMetrique.name, 'periodeAnalyse': data.periodeAnalyse.name, 'valeur': data.valeur, 'valeurPrecedente': data.valeurPrecedente, 'pourcentageEvolution': data.pourcentageEvolution, 'dateDebut': data.dateDebut.toIso8601String(), 'dateFin': data.dateFin.toIso8601String(), 'dateCalcul': data.dateCalcul.toIso8601String(), 'organisationId': data.organisationId, 'nomOrganisation': data.nomOrganisation, 'utilisateurId': data.utilisateurId, 'nomUtilisateur': data.nomUtilisateur, 'libellePersonnalise': data.libellePersonnalise, 'description': data.description, 'donneesDetaillees': data.donneesDetaillees, 'configurationGraphique': data.configurationGraphique, 'metadonnees': data.metadonnees, 'indicateurFiabilite': data.indicateurFiabilite, 'nombreElementsAnalyses': data.nombreElementsAnalyses, 'tempsCalculMs': data.tempsCalculMs, 'tempsReel': data.tempsReel, 'necessiteMiseAJour': data.necessiteMiseAJour, 'niveauPriorite': data.niveauPriorite, 'tags': data.tags, 'dateCache': DateTime.now().toIso8601String(), 'dureeVieMinutes': _determinerDureeVieCache(data.periodeAnalyse).inMinutes, }; } /// Convertit les données du cache en AnalyticsData AnalyticsData _mapCacheToAnalyticsData(Map cachedData) { return AnalyticsData( id: cachedData['id'], typeMetrique: TypeMetrique.values.firstWhere( (e) => e.name == cachedData['typeMetrique'], ), periodeAnalyse: PeriodeAnalyse.values.firstWhere( (e) => e.name == cachedData['periodeAnalyse'], ), valeur: cachedData['valeur']?.toDouble() ?? 0.0, valeurPrecedente: cachedData['valeurPrecedente']?.toDouble(), pourcentageEvolution: cachedData['pourcentageEvolution']?.toDouble(), dateDebut: DateTime.parse(cachedData['dateDebut']), dateFin: DateTime.parse(cachedData['dateFin']), dateCalcul: DateTime.parse(cachedData['dateCalcul']), organisationId: cachedData['organisationId'], nomOrganisation: cachedData['nomOrganisation'], utilisateurId: cachedData['utilisateurId'], nomUtilisateur: cachedData['nomUtilisateur'], libellePersonnalise: cachedData['libellePersonnalise'], description: cachedData['description'], donneesDetaillees: cachedData['donneesDetaillees'], configurationGraphique: cachedData['configurationGraphique'], metadonnees: cachedData['metadonnees'] != null ? Map.from(cachedData['metadonnees']) : null, indicateurFiabilite: cachedData['indicateurFiabilite']?.toDouble() ?? 95.0, nombreElementsAnalyses: cachedData['nombreElementsAnalyses'], tempsCalculMs: cachedData['tempsCalculMs'], tempsReel: cachedData['tempsReel'] ?? false, necessiteMiseAJour: cachedData['necessiteMiseAJour'] ?? false, niveauPriorite: cachedData['niveauPriorite'] ?? 3, tags: cachedData['tags'] != null ? List.from(cachedData['tags']) : null, ); } /// Détermine la durée de vie du cache selon la période Duration _determinerDureeVieCache(PeriodeAnalyse periode) { switch (periode) { case PeriodeAnalyse.aujourdHui: case PeriodeAnalyse.hier: return const Duration(minutes: 15); // 15 minutes pour les données récentes case PeriodeAnalyse.cetteSemaine: case PeriodeAnalyse.semaineDerniere: case PeriodeAnalyse.septDerniersJours: return const Duration(hours: 1); // 1 heure pour les données hebdomadaires case PeriodeAnalyse.ceMois: case PeriodeAnalyse.moisDernier: case PeriodeAnalyse.trenteDerniersJours: return const Duration(hours: 4); // 4 heures pour les données mensuelles case PeriodeAnalyse.troisDerniersMois: case PeriodeAnalyse.sixDerniersMois: return const Duration(hours: 12); // 12 heures pour les données trimestrielles case PeriodeAnalyse.cetteAnnee: case PeriodeAnalyse.anneeDerniere: return const Duration(days: 1); // 1 jour pour les données annuelles case PeriodeAnalyse.periodePersonnalisee: return const Duration(hours: 2); // 2 heures par défaut } } } /// Paramètres pour le use case CalculerMetrique class CalculerMetriqueParams extends Equatable { const CalculerMetriqueParams({ required this.typeMetrique, required this.periodeAnalyse, this.organisationId, this.forceRecalcul = false, }); final TypeMetrique typeMetrique; final PeriodeAnalyse periodeAnalyse; final String? organisationId; final bool forceRecalcul; @override List get props => [ typeMetrique, periodeAnalyse, organisationId, forceRecalcul, ]; CalculerMetriqueParams copyWith({ TypeMetrique? typeMetrique, PeriodeAnalyse? periodeAnalyse, String? organisationId, bool? forceRecalcul, }) { return CalculerMetriqueParams( typeMetrique: typeMetrique ?? this.typeMetrique, periodeAnalyse: periodeAnalyse ?? this.periodeAnalyse, organisationId: organisationId ?? this.organisationId, forceRecalcul: forceRecalcul ?? this.forceRecalcul, ); } }