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
|