Refactoring
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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<AnalyticsData, CalculerMetriqueParams> {
|
||||
const CalculerMetriqueUseCase(this.repository);
|
||||
|
||||
final AnalyticsRepository repository;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AnalyticsData>> 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<Either<Failure, AnalyticsData>> _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<String, dynamic> 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<String, dynamic> _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<String, dynamic> 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<String, dynamic>.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<String>.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<Object?> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
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 '../entities/kpi_trend.dart';
|
||||
import '../repositories/analytics_repository.dart';
|
||||
|
||||
/// Use case pour calculer les tendances d'un KPI
|
||||
class CalculerTendanceKPIUseCase implements UseCase<KPITrend, CalculerTendanceKPIParams> {
|
||||
const CalculerTendanceKPIUseCase(this.repository);
|
||||
|
||||
final AnalyticsRepository repository;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, KPITrend>> call(CalculerTendanceKPIParams params) async {
|
||||
// Vérifier d'abord le cache si pas de recalcul forcé
|
||||
if (!params.forceRecalcul) {
|
||||
final cacheKey = _genererCleCacheTendance(params);
|
||||
final cacheResult = await repository.recupererDepuisCache(cle: cacheKey);
|
||||
|
||||
final cachedTrend = await cacheResult.fold(
|
||||
(failure) => null,
|
||||
(cachedData) {
|
||||
if (cachedData != null && _isCacheValide(cachedData)) {
|
||||
return _mapCacheToKPITrend(cachedData);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
if (cachedTrend != null) {
|
||||
return Right(cachedTrend);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer la tendance depuis le serveur
|
||||
return _calculerEtCacherTendance(params);
|
||||
}
|
||||
|
||||
/// Calcule la tendance et la met en cache
|
||||
Future<Either<Failure, KPITrend>> _calculerEtCacherTendance(
|
||||
CalculerTendanceKPIParams params,
|
||||
) async {
|
||||
final result = await repository.calculerTendanceKPI(
|
||||
typeMetrique: params.typeMetrique,
|
||||
periodeAnalyse: params.periodeAnalyse,
|
||||
organisationId: params.organisationId,
|
||||
);
|
||||
|
||||
return result.fold(
|
||||
(failure) => Left(failure),
|
||||
(kpiTrend) async {
|
||||
// Mettre en cache le résultat
|
||||
final cacheKey = _genererCleCacheTendance(params);
|
||||
await repository.mettreEnCache(
|
||||
cle: cacheKey,
|
||||
donnees: _mapKPITrendToCache(kpiTrend),
|
||||
dureeVie: _determinerDureeVieCache(params.periodeAnalyse),
|
||||
);
|
||||
|
||||
return Right(kpiTrend);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Génère une clé de cache unique pour la tendance
|
||||
String _genererCleCacheTendance(CalculerTendanceKPIParams params) {
|
||||
return 'tendance_${params.typeMetrique.name}_${params.periodeAnalyse.name}_${params.organisationId ?? 'global'}';
|
||||
}
|
||||
|
||||
/// Vérifie si les données du cache sont encore valides
|
||||
bool _isCacheValide(Map<String, dynamic> cachedData) {
|
||||
final dateCache = DateTime.tryParse(cachedData['dateCache'] ?? '');
|
||||
if (dateCache == null) return false;
|
||||
|
||||
final dureeVie = Duration(minutes: cachedData['dureeVieMinutes'] ?? 120);
|
||||
return DateTime.now().difference(dateCache) < dureeVie;
|
||||
}
|
||||
|
||||
/// Convertit KPITrend en format cache
|
||||
Map<String, dynamic> _mapKPITrendToCache(KPITrend trend) {
|
||||
return {
|
||||
'id': trend.id,
|
||||
'typeMetrique': trend.typeMetrique.name,
|
||||
'periodeAnalyse': trend.periodeAnalyse.name,
|
||||
'organisationId': trend.organisationId,
|
||||
'nomOrganisation': trend.nomOrganisation,
|
||||
'dateDebut': trend.dateDebut.toIso8601String(),
|
||||
'dateFin': trend.dateFin.toIso8601String(),
|
||||
'pointsDonnees': trend.pointsDonnees.map((point) => {
|
||||
'date': point.date.toIso8601String(),
|
||||
'valeur': point.valeur,
|
||||
'libelle': point.libelle,
|
||||
'anomalie': point.anomalie,
|
||||
'prediction': point.prediction,
|
||||
'metadonnees': point.metadonnees,
|
||||
}).toList(),
|
||||
'valeurActuelle': trend.valeurActuelle,
|
||||
'valeurMinimale': trend.valeurMinimale,
|
||||
'valeurMaximale': trend.valeurMaximale,
|
||||
'valeurMoyenne': trend.valeurMoyenne,
|
||||
'ecartType': trend.ecartType,
|
||||
'coefficientVariation': trend.coefficientVariation,
|
||||
'tendanceGenerale': trend.tendanceGenerale,
|
||||
'coefficientCorrelation': trend.coefficientCorrelation,
|
||||
'pourcentageEvolutionGlobale': trend.pourcentageEvolutionGlobale,
|
||||
'predictionProchainePeriode': trend.predictionProchainePeriode,
|
||||
'margeErreurPrediction': trend.margeErreurPrediction,
|
||||
'seuilAlerteBas': trend.seuilAlerteBas,
|
||||
'seuilAlerteHaut': trend.seuilAlerteHaut,
|
||||
'alerteActive': trend.alerteActive,
|
||||
'typeAlerte': trend.typeAlerte,
|
||||
'messageAlerte': trend.messageAlerte,
|
||||
'configurationGraphique': trend.configurationGraphique,
|
||||
'intervalleRegroupement': trend.intervalleRegroupement,
|
||||
'formatDate': trend.formatDate,
|
||||
'dateDerniereMiseAJour': trend.dateDerniereMiseAJour?.toIso8601String(),
|
||||
'frequenceMiseAJourMinutes': trend.frequenceMiseAJourMinutes,
|
||||
'dateCache': DateTime.now().toIso8601String(),
|
||||
'dureeVieMinutes': _determinerDureeVieCache(trend.periodeAnalyse).inMinutes,
|
||||
};
|
||||
}
|
||||
|
||||
/// Convertit les données du cache en KPITrend
|
||||
KPITrend _mapCacheToKPITrend(Map<String, dynamic> cachedData) {
|
||||
final pointsDonneesList = cachedData['pointsDonnees'] as List<dynamic>? ?? [];
|
||||
final pointsDonnees = pointsDonneesList.map((pointData) {
|
||||
return PointDonnee(
|
||||
date: DateTime.parse(pointData['date']),
|
||||
valeur: pointData['valeur']?.toDouble() ?? 0.0,
|
||||
libelle: pointData['libelle'],
|
||||
anomalie: pointData['anomalie'] ?? false,
|
||||
prediction: pointData['prediction'] ?? false,
|
||||
metadonnees: pointData['metadonnees'],
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return KPITrend(
|
||||
id: cachedData['id'],
|
||||
typeMetrique: TypeMetrique.values.firstWhere(
|
||||
(e) => e.name == cachedData['typeMetrique'],
|
||||
),
|
||||
periodeAnalyse: PeriodeAnalyse.values.firstWhere(
|
||||
(e) => e.name == cachedData['periodeAnalyse'],
|
||||
),
|
||||
organisationId: cachedData['organisationId'],
|
||||
nomOrganisation: cachedData['nomOrganisation'],
|
||||
dateDebut: DateTime.parse(cachedData['dateDebut']),
|
||||
dateFin: DateTime.parse(cachedData['dateFin']),
|
||||
pointsDonnees: pointsDonnees,
|
||||
valeurActuelle: cachedData['valeurActuelle']?.toDouble() ?? 0.0,
|
||||
valeurMinimale: cachedData['valeurMinimale']?.toDouble(),
|
||||
valeurMaximale: cachedData['valeurMaximale']?.toDouble(),
|
||||
valeurMoyenne: cachedData['valeurMoyenne']?.toDouble(),
|
||||
ecartType: cachedData['ecartType']?.toDouble(),
|
||||
coefficientVariation: cachedData['coefficientVariation']?.toDouble(),
|
||||
tendanceGenerale: cachedData['tendanceGenerale']?.toDouble(),
|
||||
coefficientCorrelation: cachedData['coefficientCorrelation']?.toDouble(),
|
||||
pourcentageEvolutionGlobale: cachedData['pourcentageEvolutionGlobale']?.toDouble(),
|
||||
predictionProchainePeriode: cachedData['predictionProchainePeriode']?.toDouble(),
|
||||
margeErreurPrediction: cachedData['margeErreurPrediction']?.toDouble(),
|
||||
seuilAlerteBas: cachedData['seuilAlerteBas']?.toDouble(),
|
||||
seuilAlerteHaut: cachedData['seuilAlerteHaut']?.toDouble(),
|
||||
alerteActive: cachedData['alerteActive'] ?? false,
|
||||
typeAlerte: cachedData['typeAlerte'],
|
||||
messageAlerte: cachedData['messageAlerte'],
|
||||
configurationGraphique: cachedData['configurationGraphique'],
|
||||
intervalleRegroupement: cachedData['intervalleRegroupement'],
|
||||
formatDate: cachedData['formatDate'],
|
||||
dateDerniereMiseAJour: cachedData['dateDerniereMiseAJour'] != null
|
||||
? DateTime.parse(cachedData['dateDerniereMiseAJour'])
|
||||
: null,
|
||||
frequenceMiseAJourMinutes: cachedData['frequenceMiseAJourMinutes'],
|
||||
);
|
||||
}
|
||||
|
||||
/// 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: 30); // 30 minutes pour les tendances récentes
|
||||
case PeriodeAnalyse.cetteSemaine:
|
||||
case PeriodeAnalyse.semaineDerniere:
|
||||
case PeriodeAnalyse.septDerniersJours:
|
||||
return const Duration(hours: 2); // 2 heures pour les tendances hebdomadaires
|
||||
case PeriodeAnalyse.ceMois:
|
||||
case PeriodeAnalyse.moisDernier:
|
||||
case PeriodeAnalyse.trenteDerniersJours:
|
||||
return const Duration(hours: 6); // 6 heures pour les tendances mensuelles
|
||||
case PeriodeAnalyse.troisDerniersMois:
|
||||
case PeriodeAnalyse.sixDerniersMois:
|
||||
return const Duration(hours: 24); // 24 heures pour les tendances trimestrielles
|
||||
case PeriodeAnalyse.cetteAnnee:
|
||||
case PeriodeAnalyse.anneeDerniere:
|
||||
return const Duration(days: 2); // 2 jours pour les tendances annuelles
|
||||
case PeriodeAnalyse.periodePersonnalisee:
|
||||
return const Duration(hours: 4); // 4 heures par défaut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Paramètres pour le use case CalculerTendanceKPI
|
||||
class CalculerTendanceKPIParams extends Equatable {
|
||||
const CalculerTendanceKPIParams({
|
||||
required this.typeMetrique,
|
||||
required this.periodeAnalyse,
|
||||
this.organisationId,
|
||||
this.forceRecalcul = false,
|
||||
this.inclureAnomalies = true,
|
||||
this.inclurePredictions = true,
|
||||
});
|
||||
|
||||
final TypeMetrique typeMetrique;
|
||||
final PeriodeAnalyse periodeAnalyse;
|
||||
final String? organisationId;
|
||||
final bool forceRecalcul;
|
||||
final bool inclureAnomalies;
|
||||
final bool inclurePredictions;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
typeMetrique,
|
||||
periodeAnalyse,
|
||||
organisationId,
|
||||
forceRecalcul,
|
||||
inclureAnomalies,
|
||||
inclurePredictions,
|
||||
];
|
||||
|
||||
CalculerTendanceKPIParams copyWith({
|
||||
TypeMetrique? typeMetrique,
|
||||
PeriodeAnalyse? periodeAnalyse,
|
||||
String? organisationId,
|
||||
bool? forceRecalcul,
|
||||
bool? inclureAnomalies,
|
||||
bool? inclurePredictions,
|
||||
}) {
|
||||
return CalculerTendanceKPIParams(
|
||||
typeMetrique: typeMetrique ?? this.typeMetrique,
|
||||
periodeAnalyse: periodeAnalyse ?? this.periodeAnalyse,
|
||||
organisationId: organisationId ?? this.organisationId,
|
||||
forceRecalcul: forceRecalcul ?? this.forceRecalcul,
|
||||
inclureAnomalies: inclureAnomalies ?? this.inclureAnomalies,
|
||||
inclurePredictions: inclurePredictions ?? this.inclurePredictions,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user