sequenceDiagram
autonumber
actor App as APP (📱 Клиент)
participant GW as GATEWAY (FastAPI)
participant S3 as MinIO (S3 Storage)
participant K as Брокер: Kafka
participant Vision as product-vision (YOLO)
participant Censor as censorship-worker
%% ЧАСТЬ А: ЗАГРУЗКА И ПУШ В ОЧЕРЕДЬ
App->>GW: POST /upload-product-photo (Multipart, lang)
activate GW
GW->>S3: PutObject: Сохранение файла изображения
activate S3
S3-->>GW: Возврат image_file_url
deactivate S3
note over GW: trace_id Generation & Payload Assembly
GW->>K: Пуш в топик: bpds.inventory.in.product.image
GW-->>App: HTTP 202 Accepted (Шлюз свободен)
deactivate GW
%% ЧАСТЬ Б: YOLO ДЕТЕКЦИЯ
K->>Vision: Handler: ProcessProductImage()
activate Vision
Vision->>S3: GetObject: Скачивание по URL
activate S3
S3-->>Vision: Бинарный графический поток
deactivate S3
note over Vision: YOLO Inference:<br/>Локализация контуров и детекция класса
Vision->>K: Пуш в топик: bpds.inventory.in.receipt.upload
deactivate Vision
%% ЧАСТЬ В: ЦЕНЗУРА И ВЕТВЛЕНИЕ
K->>Censor: Handler: ProcessUploadText()
activate Censor
note over Censor: Lua Script Security Check (Fail-Close)
alt Исход 10а: Чисто + ИИ уверен (score >= 0.85)
Censor->>K: Пуш в топик: bpds.inventory.out.receipt.parsed
else Исход 10б: Чисто + ИИ НЕ уверен (score < 0.85)
Censor->>K: Пуш в топик: bpds.mdm.in.product.process
K-->>App: Трансляция черновика на UI (через WS)
else Исход 10в: Обнаружен спам / инъекция
Censor->>K: Пуш в топик: bpds.aid.out.profanity.violate
end
deactivate Censor
Метод POST /api/v1/gateway/upload-product-photo
Документация API: Добавление продуктов через готовый снимок с автоматической gRPC ИИ-модерацией
- IAD-MIGRATION-116 Backlog — Краткое Описание задачи 1.
- IAD-MIGRATION-117 Refinement (Уточнение) — Краткое Описание задачи 2.
- GW-2 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-интеграциями
В открытом доступе представлена демонстрационная версия метода. В настоящей публичной документации отображены не все шаги, технические сценарии и приватные эндпоинты для системы цифровых симуляторов бизнес-процессов.
- Полная спецификация метода: Будет доступна только во внутреннем контуре разработки (Confluence / Swagger Enterprise).
1 Функциональное назначение
Метод предназначен для асинхронной обработки и распознавания изображений продуктов (или чеков) с использованием компьютерного зрения (YOLO).
В рамках новой архитектуры этот метод выполняет роль поставщика медиа-контекста (Vision Context Provider):
- Изоляция бинарного трафика: Шлюз
FastAPIне держит изображение в оперативной памяти во время инференса, а мгновенно сбрасывает его в распределенное объектное хранилищеMinIO (S3), освобождая сетевые потокиUvicorn. - Асинхронная детекция (YOLO): Тяжелый инференс сверточных нейросетей вынесен из API-слоя в изолированный воркер
product-vision-processor, который транслирует графические пиксели в текстовый канонический класс предмета. - Переиспользование контура защиты: После детекции текстовая строка автоматически направляется в уже существующий воркер цензуры (
censorship-control-worker), что гарантирует соблюдение единых политик безопасности периметра (Fail-Close через Redis).
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/purchase/upload-product-photo - Формат данных:
multipart/form-data
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу составных данных формы (мультипарт) | multipart/form-data; boundary=----WebKitFormBoundary... |
Authorization |
Да | Токен авторизации для извлечения user_id и Multi-Tenancy клейма home_group_id |
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... |
X-Request-ID |
Да | Сквозной ID запроса для трассировки сессии (FastAPI \(\rightarrow\) MinIO \(\rightarrow\) Kafka \(\rightarrow\) Workers) | req-5f9c-2b1a-88ef |
Accept |
Нет | Ожидаемый формат ответа от бэкенд-шлюза | application/json |
2.2 Спецификация параметров запроса (Query Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
app_lang |
String | Да | Целевой язык приложения для адаптации системного промпта на этапе цензуры | ru |
2.3 Спецификация тела запроса (Request Body — Multipart FormData)
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
file |
Binary (File) | Да | Бинарный файл фотографии продукта (image/jpeg, image/png). Ограничение: до 10 МБ. |
snapshot.jpg |
crop_type |
String | Нет | Режим кадрирования и предобработки матрицы для ИИ-воркера (default, square, raw) |
"square" |
2.3.1 Пример сырого HTTP-запроса (Payload):
POST /api/v1/purchase/upload-product-photo?app_lang=ru HTTP/1.1
Host: foodlifecycle.com
Authorization: Bearer eyJhbGciOiJIUzI1Ni...
X-Request-ID: req-5f9c-2b1a-88ef
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="snapshot.jpg"
Content-Type: image/jpeg
[БИНАРНЫЕ ДАННЫЕ ИЗОБРАЖЕНИЯ]
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="crop_type"
square
------WebKitFormBoundary7MA4YWxkTrZu0gW--
3 Схема обработки запроса пользователя (Mermaid)
4 Расшифровка шагов
| Шаг | Действие | Параметры / Запросы / DTO | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (Gateway -> MinIO) |
API-шлюз принимает бинарный файл фотографии продукта (.jpg или .png) со смартфона пользователя, выполняет валидацию размера и сигнатуры медиафайла, после чего загружает его в S3-бакет. |
S3 API PUT Object Request: Bucket: "product-images", Key: "uploads/2026/07/product_photo_202.jpg"Headers: Content-Type: image/jpeg, Content-Length: 3145728 |
MinIOException: BucketNotFoundMinIOException: AccessDenied (неверные или просроченные учетные данные API Gateway)StorageFullException |
Шаг 2 (MinIO -> Gateway) |
Персистентное хранилище MinIO сохраняет изображение на диски и возвращает API-шлюзу внутреннюю URL-ссылку на загруженный графический объект. | S3 API PUT Object Response: HTTP Статус: 200 OKETag: "4a28c319ba27d3322d8d8a7c5cb9c402", URL: https://minio.internal |
MinIOException: ConnectionTimeoutDiskWriteError (критическая ошибка ввода-вывода или размонтирование RAID-массива) |
Шаг 3 (Gateway -> T_Photo) |
Шлюз формирует стартовое DTO задачи компьютерного зрения, внедряет метаданные языка локали приложения, сквозной trace-id и отправляет сообщение в брокер Kafka. | Kafka Message (Topic: bpds.inventory.in.product.image):Payload: { "image_file_url": "https://minio.internal", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
FastAPI.ValidationError (передан некорректный URL или отсутствуют обязательные поля)KafkaException: QueueFullExceptionMessageTimedOut |
Шаг 4 (T_Photo -> ProductAI) |
Микросервис product-vision-processor, подписанный на топик изображений продуктов, вычитывает очередную задачу для проведения объектного анализа. |
Kafka Consumer Poll Request:Group_ID: "product-cv-processors", Получен payload шага 3.Partition: 2, Offset: 99412 |
KafkaException: CommitFailedException (зависание потока детекции, консьюмер выпал из группы по таймауту)SerializationException |
Шаг 5 (ProductAI -> MinIO) |
Сервис компьютерного зрения обращается к объектному хранилищу S3 для скачивания бинарной матрицы картинки во временный буфер памяти GPU/CPU. | HTTP GET /product-images/uploads/2026/07/product_photo_202.jpg: Headers: Internal Auth Tokens. На выходе: бинарный поток байт графического файла. |
HTTP 404 Not Found (файл удален из бакета до начала обработки воркером)HTTP 403 ForbiddenConnectTimeout |
Шаг 6 (ProductAI -> T_Results) |
Локальная нейросеть YOLO определяет границы объекта и транслирует его в строковое имя класса (например, ‘Груша’). Результат вместе с trace-id пушится в общий топик. | Kafka Message (Topic: bpds.inventory.in.receipt.upload):Payload: { "raw_text_input": "Груша Конференция", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
YOLOInferenceError (низкая уверенность модели или сбой CUDA ядра)KafkaException: DeliveryTimeoutKafkaException: RecordTooLargeException |
Шаг 7 (T_Results -> Censor) |
Воркер цензуры считывает извлеченный нейросетью YOLO текстовый класс или метаданные объекта для проведения лингвистической фильтрации. | Kafka Consumer Poll Request:Group_ID: "censorship-workers", Topic: bpds.inventory.in.receipt.uploadPayload: { "raw_text_input": "Груша Конференция", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
KafkaException: CommitFailedExceptionSerializationException (ошибка десериализации JSON после CV-этапа) |
Шаг 8 (Censor -> Ollama) |
Цензор вызывает локальный LLM-контейнер для проверки семантики полученного имени класса на предмет инъекций, спама или провокационных подмен. | HTTP POST /api/generate (Ollama API):{ "model": "llama3", "prompt": "Analyze the following object class text for profanity. Return exact JSON: { 'is_clean': boolean, 'confidence': float }. Text: 'Груша Конференция'", "stream": false } |
ConnectTimeout: Ollama unreachableHTTP 500 Internal Server ErrorReadTimeout: Inference duration exceeded |
Шаг 9 (Censor -> Redis) |
Параллельно выполняется сверка по локальным словарям мата для app_lang и атомарный инкремент счетчика инцидентов пользователя. |
Redis Command Pipeline:1. SISMEMBER "dict:profanity:ru-RU" "проверяемое_слово"2. MULTI3. INCRBY "abuse:counter:user_321" 14. EXPIRE "abuse:counter:user_321" 864005. EXEC |
Redis.ConnectionError: Connection refusedRedis.TimeoutError: Command timed outRedis.ClusterDownException |
Шаг 10а (Censor -> T_Clean_Text) |
Ветка «Успех»: Если имя класса признано чистым и ИИ полностью уверен в результате, текстовое описание продукта отправляется в топик для дальнейшего парсинга. | Kafka Message (Topic: bpds.inventory.out.receipt.parsed):Payload: { "clean_text_input": "Груша Конференция", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
KafkaException: DeliveryTimeoutKafkaException: NotCoordinatorForException |
Шаг 10б (Censor -> T_Draft) |
Ветка «Сомнение»: Если фото неразборчиво, объект перекрыт или ИИ не уверен в контексте класса, воркер изолирует запись в топик черновиков. | Kafka Message (Topic: bpds.mdm.in.product.process):Payload: { "draft_data": { "raw_id": "uuid-cv", "text": "Неразборчивый класс объекта" }, "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
KafkaException: LeaderNotAvailableExceptionKafkaException: RecordTooLargeException |
Шаг 10в (Censor -> Abuse_Check -> T_Notif) |
Первичное нарушение на фото: Зафиксирован спам или мат в метаданных. Разветвитель видит в Redis Счетчик == 1 и пушит событие первого предупреждения в шину алармов. |
Kafka Message (Topic: bpds.aid.out.profanity.violate):Payload: { "user_id": "user_321", "violation_count": 1, "app_lang": "ru-RU", "notification_type": "CV_PROFANITY_WARNING" } |
KafkaException: BrokerNotAvailableKafkaException: MessageTimedOut |
Шаг 11 (T_Draft -> App) |
Из топика черновиков текст неразборчивого или сомнительного фото выводится на экран смартфона модератора во вкладку ручной корректировки приложения. | gRPC / HTTP Stream (Вкладка модерации):Payload: { "draft_id": "uuid-draft-cv", "raw_text_input": "Сомнительный класс объекта", "app_lang": "ru-RU" } |
DioException: connection errorHTTP 401 Unauthorized (истек JWT-токен модератора) |
Шаг 12 (App -> T_Results) |
Модератор вручную корректирует текстовое наименование продукта по фотографии и отправляет чистую версию на повторный цикл цензуры. | Kafka Message (Topic: bpds.inventory.in.receipt.upload):Payload: { "raw_text_input": "Исправленное вручную имя продукта модератором", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
KafkaException: QueueFullExceptionFastAPI.ValidationError (текст превысил допустимые лимиты схемы) |
Шаг 12а (Censor -> T_Clean_Text) |
Успешный исход модерации фото: Повторная автоматическая проверка цензором подтверждает чистоту текста. Данные публикуются в топик успешно разобранных строк. | Kafka Message (Topic: bpds.inventory.out.receipt.parsed):Payload: { "clean_text_input": "Исправленное вручную имя продукта модератором", "app_lang": "ru-RU", "x_request_id": "trace-cv-censor-uuid" } |
KafkaException: DeliveryTimeoutKafkaException: ConcurrentModificationException |
Шаг 12б (Abuse_Check -> T_Notif) |
Повторный мат при модерации: Если в отредактированном черновике фото снова обнаружен мат, разветвитель фиксирует Счетчик >= 2 и пушит аларм для блокировки пользователя. |
Kafka Message (Topic: bpds.aid.out.profanity.violate):Payload: { "user_id": "user_321", "violation_count": 2, "app_lang": "ru-RU", "notification_type": "CV_PROFANITY_BAN" } |
KafkaException: LeaderNotAvailableExceptionInvalidTopicException |
5 Спецификация ответов сервера (Response Body) и вилок исключений
5.1 1. Успешные ответы (Success Responses)
5.1.1 HTTP 202 Accepted (Успешный асинхронный ответ — Шаг 6)
Возвращается мобильному приложению шлюзом FastAPI Gateway мгновенно после успешного сохранения бинарного multipart-потока в MinIO S3 и публикации задачи в топик брокера сообщений Kafka.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "PHOTO_UPLOAD_ACCEPTED",
"data": {
"trace_id": "req-5f9c-2b1a-88ef",
"message": "Фотография успешно загружена в S3-хранилище и принята в пайплайн ИИ-детекции YOLO.",
"processed_at": "2026-07-20T23:21:00Z"
}
}5.1.2 HTTP 201 Created (Асинхронный Исход 10а / Финальное авто-подтверждение)
Генерируется на финальном этапе обработки чека или фото после успешного прохождения цензуры, NER-распознавания и параллельной фиксации (Fan-Out) транзакций в инвентарных базах данных.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "CONFIRMED",
"product_name": "ГРУША",
"applied_quantity": 1.0,
"unit": "шт",
"timestamp": "2026-07-20T23:21:00Z"
}5.2 2. Спецификация ошибок и вилок исключений (Error Responses)
Все ошибки бэкенда возвращаются в едином стандартизированном формате RFC 7807 (Problem Details) или стандартном JSON-формате FastAPI HTTPException. Это позволяет Flutter-клиенту (Dio) однозначно парсить код ошибки и выводить корректный локализованный текст на основе динамического справочника.
5.2.1 Исключение: Низкое качество изображения / Сбой распознавания (Асинхронный Исход 10б)
Формируется ИИ-воркером, если модель YOLO вернула score уверенности ниже порогового значения 0.85. Пайплайн инвентаризации приостанавливается, система генерирует черновик и отправляет пуш-контекст на UI через WebSocket.
{
"error_code": "ERR-LOW-VISION-CONFIDENCE",
"message": "Не удалось однозначно распознать продукт на фотографии. Требуется ручное подтверждение.",
"details": {
"yolo_score": 0.54,
"image_snapshot_url": "s3://raw-images/uuid-99.jpg",
"action": "Render interactive draft card on UI with user text-input field fallback."
}
}5.2.2 Ошибка загрузки и персистентности файла в бакет (HTTP 500 Internal Server Error — Шаг 2)
Выбрасывается шлюзом FastAPI, если внутренний кластер MinIO S3 недоступен, пул соединений переполнен или хранилище выдало отказ на прием бинарного потока put_object().
{
"error_code": "ERR-BUCKET",
"message": "Не удалось сохранить или верифицировать медиафайл во внутреннем объектном хранилище.",
"details": {
"reason": "MinIO S3 storage cluster rejected input stream or partition disk is full."
}
}5.2.3 Ошибка ИИ-цензурирования и детекции (Асинхронный Исход 10в — Шаг 14в)
Формируется воркером цензуры, если в процессе распознавания или анализа текстового класса модели YOLO локальная нейросеть Ollama через gRPC зафиксировала попытку инъекции кода в метаданные, мат, спам или деструктивный контент (is_profane == true).
{
"error_code": "ERR-CENSOR",
"message": "Изображение или распознанный текст класса не прошли автоматическую проверку политик безопасности.",
"details": {
"reason": "Spam, explicit content, or prompt injection pattern detected by Ollama Guardrails."
}
}5.2.4 Ошибка валидации структуры контракта (HTTP 422 Unprocessable Entity — Шаг 1.1)
Выбрасывается шлюзом FastAPI, если размер переданного файла превышает лимит в 10 МБ, либо нарушена multipart-структура тела запроса формы.
{
"error_code": "IAD-VAL-422",
"message": "Переданные данные формы или файл не соответствуют ожидаемой схеме валидации Pydantic.",
"details": [
{
"loc": ["body", "file"],
"msg": "Размер загружаемой фотографии продукта не должен превышать 10 МБ.",
"type": "value_error.file.size_limit"
}
]
}