40, // 0-40 = Geringes Risiko 'high' => 70, // 41-70 = Hohes Risiko 'critical' => 100, // 71-100 = Kritisches Risiko ]; /** * Gewichtung der verschiedenen Risk-Faktoren */ private const RISK_WEIGHTS = [ 'transaction_score' => 0.40, // 40% Gewichtung 'sanctions' => 0.25, // 25% Gewichtung 'country_risk' => 0.15, // 15% Gewichtung 'pep_adverse' => 0.10, // 10% Gewichtung 'corruption' => 0.10, // 10% Gewichtung ]; /** * Berechne KYC Risk Level basierend auf Transaction Outputs * * @param Collection $outputs Collection von backend_data_pool records für eine Transaction * @return string 'low', 'high', or 'critical' */ public function calculateKycRiskLevel(Collection $outputs): string { // Wenn keine Outputs vorhanden: Default = high (vorsichtig) if ($outputs->isEmpty()) { return 'high'; } // Konvertiere Collection zu Key-Value Array für einfacheren Zugriff $outputsArray = $outputs->pluck('content', 'output_key')->toArray(); $scores = [ 'transaction_score' => $this->calculateTransactionScore($outputsArray), 'sanctions' => $this->calculateSanctionsScore($outputsArray), 'country_risk' => $this->calculateCountryRiskScore($outputsArray), 'pep_adverse' => $this->calculatePepAdverseScore($outputsArray), 'corruption' => $this->calculateCorruptionScore($outputsArray), ]; $weightedScore = $this->calculateWeightedScore($scores); return $this->determineRiskLevel($weightedScore); } /** * Berechne Transaction Score (0-100) */ private function calculateTransactionScore(array $outputs): int { if (! isset($outputs['tranx_score'])) { return 60; // Default: Hohes Risiko wenn keine Daten } $scoreData = json_decode($outputs['tranx_score'], true); // Format: {"score": 75} if (is_array($scoreData) && isset($scoreData['score'])) { return min(100, max(0, (int) $scoreData['score'])); } // Format: {"risk_level": "high"} if (is_array($scoreData) && isset($scoreData['risk_level'])) { return $this->mapRiskLevelToScore($scoreData['risk_level']); } // Falls nur numerischer Wert if (is_numeric($scoreData)) { return min(100, max(0, (int) $scoreData)); } return 60; // Default } /** * Berechne Sanctions Score (0-100) */ private function calculateSanctionsScore(array $outputs): int { $sanctionFlags = [ 'corporate_eusanctions', 'corporate_ofacsanctions', 'corporate_uksanctions', 'sanctions_circumvention', ]; $score = 0; $foundSanctions = 0; foreach ($sanctionFlags as $flag) { if (! isset($outputs[$flag])) { continue; } $data = json_decode($outputs[$flag], true); if ($this->hasSanctionsMatch($data)) { $foundSanctions++; if (is_array($data) && isset($data['severity'])) { $score += $this->mapSeverityToScore($data['severity']); } else { $score += 70; // Default: Hohes Risiko } } } if ($foundSanctions === 0) { return 0; } $avgScore = $score / $foundSanctions; // Mehrere Sanctions → Kritisches Risiko if ($foundSanctions > 1) { return min(100, (int) round($avgScore * 1.3)); } return (int) round($avgScore); } /** * Berechne Country Risk Score (0-100) */ private function calculateCountryRiskScore(array $outputs): int { if (! isset($outputs['country_risk'])) { return 40; // Default: Geringes Risiko } $countryRiskData = json_decode($outputs['country_risk'], true); if (is_array($countryRiskData) && isset($countryRiskData['score'])) { return min(100, max(0, (int) $countryRiskData['score'])); } if (is_array($countryRiskData) && isset($countryRiskData['level'])) { return $this->mapRiskLevelToScore($countryRiskData['level']); } if (is_bool($countryRiskData)) { return $countryRiskData ? 80 : 20; } return 40; } /** * Berechne PEP & Adverse Media Score (0-100) */ private function calculatePepAdverseScore(array $outputs): int { $score = 0; $count = 0; $pepFields = [ 'corporate_pepexposure' => 75, 'corporate_pep' => 80, 'corporate_adverse' => 70, 'corporate_AMLexposure' => 85, ]; foreach ($pepFields as $field => $defaultScore) { if (! isset($outputs[$field])) { continue; } $data = json_decode($outputs[$field], true); if ($this->hasPositiveMatch($data)) { $score += $this->extractScoreFromData($data, $defaultScore); $count++; } } return $count > 0 ? (int) round($score / $count) : 0; } /** * Berechne Corruption Score (0-100) */ private function calculateCorruptionScore(array $outputs): int { $corruptionFields = [ 'corruption_sector', 'corruption_country', 'corruption_relationship', 'corporate_corruptionexposure', ]; $totalScore = 0; $count = 0; foreach ($corruptionFields as $field) { if (! isset($outputs[$field])) { continue; } $data = json_decode($outputs[$field], true); if ($this->hasPositiveMatch($data)) { $totalScore += $this->extractScoreFromData($data, 70); $count++; } } return $count > 0 ? (int) round($totalScore / $count) : 0; } /** * Berechne gewichteten Gesamt-Score */ private function calculateWeightedScore(array $scores): float { $totalScore = 0.0; foreach ($scores as $category => $score) { $weight = self::RISK_WEIGHTS[$category] ?? 0; $totalScore += $score * $weight; } return $totalScore; } /** * Bestimme Risk Level basierend auf Score (3 Levels) */ private function determineRiskLevel(float $score): string { if ($score <= self::RISK_THRESHOLDS['low']) { return 'low'; // Geringes Risiko } if ($score <= self::RISK_THRESHOLDS['high']) { return 'high'; // Hohes Risiko } return 'critical'; // Kritisches Risiko } /** * Hilfsfunktionen */ private function mapRiskLevelToScore(string $level): int { return match (strtolower($level)) { 'low', 'green', 'gering', 'geringes risiko' => 25, 'high', 'yellow', 'orange', 'hoch', 'hohes risiko' => 60, 'critical', 'red', 'kritisch', 'kritisches risiko' => 85, default => 60, }; } private function mapSeverityToScore(string $severity): int { return match (strtolower($severity)) { 'minor', 'low', 'gering' => 40, 'moderate', 'high', 'hoch' => 65, 'critical', 'severe', 'kritisch' => 90, default => 65, }; } private function hasSanctionsMatch($data): bool { if (! is_array($data)) { return false; } return (isset($data['matches']) && ! empty($data['matches'])) || (isset($data['found']) && $data['found'] === true) || (isset($data['sanctioned']) && $data['sanctioned'] === true); } private function hasPositiveMatch($data): bool { if (is_array($data)) { return (isset($data['match']) && $data['match'] === true) || (isset($data['found']) && $data['found'] === true) || (isset($data['matches']) && ! empty($data['matches'])) || (isset($data['exposure']) && $data['exposure'] === true); } if (is_bool($data)) { return $data; } return false; } private function extractScoreFromData($data, int $default): int { if (is_array($data) && isset($data['score'])) { return min(100, max(0, (int) $data['score'])); } if (is_array($data) && isset($data['risk_score'])) { return min(100, max(0, (int) $data['risk_score'])); } return $default; } /** * Berechne aggregierten Risk Level für eine Company basierend auf allen Transaktionen */ public function calculateCompanyRiskLevel(Collection $transactions): string { if ($transactions->isEmpty()) { return 'high'; // Default: Hohes Risiko wenn keine Daten } $riskScores = [ 'low' => 0, 'high' => 0, 'critical' => 0, ]; // Zähle Risk Levels aller Transaktionen foreach ($transactions as $transactionOutputs) { $riskLevel = $this->calculateKycRiskLevel($transactionOutputs); $riskScores[$riskLevel]++; } $totalTransactions = $transactions->count(); // Worst-Case-Prinzip: // 1. Wenn EINE Transaktion kritisches Risiko hat → Company ist kritisch if ($riskScores['critical'] > 0) { return 'critical'; } // 2. Wenn >30% der Transaktionen hohes Risiko haben → Company ist kritisch $highRiskPercentage = $riskScores['high'] / $totalTransactions; if ($highRiskPercentage > 0.3) { return 'critical'; } // 3. Wenn >10% hohes Risiko → Company ist hohes Risiko if ($highRiskPercentage > 0.1) { return 'high'; } // 4. Wenn >80% geringes Risiko → Company ist geringes Risiko if ($riskScores['low'] / $totalTransactions > 0.8) { return 'low'; } // 5. Default: Hohes Risiko (vorsichtig) return 'high'; } }