Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,323 @@
import 'package:equatable/equatable.dart';
/// Énumération des types de métriques disponibles
enum TypeMetrique {
// Métriques membres
nombreMembresActifs('Nombre de membres actifs', 'membres', 'count'),
nombreMembresInactifs('Nombre de membres inactifs', 'membres', 'count'),
tauxCroissanceMembres('Taux de croissance des membres', 'membres', 'percentage'),
moyenneAgeMembres('Âge moyen des membres', 'membres', 'average'),
// Métriques financières
totalCotisationsCollectees('Total des cotisations collectées', 'finance', 'amount'),
cotisationsEnAttente('Cotisations en attente', 'finance', 'amount'),
tauxRecouvrementCotisations('Taux de recouvrement', 'finance', 'percentage'),
moyenneCotisationMembre('Cotisation moyenne par membre', 'finance', 'average'),
// Métriques événements
nombreEvenementsOrganises('Nombre d\'événements organisés', 'evenements', 'count'),
tauxParticipationEvenements('Taux de participation aux événements', 'evenements', 'percentage'),
moyenneParticipantsEvenement('Moyenne de participants par événement', 'evenements', 'average'),
// Métriques solidarité
nombreDemandesAide('Nombre de demandes d\'aide', 'solidarite', 'count'),
montantAidesAccordees('Montant des aides accordées', 'solidarite', 'amount'),
tauxApprobationAides('Taux d\'approbation des aides', 'solidarite', 'percentage');
const TypeMetrique(this.libelle, this.categorie, this.typeValeur);
final String libelle;
final String categorie;
final String typeValeur;
/// Retourne l'unité de mesure appropriée
String get unite {
switch (typeValeur) {
case 'percentage':
return '%';
case 'amount':
return 'XOF';
case 'average':
return typeValeur == 'moyenneAgeMembres' ? 'ans' : '';
default:
return '';
}
}
/// Retourne l'icône Material Design appropriée
String get icone {
switch (categorie) {
case 'membres':
return 'people';
case 'finance':
return 'attach_money';
case 'evenements':
return 'event';
case 'solidarite':
return 'favorite';
default:
return 'analytics';
}
}
/// Retourne la couleur appropriée
String get couleur {
switch (categorie) {
case 'membres':
return '#2196F3';
case 'finance':
return '#4CAF50';
case 'evenements':
return '#FF9800';
case 'solidarite':
return '#E91E63';
default:
return '#757575';
}
}
}
/// Énumération des périodes d'analyse
enum PeriodeAnalyse {
aujourdHui('Aujourd\'hui', 'today'),
hier('Hier', 'yesterday'),
cetteSemaine('Cette semaine', 'this_week'),
semaineDerniere('Semaine dernière', 'last_week'),
ceMois('Ce mois', 'this_month'),
moisDernier('Mois dernier', 'last_month'),
troisDerniersMois('3 derniers mois', 'last_3_months'),
sixDerniersMois('6 derniers mois', 'last_6_months'),
cetteAnnee('Cette année', 'this_year'),
anneeDerniere('Année dernière', 'last_year'),
septDerniersJours('7 derniers jours', 'last_7_days'),
trenteDerniersJours('30 derniers jours', 'last_30_days'),
periodePersonnalisee('Période personnalisée', 'custom');
const PeriodeAnalyse(this.libelle, this.code);
final String libelle;
final String code;
/// Vérifie si la période est courte (moins d'un mois)
bool get isPeriodeCourte {
return [
aujourdHui,
hier,
cetteSemaine,
semaineDerniere,
septDerniersJours
].contains(this);
}
/// Vérifie si la période est longue (plus d'un an)
bool get isPeriodeLongue {
return [cetteAnnee, anneeDerniere].contains(this);
}
}
/// Entité représentant une donnée analytics
class AnalyticsData extends Equatable {
const AnalyticsData({
required this.id,
required this.typeMetrique,
required this.periodeAnalyse,
required this.valeur,
this.valeurPrecedente,
this.pourcentageEvolution,
required this.dateDebut,
required this.dateFin,
required this.dateCalcul,
this.organisationId,
this.nomOrganisation,
this.utilisateurId,
this.nomUtilisateur,
this.libellePersonnalise,
this.description,
this.donneesDetaillees,
this.configurationGraphique,
this.metadonnees,
this.indicateurFiabilite = 95.0,
this.nombreElementsAnalyses,
this.tempsCalculMs,
this.tempsReel = false,
this.necessiteMiseAJour = false,
this.niveauPriorite = 3,
this.tags,
});
final String id;
final TypeMetrique typeMetrique;
final PeriodeAnalyse periodeAnalyse;
final double valeur;
final double? valeurPrecedente;
final double? pourcentageEvolution;
final DateTime dateDebut;
final DateTime dateFin;
final DateTime dateCalcul;
final String? organisationId;
final String? nomOrganisation;
final String? utilisateurId;
final String? nomUtilisateur;
final String? libellePersonnalise;
final String? description;
final String? donneesDetaillees;
final String? configurationGraphique;
final Map<String, dynamic>? metadonnees;
final double indicateurFiabilite;
final int? nombreElementsAnalyses;
final int? tempsCalculMs;
final bool tempsReel;
final bool necessiteMiseAJour;
final int niveauPriorite;
final List<String>? tags;
/// Retourne le libellé à afficher
String get libelleAffichage {
return libellePersonnalise?.isNotEmpty == true
? libellePersonnalise!
: typeMetrique.libelle;
}
/// Retourne l'unité de mesure
String get unite => typeMetrique.unite;
/// Retourne l'icône
String get icone => typeMetrique.icone;
/// Retourne la couleur
String get couleur => typeMetrique.couleur;
/// Vérifie si la métrique a évolué positivement
bool get hasEvolutionPositive {
return pourcentageEvolution != null && pourcentageEvolution! > 0;
}
/// Vérifie si la métrique a évolué négativement
bool get hasEvolutionNegative {
return pourcentageEvolution != null && pourcentageEvolution! < 0;
}
/// Vérifie si la métrique est stable
bool get isStable {
return pourcentageEvolution != null && pourcentageEvolution! == 0;
}
/// Retourne la tendance sous forme de texte
String get tendance {
if (hasEvolutionPositive) return 'hausse';
if (hasEvolutionNegative) return 'baisse';
return 'stable';
}
/// Vérifie si les données sont fiables
bool get isDonneesFiables => indicateurFiabilite >= 80.0;
/// Vérifie si la métrique est critique
bool get isCritique => niveauPriorite >= 4;
/// Formate la valeur avec l'unité appropriée
String get valeurFormatee {
switch (typeMetrique.typeValeur) {
case 'amount':
return '${valeur.toStringAsFixed(0)} ${unite}';
case 'percentage':
return '${valeur.toStringAsFixed(1)}${unite}';
case 'average':
return valeur.toStringAsFixed(1);
default:
return valeur.toStringAsFixed(0);
}
}
/// Formate le pourcentage d'évolution
String get evolutionFormatee {
if (pourcentageEvolution == null) return '';
final signe = pourcentageEvolution! >= 0 ? '+' : '';
return '$signe${pourcentageEvolution!.toStringAsFixed(1)}%';
}
@override
List<Object?> get props => [
id,
typeMetrique,
periodeAnalyse,
valeur,
valeurPrecedente,
pourcentageEvolution,
dateDebut,
dateFin,
dateCalcul,
organisationId,
nomOrganisation,
utilisateurId,
nomUtilisateur,
libellePersonnalise,
description,
donneesDetaillees,
configurationGraphique,
metadonnees,
indicateurFiabilite,
nombreElementsAnalyses,
tempsCalculMs,
tempsReel,
necessiteMiseAJour,
niveauPriorite,
tags,
];
AnalyticsData copyWith({
String? id,
TypeMetrique? typeMetrique,
PeriodeAnalyse? periodeAnalyse,
double? valeur,
double? valeurPrecedente,
double? pourcentageEvolution,
DateTime? dateDebut,
DateTime? dateFin,
DateTime? dateCalcul,
String? organisationId,
String? nomOrganisation,
String? utilisateurId,
String? nomUtilisateur,
String? libellePersonnalise,
String? description,
String? donneesDetaillees,
String? configurationGraphique,
Map<String, dynamic>? metadonnees,
double? indicateurFiabilite,
int? nombreElementsAnalyses,
int? tempsCalculMs,
bool? tempsReel,
bool? necessiteMiseAJour,
int? niveauPriorite,
List<String>? tags,
}) {
return AnalyticsData(
id: id ?? this.id,
typeMetrique: typeMetrique ?? this.typeMetrique,
periodeAnalyse: periodeAnalyse ?? this.periodeAnalyse,
valeur: valeur ?? this.valeur,
valeurPrecedente: valeurPrecedente ?? this.valeurPrecedente,
pourcentageEvolution: pourcentageEvolution ?? this.pourcentageEvolution,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
dateCalcul: dateCalcul ?? this.dateCalcul,
organisationId: organisationId ?? this.organisationId,
nomOrganisation: nomOrganisation ?? this.nomOrganisation,
utilisateurId: utilisateurId ?? this.utilisateurId,
nomUtilisateur: nomUtilisateur ?? this.nomUtilisateur,
libellePersonnalise: libellePersonnalise ?? this.libellePersonnalise,
description: description ?? this.description,
donneesDetaillees: donneesDetaillees ?? this.donneesDetaillees,
configurationGraphique: configurationGraphique ?? this.configurationGraphique,
metadonnees: metadonnees ?? this.metadonnees,
indicateurFiabilite: indicateurFiabilite ?? this.indicateurFiabilite,
nombreElementsAnalyses: nombreElementsAnalyses ?? this.nombreElementsAnalyses,
tempsCalculMs: tempsCalculMs ?? this.tempsCalculMs,
tempsReel: tempsReel ?? this.tempsReel,
necessiteMiseAJour: necessiteMiseAJour ?? this.necessiteMiseAJour,
niveauPriorite: niveauPriorite ?? this.niveauPriorite,
tags: tags ?? this.tags,
);
}
}

View File

@@ -0,0 +1,351 @@
import 'package:equatable/equatable.dart';
import 'analytics_data.dart';
/// Point de données pour une tendance KPI
class PointDonnee extends Equatable {
const PointDonnee({
required this.date,
required this.valeur,
this.libelle,
this.anomalie = false,
this.prediction = false,
this.metadonnees,
});
final DateTime date;
final double valeur;
final String? libelle;
final bool anomalie;
final bool prediction;
final String? metadonnees;
@override
List<Object?> get props => [
date,
valeur,
libelle,
anomalie,
prediction,
metadonnees,
];
PointDonnee copyWith({
DateTime? date,
double? valeur,
String? libelle,
bool? anomalie,
bool? prediction,
String? metadonnees,
}) {
return PointDonnee(
date: date ?? this.date,
valeur: valeur ?? this.valeur,
libelle: libelle ?? this.libelle,
anomalie: anomalie ?? this.anomalie,
prediction: prediction ?? this.prediction,
metadonnees: metadonnees ?? this.metadonnees,
);
}
}
/// Entité représentant les tendances et évolutions d'un KPI
class KPITrend extends Equatable {
const KPITrend({
required this.id,
required this.typeMetrique,
required this.periodeAnalyse,
this.organisationId,
this.nomOrganisation,
required this.dateDebut,
required this.dateFin,
required this.pointsDonnees,
required this.valeurActuelle,
this.valeurMinimale,
this.valeurMaximale,
this.valeurMoyenne,
this.ecartType,
this.coefficientVariation,
this.tendanceGenerale,
this.coefficientCorrelation,
this.pourcentageEvolutionGlobale,
this.predictionProchainePeriode,
this.margeErreurPrediction,
this.seuilAlerteBas,
this.seuilAlerteHaut,
this.alerteActive = false,
this.typeAlerte,
this.messageAlerte,
this.configurationGraphique,
this.intervalleRegroupement,
this.formatDate,
this.dateDerniereMiseAJour,
this.frequenceMiseAJourMinutes,
});
final String id;
final TypeMetrique typeMetrique;
final PeriodeAnalyse periodeAnalyse;
final String? organisationId;
final String? nomOrganisation;
final DateTime dateDebut;
final DateTime dateFin;
final List<PointDonnee> pointsDonnees;
final double valeurActuelle;
final double? valeurMinimale;
final double? valeurMaximale;
final double? valeurMoyenne;
final double? ecartType;
final double? coefficientVariation;
final double? tendanceGenerale;
final double? coefficientCorrelation;
final double? pourcentageEvolutionGlobale;
final double? predictionProchainePeriode;
final double? margeErreurPrediction;
final double? seuilAlerteBas;
final double? seuilAlerteHaut;
final bool alerteActive;
final String? typeAlerte;
final String? messageAlerte;
final String? configurationGraphique;
final String? intervalleRegroupement;
final String? formatDate;
final DateTime? dateDerniereMiseAJour;
final int? frequenceMiseAJourMinutes;
/// Retourne le libellé de la métrique
String get libelleMetrique => typeMetrique.libelle;
/// Retourne l'unité de mesure
String get unite => typeMetrique.unite;
/// Retourne l'icône de la métrique
String get icone => typeMetrique.icone;
/// Retourne la couleur de la métrique
String get couleur => typeMetrique.couleur;
/// Vérifie si la tendance est positive
bool get isTendancePositive {
return tendanceGenerale != null && tendanceGenerale! > 0;
}
/// Vérifie si la tendance est négative
bool get isTendanceNegative {
return tendanceGenerale != null && tendanceGenerale! < 0;
}
/// Vérifie si la tendance est stable
bool get isTendanceStable {
return tendanceGenerale != null && tendanceGenerale! == 0;
}
/// Retourne la volatilité du KPI
String get volatilite {
if (coefficientVariation == null) return 'inconnue';
if (coefficientVariation! <= 0.1) return 'faible';
if (coefficientVariation! <= 0.3) return 'moyenne';
return 'élevée';
}
/// Vérifie si la prédiction est fiable
bool get isPredictionFiable {
return coefficientCorrelation != null && coefficientCorrelation! >= 0.7;
}
/// Retourne le nombre de points de données
int get nombrePointsDonnees => pointsDonnees.length;
/// Vérifie si des anomalies ont été détectées
bool get hasAnomalies {
return pointsDonnees.any((point) => point.anomalie);
}
/// Retourne les points d'anomalies
List<PointDonnee> get pointsAnomalies {
return pointsDonnees.where((point) => point.anomalie).toList();
}
/// Retourne les points de prédiction
List<PointDonnee> get pointsPredictions {
return pointsDonnees.where((point) => point.prediction).toList();
}
/// Formate la valeur actuelle
String get valeurActuelleFormatee {
switch (typeMetrique.typeValeur) {
case 'amount':
return '${valeurActuelle.toStringAsFixed(0)} ${unite}';
case 'percentage':
return '${valeurActuelle.toStringAsFixed(1)}${unite}';
case 'average':
return valeurActuelle.toStringAsFixed(1);
default:
return valeurActuelle.toStringAsFixed(0);
}
}
/// Formate l'évolution globale
String get evolutionGlobaleFormatee {
if (pourcentageEvolutionGlobale == null) return '';
final signe = pourcentageEvolutionGlobale! >= 0 ? '+' : '';
return '$signe${pourcentageEvolutionGlobale!.toStringAsFixed(1)}%';
}
/// Formate la prédiction
String get predictionFormatee {
if (predictionProchainePeriode == null) return '';
switch (typeMetrique.typeValeur) {
case 'amount':
return '${predictionProchainePeriode!.toStringAsFixed(0)} ${unite}';
case 'percentage':
return '${predictionProchainePeriode!.toStringAsFixed(1)}${unite}';
case 'average':
return predictionProchainePeriode!.toStringAsFixed(1);
default:
return predictionProchainePeriode!.toStringAsFixed(0);
}
}
/// Retourne la description de la tendance
String get descriptionTendance {
if (isTendancePositive) {
return 'Tendance à la hausse';
} else if (isTendanceNegative) {
return 'Tendance à la baisse';
} else {
return 'Tendance stable';
}
}
/// Retourne l'icône de la tendance
String get iconeTendance {
if (isTendancePositive) {
return 'trending_up';
} else if (isTendanceNegative) {
return 'trending_down';
} else {
return 'trending_flat';
}
}
/// Retourne la couleur de la tendance
String get couleurTendance {
if (isTendancePositive) {
return '#4CAF50'; // Vert
} else if (isTendanceNegative) {
return '#F44336'; // Rouge
} else {
return '#FF9800'; // Orange
}
}
/// Retourne le niveau de confiance de la prédiction
String get niveauConfiancePrediction {
if (coefficientCorrelation == null) return 'Inconnu';
if (coefficientCorrelation! >= 0.9) return 'Très élevé';
if (coefficientCorrelation! >= 0.7) return 'Élevé';
if (coefficientCorrelation! >= 0.5) return 'Moyen';
if (coefficientCorrelation! >= 0.3) return 'Faible';
return 'Très faible';
}
@override
List<Object?> get props => [
id,
typeMetrique,
periodeAnalyse,
organisationId,
nomOrganisation,
dateDebut,
dateFin,
pointsDonnees,
valeurActuelle,
valeurMinimale,
valeurMaximale,
valeurMoyenne,
ecartType,
coefficientVariation,
tendanceGenerale,
coefficientCorrelation,
pourcentageEvolutionGlobale,
predictionProchainePeriode,
margeErreurPrediction,
seuilAlerteBas,
seuilAlerteHaut,
alerteActive,
typeAlerte,
messageAlerte,
configurationGraphique,
intervalleRegroupement,
formatDate,
dateDerniereMiseAJour,
frequenceMiseAJourMinutes,
];
KPITrend copyWith({
String? id,
TypeMetrique? typeMetrique,
PeriodeAnalyse? periodeAnalyse,
String? organisationId,
String? nomOrganisation,
DateTime? dateDebut,
DateTime? dateFin,
List<PointDonnee>? pointsDonnees,
double? valeurActuelle,
double? valeurMinimale,
double? valeurMaximale,
double? valeurMoyenne,
double? ecartType,
double? coefficientVariation,
double? tendanceGenerale,
double? coefficientCorrelation,
double? pourcentageEvolutionGlobale,
double? predictionProchainePeriode,
double? margeErreurPrediction,
double? seuilAlerteBas,
double? seuilAlerteHaut,
bool? alerteActive,
String? typeAlerte,
String? messageAlerte,
String? configurationGraphique,
String? intervalleRegroupement,
String? formatDate,
DateTime? dateDerniereMiseAJour,
int? frequenceMiseAJourMinutes,
}) {
return KPITrend(
id: id ?? this.id,
typeMetrique: typeMetrique ?? this.typeMetrique,
periodeAnalyse: periodeAnalyse ?? this.periodeAnalyse,
organisationId: organisationId ?? this.organisationId,
nomOrganisation: nomOrganisation ?? this.nomOrganisation,
dateDebut: dateDebut ?? this.dateDebut,
dateFin: dateFin ?? this.dateFin,
pointsDonnees: pointsDonnees ?? this.pointsDonnees,
valeurActuelle: valeurActuelle ?? this.valeurActuelle,
valeurMinimale: valeurMinimale ?? this.valeurMinimale,
valeurMaximale: valeurMaximale ?? this.valeurMaximale,
valeurMoyenne: valeurMoyenne ?? this.valeurMoyenne,
ecartType: ecartType ?? this.ecartType,
coefficientVariation: coefficientVariation ?? this.coefficientVariation,
tendanceGenerale: tendanceGenerale ?? this.tendanceGenerale,
coefficientCorrelation: coefficientCorrelation ?? this.coefficientCorrelation,
pourcentageEvolutionGlobale: pourcentageEvolutionGlobale ?? this.pourcentageEvolutionGlobale,
predictionProchainePeriode: predictionProchainePeriode ?? this.predictionProchainePeriode,
margeErreurPrediction: margeErreurPrediction ?? this.margeErreurPrediction,
seuilAlerteBas: seuilAlerteBas ?? this.seuilAlerteBas,
seuilAlerteHaut: seuilAlerteHaut ?? this.seuilAlerteHaut,
alerteActive: alerteActive ?? this.alerteActive,
typeAlerte: typeAlerte ?? this.typeAlerte,
messageAlerte: messageAlerte ?? this.messageAlerte,
configurationGraphique: configurationGraphique ?? this.configurationGraphique,
intervalleRegroupement: intervalleRegroupement ?? this.intervalleRegroupement,
formatDate: formatDate ?? this.formatDate,
dateDerniereMiseAJour: dateDerniereMiseAJour ?? this.dateDerniereMiseAJour,
frequenceMiseAJourMinutes: frequenceMiseAJourMinutes ?? this.frequenceMiseAJourMinutes,
);
}
}

View File

@@ -0,0 +1,139 @@
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/analytics_data.dart';
import '../entities/kpi_trend.dart';
/// Repository abstrait pour les analytics
abstract class AnalyticsRepository {
/// Calcule une métrique analytics pour une période donnée
Future<Either<Failure, AnalyticsData>> calculerMetrique({
required TypeMetrique typeMetrique,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Calcule les tendances d'un KPI sur une période
Future<Either<Failure, KPITrend>> calculerTendanceKPI({
required TypeMetrique typeMetrique,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Obtient tous les KPI pour une organisation
Future<Either<Failure, Map<TypeMetrique, double>>> obtenirTousLesKPI({
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Calcule le KPI de performance globale
Future<Either<Failure, double>> calculerPerformanceGlobale({
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Obtient les évolutions des KPI par rapport à la période précédente
Future<Either<Failure, Map<TypeMetrique, double>>> obtenirEvolutionsKPI({
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Obtient les métriques pour le tableau de bord
Future<Either<Failure, List<AnalyticsData>>> obtenirMetriquesTableauBord({
String? organisationId,
required String utilisateurId,
});
/// Obtient les types de métriques disponibles
Future<Either<Failure, List<TypeMetrique>>> obtenirTypesMetriques();
/// Obtient les périodes d'analyse disponibles
Future<Either<Failure, List<PeriodeAnalyse>>> obtenirPeriodesAnalyse();
/// Met en cache les données analytics
Future<Either<Failure, void>> mettreEnCache({
required String cle,
required Map<String, dynamic> donnees,
Duration? dureeVie,
});
/// Récupère les données depuis le cache
Future<Either<Failure, Map<String, dynamic>?>> recupererDepuisCache({
required String cle,
});
/// Vide le cache analytics
Future<Either<Failure, void>> viderCache();
/// Synchronise les données analytics avec le serveur
Future<Either<Failure, void>> synchroniserDonnees();
/// Vérifie si les données sont à jour
Future<Either<Failure, bool>> verifierMiseAJour({
required TypeMetrique typeMetrique,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
});
/// Obtient les alertes actives
Future<Either<Failure, List<AnalyticsData>>> obtenirAlertesActives({
String? organisationId,
});
/// Marque une alerte comme lue
Future<Either<Failure, void>> marquerAlerteLue({
required String alerteId,
});
/// Exporte les données analytics
Future<Either<Failure, String>> exporterDonnees({
required List<TypeMetrique> metriques,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
required String format, // 'json', 'csv', 'excel'
});
/// Obtient l'historique des calculs
Future<Either<Failure, List<AnalyticsData>>> obtenirHistoriqueCalculs({
required TypeMetrique typeMetrique,
String? organisationId,
int limite = 50,
});
/// Sauvegarde une configuration de rapport personnalisé
Future<Either<Failure, void>> sauvegarderConfigurationRapport({
required String nom,
required List<TypeMetrique> metriques,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
Map<String, dynamic>? configuration,
});
/// Obtient les configurations de rapports sauvegardées
Future<Either<Failure, List<Map<String, dynamic>>>> obtenirConfigurationsRapports({
String? organisationId,
});
/// Supprime une configuration de rapport
Future<Either<Failure, void>> supprimerConfigurationRapport({
required String configurationId,
});
/// Planifie une mise à jour automatique
Future<Either<Failure, void>> planifierMiseAJourAutomatique({
required TypeMetrique typeMetrique,
required PeriodeAnalyse periodeAnalyse,
String? organisationId,
required Duration frequence,
});
/// Annule une mise à jour automatique planifiée
Future<Either<Failure, void>> annulerMiseAJourAutomatique({
required String planificationId,
});
/// Obtient les statistiques d'utilisation des analytics
Future<Either<Failure, Map<String, dynamic>>> obtenirStatistiquesUtilisation({
String? organisationId,
String? utilisateurId,
});
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}