# System-Architektur: Risk Intelligence Platform ## πŸ“‹ Inhaltsverzeichnis 1. [Übersicht](#ΓΌbersicht) 2. [System-Diagramm](#system-diagramm) 3. [Technologie-Stack](#technologie-stack) 4. [Datenbank-Architektur](#datenbank-architektur) 5. [Datenfluss & ETL-Pipeline](#datenfluss--etl-pipeline) 6. [Applikations-Schichten](#applikations-schichten) 7. [Frontend-Architektur](#frontend-architektur) 8. [Backend-Integration](#backend-integration) 9. [Scheduling & Automation](#scheduling--automation) 10. [Sicherheit & Authentication](#sicherheit--authentication) 11. [Deployment-Architektur](#deployment-architektur) --- ## Übersicht Die **Risk Intelligence Platform** ist eine Laravel-basierte Web-Applikation zur Analyse und Bewertung von Unternehmenstransaktionen hinsichtlich KYC (Know Your Customer) und Compliance-Risiken. ### Kernfunktionen - βœ… **Unternehmenssuche & -bewertung** mit KYC Risk Levels - βœ… **Transaktionsanalyse** mit 139 Datenfeldern (37 Core + 102 JSONB) - βœ… **Automatische Backend-Synchronisation** (ETL-Pipeline) - βœ… **Risk Score Berechnung** basierend auf Multi-Faktoren-Analyse - βœ… **Compliance-Checks** (EU/OFAC/UK Sanctions, PEP, AML) - βœ… **Benutzer-Authentication** mit 2FA-UnterstΓΌtzung ### Zielgruppe - Compliance-Teams - Risk Analysts - KYC-Spezialisten --- ## System-Diagramm ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FRONTEND (Browser) β”‚ β”‚ Livewire Volt Components + Flux UI β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LARAVEL APPLICATION β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Controllers β”‚ β”‚ Livewire Volt β”‚ β”‚ API Endpoints β”‚ β”‚ β”‚ β”‚ (Minimal) β”‚ β”‚ Components β”‚ β”‚ (Future) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β–Ό β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Eloquent Models & Repositories β”‚ β”‚ β”‚ β”‚ Company β”‚ Transaction β”‚ User β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β–Ό β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Jobs/Queue β”‚ β”‚ Services β”‚ β”‚ β”‚ β”‚ - SyncDataPool β”‚ β”‚ - KycRiskCalc β”‚ β”‚ β”‚ β”‚ - Transform β”‚ β”‚ - DataExtract β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ POSTGRESQL DATABASE (Port 5433) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Schema: PUBLIC (Default Connection) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ companies β”‚ β”‚ transactions β”‚ β”‚ backend_data_ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ (139 cols) β”‚ β”‚ pool β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - name │◄── - company_id β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - kyc_risk β”‚ β”‚ - risk_score β”‚ β”‚ - transaction_ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - sector β”‚ β”‚ - 102 JSONB colsβ”‚ β”‚ id β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - output_key β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - content β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Schema: BACKEND (Backend Connection) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ transactions β”‚ β”‚ transaction_outputs β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - id │◄── - transaction_id β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - status β”‚ β”‚ - prompt_id β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - tx_amount β”‚ β”‚ - output_key β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ - risk_score β”‚ β”‚ - content (JSONB) β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β”‚ β”‚ (ETL Pipeline - Scheduled Jobs) β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LARAVEL SCHEDULER (Cron) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Every 6h (0:00, 6:00, 12:00, 18:00): β”‚ β”‚ β”‚ β”‚ β†’ SyncBackendDataPool (Incremental) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Every 6h (0:30, 6:30, 12:30, 18:30): β”‚ β”‚ β”‚ β”‚ β†’ TransformDataPoolToProduction β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Sunday 3:00: β”‚ β”‚ β”‚ β”‚ β†’ SyncBackendDataPool (Full Rebuild) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## Technologie-Stack ### Backend | Komponente | Version | Zweck | |-----------|---------|-------| | **PHP** | 8.4.14 | Runtime | | **Laravel Framework** | 12.x | Application Framework | | **PostgreSQL** | Latest | PrimΓ€re Datenbank | | **Laravel Fortify** | 1.x | Authentication | | **Livewire** | 3.x | Reactive Components | | **Livewire Volt** | 1.x | Single-File Components | ### Frontend | Komponente | Version | Zweck | |-----------|---------|-------| | **Flux UI** | 2.x (Free) | Component Library | | **Tailwind CSS** | 4.x | Styling Framework | | **Alpine.js** | Latest (via Livewire) | JavaScript Interactivity | | **Vite** | Latest | Asset Bundling | ### Development & Testing | Komponente | Version | Zweck | |-----------|---------|-------| | **Pest** | 4.x | Testing Framework | | **PHPUnit** | 12.x | Unit Testing | | **Laravel Pint** | 1.x | Code Formatting | | **Laravel Sail** | 1.x | Docker Development | | **Laravel Boost** | 1.4+ | MCP Server (Development) | ### Infrastructure - **Docker** - Containerization - **Kubernetes** - Orchestration (k8s-cronjob.yaml) - **Cron** - Scheduling (Laravel Scheduler) - **Queue System** - Asynchronous Jobs --- ## Datenbank-Architektur ### Verbindungen Die Applikation nutzt **eine PostgreSQL-Datenbank** mit **zwei Schemas**: ```php // config/database.php 'default' => 'pgsql_second', // Default: public schema 'connections' => [ // Frontend Schema (Production Tables) 'pgsql_second' => [ 'search_path' => 'public', 'port' => env('DB_PORT2', 5432), 'database' => env('DB_DATABASE2'), ], // Backend Schema (Source Data) 'backend' => [ 'search_path' => 'backend', 'port' => env('DB_PORT2', 5432), 'database' => env('DB_DATABASE2'), // Same DB, different schema ], ] ``` ### Schema: PUBLIC (Production) #### Companies Tabelle ```sql CREATE TABLE public.companies ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, legal_name VARCHAR(255), ticker VARCHAR(50), sector VARCHAR(100), country VARCHAR(2), -- ISO 3166-1 alpha-2 headquarters TEXT, kyc_risk_level VARCHAR(20), -- low | high | critical summary TEXT, created_at TIMESTAMP, updated_at TIMESTAMP ); ``` #### Transactions Tabelle (139 Spalten) **Core Felder (37):** ```sql CREATE TABLE public.transactions ( -- Identity & Relationships id SERIAL PRIMARY KEY, company_id INTEGER REFERENCES companies(id), reference VARCHAR(255) UNIQUE NOT NULL, -- Transaction Details amount DECIMAL(15,2), currency VARCHAR(3), counterparty VARCHAR(255), counterparty_country VARCHAR(2), channel VARCHAR(50), executed_at TIMESTAMP, -- Risk Assessment risk_score INTEGER, -- 0-255 normalized status VARCHAR(50), -- true_positive | false_positive | cleared requires_review BOOLEAN, flagged_by VARCHAR(255), flagged_reason TEXT, signals JSONB, -- Additional Entity Data entity VARCHAR(255), -- Corporate entity name counterparty_kyc_risk_level VARCHAR(20), -- Risk Breakdown transaction_risk_score INTEGER, sanctions_risk_score INTEGER, country_risk_score INTEGER, pep_adverse_risk_score INTEGER, corruption_risk_score INTEGER, -- Timestamps created_at TIMESTAMP, updated_at TIMESTAMP, -- ... 102 JSONB Columns (siehe unten) ); ``` **JSONB Output Columns (102):** Alle `output_key` Werte aus `backend.transaction_outputs` werden als JSONB-Spalten gespeichert:
VollstΓ€ndige Liste der 102 JSONB Spalten (klicken zum Ausklappen) **Corporate Information:** - `corporate_summary` - `corporate_history` - `corporate_legalname` - `corporate_sector` - `corporate_HQ` - `corporate_keyexecs` - `corporate_shareholding` - `corporate_parentcompany` - `corporate_subsidiaries` - `corporate_countryofincorporation` - `corporate_ticker` - `corporate_counterparty` - `corporate_counterpartysector` - `corporate_counterpartycountry` - `corporate_counterpartysummary` - `corporate_counterpartyhistory` **Sanctions & Compliance:** - `corporate_eusanctions` - `corporate_ofacsanctions` - `corporate_uksanctions` - `corporate_pepexposure` - `corporate_AMLexposure` - `corporate_adverse` - `sanctions_circumvention` **Country Risk:** - `country_risk` - `country_name` - `country_iso2` - `country_iso3` - `country_cpi` - `country_euprivacy` - `country_dataprivacy` - `country_gdpr` - `country_sanctions` - `country_wgi` - `country_pep` **Corruption:** - `corruption_sector` - `corruption_country` - `corruption_relationship` **Financial & Transaction:** - `tranx_score` - `tranx_reasoning` - `tranx_amount` - `tranx_currency` - `tranx_date` - `tranx_counterparty` - `tranx_channel` - `financial_assessment` ... und weitere 60+ Spalten fΓΌr detaillierte Analysen
#### Backend Data Pool Tabelle ```sql CREATE TABLE public.backend_data_pool ( id SERIAL PRIMARY KEY, -- Foreign Keys (Backend Schema) transaction_id INTEGER NOT NULL, prompt_id INTEGER NOT NULL, -- Transaction Core Data corporate_entity VARCHAR(255), tx_amount DECIMAL(15,2), tx_currency VARCHAR(3), tx_date DATE, status VARCHAR(50), -- Risk Scores risk_score INTEGER, -- Backend risk_score (-100 to 100) -- Output Data output_key VARCHAR(255), content JSONB, -- Metadata synced_at TIMESTAMP DEFAULT NOW(), UNIQUE(transaction_id, prompt_id) ); CREATE INDEX idx_backend_data_pool_transaction_id ON backend_data_pool(transaction_id); CREATE INDEX idx_backend_data_pool_output_key ON backend_data_pool(output_key); ``` ### Schema: BACKEND (Source Data) #### Backend Transactions ```sql CREATE TABLE backend.transactions ( id SERIAL PRIMARY KEY, corporate_entity VARCHAR(255), tx_amount DECIMAL(15,2), tx_currency VARCHAR(3), tx_date DATE, status VARCHAR(50), -- pending | processing | done | failed risk_score INTEGER, -- -100 to 100 (Backend Scale) last_modified_at TIMESTAMP, created_at TIMESTAMP ); ``` #### Backend Transaction Outputs ```sql CREATE TABLE backend.transaction_outputs ( id SERIAL PRIMARY KEY, transaction_id INTEGER REFERENCES transactions(id), prompt_id INTEGER, output_key VARCHAR(255), -- z.B. "corporate_summary", "tranx_score" content JSONB, -- LLM-Generated Output created_at TIMESTAMP, UNIQUE(transaction_id, prompt_id) ); ``` --- ## Datenfluss & ETL-Pipeline ### Dreistufiger Datenprozess ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ STUFE 0: Backend Processing (Extern) β”‚ β”‚ β”‚ β”‚ LLM Analysis β†’ backend.transactions β”‚ β”‚ β†’ backend.transaction_outputs β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ STUFE 1: Data Pool Sync β”‚ β”‚ β”‚ β”‚ Job: SyncBackendDataPool β”‚ β”‚ Schedule: Every 6h (Incremental) | Sunday 3am (Full) β”‚ β”‚ β”‚ β”‚ Source: backend.transactions β”‚ β”‚ + backend.transaction_outputs β”‚ β”‚ β”‚ β”‚ Target: public.backend_data_pool β”‚ β”‚ β”‚ β”‚ Process: β”‚ β”‚ 1. Query backend schema (WHERE status='done') β”‚ β”‚ 2. Join transactions + outputs β”‚ β”‚ 3. Batch insert into data pool (1000 records/batch) β”‚ β”‚ 4. Track sync timestamp β”‚ β”‚ β”‚ β”‚ Performance: β”‚ β”‚ - Full Sync: ~30-60s (7,471 records) β”‚ β”‚ - Incremental: ~5-10s (new records only) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ STUFE 2: Production Transformation β”‚ β”‚ β”‚ β”‚ Job: TransformDataPoolToProduction β”‚ β”‚ Schedule: Every 6h at :30 (30min after Sync) β”‚ β”‚ β”‚ β”‚ Source: public.backend_data_pool β”‚ β”‚ β”‚ β”‚ Targets: public.companies β”‚ β”‚ + public.transactions β”‚ β”‚ β”‚ β”‚ Process: β”‚ β”‚ 1. Group by transaction_id β”‚ β”‚ 2. For each transaction: β”‚ β”‚ a) Extract/Update Company (by corporate_entity) β”‚ β”‚ - Calculate KYC Risk Level β”‚ β”‚ - Enrich with sector, country, HQ β”‚ β”‚ b) Create/Update Transaction β”‚ β”‚ - Map all 139 columns β”‚ β”‚ - Link to company_id β”‚ β”‚ - Transform risk_score (-100..100 β†’ 0..255) β”‚ β”‚ 3. Batch process (100 records/batch) β”‚ β”‚ β”‚ β”‚ Features: β”‚ β”‚ - Idempotent (updateOrCreate) β”‚ β”‚ - Deduplication by reference β”‚ β”‚ - Risk Score Normalization β”‚ β”‚ β”‚ β”‚ Performance: β”‚ β”‚ - Initial: ~30-60s (74 transactions β†’ ~70 companies) β”‚ β”‚ - Updates: ~5-10s β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ STUFE 3: Frontend Display (Real-time) β”‚ β”‚ β”‚ β”‚ Livewire Volt Components query: β”‚ β”‚ - public.companies β”‚ β”‚ - public.transactions β”‚ β”‚ β”‚ β”‚ No additional processing required β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Risk Score Transformation Backend nutzt eine Skala von **-100 bis 100**, Frontend **0 bis 255**: ```php // Backend β†’ Frontend Normalization $normalizedScore = (($backendScore + 100) / 200) * 255; // Beispiele: // Backend: -100 β†’ Frontend: 0 (Sehr gut) // Backend: 0 β†’ Frontend: 127 (Neutral) // Backend: 100 β†’ Frontend: 255 (Sehr schlecht) ``` **Risk Level Mapping:** | Backend Score | Frontend Score | Risk Level | Transaction Status | |--------------|----------------|------------|-------------------| | -100 to 20 | 0 to 153 | `low` | `cleared` | | 20 to 80 | 153 to 229 | `high` | `false_positive` | | 80 to 100 | 229 to 255 | `critical` | `true_positive` | --- ## Applikations-Schichten ### Layer Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PRESENTATION LAYER β”‚ β”‚ - Livewire Volt Components (resources/views/ β”‚ β”‚ livewire/) β”‚ β”‚ - Blade Templates β”‚ β”‚ - Flux UI Components β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ APPLICATION LAYER β”‚ β”‚ - Controllers (minimal, mostly Volt) β”‚ β”‚ - Livewire Actions (app/Livewire/Actions/) β”‚ β”‚ - Form Requests (validation) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ DOMAIN LAYER β”‚ β”‚ - Eloquent Models (app/Models/) β”‚ β”‚ Β· Company β”‚ β”‚ Β· Transaction β”‚ β”‚ Β· User β”‚ β”‚ Β· Backend\Transaction β”‚ β”‚ Β· Backend\TransactionOutput β”‚ β”‚ - Repositories (app/Repositories/) β”‚ β”‚ Β· TransactionRepository β”‚ β”‚ - Services (app/Services/) β”‚ β”‚ Β· KycRiskCalculator β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ INFRASTRUCTURE LAYER β”‚ β”‚ - Jobs (app/Jobs/) β”‚ β”‚ Β· SyncBackendDataPool β”‚ β”‚ Β· TransformDataPoolToProduction β”‚ β”‚ - Commands (app/Console/Commands/) β”‚ β”‚ Β· SyncBackendDataPoolCommand β”‚ β”‚ Β· TransformDataPoolCommand β”‚ β”‚ Β· RebuildFromDataPoolCommand β”‚ β”‚ - Database Migrations β”‚ β”‚ - Schedulers (routes/console.php) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## Frontend-Architektur ### Livewire Volt Components Die Applikation nutzt **Single-File Components** mit Livewire Volt: ```php // resources/views/livewire/company-search.blade.php Company::query() ->when($this->search, fn($q) => $q->where('name', 'ilike', "%{$this->search}%") ) ->limit(20) ->get(), ]; } } ?>
``` ### Routen-Struktur ```php // routes/web.php // Public Route::get('/', fn() => view('welcome'))->name('home'); // Authenticated Routes Route::middleware(['auth', 'verified'])->group(function () { Route::view('dashboard', 'dashboard')->name('dashboard'); // Volt Routes Volt::route('company-search', 'company-search')->name('company-search'); Volt::route('transaction-review', 'transaction-review')->name('transaction-review'); Volt::route('companies/{company}/transactions', 'companies.transactions') ->name('company.transactions'); Volt::route('upload', 'upload.index')->name('upload'); // Settings Volt::route('settings/profile', 'settings.profile')->name('profile.edit'); Volt::route('settings/password', 'settings.password')->name('password.edit'); Volt::route('settings/two-factor', 'settings.two-factor') ->middleware(['password.confirm']) ->name('two-factor.show'); }); ``` ### Hauptseiten | Route | Component | Beschreibung | |-------|-----------|--------------| | `/` | welcome.blade.php | Landing Page | | `/dashboard` | dashboard.blade.php | Dashboard-Übersicht | | `/company-search` | company-search.blade.php | Unternehmenssuche | | `/transaction-review` | transaction-review.blade.php | TransaktionsΓΌbersicht | | `/companies/{id}/transactions` | companies.transactions.blade.php | Company-Detail mit Transactions | | `/upload` | upload.index.blade.php | Upload-FunktionalitΓ€t | --- ## Backend-Integration ### Models & Relationships ```php // app/Models/Company.php class Company extends Model { protected $fillable = [ 'name', 'legal_name', 'ticker', 'sector', 'country', 'headquarters', 'kyc_risk_level', 'summary', ]; public function transactions(): HasMany { return $this->hasMany(Transaction::class); } } // app/Models/Transaction.php class Transaction extends Model { protected $guarded = []; // Allow all (for 102 JSONB columns) // Dynamic JSONB casting protected function casts(): array { $allColumns = Schema::getColumnListing('transactions'); $jsonbColumns = array_diff($allColumns, $standardColumns); foreach ($jsonbColumns as $column) { $casts[$column] = 'array'; } return $casts; } public function company(): BelongsTo { return $this->belongsTo(Company::class); } } ``` ### Jobs #### SyncBackendDataPool ```php // app/Jobs/SyncBackendDataPool.php class SyncBackendDataPool implements ShouldQueue { public function __construct( public bool $fullSync = false, public int $batchSize = 1000, ) {} public function handle(): void { if ($this->fullSync) { DB::table('backend_data_pool')->truncate(); } $query = DB::connection('pgsql') ->table('backend.transactions as t') ->join('backend.transaction_outputs as tout', 't.id', '=', 'tout.transaction_id') ->where('t.status', 'done') ->select([ 't.id as transaction_id', 'tout.prompt_id', 't.corporate_entity', 't.tx_amount', 't.tx_currency', 't.tx_date', 't.status', 't.risk_score', 'tout.output_key', 'tout.content', ]); $query->chunk($this->batchSize, function ($records) { DB::table('backend_data_pool')->insert( $records->map(fn($r) => [ ...(array) $r, 'synced_at' => now(), ])->toArray() ); }); } } ``` #### TransformDataPoolToProduction ```php // app/Jobs/TransformDataPoolToProduction.php class TransformDataPoolToProduction implements ShouldQueue { public function handle(): void { $transactionIds = DB::table('backend_data_pool') ->distinct() ->pluck('transaction_id'); foreach ($transactionIds as $transactionId) { $outputs = DB::table('backend_data_pool') ->where('transaction_id', $transactionId) ->get(); $company = $this->createOrUpdateCompany($outputs); $this->createOrUpdateTransaction($company, $outputs); } } private function createOrUpdateCompany($outputs): Company { $first = $outputs->first(); return Company::updateOrCreate( ['name' => $first->corporate_entity], [ 'sector' => $this->extractValue($outputs, 'corporate_sector'), 'country' => $this->extractValue($outputs, 'corporate_countryofincorporation'), 'headquarters' => $this->extractValue($outputs, 'corporate_HQ'), 'kyc_risk_level' => $this->calculateKycRiskLevel($first->risk_score), 'summary' => $this->extractValue($outputs, 'corporate_summary'), ] ); } } ``` ### Commands ```bash # Manuelle Sync-Commands php artisan backend:sync-data-pool --full php artisan backend:sync-data-pool --incremental php artisan backend:transform-data-pool php artisan backend:rebuild # Full Sync + Transform ``` --- ## Scheduling & Automation ### Laravel Scheduler Configuration ```php // routes/console.php // Incremental Sync: 0:00, 6:00, 12:00, 18:00 Schedule::job(new SyncBackendDataPool(fullSync: false, batchSize: 1000)) ->everySixHours() ->name('sync-backend-data-pool-incremental') ->withoutOverlapping(1800); // Full Sync: Sonntag 3:00 Uhr Schedule::job(new SyncBackendDataPool(fullSync: true, batchSize: 1000)) ->weeklyOn(0, '03:00') ->name('sync-backend-data-pool-full') ->withoutOverlapping(3600); // Transformation: 0:30, 6:30, 12:30, 18:30 Schedule::job(new TransformDataPoolToProduction(batchSize: 100)) ->cron('30 */6 * * *') ->name('transform-data-pool-to-production') ->withoutOverlapping(1800); ``` ### Aktivierung #### Lokale Entwicklung ```bash php artisan schedule:work ``` #### Produktion (Crontab) ```bash crontab -e # FΓΌge hinzu: * * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1 ``` #### Mit Supervisor ```ini [program:laravel-scheduler] command=php /path/to/project/artisan schedule:work autostart=true autorestart=true ``` --- ## Sicherheit & Authentication ### Laravel Fortify Die Applikation nutzt **Laravel Fortify** fΓΌr Authentication: ```php // config/fortify.php - Enabled Features Features::registration(), Features::resetPasswords(), Features::emailVerification(), Features::updateProfileInformation(), Features::updatePasswords(), Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, ]), ``` ### Two-Factor Authentication (2FA) - QR-Code basierte 2FA mit TOTP - Recovery Codes - Passwort-BestΓ€tigung erforderlich ### Authorization - Route Middleware: `auth`, `verified` - Password Confirmation fΓΌr sensible Aktionen ### Session Management - Standard Laravel Session-Driver - CSRF Protection aktiviert --- ## Deployment-Architektur ### Docker Setup ```yaml # docker-compose.yml services: app: build: context: . dockerfile: Dockerfile ports: - "8000:8000" volumes: - .:/var/www/html environment: - DB_CONNECTION2=pgsql - DB_HOST2=db - DB_PORT2=5432 ``` ### Kubernetes CronJob ```yaml # k8s-cronjob.yaml apiVersion: batch/v1 kind: CronJob metadata: name: backend-sync spec: schedule: "0 */6 * * *" # Every 6 hours jobTemplate: spec: template: spec: containers: - name: sync image: risk-platform:latest command: ["php", "artisan", "backend:sync-data-pool", "--incremental"] ``` ### Environment Configuration ```bash # .env (Production) APP_ENV=production APP_DEBUG=false DB_CONNECTION2=pgsql DB_HOST2=postgres.internal DB_PORT2=5432 DB_DATABASE2=risk_platform_db DB_USERNAME2=risk_user DB_PASSWORD2=*** CACHE_STORE=redis QUEUE_CONNECTION=redis SESSION_DRIVER=redis ``` ### Scaling Considerations **Horizontal Scaling:** - Stateless Laravel App β†’ Load Balancer β†’ Multiple App Instances - Shared Redis fΓΌr Sessions/Cache - Shared PostgreSQL Cluster **Performance Optimization:** - Redis Caching fΓΌr Companies/Transactions - Database Indexing (transaction_id, output_key, company_id) - Query Optimization mit Eager Loading - CDN fΓΌr Static Assets --- ## Monitoring & Logging ### Laravel Logs ```bash # Application Logs tail -f storage/logs/laravel.log # Nur Sync-Logs tail -f storage/logs/laravel.log | grep SyncBackendDataPool # Nur Transform-Logs tail -f storage/logs/laravel.log | grep TransformDataPoolToProduction ``` ### Queue Monitoring ```bash php artisan queue:work --tries=3 php artisan queue:monitor ``` ### Database Performance Monitoring ```sql -- Sync-Performance (Records pro Minute) SELECT DATE_TRUNC('minute', synced_at) as minute, COUNT(*) as records_synced FROM public.backend_data_pool WHERE synced_at >= NOW() - INTERVAL '1 hour' GROUP BY DATE_TRUNC('minute', synced_at) ORDER BY minute DESC; ``` ### Optional: Laravel Telescope ```bash composer require laravel/telescope --dev php artisan telescope:install php artisan migrate ``` --- ## Zusammenfassung ### System-Eigenschaften βœ… **Monolithische Laravel-Applikation** mit klarer Layer-Trennung βœ… **ETL-Pipeline** mit zweistufiger Transformation (Sync β†’ Transform) βœ… **Automatisierung** via Laravel Scheduler (alle 6h) βœ… **Reactive Frontend** mit Livewire Volt + Flux UI βœ… **Skalierbar** via Queue-System und horizontaler App-Skalierung βœ… **Sicher** mit Fortify Authentication + 2FA βœ… **Testbar** mit Pest Testing Framework ### Performance-Metriken | Operation | Datenmenge | Dauer | Frequenz | |-----------|-----------|-------|----------| | Incremental Sync | ~100-500 Records | 5-10s | Alle 6h | | Full Sync | 7,471 Records | 30-60s | Sonntag 3am | | Transformation | 74 Transactions | 30-60s | Alle 6h (30min nach Sync) | | Page Load (Companies) | 20 Records | <100ms | On Demand | ### NΓ€chste Schritte - [ ] API-Dokumentation (OpenAPI/Swagger) - [ ] Testing-Guide & Coverage-Ziele - [ ] Deployment-Runbooks - [ ] Entwickler-Onboarding-Guide - [ ] Performance-Optimierung (Caching-Strategie) --- **Erstellt:** 2025-11-24 **Version:** 1.0 **Autor:** Risk Intelligence Platform Team