1154 lines
32 KiB
Markdown
1154 lines
32 KiB
Markdown
# Datenmodell-Dokumentation: Risk Intelligence Platform
|
||||
|
|
|
|||
|
|
## 📋 Inhaltsverzeichnis
|
|||
|
|
|
|||
|
|
1. [Übersicht](#übersicht)
|
|||
|
|
2. [Entity-Relationship-Diagramm](#entity-relationship-diagramm)
|
|||
|
|
3. [Schema-Architektur](#schema-architektur)
|
|||
|
|
4. [Production Models (PUBLIC Schema)](#production-models-public-schema)
|
|||
|
|
5. [Backend Models (BACKEND Schema)](#backend-models-backend-schema)
|
|||
|
|
6. [ETL-Modell (Data Pool)](#etl-modell-data-pool)
|
|||
|
|
7. [Beziehungen & Constraints](#beziehungen--constraints)
|
|||
|
|
8. [Indizes & Performance](#indizes--performance)
|
|||
|
|
9. [JSONB-Spalten](#jsonb-spalten)
|
|||
|
|
10. [Business-Logik](#business-logik)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Übersicht
|
|||
|
|
|
|||
|
|
Die Risk Intelligence Platform nutzt ein **Multi-Schema PostgreSQL-Design** mit klarer Trennung zwischen:
|
|||
|
|
|
|||
|
|
- **PUBLIC Schema** - Production Tables (Companies, Transactions, Users)
|
|||
|
|
- **BACKEND Schema** - Source Data vom KI-Backend (Transactions, Outputs)
|
|||
|
|
- **ETL Layer** - Backend Data Pool als Zwischenspeicher
|
|||
|
|
|
|||
|
|
### Datenbankverbindungen
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
'pgsql_second' => [
|
|||
|
|
'search_path' => 'public', // Default Connection
|
|||
|
|
],
|
|||
|
|
|
|||
|
|
'backend' => [
|
|||
|
|
'search_path' => 'backend', // Backend Connection
|
|||
|
|
],
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Wichtig:** Beide Connections nutzen **dieselbe Datenbank**, aber unterschiedliche Schemas.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Entity-Relationship-Diagramm
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ PUBLIC SCHEMA (Production) │
|
|||
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
┌──────────────────┐
|
|||
|
|
│ users │
|
|||
|
|
├──────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ name │
|
|||
|
|
│ email │ UNIQUE
|
|||
|
|
│ password │
|
|||
|
|
│ email_verified_at│
|
|||
|
|
│ two_factor_secret│
|
|||
|
|
│ two_factor_codes │
|
|||
|
|
│ remember_token │
|
|||
|
|
│ created_at │
|
|||
|
|
│ updated_at │
|
|||
|
|
└──────────────────┘
|
|||
|
|
|
|||
|
|
|
|||
|
|
┌──────────────────┐
|
|||
|
|
│ companies │
|
|||
|
|
├──────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ name │ UNIQUE
|
|||
|
|
│ legal_name │
|
|||
|
|
│ ticker │
|
|||
|
|
│ sector │
|
|||
|
|
│ country │ (ISO 3166-1 alpha-2)
|
|||
|
|
│ headquarters │
|
|||
|
|
│ kyc_risk_level │ (low|high|critical)
|
|||
|
|
│ summary │
|
|||
|
|
│ created_at │
|
|||
|
|
│ updated_at │
|
|||
|
|
└────────┬─────────┘
|
|||
|
|
│
|
|||
|
|
│ 1:N (One Company has Many Transactions)
|
|||
|
|
│
|
|||
|
|
┌────────▼─────────┐
|
|||
|
|
│ transactions │
|
|||
|
|
├──────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ company_id │ FK → companies.id (CASCADE DELETE)
|
|||
|
|
│ reference │ UNIQUE (e.g., "MIGRATED-1")
|
|||
|
|
│ │
|
|||
|
|
│ ─── Core ─────── │
|
|||
|
|
│ amount │ DECIMAL(16,2)
|
|||
|
|
│ currency │ (3 chars, default: EUR)
|
|||
|
|
│ counterparty │
|
|||
|
|
│ counterparty_country │
|
|||
|
|
│ channel │
|
|||
|
|
│ executed_at │ DATETIME
|
|||
|
|
│ │
|
|||
|
|
│ ─── Risk ─────── │
|
|||
|
|
│ risk_score │ TINYINT (0-255)
|
|||
|
|
│ status │ (true_positive|false_positive|cleared)
|
|||
|
|
│ requires_review │ BOOLEAN
|
|||
|
|
│ flagged_by │
|
|||
|
|
│ flagged_reason │
|
|||
|
|
│ signals │ JSON
|
|||
|
|
│ │
|
|||
|
|
│ ─── Entity ───── │
|
|||
|
|
│ entity │ Corporate entity name
|
|||
|
|
│ counterparty_kyc_risk_level │
|
|||
|
|
│ │
|
|||
|
|
│ ─── Risk Breakdown ─── │
|
|||
|
|
│ transaction_risk_score │
|
|||
|
|
│ sanctions_risk_score │
|
|||
|
|
│ country_risk_score │
|
|||
|
|
│ pep_adverse_risk_score │
|
|||
|
|
│ corruption_risk_score │
|
|||
|
|
│ │
|
|||
|
|
│ ─── External Data (JSONB) ─── │
|
|||
|
|
│ registry_data │
|
|||
|
|
│ genesis_context │
|
|||
|
|
│ govdata_data │
|
|||
|
|
│ bundesanzeiger_data │
|
|||
|
|
│ insolvency_data │
|
|||
|
|
│ rss_alerts │
|
|||
|
|
│ sanctions_data │
|
|||
|
|
│ pep_data │
|
|||
|
|
│ gleif_data │
|
|||
|
|
│ eu_sanctions_data│
|
|||
|
|
│ handelsregister_data │
|
|||
|
|
│ │
|
|||
|
|
│ ─── 102 JSONB Output Columns ─── │
|
|||
|
|
│ corporate_summary│ JSONB
|
|||
|
|
│ corporate_history│ JSONB
|
|||
|
|
│ tranx_score │ JSONB
|
|||
|
|
│ ... (99 weitere) │
|
|||
|
|
│ │
|
|||
|
|
│ created_at │
|
|||
|
|
│ updated_at │
|
|||
|
|
└──────────────────┘
|
|||
|
|
|
|||
|
|
|
|||
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ ETL LAYER (PUBLIC Schema) │
|
|||
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
┌──────────────────────────┐
|
|||
|
|
│ backend_data_pool │
|
|||
|
|
├──────────────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ │
|
|||
|
|
│ ─── From backend.transactions ─── │
|
|||
|
|
│ transaction_id │ INDEX
|
|||
|
|
│ corporate_entity │
|
|||
|
|
│ corporate_counterparty │
|
|||
|
|
│ tx_date │
|
|||
|
|
│ tx_amount │
|
|||
|
|
│ tx_currency │
|
|||
|
|
│ tx_purpose │
|
|||
|
|
│ tx_country_outgoing │
|
|||
|
|
│ tx_country_incoming │
|
|||
|
|
│ source_file │
|
|||
|
|
│ raw_payload │
|
|||
|
|
│ status │ INDEX
|
|||
|
|
│ created_at │
|
|||
|
|
│ last_modified_at │
|
|||
|
|
│ │
|
|||
|
|
│ ─── From backend.transaction_outputs ─── │
|
|||
|
|
│ prompt_id │
|
|||
|
|
│ output_key │ INDEX
|
|||
|
|
│ content │ TEXT (JSON)
|
|||
|
|
│ run_id │
|
|||
|
|
│ │
|
|||
|
|
│ ─── Metadata ───── │
|
|||
|
|
│ synced_at │ INDEX
|
|||
|
|
│ updated_at │
|
|||
|
|
│ │
|
|||
|
|
│ UNIQUE(transaction_id, prompt_id) │
|
|||
|
|
└──────────────────────────┘
|
|||
|
|
|
|||
|
|
|
|||
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ BACKEND SCHEMA (Source Data) │
|
|||
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
┌──────────────────────┐
|
|||
|
|
│ backend.transactions │
|
|||
|
|
├──────────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ corporate_entity │
|
|||
|
|
│ corporate_counterparty│
|
|||
|
|
│ tx_date │
|
|||
|
|
│ tx_amount │
|
|||
|
|
│ tx_currency │
|
|||
|
|
│ tx_purpose │
|
|||
|
|
│ tx_country_outgoing │
|
|||
|
|
│ tx_country_incoming │
|
|||
|
|
│ source_file │
|
|||
|
|
│ raw_payload │
|
|||
|
|
│ status │ (pending|processing|done|failed)
|
|||
|
|
│ risk_score │ INTEGER (-100 to 100)
|
|||
|
|
│ created_at │
|
|||
|
|
│ last_modified_at │
|
|||
|
|
└──────────┬───────────┘
|
|||
|
|
│
|
|||
|
|
│ 1:N (One Transaction has Many Outputs)
|
|||
|
|
│
|
|||
|
|
┌──────────▼──────────────────┐
|
|||
|
|
│ backend.transaction_outputs │
|
|||
|
|
├─────────────────────────────┤
|
|||
|
|
│ id │ PK
|
|||
|
|
│ transaction_id │ FK → backend.transactions.id
|
|||
|
|
│ prompt_id │
|
|||
|
|
│ output_key │ VARCHAR (e.g., "corporate_summary")
|
|||
|
|
│ content │ JSONB (LLM-Generated)
|
|||
|
|
│ run_id │
|
|||
|
|
│ created_at │
|
|||
|
|
│ │
|
|||
|
|
│ UNIQUE(transaction_id, prompt_id) │
|
|||
|
|
└─────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Schema-Architektur
|
|||
|
|
|
|||
|
|
### 1. PUBLIC Schema (Production)
|
|||
|
|
|
|||
|
|
**Zweck:** Produktive Applikationsdaten
|
|||
|
|
**Models:**
|
|||
|
|
- `Company` - Unternehmensprofile mit KYC Risk Level
|
|||
|
|
- `Transaction` - Transaktionsanalysen mit 139 Spalten
|
|||
|
|
- `User` - Benutzeraccounts
|
|||
|
|
- `BackendDataPool` - ETL Zwischenspeicher
|
|||
|
|
|
|||
|
|
**Besonderheit:** Alle produktiven Daten, optimiert für Frontend-Zugriff
|
|||
|
|
|
|||
|
|
### 2. BACKEND Schema (Source)
|
|||
|
|
|
|||
|
|
**Zweck:** KI-Backend Source Data (Read-Only aus Frontend-Sicht)
|
|||
|
|
**Models:**
|
|||
|
|
- `Backend\Transaction` - Rohdaten vom KI-System
|
|||
|
|
- `Backend\TransactionOutput` - LLM-generierte Analysen
|
|||
|
|
|
|||
|
|
**Besonderheit:** Wird nur gelesen, nie geschrieben (außer vom Backend-System)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Production Models (PUBLIC Schema)
|
|||
|
|
|
|||
|
|
### Company Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `public.companies`
|
|||
|
|
**Model:** `App\Models\Company`
|
|||
|
|
|
|||
|
|
#### Struktur
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE public.companies (
|
|||
|
|
id SERIAL PRIMARY KEY,
|
|||
|
|
name VARCHAR(255) UNIQUE NOT NULL,
|
|||
|
|
legal_name VARCHAR(255),
|
|||
|
|
ticker VARCHAR(50),
|
|||
|
|
sector VARCHAR(100),
|
|||
|
|
country VARCHAR(2) DEFAULT 'DE', -- ISO 3166-1 alpha-2
|
|||
|
|
headquarters TEXT,
|
|||
|
|
kyc_risk_level VARCHAR(20) DEFAULT 'medium',
|
|||
|
|
summary TEXT,
|
|||
|
|
created_at TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Felder-Erklärung
|
|||
|
|
|
|||
|
|
| Feld | Typ | Beschreibung | Beispiel |
|
|||
|
|
|------|-----|--------------|----------|
|
|||
|
|
| `id` | SERIAL | Primary Key | `1` |
|
|||
|
|
| `name` | VARCHAR(255) | Firmenname (Unique) | `"Mercedes-Benz Group AG"` |
|
|||
|
|
| `legal_name` | VARCHAR(255) | Offizieller rechtlicher Name | `"Mercedes-Benz Group Aktiengesellschaft"` |
|
|||
|
|
| `ticker` | VARCHAR(50) | Börsen-Ticker | `"MBG"` |
|
|||
|
|
| `sector` | VARCHAR(100) | Branche | `"Automotive"` |
|
|||
|
|
| `country` | VARCHAR(2) | Land (ISO 3166-1 alpha-2) | `"DE"` |
|
|||
|
|
| `headquarters` | TEXT | Hauptsitz | `"Stuttgart, Germany"` |
|
|||
|
|
| `kyc_risk_level` | VARCHAR(20) | KYC Risiko-Level | `"low"`, `"high"`, `"critical"` |
|
|||
|
|
| `summary` | TEXT | Zusammenfassung | `"German automobile manufacturer..."` |
|
|||
|
|
|
|||
|
|
#### KYC Risk Levels
|
|||
|
|
|
|||
|
|
| Level | Beschreibung | Bedeutung |
|
|||
|
|
|-------|--------------|-----------|
|
|||
|
|
| `low` | Geringes Risiko | Unauffälliges Unternehmen, Standard-KYC |
|
|||
|
|
| `high` | Hohes Risiko | Erhöhte Due Diligence erforderlich |
|
|||
|
|
| `critical` | Kritisches Risiko | Sanctions/PEP/AML Flags, intensive Prüfung |
|
|||
|
|
|
|||
|
|
#### Relationships
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class Company extends Model
|
|||
|
|
{
|
|||
|
|
// 1:N - Eine Company hat viele Transactions
|
|||
|
|
public function transactions(): HasMany
|
|||
|
|
{
|
|||
|
|
return $this->hasMany(Transaction::class);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Constraints & Indizes
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Unique Constraint auf Name
|
|||
|
|
ALTER TABLE companies ADD CONSTRAINT companies_name_unique UNIQUE (name);
|
|||
|
|
|
|||
|
|
-- Index auf country für geografische Queries
|
|||
|
|
CREATE INDEX idx_companies_country ON companies(country);
|
|||
|
|
|
|||
|
|
-- Index auf kyc_risk_level für Risiko-Filtering
|
|||
|
|
CREATE INDEX idx_companies_kyc_risk_level ON companies(kyc_risk_level);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Transaction Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `public.transactions`
|
|||
|
|
**Model:** `App\Models\Transaction`
|
|||
|
|
|
|||
|
|
#### Struktur (139 Spalten)
|
|||
|
|
|
|||
|
|
Die Transaction-Tabelle ist in mehrere logische Gruppen unterteilt:
|
|||
|
|
|
|||
|
|
##### Core Fields (31 Spalten)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Identity & Relationships
|
|||
|
|
id SERIAL PRIMARY KEY
|
|||
|
|
company_id INTEGER REFERENCES companies(id) ON DELETE CASCADE
|
|||
|
|
reference VARCHAR(255) UNIQUE
|
|||
|
|
|
|||
|
|
-- Transaction Details
|
|||
|
|
amount DECIMAL(16,2)
|
|||
|
|
currency VARCHAR(3) DEFAULT 'EUR'
|
|||
|
|
counterparty VARCHAR(255)
|
|||
|
|
counterparty_country VARCHAR(2)
|
|||
|
|
channel VARCHAR(50)
|
|||
|
|
executed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
-- Risk Assessment
|
|||
|
|
risk_score TINYINT(0-255)
|
|||
|
|
status VARCHAR(32) -- true_positive | false_positive | cleared
|
|||
|
|
requires_review BOOLEAN DEFAULT true
|
|||
|
|
flagged_by VARCHAR(255)
|
|||
|
|
flagged_reason TEXT
|
|||
|
|
signals JSON
|
|||
|
|
|
|||
|
|
-- Entity Information
|
|||
|
|
entity VARCHAR(255)
|
|||
|
|
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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### External Data Sources (11 JSONB Spalten)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Registry & Official Data
|
|||
|
|
registry_data JSONB
|
|||
|
|
registry_company_number TEXT
|
|||
|
|
registry_source TEXT
|
|||
|
|
registry_match_score DOUBLE
|
|||
|
|
registry_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
-- Government & Public Data
|
|||
|
|
genesis_context JSONB
|
|||
|
|
genesis_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
govdata_data JSONB
|
|||
|
|
govdata_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
bundesanzeiger_data JSONB
|
|||
|
|
bundesanzeiger_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
handelsregister_data JSONB
|
|||
|
|
handelsregister_last_refreshed_at TIMESTAMP
|
|||
|
|
handelsregister_status TEXT
|
|||
|
|
handelsregister_entity_id BIGINT
|
|||
|
|
|
|||
|
|
-- Compliance Data
|
|||
|
|
insolvency_data JSONB
|
|||
|
|
insolvency_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
sanctions_data JSONB
|
|||
|
|
sanctions_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
eu_sanctions_data JSONB
|
|||
|
|
eu_sanctions_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
pep_data JSONB
|
|||
|
|
pep_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
-- GLEIF (Legal Entity Identifier)
|
|||
|
|
gleif_lei TEXT
|
|||
|
|
gleif_data JSON
|
|||
|
|
gleif_last_refreshed_at TIMESTAMP
|
|||
|
|
|
|||
|
|
-- Alerts
|
|||
|
|
rss_alerts JSONB
|
|||
|
|
rss_last_refreshed_at TIMESTAMP
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### LLM-Generated Output Fields (102 JSONB Spalten)
|
|||
|
|
|
|||
|
|
<details>
|
|||
|
|
<summary>📋 Vollständige Liste der 102 JSONB Output-Spalten (klicken zum Ausklappen)</summary>
|
|||
|
|
|
|||
|
|
**Corporate Information (57 Spalten):**
|
|||
|
|
```sql
|
|||
|
|
corporate_summary JSONB
|
|||
|
|
corporate_history JSONB
|
|||
|
|
corporate_purpose JSONB
|
|||
|
|
corporate_sector JSONB
|
|||
|
|
corporate_nace JSONB
|
|||
|
|
corporate_products JSONB
|
|||
|
|
corporate_markets JSONB
|
|||
|
|
corporate_supply JSONB
|
|||
|
|
corporate_name JSONB
|
|||
|
|
corporate_form JSONB
|
|||
|
|
corporate_forum JSONB
|
|||
|
|
corporate_HQ JSONB
|
|||
|
|
corporate_locations JSONB
|
|||
|
|
corporate_holding JSONB
|
|||
|
|
corporate_shareholders JSONB
|
|||
|
|
corporate_board JSONB
|
|||
|
|
corporate_supervisory JSONB
|
|||
|
|
corporate_powerofattorney JSONB
|
|||
|
|
corporate_taxID JSONB
|
|||
|
|
corporate_LEI JSONB
|
|||
|
|
corporate_UBO JSONB
|
|||
|
|
corporate_employeecount JSONB
|
|||
|
|
corporate_turnover JSONB
|
|||
|
|
corporate_EBIT JSONB
|
|||
|
|
corporate_netprofits JSONB
|
|||
|
|
corporate_balancesheet JSONB
|
|||
|
|
corporate_auditfindings JSONB
|
|||
|
|
corporate_insolvency JSONB
|
|||
|
|
corporate_liquidation JSONB
|
|||
|
|
corporate_adhoc JSONB
|
|||
|
|
corporate_pressrelease JSONB
|
|||
|
|
corporate_votes JSONB
|
|||
|
|
corporate_directordealings JSONB
|
|||
|
|
corporate_brands JSONB
|
|||
|
|
corporate_website JSONB
|
|||
|
|
corporate_domain JSONB
|
|||
|
|
corporate_IBAN JSONB
|
|||
|
|
corporate_solvency JSONB
|
|||
|
|
corporate_rating JSONB
|
|||
|
|
corporate_ESG JSONB
|
|||
|
|
corporate_license JSONB
|
|||
|
|
corporate_warnings JSONB
|
|||
|
|
corporate_eusanctions JSONB
|
|||
|
|
corporate_ofacsanctions JSONB
|
|||
|
|
corporate_uksanctions JSONB
|
|||
|
|
corporate_pepexposure JSONB
|
|||
|
|
corporate_adversemediascanning JSONB
|
|||
|
|
corporate_corruptionexposure JSONB
|
|||
|
|
corporate_AMLexposure JSONB
|
|||
|
|
corporate_CTYexposure JSONB
|
|||
|
|
corporate_adverse JSONB
|
|||
|
|
corporate_pep JSONB
|
|||
|
|
corporate_adverse2 JSONB
|
|||
|
|
corporate_exportcontrol JSONB
|
|||
|
|
corporate_mediamatch JSONB
|
|||
|
|
corporate_haven JSONB
|
|||
|
|
corporate_haven2 JSONB
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Transaction Analysis (30 Spalten):**
|
|||
|
|
```sql
|
|||
|
|
tranx_TX_AMOUNT JSONB
|
|||
|
|
tranx_historical JSONB
|
|||
|
|
tranx_TX_PURPOSE JSONB
|
|||
|
|
tranx_counterpartyassessment JSONB
|
|||
|
|
tranx_context JSONB
|
|||
|
|
tranx_report JSONB
|
|||
|
|
tranx_PURPOSEbase JSONB
|
|||
|
|
tranx_performanceperiod JSONB
|
|||
|
|
tranx_seasonality JSONB
|
|||
|
|
tranx_resolution JSONB
|
|||
|
|
trans_IBANvalidation JSONB
|
|||
|
|
tranx_businesslogic JSONB
|
|||
|
|
tranx_plausibilty JSONB
|
|||
|
|
tranx_outliers JSONB
|
|||
|
|
tranx_patterns JSONB
|
|||
|
|
tranx_revenueimpact JSONB
|
|||
|
|
tranx_profitimpact JSONB
|
|||
|
|
tranx_marketimpact JSONB
|
|||
|
|
tranx_duplicate JSONB
|
|||
|
|
tranx_pattern2 JSONB
|
|||
|
|
tranx_contracttenor JSONB
|
|||
|
|
tranx_mismatch JSONB
|
|||
|
|
tranx_fakepurposecheck JSONB
|
|||
|
|
tranx_structuring JSONB
|
|||
|
|
tranx_newbankaccount JSONB
|
|||
|
|
tranx_weekday JSONB
|
|||
|
|
tranx_holdingobfuscation JSONB
|
|||
|
|
tranx_score JSONB
|
|||
|
|
tranx_reasoning JSONB
|
|||
|
|
tranx_recommendation JSONB
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Risk & Compliance (10 Spalten):**
|
|||
|
|
```sql
|
|||
|
|
sanctions_circumvention JSONB
|
|||
|
|
corruption_sector JSONB
|
|||
|
|
corruption_country JSONB
|
|||
|
|
corruption_relationship JSONB
|
|||
|
|
country_risk JSONB
|
|||
|
|
corporate_insiders JSONB
|
|||
|
|
corporate_courtcases JSONB
|
|||
|
|
corporate_manda JSONB
|
|||
|
|
corporate_pepdetail JSONB
|
|||
|
|
tranx_sourcerepository JSONB
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Source Validation (5 Spalten):**
|
|||
|
|
```sql
|
|||
|
|
source_domaincheck JSONB
|
|||
|
|
source_check JSONB
|
|||
|
|
source_approveuniqueID JSONB
|
|||
|
|
source_randomplausibility JSONB
|
|||
|
|
```
|
|||
|
|
</details>
|
|||
|
|
|
|||
|
|
#### Status-Werte
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class Transaction extends Model
|
|||
|
|
{
|
|||
|
|
const STATUS_TRUE_POSITIVE = 'true_positive'; // Kritisches Risiko
|
|||
|
|
const STATUS_FALSE_POSITIVE = 'false_positive'; // Hohes Risiko
|
|||
|
|
const STATUS_CLEARED = 'cleared'; // Geringes Risiko
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| Status | Label (DE) | Bedeutung |
|
|||
|
|
|--------|-----------|-----------|
|
|||
|
|
| `true_positive` | Kritisches Risiko | Bestätigtes Risiko, Aktion erforderlich |
|
|||
|
|
| `false_positive` | Hohes Risiko | Potenzielles Risiko, Review erforderlich |
|
|||
|
|
| `cleared` | Geringes Risiko | Geprüft und freigegeben |
|
|||
|
|
|
|||
|
|
#### Relationships
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class Transaction extends Model
|
|||
|
|
{
|
|||
|
|
// N:1 - Viele Transactions gehören zu einer Company
|
|||
|
|
public function company(): BelongsTo
|
|||
|
|
{
|
|||
|
|
return $this->belongsTo(Company::class);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Dynamic JSONB Casting
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// app/Models/Transaction.php
|
|||
|
|
protected function casts(): array
|
|||
|
|
{
|
|||
|
|
// Automatisches Casting aller JSONB-Spalten als Array
|
|||
|
|
$allColumns = Schema::getColumnListing('transactions');
|
|||
|
|
$standardColumns = ['id', 'company_id', 'reference', ...];
|
|||
|
|
|
|||
|
|
$jsonbColumns = array_diff($allColumns, $standardColumns);
|
|||
|
|
|
|||
|
|
foreach ($jsonbColumns as $column) {
|
|||
|
|
$casts[$column] = 'array';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $casts;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Business Logic Methods
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// Status Label
|
|||
|
|
public function statusLabel(): string
|
|||
|
|
{
|
|||
|
|
return match($this->status) {
|
|||
|
|
self::STATUS_TRUE_POSITIVE => 'Kritisches Risiko',
|
|||
|
|
self::STATUS_FALSE_POSITIVE => 'Hohes Risiko',
|
|||
|
|
self::STATUS_CLEARED => 'Geringes Risiko',
|
|||
|
|
default => 'Unbekanntes Risiko',
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### User Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `public.users`
|
|||
|
|
**Model:** `App\Models\User`
|
|||
|
|
|
|||
|
|
#### Struktur
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE public.users (
|
|||
|
|
id SERIAL PRIMARY KEY,
|
|||
|
|
name VARCHAR(255) NOT NULL,
|
|||
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|||
|
|
email_verified_at TIMESTAMP,
|
|||
|
|
password VARCHAR(255) NOT NULL,
|
|||
|
|
|
|||
|
|
-- Two-Factor Authentication (Laravel Fortify)
|
|||
|
|
two_factor_secret TEXT,
|
|||
|
|
two_factor_recovery_codes TEXT,
|
|||
|
|
two_factor_confirmed_at TIMESTAMP,
|
|||
|
|
|
|||
|
|
remember_token VARCHAR(100),
|
|||
|
|
created_at TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Features
|
|||
|
|
|
|||
|
|
- ✅ Email/Password Authentication
|
|||
|
|
- ✅ Email Verification
|
|||
|
|
- ✅ Two-Factor Authentication (2FA)
|
|||
|
|
- ✅ Password Reset
|
|||
|
|
- ✅ Remember Me
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Backend Models (BACKEND Schema)
|
|||
|
|
|
|||
|
|
### Backend\Transaction Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `backend.transactions`
|
|||
|
|
**Model:** `App\Models\Backend\Transaction`
|
|||
|
|
**Connection:** `backend`
|
|||
|
|
|
|||
|
|
#### Struktur
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE backend.transactions (
|
|||
|
|
id SERIAL PRIMARY KEY,
|
|||
|
|
corporate_entity TEXT,
|
|||
|
|
corporate_counterparty TEXT,
|
|||
|
|
tx_date DATE,
|
|||
|
|
tx_amount DECIMAL(15,2),
|
|||
|
|
tx_currency VARCHAR(3),
|
|||
|
|
tx_purpose TEXT,
|
|||
|
|
tx_country_outgoing VARCHAR(2),
|
|||
|
|
tx_country_incoming VARCHAR(2),
|
|||
|
|
source_file TEXT,
|
|||
|
|
raw_payload TEXT,
|
|||
|
|
status VARCHAR(50), -- pending|processing|done|failed
|
|||
|
|
risk_score INTEGER, -- -100 to 100 (Backend Scale)
|
|||
|
|
created_at TIMESTAMP,
|
|||
|
|
last_modified_at TIMESTAMP
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Status-Werte
|
|||
|
|
|
|||
|
|
| Status | Bedeutung |
|
|||
|
|
|--------|-----------|
|
|||
|
|
| `pending` | Wartet auf Verarbeitung |
|
|||
|
|
| `processing` | KI-Analyse läuft |
|
|||
|
|
| `done` | Verarbeitung abgeschlossen |
|
|||
|
|
| `failed` | Fehler bei Verarbeitung |
|
|||
|
|
|
|||
|
|
#### Relationships
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class Transaction extends Model
|
|||
|
|
{
|
|||
|
|
protected $connection = 'backend';
|
|||
|
|
|
|||
|
|
// 1:N - Eine Transaction hat viele Outputs
|
|||
|
|
public function outputs(): HasMany
|
|||
|
|
{
|
|||
|
|
return $this->hasMany(TransactionOutput::class, 'transaction_id');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Helper Methods
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// Hole spezifischen Output-Typ
|
|||
|
|
public function getOutput(string $key): ?array
|
|||
|
|
{
|
|||
|
|
return $this->outputs()
|
|||
|
|
->where('output_key', $key)
|
|||
|
|
->first()
|
|||
|
|
?->content;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Alle Outputs als Key-Value Array
|
|||
|
|
public function getOutputsArray(): array
|
|||
|
|
{
|
|||
|
|
return $this->outputs()
|
|||
|
|
->get()
|
|||
|
|
->pluck('content', 'output_key')
|
|||
|
|
->toArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Convenience Methods
|
|||
|
|
public function getCompanyInfo(): ?array;
|
|||
|
|
public function getRiskAssessment(): ?array;
|
|||
|
|
public function getSanctionsCheck(): ?array;
|
|||
|
|
public function getPepCheck(): ?array;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Backend\TransactionOutput Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `backend.transaction_outputs`
|
|||
|
|
**Model:** `App\Models\Backend\TransactionOutput`
|
|||
|
|
**Connection:** `backend`
|
|||
|
|
|
|||
|
|
#### Struktur
|
|||
|
|
|
|||
|
|
```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
|
|||
|
|
run_id INTEGER,
|
|||
|
|
created_at TIMESTAMP,
|
|||
|
|
|
|||
|
|
UNIQUE(transaction_id, prompt_id)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Output Keys (Konstanten)
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
class TransactionOutput extends Model
|
|||
|
|
{
|
|||
|
|
const KEY_COMPANY_INFO = 'company_info';
|
|||
|
|
const KEY_RISK_ASSESSMENT = 'risk_assessment';
|
|||
|
|
const KEY_SANCTIONS = 'sanctions';
|
|||
|
|
const KEY_PEP = 'pep';
|
|||
|
|
const KEY_REGISTRY = 'registry';
|
|||
|
|
const KEY_GLEIF = 'gleif';
|
|||
|
|
// ... weitere 96 Keys
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Relationships
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// N:1 - Viele Outputs gehören zu einer Transaction
|
|||
|
|
public function transaction(): BelongsTo
|
|||
|
|
{
|
|||
|
|
return $this->belongsTo(Transaction::class, 'transaction_id');
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ETL-Modell (Data Pool)
|
|||
|
|
|
|||
|
|
### BackendDataPool Model
|
|||
|
|
|
|||
|
|
**Tabelle:** `public.backend_data_pool`
|
|||
|
|
**Model:** `App\Models\BackendDataPool` (via DB Facade)
|
|||
|
|
|
|||
|
|
#### Zweck
|
|||
|
|
|
|||
|
|
Denormalisierte Zwischentabelle für ETL-Pipeline:
|
|||
|
|
```sql
|
|||
|
|
SELECT * FROM backend.transactions t
|
|||
|
|
LEFT JOIN backend.transaction_outputs tout ON t.id = tout.transaction_id
|
|||
|
|
WHERE t.status = 'done'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Struktur
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE public.backend_data_pool (
|
|||
|
|
id SERIAL PRIMARY KEY,
|
|||
|
|
|
|||
|
|
-- From backend.transactions
|
|||
|
|
transaction_id INTEGER INDEX,
|
|||
|
|
corporate_entity TEXT,
|
|||
|
|
corporate_counterparty TEXT,
|
|||
|
|
tx_date TEXT,
|
|||
|
|
tx_amount DOUBLE,
|
|||
|
|
tx_currency TEXT,
|
|||
|
|
tx_purpose TEXT,
|
|||
|
|
tx_country_outgoing TEXT,
|
|||
|
|
tx_country_incoming TEXT,
|
|||
|
|
source_file TEXT,
|
|||
|
|
raw_payload TEXT,
|
|||
|
|
status TEXT INDEX,
|
|||
|
|
created_at TEXT,
|
|||
|
|
last_modified_at TEXT,
|
|||
|
|
|
|||
|
|
-- From backend.transaction_outputs
|
|||
|
|
prompt_id INTEGER,
|
|||
|
|
output_key TEXT INDEX,
|
|||
|
|
content TEXT, -- JSON als Text gespeichert
|
|||
|
|
run_id INTEGER,
|
|||
|
|
|
|||
|
|
-- Metadata
|
|||
|
|
synced_at TIMESTAMP DEFAULT NOW(),
|
|||
|
|
updated_at TIMESTAMP,
|
|||
|
|
|
|||
|
|
UNIQUE(transaction_id, prompt_id)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Indizes
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
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);
|
|||
|
|
CREATE INDEX idx_backend_data_pool_transaction_output ON backend_data_pool(transaction_id, output_key);
|
|||
|
|
CREATE INDEX idx_backend_data_pool_transaction_prompt ON backend_data_pool(transaction_id, prompt_id);
|
|||
|
|
CREATE INDEX idx_backend_data_pool_synced_at ON backend_data_pool(synced_at);
|
|||
|
|
CREATE INDEX idx_backend_data_pool_status ON backend_data_pool(status);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Beziehungen & Constraints
|
|||
|
|
|
|||
|
|
### Foreign Key Constraints
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Companies → Transactions (1:N)
|
|||
|
|
ALTER TABLE transactions
|
|||
|
|
ADD CONSTRAINT fk_transactions_company_id
|
|||
|
|
FOREIGN KEY (company_id)
|
|||
|
|
REFERENCES companies(id)
|
|||
|
|
ON DELETE CASCADE;
|
|||
|
|
|
|||
|
|
-- Backend: Transactions → Outputs (1:N)
|
|||
|
|
ALTER TABLE backend.transaction_outputs
|
|||
|
|
ADD CONSTRAINT fk_outputs_transaction_id
|
|||
|
|
FOREIGN KEY (transaction_id)
|
|||
|
|
REFERENCES backend.transactions(id)
|
|||
|
|
ON DELETE CASCADE;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Unique Constraints
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Company Name muss eindeutig sein
|
|||
|
|
ALTER TABLE companies
|
|||
|
|
ADD CONSTRAINT companies_name_unique
|
|||
|
|
UNIQUE (name);
|
|||
|
|
|
|||
|
|
-- Transaction Reference muss eindeutig sein
|
|||
|
|
ALTER TABLE transactions
|
|||
|
|
ADD CONSTRAINT transactions_reference_unique
|
|||
|
|
UNIQUE (reference);
|
|||
|
|
|
|||
|
|
-- User Email muss eindeutig sein
|
|||
|
|
ALTER TABLE users
|
|||
|
|
ADD CONSTRAINT users_email_unique
|
|||
|
|
UNIQUE (email);
|
|||
|
|
|
|||
|
|
-- Backend: Ein Output pro (transaction_id, prompt_id) Kombination
|
|||
|
|
ALTER TABLE backend.transaction_outputs
|
|||
|
|
ADD CONSTRAINT transaction_outputs_unique
|
|||
|
|
UNIQUE (transaction_id, prompt_id);
|
|||
|
|
|
|||
|
|
-- Data Pool: Ein Record pro (transaction_id, prompt_id) Kombination
|
|||
|
|
ALTER TABLE backend_data_pool
|
|||
|
|
ADD CONSTRAINT backend_data_pool_unique
|
|||
|
|
UNIQUE (transaction_id, prompt_id);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Indizes & Performance
|
|||
|
|
|
|||
|
|
### Performance-kritische Indizes
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Companies
|
|||
|
|
CREATE INDEX idx_companies_country ON companies(country);
|
|||
|
|
CREATE INDEX idx_companies_kyc_risk_level ON companies(kyc_risk_level);
|
|||
|
|
CREATE INDEX idx_companies_sector ON companies(sector);
|
|||
|
|
|
|||
|
|
-- Transactions
|
|||
|
|
CREATE INDEX idx_transactions_company_id ON transactions(company_id);
|
|||
|
|
CREATE INDEX idx_transactions_status ON transactions(status);
|
|||
|
|
CREATE INDEX idx_transactions_executed_at ON transactions(executed_at);
|
|||
|
|
CREATE INDEX idx_transactions_risk_score ON transactions(risk_score);
|
|||
|
|
CREATE INDEX idx_transactions_requires_review ON transactions(requires_review);
|
|||
|
|
CREATE INDEX idx_transactions_counterparty_country ON transactions(counterparty_country);
|
|||
|
|
|
|||
|
|
-- Backend Transactions
|
|||
|
|
CREATE INDEX idx_backend_transactions_status ON backend.transactions(status);
|
|||
|
|
CREATE INDEX idx_backend_transactions_last_modified ON backend.transactions(last_modified_at);
|
|||
|
|
|
|||
|
|
-- Backend Outputs
|
|||
|
|
CREATE INDEX idx_backend_outputs_transaction_id ON backend.transaction_outputs(transaction_id);
|
|||
|
|
CREATE INDEX idx_backend_outputs_output_key ON backend.transaction_outputs(output_key);
|
|||
|
|
|
|||
|
|
-- Data Pool (siehe oben)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Query-Optimierungen
|
|||
|
|
|
|||
|
|
#### Beispiel: Companies mit High-Risk Transactions
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Ohne Index: Full Table Scan
|
|||
|
|
SELECT c.*, COUNT(t.id) as high_risk_count
|
|||
|
|
FROM companies c
|
|||
|
|
JOIN transactions t ON c.id = t.company_id
|
|||
|
|
WHERE t.status = 'true_positive'
|
|||
|
|
GROUP BY c.id;
|
|||
|
|
|
|||
|
|
-- Mit Indizes: Index Scan
|
|||
|
|
-- Index auf t.status ermöglicht schnelles Filtern
|
|||
|
|
-- Index auf t.company_id ermöglicht schnelles Join
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## JSONB-Spalten
|
|||
|
|
|
|||
|
|
### Vorteile von JSONB
|
|||
|
|
|
|||
|
|
1. **Flexibilität:** Dynamische Struktur ohne Schema-Änderungen
|
|||
|
|
2. **Performance:** Binäres Format, schneller als JSON
|
|||
|
|
3. **Indexierung:** GIN-Indizes für schnelle Queries
|
|||
|
|
4. **Query-Support:** Volle PostgreSQL JSONB-Funktionen
|
|||
|
|
|
|||
|
|
### Typische JSONB-Struktur
|
|||
|
|
|
|||
|
|
#### corporate_summary
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"answer": "Mercedes-Benz Group AG ist ein deutscher Automobilhersteller...",
|
|||
|
|
"confidence": 0.95,
|
|||
|
|
"sources": [
|
|||
|
|
"https://www.mercedes-benz-group.com",
|
|||
|
|
"Wikipedia"
|
|||
|
|
],
|
|||
|
|
"generated_at": "2025-11-24T10:30:00Z"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### tranx_score
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"score": 45,
|
|||
|
|
"level": "medium",
|
|||
|
|
"breakdown": {
|
|||
|
|
"transaction": 20,
|
|||
|
|
"sanctions": 0,
|
|||
|
|
"country": 15,
|
|||
|
|
"pep": 0,
|
|||
|
|
"corruption": 10
|
|||
|
|
},
|
|||
|
|
"reasoning": "Moderate risk due to transaction amount and country...",
|
|||
|
|
"generated_at": "2025-11-24T10:30:00Z"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### JSONB Queries
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- Zugriff auf JSONB-Felder
|
|||
|
|
SELECT
|
|||
|
|
reference,
|
|||
|
|
corporate_summary->>'answer' as summary,
|
|||
|
|
tranx_score->'score' as risk_score
|
|||
|
|
FROM transactions
|
|||
|
|
WHERE tranx_score->>'level' = 'high';
|
|||
|
|
|
|||
|
|
-- JSONB Array-Elemente
|
|||
|
|
SELECT
|
|||
|
|
reference,
|
|||
|
|
jsonb_array_elements(corporate_shareholders->'shareholders') as shareholder
|
|||
|
|
FROM transactions;
|
|||
|
|
|
|||
|
|
-- JSONB Aggregation
|
|||
|
|
SELECT
|
|||
|
|
AVG((tranx_score->>'score')::int) as avg_risk_score
|
|||
|
|
FROM transactions;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Business-Logik
|
|||
|
|
|
|||
|
|
### Risk Score Transformation
|
|||
|
|
|
|||
|
|
Backend nutzt **-100 bis 100**, Frontend **0 bis 255**:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// TransformDataPoolToProduction Job
|
|||
|
|
private function normalizeRiskScore(int $backendScore): int
|
|||
|
|
{
|
|||
|
|
return (int) round((($backendScore + 100) / 200) * 255);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Beispiele:
|
|||
|
|
// Backend: -100 → Frontend: 0 (Sehr niedrig)
|
|||
|
|
// Backend: 0 → Frontend: 127 (Medium)
|
|||
|
|
// Backend: 100 → Frontend: 255 (Sehr hoch)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Risk Level Mapping
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
private function calculateRiskLevel(int $normalizedScore): string
|
|||
|
|
{
|
|||
|
|
return match(true) {
|
|||
|
|
$normalizedScore <= 153 => 'low', // 0-153
|
|||
|
|
$normalizedScore <= 229 => 'high', // 154-229
|
|||
|
|
default => 'critical', // 230-255
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Transaction Status Mapping
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
private function mapStatus(string $riskLevel): string
|
|||
|
|
{
|
|||
|
|
return match($riskLevel) {
|
|||
|
|
'low' => Transaction::STATUS_CLEARED,
|
|||
|
|
'high' => Transaction::STATUS_FALSE_POSITIVE,
|
|||
|
|
'critical' => Transaction::STATUS_TRUE_POSITIVE,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Company KYC Risk Calculation
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// KycRiskCalculator Service
|
|||
|
|
public function calculateCompanyRisk(Collection $transactions): string
|
|||
|
|
{
|
|||
|
|
$criticalCount = $transactions->where('status', 'true_positive')->count();
|
|||
|
|
$highCount = $transactions->where('status', 'false_positive')->count();
|
|||
|
|
$total = $transactions->count();
|
|||
|
|
|
|||
|
|
// Worst-Case-Prinzip
|
|||
|
|
if ($criticalCount > 0) {
|
|||
|
|
return 'critical';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// >30% high → critical
|
|||
|
|
if (($highCount / $total) > 0.3) {
|
|||
|
|
return 'critical';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// >10% high → high
|
|||
|
|
if (($highCount / $total) > 0.1) {
|
|||
|
|
return 'high';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 'low';
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Daten-Lifecycle
|
|||
|
|
|
|||
|
|
### ETL-Pipeline
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
1. Backend Processing
|
|||
|
|
↓
|
|||
|
|
backend.transactions (status='done')
|
|||
|
|
+ backend.transaction_outputs
|
|||
|
|
|
|||
|
|
2. Sync to Data Pool (Every 6h)
|
|||
|
|
↓
|
|||
|
|
public.backend_data_pool
|
|||
|
|
(Denormalized JOIN)
|
|||
|
|
|
|||
|
|
3. Transform to Production (30min after Sync)
|
|||
|
|
↓
|
|||
|
|
public.companies (updateOrCreate by name)
|
|||
|
|
+ public.transactions (updateOrCreate by reference)
|
|||
|
|
|
|||
|
|
4. Frontend Display
|
|||
|
|
↓
|
|||
|
|
Livewire Components query Production Tables
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Data Retention
|
|||
|
|
|
|||
|
|
- **Backend Schema:** Unbegrenzt (Source of Truth)
|
|||
|
|
- **Data Pool:** Unbegrenzt (für Re-Processing)
|
|||
|
|
- **Production Tables:** Unbegrenzt
|
|||
|
|
- **User Sessions:** 120 Minuten (configurable)
|
|||
|
|
- **Cache:** Abhängig von Cache-Driver
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Zusammenfassung
|
|||
|
|
|
|||
|
|
### Datenbank-Übersicht
|
|||
|
|
|
|||
|
|
| Schema | Tables | Zweck | Zugriff |
|
|||
|
|
|--------|--------|-------|---------|
|
|||
|
|
| `public` | 4 | Production Data | Read/Write (Frontend) |
|
|||
|
|
| `backend` | 2 | Source Data | Read-Only (Frontend) |
|
|||
|
|
|
|||
|
|
### Model-Übersicht
|
|||
|
|
|
|||
|
|
| Model | Table | Schema | Relationships |
|
|||
|
|
|-------|-------|--------|---------------|
|
|||
|
|
| `Company` | companies | public | hasMany(Transaction) |
|
|||
|
|
| `Transaction` | transactions | public | belongsTo(Company) |
|
|||
|
|
| `User` | users | public | - |
|
|||
|
|
| `Backend\Transaction` | transactions | backend | hasMany(TransactionOutput) |
|
|||
|
|
| `Backend\TransactionOutput` | transaction_outputs | backend | belongsTo(Transaction) |
|
|||
|
|
| - | backend_data_pool | public | ETL Layer (DB Facade) |
|
|||
|
|
|
|||
|
|
### Datenmenge (Beispiel)
|
|||
|
|
|
|||
|
|
- **Companies:** ~70 unique
|
|||
|
|
- **Transactions:** 74
|
|||
|
|
- **Backend Transactions:** 74
|
|||
|
|
- **Backend Outputs:** 7,471 (74 × ~101 outputs)
|
|||
|
|
- **Data Pool Records:** 7,471
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Erstellt:** 2025-11-24
|
|||
|
|
**Version:** 1.0
|
|||
|
|
**Autor:** Risk Intelligence Platform Team
|