1011 lines
37 KiB
Markdown
1011 lines
37 KiB
Markdown
# 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:
|
||
|
|
|
||
|
|
<details>
|
||
|
|
<summary>Vollständige Liste der 102 JSONB Spalten (klicken zum Ausklappen)</summary>
|
||
|
|
|
||
|
|
**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
|
||
|
|
</details>
|
||
|
|
|
||
|
|
#### 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
|
||
|
|
<?php
|
||
|
|
|
||
|
|
use Livewire\Volt\Component;
|
||
|
|
use App\Models\Company;
|
||
|
|
|
||
|
|
new class extends Component {
|
||
|
|
public string $search = '';
|
||
|
|
|
||
|
|
public function with(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'companies' => Company::query()
|
||
|
|
->when($this->search, fn($q) =>
|
||
|
|
$q->where('name', 'ilike', "%{$this->search}%")
|
||
|
|
)
|
||
|
|
->limit(20)
|
||
|
|
->get(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
?>
|
||
|
|
|
||
|
|
<div>
|
||
|
|
<flux:input wire:model.live.debounce.300ms="search" />
|
||
|
|
<!-- UI -->
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 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
|