# 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
