Lekarz fotografuje wynik morfologii. Aplikacja zwraca JSON z wartościami, nazwą badania i alertem klinicznym. Brzmi jak weekend hackathon.
Nie był to weekend hackathon.
Problem który próbowaliśmy rozwiązać
Dokumentacja medyczna w Polsce to papier. Skierowania, karty ze szpitala, wyniki laboratoryjne — lekarze dostają stosy skanów PDF albo zdjęć telefonem. Żaden z tych formatów nie jest przeszukiwalny, porównywalny ani możliwy do analizy przez model AI.
Cel był prosty do opisania: zdjęcie lub PDF dokumentu medycznego wchodzi, ustrukturyzowany JSON wychodzi. Czas odpowiedzi kilka sekund. Bez ręcznego przepisywania.
Nieprzypadkowo mało kto to robi dobrze.
Wybór modelu — pierwsza pułapka
Zaczęliśmy od Surya. Dobry model OCR open-source, skupiony na dokumentach wielojęzycznych, działał lokalnie, wyniki na testach były obiecujące.
Problem pojawił się przy migracji na produkcję. Surya 0.22 wprowadził breaking changes w sposobie integracji z backendem. Architektura OCR którą mieliśmy musiała zostać przebudowana — nie w iteracji, tylko w jeden wieczór, żeby nie blokować reszty prac.
Migrowaliśmy na llama.cpp b5600 jako wrapper dla modelu multimodal. Każdy model cache'owany na dysku. llama-server z flagą -ngl 0 bo serwer nie ma GPU — całość działa na CPU.
To jest decyzja, której nie cofniemy: CPU inference dla modelu wizyjnego jest wolne. Czas odpowiedzi 2–8 sekund to przy obecnej skali akceptowalne. Przy 10x większym ruchu — stanie się problemem. Wiemy o tym i to jest świadomy kompromis, nie niedopatrzenie.
Infrastruktura produkcyjna
Stack który wybraliśmy:
- Panel użytkownika: Next.js 15 na Vercel
- API backend: FastAPI, nginx + SSL
- Model OCR: Surya przez llama.cpp, modele cache'owane na dysku
- Baza danych: Supabase PostgreSQL, region eu-central-1, Session Pooler
- Cache: Redis 7, rate limiting i kolejkowanie requestów
- Serwer: GCP Frankfurt, e2-standard-2 (2 vCPU, 8 GB RAM, 50 GB dysk)
GCP Frankfurt — nie przypadkowo. Dane medyczne, RODO, przetrzymywanie w UE. Nie ma opcji żeby wyniki badań pacjenta trafiały do regionu poza Europą. To nie jest decyzja techniczna, to decyzja prawna która determinuje gdzie w ogóle można postawić serwer.
Co naprawdę oznacza 99,52% accuracy
Ta liczba pojawia się w rozmowach z potencjalnymi klientami i zawsze wymaga wyjaśnienia.
99,52% to accuracy na poziomie rozpoznawania tekstu. Model poprawnie odczytuje litery i cyfry w 99,52% przypadków na reprezentatywnym zbiorze testowym. To nie znaczy że system działa w 99,52% przypadków klinicznie.
Karta ze szpitala napisana ręcznie przez lekarza ze złym charakterem pisma — accuracy spada do 60–70%. Zdjęcie zrobione przy słabym oświetleniu z telefonu — podobnie. PDF z trzeciej generacji ksero — gorzej.
Dlatego w odpowiedzi API mamy pole confidence z osobnymi wartościami dla tekstu i wartości liczbowych. System sam sygnalizuje kiedy nie jest pewny. Lekarz decyduje czy zaufać wynikowi czy zweryfikować ręcznie.
Integracja przez REST API — celowa decyzja
Mogliśmy zbudować zamknięty system z własnym interfejsem i podpiąć pod niego wszystko. Zamiast tego postawiliśmy na standardowe REST API — ten sam protokół którego używają systemy HIS, EMR i aplikacje mobilne.
Jeżeli system potrafi wysłać żądanie HTTP, integracja z IKZOCR zajmuje godziny, nie tygodnie. To był świadomy wybór projektowy — budujemy infrastrukturę, nie kolejny zamknięty serwis którego nikt nie wdroży.
Webhook jako opcja zamiast pollingu. Podajesz adres, system sam wyśle wynik kiedy skan jest gotowy. Przy integracji z aplikacją mobilną wystarczy jeden nagłówek HTTP — wynik trafia automatycznie do konkretnego pacjenta.
Czego nauczyliśmy się po drodze
OCR dla dokumentów medycznych to nie problem techniczny. To problem zarządzania niepewnością w środowisku gdzie błąd ma konsekwencje kliniczne.
Najtrudniejsza część nie jest po stronie modelu. Jest po stronie infrastruktury, izolacji danych i odpowiedzi na pytanie: co system robi kiedy nie jest pewny? Czy komunikuje to użytkownikowi, czy ukrywa?
Drugi wniosek: breaking changes w open-source modelach AI przychodzą bez ostrzeżenia i mogą wymagać przebudowy całej warstwy integracji w ciągu jednego dnia. Nie ma na to dobrego zabezpieczenia poza tym żeby architektura była modułowa i każda warstwa wymienialna niezależnie od reszty.