diff --git a/.DS_Store b/.DS_Store index 5ceb50c..f54e6e7 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.env b/.env index 85901cb..fc83495 100644 --- a/.env +++ b/.env @@ -27,15 +27,15 @@ LOG_LEVEL=debug # DB_USERNAME=root # DB_PASSWORD= -# Secondary PostgreSQL Connection (for backend schema) +# Secondary PostgreSQL Connection (for backend schema) - Local Docker DB_CONNECTION2=pgsql DB_HOST2=127.0.0.1 -DB_PORT2=5433 -DB_DATABASE2=risk_ingest_db -DB_USERNAME2=risk_ingest_user -DB_PASSWORD2=S0prast3r1a +DB_PORT2=5432 +DB_DATABASE2=ingest_db +DB_USERNAME2=ingest_user +DB_PASSWORD2=ingest_pwd -SESSION_DRIVER=database +SESSION_DRIVER=file SESSION_LIFETIME=120 SESSION_ENCRYPT=false SESSION_PATH=/ @@ -43,9 +43,9 @@ SESSION_DOMAIN=null BROADCAST_CONNECTION=log FILESYSTEM_DISK=local -QUEUE_CONNECTION=database +QUEUE_CONNECTION=sync -CACHE_STORE=database +CACHE_STORE=file # CACHE_PREFIX= MEMCACHED_HOST=127.0.0.1 diff --git a/backup_backend_20251203_101741.dump b/backup_backend_20251203_101741.dump new file mode 100644 index 0000000..13846d7 Binary files /dev/null and b/backup_backend_20251203_101741.dump differ diff --git a/backup_devbackend_20251203_103857.dump b/backup_devbackend_20251203_103857.dump new file mode 100644 index 0000000..68c2886 Binary files /dev/null and b/backup_devbackend_20251203_103857.dump differ diff --git a/backup_public_20251203_101723.dump b/backup_public_20251203_101723.dump new file mode 100644 index 0000000..5222b51 Binary files /dev/null and b/backup_public_20251203_101723.dump differ diff --git a/config/database.php b/config/database.php index 22117ed..6fca29e 100644 --- a/config/database.php +++ b/config/database.php @@ -86,10 +86,10 @@ return [ 'driver' => 'pgsql', 'url' => env('DB_URL'), 'host' => env('DB_HOST2', '127.0.0.1'), - 'port' => env('DB_PORT2', '5433'), - 'database' => env('DB_DATABASE2', 'risk_ingest_db'), - 'username' => env('DB_USERNAME2', 'risk_ingest_user'), - 'password' => env('DB_PASSWORD2', ''), + 'port' => env('DB_PORT2', '5432'), + 'database' => env('DB_DATABASE2', 'ingest_db'), + 'username' => env('DB_USERNAME2', 'ingest_user'), + 'password' => env('DB_PASSWORD2', 'ingest_pwd'), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, @@ -101,9 +101,9 @@ return [ 'driver' => env('DB_CONNECTION2'), 'host' => env('DB_HOST2', '127.0.0.1'), 'port' => env('DB_PORT2', '5432'), - 'database' => env('DB_DATABASE2', 'laravel'), - 'username' => env('DB_USERNAME2', 'root'), - 'password' => env('DB_PASSWORD2', ''), + 'database' => env('DB_DATABASE2', 'ingest_db'), + 'username' => env('DB_USERNAME2', 'ingest_user'), + 'password' => env('DB_PASSWORD2', 'ingest_pwd'), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, @@ -115,9 +115,9 @@ return [ 'driver' => env('DB_CONNECTION2'), 'host' => env('DB_HOST2', '127.0.0.1'), 'port' => env('DB_PORT2', '5432'), - 'database' => env('DB_DATABASE2', 'laravel'), - 'username' => env('DB_USERNAME2', 'root'), - 'password' => env('DB_PASSWORD2', ''), + 'database' => env('DB_DATABASE2', 'ingest_db'), + 'username' => env('DB_USERNAME2', 'ingest_user'), + 'password' => env('DB_PASSWORD2', 'ingest_pwd'), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, diff --git a/docs/api-documentation.md b/docs/api-documentation.md new file mode 100644 index 0000000..edca5f2 --- /dev/null +++ b/docs/api-documentation.md @@ -0,0 +1,999 @@ +# API-Dokumentation: Risk Intelligence Platform + +## 📋 Inhaltsverzeichnis + +1. [Übersicht](#ÃŒbersicht) +2. [API-Spezifikation](#api-spezifikation) +3. [Authentication](#authentication) +4. [Endpoints](#endpoints) +5. [Datenmodelle](#datenmodelle) +6. [Beispiele](#beispiele) +7. [Error Handling](#error-handling) +8. [Rate Limiting](#rate-limiting) +9. [Changelog](#changelog) + +--- + +## Übersicht + +Die **Trusted AI Analyst Data API** stellt Daten fÃŒr das Analyst Dashboard, Transaction Review, Company Search und Company Drill-Down bereit. + +### API-Version + +**Version:** 1.0.0 +**Format:** JSON +**Authentifizierung:** Session-basiert (Laravel Session Cookie) + +### Base URLs + +| Environment | URL | +|-------------|-----| +| **Development** | `http://trusted_ai.test/api` | +| **Production** | `https://afc.trai.sft.comstack.de/api` | + +### OpenAPI Spezifikation + +Die vollstÀndige API-Spezifikation ist in [swagger.yaml](../swagger.yaml) dokumentiert. + +**Swagger UI:** Die Spezifikation kann mit [Swagger Editor](https://editor.swagger.io) oder Swagger UI visualisiert werden. + +--- + +## API-Spezifikation + +### API-Kategorien + +| Tag | Beschreibung | Endpoints | +|-----|--------------|-----------| +| **Dashboard** | KPIs und Alert-Übersichten | 1 Endpoint | +| **Transactions** | Portfolio-weite Transaction Review | 2 Endpoints | +| **Companies** | Company Discovery & Drill-Down | 4 Endpoints | + +**Gesamt:** 7 API-Endpoints + +--- + +## Authentication + +### Session-basierte Authentifizierung + +Die API nutzt **Laravel Session Cookies** fÃŒr die Authentifizierung. + +```yaml +securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: laravel_session +``` + +### Login-Flow + +1. **Login via Web-Interface:** +```http +POST /login HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +email=user@example.com&password=secret +``` + +2. **Session Cookie erhalten:** +```http +HTTP/1.1 302 Found +Set-Cookie: laravel_session=eyJ....; Path=/; HttpOnly; SameSite=lax +``` + +3. **API-Request mit Cookie:** +```http +GET /api/dashboard/overview HTTP/1.1 +Cookie: laravel_session=eyJ.... +``` + +### Authentifizierungs-Fehler + +```json +HTTP/1.1 401 Unauthorized +{ + "message": "Unauthenticated." +} +``` + +**Wichtig:** Alle API-Endpoints erfordern eine gÃŒltige Session! + +--- + +## Endpoints + +### 1. Dashboard + +#### GET /dashboard/overview + +Liefert aggregierte KPIs und Alert-Übersichten fÃŒr das Dashboard. + +**Request:** +```http +GET /api/dashboard/overview HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "generatedAt": "2025-01-19T07:30:00Z", + "activeAnalysts": 24, + "totalTransactions": 312, + "totalVolume": 98765432.10, + "alertsToday": 56, + "averageAlertsPerAnalyst": 3, + "precisionRate": 84, + "activeAlerts": { + "count": 128, + "highRiskCount": 19 + }, + "riskSegments": [ + { + "label": "Kritisch (≥80)", + "count": 17, + "volume": 8900000.0 + }, + { + "label": "Hoch (60-79)", + "count": 42, + "volume": 15200000.0 + }, + { + "label": "Mittel (40-59)", + "count": 89, + "volume": 32100000.0 + } + ], + "topCompanies": [ + { + "companyId": 24, + "companyName": "Allianz SE", + "ticker": "ALV", + "sector": "Insurance", + "alerts": 6, + "alertVolume": 1450000.0, + "avgRiskScore": 82 + } + ], + "runbooksExecutedToday": 41 +} +``` + +--- + +### 2. Transactions + +#### GET /transactions + +Liste alle Transaktionen mit globalen Filtern (Portfolio-weite Transaction Review). + +**Query Parameters:** + +| Parameter | Typ | Default | Beschreibung | +|-----------|-----|---------|--------------| +| `search` | string | - | Match gegen Reference, Counterparty, Company | +| `status` | enum | `all` | `all`, `true_positive`, `false_positive`, `cleared` | +| `page` | integer | `1` | Seiten-Nummer (min: 1) | +| `perPage` | integer | `12` | Items pro Seite (min: 1, max: 100) | + +**Request:** +```http +GET /api/transactions?status=true_positive&page=1&perPage=12 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "filters": { + "statusOptions": [ + { + "value": "true_positive", + "label": "BestÀtigte Treffer" + }, + { + "value": "false_positive", + "label": "Fehlalarme" + }, + { + "value": "cleared", + "label": "Freigegeben" + } + ], + "perPage": 12 + }, + "metrics": { + "total": { + "count": 312, + "amount": 98765432.10 + }, + "byStatus": { + "true_positive": { + "count": 42, + "amount": 15200000.0 + }, + "false_positive": { + "count": 89, + "amount": 32100000.0 + }, + "cleared": { + "count": 181, + "amount": 51465432.10 + } + } + }, + "data": [ + { + "id": 512, + "company": { + "id": 12, + "name": "Allianz", + "legalName": "Allianz SE", + "ticker": "ALV", + "sector": "Insurance", + "country": "Germany", + "headquarters": "Munich", + "kycRiskLevel": "high", + "summary": "Multinational insurance provider with EMEA focus." + }, + "counterparty": "Alpine Holdings Ltd.", + "counterpartyCountry": "Switzerland", + "status": "true_positive", + "statusLabel": "BestÀtigter Treffer", + "requiresReview": true, + "riskScore": 87, + "amount": 245000.75, + "currency": "EUR", + "reference": "PAY-2024-10-1942", + "channel": "SWIFT", + "executedAt": "2025-01-18T09:14:00Z", + "flaggedReason": "Counterparty on sanctions watchlist" + } + ], + "pagination": { + "page": 1, + "perPage": 12, + "total": 128, + "lastPage": 11 + }, + "selectedTransactionId": null, + "selectedTransaction": null +} +``` + +--- + +#### GET /transactions/{transactionId} + +Hole detaillierte Transaction Case File. + +**Path Parameters:** + +| Parameter | Typ | Beschreibung | +|-----------|-----|--------------| +| `transactionId` | integer | Transaction ID | + +**Request:** +```http +GET /api/transactions/512 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "id": 512, + "company": { ... }, + "counterparty": "Alpine Holdings Ltd.", + "status": "true_positive", + "statusLabel": "BestÀtigter Treffer", + "requiresReview": true, + "riskScore": 87, + "amount": 245000.75, + "currency": "EUR", + "reference": "PAY-2024-10-1942", + "channel": "SWIFT", + "executedAt": "2025-01-18T09:14:00Z", + "flaggedBy": "Screening Engine", + "flaggedReason": "Counterparty matched to EU sanctions list", + "signals": [ + { + "type": "Adverse Media", + "value": "Enforcement action reported in 2024-11", + "weight": 0.8 + }, + { + "type": "Sanctions", + "value": "EU Sanctions List Match", + "weight": 1.0 + } + ] +} +``` + +**Errors:** +- `401 Unauthorized` - Session ungÃŒltig +- `404 Not Found` - Transaction existiert nicht + +--- + +### 3. Companies + +#### GET /companies/search + +Suche Companies und erhalte Screening-Übersicht. + +**Query Parameters:** + +| Parameter | Typ | Default | Beschreibung | +|-----------|-----|---------|--------------| +| `q` | string | - | Freitext-Suche (Name, Legal Name, Ticker, Sector) | +| `limit` | integer | `6` | Max. Ergebnisse (min: 1, max: 25) | + +**Request:** +```http +GET /api/companies/search?q=Allianz&limit=6 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "overview": { + "totalCompanies": 40, + "openAlerts": 112, + "openAlertVolume": 5640000.0, + "averageRiskScore": 71, + "watchlistHits": 9, + "automationShare": 82 + }, + "results": [ + { + "id": 12, + "name": "Allianz", + "legalName": "Allianz SE", + "ticker": "ALV", + "sector": "Insurance", + "country": "Germany", + "headquarters": "Munich", + "kycRiskLevel": "high", + "summary": "Multinational insurance provider with EMEA focus.", + "alertCount": 7, + "alertVolume": 980000.0, + "alertRiskScore": 79, + "latestAlert": { + "transactionId": 731, + "counterparty": "Baltic Commodities LLC", + "executedAt": "2025-01-17T11:48:00Z", + "channel": "SWIFT", + "flaggedReason": "Pattern matches sanctions typology", + "riskScore": 88 + } + } + ], + "meta": { + "query": "Allianz", + "resultCount": 6, + "limit": 6 + } +} +``` + +--- + +#### GET /companies/{companyId} + +Hole Company-Profil. + +**Path Parameters:** + +| Parameter | Typ | Beschreibung | +|-----------|-----|--------------| +| `companyId` | integer | Company ID | + +**Request:** +```http +GET /api/companies/12 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "id": 12, + "name": "Allianz", + "legalName": "Allianz SE", + "ticker": "ALV", + "sector": "Insurance", + "country": "Germany", + "headquarters": "Munich", + "kycRiskLevel": "high", + "summary": "Multinational insurance provider with EMEA focus.", + "createdAt": "2024-10-15T08:30:00Z", + "updatedAt": "2025-01-18T14:22:00Z" +} +``` + +**Errors:** +- `401 Unauthorized` - Session ungÃŒltig +- `404 Not Found` - Company existiert nicht + +--- + +#### GET /companies/{companyId}/transactions + +Liste alle Transactions fÃŒr eine Company. + +**Path Parameters:** + +| Parameter | Typ | Beschreibung | +|-----------|-----|--------------| +| `companyId` | integer | Company ID | + +**Query Parameters:** + +| Parameter | Typ | Default | Beschreibung | +|-----------|-----|---------|--------------| +| `status` | enum | `all` | `all`, `true_positive`, `false_positive`, `cleared` | +| `channel` | string | `all` | Channel-Filter (z.B. `SWIFT`, `SEPA`) | +| `page` | integer | `1` | Seiten-Nummer | +| `perPage` | integer | `10` | Items pro Seite (min: 1, max: 100) | + +**Request:** +```http +GET /api/companies/12/transactions?status=true_positive&page=1&perPage=10 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "company": { + "id": 12, + "name": "Allianz", + "legalName": "Allianz SE", + "ticker": "ALV", + "sector": "Insurance", + "country": "Germany", + "headquarters": "Munich", + "kycRiskLevel": "high", + "summary": "Multinational insurance provider..." + }, + "filters": { + "statusOptions": [ + { + "value": "true_positive", + "label": "BestÀtigte Treffer" + } + ], + "channelOptions": [ + { + "value": "SWIFT", + "label": "SWIFT" + }, + { + "value": "SEPA", + "label": "SEPA" + } + ] + }, + "metrics": { + "totalCount": 96, + "totalVolume": 7200000.0, + "openAlerts": { + "count": 12, + "amount": 1850000.0 + }, + "highRiskShare": 28, + "last30Days": { + "count": 15, + "amount": 980000.0 + }, + "byStatus": { + "true_positive": { + "count": 12, + "amount": 1850000.0 + }, + "false_positive": { + "count": 24, + "amount": 2100000.0 + }, + "cleared": { + "count": 60, + "amount": 3250000.0 + } + } + }, + "data": [ + { + "id": 877, + "company": { ... }, + "counterparty": "Alpine Holdings Ltd.", + "status": "true_positive", + "riskScore": 87, + "amount": 245000.75, + "currency": "EUR", + "reference": "PAY-2024-10-1942", + "channel": "SWIFT", + "executedAt": "2025-01-18T09:14:00Z" + } + ], + "pagination": { + "page": 1, + "perPage": 10, + "total": 12, + "lastPage": 2 + }, + "selectedTransactionId": null, + "selectedTransaction": null, + "recentAlerts": [ + { + "id": 911, + "counterparty": "Northbridge Trading Ltd.", + "executedAt": "2025-01-18T14:52:00Z", + "channel": "SWIFT", + "status": "true_positive", + "statusLabel": "BestÀtigter Treffer", + "riskScore": 92 + } + ] +} +``` + +**Errors:** +- `401 Unauthorized` - Session ungÃŒltig +- `404 Not Found` - Company nicht gefunden oder keine Transactions + +--- + +#### GET /companies/{companyId}/transactions/{transactionId} + +Hole Transaction Case File mit Company-Context und History. + +**Path Parameters:** + +| Parameter | Typ | Beschreibung | +|-----------|-----|--------------| +| `companyId` | integer | Company ID | +| `transactionId` | integer | Transaction ID | + +**Request:** +```http +GET /api/companies/12/transactions/877 HTTP/1.1 +Cookie: laravel_session=... +``` + +**Response:** `200 OK` +```json +{ + "id": 877, + "company": { ... }, + "counterparty": "Alpine Holdings Ltd.", + "status": "true_positive", + "statusLabel": "BestÀtigter Treffer", + "requiresReview": true, + "riskScore": 87, + "amount": 245000.75, + "currency": "EUR", + "reference": "PAY-2024-10-1942", + "channel": "SWIFT", + "executedAt": "2025-01-18T09:14:00Z", + "flaggedBy": "Screening Engine", + "flaggedReason": "Counterparty matched to EU sanctions list", + "signals": [ + { + "type": "Adverse Media", + "value": "Enforcement action reported in 2024-11", + "weight": 0.8 + } + ], + "recommendedActions": [ + { + "title": "Verdachtsmeldung vorbereiten", + "description": "Erstellen Sie den Meldeentwurf fÃŒr die FIU und sichern Sie Belege." + }, + { + "title": "Enhanced Due Diligence", + "description": "PrÃŒfen Sie zusÀtzliche Informationsquellen zur Counterparty." + } + ], + "counterpartyHistory": [ + { + "id": 765, + "executedAt": "2024-12-22T15:37:00Z", + "channel": "SWIFT", + "reference": "PAY-2024-12-1187", + "amount": 99000.0, + "currency": "EUR", + "riskScore": 74 + } + ] +} +``` + +**Errors:** +- `401 Unauthorized` - Session ungÃŒltig +- `404 Not Found` - Transaction nicht gefunden fÃŒr diese Company + +--- + +## Datenmodelle + +### Transaction Status + +```typescript +enum TransactionStatus { + TRUE_POSITIVE = 'true_positive', // Kritisches Risiko + FALSE_POSITIVE = 'false_positive', // Hohes Risiko + CLEARED = 'cleared' // Geringes Risiko +} +``` + +### KYC Risk Level + +```typescript +enum KycRiskLevel { + LOW = 'low', + HIGH = 'high', + CRITICAL = 'critical' +} +``` + +### Pagination Meta + +```json +{ + "page": 1, + "perPage": 12, + "total": 128, + "lastPage": 11 +} +``` + +### Count Amount Summary + +```json +{ + "count": 42, + "amount": 2750000.50 +} +``` + +### Transaction Signal + +```json +{ + "type": "Adverse Media", + "value": "Enforcement action reported in 2024-11", + "weight": 0.8 +} +``` + +### Recommended Action + +```json +{ + "title": "Verdachtsmeldung vorbereiten", + "description": "Erstellen Sie den Meldeentwurf fÃŒr die FIU..." +} +``` + +--- + +## Beispiele + +### cURL-Beispiele + +#### Login & Session Cookie erhalten + +```bash +# Login +curl -c cookies.txt -X POST http://trusted_ai.test/login \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "email=user@example.com&password=secret" + +# Session Cookie ist jetzt in cookies.txt gespeichert +``` + +#### Dashboard Overview abrufen + +```bash +curl -b cookies.txt http://trusted_ai.test/api/dashboard/overview +``` + +#### Transactions filtern + +```bash +curl -b cookies.txt "http://trusted_ai.test/api/transactions?status=true_positive&page=1&perPage=12" +``` + +#### Company suchen + +```bash +curl -b cookies.txt "http://trusted_ai.test/api/companies/search?q=Allianz&limit=6" +``` + +#### Company Transactions abrufen + +```bash +curl -b cookies.txt "http://trusted_ai.test/api/companies/12/transactions?status=all&page=1" +``` + +--- + +### JavaScript (Fetch API) + +```javascript +// Session Cookie wird automatisch vom Browser gesendet + +// Dashboard Overview +const overview = await fetch('/api/dashboard/overview', { + credentials: 'include' // Wichtig: Session Cookie mitsenden +}).then(r => r.json()); + +console.log(overview); + +// Transactions mit Filter +const transactions = await fetch('/api/transactions?status=true_positive&page=1&perPage=12', { + credentials: 'include' +}).then(r => r.json()); + +console.log(transactions.data); + +// Company Search +const companies = await fetch('/api/companies/search?q=Allianz', { + credentials: 'include' +}).then(r => r.json()); + +console.log(companies.results); +``` + +--- + +### TypeScript Types + +```typescript +// Dashboard Overview Response +interface DashboardOverview { + generatedAt: string; + activeAnalysts: number; + totalTransactions: number; + totalVolume: number; + alertsToday: number; + averageAlertsPerAnalyst: number; + precisionRate: number; + activeAlerts: { + count: number; + highRiskCount: number; + }; + riskSegments: RiskSegment[]; + topCompanies: TopCompanyAlert[]; + runbooksExecutedToday: number; +} + +// Transaction Collection Response +interface TransactionCollectionResponse { + filters: { + statusOptions: StatusOption[]; + perPage: number; + }; + metrics: TransactionMetrics; + data: TransactionPreview[]; + pagination: PaginationMeta; + selectedTransactionId: number | null; + selectedTransaction: TransactionDetail | null; +} + +// Company Search Response +interface CompanySearchResponse { + overview: CompanySearchOverview; + results: CompanySearchResult[]; + meta: { + query: string | null; + resultCount: number; + limit: number; + }; +} +``` + +--- + +## Error Handling + +### Standard Error Response + +```json +{ + "message": "Error message", + "errors": { + "field": ["Validation error"] + } +} +``` + +### HTTP Status Codes + +| Code | Bedeutung | Beschreibung | +|------|-----------|--------------| +| `200` | OK | Request erfolgreich | +| `401` | Unauthorized | Session ungÃŒltig oder abgelaufen | +| `404` | Not Found | Resource nicht gefunden | +| `422` | Unprocessable Entity | Validierungsfehler | +| `500` | Internal Server Error | Server-Fehler | + +### Beispiel: 401 Unauthorized + +```json +HTTP/1.1 401 Unauthorized +Content-Type: application/json + +{ + "message": "Unauthenticated." +} +``` + +**Aktion:** Redirect zu `/login` + +### Beispiel: 404 Not Found + +```json +HTTP/1.1 404 Not Found +Content-Type: application/json + +{ + "message": "Company not found." +} +``` + +### Beispiel: 422 Validation Error + +```json +HTTP/1.1 422 Unprocessable Entity +Content-Type: application/json + +{ + "message": "The given data was invalid.", + "errors": { + "status": ["The selected status is invalid."], + "page": ["The page must be at least 1."] + } +} +``` + +--- + +## Rate Limiting + +### Aktueller Status + +**Aktuell:** Kein Rate Limiting implementiert. + +### Geplant (Future) + +``` +Rate Limit: 60 Requests pro Minute pro User +Header: X-RateLimit-Limit, X-RateLimit-Remaining +``` + +**Response bei Überschreitung:** +```json +HTTP/1.1 429 Too Many Requests +Content-Type: application/json +Retry-After: 60 + +{ + "message": "Too many requests. Please try again later." +} +``` + +--- + +## Changelog + +### Version 1.0.0 (2025-01-19) + +**Initial Release** + +**Endpoints:** +- ✅ `GET /dashboard/overview` - Dashboard KPIs +- ✅ `GET /transactions` - Portfolio-wide Transactions +- ✅ `GET /transactions/{id}` - Transaction Detail +- ✅ `GET /companies/search` - Company Search +- ✅ `GET /companies/{id}` - Company Profile +- ✅ `GET /companies/{id}/transactions` - Company Transactions +- ✅ `GET /companies/{id}/transactions/{id}` - Company Transaction Detail + +**Features:** +- Session-basierte Authentifizierung +- Pagination Support +- Filter & Search +- Metrics & Aggregationen +- OpenAPI 3.1 Spezifikation + +--- + +## Implementierungs-Status + +### ⚠ Wichtig: API noch nicht implementiert! + +Die in diesem Dokument beschriebenen Endpoints sind **aktuell noch nicht implementiert**. + +**Status:** +- ✅ OpenAPI Spezifikation vorhanden ([swagger.yaml](../swagger.yaml)) +- ❌ API Routes noch nicht definiert (`routes/api.php` ist leer) +- ❌ API Controllers noch nicht erstellt +- ❌ API Responses noch nicht implementiert + +### NÀchste Schritte zur Implementierung + +1. **API Routes definieren:** +```php +// routes/api.php +Route::middleware(['auth:sanctum'])->group(function () { + Route::get('/dashboard/overview', [DashboardController::class, 'overview']); + Route::get('/transactions', [TransactionController::class, 'index']); + Route::get('/transactions/{transaction}', [TransactionController::class, 'show']); + Route::get('/companies/search', [CompanyController::class, 'search']); + Route::get('/companies/{company}', [CompanyController::class, 'show']); + Route::get('/companies/{company}/transactions', [CompanyController::class, 'transactions']); +}); +``` + +2. **API Controllers erstellen:** +```bash +php artisan make:controller Api/DashboardController +php artisan make:controller Api/TransactionController +php artisan make:controller Api/CompanyController +``` + +3. **API Resources erstellen:** +```bash +php artisan make:resource TransactionResource +php artisan make:resource CompanyResource +``` + +4. **Tests schreiben:** +```bash +php artisan make:test Api/DashboardTest +php artisan make:test Api/TransactionTest +php artisan make:test Api/CompanyTest +``` + +--- + +## Zusammenfassung + +### API-Übersicht + +| Kategorie | Endpoints | Status | +|-----------|-----------|--------| +| Dashboard | 1 | ⚠ Spezifiziert, nicht implementiert | +| Transactions | 2 | ⚠ Spezifiziert, nicht implementiert | +| Companies | 4 | ⚠ Spezifiziert, nicht implementiert | +| **Gesamt** | **7** | - | + +### Datenmodelle + +- **DashboardOverview** - Dashboard-KPIs +- **TransactionCollectionResponse** - Paginierte Transactions +- **TransactionDetail** - Detaillierte Transaction +- **CompanySearchResponse** - Company-Suchergebnisse +- **CompanyDetail** - Company-Profil +- **CompanyTransactionsResponse** - Company-spezifische Transactions + +### Authentifizierung + +**Session-basiert** via Laravel Session Cookie +- Login erforderlich +- Cookie-basierte Authentifizierung +- CSRF-Protection + +--- + +**Erstellt:** 2025-11-24 +**Version:** 1.0.0 +**Autor:** Risk Intelligence Platform Team +**OpenAPI Spec:** [swagger.yaml](../swagger.yaml) diff --git a/docs/architecture-overview.md b/docs/architecture-overview.md new file mode 100644 index 0000000..61981d0 --- /dev/null +++ b/docs/architecture-overview.md @@ -0,0 +1,1010 @@ +# System-Architektur: Risk Intelligence Platform + +## 📋 Inhaltsverzeichnis + +1. [Übersicht](#ÃŒbersicht) +2. [System-Diagramm](#system-diagramm) +3. [Technologie-Stack](#technologie-stack) +4. [Datenbank-Architektur](#datenbank-architektur) +5. [Datenfluss & ETL-Pipeline](#datenfluss--etl-pipeline) +6. [Applikations-Schichten](#applikations-schichten) +7. [Frontend-Architektur](#frontend-architektur) +8. [Backend-Integration](#backend-integration) +9. [Scheduling & Automation](#scheduling--automation) +10. [Sicherheit & Authentication](#sicherheit--authentication) +11. [Deployment-Architektur](#deployment-architektur) + +--- + +## Übersicht + +Die **Risk Intelligence Platform** ist eine Laravel-basierte Web-Applikation zur Analyse und Bewertung von Unternehmenstransaktionen hinsichtlich KYC (Know Your Customer) und Compliance-Risiken. + +### Kernfunktionen + +- ✅ **Unternehmenssuche & -bewertung** mit KYC Risk Levels +- ✅ **Transaktionsanalyse** mit 139 Datenfeldern (37 Core + 102 JSONB) +- ✅ **Automatische Backend-Synchronisation** (ETL-Pipeline) +- ✅ **Risk Score Berechnung** basierend auf Multi-Faktoren-Analyse +- ✅ **Compliance-Checks** (EU/OFAC/UK Sanctions, PEP, AML) +- ✅ **Benutzer-Authentication** mit 2FA-UnterstÃŒtzung + +### Zielgruppe + +- Compliance-Teams +- Risk Analysts +- KYC-Spezialisten + +--- + +## System-Diagramm + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ FRONTEND (Browser) │ +│ Livewire Volt Components + Flux UI │ +└─────────────────────────┬───────────────────────────────────────────────┘ + │ + â–Œ +┌─────────────────────────────────────────────────────────────────────────┐ +│ LARAVEL APPLICATION │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ +│ │ Controllers │ │ Livewire Volt │ │ API Endpoints │ │ +│ │ (Minimal) │ │ Components │ │ (Future) │ │ +│ └────────┬────────┘ └────────┬────────┘ └─────────────────────┘ │ +│ │ │ │ +│ â–Œ â–Œ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ Eloquent Models & Repositories │ │ +│ │ Company │ Transaction │ User │ │ +│ └──────────────────┬──────────────────────────┘ │ +│ │ │ +│ ┌─────────┮─────────┐ │ +│ â–Œ â–Œ │ +│ ┌──────────────────┐ ┌──────────────────┐ │ +│ │ Jobs/Queue │ │ Services │ │ +│ │ - SyncDataPool │ │ - KycRiskCalc │ │ +│ │ - Transform │ │ - DataExtract │ │ +│ └──────────────────┘ └──────────────────┘ │ +└────────────┬────────────────────────────────────────────────────────────┘ + │ + â–Œ +┌─────────────────────────────────────────────────────────────────────────┐ +│ POSTGRESQL DATABASE (Port 5433) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Schema: PUBLIC (Default Connection) │ │ +│ │ ┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ │ +│ │ │ companies │ │ transactions │ │ backend_data_ │ │ │ +│ │ │ │ │ (139 cols) │ │ pool │ │ │ +│ │ │ - name │◄── - company_id │ │ │ │ │ +│ │ │ - kyc_risk │ │ - risk_score │ │ - transaction_ │ │ │ +│ │ │ - sector │ │ - 102 JSONB cols│ │ id │ │ │ +│ │ └─────────────┘ └──────────────────┘ │ - output_key │ │ │ +│ │ │ - content │ │ │ +│ │ └─────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ Schema: BACKEND (Backend Connection) │ │ +│ │ ┌──────────────────┐ ┌──────────────────────────────────┐ │ │ +│ │ │ transactions │ │ transaction_outputs │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ - id │◄── - transaction_id │ │ │ +│ │ │ - status │ │ - prompt_id │ │ │ +│ │ │ - tx_amount │ │ - output_key │ │ │ +│ │ │ - risk_score │ │ - content (JSONB) │ │ │ +│ │ └──────────────────┘ └──────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ + ▲ + │ + │ (ETL Pipeline - Scheduled Jobs) + │ +┌────────────┮────────────────────────────────────────────────────────────┐ +│ LARAVEL SCHEDULER (Cron) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Every 6h (0:00, 6:00, 12:00, 18:00): │ │ +│ │ → SyncBackendDataPool (Incremental) │ │ +│ │ │ │ +│ │ Every 6h (0:30, 6:30, 12:30, 18:30): │ │ +│ │ → TransformDataPoolToProduction │ │ +│ │ │ │ +│ │ Sunday 3:00: │ │ +│ │ → SyncBackendDataPool (Full Rebuild) │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Technologie-Stack + +### Backend + +| Komponente | Version | Zweck | +|-----------|---------|-------| +| **PHP** | 8.4.14 | Runtime | +| **Laravel Framework** | 12.x | Application Framework | +| **PostgreSQL** | Latest | PrimÀre Datenbank | +| **Laravel Fortify** | 1.x | Authentication | +| **Livewire** | 3.x | Reactive Components | +| **Livewire Volt** | 1.x | Single-File Components | + +### Frontend + +| Komponente | Version | Zweck | +|-----------|---------|-------| +| **Flux UI** | 2.x (Free) | Component Library | +| **Tailwind CSS** | 4.x | Styling Framework | +| **Alpine.js** | Latest (via Livewire) | JavaScript Interactivity | +| **Vite** | Latest | Asset Bundling | + +### Development & Testing + +| Komponente | Version | Zweck | +|-----------|---------|-------| +| **Pest** | 4.x | Testing Framework | +| **PHPUnit** | 12.x | Unit Testing | +| **Laravel Pint** | 1.x | Code Formatting | +| **Laravel Sail** | 1.x | Docker Development | +| **Laravel Boost** | 1.4+ | MCP Server (Development) | + +### Infrastructure + +- **Docker** - Containerization +- **Kubernetes** - Orchestration (k8s-cronjob.yaml) +- **Cron** - Scheduling (Laravel Scheduler) +- **Queue System** - Asynchronous Jobs + +--- + +## Datenbank-Architektur + +### Verbindungen + +Die Applikation nutzt **eine PostgreSQL-Datenbank** mit **zwei Schemas**: + +```php +// config/database.php + +'default' => 'pgsql_second', // Default: public schema + +'connections' => [ + // Frontend Schema (Production Tables) + 'pgsql_second' => [ + 'search_path' => 'public', + 'port' => env('DB_PORT2', 5432), + 'database' => env('DB_DATABASE2'), + ], + + // Backend Schema (Source Data) + 'backend' => [ + 'search_path' => 'backend', + 'port' => env('DB_PORT2', 5432), + 'database' => env('DB_DATABASE2'), // Same DB, different schema + ], +] +``` + +### Schema: PUBLIC (Production) + +#### Companies Tabelle +```sql +CREATE TABLE public.companies ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + legal_name VARCHAR(255), + ticker VARCHAR(50), + sector VARCHAR(100), + country VARCHAR(2), -- ISO 3166-1 alpha-2 + headquarters TEXT, + kyc_risk_level VARCHAR(20), -- low | high | critical + summary TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +#### Transactions Tabelle (139 Spalten) + +**Core Felder (37):** +```sql +CREATE TABLE public.transactions ( + -- Identity & Relationships + id SERIAL PRIMARY KEY, + company_id INTEGER REFERENCES companies(id), + reference VARCHAR(255) UNIQUE NOT NULL, + + -- Transaction Details + amount DECIMAL(15,2), + currency VARCHAR(3), + counterparty VARCHAR(255), + counterparty_country VARCHAR(2), + channel VARCHAR(50), + executed_at TIMESTAMP, + + -- Risk Assessment + risk_score INTEGER, -- 0-255 normalized + status VARCHAR(50), -- true_positive | false_positive | cleared + requires_review BOOLEAN, + flagged_by VARCHAR(255), + flagged_reason TEXT, + signals JSONB, + + -- Additional Entity Data + entity VARCHAR(255), -- Corporate entity name + counterparty_kyc_risk_level VARCHAR(20), + + -- Risk Breakdown + transaction_risk_score INTEGER, + sanctions_risk_score INTEGER, + country_risk_score INTEGER, + pep_adverse_risk_score INTEGER, + corruption_risk_score INTEGER, + + -- Timestamps + created_at TIMESTAMP, + updated_at TIMESTAMP, + + -- ... 102 JSONB Columns (siehe unten) +); +``` + +**JSONB Output Columns (102):** + +Alle `output_key` Werte aus `backend.transaction_outputs` werden als JSONB-Spalten gespeichert: + +
+VollstÀndige Liste der 102 JSONB Spalten (klicken zum Ausklappen) + +**Corporate Information:** +- `corporate_summary` +- `corporate_history` +- `corporate_legalname` +- `corporate_sector` +- `corporate_HQ` +- `corporate_keyexecs` +- `corporate_shareholding` +- `corporate_parentcompany` +- `corporate_subsidiaries` +- `corporate_countryofincorporation` +- `corporate_ticker` +- `corporate_counterparty` +- `corporate_counterpartysector` +- `corporate_counterpartycountry` +- `corporate_counterpartysummary` +- `corporate_counterpartyhistory` + +**Sanctions & Compliance:** +- `corporate_eusanctions` +- `corporate_ofacsanctions` +- `corporate_uksanctions` +- `corporate_pepexposure` +- `corporate_AMLexposure` +- `corporate_adverse` +- `sanctions_circumvention` + +**Country Risk:** +- `country_risk` +- `country_name` +- `country_iso2` +- `country_iso3` +- `country_cpi` +- `country_euprivacy` +- `country_dataprivacy` +- `country_gdpr` +- `country_sanctions` +- `country_wgi` +- `country_pep` + +**Corruption:** +- `corruption_sector` +- `corruption_country` +- `corruption_relationship` + +**Financial & Transaction:** +- `tranx_score` +- `tranx_reasoning` +- `tranx_amount` +- `tranx_currency` +- `tranx_date` +- `tranx_counterparty` +- `tranx_channel` +- `financial_assessment` + +... und weitere 60+ Spalten fÌr detaillierte Analysen +
+ +#### Backend Data Pool Tabelle +```sql +CREATE TABLE public.backend_data_pool ( + id SERIAL PRIMARY KEY, + + -- Foreign Keys (Backend Schema) + transaction_id INTEGER NOT NULL, + prompt_id INTEGER NOT NULL, + + -- Transaction Core Data + corporate_entity VARCHAR(255), + tx_amount DECIMAL(15,2), + tx_currency VARCHAR(3), + tx_date DATE, + status VARCHAR(50), + + -- Risk Scores + risk_score INTEGER, -- Backend risk_score (-100 to 100) + + -- Output Data + output_key VARCHAR(255), + content JSONB, + + -- Metadata + synced_at TIMESTAMP DEFAULT NOW(), + + UNIQUE(transaction_id, prompt_id) +); + +CREATE INDEX idx_backend_data_pool_transaction_id ON backend_data_pool(transaction_id); +CREATE INDEX idx_backend_data_pool_output_key ON backend_data_pool(output_key); +``` + +### Schema: BACKEND (Source Data) + +#### Backend Transactions +```sql +CREATE TABLE backend.transactions ( + id SERIAL PRIMARY KEY, + corporate_entity VARCHAR(255), + tx_amount DECIMAL(15,2), + tx_currency VARCHAR(3), + tx_date DATE, + status VARCHAR(50), -- pending | processing | done | failed + risk_score INTEGER, -- -100 to 100 (Backend Scale) + last_modified_at TIMESTAMP, + created_at TIMESTAMP +); +``` + +#### Backend Transaction Outputs +```sql +CREATE TABLE backend.transaction_outputs ( + id SERIAL PRIMARY KEY, + transaction_id INTEGER REFERENCES transactions(id), + prompt_id INTEGER, + output_key VARCHAR(255), -- z.B. "corporate_summary", "tranx_score" + content JSONB, -- LLM-Generated Output + created_at TIMESTAMP, + + UNIQUE(transaction_id, prompt_id) +); +``` + +--- + +## Datenfluss & ETL-Pipeline + +### Dreistufiger Datenprozess + +``` +┌─────────────────────────────────────────────────────────────┐ +│ STUFE 0: Backend Processing (Extern) │ +│ │ +│ LLM Analysis → backend.transactions │ +│ → backend.transaction_outputs │ +└─────────────┬───────────────────────────────────────────────┘ + │ + â–Œ +┌─────────────────────────────────────────────────────────────┐ +│ STUFE 1: Data Pool Sync │ +│ │ +│ Job: SyncBackendDataPool │ +│ Schedule: Every 6h (Incremental) | Sunday 3am (Full) │ +│ │ +│ Source: backend.transactions │ +│ + backend.transaction_outputs │ +│ │ +│ Target: public.backend_data_pool │ +│ │ +│ Process: │ +│ 1. Query backend schema (WHERE status='done') │ +│ 2. Join transactions + outputs │ +│ 3. Batch insert into data pool (1000 records/batch) │ +│ 4. Track sync timestamp │ +│ │ +│ Performance: │ +│ - Full Sync: ~30-60s (7,471 records) │ +│ - Incremental: ~5-10s (new records only) │ +└─────────────┬───────────────────────────────────────────────┘ + │ + â–Œ +┌─────────────────────────────────────────────────────────────┐ +│ STUFE 2: Production Transformation │ +│ │ +│ Job: TransformDataPoolToProduction │ +│ Schedule: Every 6h at :30 (30min after Sync) │ +│ │ +│ Source: public.backend_data_pool │ +│ │ +│ Targets: public.companies │ +│ + public.transactions │ +│ │ +│ Process: │ +│ 1. Group by transaction_id │ +│ 2. For each transaction: │ +│ a) Extract/Update Company (by corporate_entity) │ +│ - Calculate KYC Risk Level │ +│ - Enrich with sector, country, HQ │ +│ b) Create/Update Transaction │ +│ - Map all 139 columns │ +│ - Link to company_id │ +│ - Transform risk_score (-100..100 → 0..255) │ +│ 3. Batch process (100 records/batch) │ +│ │ +│ Features: │ +│ - Idempotent (updateOrCreate) │ +│ - Deduplication by reference │ +│ - Risk Score Normalization │ +│ │ +│ Performance: │ +│ - Initial: ~30-60s (74 transactions → ~70 companies) │ +│ - Updates: ~5-10s │ +└─────────────┬───────────────────────────────────────────────┘ + │ + â–Œ +┌─────────────────────────────────────────────────────────────┐ +│ STUFE 3: Frontend Display (Real-time) │ +│ │ +│ Livewire Volt Components query: │ +│ - public.companies │ +│ - public.transactions │ +│ │ +│ No additional processing required │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Risk Score Transformation + +Backend nutzt eine Skala von **-100 bis 100**, Frontend **0 bis 255**: + +```php +// Backend → Frontend Normalization +$normalizedScore = (($backendScore + 100) / 200) * 255; + +// Beispiele: +// Backend: -100 → Frontend: 0 (Sehr gut) +// Backend: 0 → Frontend: 127 (Neutral) +// Backend: 100 → Frontend: 255 (Sehr schlecht) +``` + +**Risk Level Mapping:** + +| Backend Score | Frontend Score | Risk Level | Transaction Status | +|--------------|----------------|------------|-------------------| +| -100 to 20 | 0 to 153 | `low` | `cleared` | +| 20 to 80 | 153 to 229 | `high` | `false_positive` | +| 80 to 100 | 229 to 255 | `critical` | `true_positive` | + +--- + +## Applikations-Schichten + +### Layer Architecture + +``` +┌──────────────────────────────────────────────────────┐ +│ PRESENTATION LAYER │ +│ - Livewire Volt Components (resources/views/ │ +│ livewire/) │ +│ - Blade Templates │ +│ - Flux UI Components │ +└──────────────────┬───────────────────────────────────┘ + │ + â–Œ +┌──────────────────────────────────────────────────────┐ +│ APPLICATION LAYER │ +│ - Controllers (minimal, mostly Volt) │ +│ - Livewire Actions (app/Livewire/Actions/) │ +│ - Form Requests (validation) │ +└──────────────────┬───────────────────────────────────┘ + │ + â–Œ +┌──────────────────────────────────────────────────────┐ +│ DOMAIN LAYER │ +│ - Eloquent Models (app/Models/) │ +│ · Company │ +│ · Transaction │ +│ · User │ +│ · Backend\Transaction │ +│ · Backend\TransactionOutput │ +│ - Repositories (app/Repositories/) │ +│ · TransactionRepository │ +│ - Services (app/Services/) │ +│ · KycRiskCalculator │ +└──────────────────┬───────────────────────────────────┘ + │ + â–Œ +┌──────────────────────────────────────────────────────┐ +│ INFRASTRUCTURE LAYER │ +│ - Jobs (app/Jobs/) │ +│ · SyncBackendDataPool │ +│ · TransformDataPoolToProduction │ +│ - Commands (app/Console/Commands/) │ +│ · SyncBackendDataPoolCommand │ +│ · TransformDataPoolCommand │ +│ · RebuildFromDataPoolCommand │ +│ - Database Migrations │ +│ - Schedulers (routes/console.php) │ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## Frontend-Architektur + +### Livewire Volt Components + +Die Applikation nutzt **Single-File Components** mit Livewire Volt: + +```php +// resources/views/livewire/company-search.blade.php + Company::query() + ->when($this->search, fn($q) => + $q->where('name', 'ilike', "%{$this->search}%") + ) + ->limit(20) + ->get(), + ]; + } +} +?> + +
+ + +
+``` + +### Routen-Struktur + +```php +// routes/web.php + +// Public +Route::get('/', fn() => view('welcome'))->name('home'); + +// Authenticated Routes +Route::middleware(['auth', 'verified'])->group(function () { + Route::view('dashboard', 'dashboard')->name('dashboard'); + + // Volt Routes + Volt::route('company-search', 'company-search')->name('company-search'); + Volt::route('transaction-review', 'transaction-review')->name('transaction-review'); + Volt::route('companies/{company}/transactions', 'companies.transactions') + ->name('company.transactions'); + Volt::route('upload', 'upload.index')->name('upload'); + + // Settings + Volt::route('settings/profile', 'settings.profile')->name('profile.edit'); + Volt::route('settings/password', 'settings.password')->name('password.edit'); + Volt::route('settings/two-factor', 'settings.two-factor') + ->middleware(['password.confirm']) + ->name('two-factor.show'); +}); +``` + +### Hauptseiten + +| Route | Component | Beschreibung | +|-------|-----------|--------------| +| `/` | welcome.blade.php | Landing Page | +| `/dashboard` | dashboard.blade.php | Dashboard-Übersicht | +| `/company-search` | company-search.blade.php | Unternehmenssuche | +| `/transaction-review` | transaction-review.blade.php | TransaktionsÃŒbersicht | +| `/companies/{id}/transactions` | companies.transactions.blade.php | Company-Detail mit Transactions | +| `/upload` | upload.index.blade.php | Upload-FunktionalitÀt | + +--- + +## Backend-Integration + +### Models & Relationships + +```php +// app/Models/Company.php +class Company extends Model +{ + protected $fillable = [ + 'name', 'legal_name', 'ticker', 'sector', + 'country', 'headquarters', 'kyc_risk_level', 'summary', + ]; + + public function transactions(): HasMany + { + return $this->hasMany(Transaction::class); + } +} + +// app/Models/Transaction.php +class Transaction extends Model +{ + protected $guarded = []; // Allow all (for 102 JSONB columns) + + // Dynamic JSONB casting + protected function casts(): array + { + $allColumns = Schema::getColumnListing('transactions'); + $jsonbColumns = array_diff($allColumns, $standardColumns); + + foreach ($jsonbColumns as $column) { + $casts[$column] = 'array'; + } + + return $casts; + } + + public function company(): BelongsTo + { + return $this->belongsTo(Company::class); + } +} +``` + +### Jobs + +#### SyncBackendDataPool + +```php +// app/Jobs/SyncBackendDataPool.php + +class SyncBackendDataPool implements ShouldQueue +{ + public function __construct( + public bool $fullSync = false, + public int $batchSize = 1000, + ) {} + + public function handle(): void + { + if ($this->fullSync) { + DB::table('backend_data_pool')->truncate(); + } + + $query = DB::connection('pgsql') + ->table('backend.transactions as t') + ->join('backend.transaction_outputs as tout', 't.id', '=', 'tout.transaction_id') + ->where('t.status', 'done') + ->select([ + 't.id as transaction_id', + 'tout.prompt_id', + 't.corporate_entity', + 't.tx_amount', + 't.tx_currency', + 't.tx_date', + 't.status', + 't.risk_score', + 'tout.output_key', + 'tout.content', + ]); + + $query->chunk($this->batchSize, function ($records) { + DB::table('backend_data_pool')->insert( + $records->map(fn($r) => [ + ...(array) $r, + 'synced_at' => now(), + ])->toArray() + ); + }); + } +} +``` + +#### TransformDataPoolToProduction + +```php +// app/Jobs/TransformDataPoolToProduction.php + +class TransformDataPoolToProduction implements ShouldQueue +{ + public function handle(): void + { + $transactionIds = DB::table('backend_data_pool') + ->distinct() + ->pluck('transaction_id'); + + foreach ($transactionIds as $transactionId) { + $outputs = DB::table('backend_data_pool') + ->where('transaction_id', $transactionId) + ->get(); + + $company = $this->createOrUpdateCompany($outputs); + $this->createOrUpdateTransaction($company, $outputs); + } + } + + private function createOrUpdateCompany($outputs): Company + { + $first = $outputs->first(); + + return Company::updateOrCreate( + ['name' => $first->corporate_entity], + [ + 'sector' => $this->extractValue($outputs, 'corporate_sector'), + 'country' => $this->extractValue($outputs, 'corporate_countryofincorporation'), + 'headquarters' => $this->extractValue($outputs, 'corporate_HQ'), + 'kyc_risk_level' => $this->calculateKycRiskLevel($first->risk_score), + 'summary' => $this->extractValue($outputs, 'corporate_summary'), + ] + ); + } +} +``` + +### Commands + +```bash +# Manuelle Sync-Commands +php artisan backend:sync-data-pool --full +php artisan backend:sync-data-pool --incremental +php artisan backend:transform-data-pool +php artisan backend:rebuild # Full Sync + Transform +``` + +--- + +## Scheduling & Automation + +### Laravel Scheduler Configuration + +```php +// routes/console.php + +// Incremental Sync: 0:00, 6:00, 12:00, 18:00 +Schedule::job(new SyncBackendDataPool(fullSync: false, batchSize: 1000)) + ->everySixHours() + ->name('sync-backend-data-pool-incremental') + ->withoutOverlapping(1800); + +// Full Sync: Sonntag 3:00 Uhr +Schedule::job(new SyncBackendDataPool(fullSync: true, batchSize: 1000)) + ->weeklyOn(0, '03:00') + ->name('sync-backend-data-pool-full') + ->withoutOverlapping(3600); + +// Transformation: 0:30, 6:30, 12:30, 18:30 +Schedule::job(new TransformDataPoolToProduction(batchSize: 100)) + ->cron('30 */6 * * *') + ->name('transform-data-pool-to-production') + ->withoutOverlapping(1800); +``` + +### Aktivierung + +#### Lokale Entwicklung +```bash +php artisan schedule:work +``` + +#### Produktion (Crontab) +```bash +crontab -e + +# FÃŒge hinzu: +* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1 +``` + +#### Mit Supervisor +```ini +[program:laravel-scheduler] +command=php /path/to/project/artisan schedule:work +autostart=true +autorestart=true +``` + +--- + +## Sicherheit & Authentication + +### Laravel Fortify + +Die Applikation nutzt **Laravel Fortify** fÃŒr Authentication: + +```php +// config/fortify.php - Enabled Features + +Features::registration(), +Features::resetPasswords(), +Features::emailVerification(), +Features::updateProfileInformation(), +Features::updatePasswords(), +Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, +]), +``` + +### Two-Factor Authentication (2FA) + +- QR-Code basierte 2FA mit TOTP +- Recovery Codes +- Passwort-BestÀtigung erforderlich + +### Authorization + +- Route Middleware: `auth`, `verified` +- Password Confirmation fÃŒr sensible Aktionen + +### Session Management + +- Standard Laravel Session-Driver +- CSRF Protection aktiviert + +--- + +## Deployment-Architektur + +### Docker Setup + +```yaml +# docker-compose.yml + +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + volumes: + - .:/var/www/html + environment: + - DB_CONNECTION2=pgsql + - DB_HOST2=db + - DB_PORT2=5432 +``` + +### Kubernetes CronJob + +```yaml +# k8s-cronjob.yaml + +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backend-sync +spec: + schedule: "0 */6 * * *" # Every 6 hours + jobTemplate: + spec: + template: + spec: + containers: + - name: sync + image: risk-platform:latest + command: ["php", "artisan", "backend:sync-data-pool", "--incremental"] +``` + +### Environment Configuration + +```bash +# .env (Production) + +APP_ENV=production +APP_DEBUG=false + +DB_CONNECTION2=pgsql +DB_HOST2=postgres.internal +DB_PORT2=5432 +DB_DATABASE2=risk_platform_db +DB_USERNAME2=risk_user +DB_PASSWORD2=*** + +CACHE_STORE=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +``` + +### Scaling Considerations + +**Horizontal Scaling:** +- Stateless Laravel App → Load Balancer → Multiple App Instances +- Shared Redis fÃŒr Sessions/Cache +- Shared PostgreSQL Cluster + +**Performance Optimization:** +- Redis Caching fÃŒr Companies/Transactions +- Database Indexing (transaction_id, output_key, company_id) +- Query Optimization mit Eager Loading +- CDN fÃŒr Static Assets + +--- + +## Monitoring & Logging + +### Laravel Logs + +```bash +# Application Logs +tail -f storage/logs/laravel.log + +# Nur Sync-Logs +tail -f storage/logs/laravel.log | grep SyncBackendDataPool + +# Nur Transform-Logs +tail -f storage/logs/laravel.log | grep TransformDataPoolToProduction +``` + +### Queue Monitoring + +```bash +php artisan queue:work --tries=3 +php artisan queue:monitor +``` + +### Database Performance Monitoring + +```sql +-- Sync-Performance (Records pro Minute) +SELECT + DATE_TRUNC('minute', synced_at) as minute, + COUNT(*) as records_synced +FROM public.backend_data_pool +WHERE synced_at >= NOW() - INTERVAL '1 hour' +GROUP BY DATE_TRUNC('minute', synced_at) +ORDER BY minute DESC; +``` + +### Optional: Laravel Telescope + +```bash +composer require laravel/telescope --dev +php artisan telescope:install +php artisan migrate +``` + +--- + +## Zusammenfassung + +### System-Eigenschaften + +✅ **Monolithische Laravel-Applikation** mit klarer Layer-Trennung +✅ **ETL-Pipeline** mit zweistufiger Transformation (Sync → Transform) +✅ **Automatisierung** via Laravel Scheduler (alle 6h) +✅ **Reactive Frontend** mit Livewire Volt + Flux UI +✅ **Skalierbar** via Queue-System und horizontaler App-Skalierung +✅ **Sicher** mit Fortify Authentication + 2FA +✅ **Testbar** mit Pest Testing Framework + +### Performance-Metriken + +| Operation | Datenmenge | Dauer | Frequenz | +|-----------|-----------|-------|----------| +| Incremental Sync | ~100-500 Records | 5-10s | Alle 6h | +| Full Sync | 7,471 Records | 30-60s | Sonntag 3am | +| Transformation | 74 Transactions | 30-60s | Alle 6h (30min nach Sync) | +| Page Load (Companies) | 20 Records | <100ms | On Demand | + +### NÀchste Schritte + +- [ ] API-Dokumentation (OpenAPI/Swagger) +- [ ] Testing-Guide & Coverage-Ziele +- [ ] Deployment-Runbooks +- [ ] Entwickler-Onboarding-Guide +- [ ] Performance-Optimierung (Caching-Strategie) + +--- + +**Erstellt:** 2025-11-24 +**Version:** 1.0 +**Autor:** Risk Intelligence Platform Team diff --git a/docs/data-model-documentation.md b/docs/data-model-documentation.md new file mode 100644 index 0000000..ec248cf --- /dev/null +++ b/docs/data-model-documentation.md @@ -0,0 +1,1153 @@ +# 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) + +
+📋 VollstÀndige Liste der 102 JSONB Output-Spalten (klicken zum Ausklappen) + +**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 +``` +
+ +#### 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 diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md new file mode 100644 index 0000000..72f2c6b --- /dev/null +++ b/docs/deployment-guide.md @@ -0,0 +1,1035 @@ +# Deployment-Guide: Risk Intelligence Platform + +## 📋 Inhaltsverzeichnis + +1. [Übersicht](#ÃŒbersicht) +2. [Voraussetzungen](#voraussetzungen) +3. [Docker Deployment](#docker-deployment) +4. [Kubernetes Deployment](#kubernetes-deployment) +5. [Production Checklist](#production-checklist) +6. [Environment Configuration](#environment-configuration) +7. [Database Migration](#database-migration) +8. [Monitoring & Logging](#monitoring--logging) +9. [Backup & Recovery](#backup--recovery) +10. [Rollback-Strategie](#rollback-strategie) +11. [Troubleshooting](#troubleshooting) + +--- + +## Übersicht + +Die Risk Intelligence Platform kann auf verschiedene Arten deployed werden: + +- **Docker** - Standalone Container oder Docker Compose +- **Kubernetes** - Production-ready mit CronJobs fÃŒr Scheduled Tasks +- **Traditional** - Apache/Nginx + PHP-FPM auf Linux-Server + +### Deployment-Architektur + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Production Environment │ +├────────────────────────────────────────────────────────────── +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Nginx │ │ PHP-FPM │ │ Supervisor │ │ +│ │ (Port 80) │ │ (Laravel) │ │ (Queue) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └─────────────────┮─────────────────┘ │ +│ │ │ +│ â–Œ │ +│ ┌────────────────────────────────┐ │ +│ │ PostgreSQL Database │ │ +│ │ (Public + Backend Schema) │ │ +│ └────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────────────────┐ │ +│ │ Kubernetes CronJob (Laravel Scheduler) │ │ +│ │ - SyncBackendDataPool (every 6h) │ │ +│ │ - TransformDataPool (every 6h at :30) │ │ +│ └────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Voraussetzungen + +### System Requirements + +| Komponente | Minimum | Empfohlen | +|------------|---------|-----------| +| **CPU** | 2 Cores | 4 Cores | +| **RAM** | 2 GB | 4 GB | +| **Disk** | 20 GB | 50 GB SSD | +| **OS** | Linux (Ubuntu 22.04+) | Alpine Linux 3.22 | + +### Software Requirements + +| Software | Version | Zweck | +|----------|---------|-------| +| **PHP** | 8.2+ | Runtime | +| **Composer** | 2.0+ | Dependencies | +| **Node.js** | 18+ | Asset Building | +| **PostgreSQL** | 14+ | Database | +| **Nginx** | 1.24+ | Web Server | +| **Supervisor** | 4.0+ | Process Management | +| **Docker** | 24.0+ | Containerization (optional) | +| **Kubernetes** | 1.28+ | Orchestration (optional) | + +--- + +## Docker Deployment + +### Multi-Stage Dockerfile + +Die Applikation nutzt einen **Multi-Stage Build** fÃŒr optimale Image-Größe. + +```dockerfile +# Stage 1: Composer Dependencies +FROM composer:2 AS vendor +WORKDIR /app +COPY composer.json composer.lock ./ +RUN composer install --no-dev --optimize-autoloader + +# Stage 2: Runtime +FROM alpine:3.22 +# Install PHP 8.2, Nginx, Supervisor, PostgreSQL Client +RUN apk add --no-cache \ + nginx supervisor php82 php82-fpm \ + php82-pgsql php82-pdo_pgsql \ + nodejs npm + +# Copy vendor from build stage +COPY --from=vendor /app/vendor ./vendor +COPY . ./ + +# Build frontend assets +RUN npm ci && npm run build + +# Optimize Laravel +RUN php artisan config:cache && \ + php artisan route:cache && \ + php artisan view:cache + +EXPOSE 80 +CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] +``` + +### Build & Run + +```bash +# Build Image +docker build -t risk-platform:latest . + +# Run Container +docker run -d \ + --name risk-platform \ + -p 8080:80 \ + -v $(pwd)/.env:/var/www/html/.env \ + -v $(pwd)/storage:/var/www/html/storage \ + risk-platform:latest +``` + +### Docker Compose + +```yaml +# docker-compose.yml +version: "3.9" + +services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: risk-platform + restart: unless-stopped + ports: + - "8080:80" + volumes: + - .env:/var/www/html/.env + - ./storage:/var/www/html/storage + depends_on: + - postgres + networks: + - risk-net + + postgres: + image: postgres:16-alpine + container_name: risk-postgres + restart: unless-stopped + environment: + POSTGRES_DB: risk_platform_db + POSTGRES_USER: risk_user + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + ports: + - "5432:5432" + networks: + - risk-net + + scheduler: + build: + context: . + dockerfile: Dockerfile + container_name: risk-scheduler + restart: unless-stopped + command: ["php", "artisan", "schedule:work"] + volumes: + - .env:/var/www/html/.env + depends_on: + - postgres + networks: + - risk-net + +volumes: + postgres-data: + +networks: + risk-net: + driver: bridge +``` + +**Starten:** +```bash +docker-compose up -d +``` + +--- + +## Kubernetes Deployment + +### Namespace erstellen + +```yaml +# namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: risk-platform +``` + +```bash +kubectl apply -f namespace.yaml +``` + +### ConfigMap fÃŒr Environment + +```yaml +# configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: risk-platform-config + namespace: risk-platform +data: + env: | + APP_NAME="Risk Intelligence Platform" + APP_ENV=production + APP_KEY=base64:xxx + APP_DEBUG=false + APP_URL=https://risk.example.com + + DB_CONNECTION2=pgsql + DB_HOST2=postgres-service + DB_PORT2=5432 + DB_DATABASE2=risk_platform_db + DB_USERNAME2=risk_user + DB_PASSWORD2=xxx + + CACHE_STORE=redis + SESSION_DRIVER=redis + QUEUE_CONNECTION=redis + + REDIS_HOST=redis-service + REDIS_PORT=6379 +``` + +```bash +kubectl apply -f configmap.yaml +``` + +### Deployment + +```yaml +# deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: risk-platform + namespace: risk-platform + labels: + app: risk-platform +spec: + replicas: 2 + selector: + matchLabels: + app: risk-platform + template: + metadata: + labels: + app: risk-platform + spec: + containers: + - name: app + image: registry.example.com/risk-platform:latest + imagePullPolicy: Always + ports: + - containerPort: 80 + name: http + env: + - name: APP_ENV + value: "production" + volumeMounts: + - name: config + mountPath: /var/www/html/.env + subPath: env + - name: storage + mountPath: /var/www/html/storage + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 10 + periodSeconds: 5 + volumes: + - name: config + configMap: + name: risk-platform-config + - name: storage + persistentVolumeClaim: + claimName: risk-platform-storage +``` + +```bash +kubectl apply -f deployment.yaml +``` + +### Service + +```yaml +# service.yaml +apiVersion: v1 +kind: Service +metadata: + name: risk-platform-service + namespace: risk-platform +spec: + selector: + app: risk-platform + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP +``` + +### Ingress (Optional) + +```yaml +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: risk-platform-ingress + namespace: risk-platform + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + ingressClassName: nginx + tls: + - hosts: + - risk.example.com + secretName: risk-platform-tls + rules: + - host: risk.example.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: risk-platform-service + port: + number: 80 +``` + +### CronJob fÃŒr Laravel Scheduler + +```yaml +# cronjob.yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: sync-backend-data-pool + namespace: risk-platform +spec: + schedule: "0 */6 * * *" # Every 6 hours + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + metadata: + labels: + app: sync-backend-data-pool + spec: + restartPolicy: OnFailure + containers: + - name: sync + image: registry.example.com/risk-platform:latest + command: ["php", "artisan", "schedule:run"] + volumeMounts: + - name: config + mountPath: /var/www/html/.env + subPath: env + volumes: + - name: config + configMap: + name: risk-platform-config +``` + +```bash +kubectl apply -f cronjob.yaml +``` + +### Alle Ressourcen deployen + +```bash +# Complete Deployment +kubectl apply -f namespace.yaml +kubectl apply -f configmap.yaml +kubectl apply -f deployment.yaml +kubectl apply -f service.yaml +kubectl apply -f ingress.yaml +kubectl apply -f cronjob.yaml + +# Status prÃŒfen +kubectl get all -n risk-platform + +# Logs anschauen +kubectl logs -f deployment/risk-platform -n risk-platform +``` + +--- + +## Production Checklist + +### Pre-Deployment + +- [ ] **Environment Configuration** + - [ ] `.env` auf `production` gesetzt + - [ ] `APP_DEBUG=false` + - [ ] `APP_KEY` generiert + - [ ] Sichere Passwörter fÃŒr DB + +- [ ] **Database** + - [ ] PostgreSQL lÀuft + - [ ] Schemas erstellt (public, backend) + - [ ] User-Rechte vergeben + - [ ] Backup-Strategie definiert + +- [ ] **Dependencies** + - [ ] `composer install --no-dev --optimize-autoloader` + - [ ] `npm ci && npm run build` + +- [ ] **Laravel Optimization** + - [ ] `php artisan config:cache` + - [ ] `php artisan route:cache` + - [ ] `php artisan view:cache` + - [ ] `php artisan event:cache` + +- [ ] **Security** + - [ ] HTTPS aktiviert (SSL/TLS) + - [ ] Firewall konfiguriert + - [ ] `.env` nicht im Git + - [ ] Secrets in Vault/ConfigMap + +### Post-Deployment + +- [ ] **Migrations** + - [ ] `php artisan migrate --force` + - [ ] Backup vor Migration + +- [ ] **Tests** + - [ ] Health Check (`/`) + - [ ] Login funktioniert + - [ ] Database Connection + - [ ] Scheduled Jobs + +- [ ] **Monitoring** + - [ ] Logs werden geschrieben + - [ ] Metrics werden gesammelt + - [ ] Alerts konfiguriert + +- [ ] **Performance** + - [ ] OPcache aktiviert + - [ ] Redis fÃŒr Cache/Session + - [ ] Database Indizes + +--- + +## Environment Configuration + +### Production .env + +```ini +# Application +APP_NAME="Risk Intelligence Platform" +APP_ENV=production +APP_KEY=base64:xxx # Generate with: php artisan key:generate +APP_DEBUG=false +APP_URL=https://risk.example.com + +# Locale +APP_LOCALE=de +APP_FALLBACK_LOCALE=en + +# Database (PostgreSQL) +DB_CONNECTION2=pgsql +DB_HOST2=postgres-host +DB_PORT2=5432 +DB_DATABASE2=risk_platform_db +DB_USERNAME2=risk_user +DB_PASSWORD2=xxx # Use strong password! + +# Cache & Session (Redis) +CACHE_STORE=redis +SESSION_DRIVER=redis +SESSION_LIFETIME=120 + +# Queue +QUEUE_CONNECTION=redis + +# Redis +REDIS_HOST=redis-host +REDIS_PASSWORD=null +REDIS_PORT=6379 + +# Mail +MAIL_MAILER=smtp +MAIL_HOST=smtp.example.com +MAIL_PORT=587 +MAIL_USERNAME=noreply@example.com +MAIL_PASSWORD=xxx +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS=noreply@example.com +MAIL_FROM_NAME="${APP_NAME}" + +# Logging +LOG_CHANNEL=stack +LOG_LEVEL=error # Production: error, nicht debug! + +# Security +SESSION_SECURE_COOKIE=true +SESSION_SAME_SITE=strict +``` + +### Secrets Management + +**Kubernetes Secrets:** +```bash +# Create Secret +kubectl create secret generic risk-platform-secrets \ + --from-literal=APP_KEY=base64:xxx \ + --from-literal=DB_PASSWORD=xxx \ + --from-literal=REDIS_PASSWORD=xxx \ + -n risk-platform + +# Use in Deployment +env: +- name: APP_KEY + valueFrom: + secretKeyRef: + name: risk-platform-secrets + key: APP_KEY +``` + +--- + +## Database Migration + +### Pre-Migration Backup + +```bash +# PostgreSQL Backup +pg_dump -h localhost -U risk_user -d risk_platform_db > backup_$(date +%Y%m%d_%H%M%S).sql + +# Verify Backup +psql -h localhost -U risk_user -d risk_platform_db_test < backup_xxx.sql +``` + +### Run Migrations + +```bash +# Production Migration +php artisan migrate --force + +# With Output +php artisan migrate --force --verbose + +# Rollback (if needed) +php artisan migrate:rollback --force +``` + +### Zero-Downtime Migration + +```bash +# 1. Maintenance Mode +php artisan down --message="Updating..." --retry=60 + +# 2. Pull latest code +git pull origin main + +# 3. Update dependencies +composer install --no-dev --optimize-autoloader + +# 4. Run migrations +php artisan migrate --force + +# 5. Clear & cache +php artisan optimize:clear +php artisan config:cache +php artisan route:cache +php artisan view:cache + +# 6. Restart services +sudo supervisorctl restart all + +# 7. Exit maintenance mode +php artisan up +``` + +--- + +## Monitoring & Logging + +### Application Logs + +```bash +# Laravel Logs +tail -f storage/logs/laravel.log + +# Nur Errors +tail -f storage/logs/laravel.log | grep ERROR + +# Mit Laravel Pail +php artisan pail --filter=error +``` + +### Nginx Logs + +```bash +# Access Log +tail -f /var/log/nginx/access.log + +# Error Log +tail -f /var/log/nginx/error.log +``` + +### Kubernetes Logs + +```bash +# Pod Logs +kubectl logs -f deployment/risk-platform -n risk-platform + +# Alle Pods +kubectl logs -f -l app=risk-platform -n risk-platform + +# CronJob Logs +kubectl logs -f cronjob/sync-backend-data-pool -n risk-platform +``` + +### Health Checks + +```bash +# Application Health +curl https://risk.example.com/ + +# Database Connection +php artisan tinker +>>> DB::connection()->getPdo() + +# Queue Status +php artisan queue:monitor + +# Scheduler Status +php artisan schedule:list +``` + +### Monitoring-Tools (Optional) + +**Laravel Telescope:** +```bash +composer require laravel/telescope --dev +php artisan telescope:install +php artisan migrate +``` + +**Prometheus + Grafana:** +- Metrics-Export via Laravel Package +- Custom Dashboards fÃŒr Transactions, Companies +- Alerts bei hoher Error-Rate + +--- + +## Backup & Recovery + +### Automated Backup Script + +```bash +#!/bin/bash +# backup.sh + +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="/backups" +DB_NAME="risk_platform_db" + +# Database Backup +pg_dump -h localhost -U risk_user $DB_NAME > $BACKUP_DIR/db_$DATE.sql + +# Storage Backup +tar -czf $BACKUP_DIR/storage_$DATE.tar.gz storage/ + +# Keep only last 7 days +find $BACKUP_DIR -type f -mtime +7 -delete + +echo "Backup completed: $DATE" +``` + +**Crontab:** +```bash +# Daily backup at 2 AM +0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1 +``` + +### Kubernetes Backup + +```yaml +# backup-cronjob.yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: database-backup + namespace: risk-platform +spec: + schedule: "0 2 * * *" # Daily at 2 AM + jobTemplate: + spec: + template: + spec: + containers: + - name: backup + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + pg_dump -h postgres-service -U risk_user risk_platform_db > /backup/db_$(date +%Y%m%d).sql + volumeMounts: + - name: backup-storage + mountPath: /backup + volumes: + - name: backup-storage + persistentVolumeClaim: + claimName: backup-pvc + restartPolicy: OnFailure +``` + +### Recovery Procedure + +```bash +# 1. Stop Application +kubectl scale deployment risk-platform --replicas=0 -n risk-platform + +# 2. Restore Database +psql -h localhost -U risk_user -d risk_platform_db < backup_xxx.sql + +# 3. Verify Data +psql -h localhost -U risk_user -d risk_platform_db +\dt +SELECT COUNT(*) FROM companies; + +# 4. Restart Application +kubectl scale deployment risk-platform --replicas=2 -n risk-platform +``` + +--- + +## Rollback-Strategie + +### Git-based Rollback + +```bash +# 1. Identify previous version +git log --oneline -10 + +# 2. Checkout previous version +git checkout + +# 3. Rebuild & Deploy +docker build -t risk-platform:rollback . +docker push registry.example.com/risk-platform:rollback + +# 4. Update Kubernetes +kubectl set image deployment/risk-platform \ + app=registry.example.com/risk-platform:rollback \ + -n risk-platform +``` + +### Database Rollback + +```bash +# Rollback last migration +php artisan migrate:rollback --step=1 --force + +# Rollback specific batch +php artisan migrate:rollback --batch=3 --force + +# Full rollback to fresh state (CAUTION!) +php artisan migrate:fresh --force +``` + +### Blue-Green Deployment + +```yaml +# deployment-blue.yaml (current) +metadata: + name: risk-platform-blue + labels: + app: risk-platform + version: blue + +# deployment-green.yaml (new version) +metadata: + name: risk-platform-green + labels: + app: risk-platform + version: green + +# Switch Traffic +# Change service selector from blue to green +kubectl patch service risk-platform-service \ + -p '{"spec":{"selector":{"version":"green"}}}' \ + -n risk-platform +``` + +--- + +## Troubleshooting + +### Problem: Container startet nicht + +**Diagnose:** +```bash +docker logs risk-platform +kubectl logs deployment/risk-platform -n risk-platform +``` + +**HÀufige Ursachen:** +- `.env` fehlt oder ungÃŒltig +- Database Connection fehlgeschlagen +- Permissions auf `storage/` fehlen + +**Lösung:** +```bash +# Check .env +docker exec -it risk-platform cat /var/www/html/.env + +# Fix permissions +docker exec -it risk-platform chown -R nginx:nginx storage +``` + +--- + +### Problem: "500 Internal Server Error" + +**Diagnose:** +```bash +# Laravel Logs +tail -f storage/logs/laravel.log + +# Nginx Error Log +tail -f /var/log/nginx/error.log +``` + +**HÀufige Ursachen:** +- `APP_KEY` nicht gesetzt +- Config Cache veraltet +- Database Connection Error + +**Lösung:** +```bash +# Generate APP_KEY +php artisan key:generate --force + +# Clear caches +php artisan optimize:clear + +# Test DB connection +php artisan tinker +>>> DB::connection()->getPdo() +``` + +--- + +### Problem: Scheduled Jobs laufen nicht + +**Diagnose:** +```bash +# Check CronJob Status +kubectl get cronjobs -n risk-platform + +# Check Job History +kubectl get jobs -n risk-platform + +# Check Logs +kubectl logs job/sync-backend-data-pool-xxx -n risk-platform +``` + +**Lösung:** +```bash +# Test Schedule manually +php artisan schedule:run + +# Test specific Job +php artisan backend:sync-data-pool --incremental +``` + +--- + +### Problem: High Memory Usage + +**Diagnose:** +```bash +# Container Stats +docker stats risk-platform + +# Pod Resource Usage +kubectl top pod -n risk-platform +``` + +**Lösung:** +- Reduce batch size in Jobs +- Increase memory limits +- Enable OPcache +- Use Redis for cache/session + +--- + +## Performance Optimization + +### OPcache Configuration + +```ini +; /etc/php82/conf.d/opcache.ini +opcache.enable=1 +opcache.memory_consumption=256 +opcache.interned_strings_buffer=16 +opcache.max_accelerated_files=10000 +opcache.revalidate_freq=60 +opcache.fast_shutdown=1 +``` + +### Redis Configuration + +```yaml +# redis.conf +maxmemory 512mb +maxmemory-policy allkeys-lru +``` + +### Database Optimization + +```sql +-- Index on frequently queried columns +CREATE INDEX idx_transactions_status ON transactions(status); +CREATE INDEX idx_transactions_risk_score ON transactions(risk_score); +CREATE INDEX idx_companies_kyc_risk_level ON companies(kyc_risk_level); + +-- Vacuum regularly +VACUUM ANALYZE companies; +VACUUM ANALYZE transactions; +``` + +--- + +## Security Best Practices + +### SSL/TLS + +```yaml +# ingress.yaml with TLS +spec: + tls: + - hosts: + - risk.example.com + secretName: risk-platform-tls +``` + +### Firewall Rules + +```bash +# Allow HTTP/HTTPS only +ufw allow 80/tcp +ufw allow 443/tcp +ufw enable +``` + +### Security Headers + +```nginx +# nginx.conf +add_header X-Frame-Options "SAMEORIGIN"; +add_header X-Content-Type-Options "nosniff"; +add_header X-XSS-Protection "1; mode=block"; +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; +``` + +--- + +## Zusammenfassung + +### Deployment-Optionen + +| Option | KomplexitÀt | Skalierbarkeit | Empfohlen fÃŒr | +|--------|-------------|----------------|---------------| +| **Docker** | Niedrig | Mittel | Development, Small Production | +| **Kubernetes** | Hoch | Hoch | Production, Enterprise | +| **Traditional** | Mittel | Niedrig | Legacy Systems | + +### Checkliste fÃŒr Production + +✅ Environment auf `production` gesetzt +✅ `APP_DEBUG=false` +✅ HTTPS aktiviert +✅ Database-Backup automatisiert +✅ Monitoring & Logging konfiguriert +✅ Scheduled Jobs laufen +✅ Health Checks aktiv +✅ Secrets sicher gespeichert +✅ Rollback-Strategie getestet + +--- + +**Erstellt:** 2025-11-24 +**Version:** 1.0 +**Autor:** Risk Intelligence Platform Team diff --git a/docs/developer-setup-guide.md b/docs/developer-setup-guide.md new file mode 100644 index 0000000..3129b81 --- /dev/null +++ b/docs/developer-setup-guide.md @@ -0,0 +1,991 @@ +# Entwickler-Setup-Guide: Risk Intelligence Platform + +## 📋 Inhaltsverzeichnis + +1. [Voraussetzungen](#voraussetzungen) +2. [Schnellstart](#schnellstart) +3. [Detaillierte Installation](#detaillierte-installation) +4. [Datenbank-Setup](#datenbank-setup) +5. [Environment-Konfiguration](#environment-konfiguration) +6. [Frontend-Assets](#frontend-assets) +7. [Erste Schritte](#erste-schritte) +8. [Development-Workflow](#development-workflow) +9. [Troubleshooting](#troubleshooting) +10. [NÃŒtzliche Commands](#nÃŒtzliche-commands) + +--- + +## Voraussetzungen + +### Erforderliche Software + +| Software | Mindestversion | Empfohlen | Installation | +|----------|---------------|-----------|--------------| +| **PHP** | 8.2 | 8.4.14+ | [Laravel Herd](https://herd.laravel.com) | +| **Composer** | 2.0 | Latest | Via Herd oder [getcomposer.org](https://getcomposer.org) | +| **Node.js** | 18.x | 20.x+ | [nodejs.org](https://nodejs.org) | +| **npm** | 9.x | Latest | Mit Node.js installiert | +| **PostgreSQL** | 14.x | 16.x+ | [postgresql.org](https://www.postgresql.org) | +| **Git** | 2.x | Latest | [git-scm.com](https://git-scm.com) | + +### Empfohlene Tools + +- **Laravel Herd** - Lokale PHP-Entwicklungsumgebung (macOS) +- **TablePlus** / **pgAdmin** - PostgreSQL GUI +- **VS Code** - Editor mit PHP/Laravel Extensions +- **Postman** / **Insomnia** - API Testing (optional) + +### PHP Extensions + +Folgende PHP Extensions werden benötigt: + +```bash +# PrÃŒfe installierte Extensions +php -m + +# Benötigte Extensions: +- pdo_pgsql # PostgreSQL Driver +- mbstring # Multibyte String +- xml # XML Support +- curl # HTTP Client +- bcmath # Precision Math +- fileinfo # File Information +- json # JSON Support +- openssl # Encryption +- tokenizer # PHP Tokenizer +- zip # ZIP Archive +``` + +--- + +## Schnellstart + +FÃŒr erfahrene Entwickler - vollstÀndige Installation in 5 Minuten: + +```bash +# 1. Repository klonen +git clone risk-platform +cd risk-platform + +# 2. Dependencies installieren & Setup ausfÃŒhren +composer setup + +# 3. PostgreSQL-Datenbank erstellen (siehe unten) +# Anpassen: .env mit DB-Credentials + +# 4. Migrations ausfÃŒhren +php artisan migrate --seed + +# 5. Development-Server starten +composer run dev +``` + +**Fertig!** Die Applikation lÀuft auf [http://localhost:8000](http://localhost:8000) + +--- + +## Detaillierte Installation + +### Schritt 1: Repository klonen + +```bash +# Via HTTPS +git clone https://github.com/your-org/risk-platform.git +cd risk-platform + +# Oder via SSH +git clone git@github.com:your-org/risk-platform.git +cd risk-platform +``` + +### Schritt 2: PHP Dependencies installieren + +```bash +composer install +``` + +**Was passiert dabei?** +- Installiert alle Laravel-Pakete +- Installiert Livewire, Flux UI, Fortify +- Installiert Dev-Dependencies (Pest, Pint, Boost) +- Generiert Autoload-Files + +### Schritt 3: Environment-Datei erstellen + +```bash +# .env.example nach .env kopieren +cp .env.example .env + +# Application Key generieren +php artisan key:generate +``` + +### Schritt 4: Node Dependencies installieren + +```bash +npm install +``` + +**Was passiert dabei?** +- Installiert Vite +- Installiert Tailwind CSS v4 +- Installiert Build-Tools + +--- + +## Datenbank-Setup + +### PostgreSQL Installation + +#### macOS (mit Homebrew) + +```bash +# PostgreSQL installieren +brew install postgresql@16 + +# Als Service starten +brew services start postgresql@16 + +# Verbindung testen +psql postgres +``` + +#### macOS (mit Postgres.app) + +1. Download von [postgresapp.com](https://postgresapp.com) +2. App starten +3. Default Server initialisieren + +#### Ubuntu/Debian + +```bash +sudo apt update +sudo apt install postgresql postgresql-contrib +sudo systemctl start postgresql +sudo systemctl enable postgresql +``` + +### Datenbank erstellen + +```bash +# Als postgres User einloggen +psql postgres + +# Oder direkt mit spezifischem User +sudo -u postgres psql +``` + +```sql +-- Datenbank erstellen +CREATE DATABASE risk_platform_db; + +-- User erstellen (optional) +CREATE USER risk_user WITH ENCRYPTED PASSWORD 'your_secure_password'; + +-- Rechte vergeben +GRANT ALL PRIVILEGES ON DATABASE risk_platform_db TO risk_user; + +-- In PostgreSQL 15+ auch Schema-Rechte vergeben +\c risk_platform_db +GRANT ALL ON SCHEMA public TO risk_user; + +-- Exit +\q +``` + +### Schemas erstellen + +```sql +-- Verbindung zur DB +psql -U risk_user -d risk_platform_db + +-- PUBLIC Schema (wird automatisch erstellt) +-- BACKEND Schema erstellen +CREATE SCHEMA IF NOT EXISTS backend; + +-- Rechte fÃŒr BACKEND Schema +GRANT ALL ON SCHEMA backend TO risk_user; + +-- Exit +\q +``` + +### Verbindung testen + +```bash +# Direkter Connect +psql -h 127.0.0.1 -p 5432 -U risk_user -d risk_platform_db + +# Mit Laravel Tinker +php artisan tinker +>>> DB::connection('pgsql_second')->select('SELECT version()'); +>>> DB::connection('backend')->select('SELECT current_schema()'); +``` + +--- + +## Environment-Konfiguration + +### .env Datei anpassen + +```bash +# .env öffnen und anpassen +nano .env +``` + +#### Grundkonfiguration + +```ini +APP_NAME="Risk Intelligence Platform" +APP_ENV=local +APP_KEY=base64:xxx # Wird von key:generate erstellt +APP_DEBUG=true +APP_URL=http://localhost:8000 + +APP_LOCALE=de +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=de_DE +``` + +#### Datenbank-Konfiguration (Wichtig!) + +```ini +# Default Connection (PUBLIC Schema) +DB_CONNECTION=pgsql +DB_CONNECTION2=pgsql +DB_HOST2=127.0.0.1 +DB_PORT2=5432 +DB_DATABASE2=risk_platform_db +DB_USERNAME2=risk_user +DB_PASSWORD2=your_secure_password +``` + +**Wichtig:** Die Applikation nutzt `DB_*2` Variablen fÃŒr PostgreSQL! + +#### Cache & Session + +```ini +CACHE_STORE=database +SESSION_DRIVER=database +QUEUE_CONNECTION=database +``` + +**Warum database?** +- Einfaches Setup ohne Redis +- Funktioniert out-of-the-box +- FÃŒr Produktion: Wechsel zu Redis empfohlen + +#### Mail-Konfiguration (Development) + +```ini +MAIL_MAILER=log +MAIL_FROM_ADDRESS="noreply@risk-platform.test" +MAIL_FROM_NAME="${APP_NAME}" +``` + +Mails werden in `storage/logs/laravel.log` gespeichert. + +#### Optional: Redis (fÃŒr bessere Performance) + +```ini +CACHE_STORE=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 +``` + +### VollstÀndige .env Vorlage + +
+Klicken fÌr vollstÀndige .env Beispiel-Datei + +```ini +APP_NAME="Risk Intelligence Platform" +APP_ENV=local +APP_KEY=base64:xxx +APP_DEBUG=true +APP_URL=http://localhost:8000 + +APP_LOCALE=de +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=de_DE + +APP_MAINTENANCE_DRIVER=file +PHP_CLI_SERVER_WORKERS=4 +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +# PostgreSQL Configuration +DB_CONNECTION=pgsql +DB_CONNECTION2=pgsql +DB_HOST2=127.0.0.1 +DB_PORT2=5432 +DB_DATABASE2=risk_platform_db +DB_USERNAME2=risk_user +DB_PASSWORD2=your_secure_password + +# Session & Cache +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +CACHE_STORE=database +QUEUE_CONNECTION=database + +# Broadcasting & Filesystem +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local + +# Mail +MAIL_MAILER=log +MAIL_FROM_ADDRESS="noreply@risk-platform.test" +MAIL_FROM_NAME="${APP_NAME}" + +# Vite +VITE_APP_NAME="${APP_NAME}" +``` +
+ +--- + +## Frontend-Assets + +### Assets kompilieren + +```bash +# Development Build (einmalig) +npm run build + +# Development mit Hot Reload +npm run dev +``` + +### Was macht `npm run dev`? + +``` +┌─────────────────────────────────────────────────┐ +│ VITE v7.0.4 ready in X ms │ +│ │ +│ ➜ Local: http://localhost:5173/ │ +│ ➜ Network: use --host to expose │ +│ │ +│ ➜ Laravel: http://localhost:8000 │ +└─────────────────────────────────────────────────┘ + +✓ compiled successfully +``` + +- Startet Vite Dev-Server auf Port 5173 +- Hot Module Replacement (HMR) aktiviert +- Tailwind CSS v4 wird kompiliert +- Livewire-Assets werden gebundled + +### Production Build + +```bash +npm run build + +# Output: +# dist/assets/app-xxxxx.js +# dist/assets/app-xxxxx.css +``` + +--- + +## Erste Schritte + +### 1. Migrations ausfÃŒhren + +```bash +# Alle Migrations ausfÃŒhren +php artisan migrate + +# Mit Seeding (Test-Daten) +php artisan migrate --seed + +# Migration-Status prÃŒfen +php artisan migrate:status +``` + +**Erwartete Tabellen nach Migration:** + +``` +┌──────────────────────────────────┬────────┐ +│ Migration │ Ran? │ +├──────────────────────────────────┌───────── +│ 0001_01_01_000000_create_users │ Yes │ +│ 0001_01_01_000001_create_cache │ Yes │ +│ 0001_01_01_000002_create_jobs │ Yes │ +│ 2025_09_02_075243_add_two_factor │ Yes │ +│ 2025_10_20_181750_create_companies │ Yes │ +│ 2025_10_20_181753_create_transactions │ Yes │ +│ 2025_11_16_140224_add_output_columns │ Yes │ +│ 2025_11_16_150000_create_backend_data_pool │ Yes │ +│ ... │ │ +└──────────────────────────────────┮────────┘ +``` + +### 2. Test-User erstellen + +```bash +php artisan tinker +``` + +```php +// Test-User erstellen +$user = App\Models\User::create([ + 'name' => 'Admin User', + 'email' => 'admin@example.com', + 'password' => bcrypt('password'), + 'email_verified_at' => now(), +]); + +// Oder mit Factory +App\Models\User::factory()->create([ + 'email' => 'test@example.com', +]); + +exit +``` + +### 3. Development-Server starten + +#### Option A: Composer Dev Command (Empfohlen) + +```bash +composer run dev +``` + +**Startet parallel:** +- `php artisan serve` → http://localhost:8000 +- `php artisan queue:listen` → Queue Worker +- `php artisan pail` → Live Logs +- `npm run dev` → Vite Dev Server + +#### Option B: Einzelne Services manuell + +```bash +# Terminal 1: Laravel Server +php artisan serve + +# Terminal 2: Vite Dev Server +npm run dev + +# Terminal 3 (optional): Queue Worker +php artisan queue:work + +# Terminal 4 (optional): Logs +php artisan pail +``` + +#### Option C: Laravel Herd (macOS) + +Wenn du Laravel Herd nutzt: + +1. Projekt in Herd-Verzeichnis verschieben +2. Automatische Domain: `risk-platform.test` +3. HTTPS automatisch aktiviert +4. Kein `php artisan serve` notwendig + +```bash +# In Herd-Verzeichnis verschieben +mv ~/risk-platform ~/Herd/risk-platform + +# Browser öffnen +open https://risk-platform.test +``` + +### 4. Applikation öffnen + +Browser öffnen: [http://localhost:8000](http://localhost:8000) + +**Login-Credentials:** +- Email: `test@example.com` +- Password: `password` + +--- + +## Development-Workflow + +### Typischer Entwicklungstag + +```bash +# 1. Morgen: Repository aktualisieren +git pull origin main + +# 2. Dependencies aktualisieren (falls composer.lock geÀndert) +composer install +npm install + +# 3. Neue Migrations (falls vorhanden) +php artisan migrate + +# 4. Dev-Server starten +composer run dev + +# 5. Am Ende: Code formatieren +vendor/bin/pint + +# 6. Tests ausfÃŒhren +php artisan test + +# 7. Commit & Push +git add . +git commit -m "feat: neue feature" +git push +``` + +### Code-Formatierung + +```bash +# Alle Dateien formatieren +vendor/bin/pint + +# Nur geÀnderte Dateien +vendor/bin/pint --dirty + +# Test-Modus (ohne Änderungen) +vendor/bin/pint --test +``` + +### Tests ausfÃŒhren + +```bash +# Alle Tests +php artisan test + +# Nur Feature Tests +php artisan test --testsuite=Feature + +# Nur Unit Tests +php artisan test --testsuite=Unit + +# Spezifische Test-Datei +php artisan test tests/Feature/CompanyTest.php + +# Mit Filter +php artisan test --filter=testCompanyCreation + +# Mit Coverage (benötigt Xdebug) +php artisan test --coverage +``` + +### Backend-Daten synchronisieren + +```bash +# Stats anzeigen (kein Sync) +php artisan backend:sync-data-pool --stats + +# Incremental Sync +php artisan backend:sync-data-pool --incremental + +# Full Sync +php artisan backend:sync-data-pool --full + +# Transformation +php artisan backend:transform-data-pool + +# Kompletter Rebuild +php artisan backend:rebuild +``` + +### Scheduler lokal testen + +```bash +# Schedule Worker (lÀuft dauerhaft) +php artisan schedule:work + +# Oder: NÀchste Due-Tasks anzeigen +php artisan schedule:list + +# Einzelnen Task testen +php artisan schedule:test sync-backend-data-pool-incremental +``` + +--- + +## Troubleshooting + +### Problem: "SQLSTATE[08006] Connection refused" + +**Ursache:** PostgreSQL lÀuft nicht oder falsche Credentials + +**Lösung:** +```bash +# PostgreSQL-Status prÃŒfen +brew services list | grep postgresql + +# PostgreSQL starten +brew services start postgresql@16 + +# Verbindung testen +psql -h 127.0.0.1 -p 5432 -U risk_user -d risk_platform_db + +# .env prÃŒfen +cat .env | grep DB_ +``` + +--- + +### Problem: "Class 'PDO' not found" + +**Ursache:** PHP PostgreSQL Extension fehlt + +**Lösung:** +```bash +# Installierte Extensions prÃŒfen +php -m | grep pdo + +# Mit Herd (macOS) +# Extensions sind normalerweise vorinstalliert + +# Oder PHP neu installieren via Homebrew +brew reinstall php@8.4 +``` + +--- + +### Problem: "Vite manifest not found" + +**Ursache:** Frontend-Assets nicht kompiliert + +**Lösung:** +```bash +# Development Build +npm run build + +# Oder Dev-Server starten +npm run dev +``` + +--- + +### Problem: "419 Page Expired" bei Forms + +**Ursache:** CSRF-Token ungÃŒltig (Session abgelaufen) + +**Lösung:** +```bash +# Cache leeren +php artisan config:clear +php artisan cache:clear +php artisan view:clear + +# Browser: Hard Refresh (Cmd+Shift+R / Ctrl+Shift+R) +``` + +--- + +### Problem: Migrations laufen nicht + +**Ursache:** Datenbank-Schema fehlt oder keine Rechte + +**Lösung:** +```bash +# Schema-Rechte prÃŒfen +psql -U risk_user -d risk_platform_db + +# In psql: +SELECT schema_name FROM information_schema.schemata; + +# Rechte vergeben +GRANT ALL ON SCHEMA public TO risk_user; +GRANT ALL ON ALL TABLES IN SCHEMA public TO risk_user; + +# Migration erneut versuchen +php artisan migrate +``` + +--- + +### Problem: Queue-Jobs laufen nicht + +**Ursache:** Queue-Worker lÀuft nicht + +**Lösung:** +```bash +# Queue-Worker starten +php artisan queue:work + +# Oder mit Auto-Restart bei Code-Änderungen +php artisan queue:listen + +# Failed Jobs anzeigen +php artisan queue:failed + +# Failed Jobs erneut versuchen +php artisan queue:retry all +``` + +--- + +### Problem: "Class not found" Fehler + +**Ursache:** Autoload-Cache veraltet + +**Lösung:** +```bash +# Autoload neu generieren +composer dump-autoload + +# Alle Caches leeren +php artisan optimize:clear + +# Oder einzeln: +php artisan config:clear +php artisan cache:clear +php artisan route:clear +php artisan view:clear +``` + +--- + +### Problem: Livewire-Components laden nicht + +**Ursache:** Volt-Components nicht registriert + +**Lösung:** +```bash +# Volt Provider prÃŒfen +cat bootstrap/providers.php | grep Volt + +# Cache leeren +php artisan optimize:clear + +# Browser Cache leeren +# Cmd+Shift+R (macOS) oder Ctrl+Shift+R (Windows/Linux) +``` + +--- + +### Problem: Styling fehlt / Tailwind funktioniert nicht + +**Ursache:** Vite-Server lÀuft nicht oder falsche Konfiguration + +**Lösung:** +```bash +# Vite Dev-Server neu starten +npm run dev + +# Oder Production-Build +npm run build + +# Tailwind-Config prÃŒfen +cat tailwind.config.js + +# Node-Modules neu installieren +rm -rf node_modules package-lock.json +npm install +``` + +--- + +## NÃŒtzliche Commands + +### Laravel Artisan + +```bash +# Liste aller Commands +php artisan list + +# Command-Hilfe +php artisan help migrate + +# Interaktive Shell +php artisan tinker + +# Cache Management +php artisan cache:clear +php artisan config:clear +php artisan route:clear +php artisan view:clear +php artisan optimize:clear # Alle Caches auf einmal + +# Database +php artisan db:show # DB-Info anzeigen +php artisan migrate:status # Migration-Status +php artisan migrate:fresh --seed # DB neu aufsetzen + Seeds + +# Make Commands +php artisan make:model Company +php artisan make:controller CompanyController +php artisan make:migration create_companies_table +php artisan make:livewire CompanySearch +php artisan make:test CompanyTest +php artisan make:class Services/MyService + +# Routes +php artisan route:list # Alle Routes anzeigen +php artisan route:list --path=companies # Gefiltert +``` + +### Composer + +```bash +# Dependencies installieren +composer install + +# Development-Dependencies ÃŒberspringen +composer install --no-dev + +# Einzelnes Package installieren +composer require laravel/telescope + +# Package entfernen +composer remove laravel/telescope + +# Autoload regenerieren +composer dump-autoload + +# Setup ausfÃŒhren (custom script) +composer setup + +# Dev-Server starten (custom script) +composer run dev + +# Tests ausfÃŒhren (custom script) +composer test +``` + +### NPM + +```bash +# Dependencies installieren +npm install + +# Dev-Server starten +npm run dev + +# Production Build +npm run build + +# Package installieren +npm install alpine.js + +# Package entfernen +npm uninstall alpine.js + +# Outdated Packages prÃŒfen +npm outdated + +# Updates installieren +npm update +``` + +### Git + +```bash +# Aktueller Branch +git branch + +# Neuen Branch erstellen +git checkout -b feature/neue-funktion + +# Änderungen anzeigen +git status +git diff + +# Commit +git add . +git commit -m "feat: neue funktion" +git push origin feature/neue-funktion + +# Main aktualisieren +git checkout main +git pull origin main +``` + +### PostgreSQL + +```bash +# Verbinden +psql -U risk_user -d risk_platform_db + +# In psql: +\dt # Alle Tabellen +\d companies # Tabellen-Schema anzeigen +\dn # Alle Schemas +\du # Alle User +\l # Alle Datenbanken +\q # Exit + +# Backup erstellen +pg_dump -U risk_user risk_platform_db > backup.sql + +# Backup einspielen +psql -U risk_user risk_platform_db < backup.sql +``` + +--- + +## NÀchste Schritte + +Nach erfolgreichem Setup: + +1. **Architektur-Dokumentation lesen** → [docs/architecture-overview.md](architecture-overview.md) +2. **Backend-Sync Commands kennenlernen** → [docs/backend-sync-commands.md](backend-sync-commands.md) +3. **Code-Konventionen studieren** → [CLAUDE.md](../CLAUDE.md) +4. **Tests schreiben & ausfÃŒhren** → `php artisan test` +5. **Erste Änderungen committen** → `git commit` + +--- + +## Hilfe & Support + +### Dokumentation + +- **Architektur-Übersicht:** [docs/architecture-overview.md](architecture-overview.md) +- **Backend-Sync:** [docs/backend-sync-commands.md](backend-sync-commands.md) +- **Scheduling:** [docs/scheduling-setup.md](scheduling-setup.md) +- **Migration:** [docs/sync-migration-instructions.md](sync-migration-instructions.md) + +### Laravel Dokumentation + +- **Laravel 12:** [https://laravel.com/docs/12.x](https://laravel.com/docs/12.x) +- **Livewire 3:** [https://livewire.laravel.com](https://livewire.laravel.com) +- **Flux UI:** [https://flux.laravel.com](https://flux.laravel.com) +- **Pest Testing:** [https://pestphp.com](https://pestphp.com) + +### Logs prÃŒfen + +```bash +# Application Logs (Live) +tail -f storage/logs/laravel.log + +# Nur Fehler +tail -f storage/logs/laravel.log | grep ERROR + +# Mit Laravel Pail (farbig) +php artisan pail +``` + +### Debugging + +```bash +# Laravel Debugbar (installiert) +# Aktiviert automatisch wenn APP_DEBUG=true +# Erscheint am unteren Bildschirmrand im Browser + +# Tinker fÃŒr schnelle Tests +php artisan tinker +>>> App\Models\Company::count() +>>> DB::table('transactions')->latest()->first() +``` + +--- + +**Setup-Zeit:** ~15-30 Minuten +**Erstellt:** 2025-11-24 +**Version:** 1.0 +**Autor:** Risk Intelligence Platform Team + +Viel Erfolg beim Entwickeln! 🚀 diff --git a/docs/testing-guide.md b/docs/testing-guide.md new file mode 100644 index 0000000..a2d8fce --- /dev/null +++ b/docs/testing-guide.md @@ -0,0 +1,1047 @@ +# Testing-Guide: Risk Intelligence Platform + +## 📋 Inhaltsverzeichnis + +1. [Übersicht](#ÃŒbersicht) +2. [Test-Framework: Pest](#test-framework-pest) +3. [Test-Struktur](#test-struktur) +4. [Tests ausfÃŒhren](#tests-ausfÃŒhren) +5. [Feature Tests schreiben](#feature-tests-schreiben) +6. [Unit Tests schreiben](#unit-tests-schreiben) +7. [Livewire/Volt Tests](#livewirevoltt-tests) +8. [Database Testing](#database-testing) +9. [Test-Factories](#test-factories) +10. [Best Practices](#best-practices) +11. [Coverage & CI/CD](#coverage--cicd) +12. [Troubleshooting](#troubleshooting) + +--- + +## Übersicht + +Die Risk Intelligence Platform nutzt **Pest v4** als Testing-Framework - eine moderne, ausdrucksstarke Alternative zu PHPUnit. + +### Test-Philosophie + +✅ **Feature Tests ÃŒber Unit Tests** - Teste Verhalten, nicht Implementierung +✅ **Factories fÃŒr Test-Daten** - Konsistente, realistische Test-Daten +✅ **RefreshDatabase** - Jeder Test startet mit sauberer DB +✅ **Arrange-Act-Assert** - Klare Test-Struktur +✅ **Beschreibende Test-Namen** - Tests als lebende Dokumentation + +### Aktuelle Test-Coverage + +``` +tests/ +├── Feature/ # 19 Test-Dateien +│ ├── Auth/ # Authentication & 2FA +│ ├── Settings/ # User Settings +│ ├── Jobs/ # Background Jobs +│ └── Livewire/ # Livewire Components +└── Unit/ # 1 Test-Datei + └── ExampleTest.php +``` + +**Gesamt:** ~20 Test-Dateien mit 50+ einzelnen Tests + +--- + +## Test-Framework: Pest + +### Warum Pest? + +**Pest v4** bietet moderne Testing-Features: + +- ✅ **Ausdrucksstarke Syntax** - `test()` statt `public function test...()` +- ✅ **Expectations API** - `expect($value)->toBe(true)` +- ✅ **Datasets** - Parametrisierte Tests +- ✅ **Higher Order Tests** - `->it()` Syntax +- ✅ **Browser Testing** - Integriertes Browser-Testing (Pest v4) +- ✅ **Parallel Execution** - Schnellere Test-AusfÃŒhrung + +### Pest vs PHPUnit + +```php +// PHPUnit (alt) +class UserTest extends TestCase +{ + public function test_user_can_login() + { + $this->assertTrue(true); + } +} + +// Pest (modern) +test('user can login', function () { + expect(true)->toBeTrue(); +}); +``` + +--- + +## Test-Struktur + +### Verzeichnis-Layout + +``` +tests/ +├── Pest.php # Pest-Konfiguration +├── TestCase.php # Basis Test-Klasse +│ +├── Feature/ # Feature Tests (End-to-End) +│ ├── Auth/ +│ │ ├── AuthenticationTest.php +│ │ ├── RegistrationTest.php +│ │ ├── PasswordResetTest.php +│ │ ├── EmailVerificationTest.php +│ │ ├── PasswordConfirmationTest.php +│ │ └── TwoFactorChallengeTest.php +│ │ +│ ├── Settings/ +│ │ ├── ProfileUpdateTest.php +│ │ ├── PasswordUpdateTest.php +│ │ └── TwoFactorAuthenticationTest.php +│ │ +│ ├── Jobs/ +│ │ └── SyncBackendDataPoolTest.php +│ │ +│ ├── Livewire/ +│ │ └── Upload/ +│ │ └── IndexTest.php +│ │ +│ ├── CompanySearchTest.php +│ ├── TransactionReviewTest.php +│ ├── CompanyTransactionsTest.php +│ ├── BackendModelsTest.php +│ ├── NavigationTest.php +│ ├── DashboardTest.php +│ └── ExampleTest.php +│ +└── Unit/ # Unit Tests (isoliert) + └── ExampleTest.php +``` + +### Test-Typen + +| Typ | Zweck | Beispiel | +|-----|-------|----------| +| **Feature** | End-to-End User Flows | "User kann sich einloggen" | +| **Unit** | Einzelne Klassen/Methoden | "calculateRiskScore() gibt korrekten Wert zurÃŒck" | +| **Browser** | UI-Tests im echten Browser | "Click-Flow durch Transaction Review" | + +--- + +## Tests ausfÃŒhren + +### Basis-Commands + +```bash +# Alle Tests ausfÃŒhren +php artisan test + +# Oder direkt mit Pest +./vendor/bin/pest + +# Nur Feature Tests +php artisan test --testsuite=Feature + +# Nur Unit Tests +php artisan test --testsuite=Unit + +# Parallele AusfÃŒhrung (schneller) +php artisan test --parallel +``` + +### Spezifische Tests + +```bash +# Einzelne Test-Datei +php artisan test tests/Feature/CompanySearchTest.php + +# Test mit bestimmtem Namen +php artisan test --filter="user can login" + +# Test-Gruppe +php artisan test --group=auth + +# Mit Ausgabe-Details +php artisan test --verbose + +# Stopp beim ersten Fehler +php artisan test --stop-on-failure +``` + +### Output-Formate + +```bash +# Minimal (nur Zusammenfassung) +php artisan test --compact + +# Mit Coverage (benötigt Xdebug) +php artisan test --coverage + +# Mit Coverage-Minimum +php artisan test --coverage --min=80 + +# HTML Coverage Report +php artisan test --coverage-html coverage/ +``` + +--- + +## Feature Tests schreiben + +Feature Tests testen **User Flows** und **End-to-End Szenarien**. + +### Beispiel 1: Authentifizierung + +```php +create([ + 'email' => 'test@example.com', + 'password' => bcrypt('password'), + ]); + + // Act - Login-Versuch + $response = $this->post('/login', [ + 'email' => 'test@example.com', + 'password' => 'password', + ]); + + // Assert - PrÃŒfungen + $response->assertRedirect('/dashboard'); + $this->assertAuthenticated(); +}); + +test('user cannot login with invalid password', function () { + $user = User::factory()->create([ + 'email' => 'test@example.com', + 'password' => bcrypt('password'), + ]); + + $response = $this->post('/login', [ + 'email' => 'test@example.com', + 'password' => 'wrong-password', + ]); + + $response->assertSessionHasErrors(); + $this->assertGuest(); +}); +``` + +### Beispiel 2: Company Search + +```php +actingAs(User::factory()->create()); + + // Act + $response = $this->get(route('company-search')); + + // Assert + $response->assertOk(); + $response->assertSee('Unternehmensauskunft'); +}); + +test('search filters companies by name', function () { + $this->actingAs(User::factory()->create()); + + // Arrange - Test-Companies erstellen + $mercedes = Company::factory()->create([ + 'name' => 'Mercedes-Benz Group AG', + 'ticker' => 'MBG', + ]); + + $volkswagen = Company::factory()->create([ + 'name' => 'Volkswagen AG', + 'ticker' => 'VOW', + ]); + + Transaction::factory()->for($mercedes)->create(); + Transaction::factory()->for($volkswagen)->create(); + + // Act - Suche nach "Mercedes" + $component = Livewire\Volt\Volt::test('company-search') + ->set('search', 'Mercedes'); + + // Assert - Nur Mercedes gefunden + $companies = $component->get('companies'); + + expect($companies) + ->toHaveCount(1) + ->and($companies->first()->id)->toBe($mercedes->id); +}); +``` + +### Beispiel 3: Job Testing + +```php +insert([ + 'transaction_id' => 999, + 'corporate_entity' => 'Old Company', + 'tx_amount' => 1000.00, + 'status' => 'done', + 'prompt_id' => 1, + 'output_key' => 'test_key', + 'content' => 'test content', + 'created_at' => now(), + 'last_modified_at' => now(), + 'synced_at' => now(), + ]); + + expect(DB::table('backend_data_pool')->count())->toBe(1); + + // Act - Full Sync ausfÃŒhren + $job = new SyncBackendDataPool(fullSync: true, batchSize: 100); + $job->handle(); + + // Assert - Alte Daten entfernt + $oldRecord = DB::table('backend_data_pool') + ->where('transaction_id', 999) + ->first(); + + expect($oldRecord)->toBeNull(); +}); + +test('incremental sync only adds new records', function () { + // Arrange - Initial Sync + $job = new SyncBackendDataPool(fullSync: true); + $job->handle(); + + $initialCount = DB::table('backend_data_pool')->count(); + + // Act - Incremental Sync + $incrementalJob = new SyncBackendDataPool(fullSync: false); + $incrementalJob->handle(); + + // Assert - Keine Duplikate + $newCount = DB::table('backend_data_pool')->count(); + expect($newCount)->toBe($initialCount); +}); +``` + +--- + +## Unit Tests schreiben + +Unit Tests testen **einzelne Klassen/Methoden** isoliert. + +### Beispiel 1: KYC Risk Calculator + +```php +calculate([ + 'transaction_score' => 20, + 'sanctions' => 0, + 'country_risk' => 10, + 'pep' => 0, + 'corruption' => 0, + ]); + + expect($result['level'])->toBe('low') + ->and($result['score'])->toBeLessThan(50); +}); + +test('calculates critical risk with sanctions', function () { + $calculator = new KycRiskCalculator(); + + $result = $calculator->calculate([ + 'transaction_score' => 50, + 'sanctions' => 100, // Critical! + 'country_risk' => 30, + 'pep' => 20, + 'corruption' => 10, + ]); + + expect($result['level'])->toBe('critical') + ->and($result['score'])->toBeGreaterThan(70); +}); +``` + +### Beispiel 2: Model Methods + +```php +make([ + 'status' => Transaction::STATUS_TRUE_POSITIVE, + ]); + + expect($transaction->statusLabel())->toBe('Kritisches Risiko'); + + $transaction->status = Transaction::STATUS_CLEARED; + expect($transaction->statusLabel())->toBe('Geringes Risiko'); +}); + +test('company has many transactions relationship', function () { + $company = Company::factory() + ->has(Transaction::factory()->count(3)) + ->create(); + + expect($company->transactions)->toHaveCount(3); + expect($company->transactions->first())->toBeInstanceOf(Transaction::class); +}); +``` + +--- + +## Livewire/Volt Tests + +### Livewire Volt Component Tests + +```php +actingAs(User::factory()->create()); + + Volt::test('company-search') + ->assertSet('search', '') + ->assertOk(); +}); + +test('search input updates component state', function () { + $this->actingAs(User::factory()->create()); + + Volt::test('company-search') + ->assertSet('search', '') + ->set('search', 'Mercedes') + ->assertSet('search', 'Mercedes'); +}); + +test('component displays search results', function () { + $this->actingAs(User::factory()->create()); + + Company::factory()->create(['name' => 'Mercedes-Benz AG']); + + Volt::test('company-search') + ->set('search', 'Mercedes') + ->assertSee('Mercedes-Benz AG'); +}); + +test('component calls action method', function () { + $this->actingAs(User::factory()->create()); + + $company = Company::factory()->create(); + + Volt::test('company-search') + ->call('viewCompany', $company->id) + ->assertRedirect(route('company.transactions', $company)); +}); +``` + +### Testing Livewire Properties + +```php +test('component has required properties', function () { + $this->actingAs(User::factory()->create()); + + Volt::test('company-search') + ->assertPropertyWired('search') // wire:model="search" + ->assertSee('wire:model.live.debounce.300ms="search"', false); +}); + +test('component computes overview correctly', function () { + $this->actingAs(User::factory()->create()); + + Company::factory() + ->has(Transaction::factory()->requiresReview()->count(5)) + ->create(); + + $component = Volt::test('company-search'); + + $overview = $component->get('overview'); + + expect($overview) + ->toHaveKey('companies') + ->toHaveKey('open_alerts') + ->toHaveKey('open_volume'); +}); +``` + +--- + +## Database Testing + +### RefreshDatabase Trait + +```php +// tests/Pest.php +pest()->extend(Tests\TestCase::class) + ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) + ->in('Feature'); +``` + +**Was macht RefreshDatabase?** +- Migriert DB vor jedem Test +- Rollt Änderungen nach jedem Test zurÃŒck +- Nutzt Transactions fÃŒr Speed + +### Database Assertions + +```php +use Illuminate\Foundation\Testing\RefreshDatabase; + +test('company is created in database', function () { + $company = Company::factory()->create([ + 'name' => 'Test AG', + ]); + + // Assert in DB + $this->assertDatabaseHas('companies', [ + 'name' => 'Test AG', + ]); + + // Oder mit Pest Expectation + expect(Company::where('name', 'Test AG')->exists())->toBeTrue(); +}); + +test('transaction is deleted with company', function () { + $company = Company::factory() + ->has(Transaction::factory()) + ->create(); + + $transactionId = $company->transactions->first()->id; + + // Delete Company (CASCADE DELETE) + $company->delete(); + + // Assert Transaction auch gelöscht + $this->assertDatabaseMissing('transactions', [ + 'id' => $transactionId, + ]); +}); +``` + +### Seeding in Tests + +```php +test('can filter high risk transactions', function () { + // Seed Daten + $this->seed(CompanySeeder::class); + + // Oder inline + Company::factory() + ->has( + Transaction::factory() + ->status(Transaction::STATUS_TRUE_POSITIVE) + ->count(3) + ) + ->create(); + + $highRisk = Transaction::where('status', 'true_positive')->get(); + + expect($highRisk)->toHaveCount(3); +}); +``` + +--- + +## Test-Factories + +Factories generieren konsistente Test-Daten. + +### Company Factory + +```php +// database/factories/CompanyFactory.php + +namespace Database\Factories; + +use Illuminate\Database\Eloquent\Factories\Factory; + +class CompanyFactory extends Factory +{ + public function definition(): array + { + return [ + 'name' => fake()->company(), + 'legal_name' => fake()->company() . ' AG', + 'ticker' => strtoupper(fake()->lexify('???')), + 'sector' => fake()->randomElement([ + 'Automotive', 'Technology', 'Finance', 'Healthcare' + ]), + 'country' => fake()->countryCode(), + 'headquarters' => fake()->city() . ', ' . fake()->country(), + 'kyc_risk_level' => fake()->randomElement(['low', 'high', 'critical']), + 'summary' => fake()->paragraph(), + ]; + } + + // Custom States + public function highRisk(): static + { + return $this->state(fn (array $attributes) => [ + 'kyc_risk_level' => 'high', + ]); + } + + public function criticalRisk(): static + { + return $this->state(fn (array $attributes) => [ + 'kyc_risk_level' => 'critical', + ]); + } +} +``` + +### Transaction Factory + +```php +// database/factories/TransactionFactory.php + +class TransactionFactory extends Factory +{ + public function definition(): array + { + return [ + 'company_id' => Company::factory(), + 'reference' => 'TXN-' . fake()->unique()->randomNumber(6), + 'amount' => fake()->randomFloat(2, 100, 1000000), + 'currency' => 'EUR', + 'counterparty' => fake()->company(), + 'counterparty_country' => fake()->countryCode(), + 'channel' => fake()->randomElement(['SEPA', 'SWIFT', 'Internal']), + 'executed_at' => fake()->dateTimeBetween('-1 year', 'now'), + 'risk_score' => fake()->numberBetween(0, 255), + 'status' => fake()->randomElement([ + Transaction::STATUS_CLEARED, + Transaction::STATUS_FALSE_POSITIVE, + Transaction::STATUS_TRUE_POSITIVE, + ]), + 'requires_review' => fake()->boolean(), + ]; + } + + // States + public function requiresReview(bool $value = true): static + { + return $this->state(fn (array $attributes) => [ + 'requires_review' => $value, + ]); + } + + public function status(string $status): static + { + return $this->state(fn (array $attributes) => [ + 'status' => $status, + ]); + } + + public function highRisk(): static + { + return $this->state(fn (array $attributes) => [ + 'risk_score' => fake()->numberBetween(150, 200), + 'status' => Transaction::STATUS_FALSE_POSITIVE, + 'requires_review' => true, + ]); + } + + public function criticalRisk(): static + { + return $this->state(fn (array $attributes) => [ + 'risk_score' => fake()->numberBetween(200, 255), + 'status' => Transaction::STATUS_TRUE_POSITIVE, + 'requires_review' => true, + ]); + } +} +``` + +### Factory-Nutzung in Tests + +```php +// Einfach +$company = Company::factory()->create(); + +// Mit Overrides +$company = Company::factory()->create([ + 'name' => 'Custom Name AG', +]); + +// Mit State +$company = Company::factory()->highRisk()->create(); + +// Mit Beziehungen +$company = Company::factory() + ->has(Transaction::factory()->count(5)) + ->create(); + +// Oder anders herum +$transaction = Transaction::factory() + ->for(Company::factory()->highRisk()) + ->create(); + +// Mehrere mit State-Chain +$companies = Company::factory() + ->count(3) + ->highRisk() + ->create(); + +// Ohne DB-Speicherung (nur Object) +$company = Company::factory()->make(); +``` + +--- + +## Best Practices + +### 1. Arrange-Act-Assert Pattern + +```php +test('user can update profile', function () { + // Arrange - Setup + $user = User::factory()->create(['name' => 'Old Name']); + $this->actingAs($user); + + // Act - Aktion ausfÃŒhren + $response = $this->put('/profile', [ + 'name' => 'New Name', + 'email' => $user->email, + ]); + + // Assert - PrÃŒfungen + $response->assertRedirect(); + expect($user->fresh()->name)->toBe('New Name'); +}); +``` + +### 2. Beschreibende Test-Namen + +```php +// ✅ Gut +test('user cannot delete other users transactions', function () { ... }); + +// ❌ Schlecht +test('test1', function () { ... }); +``` + +### 3. One Concept per Test + +```php +// ✅ Gut - Ein Test pro Konzept +test('validates required name field', function () { ... }); +test('validates email format', function () { ... }); +test('validates unique email', function () { ... }); + +// ❌ Schlecht - Zu viel in einem Test +test('validates all form fields', function () { + // Tests name, email, password, etc... +}); +``` + +### 4. Nutze Datasets fÃŒr Àhnliche Tests + +```php +test('validates email format', function (string $email, bool $valid) { + $response = $this->post('/register', [ + 'email' => $email, + 'password' => 'password', + ]); + + if ($valid) { + $response->assertSessionHasNoErrors('email'); + } else { + $response->assertSessionHasErrors('email'); + } +})->with([ + 'valid email' => ['test@example.com', true], + 'missing @' => ['testexample.com', false], + 'missing domain' => ['test@', false], + 'spaces' => ['test @example.com', false], +]); +``` + +### 5. beforeEach & afterEach Hooks + +```php +beforeEach(function () { + // Vor jedem Test in dieser Datei + $this->user = User::factory()->create(); + $this->actingAs($this->user); +}); + +afterEach(function () { + // Nach jedem Test + // Cleanup falls nötig +}); + +test('can access dashboard', function () { + // $this->user ist bereits verfÃŒgbar + $this->get('/dashboard')->assertOk(); +}); +``` + +### 6. Test Doubles & Mocking + +```php +use Illuminate\Support\Facades\Mail; +use App\Mail\WelcomeMail; + +test('sends welcome email on registration', function () { + // Arrange - Mail mocken + Mail::fake(); + + // Act + $this->post('/register', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => 'password', + ]); + + // Assert - Mail wurde gesendet + Mail::assertSent(WelcomeMail::class, function ($mail) { + return $mail->hasTo('test@example.com'); + }); +}); +``` + +--- + +## Coverage & CI/CD + +### Code Coverage generieren + +```bash +# Einfacher Coverage-Report +php artisan test --coverage + +# Mit Minimum-Schwellwert +php artisan test --coverage --min=80 + +# HTML Report +php artisan test --coverage-html coverage/ + +# Report öffnen +open coverage/index.html +``` + +### Coverage-Konfiguration + +```xml + + + + app + + + app/Console/Commands + app/Providers/AppServiceProvider.php + + +``` + +### GitHub Actions Integration + +```yaml +# .github/workflows/tests.yml +name: Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + extensions: pdo_pgsql, mbstring, xml + + - name: Install Dependencies + run: composer install --no-interaction + + - name: Run Tests + run: php artisan test --parallel +``` + +--- + +## Troubleshooting + +### Problem: "Database file does not exist" + +**Lösung:** +```bash +# SQLite DB erstellen +touch database/database.sqlite + +# Oder in .env.testing +DB_CONNECTION=sqlite +DB_DATABASE=:memory: +``` + +--- + +### Problem: "Class not found in test" + +**Lösung:** +```bash +composer dump-autoload +php artisan optimize:clear +``` + +--- + +### Problem: "RefreshDatabase Migration failed" + +**Lösung:** +```bash +# Migrations prÃŒfen +php artisan migrate:status + +# Rollback & Fresh +php artisan migrate:fresh --env=testing +``` + +--- + +### Problem: Tests laufen sehr langsam + +**Lösungen:** + +1. **Parallele AusfÃŒhrung:** +```bash +php artisan test --parallel +``` + +2. **SQLite statt PostgreSQL:** +```xml + + + +``` + +3. **Bcrypt Rounds reduzieren:** +```xml + +``` + +--- + +### Problem: "Too many connections" Error + +**Ursache:** Zu viele parallele DB-Connections + +**Lösung:** +```bash +# Weniger Prozesse +php artisan test --parallel --processes=2 +``` + +--- + +## NÃŒtzliche Commands + +```bash +# Test-Liste anzeigen +php artisan test --list-tests + +# Neue Test-Datei erstellen +php artisan make:test CompanyTest # Feature Test +php artisan make:test CompanyTest --unit # Unit Test +php artisan make:test CompanyTest --pest # Pest Syntax + +# Test mit Debugging +php artisan test --filter="specific test" --stop-on-failure + +# Watch Mode (re-run bei Änderungen) +./vendor/bin/pest --watch + +# Nur fehlgeschlagene Tests +php artisan test --failed +``` + +--- + +## Test-Checkliste + +Beim Schreiben neuer Features: + +- [ ] Feature Test geschrieben? +- [ ] Edge Cases getestet? +- [ ] Validation getestet? +- [ ] Authorization getestet? +- [ ] Database-Constraints getestet? +- [ ] Error Handling getestet? +- [ ] Factories aktualisiert? +- [ ] Tests laufen durch (`php artisan test`)? +- [ ] Code formatiert (`vendor/bin/pint`)? + +--- + +## Zusammenfassung + +### Test-Pyramide + +``` + /\ + / \ Unit Tests (schnell, viele) + /____\ + / \ + / Feature \ Feature Tests (mittel, weniger) + /___________\ + / \ + / Browser \ Browser Tests (langsam, wenige) +/_________________\ +``` + +### Coverage-Ziele + +| Bereich | Target Coverage | +|---------|----------------| +| Models | 90%+ | +| Services | 85%+ | +| Jobs | 80%+ | +| Controllers | 70%+ | +| Commands | 60%+ | + +### Test-Performance + +| Anzahl Tests | Laufzeit (sequenziell) | Laufzeit (parallel) | +|--------------|------------------------|---------------------| +| 50 Tests | ~30s | ~10s | +| 100 Tests | ~60s | ~20s | +| 200 Tests | ~120s | ~40s | + +--- + +**Erstellt:** 2025-11-24 +**Version:** 1.0 +**Autor:** Risk Intelligence Platform Team