sequenceDiagram
autonumber
participant API as Бэкенд-шлюз (FastAPI)
participant GRPC as ИИ-сервис (OCR Engine)
participant S3 as Хранилище (MinIO S3)
participant DB as Реляционная СУБД (PostgreSQL)
participant APP as Мобильное приложение (Flutter)
Note over API: Шаг 1: Фоновый запуск после маршрутизации Типа 2 (Чек)
API->>GRPC: gRPC ExtractReceiptData(draft_id, s3_key)
activate GRPC
GRPC->>S3: Скачивает бинарные байты чека по s3_key
activate S3
S3-->>GRPC: Возвращает файл изображения чека
deactivate S3
note over GRPC: Шаг 4: Сегментация и распознавание текста (OCR)
note over GRPC: Шаг 5: Регулярные выражения (Парсинг веса, цен и количества)
alt Ошибка чтения структуры чека / Смазанное фото
GRPC-->>API: gRPC error (StatusCode.DATA_LOSS, "OCR_FAILED")
else Успешный парсинг матрицы товаров
GRPC-->>API: gRPC ReceiptExtractResponse(status='SUCCESS', items=[...])
deactivate GRPC
activate API
note over API: Шаг 7: Старт транзакции записи позиций чека
API->>DB: INSERT INTO pending_receipt_items (массив строк с qty/price)
API->>DB: UPDATE pending_receipts_buffer SET status='READY_FOR_AUDIT'
API->>APP: WebSocket /ws/push-stream/{id} -> Сигнал "RECEIPT_OCR_READY"
note over APP: Экран ожидания исчезает, открывается список товаров чека для аудита
deactivate API
end
Метод gRPC /analytics.v1.AnalyticsService/ExtractReceiptData
Документация API: Асинхронный контур OCR-распознавания фискальных чеков и парсинга товарной матрицы
1 Функциональное назначение
Метод предназначен для глубокого анализа изображений, которые на этапе первичной классификации (Статья 1) были определены ИИ-сервисом как «Тип 2: Фискальный чек». Так как процесс оптического распознавания текста (OCR) и парсинга строк является вычислительно сложным и занимает от 1.5 до 4 секунд, этот контур полностью изолирован от синхронного HTTP-потока пользователя.
Метод решает следующие задачи: 1. Текстовое распознавание (OCR): Локализация текстовых блоков на фотографии чека и превращение пикселей в сырой массив строк. 2. Парсинг матрицы товаров: Извлечение из каждой строки наименования товара, его цены, единицы измерения и точного количества/веса (включая дробные значения float для весовых товаров). 3. Нормализация данных: Подготовка плоского списка позиций чека для записи в дочернюю таблицу буфера pending_receipt_items перед выводом пользователю на экран ручного аудита.
2 Протокол взаимодействия (gRPC Контракт)
- Протокол:
gRPCповерхHTTP/2 - Тип вызова: Unary RPC (вызывается фоновым воркером
FastAPIасинхронно) - Пакет (Package):
analytics.v1 - Сервис (Service):
AnalyticsService - Метод:
ExtractReceiptData
2.1 Спецификация тела запроса (Protocol Buffers Request)
Контракт описывается в файле analytics.proto. Фоновый воркер передает ИИ-сервису только указатель на файл в S3, чтобы не гонять бинарники по сети повторно.
syntax = "proto3";
package analytics.v1;
message ReceiptExtractRequest {
string draft_id = 1; // Уникальный UUID черновика чека из таблицы pending_receipts_buffer
string s3_key = 2; // Путь к файлу фотографии чека в бакете MinIO S3
}2.2 Спецификация структуры ответа (Protocol Buffers Response)
ИИ-сервис возвращает структурированный массив распознанных позиций (структура «один-ко-многим»).
message ReceiptItem {
string raw_name = 1; // Сырая строка из чека (например, "2110310 БАНАН ЭКВАДОР КГ")
double quantity = 2; // Распознанное количество или вес (например, 1.695)
double price = 3; // Цена за единицу товара
double total_sum = 4; // Общая сумма по позиции
}
message ReceiptExtractResponse {
string status = 1; // Статус обработки: "SUCCESS" или "OCR_FAILED"
string draft_id = 2; // Идентификатор черновика
repeated ReceiptItem items = 3; // Массив распознанных товарных позиций
}3 Схема обработки запроса пользователя (Mermaid)
Диаграмма описывает асинхронный процесс: пока пользователь видит экран ожидания, фоновый воркер FastAPI и gRPC ИИ-сервис скачивают чек из MinIO S3, распознают матрицу товаров через OCR-движок, парсят веса, записывают данные в PostgreSQL и отправляют WebSocket-сигнал во Flutter.
4 Расшифровка шагов
- Шаг 1 (
API -> GRPC): Сразу после того, как классификатор верхнего уровня (из Статьи 1) определил изображение как чек и бэкенд зафиксировал его в таблицеpending_receipts_buffer, асинхронный фоновый воркерFastAPIотправляет gRPC-запрос на методExtractReceiptData. Передается UUID черновика и ключ файла в S3. Пользователь в этот момент видит во Flutter экран с лоадером «ИИ читает чек…». - Шаг 2 (
GRPC -> S3): ИИ-сервис (OCR-контур) обращается к объектному хранилищуMinIO S3по полученномуs3_key, чтобы забрать оригинальное изображение чека в высоком разрешении. - Шаг 3 (
S3 -> GRPC):MinIO S3отдает бинарный файл изображения на gRPC-сервер аналитики. - Шаг 4 (
GRPC -> GRPC): Внутри ИИ-сервиса запускается OCR-движок. Он выполняет предобработку снимка (Бинаризация, выравнивание контрастности, дескьюинг — устранение наклона текста) и производит сегментацию изображения на строки, переводя графические пиксели в текстовые строки UTF-8. - Шаг 5 (
GRPC -> GRPC): Текст передается в детерминированный парсер на базе регулярных выражений (Regex). Парсер разбирает структуру чека строка за строкой. Он ищет разделители умножения (Xили*), выделяет цену (например,595.00) и строго извлекает количество/вес (например, дробное значение1.695000для строкиБАНАН ЭКВАДОР КГ).- Вилка исключений (Критический дефект фото): Если изображение полностью размыто, порвано или OCR-движок не смог распознать ни одной товарной позиции, метод прерывает выполнение и возвращает gRPC-ошибку
OCR_FAILED(кодDATA_LOSS). Бэкенд-шлюз ловит её, переводит статус чека вFAILEDи шлет WebSocket-пуш на фронтенд для вывода модального окна: «Не удалось прочитать чек. Пожалуйста, сфотографируйте четче или введите продукты вручную».
- Вилка исключений (Критический дефект фото): Если изображение полностью размыто, порвано или OCR-движок не смог распознать ни одной товарной позиции, метод прерывает выполнение и возвращает gRPC-ошибку
- Шаг 6 (
GRPC -> API): В случае успешного разбора всей матрицы товаров, gRPC-сервер упаковывает результат в структуруReceiptExtractResponse, содержащую массивrepeated ReceiptItem, и возвращает его на бэкендFastAPI. - Шаг 7 (
API -> DB): ШлюзFastAPIактивируется и открывает SQL-транзакцию. Так как чек содержит несколько позиций (связь1:M), бэкенд выполняет множественную вставку (Bulk Insert) всех распознанных строк, их цен и весов в дочернюю буферную таблицу:INSERT INTO pending_receipt_items (receipt_draft_id, raw_name, quantity, price) VALUES (...). После этого статус родительского черновика вpending_receipts_bufferобновляется наREADY_FOR_AUDIT. - Шаг 8 (
API -> APP): Бэкенд через открытое WebSocket-соединение отправляет клиенту push-сигнал"RECEIPT_OCR_READY"вместе с UUID черновика. Мобильное приложение Flutter мгновенно скрывает экран ожидания (лоадер) и перенаправляет пользователя на экран ручного аудита чека, где выгружается весь список распознанных товаров с заполненными весами и ценами из таблицыpending_receipt_items. Пользователю остается только подтвердить или скорректировать список, как это описано в Шаге 15 Версии 1.
5 Спецификация структур данных буфера и вилок исключений
5.1 1. Спецификация буферных таблиц PostgreSQL (Связь 1:M)
Для хранения результатов распознавания чека до момента ручного подтверждения пользователем используется классическая реляционная схема со связью «один-ко-многим» (One-to-Many). Это позволяет хранить метаданные чека изолированно от его позиций.
-- Таблица 1: Шапка чека (Родительская структура)
CREATE TABLE pending_receipts_buffer (
draft_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
home_group_id VARCHAR(50) NOT NULL,
s3_key VARCHAR(512) NOT NULL,
status VARCHAR(30) NOT NULL DEFAULT 'PROCESSING', -- PROCESSING, READY_FOR_AUDIT, CONFIRMED, FAILED
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Таблица 2: Распознанные позиции чека (Дочерняя структура)
CREATE TABLE pending_receipt_items (
item_id BIGSERIAL PRIMARY KEY,
receipt_draft_id UUID NOT NULL REFERENCES pending_receipts_buffer(draft_id) ON DELETE CASCADE,
raw_name VARCHAR(255) NOT NULL, -- Пример: "2110310 БАНАН ЭКВАДОР КГ"
quantity NUMERIC(10, 4) NOT NULL, -- Пример: 1.6950 (дробный float/numeric для веса)
price NUMERIC(10, 2) NOT NULL, -- Пример: 595.00
is_verified BOOLEAN DEFAULT FALSE -- Флаг подтверждения пользователем при аудите
);
-- Индекс для быстрой выборки позиций конкретного чека при рендеринге во Flutter
CREATE INDEX idx_receipt_items_draft_id ON pending_receipt_items(receipt_draft_id);5.2 2. Спецификация WebSocket-сигнала об успешном завершении OCR (Шаг 8)
После завершения Bulk Insert в базу данных, FastAPI отправляет текстовое JSON-сообщение в канал /ws/push-stream/{id}. Клиент ловит его, гасит лоадер и запрашивает данные для экрана аудита.
{
"event_type": "RECEIPT_OCR_READY",
"timestamp": "2026-07-01T21:21:40.890Z",
"data": {
"draft_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"total_items_parsed": 3,
"ui_action": "REDIRECT_TO_RECEIPT_AUDIT_SCREEN",
"fetch_url": "/api/v1/fridge/receipt-draft/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d/items"
}
}5.3 3. Вилки исключений и стратегии обработки ошибок (Failures & Fail-Safe)
Асинхронный OCR-контур чеков работает в фоновом режиме, поэтому обработка ошибок строится на изменении статусов в БД и отправке алертов через WebSockets.
5.3.1 Ошибка 1: Смазанный снимок или критический сбой OCR-движка (OCR_FAILED)
- Условие срабатывания (Шаг 5): Картинка чека не сфокусирована, текст размыт, или Regex-парсер не смог выделить структуру цен и весов. gRPC возвращает ошибку со статус-кодом
DATA_LOSS. - Действие бэкенда:
FastAPIпереводит статус в таблицеpending_receipts_bufferв значениеFAILED. В топик Kafkaanalytics.dlq.v1отправляется лог инцидента. - WebSocket-пакет ошибки для клиента:
{
"event_type": "RECEIPT_OCR_FAILED",
"timestamp": "2026-07-01T21:21:42.110Z",
"data": {
"draft_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"error_code": "ERR-RECEIPT-UNREADABLE",
"message": "Не удалось распознать текст чека. Пожалуйста, введите продукты вручную или сделайте более четкий снимок."
}
}