ai-alerts
ISSUE #3 : SYSTÈME D'ALERTES IA
🎯 OBJECTIF
Créer le moteur de détection d'anomalies avec IA pour identifier automatiquement les fraudes fiscales.
📖 CONTEXTE DU PROJET
SmartAudit utilise l'intelligence artificielle pour détecter automatiquement les anomalies fiscales en croisant les données de 5 sources. C'est le cœur du projet qui différencie SmartAudit des systèmes traditionnels.
Types d'Anomalies Détectées
- TVA_DISCREPANCY : Écart entre déclarations TVA et factures FNE
- UNDECLARED_REVENUE : Revenus non déclarés (banques vs déclarations)
- CNPS_INCONSISTENCY : Incohérences cotisations sociales
- BANK_TRANSACTION_MISMATCH : Transactions bancaires suspectes
- CUSTOMS_DECLARATION_ISSUE : Problèmes déclarations douanières
- SUSPICIOUS_PATTERN : Patterns suspects dans les données
- LATE_DECLARATION : Déclarations en retard
- AMOUNT_THRESHOLD_EXCEEDED : Montants dépassant les seuils
Algorithme de Détection
// Exemple : Détection écart TVA
BigDecimal declaredTVA = declarationService.getTVAForPeriod(company, period);
BigDecimal calculatedTVA = invoiceService.calculateTVAFromFNE(company, period);
BigDecimal discrepancy = calculatedTVA.subtract(declaredTVA);
if (discrepancy.compareTo(threshold) > 0) {
createAlert(company, AlertType.TVA_DISCREPANCY, discrepancy);
}
🛠️ TÂCHES À IMPLÉMENTER
1. Services de Détection
// Fichier : service/AnomalyDetectionService.java
@Service
@Slf4j
public class AnomalyDetectionService {
@Autowired
private AlertService alertService;
@Autowired
private DeclarationService declarationService;
@Autowired
private InvoiceService invoiceService;
@Autowired
private TransactionService transactionService;
@Scheduled(cron = "0 0 2 * * ?") // Tous les jours à 2h
public void detectAnomaliesDaily() {
log.info("Début de la détection d'anomalies quotidienne...");
List<Company> companies = companyService.findAllActive();
for (Company company : companies) {
try {
detectTVAAnomalies(company);
detectRevenueAnomalies(company);
detectCNPSAnomalies(company);
detectBankAnomalies(company);
} catch (Exception e) {
log.error("Erreur détection anomalies pour entreprise {}: {}",
company.getIfu(), e.getMessage());
}
}
log.info("Détection d'anomalies terminée. {} alertes créées.",
alertService.countNewAlerts());
}
private void detectTVAAnomalies(Company company) {
// 1. Récupérer déclarations TVA des 12 derniers mois
List<Declaration> tvaDeclarations = declarationService
.findByCompanyAndType(company, DeclarationType.TVA);
// 2. Récupérer factures FNE correspondantes
List<Invoice> invoices = invoiceService
.findByCompanyAndStatus(company, InvoiceStatus.VALID);
// 3. Calculer TVA théorique depuis FNE
BigDecimal calculatedTVA = invoices.stream()
.map(Invoice::getTvaAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 4. Calculer TVA déclarée
BigDecimal declaredTVA = tvaDeclarations.stream()
.map(Declaration::getDeclaredTVA)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 5. Détecter écart significatif
BigDecimal discrepancy = calculatedTVA.subtract(declaredTVA);
BigDecimal threshold = new BigDecimal("1000000"); // 1M FCFA
if (discrepancy.abs().compareTo(threshold) > 0) {
createTVAAlert(company, discrepancy, calculatedTVA, declaredTVA);
}
}
private void detectRevenueAnomalies(Company company) {
// 1. CA déclaré (e-impots)
BigDecimal declaredRevenue = declarationService
.getTotalRevenueForYear(company, LocalDate.now().getYear());
// 2. Crédits bancaires (Banques)
BigDecimal bankCredits = transactionService
.getTotalCreditsForYear(company, LocalDate.now().getYear());
// 3. Détecter revenus non déclarés
BigDecimal undeclaredAmount = bankCredits.subtract(declaredRevenue);
BigDecimal threshold = new BigDecimal("5000000"); // 5M FCFA
if (undeclaredAmount.compareTo(threshold) > 0) {
createRevenueAlert(company, undeclaredAmount, declaredRevenue, bankCredits);
}
}
private void createTVAAlert(Company company, BigDecimal discrepancy,
BigDecimal calculatedTVA, BigDecimal declaredTVA) {
Alert alert = new Alert();
alert.setCompany(company);
alert.setType(AlertType.TVA_DISCREPANCY);
alert.setPriority(calculatePriority(discrepancy));
alert.setTitle("Écart TVA détecté - " + company.getCompanyName());
alert.setDescription(String.format(
"Écart de %,.0f FCFA entre TVA déclarée (%,.0f) et TVA calculée depuis FNE (%,.0f)",
discrepancy, declaredTVA, calculatedTVA
));
alert.setPotentialAmount(discrepancy.abs());
alert.setConfidenceScore(calculateConfidenceScore(discrepancy, calculatedTVA));
alert.setEvidenceData(createEvidenceJSON(calculatedTVA, declaredTVA, discrepancy));
alertService.save(alert);
log.info("Alerte TVA créée pour {}: écart de {} FCFA",
company.getCompanyName(), discrepancy);
}
}
2. Service de Gestion des Alertes
// Fichier : service/AlertService.java
@Service
@Transactional
public class AlertService {
@Autowired
private AlertRepository alertRepository;
@Autowired
private NotificationService notificationService;
public List<Alert> findNewAlerts() {
return alertRepository.findByStatus(AlertStatus.NEW);
}
public List<Alert> findAlertsByAgent(Agent agent) {
return alertRepository.findByAssignedAgent(agent);
}
public Alert assignAlertToAgent(Long alertId, Agent agent) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new AlertNotFoundException("Alerte non trouvée"));
alert.setAssignedAgent(agent);
alert.setStatus(AlertStatus.ASSIGNED);
alert.setAssignedAt(LocalDateTime.now());
// Notifier l'agent
notificationService.notifyNewAlert(agent, alert);
return alertRepository.save(alert);
}
public Alert resolveAlert(Long alertId, String resolutionNotes) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new AlertNotFoundException("Alerte non trouvée"));
alert.setStatus(AlertStatus.RESOLVED);
alert.setResolvedAt(LocalDateTime.now());
alert.setResolutionNotes(resolutionNotes);
return alertRepository.save(alert);
}
public BigDecimal calculateConfidenceScore(BigDecimal discrepancy, BigDecimal totalAmount) {
// Score de confiance basé sur l'ampleur de l'écart
if (totalAmount.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO;
BigDecimal percentage = discrepancy.abs()
.divide(totalAmount, 4, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"));
// Plus l'écart est important, plus la confiance est élevée
return percentage.min(new BigDecimal("100"));
}
private AlertPriority calculatePriority(BigDecimal amount) {
if (amount.abs().compareTo(new BigDecimal("10000000")) > 0) {
return AlertPriority.URGENT;
} else if (amount.abs().compareTo(new BigDecimal("5000000")) > 0) {
return AlertPriority.HIGH;
} else if (amount.abs().compareTo(new BigDecimal("1000000")) > 0) {
return AlertPriority.MEDIUM;
} else {
return AlertPriority.LOW;
}
}
}
3. Controllers REST
// Fichier : controller/AlertController.java
@RestController
@RequestMapping("/api/alerts")
@PreAuthorize("hasAnyRole('ADMIN', 'AGENT')")
public class AlertController {
@Autowired
private AlertService alertService;
@GetMapping
public ResponseEntity<List<AlertDTO>> getAllAlerts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) AlertStatus status,
@RequestParam(required = false) AlertPriority priority) {
Pageable pageable = PageRequest.of(page, size);
Page<Alert> alerts = alertService.findAlertsWithFilters(status, priority, pageable);
List<AlertDTO> alertDTOs = alerts.getContent().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
return ResponseEntity.ok(alertDTOs);
}
@GetMapping("/{id}")
public ResponseEntity<AlertDetailsDTO> getAlertDetails(@PathVariable Long id) {
Alert alert = alertService.findById(id);
AlertDetailsDTO details = convertToDetailsDTO(alert);
return ResponseEntity.ok(details);
}
@PostMapping("/{id}/assign")
@PreAuthorize("hasAnyRole('ADMIN', 'SUPERVISEUR')")
public ResponseEntity<String> assignAlert(@PathVariable Long id,
@RequestBody AssignAlertDTO dto) {
Agent agent = agentService.findById(dto.getAgentId());
alertService.assignAlertToAgent(id, agent);
return ResponseEntity.ok("Alerte assignée avec succès");
}
@PostMapping("/{id}/resolve")
public ResponseEntity<String> resolveAlert(@PathVariable Long id,
@RequestBody ResolveAlertDTO dto) {
alertService.resolveAlert(id, dto.getResolutionNotes());
return ResponseEntity.ok("Alerte résolue avec succès");
}
@PostMapping("/detect")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<String> triggerDetection() {
anomalyDetectionService.detectAnomaliesDaily();
return ResponseEntity.ok("Détection d'anomalies lancée");
}
}
4. DTOs
// Fichier : dto/AlertDTO.java
public class AlertDTO {
private Long id;
private String companyName;
private String companyIfu;
private AlertType type;
private AlertStatus status;
private AlertPriority priority;
private String title;
private String description;
private BigDecimal potentialAmount;
private BigDecimal confidenceScore;
private LocalDateTime detectedAt;
private String assignedAgentName;
}
// Fichier : dto/AlertDetailsDTO.java
public class AlertDetailsDTO extends AlertDTO {
private String evidenceData;
private String resolutionNotes;
private LocalDateTime assignedAt;
private LocalDateTime resolvedAt;
private List<DeclarationDTO> relatedDeclarations;
private List<InvoiceDTO> relatedInvoices;
private List<TransactionDTO> relatedTransactions;
}
🔧 OUTILS & TECHNOLOGIES
Spring Boot
- @Scheduled : Détection automatique quotidienne
- Spring Data JPA : Requêtes complexes
- @Transactional : Gestion des transactions
- Jackson : Sérialisation JSON
Algorithmes IA
- Détection d'anomalies : Comparaison statistique
- Scoring de confiance : Calcul basé sur l'ampleur
- Seuils adaptatifs : Basés sur le secteur d'activité
- Machine Learning simple : Patterns suspects
🎯 CRITÈRES D'ACCEPTATION
Détection Automatique
-
Job quotidien : Exécution automatique à 2h -
8 types d'anomalies : Tous les types détectés -
Scoring confiance : 0-100% basé sur l'ampleur -
Seuils configurables : Par type d'anomalie -
Performance : < 5 minutes pour 100 entreprises
Gestion des Alertes
-
Assignation automatique : Par district et spécialisation -
Workflow complet : NEW → ASSIGNED → RESOLVED -
Notifications : Agents notifiés des nouvelles alertes -
Historique : Traçabilité complète des actions
API REST
-
Filtres avancés : Par statut, priorité, agent -
Pagination : Pour gros volumes d'alertes -
Détails complets : Données sources incluses -
Actions : Assignation, résolution, détection manuelle
📁 STRUCTURE DES FICHIERS
src/main/java/ci/dgi/smartaudit/
├── service/
│ ├── AnomalyDetectionService.java
│ ├── AlertService.java
│ └── AlertAssignmentService.java
├── controller/
│ └── AlertController.java
├── repository/
│ └── AlertRepository.java
├── dto/
│ ├── AlertDTO.java
│ ├── AlertDetailsDTO.java
│ ├── AssignAlertDTO.java
│ └── ResolveAlertDTO.java
└── config/
└── SchedulingConfig.java
🧪 TESTS À IMPLÉMENTER
Tests Unitaires
@ExtendWith(MockitoExtension.class)
class AnomalyDetectionServiceTest {
@Test
void detectTVAAnomalies_WithSignificantDiscrepancy_ShouldCreateAlert() {
// Test détection écart TVA
}
@Test
void detectRevenueAnomalies_WithUndeclaredRevenue_ShouldCreateAlert() {
// Test détection revenus non déclarés
}
@Test
void calculateConfidenceScore_WithHighDiscrepancy_ShouldReturnHighScore() {
// Test calcul score confiance
}
}
Tests d'Intégration
@SpringBootTest
@AutoConfigureTestDatabase
class AlertWorkflowIntegrationTest {
@Test
void alertWorkflow_FromDetectionToResolution_ShouldWork() {
// Test workflow complet
}
}
🚀 DÉMARRAGE RAPIDE
1. Configuration Scheduling
@Configuration
@EnableScheduling
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("smartaudit-");
scheduler.initialize();
return scheduler;
}
}
2. Configuration Seuils
# application.yml
smartaudit:
detection:
thresholds:
tva-discrepancy: 1000000
revenue-anomaly: 5000000
cnps-inconsistency: 1000000
confidence:
min-score: 70
high-confidence: 90
📊 MÉTRIQUES DE SUCCÈS
- Précision détection : > 85% des alertes justifiées
- Performance : < 5 minutes pour 100 entreprises
- Couverture : 8 types d'anomalies détectés
- Temps réponse : < 200ms pour API alertes
🔗 INTÉGRATIONS
Avec Issue #2
- Données sources : Croisement multi-sources
- Validation : Cohérence des données
Avec Issue #4
- Création audits : Alertes → Audits
- Workflow : Assignation agents
Avec Issue #6
- Notifications : Alertes nouvelles
- Multi-canal : Email, SMS, in-app
Avec Issue #7
- Traçabilité : Enregistrement actions blockchain
⚠️ POINTS D'ATTENTION
- Performance : Optimiser requêtes pour gros volumes
- Faux positifs : Ajuster seuils selon retours terrain
- Ressources : Monitoring CPU/mémoire job quotidien
- Sécurité : Logs sans données sensibles
ESTIMATION : 2.5 jours
DIFFICULTÉ : Élevée
PRIORITÉ : Critique (cœur du projet)