Files
recipe/PLAN/receipt-service-plan.md
T

10 KiB
Raw Blame History

План архитектуры 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

  1. Проверить API-ключ, размер, magic bytes и разрешение изображения.
  2. Применить маскирование зон до передачи изображения модели.
  3. Выполнить масштабирование и deskew.
  4. Получить OCR-текст и bounding boxes.
  5. Выделить роли: лекарство, дозировка, режим, дата, прочее.
  6. Удалить PII из результата.
  7. Сопоставить название со справочником через нормализацию и rapidfuzz.
  8. При близких или слабых совпадениях установить uncertain=true.
  9. Валидировать ответ Pydantic.
  10. Вернуть 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

  1. FastAPI, health и recognize.
  2. Отдельные /opt/receipt, virtualenv, systemd и nginx location.
  3. Загрузка справочника в SQLite.
  4. OCR и базовый matcher.
  5. Pydantic response без PII.
  6. 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 обезличенных рецептах.