Files
aza/AzA march 2026 - Kopie (15)/AZA_DETAILED_HANDOVER.md
2026-04-19 20:41:37 +02:00

431 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AZA Detaillierte Projektuebergabe / Handover
**Stand: 2026-04-06**
**Zweck:** Diese Datei ist die verbindliche Referenz fuer jeden neuen Chat. Zuerst lesen, dann arbeiten.
---
## 1. Projektziel / Aktueller Fokus
**AZA** (AZA Medical AI Assistant / AZA Desktop) ist eine medizinische KI-Desktop-Anwendung fuer Windows. Sie unterstuetzt Aerzte bei Diktat, Textverarbeitung, medizinischer Recherche und Dokumentation.
**Architektur (Variante B verbindlich seit 2026-03-25):**
- Desktop-App (Python/Tkinter) kommuniziert mit eigenem AZA-Backend auf Hetzner
- Backend leitet KI-Anfragen serverseitig an OpenAI weiter
- Kein OpenAI-Key beim Kunden noetig
- Kein OpenAI-Key in der Desktop-App
**Aktueller Schwerpunkt:**
Produktiver Kundenfluss mit Lizenzschluessel. Die klare Zielsequenz ist:
```
Kauf → Lizenzschluessel per E-Mail → Download → Installation → Aktivierung → Nutzung
```
---
## 2. Aktueller stabiler technischer Stand
### 2.1 Lizenzschluessel-Flow (PRODUKTIV, Stand 2026-04-06)
| Aspekt | Status |
|---|---|
| `/license/activate` | Funktioniert produktiv auf Hetzner |
| `/license/status` | Funktioniert produktiv auf Hetzner |
| Lizenzschluessel-Erzeugung | Automatisch beim Kauf (Format: `AZA-XXXX-XXXX-XXXX-XXXX`) |
| Lizenzschluessel in DB | Gespeichert in `licenses`-Tabelle, Spalte `license_key` |
| Desktop-Aktivierung | Lizenzschluessel kann in der Desktop-App eingegeben und aktiviert werden |
| `/license/status?license_key=...` | Liefert `valid: true` fuer aktive Lizenzen |
| Device-Enforcement | Aktiv und funktioniert korrekt |
| Success-Seite `/billing/success` | Zeigt dem Kunden den Lizenzschluessel nach Kauf an |
| Produktiver Test | Erfolgreich mit aktivem Lizenzschluessel |
**Wichtig:** Device-Bindings waren zeitweise ein Blocker. Fuer den Testdatensatz (`admin@aza-medwork.ch`) wurden bestehende Device-Bindings aus der DB geloescht, damit der erneute Aktivierungstest funktionierte. Bei neuen Kunden tritt dieses Problem nicht auf.
### 2.2 Mailversand (PRODUKTIV ueber Resend, Stand 2026-04-06)
| Aspekt | Status |
|---|---|
| Produktiver Versandkanal | **Resend HTTP API** |
| Resend-Domain | `mail.aza-medwork.ch` (DNS bei Hostpoint verifiziert) |
| Absender / MAIL_FROM | `AZA MedWork <noreply@mail.aza-medwork.ch>` |
| Test-Endpunkt | `POST /stripe/test_license_email?email=...` (Admin-geschuetzt) |
| Letzter erfolgreicher Test | `{"sent": true, "to": "admin@aza-medwork.ch"}` |
| E-Mail Zustellung | Produktiv bestaetigt Mail kommt an |
### 2.3 Stripe / Billing (PRODUKTIV)
| Aspekt | Status |
|---|---|
| Stripe-Modus | Live (sk_live_) |
| Webhook-Endpunkt | `https://api.aza-medwork.ch/stripe/webhook` |
| Echter Live-Kauf | CHF 59 Basic Monthly erfolgreich durchgefuehrt |
| Lizenz-Lifecycle | Kauf → active → Storno/Refund → canceled → Desktop Testmodus (bewiesen) |
| Webhook-Events | `checkout.session.completed`, `customer.subscription.updated`, `customer.subscription.deleted` |
### 2.4 Desktop-App
| Aspekt | Status |
|---|---|
| Lizenzpruefung | Ueber Backend (`/license/status`) mit Lizenzschluessel |
| Vollmodus | Wenn Backend `valid: true` liefert |
| Testmodus | Wenn keine gueltige Lizenz oder Backend nicht erreichbar |
| Lokales Aktivierungs-Gate | Bei Remote-Backend uebersprungen (Root Cause 14 behoben) |
| Update-Checker | Prueft `https://api.aza-medwork.ch/download/version.json` beim Start |
| Aktuelle Version | `APP_VERSION = "1.0.0"`, `APP_CHANNEL = "stable"` |
### 2.5 Admin Control Panel (PRODUKTIV)
8 interne Admin-Endpunkte, geschuetzt via `X-Admin-Token` / `AZA_ADMIN_TOKEN`:
**v1:**
- `GET /admin/system_status` App-Health, Uptime, Disk, Stripe-Config, DB-Info
- `GET /admin/licenses_overview` Lizenzen nach Status, letzte 20, `?email=` Filter
- `GET /admin/backup_status` Backup-Pfade, Groesse, neustes Backup, Log-Tail
- `GET /admin/billing_overview` Stripe-Health, Lizenz-Summary, Event-Log
**v2:**
- `GET /admin/license_customer_map` Detaillierte Lizenznehmer-Uebersicht, `?email=` und `?status=` Filter
- `GET /admin/revenue_overview` MRR, Stripe-Live-Daten (gross/refunds/net), recent_charges, recent_refunds
- `GET /admin/alerts` Strukturierte Warnliste (info/warning/critical)
- `GET /admin/dashboard_summary` Sammel-Endpunkt fuer alle Kennzahlen
### 2.6 Backup / Storage
- Taegliches Backup-Skript: `/root/aza-backups/backup_aza.sh` (Cronjob)
- Backup-Pfad: `/root/aza-backups/daily/`
- In Container gemountet als `/host_backups` (read-only)
- Ca. 137 GB frei, ca. 4-5% belegt kein Speicherdruck
---
## 3. Mailversand-Historie / Root Causes / Finaler Weg
### Chronologie
1. **Erster Versuch: Hostpoint-SMTP**
- Hostpoint-Mailbox `noreply@aza-medwork.ch` wurde angelegt
- SMTP-Server: `asmtp.mail.hostpoint.ch`
- Port 465 (SSL) und 587 (STARTTLS) getestet
- SMTP-Daten wurden mehrfach geprueft und waren korrekt
2. **Beobachtete Fehler (SMTP von Hetzner/Container):**
- Erste Tests: Auth-Fehler (falscher Host `mail.hostpoint.ch` statt `asmtp.mail.hostpoint.ch`)
- Nach Host-Korrektur: `OSError: [Errno 101] Network is unreachable`
- Port 465 → Timeout / Network unreachable
- Port 587 → Timeout / Network unreachable
- Ursache: Hetzner-Container kann Hostpoint-SMTP-Server nicht erreichen (Netzwerk-/Firewallsperre)
3. **Schlussfolgerung:**
- Hostpoint-SMTP ist von Hetzner aus nicht nutzbar
- Das ist ein Infrastruktur-/Netzwerkproblem, kein Code-Problem
- Hostpoint-SMTP ist **nicht** der produktive Versandweg
4. **Loesung: Umstellung auf Resend HTTP API**
- Resend-Account erstellt
- Domain `mail.aza-medwork.ch` bei Resend registriert und via DNS bei Hostpoint verifiziert
- `MAIL_FROM` gesetzt auf `AZA MedWork <noreply@mail.aza-medwork.ch>`
- Code in `stripe_routes.py` umgebaut: `_send_via_resend()` als primaerer Kanal
- **Wichtiger Fix:** Resend-HTTP-API erfordert `User-Agent` Header (ohne → Error 1010/403)
- Fix angewandt: `"User-Agent": "AZA-MedWork/1.0"` im Request
5. **Finaler erfolgreicher Test:**
```
POST /stripe/test_license_email?email=admin@aza-medwork.ch
→ {"sent": true, "to": "admin@aza-medwork.ch"}
```
E-Mail kam produktiv an.
### Aktueller Zustand der Mailfunktion in `stripe_routes.py`
```
send_license_email(to_email, license_key)
├── RESEND_API_KEY gesetzt? → _send_via_resend() [PRODUKTIVER WEG]
└── sonst → _send_via_smtp() [INAKTIVER FALLBACK]
```
- SMTP-Code ist noch vorhanden als Fallback
- SMTP-Variablen in `.env` sind Altlast, nicht produktiv aktiv
- Wenn `RESEND_API_KEY` gesetzt ist (und das ist es), wird immer Resend benutzt
---
## 4. Wichtige Pfade / Betriebsorte / Operator-Wissen
### Lokaler Windows-Rechner
| Was | Pfad |
|---|---|
| Projektordner | `C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026` |
| Desktop-App direkt starten | `python basis14.py` (im Projektordner) |
| Build-EXE | `.\build_exe.ps1` |
| Build-Installer | `.\build_installer.ps1` |
| Kompletter Release | `.\ship_release.ps1` |
| Nur Upload | `.\publish_update.ps1` |
| Installer-Artefakt | `dist\installer\aza_desktop_setup.exe` |
| Release-Manifest | `release\version.json` |
| Versionsquelle | `aza_version.py` (`APP_VERSION`, `APP_CHANNEL`) |
**Wichtig:** NICHT ueber lokale Starter starten (`start_all.bat`, `RUN_AZA_ONECLICK.bat`, `START_AZA.bat`, `start_backend_autoport.bat`) diese setzen Env-Variablen auf localhost und ueberschreiben die Live-Backend-URL.
### Hetzner-Server (SSH)
| Was | Pfad / Befehl |
|---|---|
| SSH-Zugang | `ssh root@178.104.51.177` |
| Repo-Root | `/root/aza-app` |
| Docker-Compose-Ordner | `/root/aza-app/deploy` |
| `.env`-Datei | `/root/aza-app/deploy/.env` |
| Rebuild (immer im deploy-Ordner!) | `cd /root/aza-app/deploy && docker compose down && docker compose up -d --build` |
| Container-Logs | `docker logs aza-api --tail 100` |
| ENV im Container pruefen | `docker exec aza-api env \| grep VARIABLE` |
| Backup-Ordner | `/root/aza-backups/daily/` |
**WICHTIG:** `docker compose` Befehle muessen IMMER im Ordner `/root/aza-app/deploy` ausgefuehrt werden, nicht im Repo-Root `/root/aza-app`.
### Hostpoint (Website / DNS)
| Was | Detail |
|---|---|
| Haupt-Website | Hostpoint bleibt fuer Website, Marketing, WooCommerce |
| DNS-Verwaltung | Bei Hostpoint (fuer `aza-medwork.ch` und Subdomains) |
| Mailboxen | Hostpoint verwaltet Mailboxen (z.B. `noreply@aza-medwork.ch`) |
| Resend-DNS | `mail.aza-medwork.ch` DNS-Records fuer Resend bei Hostpoint gesetzt |
### Produktive URLs
| URL | Zweck |
|---|---|
| `https://api.aza-medwork.ch` | Backend-API |
| `https://api.aza-medwork.ch/health` | Health-Check |
| `https://api.aza-medwork.ch/stripe/webhook` | Stripe-Webhook |
| `https://api.aza-medwork.ch/download/version.json` | Update-Manifest |
| `https://api.aza-medwork.ch/download/aza_desktop_setup.exe` | Installer-Download |
| `https://api.aza-medwork.ch/billing/success` | Kauf-Erfolgsseite |
---
## 5. Wichtige ENV / Konfiguration
### Aktive produktive ENV-Variablen (auf Hetzner in `/root/aza-app/deploy/.env`)
| Variable | Rolle | Status |
|---|---|---|
| `RESEND_API_KEY` | Resend API Credential fuer Mailversand | **AKTIV PRODUKTIV** |
| `MAIL_FROM` | Absender fuer Lizenzschluessel-Mail | **AKTIV PRODUKTIV** |
| `STRIPE_SECRET_KEY` | Stripe Live API Key (sk_live_...) | **AKTIV PRODUKTIV** |
| `STRIPE_WEBHOOK_SECRET` | Stripe Webhook Signing Secret | **AKTIV PRODUKTIV** |
| `AZA_ADMIN_TOKEN` | Token fuer Admin-Endpunkte | **AKTIV PRODUKTIV** |
| `MEDWORK_API_TOKENS` | API-Token fuer Desktop-Backend-Kommunikation | **AKTIV PRODUKTIV** |
| `OPENAI_API_KEY` | OpenAI API Key (serverseitig) | **AKTIV PRODUKTIV** |
| `AZA_DOMAIN` | `api.aza-medwork.ch` | **AKTIV PRODUKTIV** |
| `ACME_EMAIL` | `info@aza-medwork.ch` (fuer Caddy/HTTPS) | **AKTIV PRODUKTIV** |
### Inaktive / historische ENV-Variablen
| Variable | Rolle | Status |
|---|---|---|
| `SMTP_HOST` | Hostpoint SMTP Server | **INAKTIV** Fallback, wird nicht genutzt |
| `SMTP_PORT` | Hostpoint SMTP Port | **INAKTIV** Fallback |
| `SMTP_USER` | Hostpoint SMTP User | **INAKTIV** Fallback |
| `SMTP_PASS` | Hostpoint SMTP Passwort | **INAKTIV** Fallback |
| `SMTP_FROM` | Hostpoint SMTP Absender | **INAKTIV** Fallback |
**Keine Rueckkehr zu Hostpoint-SMTP noetig, solange Resend stabil laeuft.**
---
## 6. Naechster Hauptblock: End-to-End-Kundentest
**Ziel:** Den kompletten Kundenfluss ohne Basteln, ohne manuelle DB-Eingriffe und ohne Operator-Hilfe beweisen.
### Zielbild fuer den Kundenfluss
```
1. Kunde kauft ueber Stripe Payment Link / Checkout
2. Stripe Webhook verarbeitet den Kauf
3. Backend erzeugt Lizenzschluessel und speichert ihn in der DB
4. Resend sendet automatisch E-Mail mit Lizenzschluessel an den Kunden
5. Success-Seite zeigt dem Kunden ebenfalls den Lizenzschluessel
6. Kunde laedt AZA ueber offiziellen Download-Link herunter
7. Kunde installiert AZA
8. Kunde gibt Lizenzschluessel in der Desktop-App ein
9. Desktop aktiviert gegen Backend (/license/activate)
10. Desktop startet im Vollmodus
```
### Was dabei noch geprueft / sichergestellt werden muss
- [ ] E-Mail mit Lizenzschluessel kommt automatisch beim Kauf an (nicht nur via Test-Endpunkt)
- [ ] Download-Link in der E-Mail ist korrekt und funktioniert
- [ ] Installer laesst sich sauber installieren
- [ ] Erststart ohne vorherige Konfiguration funktioniert
- [ ] Lizenzschluessel-Eingabe in der App funktioniert auf Anhieb
- [ ] Vollmodus wird sofort nach Aktivierung erreicht
---
## 7. Download-/Installer-Entscheidung
**Fuer den naechsten Kundenfluss-Test:**
- Download soll ueber die offizielle Website / Download-Seite priorisiert werden
- Nicht zuerst einen rohen Direktlink als Hauptweg verwenden
- Die E-Mail soll idealerweise einen klaren Download-Link enthalten
- Der Kundentest soll moeglichst realistisch am echten Kundenablauf orientiert sein
**Zielbild:**
Mail mit Lizenzschluessel + klarem Download-Link → Download → Installation → Aktivierung
**Aktuell verfuegbarer Direktlink:**
`https://api.aza-medwork.ch/download/aza_desktop_setup.exe`
---
## 8. Offene Restpunkte
| Punkt | Prioritaet | Blocker? |
|---|---|---|
| Mailtext und Success-Seite inhaltlich polieren | Niedrig | Nein |
| Admin-Token rotieren (wurde im Chat offengelegt) | Mittel | Nein, aber vor echtem Kundenbetrieb empfohlen |
| SMTP-Reste in `.env` aufraeumen | Niedrig | Nein (inaktiv) |
| Resend-Setup / Domain-Policy weiter polieren | Niedrig | Nein |
| Device-Bindings-Management fuer Mehrgeraete klarer machen | Mittel | Nein |
| translate.py, aza_email.py, diktat_app.py auf Backend-Chat migrieren | Niedrig | Nein (Nebenpfade) |
| WooCommerce / Website-Kaufpfad professionalisieren | Mittel | Spaeterer Block |
| Browser-AZA / Web-App | Niedrig | Spaeterer Block |
---
## 9. Arbeitsstil / Nutzerpraeferenzen
**Diese Regeln gelten fuer ALLE zukuenftigen Chats:**
### Allgemeine Regeln
- Nutzer bastelt nicht alle Aenderungen kommen als fertige, vollstaendige Dateien (ready-to-paste)
- Nutzer fuehrt nur vorgegebene Commands aus, keine manuellen Edits
- Jede Aenderung in 1 Patch, kein schrittweises Anleiten
- Keine risky Refactors immer minimal und sicher
- Root-cause-first bei jedem Problem
- Keine Monsterpatches
- Keine Rueckfragen-Orgien
- Keine Variantenflut genau 1 Weg, der beste
### Operator-Schritte
- **Immer** explizit angeben, WO ein Schritt auszufuehren ist:
- `[Windows PowerShell]` lokaler Rechner, Projektordner
- `[Hetzner SSH]` `ssh root@178.104.51.177`
- `[Browser]` URL angeben
- `[Composer/IDE]` Cursor Editor
- **Immer** exakte Copy-Paste-Befehle liefern
- **Immer** mit Pfad oder Ort starten
- Nicht schreiben, was der Nutzer NICHT tun soll, sondern nur den naechsten exakten Schritt
- Keine vagen Formulierungen wie "send this" oder "do something like"
- Nutzer will moeglichst wenig manuelle Improvisation
### Uebergaben
- Uebergaben fuer naechste Chats sollen ausfuehrlich sein, nicht minimal
- Wichtige Root Causes immer dokumentieren
- Geloeste Probleme klar als geloest markieren
- Nicht bei alten Problemen wieder anfangen
---
## 10. Empfohlener Chat-Start fuer den naechsten Chat
### Sinnvolle naechste Hauptbloecke (nach Prioritaet)
1. **End-to-End-Kundentest** (EMPFOHLEN als naechstes)
- Kontrollierter Kauf → automatische E-Mail → Download → Installation → Aktivierung → Vollmodus
- Beweisen, dass der gesamte Fluss ohne Basteln funktioniert
2. **Download-Seite / Website-Kaufpfad**
- Offizielle Download-Seite auf der Website einrichten
- Klaren Kundenweg von Website → Kauf → Download definieren
3. **Admin-Token-Rotation + Secrets-Hygiene**
- Offengelegten Admin-Token rotieren
- Sicherstellen, dass keine Secrets im Repo liegen
### Beste Empfehlung
**Starte mit Block 1: End-to-End-Kundentest.**
### Erster konkreter Operator-Schritt
```
[Browser]
Stripe Payment Link oeffnen und kontrollierten Testkauf mit einer
frischen E-Mail-Adresse durchfuehren (NICHT admin@aza-medwork.ch,
sondern eine neue Adresse, um den Neukundenfall zu simulieren).
```
Danach pruefen:
1. `[Hetzner SSH]` `/stripe/license_debug?email=NEUE_EMAIL` → aktive Lizenz?
2. `[E-Mail-Postfach]` Lizenzschluessel-Mail angekommen?
3. `[Browser]` Installer herunterladen ueber Link aus der Mail
4. `[Windows]` Installer ausfuehren, App starten, Lizenzschluessel eingeben
5. `[Desktop-App]` Vollmodus bestaetigen
---
## 11. Geloeste Root Causes (Referenz)
| RC | Problem | Loesung | Datei | Datum |
|---|---|---|---|---|
| RC14 | Desktop zeigte Testversion trotz aktiver Remote-Lizenz | `_has_remote_backend()` Bypass fuer lokales Aktivierungs-Gate | `basis14.py` | 2026-03-30 |
| RC15 | `current_period_end` war null fuer aktive Lizenz | Fallback auf `items.data[0].current_period_end` im Webhook | `stripe_routes.py` | 2026-03-30 |
| RC16 | revenue_overview zu grob fuer Betreiber | `recent_charges` und `recent_refunds` ergaenzt | `admin_routes.py` | 2026-03-31 |
| RC17 | SMTP von Hetzner → Hostpoint nicht erreichbar | Umstellung auf Resend HTTP API | `stripe_routes.py` | 2026-04-06 |
| RC18 | Resend-API lehnte Request ab (Error 1010/403) | `User-Agent: AZA-MedWork/1.0` Header ergaenzt | `stripe_routes.py` | 2026-04-06 |
---
## 12. Wichtige Dateien im Projekt
### Backend (auf Hetzner unter `/root/aza-app/`)
| Datei | Rolle |
|---|---|
| `backend_main.py` | FastAPI-Hauptanwendung, mountet alle Router |
| `stripe_routes.py` | Stripe-Webhook, Lizenz-DB, Mailversand, Lizenzschluessel-Erzeugung |
| `admin_routes.py` | Admin Control Panel v1+v2 Endpunkte |
| `aza_license_logic.py` | `compute_license_decision()` Lizenzgueltigkeit berechnen |
| `aza_device_enforcement.py` | Device-Bindings verwalten |
| `aza_security.py` | `require_api_token`, `require_admin_token` |
### Desktop (lokal)
| Datei | Rolle |
|---|---|
| `basis14.py` | Haupt-Desktop-App (8900+ Zeilen), UI, Lizenzcheck, Aktivierung |
| `aza_version.py` | `APP_VERSION`, `APP_CHANNEL` zentrale Versionsquelle |
| `aza_style.py` | UI-Styling-Konstanten |
| `desktop_update_check.py` | Update-Checker beim App-Start |
| `aza_desktop.spec` | PyInstaller-Spezifikation |
### Build / Release (lokal)
| Datei | Rolle |
|---|---|
| `build_exe.ps1` | Baut EXE mit PyInstaller |
| `build_installer.ps1` | Baut Installer mit Inno Setup |
| `build_release_manifest.ps1` | Erzeugt `release/version.json` |
| `release.ps1` | Lokaler Release-Prozess (Build + Verify) |
| `publish_update.ps1` | Upload nach Hetzner |
| `ship_release.ps1` | **Verbindlicher Ein-Knopf-Release** (Build + Upload) |
---
## Abschluss
**Wenn der naechste Chat startet:**
1. Zuerst diese Datei lesen
2. Nicht wieder bei alten SMTP-/Deploy-/Pfadfehlern beginnen
3. Hostpoint-SMTP ist nicht der produktive Weg Resend funktioniert
4. Lizenzschluessel-Flow ist produktiv nicht neu bauen
5. Admin-Endpunkte sind produktiv nicht neu bauen
6. Direkt beim naechsten sinnvollen Block weitermachen (siehe Abschnitt 10)