sequenceDiagram
autonumber
actor User as Мобильный клиент (App)
participant GW as FastAPI Gateway (backend-api)
participant S3 as MinIO Object Storage
participant K as Apache Kafka Cluster
participant AudioAI as grpc-analytics (AID)
participant Censor as censorship-control-worker (SECURITY)
%% ШАГ 1: ЗАГРУЗКА ФАЙЛА И ПУШ ЗАДАЧИ
User->>GW: POST /api/v1/purchase/upload-voice (Multipart Form Data)
GW->>S3: PutObject: Сохранение .mp3/.ogg бинарного потока
S3-->>GW: Возврат audio_file_url
Note over GW: Шаг 3: Генерация X-Request-ID (trace_id)<br/>Сборка payload с app_lang
GW->>K: Пуш в топик: bpds.inventory.in.voice.stream
GW-->>User: HTTP 202 Accepted (Файл загружен, сессия в обработке)
%% ШАГ 2: ТРАНСКРИБАЦИЯ
K->>AudioAI: Handler: ProcessVoiceStream()
AudioAI->>S3: GetObject: Скачивание бинарного аудио по URL
S3-->>AudioAI: Бинарный аудио-поток
Note over AudioAI: Шаг 5: Инференс локального Whisper Tiny<br/>Перевод звуковых волн в сырую строку текста
AudioAI->>K: Пуш в топик: bpds.inventory.in.receipt.upload
%% ШАГ 3: ЦЕНЗУРА И ВЕТВЛЕНИЕ
K->>Censor: Handler: ProcessUploadText() (Общий контур фильтрации)
Note over Censor: Анализ строки, инкремент Redis сессий<br/>и валидация через Ollama Container
alt Исход 10а: Чисто + ИИ уверен
Censor->>K: Пуш в топик: bpds.inventory.out.receipt.parsed
else Исход 10б: Чисто + ИИ НЕ уверен
Censor->>K: Пуш в топик: bpds.mdm.in.product.process
K-->>User: Трансляция черновика (draft_data) во Вкладку модерации
else Исход 10в / 12б: Обнаружен мат/спам
Censor->>K: Пуш в топик: bpds.aid.out.profanity.violate
end
Метод POST /api/v1/purchase/upload-voice
Документация API: Добавление продуктов через готовый снимок с автоматической gRPC ИИ-модерацией
- IAD-MIGRATION-116 Backlog — Краткое Описание задачи 1.
- IAD-MIGRATION-117 Refinement (Уточнение) — Краткое Описание задачи 2.
- GW-1 Ready for Development — Подключение эндпоинта в шлюзе.
- AID-012 Ready for Development — создать метож ProcessVoiceStream.
- AID-013 Ready for Development — создать метож ProcessUploadText.
- INV-FRONTEND-222 Refinement (Уточнение) — Экран управления B2B-интеграциями
- INV-FRONTEND-223 Refinement (Уточнение) — Экран управления B2B-интеграциями
1 Функциональное назначение
Метод реализует логику быстрого добавления продуктов питания в «цифровой холодильник» через отправку снимка (jpg, png). Фотография продукта либо чек со списком продуктов.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/purchase/upload-voice - Формат данных:
multipart/(.mp3/.ogg)
2.1 Спецификация тела запроса (Request Body)
| Поле | Тип | Обязательный | Описание |
|---|---|---|---|
TBD |
String | Да | Файл (например: "*.mp3/.ogg") |
3 Схема обработки запроса пользователя (Mermaid)
4 Расшифровка шагов
4.1 Шаги 1–6: Загрузка файла и первичная фиксация
- Шаг 1 (
User -> APP): Пользователь во Flutter-интерфейсе нажимает кнопку «ПАКЕТ ЧЕКА v1» и выбирает фотографию из галереи или камеры. - Шаг 2 (
APP -> NGINX): Приложение через клиентDioотправляет бинарный файл изображения методомHTTP POST /product-photo(форматmultipart/form-data) на прокси-сервер. - Шаг 3 (
NGINX -> API):Nginxпринимает запрос и перенаправляет (проксирует) его на бэкенд-шлюзFastAPI. В бэкенде активируется контекст обработки (activate API). - Шаг 4 (
API -> S3):FastAPIвызывает асинхронный методs3_client.upload_fileobj(), отправляя бинарный поток фотографии апельсина/чека на сохранение в бакетMinIO S3. - Шаг 5 (
API -> DB): Бэкенд делает запись в базу данныхPostgreSQL:INSERT INTO pending_receipts_buffer (status='PROCESSING'). Этим он резервирует буфер и генерирует уникальныйUUID(draft_id) для черновика. - Шаг 6 (
API -> NGINX): Бэкенд мгновенно, не дожидаясь ИИ, возвращает прокси-серверу ответHTTP 202 Acceptedвместе со сгенерированнымdraft_id = UUID.
4.2 Шаги 7–13: Асинхронный ИИ-инференс, генерация тикета и Push-уведомление
- Шаг 7 (
NGINX -> APP):Nginxтранслирует ответHTTP 202в мобильное приложение. Экран смартфона освобождается для пользователя, он может идти дальше по приложению. - Шаг 8 (
API -> GRPC): На бэкенде фоновый воркерasync_bupar_ai_workerинициирует удаленный вызов к ИИ-сервису через методgRPC CheckProduct(draft_id, s3_key, crop_type). ШлюзFastAPIвременно деактивируется, а gRPC-сервер активируется (activate GRPC). - Шаг 9 (
GRPC -> S3): ИИ-сервис на базеPyTorchобращается к хранилищуMinIO S3и скачивает сырые байты картинки по переданному адресуs3_key. - Шаг 10 (
GRPC -> GRPC): Внутренний шаг ИИ-сервиса: нейросетьPyTorch (MobileNetV2)рассчитывает тензор весов. На выходе определяется ID класса950, который автоматически маппится в строку"ORANGE". - Шаг 12 (
GRPC -> API): ИИ-сервис возвращает структурированный gRPC-ответImageResponse(status='PENDING', info='ORANGE')обратно на бэкендFastAPI. Работа gRPC-сервера завершается. - Шаг 12 (
API -> DB):FastAPIснова активен. Он валидирует ответ (что пришла именно строка, а не JSON) и создаёт запись в БД:INSERT INTO product_moderation_tickets (status='PENDING')для ручного подтверждения. - Шаг 13 (
API -> KAFKA): Бэкенд отправляет асинхронное сообщениеsend_to_kafka_async("analytics.dlq.v1"). Этот инцидент SKU логируется в брокер сообщений для команды MLOps (чтобы позже дообучать нейросеть).
4.3 Шаги 14–21: Ручное исправление, транзакция в БД и обновление остатков
- Шаг 14 (
API -> APP): Бэкенд через открытый WebSocket-канал/ws/push-stream/{id}отправляет клиенту push-сигнал"NEW_MODERATION_TICKET". У пользователя во Flutter мгновенно обновляется вкладка «Модерация ИИ» и выводится карточкаORANGE. Контекст API засыпает. - Шаг 15 (
User -> APP): Пользователь видит карточкуORANGE, вручную стирает её, пишет"АПЕЛЬСИНЫ"и нажимает кнопку «Утвердить». - Шаг 16 (
APP -> NGINX): Приложение отправляет запросHTTP POST /receipt-draft/{UUID}/confirmс телом"qty": 1.0(тип double) на прокси-сервер. - Шаг 17 (
NGINX -> API):Nginxперенаправляет этот POST-запрос на бэкендFastAPI. Бэкенд снова активируется. - Шаг 18 (
API -> DB): Бэкенд идет в базу данных с запросомSELECT FROM product_moderation_tickets, чтобы проверить статус тикета (защита от повторного подтверждения). В этот момент стартует атомарная SQL-транзакция, внутри которой последовательно выполняются еще три операции:INSERT INTO bupar_event_log (activity='PURCHASE')— логирование для Process Mining в R.INSERT INTO fridge_inventory ON CONFLICT DO UPDATE— инкремент количества апельсинов в холодильнике, если они уже там были, или создание новой записи.UPDATE product_moderation_tickets (status='CONFIRMED')— закрытие тикета.
- Шаг 19 (
API -> KAFKA): После успешного коммита транзакции в БД, бэкенд отправляет событиеsend_to_kafka_async("bupar.fridge.events.add"), сигнализируя всей системе, что остатки продуктов на складе изменились. - Шаг 20 (
API -> NGINX): Бэкенд сообщает об успехе, возвращая статусHTTP 201 Createdпрокси-серверу, и закрывает свой контекст. - Шаг 21 (
NGINX -> APP):Nginxтранслирует статусHTTP 201 Createdв мобильное приложение. Приложение ловит этот статус, делает автоматический фоновый запросGET /fridge/stock(для вычитывания актуального склада), карточка тикета исчезает, и на экране пользователя появляются реальные апельсины.
5 Сквозная бизнес-логика и ИИ-модерация
Процесс обработки запроса живого пользователя разделен на три этапа:
5.1 Этап : HTTP POST (Контур отправки)
MediaType('image', 'jpeg') асинхронно перенаправляется во внутреннее хранилище на метод upload_fileobj() для сохранения в выделенном бакете.
- Если метод возвращает статус
REJECTED, бэкенд прерывает транзакцию и выбрасывает ошибку цензурыERR-BUCKET(HTTP 400). Ошибка маппится с текстом для модалки.
5.2 Этап : Запись в PostgreSQL
Запись вносится в базу данных. В:
INSERT INTO pending_receipts_buffer (status="PROCESSING", draft_id=uuid)
VALUES (\$1, \$2)- Если база данных не доступна возвращает
- Возвращает HTTP 202 Accepted.
5.3 Этап : gRPC Цензурирование (ИИ-контур модерации)
MediaType('image', 'jpeg') асинхронно перенаправляется во внутренний gRPC-сервер аналитики на метод CheckProduct для проверки на спам и ненормативную лексику.
- Если ИИ-модератор возвращает статус
REJECTED, бэкенд прерывает транзакцию и выбрасывает ошибку цензурыERR-BUPAR-CENSOR(HTTP 400). - Стратегия Fail-Safe: Если gRPC-сервер оффлайн, система пишет предупреждение
[Moderation Warning]в системный лог и пропускает запрос по сценарию ограниченного доверия, чтобы не блокировать действия реального человека.
5.4 Этап : Парсинг строки
Чистый текст разбивается по разделителям (пробелам). Первое слово интерпретируется как наименование продукта, второе — преобразуется в тип float как количество (дефолтное значение = 1.0).
5.5 Этап : Фиксация в PostgreSQL
Запись вносится в базу данных. В случае коллизии (если такой продукт уже добавлен в рамках этой семьи), срабатывает логика инкремента остатков:
INSERT INTO fridge_inventory (user_id, home_group_id, product_name, quantity, unit)
VALUES (\$1, \$2, \$3, \$4, 'шт')
ON CONFLICT (home_group_id, product_name)
DO UPDATE SET quantity = fridge_inventory.quantity + EXCLUDED.quantity;5.6 Этап : gRPC Цензурирование (ИИ-контур модерации)
Строка raw_text_input асинхронно перенаправляется во внутренний gRPC-сервер аналитики на метод CheckProduct для проверки на спам и ненормативную лексику.
- Если ИИ-модератор возвращает статус
REJECTED, бэкенд прерывает транзакцию и выбрасывает ошибку цензурыERR-BUPAR-CENSOR(HTTP 400). - Стратегия Fail-Safe: Если gRPC-сервер оффлайн, система пишет предупреждение
[Moderation Warning]в системный лог и пропускает запрос по сценарию ограниченного доверия, чтобы не блокировать действия реального человека.
5.7 Этап : Фиксация в PostgreSQL
Запись вносится в базу данных. В случае коллизии (если такой продукт уже добавлен в рамках этой семьи), срабатывает логика инкремента остатков:
INSERT INTO fridge_inventory (user_id, home_group_id, product_name, quantity, unit)
VALUES (\$1, \$2, \$3, \$4, 'шт')
ON CONFLICT (home_group_id, product_name)
DO UPDAT
## Спецификация ответов сервера
### Успешное добавление (201 Created)
```json
{
"status": "SUCCESS",
"message": "Продукт 'Яблоки' успешно отмодерирован и добавлен текстовым контуром."
}5.8 Ошибка модерации (400 Bad Request)
Возвращается, если ИИ-контур зафиксировал мат или спам в текстовом вводе реального пользователя.
{
"code": "ERR-BUPAR-CENSOR",
"detail": "Текст не прошел модерацию: обнаружена ненормативная лексика или спам."
}