Lokale Flask-Web-App zur Aufnahme, Transkription (whisper.cpp) und Zusammenfassung (LM Studio/Ollama).
Siehe .env.example und requirements.txt. Secrets/Transkripte werden per .gitignore ausgeschlossen.
- Überblick
- Systemvoraussetzungen
- Projektstruktur
- Konfiguration
- Schnellstart (nur HTTP, Test)
- Produktionsbetrieb mit HTTPS
- App als Dienst starten
- Zertifikate (lokal)
- Troubleshooting
- Sicherheitshinweise
- Backend: Flask (Python). Standardmäßig läuft die App lokal unter
http://127.0.0.1:5001. - Frontend: Greift per Browser oder optionaler PyInstaller‑Client zu.
- KI‑Module:
- Whisper / whisper.cpp: lokale Transkription (benötigt Modelldatei).
- Ollama (optional): lokale LLM‑Generierung für Zusammenfassungen (Standard:
http://127.0.0.1:11434).
- Empfohlen: Betrieb hinter einem Reverse‑Proxy (Nginx oder Caddy) mit HTTPS auf Port 443 im LAN.
Wichtig: Die App ist für lokale Nutzung vorgesehen. Kein öffentliches Expose ins Internet, außer du härtest das Setup separat ab.
- Python 3.10+ (empfohlen 3.11)
- pip & venv
- Git (für Clone)
- Reverse‑Proxy (eine Option):
- macOS: Homebrew + Nginx (oder Caddy)
- Windows: Caddy (empfohlen) oder Nginx für Windows
- KI‑Komponenten (optional):
- Whisper Modelldatei (
.binfür whisper.cpp) – Pfad konfigurierbar - Ollama lokal installiert & gestartet, falls LLM‑Features genutzt werden
- Whisper Modelldatei (
web_app/
├─ app.py # Flask Einstiegspunkt
├─ requirements.txt # Python-Abhängigkeiten
├─ settings.json # App-Konfiguration (Pfade, Ports, Modelle)
├─ static/, templates/ # Frontend
├─ utils.py, ... # Hilfsfunktionen (Whisper/Ollama)
└─ certs/ # (optional) Zertifikate, wenn lokal abgelegt
Minimalbeispiel:
{
"host": "127.0.0.1",
"port": 5001,
"ollama_url": "http://127.0.0.1:11434",
"whisper_model_path": "./whisper.cpp/models/ggml-medium.bin",
"upload_dir": "./uploads",
"secret_key": "<ersetzen-oder-über-env>"
}Hinweis: Vermeide absolute macOS‑Pfade wie
/Users/<name>/...– nutze relative Pfade oder ENV‑Variablen.
Lege eine .env an (wenn du mit python-dotenv arbeitest) oder setze ENV im Startskript:
FLASK_ENV=production
FLASK_SECRET_KEY=<zufällig>
OLLAMA_URL=http://127.0.0.1:11434
In app.py nach app = Flask(__name__):
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)Dadurch generiert Flask korrekte HTTPS‑URLs hinter dem Reverse‑Proxy.
# 1) Repository klonen
git clone <REPO_URL>
cd web_app
# 2) Python‑Umgebung
python3 -m venv .venv
source ./.venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# 3) Start im Entwicklungsmodus (ohne TLS)
python app.py
# → http://127.0.0.1:5001Für den LAN‑Zugriff ohne HTTPS würdest du
host='0.0.0.0'setzen und Port (z. B. 5001) im Router/Firewall nur im LAN öffnen. Für echte Nutzung wird HTTPS empfohlen.
Einfacher, aber der Proxy bindet auf Port 443 → benötigt Admin‑Rechte.
- Homebrew & Nginx (nur falls nicht vorhanden)
brew install nginx
- Zertifikate: siehe Abschnitt Zertifikate (oder vorhandene nutzen).
- Minimal‑Konfig (nur deine Site laden):
$(brew --prefix)/etc/nginx/nginx.confworker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; include servers/*.conf; error_log /Users/<USER>/Library/Logs/nginx/error.log; access_log /Users/<USER>/Library/Logs/nginx/access.log; }
- Site‑Datei
servers/arztapp.confserver { listen 192.168.105.136:443 ssl; # oder 0.0.0.0:443 fürs gesamte LAN http2 on; server_name 192.168.105.136; ssl_certificate /Pfad/zum/cert.pem; ssl_certificate_key /Pfad/zum/key.pem; client_max_body_size 200M; location / { proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
- Starten & Autostart
mkdir -p ~/Library/Logs/nginx && : > ~/Library/Logs/nginx/{access,error}.log "$(brew --prefix)"/bin/nginx -t && sudo "$(brew --prefix)"/bin/nginx # Autostart (Root) optional via LaunchDaemon oder: brew services run nginx (bindet aber i. d. R. nicht 443)
Empfohlen, wenn kein Root‑Dienst laufen soll.
-
Nginx auf 8443 (User‑Dienst)
# servers/arztapp.conf server { listen 127.0.0.1:8443 ssl; http2 on; server_name 192.168.105.136; ssl_certificate /Pfad/zum/cert.pem; ssl_certificate_key /Pfad/zum/key.pem; client_max_body_size 200M; location / { proxy_pass http://127.0.0.1:5001; proxy_http_version 1.1; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
- Logs & Temp‑Dirs in den Benutzerordner legen:
# in nginx.conf (http{}) error_log /Users/<USER>/Library/Logs/nginx/error.log; access_log /Users/<USER>/Library/Logs/nginx/access.log; client_body_temp_path /Users/<USER>/Library/Caches/nginx/client_temp; proxy_temp_path /Users/<USER>/Library/Caches/nginx/proxy_temp; fastcgi_temp_path /Users/<USER>/Library/Caches/nginx/fastcgi_temp; uwsgi_temp_path /Users/<USER>/Library/Caches/nginx/uwsgi_temp; scgi_temp_path /Users/<USER>/Library/Caches/nginx/scgi_temp; proxy_request_buffering off; client_max_body_size 200M;
- Start/Autostart:
brew services start nginx # startet beim User‑Login automatisch
- Logs & Temp‑Dirs in den Benutzerordner legen:
-
Portumleitung 443→8443 mit
pf- Anchor:
/etc/pf.anchors/arztapprdr pass on lo0 inet proto tcp from any to 192.168.105.136 port 443 -> 127.0.0.1 port 8443 rdr pass on en0 inet proto tcp from any to 192.168.105.136 port 443 -> 127.0.0.1 port 8443en0ggf. durch dein physisches Interface ersetzen. - In
/etc/pf.confim translation‑Block einhängen:rdr-anchor "arztapp" load anchor "arztapp" from "/etc/pf.anchors/arztapp" - Laden & prüfen:
sudo pfctl -nf /etc/pf.conf && sudo pfctl -f /etc/pf.conf && sudo pfctl -e sudo pfctl -a arztapp -sn
- Anchor:
-
Test
curl -vk https://192.168.105.136/
Caddy ist hier am einfachsten (ein Binary, moderne Defaults). Alternativ: Nginx für Windows.
Variante Caddy
- Caddy herunterladen & ins Projekt (oder nach
C:\caddy\), dannCaddyfileanlegen:https://192.168.105.136 { tls { # Selbstsigniertes internes Zertifikat – für LAN ausreichend internal } encode zstd gzip header Strict-Transport-Security "max-age=31536000" reverse_proxy 127.0.0.1:5001 }Alternativ kannst du eigene Zertifikate verwenden:
tls C:\pfad\zu\cert.pem C:\pfad\zu\key.pem - Starten:
caddy run --config Caddyfile --adapter caddyfile
- Autostart als Windows‑Dienst:
caddy.exebietetinstall/start(siehe Caddy‑Doku) oder den Aufgabenplaner verwenden.
Variante Nginx (Windows)
nginx.confanalog zur macOS‑Variante A:listen 0.0.0.0:443 ssl;, Zertifikate referenzieren,proxy_pass http://127.0.0.1:5001;- Start als Dienst z. B. via NSSM.
~/Library/LaunchAgents/com.app.local.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.app.local</string>
<key>ProgramArguments</key><array>
<string>/usr/bin/python3</string>
<string>/Users/<USER>/web_app/app.py</string>
</array>
<key>WorkingDirectory</key><string>/Users/<USER>/web_app</string>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>EnvironmentVariables</key><dict>
<key>FLASK_ENV</key><string>production</string>
</dict>
</dict></plist>launchctl load ~/Library/LaunchAgents/com.app.local.plist- Taskplaner: Aufgabe „Beim Anmelden“ →
python.exe C:\Pfad\web_app\app.py - NSSM:
nssm install ArztApp→ Pfad zupython.exeundapp.py, „Automatic (Delayed Start)“.
- Self‑Signed (OpenSSL) – IP‑SAN eintragen:
# macOS Beispiel cat > ip.cnf <<'EOF' [ req ] default_bits = 2048 prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [ dn ] C=DE ST=Local L=Local O=Local LAN CN=192.168.105.136 [ req_ext ] subjectAltName = @alt_names [ alt_names ] IP.1 = 192.168.105.136 EOF openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout 192.168.105.136.key -out 192.168.105.136.crt -config ip.cnf
- Vertrauen: Zertifikat im System‑Schlüsselbund (macOS) oder Zertifikatsspeicher (Windows) als „Vertrauenswürdig“ importieren, um Browser‑Warnungen zu vermeiden.
https://IPlädt nicht, aberhttp://127.0.0.1:5001geht- Reverse‑Proxy läuft?
lsof -nP -iTCP:443 -sTCP:LISTEN(bzw. 8443) und Logs prüfen - Zertifikate lesbar? (Key 600, Cert 644)
- Firewall lässt 443 im LAN zu?
- macOS Variante B:
pfctl -a arztapp -snzeigt beide Regeln (lo0+en0)?
- Reverse‑Proxy läuft?
connect() failed (61: Connection refused) while connecting to upstream- Flask nicht gestartet oder Port falsch →
curl -v http://127.0.0.1:5001/
- Flask nicht gestartet oder Port falsch →
Permission deniedaufclient_body_temp- Nginx‑Temp‑Pfade in Benutzerverzeichnis legen (siehe macOS Variante B, Schritt 1)
- Zugriff aus dem LAN klappt, lokal nicht (oder umgekehrt)
- Prüfe, ob pf‑Regeln sowohl für
lo0(lokal) als auchen0(LAN) gesetzt sind
- Prüfe, ob pf‑Regeln sowohl für
- Ollama/Whisper Fehlermeldungen
- Stimmt
ollama_url? Läuft der Dienst? - Existiert
whisper_model_pathtatsächlich? (Dateipfad korrekt, Datei vorhanden)
- Stimmt
- Standard‑
secret_keyniemals im Repo belassen – per ENV setzen. - Das Setup ist für LAN konzipiert. Für Internet‑Expose (Port‑Forwarding) sind zusätzlich Hardening‑Schritte nötig (Rate‑Limit, Auth, CSRF, CSP, Logging, Updates, …).
- Zertifikate regelmäßig erneuern (auch Self‑Signed).
Viel Erfolg!
Hallo, dies ist mein erstes GitHub-Projekt. Ja, sogar mein erstes Projekt überhaupt. Man möge mir meine Fehler und Fauxpas verzeihen. Ich hoffe auf konstruktive Beiträge der Community und hoffe, dieses Projekt fortführen und verbessern zu können. Worum geht es? Es gibt schon einige Anbieter, die ein Arzt-Patienten-Gespräch aufzeichnen, transkribieren und zusammenfassen. Hier einige Anbieter: Noa Notes Eudaria hedihealth Via-health PlaynVoice (eher diktieren als KI) Doq Copilot cgmone DokuAssistent Jedoch stellt meiner Meinung nach die Cloudanbindung dieser Programme im medizinischen Bereich ein datenschutztechnisches NoGo dar. Meine Idee war es, diesen Prozess lokal laufen zu lassen. Hierfür habe ich auf einem Mac Mini mit M4 Pro Chip mit 14 Core CPU, 20 Core GPU, 16 Core Neutral Engine, 64 GB RAM und 1 TB SSD Ollama laufen. Als LLM nutze ich Mistral 7b, da dies schnell läuft und eine Apache-Lizenz hat. Im Backend läuft dieses Python-Programm. Ich greife im Netzwerk über das Frontend zu. Anfangs hatte ich noch mit einer gesonderten Sprechererkennung, erst mit Pyannote (Audio basiert) und dann über LLM versucht. Letztendlich habe ich es aber in ein Prompt zusammengefasst. Im Admin-Bereich sieht man noch weitere Informationen wie Verarbeitungszeit und Transkript. Auf der Hauptseite habe ich zwischen klassischen Mikroaufnahme und anschließendem Transkript und Livetranskript experimentiert. Bei meinen Versuchen stellt sich jedoch heraus, dass für meinen Anwendungsfall die Transkription bei der klassischen Aufnahme immer noch zu lange dauert. Zu guter Letzt will ich offen zugeben, dass meine Python-Kenntnisse nur marginal sind und ich größtenteils mit Hilfe von ChatGPT mühsam alles erarbeitet habe. Hier noch ein paar Screenshots.
v2025_9_2.7
Folgende Änderungen:
-
Die Aufnahme mit Übertragung der .wav Datei nach Beendigung der Aufnahme zum Transkript und anschließende Übergabe an die KI zum zusammenfassen hat einfach zu lange gedauert sodass ich mich ab jetzt nur noch auf den Live Transkript konzentriere. Während der Aufnahme werden Chunks nach Heuristik erzeugt mit einer gewissen Überlappung so dass während der Aufnahme der Transkript erfolgt. Die Qualität des Transkript ist zwar minimal schlechter aber die gesamte Verarbeitungszeit ist deutlich schneller.
-
Der Transkript läuft über whisper.cpp mit ggml mit CoreML Unterstützung. Der beste Kompromiss zwischen Geschwindigkeit und Qualität ist bei mir ggml-medium.bin. Auch die quantisierten Modelle q5 und q8 sind gut.
-
Es gibt eine Live Simulation aus einer .wav Datei. Hierbei wird nicht einfach die .wav Datei hochgeladen (Wie bei Datei Upload ganz unten) sondern die Aufnahme mit Live Transkript simuliert.
-
Zur Verbesserung der Aufnahme nutze ich folgendes Tischmikrofon mit 4 Microfonen: beyerdynamic Space
Themen in Arbeit:
-
Starten der Aufnahme über Gerätestart im PVS (T2med)
-
1 Klick Übernahme von Anamnese, Befund und Therapie mit automatischem Speichern der Änderungen in das PVS
-
Benutzerstruktur aufbauen sodass mehrere Nutzer parallel arbeiten können.
v2025.10.1
Änderungen:
- 1 Klick Übernahme von Anamnese, Befund und Therapie o Die Umsetzung der Übernahme von Anamnese, Befund und Therapie in T2med gestaltete sich doch etwas komplizierter als gedacht. Leider ist es aus sicherheitstechnischen Gründen nicht möglich aus einem Browser heraus eine andere App zu steuern. Also musste ich eine Client App entwickeln. Diese App zu bauen hat fast genauso lang gedauert wie die erste Version der Server App. Die App steht bei Github unter AuricaClient App zum Download. Der Zugriff auf Aurica kann aber weiterhin auch über den Browser über die IP Adresse des Servers erfolgen. Dann funktioniert aber der Button natürlich nicht und man muss den Text mit dem Button „in Zwischenablage kopieren“ und einfügen wie bisher. o Die AuricaClient App funktioniert auf Windows und MacOs und wird per Pyinstaller installiert. Siehe die Installationsanleitung auf Github. Durch klick auf den Button „Nach T2med einfügen“ wird der Text in dem obrigen Textfeld entsprechend unter Anamnese, Befund oder Therapie nach T2med mit Tastatureingabefolge automatisiert übernommen. (Copy Text, Fokus auf T2med, a, Enter, Paste, Enter). Zwischen den Tastatureingaben wird ein delay gesetzt. Diesen habe ich an meine ClientPC Performance angepasst und könnte bei unterschiedlicher Ausstattung auch geändert werden. Dies müsste unter client_app.py vor dem build der .exe bzw app Datei geändert werden. Ich hoffe diese Tastatureingabe stellt keine Verletzung der Nutzungsbedingungen von T2med dar. Nochmal ein zwinkern an T2med. Eine API wäre schön da die Eingabe über Tastaturfolge Fehleranfällig ist.
- Fuzzy Match mit Erstellung einer medical_terms_de.txt o Was ist Fuzzy Matching? Du hast eine Liste medizinischer Fachbegriffe. Für jedes Wort im Transkript suchen wir den ähnlichsten Begriff aus dieser Liste. Gibt die Ähnlichkeit (Score 0–1) ≥ cutoff (z. B. 0,90), ersetzen wir das Wort durch den Fachbegriff (inkl. passender Groß-/Kleinschreibung). So wird z. B. „Rhinorhoe“ → „Rhinorrhö“ oder „krankschreibung“ → „Krankmeldung“.
- Löschen Button auf result.html Seite
- Auf der Ergebnis Seite werden nun der Dateiname mit Datum und Uhrzeit gezeigt
- Versionierung geändert.
Themen in Arbeit:
- Der geänderte Text sollte bei Button klick automatisch gespeichert werden. Ich habe überlegt eine originale Textversion sowie geänderte Textversion zu erstellen. Damit könnte man eine Korrektur nachvollziehen und eventuell mit dieser Änderung die KI „tunen“. (Ich merke ich habe wieder zu viele Ideen im Kopf)
- Starten der Aufnahme über Gerätestart. Ich bin noch nicht dazu gekommen die GDT-Spezifikationen und das GDT Mapping anzuschauen.
- Benutzerstruktur aufbauen, sodass mehrere Nutzer parallel arbeiten können.
- Automatische Löschung der Audiodateien nach einer gewissen Zeit damit der Server nicht vollläuft.
- Verbesserung Qualität der Zusammenfassung. Ich hatte mal probeweise auf gpt-oss 20b anstatt mistral 7b umgestellt. Leider ist die Verarbeitungszeit bei besserer Qualität zu lang. Ich denke hier muss ich noch am Prompt arbeiten. Hinsichtlich der Transkription mit ggml-medium.bin bin ich zufrieden, aber ich würde gerne wieder die Sprecherdiarisierung versuchen da ich mir dadurch auch eine bessere Qualität erhoffe.