21 KiB
AZA - Master Handover / Operational Runbook
Working Mode Rules
- User does not tinker or manually edit files.
- Always provide 1:1 Composer patches (usually Opus).
- One step at a time.
CURRENT PROJECT PHASE (2026-03-27)
Phase: B1 Backend Sprint COMPLETE – Variant B proven in production
STATUS: B1 Backend Sprint FULLY COMPLETE (2026-03-27)
- B1-W1 through B1-W5 all successfully completed
- Desktop → Hetzner → OpenAI works in the real app
- No local OpenAI key needed for main path
basis14.pylocally tested against live backend successfully
Hetzner Backend is LIVE (2026-03-26)
- Production API path:
https://api.aza-medwork.ch - DNS:
api.aza-medwork.ch→178.104.51.177 - Repo on Hetzner:
/root/aza-app - Deploy directory:
/root/aza-app/deploy - Running services:
aza-api+aza-caddy - Variant B technically proven end-to-end
ARCHITECTURE DECISION VARIANT B (mandatory, 2026-03-25):
- NO OpenAI key in the desktop app
- NO per-customer OpenAI key requirement
- NO shared OpenAI key client-side
- Instead: AZA Office talks ONLY to own AZA backend
- Only the AZA backend talks to OpenAI
- The OpenAI key resides EXCLUSIVELY server-side
- NO half-measures, NO shared-key hacks
Successful Proofs (2026-03-26):
curl https://api.aza-medwork.ch/health→ successfulcurl -X POST https://api.aza-medwork.ch/v1/chatwith validX-API-Token→ success:true, content:OK- Proven: Hetzner backend runs, Caddy/HTTPS runs, server-side OpenAI access works, Variant B works end-to-end
Root Causes Solved During Deploy:
- Wrong git remote (
naswinterthur) was unsuitable - Uploading only
deploy/was wrong –docker-compose.ymlexpects repo root context (context: ..,dockerfile: deploy/Dockerfile) - Host
nginxblocked port 80 - Caddy DNS/ACME issues due to resolver
127.0.0.53→ Fix: explicit DNS1.1.1.1+8.8.8.8in caddy service
Final Hetzner State:
- Services:
aza-api+aza-caddy - Compose runs from
/root/aza-app/deploy .envis production-relevant thereAZA_DOMAIN=api.aza-medwork.chACME_EMAIL=info@aza-medwork.chMEDWORK_API_TOKENSis set productivelyOPENAI_API_KEYis set server-side
B1 BACKEND SPRINT (4 Weeks)
| Week | Approx. Dates | Focus |
|---|---|---|
| 1 | Mar 25 – Apr 01 | Backend Chat Proxy Endpoint (POST /v1/chat) + Auth + Rate Limiting |
| 2 | Apr 02 – Apr 08 | Desktop app: all OpenAI calls via backend instead of direct |
| 3 | Apr 09 – Apr 15 | Hetzner Deploy: Docker/Caddy/HTTPS + Production Env + Smoke Tests |
| 4 | Apr 16 – Apr 22 | E2E Test Desktop→Backend→OpenAI + Customer journey without OpenAI key + Go-Live |
CURRENT PRIORITY ORDER (B1 Backend Sprint)
- Backend Chat Proxy POST /v1/chat (Week 1) – Foundation of architecture
- Desktop app migration to backend (Week 2) – Migrate all OpenAI calls
- Hetzner Deploy (Week 3) – Set up production server
- E2E Test + Go-Live backend path (Week 4) – Customer journey without OpenAI key
- WooCommerce/Stripe purchase path runs IN PARALLEL (Hostpoint)
EXPLICITLY DEFERRED / NOT NOW:
- Update comfort / separate auto-updater
- Browser-AZA web app (after backend architecture)
- Large refactors not serving Variant B
HOSTPOINT vs. HETZNER:
- Hostpoint remains for website/marketing/WooCommerce/Stripe
- Hetzner is NOW the backend path for OpenAI proxy/API
- Both run in parallel, not against each other
DONE: B1-W1 Backend Chat Proxy Endpoint (2026-03-26)
POST /v1/chat – IMPLEMENTED AND VERIFIED.
| Aspect | Detail |
|---|---|
| Auth | require_api_token (X-API-Token header, existing pattern) |
| Rate Limiting | default_ip_limiter + default_token_limiter |
| Request Schema | ChatRequest: model, messages[], temperature?, max_tokens?, top_p? |
| Message Schema | ChatMessage: role (system/user/assistant), content |
| Model Whitelist | gpt-5.2, gpt-5-mini, gpt-5-nano, gpt-4o, gpt-4o-mini, gpt-4o-mini-search-preview |
| Input Limits | Max 64 messages, max 100k chars/message |
| OpenAI Call | _get_openai().chat.completions.create(**params) server-side |
| Response Schema | {success, content, finish_reason, model, usage, request_id, duration_ms, error} |
| Secret Scrubbing | sk-, sk-proj-, org- in error texts are replaced |
| Tests | 7/7 green (auth, model, validation, OpenAI proxy, /license/status, schema) |
File: backend_main.py (~100 lines added)
DONE: B1-W2 Desktop Chat Migration (2026-03-26)
Desktop app: Chat completions now routed through backend POST /v1/chat.
Migrated:
call_chat_completion()in basis14.py – central wrapper ->_backend_chat_completion()- News search – direct
self.clientcall ->_backend_chat_completion() - Comments – own
OpenAI()client ->_backend_chat_completion() - Med detail short info – own
OpenAI()client ->_backend_chat_completion() - Letter style analysis in
aza_text_windows_mixin.py->_backend_chat_completion()
New architecture in basis14.py:
_BackendChatResponse– wrapper class, OpenAI-interface compatible (.choices[0].message.content, .usage)_backend_chat_completion(**kwargs)– POST /v1/chat using existing token mechanism (get_backend_url/get_backend_token)call_chat_completion(**kwargs)– consent/capacity check, then_backend_chat_completion()- Timeout: 5s connect, 120s read
- Errors: RuntimeError with readable message, no secrets
Remaining for later blocks: translate.py, congress_window.py, aza_email.py (standalone modules)
7/7 tests green: Health, Auth, Model validation, E2E Desktop->Backend->OpenAI, Response schema, /license/status OK.
DONE: B1-W3 Hetzner Deploy (2026-03-26)
Hetzner backend is LIVE.
| Aspect | Detail |
|---|---|
| Domain | https://api.aza-medwork.ch |
| DNS | api.aza-medwork.ch → 178.104.51.177 (A record) |
| Repo on Hetzner | /root/aza-app |
| Deploy directory | /root/aza-app/deploy |
| Running services | aza-api + aza-caddy |
| Compose | runs from /root/aza-app/deploy |
.env production |
AZA_DOMAIN=api.aza-medwork.ch, ACME_EMAIL=info@aza-medwork.ch, MEDWORK_API_TOKENS set, OPENAI_API_KEY set server-side |
DONE: B1-W4 Server E2E Test (2026-03-26)
Server-side Variant B proven end-to-end:
curl https://api.aza-medwork.ch/health→ successfulcurl -X POST /v1/chatwith validX-API-Token→ success:true, content:OK
DONE: B1 Desktop Finalization (Code Patches, 2026-03-27)
All code patches for Variant B in the desktop app are verified and correct:
| Patch | Status | Detail |
|---|---|---|
ensure_ready() |
CORRECT | Remote backend counts as ready (no local OpenAI key needed) |
| OpenAI key setup dialog | CORRECT | Dialog suppressed when remote backend configured |
backend_url.txt |
CORRECT | Points to https://api.aza-medwork.ch |
backend_token.txt |
CORRECT | Primary local token source, prioritized by app |
start_all.bat guard |
CORRECT | Variant B protection: does not overwrite URL for live backend |
start_backend_autoport.bat guard |
CORRECT | Variant B protection: does not overwrite URL for live backend |
| Chat/text path | CORRECT | Routes through POST {backend_url}/v1/chat |
Token priority (confirmed): backend_token.txt > Env MEDWORK_API_TOKENS > Env MEDWORK_API_TOKEN
Backend URL priority (confirmed): Env MEDWORK_BACKEND_URL > backend_url.txt
Unchanged side paths (do NOT block main test):
translate.py,aza_email.py,apps/diktat/diktat_app.pystill have local OpenAI clients → side paths onlykongress2_window.pyhas hardcoded URL → congress feature is parked
DONE: B1-W5 Local Desktop Live Test (2026-03-27)
Desktop → Hetzner → OpenAI successfully tested in the real app.
backend_url.txtpoints tohttps://api.aza-medwork.chbackend_token.txtlocally present, accepted by live backendbasis14.pystarted locally and successfully tested against live backend- No local OpenAI key needed
- No OpenAI key dialog appears
- Chat/text path routes through Hetzner backend
Desktop-specific notes (for future chats):
ensure_ready()is correctly patched for remote backend- OpenAI key dialog suppressed when remote backend configured
- Do NOT use local starters for live test (
start_all.bat,RUN_AZA_ONECLICK.bat,START_AZA.bat,start_backend_autoport.bat) – they set env to localhost - Preferred start method:
python basis14.pydirectly
CURRENT MAIN BLOCK: License/Subscription Lifecycle (from 2026-03-27)
Goal: Reliably tie license status to subscription payment.
Scope:
- License active as long as subscription is paid
- Fall back to test mode / restricted mode when user cancels or stops paying
- Check app/license status reliably
- Do NOT break existing
/license/statusstructure - No premature auth/API contract changes
PARALLEL BLOCK: WooCommerce / Stripe Live Setup (from 2026-03-27)
Goal: Make payment flow production-ready including payout to bank account.
Scope:
- Complete WooCommerce/Stripe configuration
- Prepare live payment flow
- Ensure payouts go to user's bank account
- Validate test purchase / payment flow
CURRENT SUBSCRIPTION PRICES (2026-03-27)
| Plan | Monthly | Yearly |
|---|---|---|
| 1 User (Basic) | CHF 59 | CHF 590 |
| 2 Users (Team) | CHF 89 | CHF 890 |
Stripe Lookup Keys and Price IDs:
| Lookup Key | Price ID | Amount |
|---|---|---|
aza_basic_monthly |
price_1T53xHL5lREAW68VbuK43lmz |
CHF 59 |
aza_basic_yearly |
price_1T542BL5lREAW68VNLQGCKWZ |
CHF 590 |
aza_team_monthly |
price_1T544tL5lREAW68VkmnmZ21Q |
CHF 89 |
aza_team_yearly |
price_1T545RL5lREAW68VLbIh73AN |
CHF 890 |
OPEN REQUIRED DOCUMENTATION – Stripe Account:
- Active Stripe account (login / email): NOT YET DOCUMENTED
- Test mode or live mode currently active: NOT YET DOCUMENTED
- Bank account for payouts configured: NOT YET DOCUMENTED
- Webhook active and URL: NOT YET DOCUMENTED
- Leading success/cancel URLs: NOT YET DOCUMENTED These items must be documented before the first real customer purchase.
EXPLICITLY DEFERRED (Polish Phase)
- Autotext fix
- Window sizes adjustment
- Button sizes / layout for online presentation
- General UI polish
- Clean up direct OpenAI side paths in secondary modules (translate.py, aza_email.py, diktat_app.py)
Blocker Rule: One clear block at a time.
CUSTOMER JOURNEY ANALYSIS (2026-03-25)
| Step | Status | Detail |
|---|---|---|
| Purchase (WooCommerce) | IN PROGRESS (parallel) | Docs ready, 7 admin steps on Hostpoint |
| Download | PARTIAL | Mechanism exists, WooCommerce upload missing |
| Installer | FUNCTIONALLY COMPLETE | No code signing (SmartScreen warning) |
| Activation | BRIDGE IMPLEMENTED | AZA key sets full mode, trial dialog shows status |
| First Start | VARIANT B LIVE (2026-03-27) | Desktop → Hetzner → OpenAI works. No local OpenAI key needed. |
FOCUS BLOCKS
| Priority | Block | Description |
|---|---|---|
| DONE | B1-W1: Backend /v1/chat | Chat Proxy Endpoint – foundation of Variant B |
| DONE | B1-W2: Desktop migration | All OpenAI calls in basis14.py + mixin via backend |
| DONE | B1-W3: Hetzner Deploy | Backend LIVE on api.aza-medwork.ch |
| DONE | B1-W4: Server E2E | /health + /v1/chat successful, Variant B server-side proven |
| DONE | B1-W5: Desktop Live Test | basis14.py locally tested against live backend (2026-03-27) |
| CURRENT | License/Subscription Lifecycle | Tie license to subscription, fallback on cancellation |
| PARALLEL | WooCommerce / Stripe Live | Payment flow + payout to bank account |
| DEFERRED | UI Polish | Autotext fix, window sizes, buttons, layout |
| DEFERRED | FB-C: Signing | Signing readiness prepared |
| DEFERRED | FB-D: Update Comfort | Only after stable customer journey |
PRODUCT NAME – CURRENT DIRECTION
Current favorite: AZA Office
Preferred long form:
- AZA Office – Ihr medizinischer KI-Arbeitsplatz fuer die Praxis
Second good variant:
- AZA Office – Die KI-Assistenz fuer medizinische Dokumentation
Status: Current preferred naming direction. Not yet legally/brand-strategically finalized. Use for WooCommerce/website/download/Go-Live/product presentation.
Earlier shortlist (AZA Desktop): Documented in project_roadmap.json and project_plan.json. Superseded by AZA Office.
WORKING PRINCIPLES FOR NEXT CHATS
- Root-cause-first instead of blind patching
- One clear block at a time
- No 10 construction sites simultaneously
- Weight real installed builds higher than code assertions
- Don't declare "done" too early
- Distinguish Desktop into:
- Dev code
- Newly built installer
- Real behavior in installed build
COMMUNICATION NOTE (2026-03-27):
- In next chat do NOT re-explain server deploy from scratch – Hetzner is live
- Do NOT re-discuss Caddy/nginx/DNS/TLS – it is completed
- Do NOT re-debate Variant A/B – Variant B is mandatory and productive
- Do NOT re-prioritize shop/translate/naming
- Do NOT keep repeating rm/cleanup commands
- B1 desktop live test is DONE – do not reopen
- Go directly to: License/Subscription logic + WooCommerce/Stripe live setup
- Secure product/customer journey
- Weight real behavior higher than old assumptions
Current Local Workspace
CURRENT PATH (from 2026-03-27):
C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026
OLD PATH (HISTORICAL, do not use):
C:\Users\surov\Documents\AZA\backup 24.2.26
Key Commands
Preferred start method for live test against Hetzner backend:
cd "C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026"
python basis14.py
DO NOT use for live test (set env to localhost):
start_all.bat,RUN_AZA_ONECLICK.bat,START_AZA.bat,start_backend_autoport.bat
Local dev start (only for local development with own backend):
cd "C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026"
powershell -ExecutionPolicy Bypass -File .\deploy\local_reset_and_start.ps1
Tests:
cd "C:\Users\surov\Documents\AZA_GIT\aza\AzA march 2026"
powershell -ExecutionPolicy Bypass -File .\deploy\authorized_status.ps1
powershell -ExecutionPolicy Bypass -File .\deploy\smoke_suite.ps1
powershell -ExecutionPolicy Bypass -File .\deploy\docker_smoke.ps1
Desktop UX Block (2026-03-18)
- Benutzerdaten bei Deinstallation erhalten (Inno Setup, Standard: Nein)
- Signatur-UI: Haekchen "Profilname verwenden" + abweichender Signaturname in Einstellungen
- Rechtsklick-Haekchen im Minifenster (synchronisiert)
- Kommentare-Fenster (TEILWEISE: Grundstruktur, Detailmodus + Live-Update offen)
- Briefstil-Lernen (DOCX-Upload, Stilprofil-Analyse, Profilauswahl, Integration)
- Briefstil-Profile Fix: KISIM/Klinisch als feste Systemprofile, vereinheitlichtes Stilprofil-UI im Brief-Bereich
- Autotext Root-Cause-Fix (_is_admin NameError, Listener-Revert)
- Persistenz-Patch: Erststart-Consent, Profil+Code, Kommentare-Toggle, Einstellungs-Gruppierung
- Uebersetzer-Stabilitaetsfix: Toplevel-Embedded statt Tkinter-in-Thread
- Briefprofile: KISIM Bericht + Klinischer Bericht als vordefinierte Profile
AZA Clean Uninstall / Reset Tool
Per Doppelklick: AZA_Deinstallieren.bat
Per PowerShell:
powershell -ExecutionPolicy Bypass -File .\tools\aza_clean_uninstall.ps1
- Modus 1: Nur App entfernen, Benutzerdaten behalten
- Modus 2: Vollstaendig zuruecksetzen (App + Benutzerdaten)
- Kein Neustart noetig (Standardfall)
- Danach direkt Neuinstallation moeglich
Do-Not-Break Rules
- Do not change existing API response formats (especially /license/status).
- Do not modify auth/security mechanisms.
- Do not log or print secrets/tokens/keys.
- Signature fallback: profile name used when no explicit signature set.
- User data in %APPDATA%\AZA Desktop NOT deleted on uninstall by default.
- Style learning from old letters: only style/structure, NEVER copy patient data or old content.
Correction Patch (FIX-01) – 2026-03-19
- Translator label: "Fachuebersetzer" renamed to "Uebersetzer" everywhere
- Comments window: auto-open checkbox (persistent), live-update on KG change, clickable diagnosis headings with detail popups
- Correction window: scrollbar for saved corrections list
- Style profile live application: brief regenerated immediately on profile change/toggle
- "Profil anwenden" button in style profile management dialog
- KG creation writes directly to main window (no popup)
- Persistence: dokumente_collapsed now properly saved/loaded
- Main window centering: robust delayed centering after widget build via self.after()
FIX-02 Nachschaerfungs-Patch (2026-03-19)
- Style profile dialog: now management-only (status display, learn, delete). Active selection exclusively in brief window.
- Comments window: auto-opens after KG creation when checkbox is active (not just refreshes existing window).
- Logo separation: Wassertropfen (logo.ico) for EXE/desktop/installer/title bar icon. Original logo (logo.png) for internal branding (bottom-left).
Files: basis14.py, aza_text_windows_mixin.py, aza_desktop.spec, logo.png, logo.ico
FIX-09/10/11 Quellenstrenge Kommentarlogik – Inhaltsquelle / Originallink Trennung (2026-03-22)
Root Cause (FIX-09): LLM generated medication info purely from model knowledge. Root Cause (FIX-10): PharmaWiki regex wrong (used h2/h3 instead of span#subtitle). Compendium.ch is SPA (not scrapable). Root Cause (FIX-11): Content source and external link were mixed in one dropdown/dict.
Fix (FIX-11): Clean separation of content source and external link:
CONTENT SOURCE (what is shown in detail window):
_fetch_doccheck_info(med_name): DocCheck Flexikon (default) – server-rendered, h2/h3 headings_fetch_pharmawiki_info(med_name): PharmaWiki (fallback) – span#subtitle headings- User-selectable via "Inhaltsquelle:" dropdown, persistent in
med_content_quelle - New dict
_MED_CONTENT_QUELLEN(DocCheck, PharmaWiki) - Curated
_MEDICATION_FACTSas offline fallback
EXTERNAL LINK ("Originalquelle oeffnen" button):
- CH = Compendium, AT = BASG, DE = BfArM – UNCHANGED
- Still via
_MED_QUELLEN/medikament_quelle– NOT modified
Therapies/procedures: separate handling via DocCheck/PharmaWiki (unchanged). Candidate logic (Dermowarte→Dermovate etc.): untouched.
Files: basis14.py
ARCH-MED: Medication Source Architecture (2026-03-22)
Architecture (implemented):
- Detect medication in KG text (existing tagging + validation)
- Fetch content from DocCheck Flexikon (default) or PharmaWiki (user choice/fallback)
- Inject source text into LLM prompt (strict: only use provided data)
- Curated facts list as offline fallback
- External link always country-based (CH/AT/DE)
- Omit anything not from the source
Coverage: All medications available on DocCheck Flexikon + PharmaWiki.
Next steps:
- Caching strategy (cache fetched content locally)
- Robustness against HTML structure changes
- Long-term: Compendium API / HCI Solutions data license evaluation
Later market profiles (DE: BfArM, AT: BASG) separately.
Zukunftsblock – Internationalisierung / Laender- und Quellenprofile (GEPARKT)
Status: Zukunftsthema – NICHT fuer jetzt. Erst nach DACH-Stabilitaet und Produkterfolg.
Voraussetzung: DACH (CH/DE/AT) stabil, Go-Live gesichert, Produkt erfolgreich.
Zielbild:
- UI/Sprache, Medikamenten-/Diagnose-/Therapiequellen pro Markt anpassbar
- Profil-Felder: app_language, market_region, med_source_profile, dx_source_profile, therapy_source_profile
- Manueller Override durch Benutzer/Praxis
- Nicht hart nach Herkunftsland schalten – saubere Profil-Logik
- Handelsnamen/Zulassungen/Fachinfos sind laenderspezifisch → Quellenprofile pro Markt
Kein aktueller Implementierungsblock. Erst nach Go-Live und DACH-Erfolg relevant.
Windows Code-Signing / Smart App Control Readiness (2026-03-23)
Problem: Windows Smart App Control blockiert unsignierte Apps bei Kunden.
Status: Signing-Readiness vorbereitet, noch NICHT produktiv aktiviert.
Vorbereitet:
sign_release.ps1– signiert EXE + DLLs/PYDs + Installer (DryRun-Modus verfuegbar)build_and_test_release.ps1– Signing-Schritt integriert (optional, graceful skip)build_release_artifacts.ps1– Artefakt-Report mit Signatur-StatusSIGNING_READINESS.md– Vollstaendige Dokumentation
Signing-Reihenfolge: DLLs/PYDs → EXE → Installer-Build → Installer signieren → Artefakt-Report
Vor Kundenauslieferung noch offen:
- EV Code-Signing-Zertifikat beschaffen
- signtool.exe installieren (Windows SDK)
- Publisher-Name abstimmen (Zertifikat ↔ AppPublisher ↔ Handelsregister)
- Produktiver Signierlauf + Test auf Windows-PC mit Smart App Control
Publisher-/Namenskonsistenz (Analyse 2026-03-23):
3 Namensformen im Projekt – beabsichtigt, keine Inkonsistenz:
- AZA MedWork = Firma/Publisher → SIGNING-KRITISCH (Installer, Legal, Consent, E-Mail-Absender)
- AZA Desktop = Produktname → konsistent, kein Handlungsbedarf
- AZA Medical AI Assistant = interner Projektname → nicht signing-relevant
Vor Zertifikatskauf: HR-Name pruefen, AppPublisher auf Zertifikats-Subject abstimmen. Falls HR-Name abweicht: alle signing-relevanten Stellen gemeinsam anpassen (Liste in handover.md). Nach Festlegung: Publisher-Name NICHT mehr wechseln (SmartScreen-Reputation).