Refactoring
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../../../core/error/failures.dart';
|
||||
import '../../../../core/usecases/usecase.dart';
|
||||
import '../entities/demande_aide.dart';
|
||||
import '../entities/proposition_aide.dart';
|
||||
import '../repositories/solidarite_repository.dart';
|
||||
|
||||
/// Cas d'usage pour trouver les propositions compatibles avec une demande
|
||||
class TrouverPropositionsCompatiblesUseCase implements UseCase<List<PropositionAide>, TrouverPropositionsCompatiblesParams> {
|
||||
final SolidariteRepository repository;
|
||||
|
||||
TrouverPropositionsCompatiblesUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<PropositionAide>>> call(TrouverPropositionsCompatiblesParams params) async {
|
||||
return await repository.trouverPropositionsCompatibles(params.demandeId);
|
||||
}
|
||||
}
|
||||
|
||||
class TrouverPropositionsCompatiblesParams {
|
||||
final String demandeId;
|
||||
|
||||
TrouverPropositionsCompatiblesParams({required this.demandeId});
|
||||
}
|
||||
|
||||
/// Cas d'usage pour trouver les demandes compatibles avec une proposition
|
||||
class TrouverDemandesCompatiblesUseCase implements UseCase<List<DemandeAide>, TrouverDemandesCompatiblesParams> {
|
||||
final SolidariteRepository repository;
|
||||
|
||||
TrouverDemandesCompatiblesUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<DemandeAide>>> call(TrouverDemandesCompatiblesParams params) async {
|
||||
return await repository.trouverDemandesCompatibles(params.propositionId);
|
||||
}
|
||||
}
|
||||
|
||||
class TrouverDemandesCompatiblesParams {
|
||||
final String propositionId;
|
||||
|
||||
TrouverDemandesCompatiblesParams({required this.propositionId});
|
||||
}
|
||||
|
||||
/// Cas d'usage pour rechercher des proposants financiers
|
||||
class RechercherProposantsFinanciersUseCase implements UseCase<List<PropositionAide>, RechercherProposantsFinanciersParams> {
|
||||
final SolidariteRepository repository;
|
||||
|
||||
RechercherProposantsFinanciersUseCase(this.repository);
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<PropositionAide>>> call(RechercherProposantsFinanciersParams params) async {
|
||||
return await repository.rechercherProposantsFinanciers(params.demandeId);
|
||||
}
|
||||
}
|
||||
|
||||
class RechercherProposantsFinanciersParams {
|
||||
final String demandeId;
|
||||
|
||||
RechercherProposantsFinanciersParams({required this.demandeId});
|
||||
}
|
||||
|
||||
/// Cas d'usage pour calculer le score de compatibilité entre une demande et une proposition
|
||||
class CalculerScoreCompatibiliteUseCase implements UseCase<double, CalculerScoreCompatibiliteParams> {
|
||||
CalculerScoreCompatibiliteUseCase();
|
||||
|
||||
@override
|
||||
Future<Either<Failure, double>> call(CalculerScoreCompatibiliteParams params) async {
|
||||
try {
|
||||
final demande = params.demande;
|
||||
final proposition = params.proposition;
|
||||
|
||||
double score = 0.0;
|
||||
|
||||
// 1. Correspondance du type d'aide (40 points max)
|
||||
if (demande.typeAide == proposition.typeAide) {
|
||||
score += 40.0;
|
||||
} else if (_sontTypesCompatibles(demande.typeAide, proposition.typeAide)) {
|
||||
score += 25.0;
|
||||
} else if (proposition.typeAide == TypeAide.autre) {
|
||||
score += 15.0;
|
||||
}
|
||||
|
||||
// 2. Compatibilité financière (25 points max)
|
||||
if (_necessiteMontant(demande.typeAide) && proposition.montantMaximum != null) {
|
||||
final montantDemande = demande.montantDemande;
|
||||
if (montantDemande != null) {
|
||||
if (montantDemande <= proposition.montantMaximum!) {
|
||||
score += 25.0;
|
||||
} else {
|
||||
// Pénalité proportionnelle au dépassement
|
||||
double ratio = proposition.montantMaximum! / montantDemande;
|
||||
score += 25.0 * ratio;
|
||||
}
|
||||
}
|
||||
} else if (!_necessiteMontant(demande.typeAide)) {
|
||||
score += 25.0; // Pas de contrainte financière
|
||||
}
|
||||
|
||||
// 3. Expérience du proposant (15 points max)
|
||||
if (proposition.nombreBeneficiairesAides > 0) {
|
||||
score += (proposition.nombreBeneficiairesAides * 2.0).clamp(0.0, 15.0);
|
||||
}
|
||||
|
||||
// 4. Réputation (10 points max)
|
||||
if (proposition.noteMoyenne != null && proposition.nombreEvaluations >= 3) {
|
||||
score += (proposition.noteMoyenne! - 3.0) * 3.33;
|
||||
}
|
||||
|
||||
// 5. Disponibilité et capacité (10 points max)
|
||||
if (proposition.peutAccepterBeneficiaires) {
|
||||
double ratioCapacite = proposition.placesRestantes / proposition.nombreMaxBeneficiaires;
|
||||
score += 10.0 * ratioCapacite;
|
||||
}
|
||||
|
||||
// Bonus et malus additionnels
|
||||
score += _calculerBonusGeographique(demande, proposition);
|
||||
score += _calculerBonusTemporel(demande, proposition);
|
||||
score -= _calculerMalusDelai(demande, proposition);
|
||||
|
||||
return Right(score.clamp(0.0, 100.0));
|
||||
} catch (e) {
|
||||
return Left(UnexpectedFailure('Erreur lors du calcul de compatibilité: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
bool _sontTypesCompatibles(TypeAide typeAide1, TypeAide typeAide2) {
|
||||
// Définir les groupes de types compatibles
|
||||
final groupesCompatibles = [
|
||||
[TypeAide.aideFinanciereUrgente, TypeAide.aideFinanciereMedicale, TypeAide.aideFinanciereEducation],
|
||||
[TypeAide.aideMaterielleVetements, TypeAide.aideMaterielleNourriture],
|
||||
[TypeAide.aideProfessionnelleFormation, TypeAide.aideSocialeAccompagnement],
|
||||
];
|
||||
|
||||
for (final groupe in groupesCompatibles) {
|
||||
if (groupe.contains(typeAide1) && groupe.contains(typeAide2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _necessiteMontant(TypeAide typeAide) {
|
||||
return [
|
||||
TypeAide.aideFinanciereUrgente,
|
||||
TypeAide.aideFinanciereMedicale,
|
||||
TypeAide.aideFinanciereEducation,
|
||||
].contains(typeAide);
|
||||
}
|
||||
|
||||
double _calculerBonusGeographique(DemandeAide demande, PropositionAide proposition) {
|
||||
// Simulation - dans une vraie implémentation, on utiliserait les données de localisation
|
||||
if (demande.localisation != null && proposition.zonesGeographiques.isNotEmpty) {
|
||||
// Logique de proximité géographique
|
||||
return 5.0;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double _calculerBonusTemporel(DemandeAide demande, PropositionAide proposition) {
|
||||
double bonus = 0.0;
|
||||
|
||||
// Bonus pour demande urgente
|
||||
if (demande.estUrgente) {
|
||||
bonus += 5.0;
|
||||
}
|
||||
|
||||
// Bonus pour proposition récente
|
||||
final joursDepuisCreation = DateTime.now().difference(proposition.dateCreation).inDays;
|
||||
if (joursDepuisCreation <= 30) {
|
||||
bonus += 3.0;
|
||||
}
|
||||
|
||||
return bonus;
|
||||
}
|
||||
|
||||
double _calculerMalusDelai(DemandeAide demande, PropositionAide proposition) {
|
||||
double malus = 0.0;
|
||||
|
||||
// Malus si la demande est en retard
|
||||
if (demande.delaiDepasse) {
|
||||
malus += 5.0;
|
||||
}
|
||||
|
||||
// Malus si la proposition a un délai de réponse long
|
||||
if (proposition.delaiReponseHeures > 168) { // Plus d'une semaine
|
||||
malus += 3.0;
|
||||
}
|
||||
|
||||
return malus;
|
||||
}
|
||||
}
|
||||
|
||||
class CalculerScoreCompatibiliteParams {
|
||||
final DemandeAide demande;
|
||||
final PropositionAide proposition;
|
||||
|
||||
CalculerScoreCompatibiliteParams({
|
||||
required this.demande,
|
||||
required this.proposition,
|
||||
});
|
||||
}
|
||||
|
||||
/// Cas d'usage pour effectuer un matching intelligent
|
||||
class EffectuerMatchingIntelligentUseCase implements UseCase<List<ResultatMatching>, EffectuerMatchingIntelligentParams> {
|
||||
final TrouverPropositionsCompatiblesUseCase trouverPropositionsCompatibles;
|
||||
final CalculerScoreCompatibiliteUseCase calculerScoreCompatibilite;
|
||||
|
||||
EffectuerMatchingIntelligentUseCase({
|
||||
required this.trouverPropositionsCompatibles,
|
||||
required this.calculerScoreCompatibilite,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, List<ResultatMatching>>> call(EffectuerMatchingIntelligentParams params) async {
|
||||
try {
|
||||
// 1. Trouver les propositions compatibles
|
||||
final propositionsResult = await trouverPropositionsCompatibles(
|
||||
TrouverPropositionsCompatiblesParams(demandeId: params.demande.id)
|
||||
);
|
||||
|
||||
return propositionsResult.fold(
|
||||
(failure) => Left(failure),
|
||||
(propositions) async {
|
||||
// 2. Calculer les scores de compatibilité
|
||||
final resultats = <ResultatMatching>[];
|
||||
|
||||
for (final proposition in propositions) {
|
||||
final scoreResult = await calculerScoreCompatibilite(
|
||||
CalculerScoreCompatibiliteParams(
|
||||
demande: params.demande,
|
||||
proposition: proposition,
|
||||
)
|
||||
);
|
||||
|
||||
scoreResult.fold(
|
||||
(failure) {
|
||||
// Ignorer les erreurs de calcul de score individuel
|
||||
},
|
||||
(score) {
|
||||
if (score >= params.scoreMinimum) {
|
||||
resultats.add(ResultatMatching(
|
||||
proposition: proposition,
|
||||
score: score,
|
||||
raisonCompatibilite: _genererRaisonCompatibilite(params.demande, proposition, score),
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Trier par score décroissant
|
||||
resultats.sort((a, b) => b.score.compareTo(a.score));
|
||||
|
||||
// 4. Limiter le nombre de résultats
|
||||
final resultatsLimites = resultats.take(params.limiteResultats).toList();
|
||||
|
||||
return Right(resultatsLimites);
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
return Left(UnexpectedFailure('Erreur lors du matching intelligent: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
String _genererRaisonCompatibilite(DemandeAide demande, PropositionAide proposition, double score) {
|
||||
final raisons = <String>[];
|
||||
|
||||
// Type d'aide
|
||||
if (demande.typeAide == proposition.typeAide) {
|
||||
raisons.add('Type d\'aide identique');
|
||||
}
|
||||
|
||||
// Compatibilité financière
|
||||
if (demande.montantDemande != null && proposition.montantMaximum != null) {
|
||||
if (demande.montantDemande! <= proposition.montantMaximum!) {
|
||||
raisons.add('Montant compatible');
|
||||
}
|
||||
}
|
||||
|
||||
// Expérience
|
||||
if (proposition.nombreBeneficiairesAides > 5) {
|
||||
raisons.add('Proposant expérimenté');
|
||||
}
|
||||
|
||||
// Réputation
|
||||
if (proposition.noteMoyenne != null && proposition.noteMoyenne! >= 4.0) {
|
||||
raisons.add('Excellente réputation');
|
||||
}
|
||||
|
||||
// Disponibilité
|
||||
if (proposition.peutAccepterBeneficiaires) {
|
||||
raisons.add('Places disponibles');
|
||||
}
|
||||
|
||||
return raisons.isEmpty ? 'Compatible' : raisons.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
class EffectuerMatchingIntelligentParams {
|
||||
final DemandeAide demande;
|
||||
final double scoreMinimum;
|
||||
final int limiteResultats;
|
||||
|
||||
EffectuerMatchingIntelligentParams({
|
||||
required this.demande,
|
||||
this.scoreMinimum = 30.0,
|
||||
this.limiteResultats = 10,
|
||||
});
|
||||
}
|
||||
|
||||
/// Classe représentant un résultat de matching
|
||||
class ResultatMatching {
|
||||
final PropositionAide proposition;
|
||||
final double score;
|
||||
final String raisonCompatibilite;
|
||||
|
||||
const ResultatMatching({
|
||||
required this.proposition,
|
||||
required this.score,
|
||||
required this.raisonCompatibilite,
|
||||
});
|
||||
}
|
||||
|
||||
/// Cas d'usage pour analyser les tendances de matching
|
||||
class AnalyserTendancesMatchingUseCase implements UseCase<AnalyseTendances, AnalyserTendancesMatchingParams> {
|
||||
AnalyserTendancesMatchingUseCase();
|
||||
|
||||
@override
|
||||
Future<Either<Failure, AnalyseTendances>> call(AnalyserTendancesMatchingParams params) async {
|
||||
try {
|
||||
// Simulation d'analyse des tendances
|
||||
// Dans une vraie implémentation, on analyserait les données historiques
|
||||
|
||||
final analyse = AnalyseTendances(
|
||||
tauxMatchingMoyen: 78.5,
|
||||
tempsMatchingMoyen: const Duration(hours: 6),
|
||||
typesAidePlusDemandesMap: {
|
||||
TypeAide.aideFinanciereUrgente: 45,
|
||||
TypeAide.aideFinanciereMedicale: 32,
|
||||
TypeAide.aideMaterielleNourriture: 28,
|
||||
},
|
||||
typesAidePlusProposesMap: {
|
||||
TypeAide.aideFinanciereEducation: 38,
|
||||
TypeAide.aideProfessionnelleFormation: 25,
|
||||
TypeAide.aideSocialeAccompagnement: 22,
|
||||
},
|
||||
heuresOptimalesMatching: ['09:00', '14:00', '18:00'],
|
||||
recommandations: [
|
||||
'Augmenter les propositions d\'aide financière urgente',
|
||||
'Promouvoir les aides matérielles auprès des proposants',
|
||||
'Optimiser les notifications entre 9h et 18h',
|
||||
],
|
||||
);
|
||||
|
||||
return Right(analyse);
|
||||
} catch (e) {
|
||||
return Left(UnexpectedFailure('Erreur lors de l\'analyse des tendances: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnalyserTendancesMatchingParams {
|
||||
final String organisationId;
|
||||
final DateTime? dateDebut;
|
||||
final DateTime? dateFin;
|
||||
|
||||
AnalyserTendancesMatchingParams({
|
||||
required this.organisationId,
|
||||
this.dateDebut,
|
||||
this.dateFin,
|
||||
});
|
||||
}
|
||||
|
||||
/// Classe représentant une analyse des tendances de matching
|
||||
class AnalyseTendances {
|
||||
final double tauxMatchingMoyen;
|
||||
final Duration tempsMatchingMoyen;
|
||||
final Map<TypeAide, int> typesAidePlusDemandesMap;
|
||||
final Map<TypeAide, int> typesAidePlusProposesMap;
|
||||
final List<String> heuresOptimalesMatching;
|
||||
final List<String> recommandations;
|
||||
|
||||
const AnalyseTendances({
|
||||
required this.tauxMatchingMoyen,
|
||||
required this.tempsMatchingMoyen,
|
||||
required this.typesAidePlusDemandesMap,
|
||||
required this.typesAidePlusProposesMap,
|
||||
required this.heuresOptimalesMatching,
|
||||
required this.recommandations,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user