Configure Maven repository for unionflow-server-api dependency
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.DemandeAideDTO;
|
||||
import dev.lions.unionflow.server.api.dto.solidarite.PropositionAideDTO;
|
||||
import dev.lions.unionflow.server.api.enums.solidarite.TypeAide;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Service intelligent de matching entre demandes et propositions d'aide
|
||||
*
|
||||
* <p>Ce service utilise des algorithmes avancés pour faire correspondre les demandes d'aide avec
|
||||
* les propositions les plus appropriées.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2025-01-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class MatchingService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MatchingService.class);
|
||||
|
||||
@Inject PropositionAideService propositionAideService;
|
||||
|
||||
@Inject DemandeAideService demandeAideService;
|
||||
|
||||
@ConfigProperty(name = "unionflow.matching.score-minimum", defaultValue = "30.0")
|
||||
double scoreMinimumMatching;
|
||||
|
||||
@ConfigProperty(name = "unionflow.matching.max-resultats", defaultValue = "10")
|
||||
int maxResultatsMatching;
|
||||
|
||||
@ConfigProperty(name = "unionflow.matching.boost-geographique", defaultValue = "10.0")
|
||||
double boostGeographique;
|
||||
|
||||
@ConfigProperty(name = "unionflow.matching.boost-experience", defaultValue = "5.0")
|
||||
double boostExperience;
|
||||
|
||||
// === MATCHING DEMANDES -> PROPOSITIONS ===
|
||||
|
||||
/**
|
||||
* Trouve les propositions compatibles avec une demande d'aide
|
||||
*
|
||||
* @param demande La demande d'aide
|
||||
* @return Liste des propositions compatibles triées par score
|
||||
*/
|
||||
public List<PropositionAideDTO> trouverPropositionsCompatibles(DemandeAideDTO demande) {
|
||||
LOG.infof("Recherche de propositions compatibles pour la demande: %s", demande.getId());
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// 1. Recherche de base par type d'aide
|
||||
List<PropositionAideDTO> candidats =
|
||||
propositionAideService.obtenirPropositionsActives(demande.getTypeAide());
|
||||
|
||||
// 2. Si pas assez de candidats, élargir à la catégorie
|
||||
if (candidats.size() < 3) {
|
||||
candidats.addAll(rechercherParCategorie(demande.getTypeAide().getCategorie()));
|
||||
}
|
||||
|
||||
// 3. Filtrage et scoring
|
||||
List<ResultatMatching> resultats =
|
||||
candidats.stream()
|
||||
.filter(PropositionAideDTO::isActiveEtDisponible)
|
||||
.filter(p -> p.peutAccepterBeneficiaires())
|
||||
.map(
|
||||
proposition -> {
|
||||
double score = calculerScoreCompatibilite(demande, proposition);
|
||||
return new ResultatMatching(proposition, score);
|
||||
})
|
||||
.filter(resultat -> resultat.score >= scoreMinimumMatching)
|
||||
.sorted((r1, r2) -> Double.compare(r2.score, r1.score))
|
||||
.limit(maxResultatsMatching)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 4. Extraction des propositions
|
||||
List<PropositionAideDTO> propositionsCompatibles =
|
||||
resultats.stream()
|
||||
.map(
|
||||
resultat -> {
|
||||
// Stocker le score dans les données personnalisées
|
||||
if (resultat.proposition.getDonneesPersonnalisees() == null) {
|
||||
resultat.proposition.setDonneesPersonnalisees(new HashMap<>());
|
||||
}
|
||||
resultat
|
||||
.proposition
|
||||
.getDonneesPersonnalisees()
|
||||
.put("scoreMatching", resultat.score);
|
||||
return resultat.proposition;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
LOG.infof(
|
||||
"Matching terminé en %d ms. Trouvé %d propositions compatibles",
|
||||
duration, propositionsCompatibles.size());
|
||||
|
||||
return propositionsCompatibles;
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du matching pour la demande: %s", demande.getId());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve les demandes compatibles avec une proposition d'aide
|
||||
*
|
||||
* @param proposition La proposition d'aide
|
||||
* @return Liste des demandes compatibles triées par score
|
||||
*/
|
||||
public List<DemandeAideDTO> trouverDemandesCompatibles(PropositionAideDTO proposition) {
|
||||
LOG.infof("Recherche de demandes compatibles pour la proposition: %s", proposition.getId());
|
||||
|
||||
try {
|
||||
// Recherche des demandes actives du même type
|
||||
Map<String, Object> filtres =
|
||||
Map.of(
|
||||
"typeAide", proposition.getTypeAide(),
|
||||
"statut",
|
||||
List.of(
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutAide.SOUMISE,
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutAide.EN_ATTENTE,
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutAide
|
||||
.EN_COURS_EVALUATION,
|
||||
dev.lions.unionflow.server.api.enums.solidarite.StatutAide.APPROUVEE));
|
||||
|
||||
List<DemandeAideDTO> candidats = demandeAideService.rechercherAvecFiltres(filtres);
|
||||
|
||||
// Scoring et tri
|
||||
return candidats.stream()
|
||||
.map(
|
||||
demande -> {
|
||||
double score = calculerScoreCompatibilite(demande, proposition);
|
||||
// Stocker le score temporairement
|
||||
if (demande.getDonneesPersonnalisees() == null) {
|
||||
demande.setDonneesPersonnalisees(new HashMap<>());
|
||||
}
|
||||
demande.getDonneesPersonnalisees().put("scoreMatching", score);
|
||||
return demande;
|
||||
})
|
||||
.filter(
|
||||
demande ->
|
||||
(Double) demande.getDonneesPersonnalisees().get("scoreMatching")
|
||||
>= scoreMinimumMatching)
|
||||
.sorted(
|
||||
(d1, d2) -> {
|
||||
Double score1 = (Double) d1.getDonneesPersonnalisees().get("scoreMatching");
|
||||
Double score2 = (Double) d2.getDonneesPersonnalisees().get("scoreMatching");
|
||||
return Double.compare(score2, score1);
|
||||
})
|
||||
.limit(maxResultatsMatching)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors du matching pour la proposition: %s", proposition.getId());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
// === MATCHING SPÉCIALISÉ ===
|
||||
|
||||
/**
|
||||
* Recherche spécialisée de proposants financiers pour une demande approuvée
|
||||
*
|
||||
* @param demande La demande d'aide financière approuvée
|
||||
* @return Liste des proposants financiers compatibles
|
||||
*/
|
||||
public List<PropositionAideDTO> rechercherProposantsFinanciers(DemandeAideDTO demande) {
|
||||
LOG.infof("Recherche de proposants financiers pour la demande: %s", demande.getId());
|
||||
|
||||
if (!demande.getTypeAide().isFinancier()) {
|
||||
LOG.warnf("La demande %s n'est pas de type financier", demande.getId());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// Filtres spécifiques pour les aides financières
|
||||
Map<String, Object> filtres =
|
||||
Map.of(
|
||||
"typeAide",
|
||||
demande.getTypeAide(),
|
||||
"estDisponible",
|
||||
true,
|
||||
"montantMaximum",
|
||||
demande.getMontantApprouve() != null
|
||||
? demande.getMontantApprouve()
|
||||
: demande.getMontantDemande());
|
||||
|
||||
List<PropositionAideDTO> propositions = propositionAideService.rechercherAvecFiltres(filtres);
|
||||
|
||||
// Scoring spécialisé pour les aides financières
|
||||
return propositions.stream()
|
||||
.map(
|
||||
proposition -> {
|
||||
double score = calculerScoreFinancier(demande, proposition);
|
||||
if (proposition.getDonneesPersonnalisees() == null) {
|
||||
proposition.setDonneesPersonnalisees(new HashMap<>());
|
||||
}
|
||||
proposition.getDonneesPersonnalisees().put("scoreFinancier", score);
|
||||
return proposition;
|
||||
})
|
||||
.filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreFinancier") >= 40.0)
|
||||
.sorted(
|
||||
(p1, p2) -> {
|
||||
Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreFinancier");
|
||||
Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreFinancier");
|
||||
return Double.compare(score2, score1);
|
||||
})
|
||||
.limit(5) // Limiter à 5 pour les aides financières
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Matching d'urgence pour les demandes critiques
|
||||
*
|
||||
* @param demande La demande d'aide urgente
|
||||
* @return Liste des propositions d'urgence
|
||||
*/
|
||||
public List<PropositionAideDTO> matchingUrgence(DemandeAideDTO demande) {
|
||||
LOG.infof("Matching d'urgence pour la demande: %s", demande.getId());
|
||||
|
||||
// Recherche élargie pour les urgences
|
||||
List<PropositionAideDTO> candidats = new ArrayList<>();
|
||||
|
||||
// 1. Même type d'aide
|
||||
candidats.addAll(propositionAideService.obtenirPropositionsActives(demande.getTypeAide()));
|
||||
|
||||
// 2. Types d'aide de la même catégorie
|
||||
candidats.addAll(rechercherParCategorie(demande.getTypeAide().getCategorie()));
|
||||
|
||||
// 3. Propositions généralistes (type AUTRE)
|
||||
candidats.addAll(propositionAideService.obtenirPropositionsActives(TypeAide.AUTRE));
|
||||
|
||||
// Scoring avec bonus d'urgence
|
||||
return candidats.stream()
|
||||
.distinct()
|
||||
.filter(PropositionAideDTO::isActiveEtDisponible)
|
||||
.map(
|
||||
proposition -> {
|
||||
double score = calculerScoreCompatibilite(demande, proposition);
|
||||
// Bonus d'urgence
|
||||
score += 20.0;
|
||||
|
||||
if (proposition.getDonneesPersonnalisees() == null) {
|
||||
proposition.setDonneesPersonnalisees(new HashMap<>());
|
||||
}
|
||||
proposition.getDonneesPersonnalisees().put("scoreUrgence", score);
|
||||
return proposition;
|
||||
})
|
||||
.filter(p -> (Double) p.getDonneesPersonnalisees().get("scoreUrgence") >= 25.0)
|
||||
.sorted(
|
||||
(p1, p2) -> {
|
||||
Double score1 = (Double) p1.getDonneesPersonnalisees().get("scoreUrgence");
|
||||
Double score2 = (Double) p2.getDonneesPersonnalisees().get("scoreUrgence");
|
||||
return Double.compare(score2, score1);
|
||||
})
|
||||
.limit(15) // Plus de résultats pour les urgences
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// === ALGORITHMES DE SCORING ===
|
||||
|
||||
/** Calcule le score de compatibilité entre une demande et une proposition */
|
||||
private double calculerScoreCompatibilite(
|
||||
DemandeAideDTO demande, PropositionAideDTO proposition) {
|
||||
double score = 0.0;
|
||||
|
||||
// 1. Correspondance du type d'aide (40 points max)
|
||||
if (demande.getTypeAide() == proposition.getTypeAide()) {
|
||||
score += 40.0;
|
||||
} else if (demande
|
||||
.getTypeAide()
|
||||
.getCategorie()
|
||||
.equals(proposition.getTypeAide().getCategorie())) {
|
||||
score += 25.0;
|
||||
} else if (proposition.getTypeAide() == TypeAide.AUTRE) {
|
||||
score += 15.0;
|
||||
}
|
||||
|
||||
// 2. Compatibilité financière (25 points max)
|
||||
if (demande.getTypeAide().isNecessiteMontant() && proposition.getMontantMaximum() != null) {
|
||||
BigDecimal montantDemande =
|
||||
demande.getMontantApprouve() != null
|
||||
? demande.getMontantApprouve()
|
||||
: demande.getMontantDemande();
|
||||
|
||||
if (montantDemande != null) {
|
||||
if (montantDemande.compareTo(proposition.getMontantMaximum()) <= 0) {
|
||||
score += 25.0;
|
||||
} else {
|
||||
// Pénalité proportionnelle au dépassement
|
||||
double ratio = proposition.getMontantMaximum().divide(montantDemande, 4, java.math.RoundingMode.HALF_UP).doubleValue();
|
||||
score += 25.0 * ratio;
|
||||
}
|
||||
}
|
||||
} else if (!demande.getTypeAide().isNecessiteMontant()) {
|
||||
score += 25.0; // Pas de contrainte financière
|
||||
}
|
||||
|
||||
// 3. Expérience du proposant (15 points max)
|
||||
if (proposition.getNombreBeneficiairesAides() > 0) {
|
||||
score += Math.min(15.0, proposition.getNombreBeneficiairesAides() * boostExperience);
|
||||
}
|
||||
|
||||
// 4. Réputation (10 points max)
|
||||
if (proposition.getNoteMoyenne() != null && proposition.getNombreEvaluations() >= 3) {
|
||||
score += (proposition.getNoteMoyenne() - 3.0) * 3.33; // 0 à 10 points
|
||||
}
|
||||
|
||||
// 5. Disponibilité et capacité (10 points max)
|
||||
if (proposition.peutAccepterBeneficiaires()) {
|
||||
double ratioCapacite =
|
||||
(double) proposition.getPlacesRestantes() / proposition.getNombreMaxBeneficiaires();
|
||||
score += 10.0 * ratioCapacite;
|
||||
}
|
||||
|
||||
// Bonus et malus additionnels
|
||||
score += calculerBonusGeographique(demande, proposition);
|
||||
score += calculerBonusTemporel(demande, proposition);
|
||||
score -= calculerMalusDelai(demande, proposition);
|
||||
|
||||
return Math.max(0.0, Math.min(100.0, score));
|
||||
}
|
||||
|
||||
/** Calcule le score spécialisé pour les aides financières */
|
||||
private double calculerScoreFinancier(DemandeAideDTO demande, PropositionAideDTO proposition) {
|
||||
double score = calculerScoreCompatibilite(demande, proposition);
|
||||
|
||||
// Bonus spécifiques aux aides financières
|
||||
|
||||
// 1. Historique de versements
|
||||
if (proposition.getMontantTotalVerse() > 0) {
|
||||
score += Math.min(10.0, proposition.getMontantTotalVerse() / 10000.0);
|
||||
}
|
||||
|
||||
// 2. Fiabilité (ratio versements/promesses)
|
||||
if (proposition.getNombreDemandesTraitees() > 0) {
|
||||
// Simulation d'un ratio de fiabilité
|
||||
double ratioFiabilite = 0.9; // À calculer réellement
|
||||
score += ratioFiabilite * 15.0;
|
||||
}
|
||||
|
||||
// 3. Rapidité de réponse
|
||||
if (proposition.getDelaiReponseHeures() <= 24) {
|
||||
score += 10.0;
|
||||
} else if (proposition.getDelaiReponseHeures() <= 72) {
|
||||
score += 5.0;
|
||||
}
|
||||
|
||||
return Math.max(0.0, Math.min(100.0, score));
|
||||
}
|
||||
|
||||
/** Calcule le bonus géographique */
|
||||
private double calculerBonusGeographique(DemandeAideDTO demande, PropositionAideDTO proposition) {
|
||||
// Simulation - dans une vraie implémentation, ceci utiliserait les données de localisation
|
||||
if (demande.getLocalisation() != null && proposition.getZonesGeographiques() != null) {
|
||||
// Logique de proximité géographique
|
||||
return boostGeographique;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/** Calcule le bonus temporel (urgence, disponibilité) */
|
||||
private double calculerBonusTemporel(DemandeAideDTO demande, PropositionAideDTO proposition) {
|
||||
double bonus = 0.0;
|
||||
|
||||
// Bonus pour demande urgente
|
||||
if (demande.estUrgente()) {
|
||||
bonus += 5.0;
|
||||
}
|
||||
|
||||
// Bonus pour proposition récente
|
||||
long joursDepuisCreation =
|
||||
java.time.Duration.between(proposition.getDateCreation(), LocalDateTime.now()).toDays();
|
||||
if (joursDepuisCreation <= 30) {
|
||||
bonus += 3.0;
|
||||
}
|
||||
|
||||
return bonus;
|
||||
}
|
||||
|
||||
/** Calcule le malus de délai */
|
||||
private double calculerMalusDelai(DemandeAideDTO demande, PropositionAideDTO proposition) {
|
||||
double malus = 0.0;
|
||||
|
||||
// Malus si la demande est en retard
|
||||
if (demande.estDelaiDepasse()) {
|
||||
malus += 5.0;
|
||||
}
|
||||
|
||||
// Malus si la proposition a un délai de réponse long
|
||||
if (proposition.getDelaiReponseHeures() > 168) { // Plus d'une semaine
|
||||
malus += 3.0;
|
||||
}
|
||||
|
||||
return malus;
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
/** Recherche des propositions par catégorie */
|
||||
private List<PropositionAideDTO> rechercherParCategorie(String categorie) {
|
||||
Map<String, Object> filtres = Map.of("estDisponible", true);
|
||||
|
||||
return propositionAideService.rechercherAvecFiltres(filtres).stream()
|
||||
.filter(p -> p.getTypeAide().getCategorie().equals(categorie))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Classe interne pour stocker les résultats de matching */
|
||||
private static class ResultatMatching {
|
||||
final PropositionAideDTO proposition;
|
||||
final double score;
|
||||
|
||||
ResultatMatching(PropositionAideDTO proposition, double score) {
|
||||
this.proposition = proposition;
|
||||
this.score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user