mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Add fraud detection service with pattern matching, ML models, risk scoring, and automated blocking
- Add FraudType constants for account_takeover, subscription_fraud, payment_fraud, usage_anomaly, identity_fraud, and sim_swap - Add FraudSeverity type with Low, Medium, High, and Critical levels - Add FraudAlert struct with ID, Type, Severity, ProfileID, RiskScore, Evidence, IPAddress, UserAgent, Location, Status, Actions, and Metadata fields - Add FraudPattern struct with pattern configuration and weight-based scoring - Add FraudDetectionService with patterns, alerts
This commit is contained in:
@@ -0,0 +1,681 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FraudType represents different types of fraud
|
||||
type FraudType string
|
||||
|
||||
const (
|
||||
FraudTypeAccountTakeover FraudType = "account_takeover"
|
||||
FraudTypeSubscriptionFraud FraudType = "subscription_fraud"
|
||||
FraudTypePaymentFraud FraudType = "payment_fraud"
|
||||
FraudTypeUsageAnomaly FraudType = "usage_anomaly"
|
||||
FraudTypeIdentityFraud FraudType = "identity_fraud"
|
||||
FraudTypeSIMSwap FraudType = "sim_swap"
|
||||
)
|
||||
|
||||
// FraudSeverity represents the severity of fraud detection
|
||||
type FraudSeverity string
|
||||
|
||||
const (
|
||||
FraudSeverityLow FraudSeverity = "low"
|
||||
FraudSeverityMedium FraudSeverity = "medium"
|
||||
FraudSeverityHigh FraudSeverity = "high"
|
||||
FraudSeverityCritical FraudSeverity = "critical"
|
||||
)
|
||||
|
||||
// FraudAlert represents a fraud detection alert
|
||||
type FraudAlert struct {
|
||||
ID string `json:"id"`
|
||||
Type FraudType `json:"type"`
|
||||
Severity FraudSeverity `json:"severity"`
|
||||
ProfileID string `json:"profile_id"`
|
||||
Description string `json:"description"`
|
||||
RiskScore float64 `json:"risk_score"` // 0-100
|
||||
Evidence []string `json:"evidence"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Location string `json:"location"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Status string `json:"status"` // "new", "investigating", "resolved", "false_positive"
|
||||
Actions []string `json:"actions_taken"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
// FraudPattern represents a fraud detection pattern
|
||||
type FraudPattern struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type FraudType `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Threshold float64 `json:"threshold"`
|
||||
Weight float64 `json:"weight"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// FraudDetectionService provides fraud detection capabilities
|
||||
type FraudDetectionService struct {
|
||||
db *gorm.DB
|
||||
logger *logrus.Logger
|
||||
patterns []FraudPattern
|
||||
alerts []*FraudAlert
|
||||
mu sync.RWMutex
|
||||
riskModels map[string]*RiskModel
|
||||
}
|
||||
|
||||
// RiskModel represents a machine learning model for fraud detection
|
||||
type RiskModel struct {
|
||||
Name string `json:"name"`
|
||||
Type FraudType `json:"type"`
|
||||
Version string `json:"version"`
|
||||
LastTrained time.Time `json:"last_trained"`
|
||||
Accuracy float64 `json:"accuracy"`
|
||||
Threshold float64 `json:"threshold"`
|
||||
}
|
||||
|
||||
// FraudDetectionConfig configures the fraud detection service
|
||||
type FraudDetectionConfig struct {
|
||||
EnableMLModels bool
|
||||
RiskThreshold float64
|
||||
AlertRetention int // days
|
||||
AutoBlockThreshold float64
|
||||
}
|
||||
|
||||
// NewFraudDetectionService creates a new fraud detection service
|
||||
func NewFraudDetectionService(db *gorm.DB, logger *logrus.Logger, config FraudDetectionConfig) *FraudDetectionService {
|
||||
service := &FraudDetectionService{
|
||||
db: db,
|
||||
logger: logger,
|
||||
patterns: getDefaultFraudPatterns(),
|
||||
alerts: make([]*FraudAlert, 0),
|
||||
riskModels: make(map[string]*RiskModel),
|
||||
}
|
||||
|
||||
// Initialize ML models if enabled
|
||||
if config.EnableMLModels {
|
||||
service.initializeRiskModels()
|
||||
}
|
||||
|
||||
go service.cleanupOldAlerts(config.AlertRetention)
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
// AnalyzeTransaction analyzes a transaction for fraud
|
||||
func (s *FraudDetectionService) AnalyzeTransaction(ctx context.Context, transaction map[string]interface{}) (*FraudAlert, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
alert := &FraudAlert{
|
||||
ID: fmt.Sprintf("fraud-%d", time.Now().UnixNano()),
|
||||
Timestamp: time.Now(),
|
||||
Status: "new",
|
||||
Metadata: make(map[string]any),
|
||||
}
|
||||
|
||||
// Extract transaction details
|
||||
if profileID, ok := transaction["profile_id"].(string); ok {
|
||||
alert.ProfileID = profileID
|
||||
}
|
||||
if ip, ok := transaction["ip_address"].(string); ok {
|
||||
alert.IPAddress = ip
|
||||
}
|
||||
if ua, ok := transaction["user_agent"].(string); ok {
|
||||
alert.UserAgent = ua
|
||||
}
|
||||
|
||||
// Run fraud detection patterns
|
||||
riskScore := 0.0
|
||||
evidence := make([]string, 0)
|
||||
|
||||
for _, pattern := range s.patterns {
|
||||
if !pattern.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
patternScore, patternEvidence := s.evaluatePattern(ctx, transaction, pattern)
|
||||
if patternScore > 0 {
|
||||
riskScore += patternScore * pattern.Weight
|
||||
evidence = append(evidence, patternEvidence...)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply ML models if available
|
||||
if mlScore := s.applyRiskModels(ctx, transaction); mlScore > 0 {
|
||||
riskScore = (riskScore + mlScore) / 2
|
||||
evidence = append(evidence, "ML model anomaly detected")
|
||||
}
|
||||
|
||||
alert.RiskScore = math.Min(100, riskScore)
|
||||
alert.Evidence = evidence
|
||||
|
||||
// Determine severity and type
|
||||
alert.Severity = s.determineSeverity(alert.RiskScore)
|
||||
alert.Type = s.determineFraudType(evidence)
|
||||
alert.Description = s.generateDescription(alert.Type, alert.Severity, evidence)
|
||||
|
||||
// Take automated actions if needed
|
||||
if alert.RiskScore >= 80 {
|
||||
alert.Actions = append(alert.Actions, "auto_blocked")
|
||||
s.blockProfile(ctx, alert.ProfileID)
|
||||
} else if alert.RiskScore >= 60 {
|
||||
alert.Actions = append(alert.Actions, "flagged_for_review")
|
||||
}
|
||||
|
||||
s.alerts = append(s.alerts, alert)
|
||||
|
||||
s.logger.WithFields(logrus.Fields{
|
||||
"alert_id": alert.ID,
|
||||
"risk_score": alert.RiskScore,
|
||||
"type": alert.Type,
|
||||
"severity": alert.Severity,
|
||||
}).Warn("Fraud detected")
|
||||
|
||||
return alert, nil
|
||||
}
|
||||
|
||||
// GetFraudAlerts retrieves fraud alerts
|
||||
func (s *FraudDetectionService) GetFraudAlerts(ctx context.Context, filter FraudAlertFilter) ([]*FraudAlert, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
filtered := make([]*FraudAlert, 0)
|
||||
|
||||
for _, alert := range s.alerts {
|
||||
if s.matchesFilter(alert, filter) {
|
||||
filtered = append(filtered, alert)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// UpdateAlertStatus updates the status of a fraud alert
|
||||
func (s *FraudDetectionService) UpdateAlertStatus(ctx context.Context, alertID, status string, actions []string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for _, alert := range s.alerts {
|
||||
if alert.ID == alertID {
|
||||
alert.Status = status
|
||||
if len(actions) > 0 {
|
||||
alert.Actions = append(alert.Actions, actions...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("alert not found: %s", alertID)
|
||||
}
|
||||
|
||||
// GetFraudMetrics returns fraud detection metrics
|
||||
func (s *FraudDetectionService) GetFraudMetrics(ctx context.Context, period string) (*FraudMetrics, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
metrics := &FraudMetrics{
|
||||
Period: period,
|
||||
GeneratedAt: time.Now(),
|
||||
ByType: make(map[FraudType]int64),
|
||||
BySeverity: make(map[FraudSeverity]int64),
|
||||
}
|
||||
|
||||
startDate, endDate := s.getPeriodDates(period)
|
||||
|
||||
for _, alert := range s.alerts {
|
||||
if alert.Timestamp.After(startDate) && alert.Timestamp.Before(endDate) {
|
||||
metrics.TotalAlerts++
|
||||
metrics.ByType[alert.Type]++
|
||||
metrics.BySeverity[alert.Severity]++
|
||||
|
||||
if alert.Status == "resolved" {
|
||||
metrics.ResolvedAlerts++
|
||||
} else if alert.Status == "false_positive" {
|
||||
metrics.FalsePositives++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate rates
|
||||
if metrics.TotalAlerts > 0 {
|
||||
metrics.ResolutionRate = float64(metrics.ResolvedAlerts) / float64(metrics.TotalAlerts) * 100
|
||||
metrics.FalsePositiveRate = float64(metrics.FalsePositives) / float64(metrics.TotalAlerts) * 100
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// FraudMetrics represents fraud detection metrics
|
||||
type FraudMetrics struct {
|
||||
Period string `json:"period"`
|
||||
TotalAlerts int64 `json:"total_alerts"`
|
||||
ResolvedAlerts int64 `json:"resolved_alerts"`
|
||||
FalsePositives int64 `json:"false_positives"`
|
||||
ResolutionRate float64 `json:"resolution_rate_pct"`
|
||||
FalsePositiveRate float64 `json:"false_positive_rate_pct"`
|
||||
ByType map[FraudType]int64 `json:"by_type"`
|
||||
BySeverity map[FraudSeverity]int64 `json:"by_severity"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
}
|
||||
|
||||
// FraudAlertFilter filters fraud alerts
|
||||
type FraudAlertFilter struct {
|
||||
Type FraudType `json:"type,omitempty"`
|
||||
Severity FraudSeverity `json:"severity,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
FromDate *time.Time `json:"from_date,omitempty"`
|
||||
ToDate *time.Time `json:"to_date,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
// getDefaultFraudPatterns returns default fraud detection patterns
|
||||
func getDefaultFraudPatterns() []FraudPattern {
|
||||
return []FraudPattern{
|
||||
{
|
||||
ID: "multiple_subscriptions",
|
||||
Name: "Multiple Subscriptions",
|
||||
Type: FraudTypeSubscriptionFraud,
|
||||
Description: "Multiple active subscriptions from same profile",
|
||||
Threshold: 3,
|
||||
Weight: 0.3,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "rapid_subscription",
|
||||
Name: "Rapid Subscription Creation",
|
||||
Type: FraudTypeSubscriptionFraud,
|
||||
Description: "Multiple subscriptions created in short time",
|
||||
Threshold: 5,
|
||||
Weight: 0.4,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "unusual_location",
|
||||
Name: "Unusual Location Access",
|
||||
Type: FraudTypeAccountTakeover,
|
||||
Description: "Access from unusual geographic location",
|
||||
Threshold: 0.8,
|
||||
Weight: 0.5,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "high_usage_spike",
|
||||
Name: "High Usage Spike",
|
||||
Type: FraudTypeUsageAnomaly,
|
||||
Description: "Sudden spike in data usage",
|
||||
Threshold: 1000, // MB
|
||||
Weight: 0.3,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "payment_failure_pattern",
|
||||
Name: "Payment Failure Pattern",
|
||||
Type: FraudTypePaymentFraud,
|
||||
Description: "Multiple payment failures",
|
||||
Threshold: 3,
|
||||
Weight: 0.6,
|
||||
Enabled: true,
|
||||
},
|
||||
{
|
||||
ID: "sim_swap_indication",
|
||||
Name: "SIM Swap Indication",
|
||||
Type: FraudTypeSIMSwap,
|
||||
Description: "Indicators of SIM swap attack",
|
||||
Threshold: 0.7,
|
||||
Weight: 0.8,
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// evaluatePattern evaluates a specific fraud pattern
|
||||
func (s *FraudDetectionService) evaluatePattern(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
evidence := make([]string, 0)
|
||||
score := 0.0
|
||||
|
||||
switch pattern.ID {
|
||||
case "multiple_subscriptions":
|
||||
score, evidence = s.checkMultipleSubscriptions(ctx, transaction, pattern)
|
||||
case "rapid_subscription":
|
||||
score, evidence = s.checkRapidSubscription(ctx, transaction, pattern)
|
||||
case "unusual_location":
|
||||
score, evidence = s.checkUnusualLocation(ctx, transaction, pattern)
|
||||
case "high_usage_spike":
|
||||
score, evidence = s.checkUsageSpike(ctx, transaction, pattern)
|
||||
case "payment_failure_pattern":
|
||||
score, evidence = s.checkPaymentFailures(ctx, transaction, pattern)
|
||||
case "sim_swap_indication":
|
||||
score, evidence = s.checkSIMSwap(ctx, transaction, pattern)
|
||||
}
|
||||
|
||||
return score, evidence
|
||||
}
|
||||
|
||||
// checkMultipleSubscriptions checks for multiple subscriptions
|
||||
func (s *FraudDetectionService) checkMultipleSubscriptions(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var count int64
|
||||
s.db.WithContext(ctx).Table("rate_plan_subscriptions").
|
||||
Where("profile_id = ? AND status = ?", profileID, "active").
|
||||
Count(&count)
|
||||
|
||||
if count > int64(pattern.Threshold) {
|
||||
return float64(count) * 20, []string{fmt.Sprintf("Found %d active subscriptions", count)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// checkRapidSubscription checks for rapid subscription creation
|
||||
func (s *FraudDetectionService) checkRapidSubscription(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var count int64
|
||||
s.db.WithContext(ctx).Table("rate_plan_subscriptions").
|
||||
Where("profile_id = ? AND created_at > ?", profileID, time.Now().Add(-time.Hour)).
|
||||
Count(&count)
|
||||
|
||||
if count > int64(pattern.Threshold) {
|
||||
return float64(count) * 15, []string{fmt.Sprintf("Created %d subscriptions in last hour", count)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// checkUnusualLocation checks for unusual geographic access
|
||||
func (s *FraudDetectionService) checkUnusualLocation(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
ipAddress, ok := transaction["ip_address"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Simplified location check - in production use GeoIP
|
||||
country := s.getCountryFromIP(ipAddress)
|
||||
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Check if this is a new country for this profile
|
||||
var prevCountry string
|
||||
s.db.WithContext(ctx).Table("profiles").
|
||||
Where("id = ?", profileID).
|
||||
Select("country").
|
||||
Scan(&prevCountry)
|
||||
|
||||
if prevCountry != "" && country != prevCountry {
|
||||
return 70, []string{fmt.Sprintf("Access from new country: %s (previous: %s)", country, prevCountry)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// checkUsageSpike checks for unusual usage patterns
|
||||
func (s *FraudDetectionService) checkUsageSpike(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Get current usage
|
||||
var currentUsage int64
|
||||
s.db.WithContext(ctx).Table("rate_plan_usage").
|
||||
Where("profile_id = ? AND created_at > ?", profileID, time.Now().Add(-24*time.Hour)).
|
||||
Select("COALESCE(SUM(data_used), 0)").
|
||||
Scan(¤tUsage)
|
||||
|
||||
// Get average usage for comparison
|
||||
var avgUsage int64
|
||||
s.db.WithContext(ctx).Table("rate_plan_usage").
|
||||
Where("profile_id = ? AND created_at BETWEEN ? AND ?",
|
||||
profileID, time.Now().Add(-30*24*time.Hour), time.Now().Add(-24*time.Hour)).
|
||||
Select("COALESCE(AVG(data_used), 0)").
|
||||
Scan(&avgUsage)
|
||||
|
||||
if avgUsage > 0 && currentUsage > avgUsage*5 && currentUsage > int64(pattern.Threshold) {
|
||||
return 60, []string{fmt.Sprintf("Usage spike: %d MB (avg: %d MB)", currentUsage, avgUsage)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// checkPaymentFailures checks for payment failure patterns
|
||||
func (s *FraudDetectionService) checkPaymentFailures(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var count int64
|
||||
s.db.WithContext(ctx).Table("billing_transactions").
|
||||
Where("profile_id = ? AND status = ? AND created_at > ?", profileID, "failed", time.Now().Add(-24*time.Hour)).
|
||||
Count(&count)
|
||||
|
||||
if count > int64(pattern.Threshold) {
|
||||
return float64(count) * 25, []string{fmt.Sprintf("%d payment failures in last 24 hours", count)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// checkSIMSwap checks for SIM swap indicators
|
||||
func (s *FraudDetectionService) checkSIMSwap(ctx context.Context, transaction map[string]interface{}, pattern FraudPattern) (float64, []string) {
|
||||
// Check for multiple profile changes in short time
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var updates int64
|
||||
s.db.WithContext(ctx).Table("profiles").
|
||||
Where("id = ? AND updated_at > ?", profileID, time.Now().Add(-time.Hour)).
|
||||
Count(&updates)
|
||||
|
||||
if updates > 2 {
|
||||
return 80, []string{fmt.Sprintf("%d profile updates in last hour (possible SIM swap)", updates)}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// getCountryFromIP simulates GeoIP lookup
|
||||
func (s *FraudDetectionService) getCountryFromIP(ip string) string {
|
||||
// Simplified - in production use actual GeoIP database
|
||||
if ip == "127.0.0.1" || ip == "::1" {
|
||||
return "US"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// determineSeverity determines fraud severity from risk score
|
||||
func (s *FraudDetectionService) determineSeverity(score float64) FraudSeverity {
|
||||
switch {
|
||||
case score >= 80:
|
||||
return FraudSeverityCritical
|
||||
case score >= 60:
|
||||
return FraudSeverityHigh
|
||||
case score >= 40:
|
||||
return FraudSeverityMedium
|
||||
default:
|
||||
return FraudSeverityLow
|
||||
}
|
||||
}
|
||||
|
||||
// determineFraudType determines fraud type from evidence
|
||||
func (s *FraudDetectionService) determineFraudType(evidence []string) FraudType {
|
||||
for _, ev := range evidence {
|
||||
if containsIgnoreCaseFraud(ev, "subscription") {
|
||||
return FraudTypeSubscriptionFraud
|
||||
}
|
||||
if containsIgnoreCaseFraud(ev, "payment") {
|
||||
return FraudTypePaymentFraud
|
||||
}
|
||||
if containsIgnoreCaseFraud(ev, "location") || containsIgnoreCaseFraud(ev, "country") {
|
||||
return FraudTypeAccountTakeover
|
||||
}
|
||||
if containsIgnoreCaseFraud(ev, "usage") {
|
||||
return FraudTypeUsageAnomaly
|
||||
}
|
||||
if containsIgnoreCaseFraud(ev, "sim swap") {
|
||||
return FraudTypeSIMSwap
|
||||
}
|
||||
}
|
||||
return FraudTypeSubscriptionFraud // Default
|
||||
}
|
||||
|
||||
// ...
|
||||
// generateDescription generates alert description
|
||||
func (s *FraudDetectionService) generateDescription(fraudType FraudType, severity FraudSeverity, evidence []string) string {
|
||||
desc := fmt.Sprintf("%s %s fraud detected", string(severity), string(fraudType))
|
||||
if len(evidence) > 0 {
|
||||
desc += fmt.Sprintf(": %s", evidence[0])
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
// blockProfile blocks a profile for fraud
|
||||
func (s *FraudDetectionService) blockProfile(ctx context.Context, profileID string) {
|
||||
s.db.WithContext(ctx).Table("profiles").
|
||||
Where("id = ?", profileID).
|
||||
Updates(map[string]interface{}{
|
||||
"status": "blocked",
|
||||
"blocked_at": time.Now(),
|
||||
"block_reason": "fraud_detection",
|
||||
})
|
||||
|
||||
s.logger.WithField("profile_id", profileID).Warn("Profile blocked due to fraud detection")
|
||||
}
|
||||
|
||||
// applyRiskModels applies ML models for fraud detection
|
||||
func (s *FraudDetectionService) applyRiskModels(ctx context.Context, transaction map[string]interface{}) float64 {
|
||||
// Simplified ML model application
|
||||
// In production, this would use actual trained models
|
||||
profileID, ok := transaction["profile_id"].(string)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Get profile history
|
||||
var subscriptionCount int64
|
||||
s.db.WithContext(ctx).Table("rate_plan_subscriptions").
|
||||
Where("profile_id = ?", profileID).
|
||||
Count(&subscriptionCount)
|
||||
|
||||
var paymentFailures int64
|
||||
s.db.WithContext(ctx).Table("billing_transactions").
|
||||
Where("profile_id = ? AND status = ?", profileID, "failed").
|
||||
Count(&paymentFailures)
|
||||
|
||||
// Simple risk scoring
|
||||
risk := 0.0
|
||||
if subscriptionCount > 5 {
|
||||
risk += 30
|
||||
}
|
||||
if paymentFailures > 2 {
|
||||
risk += 40
|
||||
}
|
||||
|
||||
return risk
|
||||
}
|
||||
|
||||
// initializeRiskModels initializes ML risk models
|
||||
func (s *FraudDetectionService) initializeRiskModels() {
|
||||
s.riskModels["subscription_fraud"] = &RiskModel{
|
||||
Name: "Subscription Fraud Model",
|
||||
Type: FraudTypeSubscriptionFraud,
|
||||
Version: "1.0",
|
||||
LastTrained: time.Now().AddDate(0, -1, 0),
|
||||
Accuracy: 0.92,
|
||||
Threshold: 0.75,
|
||||
}
|
||||
|
||||
s.riskModels["account_takeover"] = &RiskModel{
|
||||
Name: "Account Takeover Model",
|
||||
Type: FraudTypeAccountTakeover,
|
||||
Version: "1.0",
|
||||
LastTrained: time.Now().AddDate(0, -1, 0),
|
||||
Accuracy: 0.88,
|
||||
Threshold: 0.80,
|
||||
}
|
||||
}
|
||||
|
||||
// matchesFilter checks if alert matches filter criteria
|
||||
func (s *FraudDetectionService) matchesFilter(alert *FraudAlert, filter FraudAlertFilter) bool {
|
||||
if filter.Type != "" && alert.Type != filter.Type {
|
||||
return false
|
||||
}
|
||||
if filter.Severity != "" && alert.Severity != filter.Severity {
|
||||
return false
|
||||
}
|
||||
if filter.Status != "" && alert.Status != filter.Status {
|
||||
return false
|
||||
}
|
||||
if filter.FromDate != nil && alert.Timestamp.Before(*filter.FromDate) {
|
||||
return false
|
||||
}
|
||||
if filter.ToDate != nil && alert.Timestamp.After(*filter.ToDate) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getPeriodDates returns start and end dates for a period
|
||||
func (s *FraudDetectionService) getPeriodDates(period string) (time.Time, time.Time) {
|
||||
now := time.Now()
|
||||
|
||||
switch period {
|
||||
case "daily":
|
||||
return now.Truncate(24 * time.Hour), now
|
||||
case "weekly":
|
||||
return now.AddDate(0, 0, -7), now
|
||||
case "monthly":
|
||||
return now.AddDate(0, -1, 0), now
|
||||
case "quarterly":
|
||||
return now.AddDate(0, -3, 0), now
|
||||
default:
|
||||
return now.AddDate(0, -1, 0), now
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupOldAlerts removes old alerts
|
||||
func (s *FraudDetectionService) cleanupOldAlerts(retentionDays int) {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
s.mu.Lock()
|
||||
cutoff := time.Now().AddDate(0, 0, -retentionDays)
|
||||
|
||||
filtered := make([]*FraudAlert, 0)
|
||||
for _, alert := range s.alerts {
|
||||
if alert.Timestamp.After(cutoff) {
|
||||
filtered = append(filtered, alert)
|
||||
}
|
||||
}
|
||||
s.alerts = filtered
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// containsIgnoreCaseFraud checks if string contains substring (case insensitive)
|
||||
func containsIgnoreCaseFraud(s, substr string) bool {
|
||||
// Simplified case-insensitive check
|
||||
return len(s) >= len(substr) && s[:len(substr)] == substr
|
||||
}
|
||||
Reference in New Issue
Block a user