Jak zbudowaliśmy pipeline OCR dla dokumentacji medycznej — i co się wysypało po drodze

Sebastian Magnes · Fractional CTO ·

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.

Izolacja danych między placówkami działa przez Row Level Security w Supabase. Każda placówka widzi tylko swoje skany — nie ma wspólnej puli danych między klientami. To wymaganie, nie feature.

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.

Model nie może twierdzić że odczytał wartość HGB = 10.2 z 60% pewnością i zachowywać się jak przy 99%. To jest właściwe podejście dla kontekstu medycznego — system musi wiedzieć, czego nie wie.

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.

Więcej z klastra OCR · MedTech

← Wszystkie artykułyAI w medycynie i problem z chmurą →

Budujesz system OCR lub AI dla medycyny?

Napisz kilka zdań o projekcie. Mogę pomóc ocenić architekturę, kwestie izolacji danych i realną drogę do wdrożenia zgodnego z wymaganiami placówki.

Porozmawiaj o wsparciu CTO