1172 lines
42 KiB
PHP
1172 lines
42 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
class KycRiskCalculator
|
|
{
|
|
/**
|
|
* KYC Risk Level Schwellenwerte (3 Levels)
|
|
*/
|
|
private const RISK_THRESHOLDS = [
|
|
'low' => 20, // 0-20 = Geringes Risiko (nur saubere EU/OECD Transaktionen ohne Red Flags)
|
|
'high' => 50, // 20-50 = Hohes Risiko (Tax Havens, High-Risk Countries, PEP, Corruption)
|
|
'critical' => 100, // 50-100 = Kritisches Risiko (Sanctions, Multiple Red Flags, Structuring)
|
|
];
|
|
|
|
/**
|
|
* Gewichtung der verschiedenen Risk-Faktoren
|
|
*/
|
|
private const RISK_WEIGHTS = [
|
|
'transaction_score' => 0.15, // 15% Gewichtung (reduziert für mehr Raum bei kritischen Faktoren)
|
|
'transaction_analysis' => 0.15, // 15% Gewichtung (duplicate, structuring, fake purpose)
|
|
'sanctions' => 0.25, // 25% Gewichtung (ERHÖHT - sehr kritisch!)
|
|
'country_risk' => 0.15, // 15% Gewichtung (ERHÖHT - wichtiger Indikator)
|
|
'pep_adverse' => 0.10, // 10% Gewichtung (ERHÖHT - staatliche Verbindungen)
|
|
'corruption' => 0.10, // 10% Gewichtung (ERHÖHT - Korruption ist kritisch)
|
|
'financial_health' => 0.07, // 7% Gewichtung (insolvency, liquidation, rating)
|
|
'legal_issues' => 0.03, // 3% Gewichtung (court cases, tax haven, export violations)
|
|
];
|
|
|
|
/**
|
|
* Berechne KYC Risk Level basierend auf Transaction Outputs (Legacy - verwendet Entity)
|
|
*
|
|
* @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
|
|
{
|
|
return $this->calculateEntityKycRiskLevel($outputs);
|
|
}
|
|
|
|
/**
|
|
* Berechne Entity KYC Risk Level (sendende Partei)
|
|
*
|
|
* @param Collection $outputs Collection von backend_data_pool records
|
|
* @return string 'low', 'high', or 'critical'
|
|
*/
|
|
public function calculateEntityKycRiskLevel(Collection $outputs): string
|
|
{
|
|
$breakdown = $this->calculateEntityKycRiskBreakdown($outputs);
|
|
|
|
return $breakdown['risk_level'];
|
|
}
|
|
|
|
/**
|
|
* Berechne Entity KYC Risk Level mit detailliertem Breakdown
|
|
*
|
|
* @param Collection $outputs Collection von backend_data_pool records
|
|
* @return array Breakdown mit scores und risk_level
|
|
*/
|
|
public function calculateEntityKycRiskBreakdown(Collection $outputs): array
|
|
{
|
|
if ($outputs->isEmpty()) {
|
|
return [
|
|
'risk_level' => 'high',
|
|
'weighted_score' => 70.0,
|
|
'scores' => [],
|
|
];
|
|
}
|
|
|
|
$outputsArray = $outputs->pluck('content', 'output_key')->toArray();
|
|
|
|
$scores = [
|
|
'transaction_score' => $this->calculateTransactionScore($outputsArray),
|
|
'transaction_analysis' => $this->calculateTransactionAnalysisScore($outputsArray),
|
|
'sanctions' => $this->calculateSanctionsScore($outputsArray, 'entity_'),
|
|
'country_risk' => $this->calculateCountryRiskScore($outputsArray, 'entity_'),
|
|
'pep_adverse' => $this->calculatePepAdverseScore($outputsArray, 'entity_'),
|
|
'corruption' => $this->calculateCorruptionScore($outputsArray, 'entity_'),
|
|
'financial_health' => $this->calculateFinancialHealthScore($outputsArray, 'entity_'),
|
|
'legal_issues' => $this->calculateLegalIssuesScore($outputsArray, 'entity_'),
|
|
];
|
|
|
|
$weightedScore = $this->calculateWeightedScore($scores);
|
|
$riskLevel = $this->determineRiskLevel($weightedScore);
|
|
|
|
return [
|
|
'risk_level' => $riskLevel,
|
|
'weighted_score' => round($weightedScore, 2),
|
|
'scores' => $scores,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Berechne Counterparty KYC Risk Level (empfangende Partei)
|
|
*
|
|
* @param Collection $outputs Collection von backend_data_pool records
|
|
* @return string 'low', 'high', or 'critical'
|
|
*/
|
|
public function calculateCounterpartyKycRiskLevel(Collection $outputs): string
|
|
{
|
|
$breakdown = $this->calculateCounterpartyKycRiskBreakdown($outputs);
|
|
|
|
return $breakdown['risk_level'];
|
|
}
|
|
|
|
/**
|
|
* Berechne Counterparty KYC Risk Level mit detailliertem Breakdown
|
|
*
|
|
* @param Collection $outputs Collection von backend_data_pool records
|
|
* @return array Breakdown mit scores und risk_level
|
|
*/
|
|
public function calculateCounterpartyKycRiskBreakdown(Collection $outputs): array
|
|
{
|
|
if ($outputs->isEmpty()) {
|
|
return [
|
|
'risk_level' => 'high',
|
|
'weighted_score' => 70.0,
|
|
'scores' => [],
|
|
];
|
|
}
|
|
|
|
$outputsArray = $outputs->pluck('content', 'output_key')->toArray();
|
|
|
|
$scores = [
|
|
'transaction_score' => $this->calculateTransactionScore($outputsArray),
|
|
'transaction_analysis' => $this->calculateTransactionAnalysisScore($outputsArray),
|
|
'sanctions' => $this->calculateSanctionsScore($outputsArray, 'counterparty_'),
|
|
'country_risk' => $this->calculateCountryRiskScore($outputsArray, 'counterparty_'),
|
|
'pep_adverse' => $this->calculatePepAdverseScore($outputsArray, 'counterparty_'),
|
|
'corruption' => $this->calculateCorruptionScore($outputsArray, 'counterparty_'),
|
|
'financial_health' => $this->calculateFinancialHealthScore($outputsArray, 'counterparty_'),
|
|
'legal_issues' => $this->calculateLegalIssuesScore($outputsArray, 'counterparty_'),
|
|
];
|
|
|
|
$weightedScore = $this->calculateWeightedScore($scores);
|
|
$riskLevel = $this->determineRiskLevel($weightedScore);
|
|
|
|
return [
|
|
'risk_level' => $riskLevel,
|
|
'weighted_score' => round($weightedScore, 2),
|
|
'scores' => $scores,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 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, string $prefix = ''): int
|
|
{
|
|
$sanctionFlags = [
|
|
$prefix.'corporate_eusanctions',
|
|
$prefix.'corporate_ofacsanctions',
|
|
$prefix.'corporate_uksanctions',
|
|
$prefix.'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, string $prefix = ''): int
|
|
{
|
|
$key = $prefix.'country_risk';
|
|
|
|
// Wenn Country Risk Daten vorhanden sind, diese verwenden
|
|
if (isset($outputs[$key])) {
|
|
$countryRiskData = json_decode($outputs[$key], true);
|
|
|
|
// Prüfe ob flag=true ist (valide Daten vorhanden)
|
|
if (is_array($countryRiskData) && isset($countryRiskData['flag']) && $countryRiskData['flag'] === true) {
|
|
if (isset($countryRiskData['score'])) {
|
|
return min(100, max(0, (int) $countryRiskData['score']));
|
|
}
|
|
|
|
if (isset($countryRiskData['level'])) {
|
|
return $this->mapRiskLevelToScore($countryRiskData['level']);
|
|
}
|
|
}
|
|
}
|
|
|
|
// FALLBACK: Versuche Land aus HQ oder anderen Feldern zu extrahieren
|
|
$countryCode = $this->extractCountryCodeFromOutputs($outputs, $prefix);
|
|
|
|
if ($countryCode) {
|
|
return $this->getCountryRiskScoreByCode($countryCode);
|
|
}
|
|
|
|
return 40; // Default: Mittleres Risiko
|
|
}
|
|
|
|
/**
|
|
* Extrahiere Ländercode aus verfügbaren Daten
|
|
*/
|
|
private function extractCountryCodeFromOutputs(array $outputs, string $prefix): ?string
|
|
{
|
|
// Bekannte Länder-Mappings (WICHTIG: Zuerst Länder-Namen matchen!)
|
|
$countryPatterns = [
|
|
'/\bAzerbaijan\b/i' => 'AZ',
|
|
'/\bGermany\b/i' => 'DE',
|
|
'/\bDeutschland\b/i' => 'DE',
|
|
'/\bRussia\b/i' => 'RU',
|
|
'/\bChina\b/i' => 'CN',
|
|
'/\bUnited States\b/i' => 'US',
|
|
'/\bKazakhstan\b/i' => 'KZ',
|
|
'/\bTurkmenistan\b/i' => 'TM',
|
|
'/\bVenezuela\b/i' => 'VE',
|
|
'/\bIran\b/i' => 'IR',
|
|
'/\bNorth Korea\b/i' => 'KP',
|
|
'/\bBelarus\b/i' => 'BY',
|
|
'/\bCuba\b/i' => 'CU',
|
|
'/\bMyanmar\b/i' => 'MM',
|
|
'/\bSyria\b/i' => 'SY',
|
|
'/\bAfghanistan\b/i' => 'AF',
|
|
'/\bYemen\b/i' => 'YE',
|
|
'/\bSomalia\b/i' => 'SO',
|
|
'/\bSudan\b/i' => 'SD',
|
|
'/\b(?:British )?Virgin Islands\b/i' => 'VG', // British Virgin Islands (Tax Haven)
|
|
'/\bCayman Islands\b/i' => 'KY', // Cayman Islands (Tax Haven)
|
|
'/\bBermuda\b/i' => 'BM', // Bermuda (Tax Haven)
|
|
'/\bBahamas\b/i' => 'BS', // Bahamas (Tax Haven)
|
|
'/\bPanama\b/i' => 'PA', // Panama (Tax Haven)
|
|
'/\bLiechtenstein\b/i' => 'LI', // Liechtenstein (Tax Haven)
|
|
'/\bMonaco\b/i' => 'MC', // Monaco (Tax Haven)
|
|
'/\bLuxembourg\b/i' => 'LU', // Luxembourg (Tax Haven)
|
|
];
|
|
|
|
// HIGHEST PRIORITY: Check corporate_name field (company name often contains country)
|
|
$nameKey = $prefix.'corporate_name';
|
|
if (isset($outputs[$nameKey])) {
|
|
$nameData = json_decode($outputs[$nameKey], true);
|
|
if (is_array($nameData) && isset($nameData['answer'])) {
|
|
foreach ($countryPatterns as $pattern => $code) {
|
|
if (preg_match($pattern, $nameData['answer'])) {
|
|
return $code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Versuche aus summary zu extrahieren (zweite Priorität)
|
|
$summaryKey = $prefix.'corporate_summary';
|
|
if (isset($outputs[$summaryKey])) {
|
|
$summaryData = json_decode($outputs[$summaryKey], true);
|
|
if (is_array($summaryData) && isset($summaryData['answer'])) {
|
|
$answer = $summaryData['answer'];
|
|
|
|
foreach ($countryPatterns as $pattern => $code) {
|
|
if (preg_match($pattern, $answer)) {
|
|
return $code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Versuche aus HQ zu extrahieren
|
|
$hqKey = $prefix.'corporate_HQ';
|
|
if (isset($outputs[$hqKey])) {
|
|
$hqData = json_decode($outputs[$hqKey], true);
|
|
if (is_array($hqData) && isset($hqData['answer'])) {
|
|
$answer = $hqData['answer'];
|
|
|
|
// Erst Länder-Namen matchen
|
|
foreach ($countryPatterns as $pattern => $code) {
|
|
if (preg_match($pattern, $answer)) {
|
|
return $code;
|
|
}
|
|
}
|
|
|
|
// Falls keine Länder-Namen gefunden: Suche nach 2-Buchstaben Ländercodes
|
|
// ABER: Exclude common false positives wie "AG" (Aktiengesellschaft)
|
|
$excludePatterns = ['AG', 'SA', 'AB', 'CO', 'LT', 'LP', 'PC', 'UK']; // UK ist OK, aber später
|
|
if (preg_match('/\b([A-Z]{2})\b/', $answer, $matches)) {
|
|
$potentialCode = $matches[1];
|
|
if (! in_array($potentialCode, $excludePatterns)) {
|
|
return $potentialCode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Bestimme Country Risk Score basierend auf Ländercode (Fallback)
|
|
*/
|
|
private function getCountryRiskScoreByCode(string $countryCode): int
|
|
{
|
|
// Kritische Hochrisikoländer (Sanktionen, Terror)
|
|
$criticalRiskCountries = [
|
|
'KP' => 95, // Nordkorea
|
|
'IR' => 95, // Iran
|
|
'SY' => 95, // Syrien
|
|
'AF' => 90, // Afghanistan
|
|
'YE' => 90, // Jemen
|
|
'SO' => 90, // Somalia
|
|
'SD' => 85, // Sudan
|
|
];
|
|
|
|
// Hochrisikoländer (Sanktionen, Korruption, Geldwäsche)
|
|
$highRiskCountries = [
|
|
'RU' => 85, // Russland
|
|
'BY' => 85, // Belarus
|
|
'VE' => 85, // Venezuela
|
|
'CU' => 80, // Kuba
|
|
'MM' => 80, // Myanmar
|
|
'ZW' => 80, // Simbabwe
|
|
'LY' => 80, // Libyen
|
|
'IQ' => 75, // Irak
|
|
];
|
|
|
|
// Erhöhtes Risiko (Korruption, schwache Governance, Öl-Staaten)
|
|
$mediumHighRiskCountries = [
|
|
'AZ' => 70, // Aserbaidschan (Öl, Korruption) ← WICHTIG!
|
|
'KZ' => 70, // Kasachstan
|
|
'TM' => 70, // Turkmenistan
|
|
'UZ' => 65, // Usbekistan
|
|
'TJ' => 65, // Tadschikistan
|
|
'PK' => 65, // Pakistan
|
|
'BD' => 60, // Bangladesch
|
|
'NG' => 65, // Nigeria
|
|
'CD' => 70, // DR Kongo
|
|
'GQ' => 75, // Äquatorialguinea
|
|
'AO' => 65, // Angola
|
|
'GN' => 65, // Guinea
|
|
'HT' => 70, // Haiti
|
|
'NI' => 65, // Nicaragua
|
|
'ER' => 75, // Eritrea
|
|
'LA' => 60, // Laos
|
|
'KH' => 60, // Kambodscha
|
|
'CN' => 55, // China (Governance, Menschenrechte)
|
|
];
|
|
|
|
// Steueroasen (ERHÖHT für strengere Bewertung - Tax Havens sind hohes AML-Risiko!)
|
|
$taxHavens = [
|
|
'KY' => 75, // Cayman Islands (stark erhöht)
|
|
'BM' => 75, // Bermuda (stark erhöht)
|
|
'VG' => 80, // British Virgin Islands (stark erhöht - sehr häufig für Briefkastenfirmen)
|
|
'BS' => 75, // Bahamas (stark erhöht)
|
|
'PA' => 80, // Panama (stark erhöht - Panama Papers)
|
|
'LI' => 70, // Liechtenstein
|
|
'MC' => 65, // Monaco
|
|
'LU' => 60, // Luxembourg (trotz EU - Steueroase)
|
|
];
|
|
|
|
// Prüfe Kritisch
|
|
if (isset($criticalRiskCountries[$countryCode])) {
|
|
return $criticalRiskCountries[$countryCode];
|
|
}
|
|
|
|
// Prüfe Hoch
|
|
if (isset($highRiskCountries[$countryCode])) {
|
|
return $highRiskCountries[$countryCode];
|
|
}
|
|
|
|
// Prüfe Mittel-Hoch
|
|
if (isset($mediumHighRiskCountries[$countryCode])) {
|
|
return $mediumHighRiskCountries[$countryCode];
|
|
}
|
|
|
|
// Prüfe Steueroasen
|
|
if (isset($taxHavens[$countryCode])) {
|
|
return $taxHavens[$countryCode];
|
|
}
|
|
|
|
// EU & OECD Länder = Geringes Risiko
|
|
$lowRiskCountries = [
|
|
'DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT', 'CH', 'SE', 'NO', 'DK', 'FI',
|
|
'GB', 'IE', 'PT', 'GR', 'PL', 'CZ', 'HU', 'RO', 'BG', 'SK', 'SI', 'HR',
|
|
'LT', 'LV', 'EE', 'CY', 'MT', 'LU', 'IS',
|
|
'US', 'CA', 'AU', 'NZ', 'JP', 'KR', 'SG',
|
|
];
|
|
|
|
if (in_array($countryCode, $lowRiskCountries)) {
|
|
return 25; // Geringes Risiko
|
|
}
|
|
|
|
return 40; // Default: Mittleres Risiko
|
|
}
|
|
|
|
/**
|
|
* Berechne PEP & Adverse Media Score (0-100)
|
|
*/
|
|
private function calculatePepAdverseScore(array $outputs, string $prefix = ''): int
|
|
{
|
|
$score = 0;
|
|
$count = 0;
|
|
|
|
$pepFields = [
|
|
$prefix.'corporate_pepexposure' => 75,
|
|
$prefix.'corporate_pep' => 80,
|
|
$prefix.'corporate_adverse' => 70,
|
|
$prefix.'corporate_AMLexposure' => 85,
|
|
];
|
|
|
|
foreach ($pepFields as $field => $defaultScore) {
|
|
if (! isset($outputs[$field])) {
|
|
continue;
|
|
}
|
|
|
|
$data = json_decode($outputs[$field], true);
|
|
|
|
// Prüfe zuerst auf flag=true
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += $this->extractScoreFromData($data, $defaultScore);
|
|
$count++;
|
|
} elseif (is_array($data) && isset($data['answer'])) {
|
|
// FALLBACK: Wenn flag=false, aber Answer enthält PEP-Keywords
|
|
$answer = strtolower($data['answer']);
|
|
|
|
// PEP-Keywords (state-owned, government, politically exposed, etc.)
|
|
$pepKeywords = [
|
|
'politically exposed person',
|
|
'state-owned',
|
|
'state owned',
|
|
'government official',
|
|
'government-owned',
|
|
'public official',
|
|
'may be considered pep',
|
|
'pep proximity',
|
|
'close associate',
|
|
];
|
|
|
|
// Adverse Media Keywords
|
|
$adverseKeywords = [
|
|
'bribery',
|
|
'corruption',
|
|
'scandal',
|
|
'allegation',
|
|
'investigation',
|
|
'money laundering',
|
|
'fraud',
|
|
'sanctions',
|
|
];
|
|
|
|
// Prüfe auf Keywords
|
|
foreach ($pepKeywords as $keyword) {
|
|
if (stripos($answer, $keyword) !== false) {
|
|
// PEP-Indikator gefunden, aber mit reduziertem Score (da flag=false)
|
|
$score += $defaultScore * 0.7; // 70% des Default Scores
|
|
$count++;
|
|
break; // Nur einmal pro Feld zählen
|
|
}
|
|
}
|
|
|
|
// Wenn noch nicht gezählt, prüfe auf Adverse Keywords
|
|
if ($count === 0 || ! isset($scored)) {
|
|
foreach ($adverseKeywords as $keyword) {
|
|
if (stripos($answer, $keyword) !== false) {
|
|
$score += $defaultScore * 0.6; // 60% bei Adverse Keywords
|
|
$count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $count > 0 ? (int) round($score / $count) : 0;
|
|
}
|
|
|
|
/**
|
|
* Berechne Corruption Score (0-100)
|
|
*/
|
|
private function calculateCorruptionScore(array $outputs, string $prefix = ''): int
|
|
{
|
|
$corruptionFields = [
|
|
$prefix.'corruption_sector',
|
|
$prefix.'corruption_country',
|
|
$prefix.'corruption_relationship',
|
|
$prefix.'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)
|
|
|| (isset($data['flag']) && $data['flag'] === true); // WICHTIG: Flag-Check hinzugefügt
|
|
}
|
|
|
|
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)
|
|
|| (isset($data['flag']) && $data['flag'] === true); // WICHTIG: Flag-Check hinzugefügt
|
|
}
|
|
|
|
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 Transaction Plausibility Score (0-100)
|
|
* Analysiert die Plausibilität der Transaktion basierend auf:
|
|
* - Betragshöhe
|
|
* - Länder-Kombination
|
|
* - Zweck
|
|
* - Währung
|
|
*/
|
|
public function calculateTransactionPlausibilityScore(array $transactionData): int
|
|
{
|
|
$riskFactors = [];
|
|
$score = 0; // Start bei 0 = kein Risiko
|
|
|
|
// 1. Betragshöhe analysieren
|
|
$amount = (float) ($transactionData['tx_amount'] ?? 0);
|
|
if ($amount > 10000000) { // > 10 Mio
|
|
$riskFactors[] = 40; // Sehr hohe Beträge = erhöhtes Risiko
|
|
} elseif ($amount > 1000000) { // > 1 Mio
|
|
$riskFactors[] = 25;
|
|
} elseif ($amount > 100000) { // > 100k
|
|
$riskFactors[] = 15;
|
|
} else {
|
|
$riskFactors[] = 5; // Kleine Beträge = geringes Risiko
|
|
}
|
|
|
|
// 2. Länder-Kombination analysieren
|
|
$outgoing = $transactionData['tx_country_outgoing'] ?? '';
|
|
$incoming = $transactionData['tx_country_incoming'] ?? '';
|
|
|
|
// Kritische Hochrisikoländer (Sanktionen, Terror, schwere Korruption)
|
|
$criticalRiskCountries = [
|
|
'KP', // Nordkorea
|
|
'IR', // Iran
|
|
'SY', // Syrien
|
|
'AF', // Afghanistan (Taliban)
|
|
'YE', // Jemen
|
|
'SO', // Somalia
|
|
'SD', // Sudan
|
|
];
|
|
|
|
// Hochrisikoländer (Sanktionen, Korruption, Geldwäsche)
|
|
$highRiskCountries = [
|
|
'RU', // Russland
|
|
'BY', // Belarus
|
|
'VE', // Venezuela
|
|
'CU', // Kuba
|
|
'MM', // Myanmar
|
|
'ZW', // Simbabwe
|
|
'LY', // Libyen
|
|
'IQ', // Irak
|
|
];
|
|
|
|
// Erhöhtes Risiko (Korruption, schwache Governance, Öl-Staaten)
|
|
$mediumHighRiskCountries = [
|
|
'AZ', // Aserbaidschan (Öl, Korruption)
|
|
'KZ', // Kasachstan (Öl, Korruption)
|
|
'TM', // Turkmenistan (Öl, Diktatur)
|
|
'UZ', // Usbekistan
|
|
'TJ', // Tadschikistan
|
|
'PK', // Pakistan
|
|
'BD', // Bangladesch
|
|
'NG', // Nigeria (Korruption, Öl)
|
|
'CD', // DR Kongo
|
|
'GQ', // Äquatorialguinea (Öl, Diktatur)
|
|
'AO', // Angola (Öl, Korruption)
|
|
'GN', // Guinea
|
|
'HT', // Haiti
|
|
'NI', // Nicaragua
|
|
'ER', // Eritrea
|
|
'LA', // Laos
|
|
'KH', // Kambodscha
|
|
];
|
|
|
|
// Steueroasen & Offshore-Zentren
|
|
$taxHavens = [
|
|
'KY', // Cayman Islands
|
|
'BM', // Bermuda
|
|
'VG', // British Virgin Islands
|
|
'BS', // Bahamas
|
|
'PA', // Panama
|
|
'LI', // Liechtenstein
|
|
'MC', // Monaco
|
|
'AN', // Niederländische Antillen
|
|
'AG', // Antigua und Barbuda
|
|
'BZ', // Belize
|
|
'SC', // Seychellen
|
|
'MU', // Mauritius
|
|
'WS', // Samoa
|
|
'CK', // Cook Islands
|
|
'IM', // Isle of Man
|
|
'JE', // Jersey
|
|
'GG', // Guernsey
|
|
'MT', // Malta (teilweise)
|
|
'CY', // Zypern (teilweise)
|
|
];
|
|
|
|
// China separat bewerten (wegen Größe und wirtschaftlicher Bedeutung)
|
|
$chinaRelated = ['CN', 'HK', 'MO']; // China, Hong Kong, Macau
|
|
|
|
// Risiko-Bewertung nach Priorität
|
|
if (in_array($outgoing, $criticalRiskCountries) || in_array($incoming, $criticalRiskCountries)) {
|
|
$riskFactors[] = 70; // Kritisches Risiko
|
|
} elseif (in_array($outgoing, $highRiskCountries) || in_array($incoming, $highRiskCountries)) {
|
|
$riskFactors[] = 50; // Hohes Risiko
|
|
} elseif (in_array($outgoing, $taxHavens) || in_array($incoming, $taxHavens)) {
|
|
$riskFactors[] = 45; // Steueroase
|
|
} elseif (in_array($outgoing, $mediumHighRiskCountries) || in_array($incoming, $mediumHighRiskCountries)) {
|
|
$riskFactors[] = 35; // Erhöhtes Risiko (Korruption, schwache Governance)
|
|
} elseif (in_array($outgoing, $chinaRelated) || in_array($incoming, $chinaRelated)) {
|
|
$riskFactors[] = 30; // China: Moderates Risiko (Kapitalkontrolle, Transparenz)
|
|
} else {
|
|
$riskFactors[] = 10; // Normale Länder (EU, USA, entwickelte Demokratien)
|
|
}
|
|
|
|
// 3. Zweck analysieren (Verdächtige Keywords)
|
|
$purpose = strtolower($transactionData['tx_purpose'] ?? '');
|
|
$suspiciousKeywords = ['cash', 'loan', 'investment', 'consulting', 'service fee', 'commission'];
|
|
|
|
$suspiciousCount = 0;
|
|
foreach ($suspiciousKeywords as $keyword) {
|
|
if (str_contains($purpose, $keyword)) {
|
|
$suspiciousCount++;
|
|
}
|
|
}
|
|
|
|
if ($suspiciousCount >= 2) {
|
|
$riskFactors[] = 30; // Mehrere verdächtige Keywords
|
|
} elseif ($suspiciousCount === 1) {
|
|
$riskFactors[] = 15;
|
|
} else {
|
|
$riskFactors[] = 5;
|
|
}
|
|
|
|
// 4. Währungs-Plausibilität
|
|
$currency = $transactionData['tx_currency'] ?? 'EUR';
|
|
if (! in_array($currency, ['EUR', 'USD', 'GBP', 'CHF', 'JPY'])) {
|
|
$riskFactors[] = 20; // Unübliche Währung
|
|
} else {
|
|
$riskFactors[] = 5;
|
|
}
|
|
|
|
// Berechne Durchschnitt aller Risikofaktoren
|
|
$score = (int) round(array_sum($riskFactors) / count($riskFactors));
|
|
|
|
return min(100, max(0, $score));
|
|
}
|
|
|
|
/**
|
|
* Berechne kombinierten Risk Score mit Entity, Counterparty und Transaction Plausibility
|
|
* KATEGORISCHER ANSATZ: Höheres Risiko schlägt niedrigeres
|
|
* - Wenn IRGENDEIN Faktor CRITICAL → Gesamt CRITICAL
|
|
* - Wenn IRGENDEIN Faktor HIGH → Gesamt HIGH
|
|
* - Sonst → LOW
|
|
*
|
|
* @return array ['entity_risk_level', 'counterparty_risk_level', 'plausibility_score', 'plausibility_level', 'combined_score', 'combined_risk_level', 'entity_risk_breakdown', 'counterparty_risk_breakdown', 'transaction_plausibility_breakdown', 'combined_risk_breakdown']
|
|
*/
|
|
public function calculateCombinedRisk(Collection $outputs, array $transactionData): array
|
|
{
|
|
// Berechne Entity Risk mit Breakdown
|
|
$entityBreakdown = $this->calculateEntityKycRiskBreakdown($outputs);
|
|
$entityRiskLevel = $entityBreakdown['risk_level'];
|
|
|
|
// Berechne Counterparty Risk mit Breakdown
|
|
$counterpartyBreakdown = $this->calculateCounterpartyKycRiskBreakdown($outputs);
|
|
$counterpartyRiskLevel = $counterpartyBreakdown['risk_level'];
|
|
|
|
// Berechne Transaction Plausibility Score und konvertiere zu Level
|
|
$plausibilityScore = $this->calculateTransactionPlausibilityScore($transactionData);
|
|
$plausibilityLevel = $this->determineRiskLevel($plausibilityScore);
|
|
|
|
// KATEGORISCHER ANSATZ: Critical schlägt High schlägt Low
|
|
$combinedRiskLevel = $this->determineCategoricalRiskLevel([
|
|
$entityRiskLevel,
|
|
$counterpartyRiskLevel,
|
|
$plausibilityLevel,
|
|
]);
|
|
|
|
// Konvertiere finales Risk Level zu Score für die Datenbank
|
|
$combinedScore = $this->riskLevelToNumericScore($combinedRiskLevel);
|
|
|
|
// Transaction Plausibility Breakdown
|
|
$transactionPlausibilityBreakdown = [
|
|
'score' => $plausibilityScore,
|
|
'risk_level' => $plausibilityLevel,
|
|
'transaction_data' => $transactionData,
|
|
];
|
|
|
|
// Combined Risk Breakdown
|
|
$combinedRiskBreakdown = [
|
|
'combined_risk_level' => $combinedRiskLevel,
|
|
'combined_score' => $combinedScore,
|
|
'method' => 'categorical',
|
|
'contributing_factors' => [
|
|
'entity_risk_level' => $entityRiskLevel,
|
|
'counterparty_risk_level' => $counterpartyRiskLevel,
|
|
'plausibility_level' => $plausibilityLevel,
|
|
],
|
|
];
|
|
|
|
return [
|
|
'entity_risk_level' => $entityRiskLevel,
|
|
'counterparty_risk_level' => $counterpartyRiskLevel,
|
|
'plausibility_score' => $plausibilityScore,
|
|
'plausibility_level' => $plausibilityLevel,
|
|
'combined_score' => $combinedScore,
|
|
'combined_risk_level' => $combinedRiskLevel,
|
|
// Neue Breakdown-Daten
|
|
'entity_risk_breakdown' => $entityBreakdown,
|
|
'counterparty_risk_breakdown' => $counterpartyBreakdown,
|
|
'transaction_plausibility_breakdown' => $transactionPlausibilityBreakdown,
|
|
'combined_risk_breakdown' => $combinedRiskBreakdown,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Bestimme kategorisches Risk Level: Höchstes Risiko gewinnt
|
|
* CRITICAL > HIGH > LOW
|
|
*
|
|
* @param array $riskLevels Array von Risk Levels ['low', 'high', 'critical']
|
|
* @return string 'low', 'high', or 'critical'
|
|
*/
|
|
private function determineCategoricalRiskLevel(array $riskLevels): string
|
|
{
|
|
// Wenn IRGENDEIN Faktor CRITICAL → Gesamt CRITICAL
|
|
if (in_array('critical', $riskLevels)) {
|
|
return 'critical';
|
|
}
|
|
|
|
// Wenn IRGENDEIN Faktor HIGH → Gesamt HIGH
|
|
if (in_array('high', $riskLevels)) {
|
|
return 'high';
|
|
}
|
|
|
|
// Sonst → LOW
|
|
return 'low';
|
|
}
|
|
|
|
/**
|
|
* Konvertiere Risk Level zu numerischem Score (0-100)
|
|
*/
|
|
private function riskLevelToNumericScore(string $riskLevel): int
|
|
{
|
|
return match ($riskLevel) {
|
|
'low' => 20,
|
|
'high' => 60,
|
|
'critical' => 90,
|
|
default => 50,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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';
|
|
}
|
|
|
|
/**
|
|
* NEU: Berechne Transaction Analysis Score (0-100)
|
|
* Analysiert: Duplikate, Structuring/Smurfing, Fake Purpose
|
|
*/
|
|
private function calculateTransactionAnalysisScore(array $outputs): int
|
|
{
|
|
$score = 0;
|
|
$flagCount = 0;
|
|
|
|
// 1. Duplikats-Erkennung
|
|
if (isset($outputs['tranx_duplicate'])) {
|
|
$data = json_decode($outputs['tranx_duplicate'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 85; // Duplikate = sehr hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 2. Structuring/Smurfing Detection (Geldwäsche-Indikator)
|
|
if (isset($outputs['tranx_structuring'])) {
|
|
$data = json_decode($outputs['tranx_structuring'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 95; // Structuring = kritisches Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 3. Fake Purpose Check
|
|
if (isset($outputs['tranx_fakepurposecheck'])) {
|
|
$data = json_decode($outputs['tranx_fakepurposecheck'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 80; // Fake Purpose = hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 4. Round Amount (Rundbeträge können auf Geldwäsche hindeuten)
|
|
if (isset($outputs['tranx_roundamount'])) {
|
|
$data = json_decode($outputs['tranx_roundamount'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 40; // Rundbeträge = mittleres Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 5. Velocity Check (zu schnelle/viele Transaktionen)
|
|
if (isset($outputs['tranx_velocitycheck'])) {
|
|
$data = json_decode($outputs['tranx_velocitycheck'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 70; // Velocity = hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 6. Unusual Pattern
|
|
if (isset($outputs['tranx_unusualpattern'])) {
|
|
$data = json_decode($outputs['tranx_unusualpattern'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 60; // Ungewöhnliche Muster = erhöhtes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
if ($flagCount === 0) {
|
|
return 0; // Keine verdächtigen Muster gefunden
|
|
}
|
|
|
|
// Durchschnitt berechnen
|
|
return (int) round($score / $flagCount);
|
|
}
|
|
|
|
/**
|
|
* NEU: Berechne Financial Health Score (0-100)
|
|
* Analysiert: Insolvenz, Liquidation, Rating, Solvency
|
|
*/
|
|
private function calculateFinancialHealthScore(array $outputs, string $prefix = ''): int
|
|
{
|
|
$score = 0;
|
|
$flagCount = 0;
|
|
|
|
// 1. Insolvenz (kritisch)
|
|
if (isset($outputs[$prefix.'corporate_insolvency'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_insolvency'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 95; // Insolvenz = kritisches Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 2. Liquidation (kritisch)
|
|
if (isset($outputs[$prefix.'corporate_liquidation'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_liquidation'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 95; // Liquidation = kritisches Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 3. Rating (falls vorhanden)
|
|
if (isset($outputs[$prefix.'corporate_rating'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_rating'], true);
|
|
|
|
if (is_array($data) && isset($data['rating'])) {
|
|
$rating = strtoupper($data['rating']);
|
|
|
|
// Rating-basierte Scores
|
|
if (in_array($rating, ['D', 'C', 'CC', 'CCC'])) {
|
|
$score += 90; // Sehr schlechtes Rating
|
|
$flagCount++;
|
|
} elseif (in_array($rating, ['B', 'BB', 'BBB'])) {
|
|
$score += 60; // Mittleres Rating
|
|
$flagCount++;
|
|
} elseif (in_array($rating, ['A', 'AA', 'AAA'])) {
|
|
$score += 10; // Gutes Rating (geringes Risiko)
|
|
$flagCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Solvency (Zahlungsfähigkeit)
|
|
if (isset($outputs[$prefix.'corporate_solvency'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_solvency'], true);
|
|
|
|
if (is_array($data) && isset($data['solvent']) && $data['solvent'] === false) {
|
|
$score += 85; // Nicht zahlungsfähig = hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 5. Audit Findings (Prüfungsfeststellungen)
|
|
if (isset($outputs[$prefix.'corporate_auditfindings'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_auditfindings'], true);
|
|
|
|
if (is_array($data) && isset($data['critical_findings']) && $data['critical_findings'] === true) {
|
|
$score += 70; // Kritische Prüfungsfeststellungen
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
if ($flagCount === 0) {
|
|
return 0; // Keine finanziellen Probleme gefunden
|
|
}
|
|
|
|
return (int) round($score / $flagCount);
|
|
}
|
|
|
|
/**
|
|
* NEU: Berechne Legal Issues Score (0-100)
|
|
* Analysiert: Gerichtsverfahren, Steueroasen, Exportverstöße, Warnungen
|
|
*/
|
|
private function calculateLegalIssuesScore(array $outputs, string $prefix = ''): int
|
|
{
|
|
$score = 0;
|
|
$flagCount = 0;
|
|
|
|
// 1. Court Cases (Gerichtsverfahren)
|
|
if (isset($outputs[$prefix.'corporate_courtcases'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_courtcases'], true);
|
|
|
|
if (is_array($data) && isset($data['active_cases']) && $data['active_cases'] > 0) {
|
|
$caseCount = (int) $data['active_cases'];
|
|
|
|
if ($caseCount >= 5) {
|
|
$score += 80; // Viele Verfahren = hohes Risiko
|
|
} elseif ($caseCount >= 2) {
|
|
$score += 60; // Einige Verfahren = mittleres Risiko
|
|
} else {
|
|
$score += 40; // Ein Verfahren = erhöhtes Risiko
|
|
}
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 2. Tax Haven (Steueroasen-Nutzung)
|
|
if (isset($outputs[$prefix.'corporate_haven'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_haven'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 75; // Steueroasen = hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 3. Export Control Violations (Exportverstöße)
|
|
if (isset($outputs[$prefix.'corporate_exportcontrol'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_exportcontrol'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 90; // Exportverstöße = kritisches Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 4. Warnings (Behördliche Warnungen)
|
|
if (isset($outputs[$prefix.'corporate_warnings'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_warnings'], true);
|
|
if ($this->hasPositiveMatch($data)) {
|
|
$score += 70; // Warnungen = hohes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
// 5. License Issues (Lizenzprobleme)
|
|
if (isset($outputs[$prefix.'corporate_license'])) {
|
|
$data = json_decode($outputs[$prefix.'corporate_license'], true);
|
|
|
|
if (is_array($data) && isset($data['license_valid']) && $data['license_valid'] === false) {
|
|
$score += 65; // Ungültige/fehlende Lizenz = erhöhtes Risiko
|
|
$flagCount++;
|
|
}
|
|
}
|
|
|
|
if ($flagCount === 0) {
|
|
return 0; // Keine rechtlichen Probleme gefunden
|
|
}
|
|
|
|
return (int) round($score / $flagCount);
|
|
}
|
|
}
|