diff --git a/.DS_Store b/.DS_Store index 9fb6f0f..2f628c4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app/Jobs/SyncBackendDataPool.php b/app/Jobs/SyncBackendDataPool.php index d61951c..bc25857 100644 --- a/app/Jobs/SyncBackendDataPool.php +++ b/app/Jobs/SyncBackendDataPool.php @@ -138,6 +138,9 @@ class SyncBackendDataPool implements ShouldQueue 'status' => $record->status, 'created_at' => $record->created_at, 'last_modified_at' => $record->last_modified_at, + 'risk_score' => $record->risk_score, + 'risk_level' => $record->risk_level, + 'risk_details_json' => $record->risk_details_json, 'prompt_id' => $record->prompt_id, 'output_key' => $record->output_key, 'content' => $record->content, @@ -191,6 +194,9 @@ class SyncBackendDataPool implements ShouldQueue 't.status', 't.created_at', 't.last_modified_at', + 't.risk_score', + 't.risk_level', + 't.risk_details_json', 'tout.prompt_id', 'tout.output_key', 'tout.content', diff --git a/app/Jobs/TransformDataPoolToProduction.php b/app/Jobs/TransformDataPoolToProduction.php index d80b1a4..c940e53 100644 --- a/app/Jobs/TransformDataPoolToProduction.php +++ b/app/Jobs/TransformDataPoolToProduction.php @@ -6,7 +6,6 @@ namespace App\Jobs; use App\Models\Company; use App\Models\Transaction; -use App\Services\KycRiskCalculator; use Carbon\Carbon; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -32,13 +31,12 @@ class TransformDataPoolToProduction implements ShouldQueue */ public function __construct( public ?int $batchSize = 100, - ) { - } + ) {} /** * Execute the job. */ - public function handle(KycRiskCalculator $kycCalculator): void + public function handle(): void { $startTime = now(); @@ -47,8 +45,8 @@ class TransformDataPoolToProduction implements ShouldQueue ]); try { - DB::transaction(function () use ($kycCalculator) { - $this->transformData($kycCalculator); + DB::transaction(function () { + $this->transformData(); }); $duration = now()->diffInSeconds($startTime); @@ -69,7 +67,7 @@ class TransformDataPoolToProduction implements ShouldQueue /** * Transform data from backend_data_pool to companies and transactions */ - private function transformData(KycRiskCalculator $kycCalculator): void + private function transformData(): void { // Get all unique transaction IDs from data pool $transactionIds = DB::table('backend_data_pool') @@ -97,7 +95,7 @@ class TransformDataPoolToProduction implements ShouldQueue $firstOutput = $outputs->first(); // 1. Create or update Company - $company = $this->createOrUpdateCompany($firstOutput, $outputs, $kycCalculator); + $company = $this->createOrUpdateCompany($firstOutput, $outputs); if ($company->wasRecentlyCreated) { $processedCompanies++; } @@ -118,21 +116,23 @@ class TransformDataPoolToProduction implements ShouldQueue /** * Create or update a Company from data pool outputs */ - private function createOrUpdateCompany($firstOutput, $outputs, KycRiskCalculator $kycCalculator): Company + private function createOrUpdateCompany($firstOutput, $outputs): Company { $corporateEntity = $firstOutput->corporate_entity; - // Calculate KYC Risk Level - $kycRiskLevel = $kycCalculator->calculateKycRiskLevel(collect([$outputs])->first()); + // Use backend risk_score and map to risk_level + $backendRiskScore = $firstOutput->risk_score ?? 50; + $kycRiskLevel = $this->mapBackendRiskScoreToLevel($backendRiskScore); // Extract company data from outputs $companyData = [ 'country' => $this->extractCountry($firstOutput, $outputs), 'kyc_risk_level' => $kycRiskLevel, - 'sector' => $this->extractField($outputs, 'corporate_sector', 255), - 'headquarters' => $this->extractField($outputs, 'corporate_HQ', 255), - 'summary' => $this->extractField($outputs, 'corporate_summary', 5000), // text field, longer OK - 'legal_name' => $this->extractField($outputs, 'corporate_name', 255), + 'sector' => $this->extractLeanField($outputs, 'entity_corporate_sector', 255), + 'headquarters' => $this->extractLeanField($outputs, 'entity_corporate_HQ', 255), + 'summary' => $this->extractField($outputs, 'entity_corporate_summary', 5000), // text field, longer OK + 'legal_name' => $this->extractLegalName($outputs, $corporateEntity), + 'ticker' => null, // Ticker is not available in backend_data_pool ]; return Company::updateOrCreate( @@ -146,14 +146,27 @@ class TransformDataPoolToProduction implements ShouldQueue */ private function createOrUpdateTransaction(Company $company, $firstOutput, $outputs): Transaction { - $reference = 'MIGRATED-' . $firstOutput->transaction_id; + // Generate better reference number + $reference = $this->generateTransactionReference($firstOutput); - // Map KYC Risk Level to Transaction Status - $status = match ($company->kyc_risk_level) { + // Use risk_score from backend (leading field) + $backendRiskScore = $firstOutput->risk_score ?? 50; // Default to 50 (high risk) if not set + + // Map backend risk_score to risk_level + // -100 to 20 = Critical + // 20 to 80 = High + // 80 to 100 = Low + $riskLevel = $this->mapBackendRiskScoreToLevel($backendRiskScore); + + // Convert to 0-255 scale for frontend transactions table + $riskScore = $this->mapBackendRiskScoreTo255Scale($backendRiskScore); + + // Map risk level to Transaction Status + $status = match ($riskLevel) { 'critical' => Transaction::STATUS_TRUE_POSITIVE, 'high' => Transaction::STATUS_FALSE_POSITIVE, 'low' => Transaction::STATUS_CLEARED, - default => Transaction::STATUS_FALSE_POSITIVE, // Default to high risk + default => Transaction::STATUS_FALSE_POSITIVE, }; // Core transaction data @@ -162,13 +175,19 @@ class TransformDataPoolToProduction implements ShouldQueue 'amount' => $firstOutput->tx_amount, 'currency' => $firstOutput->tx_currency ?? 'EUR', 'counterparty' => $firstOutput->corporate_counterparty, - 'counterparty_country' => $this->extractCountryCode($firstOutput->tx_country_incoming), - 'channel' => $firstOutput->source_file, + 'counterparty_country' => $this->extractCountryCode($firstOutput->tx_country_incoming, $firstOutput->corporate_counterparty), + 'counterparty_kyc_risk_level' => $riskLevel, + 'channel' => $this->determineTransactionType($firstOutput), 'executed_at' => $this->parseDate($firstOutput->tx_date), - 'risk_score' => $this->extractRiskScore($outputs), + 'risk_score' => $riskScore, 'status' => $status, - 'requires_review' => true, + 'requires_review' => $this->shouldRequireReview($riskLevel), 'flagged_reason' => $firstOutput->tx_purpose, + // Store backend risk data + 'entity_risk_breakdown' => $firstOutput->risk_details_json, + 'counterparty_risk_breakdown' => null, + 'transaction_plausibility_breakdown' => null, + 'combined_risk_breakdown' => null, ]; // Map all output_keys to their respective columns @@ -218,15 +237,29 @@ class TransformDataPoolToProduction implements ShouldQueue /** * Extract country code (convert to ISO 2-letter if needed) + * Also checks counterparty name for country indicators */ - private function extractCountryCode(?string $country): ?string + private function extractCountryCode(?string $country, ?string $counterpartyName = null): ?string { + // First, try to extract from counterparty name (highest priority for tax havens) + if (! empty($counterpartyName)) { + $extractedFromName = $this->extractCountryFromText($counterpartyName); + if ($extractedFromName) { + return $extractedFromName; + } + } + if (empty($country)) { return null; } - // If already 2 letters, return uppercase + // If already 2 letters, check if it's a suspicious/wrong code if (strlen($country) === 2) { + $suspiciousCodes = ['IO']; // British Indian Ocean Territory often wrong for Virgin Islands + if (in_array(strtoupper($country), $suspiciousCodes)) { + return null; // Reject suspicious codes + } + return strtoupper($country); } @@ -257,26 +290,77 @@ class TransformDataPoolToProduction implements ShouldQueue } /** - * Extract risk score from tranx_score output + * Extract country code from text (e.g., company name) */ - private function extractRiskScore($outputs): int + private function extractCountryFromText(string $text): ?string { - $scoreOutput = $outputs->firstWhere('output_key', 'tranx_score'); + $countryPatterns = [ + '/\b(?:British )?Virgin Islands\b/i' => 'VG', + '/\bCayman Islands\b/i' => 'KY', + '/\bBermuda\b/i' => 'BM', + '/\bBahamas\b/i' => 'BS', + '/\bPanama\b/i' => 'PA', + '/\bLiechtenstein\b/i' => 'LI', + '/\bMonaco\b/i' => 'MC', + '/\bLuxembourg\b/i' => 'LU', + '/\bSwitzerland\b/i' => 'CH', + '/\bAzerbaijan\b/i' => 'AZ', + '/\bRussia\b/i' => 'RU', + '/\bChina\b/i' => 'CN', + '/\bGermany\b/i' => 'DE', + ]; - if (! $scoreOutput) { - return 128; // Default: Medium risk (255/2) + foreach ($countryPatterns as $pattern => $code) { + if (preg_match($pattern, $text)) { + return $code; + } } - $scoreData = json_decode($scoreOutput->content, true); + return null; + } - if (is_array($scoreData) && isset($scoreData['score'])) { - // Assume score is 0-100, convert to 0-255 - $score = (int) $scoreData['score']; - - return (int) round(($score / 100) * 255); + /** + * Map backend risk_score (-100 to 100) to risk_level + * -100 to 20 = Critical + * 20 to 80 = High + * 80 to 100 = Low + */ + private function mapBackendRiskScoreToLevel(int $backendRiskScore): string + { + if ($backendRiskScore <= 20) { + return 'critical'; } - return 128; + if ($backendRiskScore <= 80) { + return 'high'; + } + + return 'low'; + } + + /** + * Map backend risk_score (-100 to 100) to 0-255 scale + */ + private function mapBackendRiskScoreTo255Scale(int $backendRiskScore): int + { + // Normalize -100 to 100 → 0 to 255 + // Formula: ((score + 100) / 200) * 255 + $normalized = (($backendRiskScore + 100) / 200) * 255; + + return (int) round(max(0, min(255, $normalized))); + } + + /** + * Determine if transaction requires review based on risk level + */ + private function shouldRequireReview(string $riskLevel): bool + { + return match ($riskLevel) { + 'critical' => true, + 'high' => true, + 'low' => false, + default => true, + }; } /** @@ -313,6 +397,71 @@ class TransformDataPoolToProduction implements ShouldQueue return $this->truncateString((string) $data, $maxLength); } + /** + * Extract a lean/concise field from outputs (extracts first sentence only) + */ + private function extractLeanField($outputs, string $outputKey, int $maxLength = 255): mixed + { + $fullText = $this->extractField($outputs, $outputKey, 5000); + + if (! $fullText) { + return null; + } + + // Extract first sentence or up to first period + $firstSentence = preg_split('/\.(?:\s|$)/', $fullText, 2)[0]; + + if ($firstSentence) { + $firstSentence = trim($firstSentence).'.'; + + return $this->truncateString($firstSentence, $maxLength); + } + + return $this->truncateString($fullText, $maxLength); + } + + /** + * Extract legal name from corporate name field + */ + private function extractLegalName($outputs, string $fallbackName): ?string + { + $fullText = $this->extractField($outputs, 'entity_corporate_name', 5000); + + if (! $fullText) { + return $fallbackName; + } + + // Pattern 1: "is exactly as shown in an official register: [NAME]" + if (preg_match('/is exactly as shown in an official register:\s*([A-Z][^.,]+(?:AG|SE|GmbH|Inc\.|Ltd\.|Corp\.|N\.V\.|S\.A\.|Aktiengesellschaft))/i', $fullText, $matches)) { + return trim($matches[1]); + } + + // Pattern 2: "is [NAME]" - matches various patterns like "legal name is NAME", "company is NAME" + if (preg_match('/(?:legal name|name|company) (?:of .+ )?is\s+([A-Z][^.,]+(?:AG|SE|GmbH|Inc\.|Ltd\.|Corp\.|N\.V\.|S\.A\.|Aktiengesellschaft))/i', $fullText, $matches)) { + $name = trim($matches[1]); + // Remove trailing explanatory text + $name = preg_replace('/\s+(?:is exactly|exactly as shown|with former|former names).*$/i', '', $name); + + return trim($name); + } + + // Pattern 3: "of [NAME]" - matches "legal name of Deutsche Bank AG" + if (preg_match('/legal name of\s+([A-Z][^.,]+(?:AG|SE|GmbH|Inc\.|Ltd\.|Corp\.|N\.V\.|S\.A\.|Aktiengesellschaft))/i', $fullText, $matches)) { + $name = trim($matches[1]); + $name = preg_replace('/\s+(?:is exactly|with former).*$/i', '', $name); + + return trim($name); + } + + // Pattern 4: Extract company name with suffix at start of sentence + if (preg_match('/^(?:The current legal name (?:of|is) )?([A-Z][^.,]+(?:AG|SE|GmbH|Inc\.|Ltd\.|Corp\.|N\.V\.|S\.A\.|Aktiengesellschaft))/i', $fullText, $matches)) { + return trim($matches[1]); + } + + // If no pattern matches, use the fallback name + return $fallbackName; + } + /** * Truncate string to max length */ @@ -322,7 +471,7 @@ class TransformDataPoolToProduction implements ShouldQueue return $text; } - return substr($text, 0, $maxLength - 3) . '...'; + return substr($text, 0, $maxLength - 3).'...'; } /** @@ -343,6 +492,98 @@ class TransformDataPoolToProduction implements ShouldQueue } } + /** + * Generate a meaningful transaction reference + */ + private function generateTransactionReference($firstOutput): string + { + // Try to extract reference from raw_payload + if (! empty($firstOutput->raw_payload)) { + $payload = json_decode($firstOutput->raw_payload, true); + + // Look for common reference fields + $referenceFields = [ + 'Transaction Reference', + 'Reference', + 'Transaction ID', + 'Payment Reference', + 'Transfer Reference', + ]; + + foreach ($referenceFields as $field) { + if (isset($payload[$field]) && ! empty($payload[$field])) { + return (string) $payload[$field]; + } + } + } + + // Generate a date-based reference if no reference found + $date = $this->parseDate($firstOutput->tx_date); + $dateStr = $date ? $date->format('Ymd') : date('Ymd'); + + return sprintf('TXN-%s-%05d', $dateStr, $firstOutput->transaction_id); + } + + /** + * Determine transaction type from available data + */ + private function determineTransactionType($firstOutput): string + { + // Try to determine from raw_payload first + if (! empty($firstOutput->raw_payload)) { + $payload = json_decode($firstOutput->raw_payload, true); + + // Check for explicit transaction type field + if (isset($payload['Transaction Type']) && ! empty($payload['Transaction Type'])) { + return $payload['Transaction Type']; + } + + if (isset($payload['Payment Type']) && ! empty($payload['Payment Type'])) { + return $payload['Payment Type']; + } + } + + // Determine based on amount and countries + $amount = (float) $firstOutput->tx_amount; + $isInternational = $firstOutput->tx_country_outgoing !== $firstOutput->tx_country_incoming; + + // Large international transactions are likely SWIFT + if ($isInternational && $amount > 100000) { + return 'SWIFT Transfer'; + } + + // European transfers + if ($isInternational && $this->isEuropeanCountry($firstOutput->tx_country_incoming)) { + return 'SEPA Transfer'; + } + + // Domestic high-value + if (! $isInternational && $amount > 50000) { + return 'Wire Transfer'; + } + + // Default + return 'Bank Transfer'; + } + + /** + * Check if country is in Europe + */ + private function isEuropeanCountry(?string $countryCode): bool + { + if (empty($countryCode)) { + return false; + } + + $europeanCountries = [ + 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', + 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', + 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH', 'NO', 'IS', + ]; + + return in_array(strtoupper($countryCode), $europeanCountries); + } + /** * Check if column exists in transactions table */ diff --git a/app/Services/KycRiskCalculator.php b/app/Services/KycRiskCalculator.php index 6b0bb51..163f9ed 100644 --- a/app/Services/KycRiskCalculator.php +++ b/app/Services/KycRiskCalculator.php @@ -12,49 +12,138 @@ class KycRiskCalculator * KYC Risk Level Schwellenwerte (3 Levels) */ private const RISK_THRESHOLDS = [ - 'low' => 40, // 0-40 = Geringes Risiko - 'high' => 70, // 41-70 = Hohes Risiko - 'critical' => 100, // 71-100 = Kritisches Risiko + '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.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 + '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 + * Berechne KYC Risk Level basierend auf Transaction Outputs (Legacy - verwendet Entity) * - * @param Collection $outputs Collection von backend_data_pool records für eine Transaction + * @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) + 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 'high'; + return [ + 'risk_level' => 'high', + 'weighted_score' => 70.0, + 'scores' => [], + ]; } - // 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), + '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 $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, + ]; } /** @@ -89,13 +178,13 @@ class KycRiskCalculator /** * Berechne Sanctions Score (0-100) */ - private function calculateSanctionsScore(array $outputs): int + private function calculateSanctionsScore(array $outputs, string $prefix = ''): int { $sanctionFlags = [ - 'corporate_eusanctions', - 'corporate_ofacsanctions', - 'corporate_uksanctions', - 'sanctions_circumvention', + $prefix.'corporate_eusanctions', + $prefix.'corporate_ofacsanctions', + $prefix.'corporate_uksanctions', + $prefix.'sanctions_circumvention', ]; $score = 0; @@ -136,42 +225,239 @@ class KycRiskCalculator /** * Berechne Country Risk Score (0-100) */ - private function calculateCountryRiskScore(array $outputs): int + private function calculateCountryRiskScore(array $outputs, string $prefix = ''): int { - if (! isset($outputs['country_risk'])) { - return 40; // Default: Geringes Risiko + $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']); + } + } } - $countryRiskData = json_decode($outputs['country_risk'], true); + // FALLBACK: Versuche Land aus HQ oder anderen Feldern zu extrahieren + $countryCode = $this->extractCountryCodeFromOutputs($outputs, $prefix); - if (is_array($countryRiskData) && isset($countryRiskData['score'])) { - return min(100, max(0, (int) $countryRiskData['score'])); + if ($countryCode) { + return $this->getCountryRiskScoreByCode($countryCode); } - if (is_array($countryRiskData) && isset($countryRiskData['level'])) { - return $this->mapRiskLevelToScore($countryRiskData['level']); + 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; + } + } + } } - if (is_bool($countryRiskData)) { - return $countryRiskData ? 80 : 20; + // 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; + } + } + } } - return 40; + // 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): int + private function calculatePepAdverseScore(array $outputs, string $prefix = ''): int { $score = 0; $count = 0; $pepFields = [ - 'corporate_pepexposure' => 75, - 'corporate_pep' => 80, - 'corporate_adverse' => 70, - 'corporate_AMLexposure' => 85, + $prefix.'corporate_pepexposure' => 75, + $prefix.'corporate_pep' => 80, + $prefix.'corporate_adverse' => 70, + $prefix.'corporate_AMLexposure' => 85, ]; foreach ($pepFields as $field => $defaultScore) { @@ -181,9 +467,59 @@ class KycRiskCalculator $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; + } + } + } } } @@ -193,13 +529,13 @@ class KycRiskCalculator /** * Berechne Corruption Score (0-100) */ - private function calculateCorruptionScore(array $outputs): int + private function calculateCorruptionScore(array $outputs, string $prefix = ''): int { $corruptionFields = [ - 'corruption_sector', - 'corruption_country', - 'corruption_relationship', - 'corporate_corruptionexposure', + $prefix.'corruption_sector', + $prefix.'corruption_country', + $prefix.'corruption_relationship', + $prefix.'corporate_corruptionexposure', ]; $totalScore = 0; @@ -283,7 +619,8 @@ class KycRiskCalculator return (isset($data['matches']) && ! empty($data['matches'])) || (isset($data['found']) && $data['found'] === true) - || (isset($data['sanctioned']) && $data['sanctioned'] === true); + || (isset($data['sanctioned']) && $data['sanctioned'] === true) + || (isset($data['flag']) && $data['flag'] === true); // WICHTIG: Flag-Check hinzugefügt } private function hasPositiveMatch($data): bool @@ -292,7 +629,8 @@ class KycRiskCalculator 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['exposure']) && $data['exposure'] === true) + || (isset($data['flag']) && $data['flag'] === true); // WICHTIG: Flag-Check hinzugefügt } if (is_bool($data)) { @@ -315,6 +653,256 @@ class KycRiskCalculator 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 */ @@ -363,4 +951,221 @@ class KycRiskCalculator // 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); + } } diff --git a/database/migrations/2025_11_18_110420_add_entity_and_counterparty_columns_to_transactions_table.php b/database/migrations/2025_11_18_110420_add_entity_and_counterparty_columns_to_transactions_table.php new file mode 100644 index 0000000..5f2de6b --- /dev/null +++ b/database/migrations/2025_11_18_110420_add_entity_and_counterparty_columns_to_transactions_table.php @@ -0,0 +1,299 @@ +jsonb('entity_corporate_summary')->nullable(); + $table->jsonb('entity_corporate_history')->nullable(); + $table->jsonb('entity_corporate_purpose')->nullable(); + $table->jsonb('entity_corporate_sector')->nullable(); + $table->jsonb('entity_corporate_nace')->nullable(); + $table->jsonb('entity_corporate_products')->nullable(); + $table->jsonb('entity_corporate_markets')->nullable(); + $table->jsonb('entity_corporate_supply')->nullable(); + $table->jsonb('entity_corporate_name')->nullable(); + $table->jsonb('entity_corporate_form')->nullable(); + $table->jsonb('entity_corporate_forum')->nullable(); + $table->jsonb('entity_corporate_HQ')->nullable(); + $table->jsonb('entity_corporate_locations')->nullable(); + $table->jsonb('entity_corporate_holding')->nullable(); + $table->jsonb('entity_corporate_shareholders')->nullable(); + $table->jsonb('entity_corporate_board')->nullable(); + $table->jsonb('entity_corporate_supervisory')->nullable(); + $table->jsonb('entity_corporate_powerofattorney')->nullable(); + $table->jsonb('entity_corporate_taxID')->nullable(); + $table->jsonb('entity_corporate_LEI')->nullable(); + $table->jsonb('entity_corporate_UBO')->nullable(); + $table->jsonb('entity_corporate_employeecount')->nullable(); + $table->jsonb('entity_corporate_turnover')->nullable(); + $table->jsonb('entity_corporate_EBIT')->nullable(); + $table->jsonb('entity_corporate_netprofits')->nullable(); + $table->jsonb('entity_corporate_balancesheet')->nullable(); + $table->jsonb('entity_corporate_auditfindings')->nullable(); + $table->jsonb('entity_corporate_insolvency')->nullable(); + $table->jsonb('entity_corporate_liquidation')->nullable(); + $table->jsonb('entity_corporate_adhoc')->nullable(); + $table->jsonb('entity_corporate_pressrelease')->nullable(); + $table->jsonb('entity_corporate_votes')->nullable(); + $table->jsonb('entity_corporate_directordealings')->nullable(); + $table->jsonb('entity_corporate_brands')->nullable(); + $table->jsonb('entity_corporate_website')->nullable(); + $table->jsonb('entity_corporate_domain')->nullable(); + $table->jsonb('entity_corporate_IBAN')->nullable(); + $table->jsonb('entity_corporate_solvency')->nullable(); + $table->jsonb('entity_corporate_rating')->nullable(); + $table->jsonb('entity_corporate_ESG')->nullable(); + $table->jsonb('entity_corporate_license')->nullable(); + $table->jsonb('entity_corporate_warnings')->nullable(); + $table->jsonb('entity_corporate_eusanctions')->nullable(); + $table->jsonb('entity_corporate_ofacsanctions')->nullable(); + $table->jsonb('entity_corporate_uksanctions')->nullable(); + $table->jsonb('entity_corporate_pepexposure')->nullable(); + $table->jsonb('entity_corporate_adversemediascanning')->nullable(); + $table->jsonb('entity_corporate_corruptionexposure')->nullable(); + $table->jsonb('entity_corporate_AMLexposure')->nullable(); + $table->jsonb('entity_corporate_CTYexposure')->nullable(); + $table->jsonb('entity_corporate_adverse')->nullable(); + $table->jsonb('entity_corporate_pep')->nullable(); + $table->jsonb('entity_corporate_adverse2')->nullable(); + $table->jsonb('entity_corporate_exportcontrol')->nullable(); + $table->jsonb('entity_corporate_mediamatch')->nullable(); + $table->jsonb('entity_corporate_haven')->nullable(); + $table->jsonb('entity_corporate_insiders')->nullable(); + $table->jsonb('entity_corporate_courtcases')->nullable(); + $table->jsonb('entity_corporate_manda')->nullable(); + $table->jsonb('entity_corporate_pepdetail')->nullable(); + + // Counterparty Corporate Information + $table->jsonb('counterparty_corporate_summary')->nullable(); + $table->jsonb('counterparty_corporate_history')->nullable(); + $table->jsonb('counterparty_corporate_purpose')->nullable(); + $table->jsonb('counterparty_corporate_sector')->nullable(); + $table->jsonb('counterparty_corporate_nace')->nullable(); + $table->jsonb('counterparty_corporate_products')->nullable(); + $table->jsonb('counterparty_corporate_markets')->nullable(); + $table->jsonb('counterparty_corporate_supply')->nullable(); + $table->jsonb('counterparty_corporate_name')->nullable(); + $table->jsonb('counterparty_corporate_form')->nullable(); + $table->jsonb('counterparty_corporate_forum')->nullable(); + $table->jsonb('counterparty_corporate_HQ')->nullable(); + $table->jsonb('counterparty_corporate_locations')->nullable(); + $table->jsonb('counterparty_corporate_holding')->nullable(); + $table->jsonb('counterparty_corporate_shareholders')->nullable(); + $table->jsonb('counterparty_corporate_board')->nullable(); + $table->jsonb('counterparty_corporate_supervisory')->nullable(); + $table->jsonb('counterparty_corporate_powerofattorney')->nullable(); + $table->jsonb('counterparty_corporate_taxID')->nullable(); + $table->jsonb('counterparty_corporate_LEI')->nullable(); + $table->jsonb('counterparty_corporate_UBO')->nullable(); + $table->jsonb('counterparty_corporate_employeecount')->nullable(); + $table->jsonb('counterparty_corporate_turnover')->nullable(); + $table->jsonb('counterparty_corporate_EBIT')->nullable(); + $table->jsonb('counterparty_corporate_netprofits')->nullable(); + $table->jsonb('counterparty_corporate_balancesheet')->nullable(); + $table->jsonb('counterparty_corporate_auditfindings')->nullable(); + $table->jsonb('counterparty_corporate_insolvency')->nullable(); + $table->jsonb('counterparty_corporate_liquidation')->nullable(); + $table->jsonb('counterparty_corporate_adhoc')->nullable(); + $table->jsonb('counterparty_corporate_pressrelease')->nullable(); + $table->jsonb('counterparty_corporate_votes')->nullable(); + $table->jsonb('counterparty_corporate_directordealings')->nullable(); + $table->jsonb('counterparty_corporate_brands')->nullable(); + $table->jsonb('counterparty_corporate_website')->nullable(); + $table->jsonb('counterparty_corporate_domain')->nullable(); + $table->jsonb('counterparty_corporate_IBAN')->nullable(); + $table->jsonb('counterparty_corporate_solvency')->nullable(); + $table->jsonb('counterparty_corporate_rating')->nullable(); + $table->jsonb('counterparty_corporate_ESG')->nullable(); + $table->jsonb('counterparty_corporate_license')->nullable(); + $table->jsonb('counterparty_corporate_warnings')->nullable(); + $table->jsonb('counterparty_corporate_eusanctions')->nullable(); + $table->jsonb('counterparty_corporate_ofacsanctions')->nullable(); + $table->jsonb('counterparty_corporate_uksanctions')->nullable(); + $table->jsonb('counterparty_corporate_pepexposure')->nullable(); + $table->jsonb('counterparty_corporate_adversemediascanning')->nullable(); + $table->jsonb('counterparty_corporate_corruptionexposure')->nullable(); + $table->jsonb('counterparty_corporate_AMLexposure')->nullable(); + $table->jsonb('counterparty_corporate_CTYexposure')->nullable(); + $table->jsonb('counterparty_corporate_adverse')->nullable(); + $table->jsonb('counterparty_corporate_pep')->nullable(); + $table->jsonb('counterparty_corporate_adverse2')->nullable(); + $table->jsonb('counterparty_corporate_exportcontrol')->nullable(); + $table->jsonb('counterparty_corporate_mediamatch')->nullable(); + $table->jsonb('counterparty_corporate_haven')->nullable(); + $table->jsonb('counterparty_corporate_insiders')->nullable(); + $table->jsonb('counterparty_corporate_courtcases')->nullable(); + $table->jsonb('counterparty_corporate_manda')->nullable(); + $table->jsonb('counterparty_corporate_pepdetail')->nullable(); + + // Entity Risk Assessment + $table->jsonb('entity_sanctions_circumvention')->nullable(); + $table->jsonb('entity_corruption_sector')->nullable(); + $table->jsonb('entity_corruption_country')->nullable(); + $table->jsonb('entity_corruption_relationship')->nullable(); + $table->jsonb('entity_country_risk')->nullable(); + + // Counterparty Risk Assessment + $table->jsonb('counterparty_sanctions_circumvention')->nullable(); + $table->jsonb('counterparty_corruption_sector')->nullable(); + $table->jsonb('counterparty_corruption_country')->nullable(); + $table->jsonb('counterparty_corruption_relationship')->nullable(); + $table->jsonb('counterparty_country_risk')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->dropColumn([ + // Entity Corporate Information + 'entity_corporate_summary', + 'entity_corporate_history', + 'entity_corporate_purpose', + 'entity_corporate_sector', + 'entity_corporate_nace', + 'entity_corporate_products', + 'entity_corporate_markets', + 'entity_corporate_supply', + 'entity_corporate_name', + 'entity_corporate_form', + 'entity_corporate_forum', + 'entity_corporate_HQ', + 'entity_corporate_locations', + 'entity_corporate_holding', + 'entity_corporate_shareholders', + 'entity_corporate_board', + 'entity_corporate_supervisory', + 'entity_corporate_powerofattorney', + 'entity_corporate_taxID', + 'entity_corporate_LEI', + 'entity_corporate_UBO', + 'entity_corporate_employeecount', + 'entity_corporate_turnover', + 'entity_corporate_EBIT', + 'entity_corporate_netprofits', + 'entity_corporate_balancesheet', + 'entity_corporate_auditfindings', + 'entity_corporate_insolvency', + 'entity_corporate_liquidation', + 'entity_corporate_adhoc', + 'entity_corporate_pressrelease', + 'entity_corporate_votes', + 'entity_corporate_directordealings', + 'entity_corporate_brands', + 'entity_corporate_website', + 'entity_corporate_domain', + 'entity_corporate_IBAN', + 'entity_corporate_solvency', + 'entity_corporate_rating', + 'entity_corporate_ESG', + 'entity_corporate_license', + 'entity_corporate_warnings', + 'entity_corporate_eusanctions', + 'entity_corporate_ofacsanctions', + 'entity_corporate_uksanctions', + 'entity_corporate_pepexposure', + 'entity_corporate_adversemediascanning', + 'entity_corporate_corruptionexposure', + 'entity_corporate_AMLexposure', + 'entity_corporate_CTYexposure', + 'entity_corporate_adverse', + 'entity_corporate_pep', + 'entity_corporate_adverse2', + 'entity_corporate_exportcontrol', + 'entity_corporate_mediamatch', + 'entity_corporate_haven', + 'entity_corporate_insiders', + 'entity_corporate_courtcases', + 'entity_corporate_manda', + 'entity_corporate_pepdetail', + // Counterparty Corporate Information + 'counterparty_corporate_summary', + 'counterparty_corporate_history', + 'counterparty_corporate_purpose', + 'counterparty_corporate_sector', + 'counterparty_corporate_nace', + 'counterparty_corporate_products', + 'counterparty_corporate_markets', + 'counterparty_corporate_supply', + 'counterparty_corporate_name', + 'counterparty_corporate_form', + 'counterparty_corporate_forum', + 'counterparty_corporate_HQ', + 'counterparty_corporate_locations', + 'counterparty_corporate_holding', + 'counterparty_corporate_shareholders', + 'counterparty_corporate_board', + 'counterparty_corporate_supervisory', + 'counterparty_corporate_powerofattorney', + 'counterparty_corporate_taxID', + 'counterparty_corporate_LEI', + 'counterparty_corporate_UBO', + 'counterparty_corporate_employeecount', + 'counterparty_corporate_turnover', + 'counterparty_corporate_EBIT', + 'counterparty_corporate_netprofits', + 'counterparty_corporate_balancesheet', + 'counterparty_corporate_auditfindings', + 'counterparty_corporate_insolvency', + 'counterparty_corporate_liquidation', + 'counterparty_corporate_adhoc', + 'counterparty_corporate_pressrelease', + 'counterparty_corporate_votes', + 'counterparty_corporate_directordealings', + 'counterparty_corporate_brands', + 'counterparty_corporate_website', + 'counterparty_corporate_domain', + 'counterparty_corporate_IBAN', + 'counterparty_corporate_solvency', + 'counterparty_corporate_rating', + 'counterparty_corporate_ESG', + 'counterparty_corporate_license', + 'counterparty_corporate_warnings', + 'counterparty_corporate_eusanctions', + 'counterparty_corporate_ofacsanctions', + 'counterparty_corporate_uksanctions', + 'counterparty_corporate_pepexposure', + 'counterparty_corporate_adversemediascanning', + 'counterparty_corporate_corruptionexposure', + 'counterparty_corporate_AMLexposure', + 'counterparty_corporate_CTYexposure', + 'counterparty_corporate_adverse', + 'counterparty_corporate_pep', + 'counterparty_corporate_adverse2', + 'counterparty_corporate_exportcontrol', + 'counterparty_corporate_mediamatch', + 'counterparty_corporate_haven', + 'counterparty_corporate_insiders', + 'counterparty_corporate_courtcases', + 'counterparty_corporate_manda', + 'counterparty_corporate_pepdetail', + // Entity Risk Assessment + 'entity_sanctions_circumvention', + 'entity_corruption_sector', + 'entity_corruption_country', + 'entity_corruption_relationship', + 'entity_country_risk', + // Counterparty Risk Assessment + 'counterparty_sanctions_circumvention', + 'counterparty_corruption_sector', + 'counterparty_corruption_country', + 'counterparty_corruption_relationship', + 'counterparty_country_risk', + ]); + }); + } +}; diff --git a/database/migrations/2025_11_18_134018_add_counterparty_kyc_risk_level_to_transactions_table.php b/database/migrations/2025_11_18_134018_add_counterparty_kyc_risk_level_to_transactions_table.php new file mode 100644 index 0000000..3c21150 --- /dev/null +++ b/database/migrations/2025_11_18_134018_add_counterparty_kyc_risk_level_to_transactions_table.php @@ -0,0 +1,28 @@ +string('counterparty_kyc_risk_level', 32)->nullable()->after('status'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->dropColumn('counterparty_kyc_risk_level'); + }); + } +}; diff --git a/database/migrations/2025_11_18_165725_add_risk_breakdown_columns_to_transactions_table.php b/database/migrations/2025_11_18_165725_add_risk_breakdown_columns_to_transactions_table.php new file mode 100644 index 0000000..f7d3437 --- /dev/null +++ b/database/migrations/2025_11_18_165725_add_risk_breakdown_columns_to_transactions_table.php @@ -0,0 +1,43 @@ +jsonb('entity_risk_breakdown')->nullable()->after('counterparty_kyc_risk_level'); + + // Counterparty risk breakdown (receiver) + $table->jsonb('counterparty_risk_breakdown')->nullable()->after('entity_risk_breakdown'); + + // Transaction plausibility breakdown + $table->jsonb('transaction_plausibility_breakdown')->nullable()->after('counterparty_risk_breakdown'); + + // Combined risk breakdown + $table->jsonb('combined_risk_breakdown')->nullable()->after('transaction_plausibility_breakdown'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('transactions', function (Blueprint $table) { + $table->dropColumn([ + 'entity_risk_breakdown', + 'counterparty_risk_breakdown', + 'transaction_plausibility_breakdown', + 'combined_risk_breakdown', + ]); + }); + } +}; diff --git a/database/migrations/2025_11_19_110500_add_risk_fields_to_backend_data_pool_table.php b/database/migrations/2025_11_19_110500_add_risk_fields_to_backend_data_pool_table.php new file mode 100644 index 0000000..172c0f8 --- /dev/null +++ b/database/migrations/2025_11_19_110500_add_risk_fields_to_backend_data_pool_table.php @@ -0,0 +1,31 @@ +integer('risk_score')->nullable()->after('last_modified_at'); + $table->text('risk_level')->nullable()->after('risk_score'); + $table->text('risk_details_json')->nullable()->after('risk_level'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('backend_data_pool', function (Blueprint $table) { + $table->dropColumn(['risk_score', 'risk_level', 'risk_details_json']); + }); + } +}; diff --git a/misc/database_table_schemes/backend_transaction_outputs_output_key_.csv b/misc/database_table_schemes/backend_transaction_outputs_output_key_.csv new file mode 100644 index 0000000..f9fb946 --- /dev/null +++ b/misc/database_table_schemes/backend_transaction_outputs_output_key_.csv @@ -0,0 +1,167 @@ +"output_key" +"counterparty_corporate_summary" +"entity_corporate_summary" +"entity_corporate_history" +"counterparty_corporate_history" +"entity_corporate_purpose" +"counterparty_corporate_purpose" +"entity_corporate_sector" +"counterparty_corporate_sector" +"counterparty_corporate_nace" +"entity_corporate_nace" +"entity_corporate_products" +"counterparty_corporate_products" +"entity_corporate_markets" +"counterparty_corporate_markets" +"counterparty_corporate_supply" +"entity_corporate_supply" +"entity_corporate_name" +"counterparty_corporate_name" +"entity_corporate_form" +"counterparty_corporate_form" +"counterparty_corporate_forum" +"entity_corporate_forum" +"counterparty_corporate_HQ" +"entity_corporate_HQ" +"entity_corporate_locations" +"counterparty_corporate_locations" +"entity_corporate_holding" +"counterparty_corporate_holding" +"counterparty_corporate_shareholders" +"entity_corporate_shareholders" +"entity_corporate_board" +"counterparty_corporate_board" +"counterparty_corporate_supervisory" +"entity_corporate_supervisory" +"counterparty_corporate_powerofattorney" +"entity_corporate_powerofattorney" +"counterparty_corporate_taxID" +"entity_corporate_taxID" +"counterparty_corporate_LEI" +"entity_corporate_LEI" +"counterparty_corporate_UBO" +"entity_corporate_UBO" +"counterparty_corporate_employeecount" +"entity_corporate_employeecount" +"entity_corporate_turnover" +"counterparty_corporate_turnover" +"counterparty_corporate_EBIT" +"entity_corporate_EBIT" +"entity_corporate_netprofits" +"counterparty_corporate_netprofits" +"entity_corporate_balancesheet" +"counterparty_corporate_balancesheet" +"entity_corporate_auditfindings" +"counterparty_corporate_auditfindings" +"counterparty_corporate_insolvency" +"entity_corporate_insolvency" +"counterparty_corporate_liquidation" +"entity_corporate_liquidation" +"entity_corporate_adhoc" +"counterparty_corporate_adhoc" +"entity_corporate_pressrelease" +"counterparty_corporate_pressrelease" +"entity_corporate_votes" +"counterparty_corporate_votes" +"entity_corporate_directordealings" +"counterparty_corporate_directordealings" +"entity_corporate_brands" +"counterparty_corporate_brands" +"entity_corporate_website" +"counterparty_corporate_website" +"entity_corporate_domain" +"counterparty_corporate_domain" +"counterparty_corporate_IBAN" +"entity_corporate_IBAN" +"entity_corporate_solvency" +"counterparty_corporate_solvency" +"counterparty_corporate_rating" +"entity_corporate_rating" +"entity_corporate_ESG" +"counterparty_corporate_ESG" +"counterparty_corporate_license" +"entity_corporate_license" +"counterparty_corporate_warnings" +"entity_corporate_warnings" +"entity_corporate_eusanctions" +"counterparty_corporate_eusanctions" +"counterparty_corporate_ofacsanctions" +"entity_corporate_ofacsanctions" +"entity_corporate_uksanctions" +"counterparty_corporate_uksanctions" +"counterparty_corporate_pepexposure" +"entity_corporate_pepexposure" +"entity_corporate_adversemediascanning" +"counterparty_corporate_adversemediascanning" +"entity_corporate_corruptionexposure" +"counterparty_corporate_corruptionexposure" +"entity_corporate_AMLexposure" +"counterparty_corporate_AMLexposure" +"counterparty_corporate_CTYexposure" +"entity_corporate_CTYexposure" +"tranx_TX_AMOUNT" +"tranx_historical" +"tranx_TX_PURPOSE" +"tranx_counterpartyassessment" +"tranx_context" +"tranx_report" +"tranx_PURPOSEbase" +"tranx_performanceperiod" +"tranx_seasonality" +"tranx_resolution" +"trans_IBANvalidation" +"tranx_businesslogic" +"source_domaincheck" +"tranx_plausibilty" +"tranx_outliers" +"tranx_patterns" +"tranx_revenueimpact" +"tranx_profitimpact" +"tranx_marketimpact" +"source_check" +"source_approveuniqueID" +"counterparty_sanctions_circumvention" +"entity_sanctions_circumvention" +"entity_corruption_sector" +"counterparty_corruption_sector" +"counterparty_corruption_country" +"entity_corruption_country" +"counterparty_corruption_relationship" +"entity_corruption_relationship" +"counterparty_corporate_adverse" +"entity_corporate_adverse" +"counterparty_corporate_pep" +"entity_corporate_pep" +"entity_corporate_adverse2" +"counterparty_corporate_adverse2" +"tranx_duplicate" +"tranx_pattern2" +"counterparty_corporate_exportcontrol" +"entity_corporate_exportcontrol" +"counterparty_corporate_mediamatch" +"entity_corporate_mediamatch" +"tranx_contracttenor" +"entity_corporate_haven" +"counterparty_corporate_haven" +"tranx_mismatch" +"entity_country_risk" +"counterparty_country_risk" +"corporate_haven2" +"counterparty_corporate_insiders" +"entity_corporate_insiders" +"counterparty_corporate_courtcases" +"entity_corporate_courtcases" +"counterparty_corporate_manda" +"entity_corporate_manda" +"tranx_fakepurposecheck" +"tranx_structuring" +"tranx_newbankaccount" +"counterparty_corporate_pepdetail" +"entity_corporate_pepdetail" +"tranx_weekday" +"tranx_holdingobfuscation" +"source_randomplausibility" +"tranx_score" +"tranx_reasoning" +"tranx_recommendation" +"tranx_sourcerepository" diff --git a/resources/views/livewire/transaction-review.blade.php b/resources/views/livewire/transaction-review.blade.php index e46b38a..4e25937 100644 --- a/resources/views/livewire/transaction-review.blade.php +++ b/resources/views/livewire/transaction-review.blade.php @@ -535,28 +535,28 @@ Transaction::STATUS_CLEARED => [

Kerndaten Unternehmen​