Add isolated Gemini image proxy
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 733 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 671 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
@@ -0,0 +1,184 @@
|
||||
# План архитектуры obdai.ru/receipt
|
||||
|
||||
## 1. Цель
|
||||
|
||||
Создать независимый Python API-сервис для распознавания рукописных рецептов. API принимает изображение рецепта или фрагменты с названиями лекарств и дозировками, а возвращает структурированный текст для отображения в Android-приложении.
|
||||
|
||||
ФИО пациента, дата рождения, адрес и другие идентификаторы не должны извлекаться, сохраняться или возвращаться.
|
||||
|
||||
## 2. Размещение
|
||||
|
||||
```text
|
||||
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-модель возвращает несколько гипотез наблюдаемого текста, после чего локальный справочник участвует в выборе допустимого препарата.
|
||||
|
||||
```text
|
||||
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 обезличенных рецептах.
|
||||
Reference in New Issue
Block a user