1163 lines
44 KiB
Markdown
1163 lines
44 KiB
Markdown
# ERP Nutzfahrzeuge – Architekturdokument
|
||
|
||
> **Version**: 1.0
|
||
> **Datum**: 2026-07-12
|
||
> **Status**: Approved for Implementation
|
||
> **Solution Architect**: A0 Orchestrator
|
||
|
||
---
|
||
|
||
## 1. Übersicht
|
||
|
||
ERP-System für den Handel mit Nutzfahrzeugen (LKW, Baumaschinen, PKW, Stapler, Transporter).
|
||
Backend in Python/FastAPI, Frontend in React/Next.js (mobile-first), PostgreSQL als Datenbank,
|
||
Redis für Caching/Queues. Hosting via Coolify self-hosted mit Docker-Compose (4 Container).
|
||
|
||
### 1.1 Stakeholder & Rollen
|
||
|
||
| Rolle | Berechtigungen | Nutzer (~10) |
|
||
|---|---|---|
|
||
| Admin | Vollzugriff, User-Management, Systemeinstellungen | 1-2 |
|
||
| Verkäufer | Fahrzeuge, Kontakte, Verkäufe, OCR, KI-Copilot | 5-7 |
|
||
| Buchhaltung | Verkäufe (read), DATEV-Export, USt-IdNr.-Prüfung | 1-2 |
|
||
|
||
### 1.2 Sprachen
|
||
- Deutsch (DE) – Primärsprache
|
||
- Englisch (EN) – Sekundärsprache
|
||
|
||
---
|
||
|
||
## 2. Tech-Stack
|
||
|
||
| Schicht | Technologie | Version |
|
||
|---|---|---|
|
||
| Backend | Python / FastAPI | Python 3.12, FastAPI 0.111+ |
|
||
| Frontend | React / Next.js | Next.js 15 (App Router), React 19 |
|
||
| CSS | Tailwind CSS | 3.4+ |
|
||
| Datenbank | PostgreSQL | 16+ |
|
||
| Cache/Queue | Redis | 7+ |
|
||
| ORM | SQLAlchemy 2.0 + Alembic | async |
|
||
| KI | OpenRouter API | Qwen2.5-VL (OCR), Flux.1-Pro (Bild), Claude/GPT-4 (Copilot) |
|
||
| mobile.de | Seller API (Push only) | REST |
|
||
| OCR | OpenRouter Vision (Qwen2.5-VL) | – |
|
||
| DATEV | CSV-Export (DATEV-Format) | – |
|
||
| USt-IdNr. | BZSt API (geplant, aktuell kein Zugang) | REST |
|
||
| Deployment | Docker-Compose via Coolify | 4 Container |
|
||
| Auth | JWT (access + refresh) | – |
|
||
| Testing | pytest + httpx (Backend), Vitest + Playwright (Frontend) | – |
|
||
|
||
---
|
||
|
||
## 3. Deployment-Architektur
|
||
|
||
### 3.1 Docker-Compose (4 Container)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ Coolify (46.225.91.159) │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||
│ │ frontend │ │ backend │ │ redis │ │
|
||
│ │ Next.js │ │ FastAPI │ │ cache │ │
|
||
│ │ :3000 │ │ :8000 │ │ :6379 │ │
|
||
│ └────┬─────┘ └────┬─────┘ └──────────┘ │
|
||
│ │ │ │
|
||
│ │ ┌────┴─────┐ │
|
||
│ │ │ postgres │ │
|
||
│ │ │ :5432 │ │
|
||
│ │ └──────────┘ │
|
||
│ │ │
|
||
│ Traefik (SSL, Let's Encrypt) │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 Netzwerk
|
||
- Traefik als Reverse Proxy mit automatischem SSL
|
||
- Frontend erreichbar unter `https://erp.media-on.de`
|
||
- Backend API unter `https://erp.media-on.de/api`
|
||
- Interne Kommunikation über Docker-Netzwerk
|
||
|
||
### 3.3 Volumes
|
||
- `postgres_data`: Datenbank-Persistenz
|
||
- `redis_data`: Redis-Persistenz (optional)
|
||
- `uploads_data`: Fahrzeug-Dateien (Bilder, Dokumente, OCR-Scans)
|
||
- `retouched_images`: Retuschierte Bilder
|
||
|
||
---
|
||
|
||
## 4. Modulstruktur
|
||
|
||
### 4.1 Backend-Struktur (FastAPI)
|
||
|
||
```
|
||
backend/
|
||
├── app/
|
||
│ ├── main.py # FastAPI app entry, CORS, routers
|
||
│ ├── config.py # Settings (Pydantic BaseSettings)
|
||
│ ├── database.py # async engine, session factory
|
||
│ ├── dependencies.py # Common deps (auth, pagination)
|
||
│ ├── models/ # SQLAlchemy models
|
||
│ │ ├── user.py
|
||
│ │ ├── vehicle.py
|
||
│ │ ├── contact.py
|
||
│ │ ├── sale.py
|
||
│ │ ├── document.py
|
||
│ │ └── ocr_result.py
|
||
│ ├── schemas/ # Pydantic schemas (request/response)
|
||
│ │ ├── user.py
|
||
│ │ ├── vehicle.py
|
||
│ │ ├── contact.py
|
||
│ │ ├── sale.py
|
||
│ │ └── ocr.pyn│ ├── routers/ # API route handlers
|
||
│ │ ├── auth.py
|
||
│ │ ├── vehicles.py
|
||
│ │ ├── contacts.py
|
||
│ │ ├── sales.py
|
||
│ │ ├── ocr.py
|
||
│ │ ├── files.py
|
||
│ │ ├── copilot.py
|
||
│ │ └── image_retouch.py
|
||
│ ├── services/ # Business logic
|
||
│ │ ├── auth_service.py
|
||
│ │ ├── vehicle_service.py
|
||
│ │ ├── mobilede_service.py
|
||
│ │ ├── contact_service.py
|
||
│ │ ├── sale_service.py
|
||
│ │ ├── ocr_service.py
|
||
│ │ ├── file_service.py
|
||
│ │ ├── copilot_service.py
|
||
│ │ ├── retouch_service.py
|
||
│ │ └── datev_service.py
|
||
│ ├── tasks/ # Background tasks (async)
|
||
│ │ ├── mobilede_push.py
|
||
│ │ └── ocr_processing.py
|
||
│ └── utils/
|
||
│ ├── openrouter.py # OpenRouter API client
|
||
│ ├── i18n.py # Backend i18n helpers
|
||
│ └── datev.py # DATEV export formatter
|
||
├── alembic/ # DB migrations
|
||
├── tests/ # pytest test suite
|
||
├── requirements.txt
|
||
├── Dockerfile
|
||
└── pyproject.toml
|
||
```
|
||
|
||
### 4.2 Frontend-Struktur (Next.js)
|
||
|
||
```
|
||
frontend/
|
||
├── app/ # App Router
|
||
│ ├── layout.tsx # Root layout (i18n provider, auth)
|
||
│ ├── page.tsx # Dashboard
|
||
│ ├── (auth)/
|
||
│ │ └── login/page.tsx
|
||
│ ├── vehicles/
|
||
│ │ ├── page.tsx # List + filter
|
||
│ │ ├── [id]/page.tsx # Detail
|
||
│ │ └── new/page.tsx # Create form
|
||
│ ├── contacts/
|
||
│ │ ├── page.tsx # List
|
||
│ │ ├── [id]/page.tsx # Detail
|
||
│ │ └── new/page.tsx # Create form
|
||
│ ├── sales/
|
||
│ │ ├── page.tsx # List
|
||
│ │ ├── [id]/page.tsx # Detail + contract
|
||
│ │ └── new/page.tsx # Create sale
|
||
│ ├── ocr/
|
||
│ │ └── page.tsx # OCR upload + results
|
||
│ ├── copilot/
|
||
│ │ └── page.tsx # KI-Copilot chat
|
||
│ └── settings/
|
||
│ └── page.tsx # Admin settings
|
||
├── components/ # Reusable components
|
||
│ ├── ui/ # Base UI (Button, Input, Card, Table)
|
||
│ ├── vehicles/ # Vehicle-specific components
|
||
│ ├── contacts/ # Contact-specific components
|
||
│ ├── sales/ # Sale-specific components
|
||
│ ├── ocr/ # OCR components
|
||
│ └── copilot/ # Copilot chat components
|
||
├── lib/ # Client utilities
|
||
│ ├── api.ts # API client (fetch wrapper)
|
||
│ ├── auth.ts # Auth context/hooks
|
||
│ └── i18n.ts # i18n config
|
||
├── messages/ # i18n translation files
|
||
│ ├── de.json
|
||
│ └── en.json
|
||
├── public/
|
||
├── Dockerfile
|
||
├── tailwind.config.ts
|
||
└── package.json
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Datenmodell
|
||
|
||
### 5.1 Entity-Relationship-Übersicht
|
||
|
||
```
|
||
User ──< Vehicle ──< File (Dateiablage)
|
||
│ │
|
||
│ ├──< OCRResult
|
||
│ ├──< RetouchedImage
|
||
│ └──< Sale ──< ContractDocument
|
||
│ └──< DATEVExport
|
||
│
|
||
└──< Contact (Kunde/Lieferant)
|
||
```
|
||
|
||
### 5.2 Kern-Entitäten
|
||
|
||
#### User
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| email | VARCHAR(255) | unique, not null |
|
||
| full_name | VARCHAR(200) | not null |
|
||
| role | ENUM('admin','verkaeufer','buchhaltung') | not null |
|
||
| password_hash | VARCHAR(255) | not null |
|
||
| is_active | BOOLEAN | default true |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### Vehicle
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| vehicle_type | ENUM('lkw','baumaschine','pkw','stapler','transporter') | not null |
|
||
| brand | VARCHAR(100) | not null |
|
||
| model | VARCHAR(150) | not null |
|
||
| vin | VARCHAR(17) | unique |
|
||
| registration_plate | VARCHAR(20) | |
|
||
| first_registration | DATE | |
|
||
| mileage_km | INTEGER | |
|
||
| power_kw | INTEGER | |
|
||
| fuel_type | ENUM('diesel','petrol','electric','hybrid','other') | |
|
||
| purchase_price | DECIMAL(12,2) | not null |
|
||
| sale_price | DECIMAL(12,2) | |
|
||
| status | ENUM('in_stock','reserved','sold','deleted') | default 'in_stock' |
|
||
| mobilede_ad_id | VARCHAR(50) | |
|
||
| mobilede_synced_at | TIMESTAMPTZ | |
|
||
| notes | TEXT | |
|
||
| created_by | UUID | FK → User |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
| updated_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### Contact
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| contact_type | ENUM('kunde','lieferant','beide') | not null |
|
||
| company_name | VARCHAR(200) | |
|
||
| first_name | VARCHAR(100) | |
|
||
| last_name | VARCHAR(100) | |
|
||
| street | VARCHAR(255) | |
|
||
| zip_code | VARCHAR(10) | |
|
||
| city | VARCHAR(100) | |
|
||
| country | VARCHAR(2) | default 'DE' |
|
||
| is_eu | BOOLEAN | default false |
|
||
| ust_id_nr | VARCHAR(20) | |
|
||
| phone | VARCHAR(30) | |
|
||
| email | VARCHAR(255) | |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### Sale
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| vehicle_id | UUID | FK → Vehicle, not null |
|
||
| buyer_contact_id | UUID | FK → Contact, not null |
|
||
| seller_user_id | UUID | FK → User, not null |
|
||
| sale_price | DECIMAL(12,2) | not null |
|
||
| sale_date | DATE | not null |
|
||
| payment_method | ENUM('bank_transfer','cash','financing','other') | |
|
||
| is_gwg | BOOLEAN | default false (Geringwertiges Wirtschaftsgut) |
|
||
| gwg_amount | DECIMAL(12,2) | |
|
||
| vat_rate | DECIMAL(5,2) | default 19.00 |
|
||
| ust_id_nr_verified | BOOLEAN | default false |
|
||
| contract_pdf_path | VARCHAR(500) | |
|
||
| datev_export_id | UUID | FK → DATEVExport (nullable) |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### File (Dateiablage pro Fahrzeug)
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| vehicle_id | UUID | FK → Vehicle, not null |
|
||
| file_type | ENUM('image','document','ocr_scan','retouched_image') | not null |
|
||
| original_filename | VARCHAR(255) | not null |
|
||
| stored_path | VARCHAR(500) | not null |
|
||
| mime_type | VARCHAR(100) | not null |
|
||
| file_size_bytes | BIGINT | not null |
|
||
| uploaded_by | UUID | FK → User |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### OCRResult
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| vehicle_id | UUID | FK → Vehicle, not null |
|
||
| file_id | UUID | FK → File, not null |
|
||
| raw_text | TEXT | extracted text |
|
||
| structured_data | JSONB | parsed fields (brand, model, vin, etc.) |
|
||
| ocr_type | ENUM('zb_i','zb_ii') | not null |
|
||
| confidence_score | FLOAT | |
|
||
| status | ENUM('pending','completed','failed') | default 'pending' |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
#### DATEVExport
|
||
| Feld | Typ | Constraints |
|
||
|---|---|---|
|
||
| id | UUID | PK |
|
||
| export_date | DATE | not null |
|
||
| period_from | DATE | not null |
|
||
| period_to | DATE | not null |
|
||
| file_path | VARCHAR(500) | generated CSV path |
|
||
| record_count | INTEGER | |
|
||
| created_by | UUID | FK → User |
|
||
| created_at | TIMESTAMPTZ | default now() |
|
||
|
||
---
|
||
|
||
## 6. API Design
|
||
|
||
### 6.1 Auth
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| POST | `/api/auth/login` | Login → JWT (access + refresh) |
|
||
| POST | `/api/auth/refresh` | Token refresh |
|
||
| GET | `/api/auth/me` | Aktuellen User abrufen |
|
||
| PUT | `/api/auth/me` | Profil aktualisieren |
|
||
|
||
### 6.2 Vehicles (M1)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/api/vehicles` | List with pagination, filter (type, status, brand), sort |
|
||
| GET | `/api/vehicles/:id` | Detail view |
|
||
| POST | `/api/vehicles` | Create new vehicle |
|
||
| PUT | `/api/vehicles/:id` | Update vehicle |
|
||
| DELETE | `/api/vehicles/:id` | Soft delete (status='deleted') |
|
||
| POST | `/api/vehicles/:id/mobilede-push` | Push to mobile.de Seller API |
|
||
| GET | `/api/vehicles/:id/mobilede-status` | Sync status abrufen |
|
||
|
||
### 6.3 OCR (M2)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| POST | `/api/ocr/upload` | Upload ZB I/II scan → async OCR processing |
|
||
| GET | `/api/ocr/results/:id` | OCR result abrufen |
|
||
| GET | `/api/ocr/results?vehicle_id=X` | OCR results für Fahrzeug |
|
||
| POST | `/api/ocr/results/:id/apply` | OCR-Daten auf Vehicle anwenden |
|
||
|
||
### 6.4 Contacts (M3)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/api/contacts` | List with pagination, filter (type, eu/inland), search |
|
||
| GET | `/api/contacts/:id` | Detail view |
|
||
| POST | `/api/contacts` | Create contact |
|
||
| PUT | `/api/contacts/:id` | Update contact |
|
||
| DELETE | `/api/contacts/:id` | Delete contact |
|
||
|
||
### 6.5 Files (M4)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/api/vehicles/:id/files` | List files for vehicle |
|
||
| POST | `/api/vehicles/:id/files` | Upload file (multipart) |
|
||
| GET | `/api/vehicles/:id/files/:fileId` | Download file |
|
||
| DELETE | `/api/vehicles/:id/files/:fileId` | Delete file |
|
||
|
||
### 6.6 Sales (M5)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/api/sales` | List with pagination, filter (date, status) |
|
||
| GET | `/api/sales/:id` | Detail view |
|
||
| POST | `/api/sales` | Create sale (generates contract PDF) |
|
||
| PUT | `/api/sales/:id` | Update sale |
|
||
| DELETE | `/api/sales/:id` | Cancel/delete sale |
|
||
| POST | `/api/sales/:id/contract` | Re-generate contract PDF |
|
||
| GET | `/api/sales/:id/contract` | Download contract PDF |
|
||
| POST | `/api/sales/:id/verify-ust-id` | USt-IdNr. BZSt API Prüfung (geplant) |
|
||
| POST | `/api/datev/export` | DATEV CSV Export für Zeitraum |
|
||
| GET | `/api/datev/exports` | List DATEV exports |
|
||
| GET | `/api/datev/exports/:id/download` | Download DATEV CSV |
|
||
|
||
### 6.7 KI-Copilot (M7)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| POST | `/api/copilot/chat` | Text chat → OpenRouter (Claude/GPT-4) |
|
||
| POST | `/api/copilot/voice` | Voice input (STT) → chat response |
|
||
| GET | `/api/copilot/history` | Chat history (paginated) |
|
||
| POST | `/api/copilot/action` | System action ausführen (vehicle create, search, etc.) |
|
||
|
||
### 6.8 Image Retouch (M8)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| POST | `/api/retouch/process` | Bild → Flux.1-Pro Retusche |
|
||
| GET | `/api/retouch/results/:id` | Retuschiertes Bild abrufen |
|
||
| POST | `/api/retouch/price-compare` | Preisvergleich mit mobile.de Listings |
|
||
|
||
### 6.9 Users (Admin)
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/api/users` | List users (admin only) |
|
||
| POST | `/api/users` | Create user (admin only) |
|
||
| PUT | `/api/users/:id` | Update user (admin only) |
|
||
| DELETE | `/api/users/:id` | Deactivate user (admin only) |
|
||
|
||
---
|
||
|
||
## 7. Test-Strategie
|
||
|
||
### 7.1 Backend (pytest)
|
||
- **Unit Tests**: Services isoliert mit Mocks (OpenRouter, mobile.de API)
|
||
- **Integration Tests**: API Endpoints mit Test-DB (SQLite/PostgreSQL testcontainer)
|
||
- **Coverage Target**: ≥ 80% pro Modul
|
||
- **Fixtures**: DB session, test client, mock OpenRouter responses
|
||
|
||
### 7.2 Frontend (Vitest + Playwright)
|
||
- **Unit Tests**: Komponenten mit Vitest + React Testing Library
|
||
- **E2E Tests**: Playwright für kritische User-Flows (Login, Vehicle CRUD, OCR, Sale)
|
||
- **Coverage Target**: ≥ 70% pro Komponentengruppe
|
||
|
||
### 7.3 Test-Commands
|
||
```bash
|
||
# Backend
|
||
cd backend && pytest --cov=app --cov-report=term-missing
|
||
|
||
# Frontend
|
||
cd frontend && npm run test
|
||
cd frontend && npm run e2e
|
||
|
||
# Full integration
|
||
docker compose -f docker-compose.test.yml up --abort-on-container-exit
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Integrationsarchitektur
|
||
|
||
### 8.1 OpenRouter KI-Pipeline
|
||
|
||
```
|
||
User Request → FastAPI → OpenRouter API → Response
|
||
│
|
||
├── OCR: Qwen2.5-VL (Vision) → structured JSON
|
||
├── Retouch: Flux.1-Pro → Image URL/base64
|
||
└── Copilot: Claude/GPT-4 → Text/Action JSON
|
||
```
|
||
|
||
- API-Key via Environment Variable `OPENROUTER_API_KEY`
|
||
- Rate-Limiting via Redis Token Bucket
|
||
- Fehlerbehandlung: Retry mit exponential backoff (max 3)
|
||
- Response-Caching für identische OCR-Scans
|
||
|
||
### 8.2 mobile.de Seller API
|
||
|
||
```
|
||
Vehicle Update → FastAPI → mobile.de Seller API (POST/PUT)
|
||
↓
|
||
Ad ID gespeichert in Vehicle.mobilede_ad_id
|
||
```
|
||
|
||
- **Nur Push-Richtung** (kein Import von mobile.de)
|
||
- Auth: OAuth2 oder API-Key (in mobile.de Seller Portal konfigurierbar)
|
||
- Mapping: ERP Vehicle → mobile.de Ad Format
|
||
- Retry-Queue bei Fehlern (Redis)
|
||
- Status-Tracking: `mobilede_synced_at`, `mobilede_ad_id`
|
||
|
||
### 8.3 DATEV-Export
|
||
|
||
```
|
||
Buchhaltung User → DATEV Export Request → FastAPI
|
||
→ Sammle Sales im Zeitraum → Format CSV (DATEV-Format) → Download
|
||
```
|
||
|
||
- Format: DATEV CSV (Buchungsstapel)
|
||
- Felder: Datum, Konto, Gegenkonto, Betrag, Belegfeld, Buchungstext
|
||
- GwG-Behandlung: Sofortabzug bei ≤ 800€ (§6 Abs. 2 EStG)
|
||
- USt-IdNr.-Prüfung: BZSt API (geplant, Feature-Flag `bzst_api_enabled`)
|
||
|
||
---
|
||
|
||
## 9. ADRs (Architecture Decision Records)
|
||
|
||
### ADR-001: FastAPI statt Django
|
||
**Status**: Accepted
|
||
**Entscheidung**: FastAPI als Backend-Framework.
|
||
**Begründung**: Async-native, bessere Performance, automatische OpenAPI-Dokumentation,
|
||
lichtgewichtiger als Django, native Pydantic-Integration.
|
||
**Alternativen**: Django REST Framework (zu schwerfällig), Flask (kein async).
|
||
|
||
### ADR-002: Next.js App Router statt Pages Router
|
||
**Status**: Accepted
|
||
**Entscheidung**: Next.js 15 mit App Router.
|
||
**Begründung**: Server Components, Streaming, bessere Code-Organisation,
|
||
Zukunftssicherheit.
|
||
**Alternativen**: Pages Router (deprecated), CRA (deprecated).
|
||
|
||
### ADR-003: OpenRouter als KI-Gateway
|
||
**Status**: Accepted
|
||
**Entscheidung**: Alle KI-Aufrufe über OpenRouter API.
|
||
**Begründung**: Ein API-Key für multiple Modelle, einheitliche API,
|
||
kein Vendor Lock-in, schnelle Modellwechsel.
|
||
**Alternativen**: Direkte OpenAI/Anthropic API (Vendor Lock-in),
|
||
lokale Modelle (zu langsam für OCR).
|
||
|
||
### ADR-004: OCR via Vision-Modell statt Tesseract
|
||
**Status**: Accepted
|
||
**Entscheidung**: OCR über OpenRouter Qwen2.5-VL Vision-Modell.
|
||
**Begründung**: Bessere Erkennung bei handschriftlichen und undeutlichen ZB-Scans,
|
||
keine Infrastruktur für Tesseract nötig, strukturierbare JSON-Ausgabe.
|
||
**Alternativen**: Tesseract (schlechte Qualität bei Scans),
|
||
Google Cloud Vision (Privacy-Bedenken, kostenpflichtig).
|
||
|
||
### ADR-005: Soft-Delete für Vehicles
|
||
**Status**: Accepted
|
||
**Entscheidung**: Vehicles werden nicht physisch gelöscht, sondern status='deleted'.
|
||
**Begründung**: Referentielle Integrität (Sales, Files, OCR Results),
|
||
Audit-Trail für Buchhaltung.
|
||
**Alternativen**: Hard Delete (Datenverlust, FK-Constraints).
|
||
|
||
### ADR-006: JWT statt Session-based Auth
|
||
**Status**: Accepted
|
||
**Entscheidung**: JWT (access + refresh token) für Authentifizierung.
|
||
**Begründung**: Stateless, geeignet für API + SPA, keine Server-Side-Session nötig.
|
||
**Alternativen**: Session-Cookies (CSRF-Risiko, Server-State nötig).
|
||
|
||
### ADR-007: Redis für Caching und Background Queues
|
||
**Status**: Accepted
|
||
**Entscheidung**: Redis als Cache und Task-Queue.
|
||
**Begründung**: Schnelles In-Memory-Caching für OCR-Results und mobile.de-Status,
|
||
Background-Queues für async OCR und mobile.de Push.
|
||
**Alternativen**: Celery+RabbitMQ (zu komplex für ~10 Nutzer),
|
||
keine Queue (blocking API).
|
||
|
||
### ADR-008: Docker-Compose mit 4 Containern statt K8s
|
||
**Status**: Accepted
|
||
**Entscheidung**: Docker-Compose mit frontend, backend, postgres, redis.
|
||
**Begründung**: Einfach zu deployen via Coolify, ausreichend für ~10 Nutzer,
|
||
keine K8s-Komplexität.
|
||
**Alternativen**: Kubernetes (overkill), einzelne Coolify Services
|
||
(mehr Konfigurationsaufwand).
|
||
|
||
---
|
||
|
||
## 10. Nicht-funktionale Anforderungen
|
||
|
||
### 10.1 Performance
|
||
- API-Response < 500ms für Standard-CRUD (ohne KI-Calls)
|
||
- OCR-Verarbeitung < 30s pro Scan (async, User wird benachrichtigt)
|
||
- Bildretusche < 60s pro Bild (async)
|
||
- Frontend LCP < 2.5s (mobile.de Performance Budget)
|
||
|
||
### 10.2 Sicherheit
|
||
- JWT mit kurzer Access-Token-Gültigkeit (15min) + Refresh (7d)
|
||
- Role-based Access Control (RBAC) auf Endpoint-Ebene
|
||
- Input-Validierung via Pydantic auf allen Endpoints
|
||
- SQL-Injection-Schutz durch SQLAlchemy parameterized queries
|
||
- File-Upload: MIME-Type-Validierung, max 20MB pro Datei
|
||
- OpenRouter API-Key in Environment Variables, nie im Code
|
||
- HTTPS-only via Traefik + Let's Encrypt
|
||
|
||
### 10.3 Skalierbarkeit
|
||
- Horizontal skalierbar (stateless Backend, sticky sessions nicht nötig)
|
||
- Redis als verteiltes Cache → mehrere Backend-Instanzen möglich
|
||
- PostgreSQL Connection Pooling via SQLAlchemy async engine
|
||
- Für ~10 Nutzer: Single-Instance ausreichend
|
||
|
||
### 10.4 Verfügbarkeit
|
||
- Docker-Compose mit `restart: unless-stopped`
|
||
- PostgreSQL mit WAL-Archiving für Point-in-Time-Recovery
|
||
- Daily Backup via Coolify (PostgreSQL dump)
|
||
- Health-Checks auf `/api/health` (Backend) und `/` (Frontend)
|
||
|
||
---
|
||
|
||
## 11. Risiken & Mitigation
|
||
|
||
| Risiko | Wahrscheinlichkeit | Impact | Mitigation |
|
||
|---|---|---|---|
|
||
| OpenRouter API-Ausfall | Mittel | Hoch | Retry mit Backoff, Fallback-Modell, Cache |
|
||
| mobile.de API-Änderung | Niedrig | Mittel | API-Version pinnen, Monitoring |
|
||
| OCR-Qualität unzureichend | Mittel | Mittel | Confidence-Score → Manual Review bei < 0.7 |
|
||
| BZSt API kein Zugang | Hoch | Niedrig | Feature-Flag, manuelle Prüfung als Fallback |
|
||
| Datenverlust PostgreSQL | Niedrig | Kritisch | Daily Backups, WAL-Archiving |
|
||
| Token-Kosten OpenRouter | Mittel | Mittel | Caching, Rate-Limiting pro User |
|
||
|
||
---
|
||
|
||
## 12. i18n Architektur
|
||
|
||
- **Backend**: Fehlermeldungen auf Deutsch/Englisch via Accept-Language Header
|
||
- **Frontend**: next-intl oder react-i18next
|
||
- **Translation Files**: `frontend/messages/de.json`, `frontend/messages/en.json`
|
||
- **Sprachumschaltung**: User-Settings → Locale in localStorage + Cookie
|
||
- **Backend-Fehler**: Error-Codes + lokalisierte Messages
|
||
|
||
---
|
||
|
||
## 13. Konventionen
|
||
|
||
## 14. PDF-Generierung & Briefpapier (Letterhead)
|
||
|
||
### 14.1 Library-Wahl: WeasyPrint
|
||
|
||
**Entscheidung**: WeasyPrint als PDF-Generierungs-Library.
|
||
|
||
**Begründung**: WeasyPrint rendert HTML/CSS → PDF. Dadurch können PDF-Templates
|
||
mit Standard-Web-Technologien (HTML, CSS, Jinja2) erstellt werden. Briefpapier
|
||
(Letterhead) wird über CSS `@page` Rules mit `@top`/`@bottom` Margin-Boxes
|
||
umgesetzt – inkl. Logo, Firmenadresse, Footer, Seitenzahlen.
|
||
|
||
**Vergleich**:
|
||
|
||
| Kriterium | WeasyPrint | ReportLab |
|
||
|---|---|---|
|
||
| Template-Erstellung | HTML/CSS + Jinja2 | Python-Code (Programmatic) |
|
||
| Briefpapier/Header/Footer | CSS @page Rules, repeating on every page | PageTemplate + Frame + onPage callback |
|
||
| Logo/Bild-Embedding | `<img>` oder CSS `background-image` | `Image()` flowable |
|
||
| Lernkurve | Niedrig (Web-Entwickler kennen HTML/CSS) | Mittel (ReportLab-spezifische API) |
|
||
| Flexibilität | Hoch (volle CSS-Unterstützung) | Mittel (eingeschränkte Layout-Optionen) |
|
||
| Wartbarkeit | Hoch (Templates separiert von Code) | Niedrig (Layout im Python-Code) |
|
||
|
||
**Alternativen**: ReportLab (pixel-präzise Kontrolle, aber komplexer),
|
||
fpdf2 (einfach aber weniger Features), wkhtmltopdf (veraltet).
|
||
|
||
### 14.2 Briefpapier-Implementierung
|
||
|
||
**Template-Struktur**:
|
||
```
|
||
backend/app/utils/pdf/
|
||
├── letterhead_template.html # Jinja2 HTML-Template mit Briefpapier
|
||
├── letterhead_style.css # CSS mit @page Rules (Header/Footer/Logo)
|
||
├── contract_template.html # Vertrag-Template (erbt Briefpapier)
|
||
├── invoice_template.html # Rechnung-Template (erbt Briefpapier)
|
||
└── pdf_generator.py # WeasyPrint Wrapper-Service
|
||
```
|
||
|
||
**CSS @page für Briefpapier**:
|
||
```css
|
||
@page {
|
||
size: A4;
|
||
margin: 120px 40px 80px 40px; /* top right bottom left */
|
||
|
||
@top-left {
|
||
content: url('/data/letterhead/logo.png');
|
||
width: 180px;
|
||
}
|
||
@top-right {
|
||
content: 'Firmenname GmbH';
|
||
font-size: 9pt;
|
||
color: #666;
|
||
}
|
||
@bottom-left {
|
||
content: 'Firmenname GmbH · Straßenname 1 · 12345 Stadt';
|
||
font-size: 8pt;
|
||
color: #999;
|
||
}
|
||
@bottom-right {
|
||
content: 'Seite ' counter(page) ' von ' counter(pages);
|
||
font-size: 8pt;
|
||
color: #999;
|
||
}
|
||
}
|
||
```
|
||
|
||
**Briefpapier-Elemente** (konfigurierbar über Admin-Settings):
|
||
- Logo (PNG/SVG, Position: top-left oder top-center)
|
||
- Firmenname & Adresse (Header oder Footer)
|
||
- Kontaktdaten (Tel, Email, Website)
|
||
- Steuernummer / USt-IdNr. (Footer)
|
||
- Seitenzahlen (Footer rechts)
|
||
- Hintergrund-Wasserzeichen (optional, z.B. "ENTWURF")
|
||
- Bankverbindung (Footer)
|
||
|
||
**Admin-Settings für Briefpapier**:
|
||
- Endpoint: `PUT /api/settings/letterhead` (admin only)
|
||
- Felder: logo_path, company_name, street, zip_city, phone, email, website,
|
||
tax_number, ust_id_nr, bank_name, bank_iban, bank_bic
|
||
- Briefpapier-Vorschau: `GET /api/settings/letterhead/preview` → PDF Preview
|
||
|
||
### 14.3 Dokument-Typen
|
||
|
||
| Dokument | Template | Briefpapier |
|
||
|---|---|---|
|
||
| Kaufvertrag (Sale Contract) | contract_template.html | Ja (Full Letterhead) |
|
||
| Rechnung (Invoice) | invoice_template.html | Ja (Full Letterhead) |
|
||
| DATEV-Export | – (CSV, kein PDF) | Nein |
|
||
| Fahrzeug-Inventarliste | inventory_template.html | Ja (Full Letterhead) |
|
||
|
||
### ADR-009: WeasyPrint statt ReportLab
|
||
**Status**: Accepted
|
||
**Entscheidung**: WeasyPrint für alle PDF-Generierungen.
|
||
**Begründung**: HTML/CSS-Templates sind einfacher zu erstellen und zu warten
|
||
als ReportLab-Code. Briefpapier über CSS @page ist flexibler und
|
||
Web-Entwickler können Templates ohne Python-Kenntnisse anpassen.
|
||
**Alternativen**: ReportLab (pixel-präzise aber komplex), wkhtmltopdf (veraltet).
|
||
|
||
---
|
||
|
||
## 15. mobile.de Seller API – Detaillierte Integration
|
||
|
||
### 15.1 API-Übersicht
|
||
|
||
mobile.de bietet zwei Schnittstellen:
|
||
|
||
| Schnittstelle | Format | Use Case |
|
||
|---|---|---|
|
||
| **Seller API (REST)** | XML (`application/vnd.de.mobile.seller-ad-v1.1+xml`) | Granulare CRUD-Operationen, Real-Time Sync |
|
||
| **CSV Upload Interface** | CSV (Semikolon-getrennt, ISO-8859-15) | Bulk-Upload aller Fahrzeuge auf einmal |
|
||
|
||
**Entscheidung**: Seller API (REST) als primäre Schnittstelle. CSV Upload als
|
||
Fallback/Initial-Bulk-Import.
|
||
|
||
### 15.2 Authentifizierung
|
||
|
||
**HTTP Basic Authentication** (empfohlen):
|
||
- Username + Password aus mobile.de Seller Portal
|
||
- `Authorization: Basic <base64(username:password)>`
|
||
- Alternativ (deprecated): `X-MOBILE-SELLER-TOKEN` Header
|
||
|
||
**Credentials in Environment Variables**:
|
||
```
|
||
MOBILEDE_SELLER_API_USERNAME=<username>
|
||
MOBILEDE_SELLER_API_PASSWORD=<password>
|
||
MOBILEDE_SELLER_KEY=<seller-key>
|
||
MOBILEDE_SELLER_API_URL=https://services.mobile.de
|
||
```
|
||
|
||
### 15.3 REST API Endpunkte
|
||
|
||
Base URL: `https://services.mobile.de`
|
||
|
||
| Method | Endpoint | Beschreibung |
|
||
|---|---|---|
|
||
| GET | `/seller-api/sellers` | Alle Sellers abrufen |
|
||
| GET | `/seller-api/sellers/{seller-key}` | Einzelnen Seller abrufen |
|
||
| GET | `/seller-api/sellers/{seller-key}/ads` | Alle Anzeigen eines Sellers |
|
||
| POST | `/seller-api/sellers/{seller-key}/ads` | Anzeige erstellen (XML Body) |
|
||
| GET | `/seller-api/sellers/{seller-key}/ads/{adId}` | Einzelne Anzeige abrufen |
|
||
| PUT | `/seller-api/sellers/{seller-key}/ads/{adId}` | Anzeige aktualisieren (XML Body) |
|
||
| DELETE | `/seller-api/sellers/{seller-key}/ads/{adId}` | Anzeige löschen |
|
||
| POST | `/seller-api/sellers/{seller-key}/ads/{adId}/vehicle-attribute/renewal-date` | Anzeige erneuern |
|
||
| PUT | `/seller-api/sellers/{seller-key}/ads/{adId}/images` | Bilder hochladen/ändern |
|
||
| GET | `/seller-api/sellers/{seller-key}/ads/{adId}/images` | Bild-URLs abrufen |
|
||
| DELETE | `/seller-api/sellers/{seller-key}/ads/{adId}/images` | Alle Bilder löschen |
|
||
| GET | `/seller-api/sellers/{seller-key}/ads/{ad-key}/statistic` | Anzeigen-Statistiken |
|
||
|
||
### 15.4 XML Ad-Format (Beispiel)
|
||
|
||
```xml
|
||
<ad:ad xmlns:ad="http://services.mobile.de/schema/ad">
|
||
<ad:vehicle>
|
||
<ad:category>Lkw</ad:category>
|
||
<ad:make>Mercedes-Benz</ad:make>
|
||
<ad:model>Actros</ad:model>
|
||
<ad:kilometre>150000</ad:kilometre>
|
||
<ad:firstRegistration>2020-03-15</ad:firstRegistration>
|
||
<ad:fuel>Diesel</ad:fuel>
|
||
<ad:gearbox>Manual</ad:gearbox>
|
||
<ad:price consumerPrice="true">45000.00</ad:price>
|
||
<ad:vat>1</ad:vat>
|
||
<ad:damagedVehicle>0</ad:damagedVehicle>
|
||
<ad:images>
|
||
<ad:image url="https://erp.media-on.de/files/vehicle-123/img1.jpg"/>
|
||
</ad:images>
|
||
</ad:vehicle>
|
||
</ad:ad>
|
||
```
|
||
|
||
### 15.5 Mapping: ERP Vehicle → mobile.de Ad
|
||
|
||
| ERP Feld | mobile.de XML Feld | Anmerkung |
|
||
|---|---|---|
|
||
| vehicle_type | ad:category | Lkw/Baumaschine/Pkw/Stapler/Transporter |
|
||
| brand | ad:make | |
|
||
| model | ad:model | |
|
||
| mileage_km | ad:kilometre | |
|
||
| first_registration | ad:firstRegistration | ISO Datum |
|
||
| power_kw | ad:power | kW-Wert |
|
||
| fuel_type | ad:fuel | Diesel/Petrol/Electric/Hybrid |
|
||
| sale_price | ad:price | consumerPrice="true" |
|
||
| registration_plate | ad:licensePlate | |
|
||
| vin | ad:vin | Fahrzeug-Identifikationsnummer |
|
||
| notes | ad:description | Freitext |
|
||
|
||
### 15.6 CSV Upload Interface (Fallback)
|
||
|
||
Format: Semikolon-getrennt, ISO-8859-15, `.csv` in `.zip` verpackt.
|
||
Pflichtfelder: internal number, category, make, model, kilometre, VAT,
|
||
damaged_vehicle, one-year-old car, new car, our recommendation, metallic, warranty.
|
||
|
||
**Use Case**: Initial-Bulk-Import aller Fahrzeuge nach mobile.de, oder wenn
|
||
Seller API nicht verfügbar.
|
||
|
||
### 15.7 Rate-Limits
|
||
|
||
Die mobile.de Dokumentation nennt keine expliziten Rate-Limits. Dennoch
|
||
implementieren wir einen eigenen Rate-Limiter (Redis Token Bucket):
|
||
- Max 10 Requests/Sekunde an mobile.de
|
||
- Retry-Queue bei HTTP 429 oder 5xx Fehlern (max 3 Retries mit Backoff)
|
||
|
||
### ADR-010: Seller API (REST) statt CSV Upload als primäre Schnittstelle
|
||
**Status**: Accepted
|
||
**Entscheidung**: REST Seller API als primäre mobile.de Schnittstelle.
|
||
**Begründung**: Granulare CRUD-Operationen, Real-Time Sync, Bild-Management,
|
||
Statistiken. CSV nur für Initial-Import oder Bulk-Operationen.
|
||
**Alternativen**: CSV Upload nur (kein Real-Time, kein Bild-Management).
|
||
|
||
---
|
||
|
||
## 16. Buchhaltungsschnittstellen
|
||
|
||
### 16.1 DATEV-Export (primär)
|
||
|
||
**Format**: DATEV CSV (ASCII, semikolon-getrennt) – Buchungsstapel.
|
||
|
||
**DATEV CSV Felder** (Buchungsstapel-Format):
|
||
```csv
|
||
"Buchungsstapel";"01.01.2026";"31.01.2026";"0001";"Mandantenname";"1000";"Beraternummer"
|
||
"Umsatz";"Soll";"Haben";"Konto";"Gegenkonto";"Datum";"Belegfeld";"Buchungstext"
|
||
45000.00;"S";4200;0840;15.01.2026;"RE-2026-001";"Fahrzeugverkauf Mercedes Actros"
|
||
```
|
||
|
||
**DATEV XML-Schnittstelle** (erweitert):
|
||
- DATEV unterstützt zusätzlich XML-Format für Belegdaten
|
||
- Belegbilder + Belegdaten als DATEV-konforme XML
|
||
- Vorteil: Belegbilder (Rechnungen/Verträge) können direkt mitgeliefert werden
|
||
- Implementiert als Feature-Flag `datev_xml_export_enabled`
|
||
|
||
### 16.2 Weitere Buchhaltungsschnittstellen
|
||
|
||
| System | Format | Status | Implementierung |
|
||
|---|---|---|---|
|
||
| DATEV | CSV (ASCII) + XML | Geplant (primär) | T06 – datev_service |
|
||
| Lexware | DATEV-CSV kompatibel | Über DATEV-Export nutzbar | Keine separate Implementierung nötig |
|
||
| SAP | CSV/IDoc | Zukunft (v2) | Feature-Flag, separates Export-Modul |
|
||
| DATEV Belegbilder | XML + Bilder | Zukunft (v2) | Feature-Flag `datev_xml_export_enabled` |
|
||
|
||
**Lexware-Kompatibilität**: Lexware unterstützt den DATEV-Import (CSV-Format).
|
||
Daher ist der DATEV-CSV-Export automatisch Lexware-kompatibel.
|
||
Lexware nutzt denselben DATEV-Datenexport "Belegbilder mit Belegdaten".
|
||
|
||
### 16.3 Export-Service Architektur
|
||
|
||
```
|
||
backend/app/utils/datev.py # DATEV CSV Formatter
|
||
backend/app/utils/accounting_export.py # Generic Export Base (für zukünftige Formate)
|
||
backend/app/services/datev_service.py # DATEV Export Logic
|
||
```
|
||
|
||
**Generic Export Pattern** (für zukünftige SAP/Weitere):
|
||
```python
|
||
class AccountingExporter(ABC):
|
||
@abstractmethod
|
||
def export(self, sales: list[Sale], period: DateRange) -> bytes: ...
|
||
|
||
class DatevCSVExporter(AccountingExporter): ...
|
||
class DatevXMLExporter(AccountingExporter): ... # v2
|
||
class SAPExporter(AccountingExporter): ... # v2
|
||
```
|
||
|
||
### ADR-011: DATEV CSV als primäre Buchhaltungsschnittstelle
|
||
**Status**: Accepted
|
||
**Entscheidung**: DATEV CSV-Format als primärer Export. Generic Export Base
|
||
für zukünftige Formate (XML, SAP).
|
||
**Begründung**: DATEV ist der deutsche Standard, Lexware-kompatibel,
|
||
einfach zu generieren. Abstract Base Class ermöglicht Erweiterung ohne
|
||
Code-Duplikation.
|
||
**Alternativen**: Direkte DATEV online API (zu komplex, benötigt
|
||
Zertifizierung), SAP IDoc (overkill für ~10 Nutzer).
|
||
|
||
---
|
||
|
||
## 17. KI-Copilot: Vollständige Systemsteuerung
|
||
|
||
### 17.1 Anforderung
|
||
|
||
Der KI-Copilot soll **ALLE** System-Funktionen steuern können. Der User
|
||
kann per Text oder Sprache mit dem Copilot interagieren und beliebige
|
||
Aktionen ausführen lassen.
|
||
|
||
### 17.2 Technische Umsetzung: Function Calling
|
||
|
||
**Mechanismus**: OpenRouter Function Calling / Tool Use
|
||
|
||
Der LLM (Claude/GPT-4) erhält einen System-Prompt mit allen verfügbaren
|
||
Functions (Tools). Bei einer User-Anfrage entscheidet der LLM, welche
|
||
Function aufgerufen werden muss, und gibt strukturiertes JSON zurück.
|
||
|
||
```
|
||
User: "Erstelle ein neues Fahrzeug: Mercedes Actros, 150000 km, 45000 EUR"
|
||
↓
|
||
LLM Output: {
|
||
"function": "create_vehicle",
|
||
"arguments": {
|
||
"vehicle_type": "lkw",
|
||
"brand": "Mercedes-Benz",
|
||
"model": "Actros",
|
||
"mileage_km": 150000,
|
||
"purchase_price": 45000.00
|
||
}
|
||
}
|
||
↓
|
||
Copilot Service: POST /api/vehicles (intern, mit User-JWT)
|
||
↓
|
||
Response to User: "Fahrzeug erstellt ✓ (ID: abc-123). Soll ich es
|
||
nach mobile.de pushen?"
|
||
```
|
||
|
||
### 17.3 Verfügbare System-Functions (Tools)
|
||
|
||
| Function | Beschreibung | Endpoint |
|
||
|---|---|---|
|
||
| search_vehicles | Fahrzeuge suchen/filtern | GET /api/vehicles |
|
||
| create_vehicle | Neues Fahrzeug anlegen | POST /api/vehicles |
|
||
| update_vehicle | Fahrzeug aktualisieren | PUT /api/vehicles/:id |
|
||
| delete_vehicle | Fahrzeug löschen (soft) | DELETE /api/vehicles/:id |
|
||
| push_to_mobilede | Nach mobile.de pushen | POST /api/vehicles/:id/mobilede-push |
|
||
| upload_ocr | OCR-Scan hochladen | POST /api/ocr/upload |
|
||
| apply_ocr | OCR-Daten anwenden | POST /api/ocr/results/:id/apply |
|
||
| search_contacts | Kontakte suchen | GET /api/contacts |
|
||
| create_contact | Kontakt anlegen | POST /api/contacts |
|
||
| update_contact | Kontakt aktualisieren | PUT /api/contacts/:id |
|
||
| upload_file | Datei hochladen | POST /api/vehicles/:id/files |
|
||
| list_files | Dateien auflisten | GET /api/vehicles/:id/files |
|
||
| create_sale | Verkauf anlegen | POST /api/sales |
|
||
| get_sale | Verkaufsdetails | GET /api/sales/:id |
|
||
| generate_contract | Vertrag generieren | POST /api/sales/:id/contract |
|
||
| export_datev | DATEV-Export | POST /api/datev/export |
|
||
| retouch_image | Bild retuschieren | POST /api/retouch/process |
|
||
| price_compare | Preisvergleich | POST /api/retouch/price-compare |
|
||
| get_statistics | Dashboard-Statistiken | GET /api/statistics |
|
||
| search_copilot | Eigene Historie durchsuchen | GET /api/copilot/history |
|
||
|
||
**Echt ALLES** – inkl. Admin-Functions (nur für Admin-Role):
|
||
| Function | Beschreibung | Endpoint |
|
||
|---|---|---|
|
||
| list_users | User auflisten | GET /api/users |
|
||
| create_user | User anlegen | POST /api/users |
|
||
| deactivate_user | User deaktivieren | DELETE /api/users/:id |
|
||
| update_letterhead | Briefpapier ändern | PUT /api/settings/letterhead |
|
||
|
||
### 17.4 Safety & Confirmation
|
||
|
||
**Action Preview Pattern** (wie in T07 definiert):
|
||
1. LLM schlägt Action vor (Function + Arguments)
|
||
2. Frontend zeigt Action Preview: "Ich möchte folgendes tun: [Action]"
|
||
3. User bestätigt oder ablehnt
|
||
4. Bei Bestätigung: Copilot führt Action aus (interner API-Call mit User-JWT)
|
||
5. Ergebnis wird im Chat angezeigt
|
||
|
||
**Auto-Execute bei Read-Only Actions**:
|
||
- search_vehicles, search_contacts, get_sale, list_files, get_statistics,
|
||
price_compare → automatisch ausgeführt (kein Bestätigung nötig)
|
||
- Alle schreibenden Actions → User-Bestätigung erforderlich
|
||
|
||
**RBAC durchgesetzt**: Copilot nutzt den JWT des aktuellen Users.
|
||
Ein Verkäufer kann keine User verwalten, auch nicht über den Copilot.
|
||
|
||
### 17.5 System-Prompt Struktur
|
||
|
||
```
|
||
Du bist der KI-Assistent für ein Nutzfahrzeug-ERP-System.
|
||
Du kannst Fahrzeuge verwalten, Kontakte anlegen, Verkäufe erstellen,
|
||
OCR-Scans verarbeiten, Bilder retuschieren und DATEV-Exporte erstellen.
|
||
|
||
Verfügbare Aktionen:
|
||
[Liste aller Functions mit Beschreibung und Parameters]
|
||
|
||
Regeln:
|
||
- Bei schreibenden Aktionen: schlage die Aktion vor und warte auf Bestätigung
|
||
- Bei Lese-Aktionen: führe sie direkt aus und präsentiere die Ergebnisse
|
||
- Antworte auf Deutsch (oder Englisch je nach User-Setting)
|
||
- Wenn Informationen fehlen: frage nach
|
||
```
|
||
|
||
### ADR-012: Function Calling für KI-Vollsteuerung
|
||
**Status**: Accepted
|
||
**Entscheidung**: OpenRouter Function Calling für alle System-Aktionen.
|
||
**Begründung**: Strukturierte JSON-Ausgabe, zuverlässig, von Claude/GPT-4
|
||
unterstützt. User-Bestätigung für schreibende Aktionen als Safety-Mechanismus.
|
||
**Alternativen**: Free-text parsing (fehleranfällig), MCP Protocol
|
||
(zu neu, noch nicht etabliert).
|
||
|
||
---
|
||
|
||
## 18. KI Multi-Provider Support
|
||
|
||
### 18.1 OpenRouter als Unified Gateway
|
||
|
||
**Entscheidung**: OpenRouter als alleiniges KI-Gateway. Keine direkten
|
||
Provider-Anbindungen.
|
||
|
||
**Begründung**: OpenRouter bietet:
|
||
- **400+ Modelle** von allen major providers (OpenAI, Anthropic, Google, Meta, Mistral, etc.)
|
||
- **Ein API-Key** für alle Modelle (kein separates Key-Management)
|
||
- **OpenAI-kompatible API** (drop-in replacement)
|
||
- **Provider-Routing**: Automatic fallback auf Backup-Provider bei Ausfall
|
||
- **Side-by-side Pricing**: Kostenvergleich aller Modelle
|
||
- **Tool Calling Support**: Modelle mit Function Calling verfügbar
|
||
|
||
### 18.2 Modell-Konfiguration pro Use-Case
|
||
|
||
Der User kann in den Admin-Settings konfigurieren, welches Modell für
|
||
welchen Use-Case verwendet wird:
|
||
|
||
| Use Case | Default Modell | Alternative |
|
||
|---|---|---|
|
||
| OCR (ZB I/II) | qwen/qwen-2.5-vl-72b-instruct | openai/gpt-4o |
|
||
| Bild-Retusche | black-forest-labs/flux-1.1-pro | stabilityai/stable-diffusion-xl |
|
||
| Copilot (Chat) | anthropic/claude-3.5-sonnet | openai/gpt-4o, google/gemini-flash-1.5 |
|
||
| Copilot (Voice STT) | openai/whisper-large-v3 | – |
|
||
|
||
**Settings Endpoint**:
|
||
```
|
||
GET /api/settings/ai-models → Aktuelle Modell-Konfiguration
|
||
PUT /api/settings/ai-models → Modell-Konfiguration aktualisieren (admin only)
|
||
```
|
||
|
||
**Config in DB** (Settings-Tabelle):
|
||
```json
|
||
{
|
||
"ocr_model": "qwen/qwen-2.5-vl-72b-instruct",
|
||
"retouch_model": "black-forest-labs/flux-1.1-pro",
|
||
"copilot_chat_model": "anthropic/claude-3.5-sonnet",
|
||
"copilot_voice_model": "openai/whisper-large-v3"
|
||
}
|
||
```
|
||
|
||
### 18.3 Provider-Fallback
|
||
|
||
OpenRouter bietet automatisches Provider-Routing:
|
||
- Bei Ausfall des primären Providers → automatischer Fallback auf Backup-Provider
|
||
- Beispiel: anthropic/claude-3.5-sonnet → bei Anthropic-Ausfall →
|
||
OpenRouter versucht alternative Provider für dasselbe Modell
|
||
|
||
**Eigenes Fallback-Handling** (zusätzlich):
|
||
```python
|
||
COPILOT_MODELS = [
|
||
"anthropic/claude-3.5-sonnet",
|
||
"openai/gpt-4o",
|
||
"google/gemini-flash-1.5",
|
||
"meta-llama/llama-3.1-70b-instruct"
|
||
]
|
||
# Bei Fehler: nächstes Modell in der Liste probieren
|
||
```
|
||
|
||
### 18.4 Kostenkontrolle
|
||
|
||
- OpenRouter zeigt Token-Kosten pro Request
|
||
- Logging: ai_usage_log Tabelle (model, tokens_in, tokens_out, cost, user_id, timestamp)
|
||
- Rate-Limiting pro User (Redis Token Bucket)
|
||
- Monatsbudget konfigurierbar (Admin-Setting ai_monthly_budget_eur)
|
||
- Bei Überschreitung: Warnung + ggf. Drosselung
|
||
|
||
### ADR-013: OpenRouter als alleiniges KI-Gateway
|
||
**Status**: Accepted
|
||
**Entscheidung**: Alle KI-Aufrufe über OpenRouter, keine direkten Provider-APIs.
|
||
**Begründung**: Ein API-Key, 400+ Modelle, Provider-Routing, einheitliche API,
|
||
keine Multi-SDK-Wartung. Modell-Konfiguration pro Use-Case ermöglicht
|
||
flexible Anpassung ohne Code-Änderung.
|
||
**Alternativen**: Direkte OpenAI + Anthropic APIs (Multi-Key-Management,
|
||
höhere Wartung), lokale Modelle via Ollama (zu langsam für OCR/Retusche,
|
||
aber möglich als Fallback für Copilot-Chat).
|
||
|
||
### 18.5 Optional: Lokale Modelle via Ollama (Copilot Fallback)
|
||
|
||
Für den Copilot-Chat (nicht OCR/Retusche) kann optional ein lokales Modell
|
||
via Ollama als Offline-Fallback konfiguriert werden:
|
||
- Feature-Flag local_model_fallback_enabled
|
||
- Ollama läuft als 5. Container (optional, nur bei Bedarf)
|
||
- Modell: llama3.1:8b-instruct oder qwen2.5:14b
|
||
- Use Case: Offline-Betrieb oder Kostenersparnis für einfache Chat-Tasks
|
||
- Function Calling Support: Llama 3.1 unterstützt native Tool-Calling
|
||
|
||
**Architektur-Erweiterung** (v2, Feature-Flag):
|
||
```
|
||
Copilot Request → Try OpenRouter → Fail → Try local Ollama → Fail → Error
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Konventionen
|
||
|
||
### 13.1 Backend
|
||
- async/await für alle DB-Operationen und externen API-Calls
|
||
- Pydantic v2 für alle Request/Response-Schemas
|
||
- SQLAlchemy 2.0 mit typed Mapped columns
|
||
- Alembic für DB-Migrationen (kein auto-create in Produktion)
|
||
- Router-Prefix: `/api/<resource>`
|
||
- Error-Format: `{"error": {"code": "VEHICLE_NOT_FOUND", "message": "...", "details": {}}}`
|
||
|
||
### 13.2 Frontend
|
||
- TypeScript strict mode
|
||
- Tailwind CSS für Styling (kein CSS-in-JS)
|
||
- Server Components für statische Daten, Client Components für Interaktion
|
||
- API-Client: zentrale fetch-Wrapper mit Auth-Header-Injection
|
||
- Date-Naming: kebab-case für Dateien, camelCase für Variablen
|
||
|
||
### 13.3 Git
|
||
- Conventional Commits: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`
|
||
- Branch-Naming: `feature/T01-vehicle-module`, `fix/...`, `hotfix/...`
|
||
- PRs erforderlich für main-Branch
|
||
|
||
## 19. Provider Architecture (Multi-Provider mit Ollama Cloud)
|
||
|
||
### 19.1 Provider Abstraktion (ABC Pattern)
|
||
|
||
```python
|
||
from abc import ABC, abstractmethod
|
||
|
||
class BaseAIProvider(ABC):
|
||
@abstractmethod
|
||
async def chat(self, messages, tools=None, model=None) -> dict: ...
|
||
@abstractmethod
|
||
async def vision(self, image_base64, prompt, model=None) -> str: ...
|
||
@abstractmethod
|
||
async def image_edit(self, image_base64, instruction, model=None) -> str: ...
|
||
@abstractmethod
|
||
def list_models(self) -> list[str]: ...
|
||
@abstractmethod
|
||
def health_check(self) -> bool: ...
|
||
|
||
class OpenRouterProvider(BaseAIProvider): ...
|
||
class OllamaCloudProvider(BaseAIProvider): ...
|
||
class OllamaLocalProvider(BaseAIProvider): ...
|
||
```
|
||
|
||
Neue Provider durch Implementierung von BaseAIProvider + Eintrag in providers.yaml hinzufügbar.
|
||
|
||
### 19.2 providers.yaml
|
||
|
||
```yaml
|
||
providers:
|
||
openrouter:
|
||
enabled: true
|
||
api_base: https://openrouter.ai/api/v1
|
||
api_key: ${OPENROUTER_API_KEY}
|
||
models: [anthropic/claude-3.5-sonnet, openai/gpt-4o, qwen/qwen2.5-vl]
|
||
ollama_cloud:
|
||
enabled: true
|
||
api_base: ${OLLAMA_CLOUD_URL}
|
||
api_key: ${OLLAMA_CLOUD_API_KEY}
|
||
models: [glm-5.2:cloud, qwen2.5:14b, llava:13b]
|
||
ollama_local:
|
||
enabled: false
|
||
api_base: http://ollama:11434
|
||
models: [llama3.1:8b-instruct, qwen2.5:14b]
|
||
```
|
||
|
||
### 19.3 Use-Case → Provider Mapping
|
||
|
||
| Use Case | Default | Fallback |
|
||
|---|---|---|
|
||
| Copilot Chat | ollama_cloud (glm-5.2) | openrouter (claude-3.5) |
|
||
| OCR | openrouter (qwen2.5-vl) | ollama_cloud (llava) |
|
||
| Bildretusche | openrouter (flux.1-pro) | - |
|
||
| Voice | openrouter | browser-native |
|
||
|
||
Admin konfigurierbar via PUT /api/settings/ai-providers
|
||
|
||
### ADR-014: Multi-Provider mit Ollama Cloud
|
||
- Ollama Cloud als ECHTER Provider (nicht nur Fallback)
|
||
- Provider-Abstraktion ermöglicht beliebige zukünftige Provider
|
||
- Admin kann pro Use-Case Provider wählen
|