392 lines
13 KiB
Dart
392 lines
13 KiB
Dart
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,
|
|
});
|
|
}
|