Files
aza/AzA march 2026 - Kopie (6)/project_handover.md
2026-04-16 13:32:32 +02:00

23 KiB
Raw Permalink Blame History

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.py locally tested against live backend successfully

Hetzner Backend is LIVE (2026-03-26)

  • Production API path: https://api.aza-medwork.ch
  • DNS: api.aza-medwork.ch178.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 → successful
  • curl -X POST https://api.aza-medwork.ch/v1/chat with valid X-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:

  1. Wrong git remote (naswinterthur) was unsuitable
  2. Uploading only deploy/ was wrong docker-compose.yml expects repo root context (context: .., dockerfile: deploy/Dockerfile)
  3. Host nginx blocked port 80
  4. Caddy DNS/ACME issues due to resolver 127.0.0.53 → Fix: explicit DNS 1.1.1.1 + 8.8.8.8 in caddy service

Final Hetzner State:

  • Services: aza-api + aza-caddy
  • Compose runs from /root/aza-app/deploy
  • .env is production-relevant there
  • AZA_DOMAIN=api.aza-medwork.ch
  • ACME_EMAIL=info@aza-medwork.ch
  • MEDWORK_API_TOKENS is set productively
  • OPENAI_API_KEY is 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)

  1. Backend Chat Proxy POST /v1/chat (Week 1) Foundation of architecture
  2. Desktop app migration to backend (Week 2) Migrate all OpenAI calls
  3. Hetzner Deploy (Week 3) Set up production server
  4. E2E Test + Go-Live backend path (Week 4) Customer journey without OpenAI key
  5. 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:

  1. call_chat_completion() in basis14.py central wrapper -> _backend_chat_completion()
  2. News search direct self.client call -> _backend_chat_completion()
  3. Comments own OpenAI() client -> _backend_chat_completion()
  4. Med detail short info own OpenAI() client -> _backend_chat_completion()
  5. 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.ch178.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 → successful
  • curl -X POST /v1/chat with valid X-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.py still have local OpenAI clients → side paths only
  • kongress2_window.py has 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.txt points to https://api.aza-medwork.ch
  • backend_token.txt locally present, accepted by live backend
  • basis14.py started 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.py directly

CURRENT MAIN BLOCK: License/Subscription Lifecycle (from 2026-03-27)

Goal: Reliably tie license status to subscription payment.

Scope:

  1. License active as long as subscription is paid
  2. Fall back to test mode / restricted mode when user cancels or stops paying
  3. Check app/license status reliably
  4. Do NOT break existing /license/status structure
  5. 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:

  1. Complete WooCommerce/Stripe configuration
  2. Prepare live payment flow
  3. Ensure payouts go to user's bank account
  4. 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.

DONE: Stripe Live Webhook Stabilized (2026-03-27)

Status: Webhook returns 200 OK. License entry written to SQLite.

Solved problems:

  1. StripeObject vs dict: Code used .get(...) on raw Stripe objects → AttributeError: get. Fix: parse event as json.loads(body) after signature verification.
  2. .to_dict_recursive() does not exist in the installed stripe-python version. Never use it.
  3. Double prefix 404: main.py mounts router with prefix="/stripe". Using @router.post("/stripe/webhook") creates /stripe/stripe/webhook. Correct: @router.post("/webhook").
  4. Email lookup: customer_email is often null. Cascade: session.customer_emailsession.customer_details.emailstripe.Customer.retrieve(id).
  5. Container env trap: .env was correct but container had stale values. docker compose restart is not enough. Correct: docker compose up -d --build --force-recreate aza-api.

NEVER AGAIN Stripe Webhook Rules:

  • Never use .to_dict_recursive() does not exist in all stripe-python versions
  • Never use .get(...) on raw StripeObjects
  • After signature verification, always parse as plain JSON: event = json.loads(body)
  • For Subscription.retrieve() / Customer.retrieve(): use json.loads(str(obj))
  • Always consider prefix routing in FastAPI: with prefix="/stripe", decorator must be @router.post("/webhook")
  • For Stripe .env changes: docker compose up -d --build --force-recreate aza-api, not just restart

Operational reference Hetzner:

  • Working backup: /root/aza-app/stripe_routes.py.working_backup
  • SQLite DB: /root/aza-app/data/stripe_webhook.sqlite
  • Logs: docker logs -f --tail 20 aza-api

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)
DONE Stripe Live Webhook Webhook 200 OK, license entry in DB, StripeObject/routing/env traps solved (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:
    1. Dev code
    2. Newly built installer
    3. 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

  1. Do not change existing API response formats (especially /license/status).
  2. Do not modify auth/security mechanisms.
  3. Do not log or print secrets/tokens/keys.
  4. Signature fallback: profile name used when no explicit signature set.
  5. User data in %APPDATA%\AZA Desktop NOT deleted on uninstall by default.
  6. Style learning from old letters: only style/structure, NEVER copy patient data or old content.

Correction Patch (FIX-01) 2026-03-19

  1. Translator label: "Fachuebersetzer" renamed to "Uebersetzer" everywhere
  2. Comments window: auto-open checkbox (persistent), live-update on KG change, clickable diagnosis headings with detail popups
  3. Correction window: scrollbar for saved corrections list
  4. Style profile live application: brief regenerated immediately on profile change/toggle
  5. "Profil anwenden" button in style profile management dialog
  6. KG creation writes directly to main window (no popup)
  7. Persistence: dokumente_collapsed now properly saved/loaded
  8. Main window centering: robust delayed centering after widget build via self.after()

FIX-02 Nachschaerfungs-Patch (2026-03-19)

  1. Style profile dialog: now management-only (status display, learn, delete). Active selection exclusively in brief window.
  2. Comments window: auto-opens after KG creation when checkbox is active (not just refreshes existing window).
  3. 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

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_FACTS as 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):

  1. Detect medication in KG text (existing tagging + validation)
  2. Fetch content from DocCheck Flexikon (default) or PharmaWiki (user choice/fallback)
  3. Inject source text into LLM prompt (strict: only use provided data)
  4. Curated facts list as offline fallback
  5. External link always country-based (CH/AT/DE)
  6. Omit anything not from the source

Coverage: All medications available on DocCheck Flexikon + PharmaWiki.

Next steps:

  1. Caching strategy (cache fetched content locally)
  2. Robustness against HTML structure changes
  3. 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-Status
  • SIGNING_READINESS.md Vollstaendige Dokumentation

Signing-Reihenfolge: DLLs/PYDs → EXE → Installer-Build → Installer signieren → Artefakt-Report

Vor Kundenauslieferung noch offen:

  1. EV Code-Signing-Zertifikat beschaffen
  2. signtool.exe installieren (Windows SDK)
  3. Publisher-Name abstimmen (Zertifikat ↔ AppPublisher ↔ Handelsregister)
  4. 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).