10 KiB
План архитектуры obdai.ru/receipt
1. Цель
Создать независимый Python API-сервис для распознавания рукописных рецептов. API принимает изображение рецепта или фрагменты с названиями лекарств и дозировками, а возвращает структурированный текст для отображения в Android-приложении.
ФИО пациента, дата рождения, адрес и другие идентификаторы не должны извлекаться, сохраняться или возвращаться.
2. Размещение
Android
-> HTTPS
nginx: obdai.ru/receipt/
-> 127.0.0.1:8768
receipt.service
-> FastAPI/Gunicorn
-> OCR/vision-модель
-> нормализация по справочнику лекарств
-> JSON
elmer остаётся полностью независимым:
- код:
/opt/receipt/; - virtualenv:
/opt/receipt/venv/; - пользователь systemd: отдельный
receipt; - порт:
127.0.0.1:8768; - юнит:
/etc/systemd/system/receipt.service; - отдельные логи и секреты;
- отдельный deploy/restart;
- отсутствие общих импортов, virtualenv и файлов с
elmer.
3. API
Основной endpoint: POST /receipt/recognize.
Формат: multipart/form-data.
Поля:
image— JPEG, PNG или WEBP, до 10 MB;request_id— UUID клиента;masked_zones— координаты зон, которые нужно закрыть до OCR;- при необходимости отдельный тип запроса для уже выделенных фрагментов.
Ответ должен содержать:
request_id;- дату рецепта, если она распознана;
- список лекарств;
- исходный распознанный фрагмент без PII;
- bounding box;
- дозировку и режим приёма как написано;
confidence;uncertain;- кандидатов из справочника при сомнительном совпадении.
Ошибки: 400, 401, 413, 422, 429, 503.
4. Pipeline
- Проверить API-ключ, размер, magic bytes и разрешение изображения.
- Применить маскирование зон до передачи изображения модели.
- Выполнить масштабирование и deskew.
- Получить OCR-текст и bounding boxes.
- Выделить роли: лекарство, дозировка, режим, дата, прочее.
- Удалить PII из результата.
- Сопоставить название со справочником через нормализацию и
rapidfuzz. - При близких или слабых совпадениях установить
uncertain=true. - Валидировать ответ Pydantic.
- Вернуть JSON без записи изображения и медицинского текста на диск.
5. Модель и OCR
Сначала проверить, поддерживает ли используемый endpoint gpt-oss-120b изображения. Размер 120B сам по себе не означает наличие vision-возможностей.
- Если модель vision-capable: изображение -> модель -> строгий JSON.
- Если text-only: Tesseract 5 или облачный OCR с русским языком и bounding boxes ->
gpt-oss-120bдля структурного разбора -> справочник. - Финальное название лекарства нельзя принимать только от LLM: источник истины — справочник и fuzzy matching.
- Дозировка возвращается как распознанный текст, без медицинской интерпретации.
6. Справочник лекарств
Для MVP использовать локальную read-only SQLite БД.
Минимальные сущности:
- препарат;
- МНН;
- торговое название;
- форма выпуска;
- дозировка;
- производитель;
- синонимы и распространённые варианты написания;
- поисковый индекс;
- пары опасно похожих названий.
Возможный базовый источник — ГРЛС. Перед использованием необходимо проверить формат, условия использования, полноту данных и порядок обновления. Коммерческие справочники требуют отдельной лицензии.
Рекомендуемая логика:
- высокий score: совпадение;
- средний score:
uncertain=trueи список кандидатов; - низкий score: совпадение не подтверждать;
- при близости двух похожих препаратов принудительно требовать подтверждение пользователя.
7. Персональные данные
- Android маскирует ФИО/ДР до отправки.
- Сервер повторно применяет
masked_zonesдо OCR. - В response schema отсутствуют поля пациента.
- Изображения и raw OCR не записываются на диск.
- В логах только
request_id, статус, размер, latency и количество сомнительных позиций. - TLS обеспечивается nginx.
- API требует Bearer-ключ.
- Должны быть TTL, rate limiting и аудит без медицинского содержимого.
8. Надёжность
- Жёсткие timeout для nginx, Gunicorn, HTTP-клиента модели и SQLite.
- На MVP синхронная обработка без Redis/Celery.
- При недоступности модели возвращать
503. - Ограничить размер запроса и частоту запросов.
- Health endpoint не должен требовать API-ключ.
- Metrics endpoint доступен только локально.
- Бэкапить только справочник и конфигурацию ключей, не изображения.
9. Этапы
MVP
- FastAPI, health и recognize.
- Отдельные
/opt/receipt, virtualenv, systemd и nginx location. - Загрузка справочника в SQLite.
- OCR и базовый matcher.
- Pydantic response без PII.
- API-ключ, лимиты и journald.
Следующий этап
- интеграция vision-модели;
- calibrated confidence;
- подтверждение кандидатов в Android;
- автообновление справочника;
- Prometheus metrics;
- circuit breaker;
- расширенная защита от похожих названий.
10. Основные риски
gpt-oss-120bможет оказаться text-only;- рукописный русский текст может распознаваться с низкой точностью;
- fuzzy matching может выбрать похожий препарат;
- ГРЛС может быть неполным или неудобным для автоматической синхронизации;
- клиент может случайно отправить PII;
- медицинский сценарий требует явного отказа от диагностики и назначения лечения.
11. Утверждённый вариант распознавания
Сервис не строится как OCR -> исправление строки. Основной принцип: vision-модель возвращает несколько гипотез наблюдаемого текста, после чего локальный справочник участвует в выборе допустимого препарата.
crop лекарственной строки
-> несколько вариантов preprocessing
-> DeepSeek Vision: observed_text + alternatives
-> exact/alias/fuzzy candidate resolver
-> проверка дозировки и лекарственной формы
-> scoring
-> CONFIDENT / NEED_CONFIRMATION / UNKNOWN
Ключевые правила:
- не просить модель угадывать единственный препарат без альтернатив;
- не отправлять весь словарь в prompt;
- сначала получать ограниченный набор кандидатов локальным поиском, затем передавать модели только top-N;
- название, strength и схему приёма распознавать отдельными полями;
- несовместимую дозировку использовать для снижения уверенности, а не для автоматического исправления;
- при близких названиях показывать варианты пользователю;
- добавить endpoint подтверждения результата, чтобы собирать обезличенные correction samples;
- начать с SQLite и перейти на отдельный PostgreSQL только при появлении требований к параллельной записи, админке или нескольким экземплярам сервиса.
Перед фиксацией порогов и коэффициентов scoring провести эксперимент на 50–200 обезличенных рецептах.