sequenceDiagram
autonumber
actor User as Мобильный клиент (App)
participant GW as FastAPI Gateway (backend-api)
participant K as Apache Kafka Cluster
participant Censor as censorship-control-worker (SECURITY)
participant Ollama as Ollama Container (AID)
participant Redis as In-Memory Redis (SECURITY)
participant Promtail as Collector: Promtail (TELEMETRY)
%% СЦЕНАРИЙ А: ПЕРВИЧНЫЙ ВВОД С КЛИЕНТА
alt Валидация входных данных на шлюзе
User->>GW: POST /api/v1/purchase/upload-text (raw_text_input, app_lang)
Note over GW: Шаг 1: Проверка длины <= 120 симв.<br/>Генерация X-Request-ID (trace_id)
else Ошибка: Превышена длина (длина > 120 симв.)
GW-->>User: HTTP 422 Unprocessable Entity
Note over User: Всплывающее окно (Toast / Push):<br/>"Упс! Что-то пошло не так.<br/>Пожалуйста, повторите попытку позже."
%% НОВЫЙ ШАГ ЛОГИРОВАНИЯ БИЗНЕС-МЕТРИКИ
Note over GW: Шаг 1.1: Запись бизнес-метрики сбоя в stdout лог
GW->>Promtail: Отправка структурированного лога (stdout stream)
Note over Promtail: JSON Payload:<br/>{<br/> "trace_id": "X-Request-ID-12345",<br/> "domain": "INVENTORY",<br/> "error_code": "IAD-VAL-422",<br/> "error_type": "BUSINESS_VALIDATION_ERROR",<br/> "user_id": "usr_9876",<br/> "message": "Length validation failed",<br/> "context": {"max_len": 120, "actual_len": 145}<br/>}
end
critical Пуш в брокер сообщений
GW->>K: Пуш в топик: bpds.inventory.in.receipt.upload
GW-->>User: HTTP 202 Accepted (Запрос принят в обработку)
option Сбой Kafka / Таймаут записи
GW-->>User: HTTP 503 Service Unavailable (Fallback на повтор)
Note over User: Всплывающее окно (Toast / Push):<br/>"Упс! Что-то пошло не так.<br/>Пожалуйста, повторите попытку позже."
end
%% АСИНХРОННАЯ ВЫЧИТКА И ОЦЕНКА ЦЕНЗОРА
K->>Censor: Handler: ProcessUploadText() (Вычитка из receipt.upload)
critical Анализ текста через нейросеть
Censor->>Ollama: gRPC: EvalTextConfidence(text, lang)
Ollama-->>Censor: Ответ: is_profane (bool), confidence_score (float)
option Ошибка Ollama (Таймаут / Падение контейнера)
Note over Censor: Fallback: Аварийное перенаправление<br/>на ручной разбор в MDM
Censor->>K: Пуш в топик: bpds.mdm.in.product.process
end
critical Проверка лимитов нарушений в кэше
Censor->>Redis: Вызов Lua-скрипта (Проверка словаря по app_lang + INCR счетчик)
Redis-->>Censor: Текущий count_violation для user_id
option Ошибка Redis (Connection Refused)
Note over Censor: Режим отказоустойчивости:<br/>Игнорируем счетчик нарушений
end
%% АЛГОРИТМИЧЕСКОЕ ВЕТВЛЕНИЕ РЕЗУЛЬТАТОВ
alt Исход 5а: Текст чист + ИИ уверен (Confidence >= 0.85)
Censor->>K: Пуш в топик: bpds.inventory.out.receipt.parsed
else Исход 5б: Текст чист + ИИ НЕ уверен (Confidence < 0.85)
Censor->>K: Пуш в топик: bpds.mdm.in.product.process
K-->>User: Трансляция draft_data через WebSocket на «Вкладку модерации»
else Исход 5в / 8б: Обнаружен мат/спам (is_profane == true)
Note over Censor: Развилка Abuse_Check:<br/>Оценка count_violation из Redis
Censor->>K: Пуш в топик: bpds.aid.out.profanity.violate (Payload: violation_count)
end
%% СЦЕНАРИЙ Б: РАБОТА С ЧЕРНОВИКОМ
alt Валидация измененного контракта
User->>GW: POST /api/v1/purchase/draft-reconcile (Отредактированный руками текст)
Note over GW: Шаг 7: Валидация Pydantic контракта черновика
else Ошибка: Невалидный JSON / Сломан Pydantic контракт
GW-->>User: HTTP 400 Bad Request
end
GW->>K: Повторный пуш в топик: bpds.inventory.in.receipt.upload (Source: USER_EDIT)
K->>Censor: Повторный цикл цензуры
Note over Censor: Исход 8а: Отредактировано чисто
Censor->>K: Пуш в топик: bpds.inventory.out.receipt.parsed
POST /api/v1/fridge/items/upload-text
Метод ручного текстового ввода ресурсов. Документация микросервисов домена fridge
- INV-MIGRATION-120 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 Контракт и валидация данных
Этот документ описывает внутреннюю логику, последовательность синхронизации и спецификацию контракта для базового метода домена Purchase. Метод реализует логику добавления продуктов в холодильник на основе текстового ввода пользователя.
1.1 Функциональное назначение метода
Метод является входной точкой для добавления продуктов в холодильник, если пользователь не использует фото- или видео-сканирование чеков, другие способы, а самостоятельно вбивает наименование товара текстом в поисковую строку мобильного приложения.
Метод решает следующие критические задачи:
- Асинхронный аудит (NLP Check): бэкенд-шлюз перенаправляет сырую текстовую строку во внутренний микросервис
Censorship Serviceдля проверки на обсценную лексику (мат). - Контроль полноты обязательных данных: Валидационная модель проверяет наличие текстовой строки длиной не более 120 символов (примерно два или три продукта)
- Прямое добавление (Direct Update): В случае успешного прохождения ИИ-цензуры и валидации структуры, продукт добавляется в (холодильник) таблицу
fridge_inventoryконкретной группы, не сохраняясь в буферные таблицы чеков и модерации.
1.2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/fridge/items/upload-text - Формат данных:
application/json
1.2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token). Выделяет user_id, home_group_id и account_type. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для связывания логов ручного ввода с NLP-подсистемой | req-manual-text-99aa |
1.2.2 Спецификация тела запроса (Request Body)
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
raw_text_input |
String | Да | Текстовая строка, введенная пользователем вручную | Молоко Юрта в ауле 3.2% 1.5 л 1029 тенге |
1.2.2.1 Пример сырого JSON-запроса (Payload):
{
"raw_text_input": "Молоко Юрта в ауле 3.2% 1.5 л 1029 тенге"
}1.2.3 Диаграмма последовательности (Sequence Diagram)
1.3 Расшифровка шагов
| Шаг | Действие | Параметры / Запросы / DTO | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (Gateway -> T_Results) |
API-шлюз проверяет длину входящей строки (len(text) <= 120), обогащает метаданными локали приложения, пробрасывает трассировочный ID и публикует событие в брокер. |
Kafka Message (Topic: bpds.inventory.in.receipt.upload):Payload: { "raw_text_input": "Строка пользователя...", "app_lang": "ru-RU", "x_request_id": "req-censor-999-uuid" } |
FastAPI.ValidationError (строка > 120 символов)KafkaException: MessageTooLargeKafkaException: QueueFullException |
Шаг 2 (T_Results -> Censor) |
Фоновый воркер цензуры, подписанный на топик загрузки, вычитывает новую задачу из очереди для проведения лингвистического анализа. | Kafka Consumer Poll Request:Group_ID: "censorship-workers", Получен payload шага 1.Offset: 452091 |
KafkaException: CommitFailedException (воркер завис, сработал max.poll.interval.ms)SerializationException (битый json в пайлоаде) |
Шаг 3 (Censor -> Ollama) |
Воркер отправляет сырой текст в локальный ИИ-контейнер для семантического анализа и определения вероятности наличия скрытого спама, мата или оскорблений. | HTTP POST /api/generate (Ollama API):{ "model": "llama3", "prompt": "Analyze the following text for profanity, hate speech, or spam. Return exact JSON: { 'is_clean': boolean, 'confidence': float }. Text: 'Строка пользователя...'", "stream": false } |
ConnectTimeout: Ollama unreachableHTTP 500 Internal Server Error (сбой аллокации VRAM внутри контейнера Ollama)ReadTimeout: Inference duration exceeded |
Шаг 4 (Censor -> Redis) |
Параллельно с ИИ воркер проверяет текст по жестким локальным словарям стоп-слов, загруженным под конкретный язык, и выполняет условный инкремент счетчика для пользователя. | Redis Command Pipeline:1. SISMEMBER "dict:profanity:ru-RU" "входящее_слово"2. MULTI3. INCRBY "abuse:counter:user_123" 14. EXPIRE "abuse:counter:user_123" 864005. EXEC |
Redis.ConnectionError: Connection refusedRedis.TimeoutError: Command timed outRedis.ClusterDownException (сбой репликации или сегментации нод Redis памяти) |
Шаг 5а (Censor -> T_Clean_Text) |
Ветка «Успех»: Если стоп-слова не найдены, а ИИ выдал флаг чистоты с высокой степенью уверенности, текст отправляется в топик очищенных данных для дальнейшего парсинга. | Kafka Message (Topic: bpds.inventory.out.receipt.parsed):Payload: { "clean_text_input": "Строка пользователя...", "app_lang": "ru-RU", "x_request_id": "req-censor-999-uuid" } |
KafkaException: DeliveryTimeoutKafkaException: NotCoordinatorForException (временный ребаланс брокеров во время пуша) |
Шаг 5б (Censor -> T_Draft) |
Ветка «Подозрение»: Если локальные словари чисты, но ИИ сомневается в контексте (confidence < 0.85), воркер маркирует запись как сомнительный черновик для ручной модерации. |
Kafka Message (Topic: bpds.mdm.in.product.process):Payload: { "draft_data": { "raw_id": "uuid", "text": "Строка пользователя..." }, "app_lang": "ru-RU", "x_request_id": "req-censor-999-uuid" } |
KafkaException: LeaderNotAvailableExceptionKafkaException: RecordTooLargeException |
Шаг 5в (Censor -> Abuse_Check -> T_Notif) |
Первичное нарушение: Цензор обнаружил мат. Логический разветвитель проверяет счетчик в Redis. Если Счетчик == 1, генерируется событие первичного предупреждения с учетом языка. |
Kafka Message (Topic: bpds.aid.out.profanity.violate):Payload: { "user_id": "user_123", "violation_count": 1, "app_lang": "ru-RU", "notification_type": "FIRST_WARNING_PUSH" } |
KafkaException: BrokerNotAvailableKafkaException: MessageTimedOut (сбой подтверждения доставки от реплик брокера) |
Шаг 6 (T_Draft -> App) |
Мобильное приложение вычитывает из топика черновиков сомнительную запись и отображает её во вкладке модерации на родном языке пользователя для исправления. | gRPC / HTTP Stream (вкладка модерации):Payload: { "draft_id": "uuid-draft-555", "raw_text_input": "Сомнительный текст...", "app_lang": "ru-RU" } |
DioException: connection error (клиент потерял сеть)HTTP 401 Unauthorized (истекла сессия модератора в приложении) |
Шаг 7 (App -> T_Results) |
Пользователь (или модератор) отправляет отредактированную, очищенную версию текста обратно в систему под тем же сквозным языковым контекстом. | Kafka Message (Topic: bpds.inventory.in.receipt.upload):Payload: { "raw_text_input": "Отредактированный чистый текст", "app_lang": "ru-RU", "x_request_id": "req-censor-999-uuid" } |
KafkaException: QueueFullExceptionFastAPI.ValidationError (текст пустой или превысил лимит после редактирования) |
Шаг 8а (Censor -> T_Clean_Text) |
Успешный исход модерации: Повторный цикл проверки цензором подтверждает, что отредактированный текст полностью чист. Запись уходит в финальный топик. | Kafka Message (Topic: bpds.inventory.out.receipt.parsed):Payload: { "clean_text_input": "Отредактированный чистый текст", "app_lang": "ru-RU", "x_request_id": "req-censor-999-uuid" } |
KafkaException: DeliveryTimeoutKafkaException: ConcurrentModificationException |
Шаг 8б (Abuse_Check -> T_Notif) |
Повторный мат при модерации: Если в отредактированном тексте снова найден мат, разветвитель фиксирует Счетчик >= 2. Пользователь помечается как ненадежный. |
Kafka Message (Topic: bpds.aid.out.profanity.violate):Payload: { "user_id": "user_123", "violation_count": 2, "app_lang": "ru-RU", "notification_type": "BAN_OR_STRICT_ALERT" } |
KafkaException: LeaderNotAvailableExceptionInvalidTopicException (топик заблокирован администратором кафки) |
1.3.1 Диаграмма последовательности: (Этап 2 — парсинг текста и публикация сообщения потребителям)
sequenceDiagram
autonumber
participant K as Брокер: Apache Kafka Cluster
participant Worker as purchase-ollama-worker (AID)
participant Llama as Llama Container: NER Parsing (AID)
participant Billing as billing-service (INVENTORY)
participant Fridge as fridge-service (INVENTORY)
participant Bupar as bupar-audit-service (PMA)
%% ЭТАП 1: ИИ-РАСПОЗНАВАНИЕ И СИНТЕЗ JSON
K->>Worker: Шаг 1: Вычитка: bpds.inventory.out.receipt.parsed (Текст + app_lang)
activate Worker
note over Worker: Шаг 2: Адаптация системного промпта<br/>под локализацию app_lang
Worker->>Llama: Шаг 3: API: Запрос Structured Outputs (Pydantic-схема)
activate Llama
Llama-->>Worker: Шаг 4: Возврат структурированного JSON-массива продуктов
deactivate Llama
note over Worker: Шаг 5: Валидация паспорта расходов в памяти
Worker->>K: Шаг 6: Пуш в топик: bpds.mdm.out.product.templated
deactivate Worker
%% ЭТАП 2: ВЕЕРНАЯ АСИНХРОННАЯ ДИСТРИБУЦИЯ (FAN-OUT)
par Поток финансовой аналитики чека
K->>Billing: Шаг 7: Handler: ProcessBillingTransaction()
activate Billing
note over Billing: Шаг 7.1: Расчет сумм и shop_id
Billing->>Billing: Шаг 7.2: SQL: INSERT INTO billing_db
deactivate Billing
and Поток зачисления физической еды
K->>Fridge: Шаг 8: Handler: MutateFridgeBalances()
activate Fridge
note over Fridge: Шаг 8.1: Поштучный разбор граммов/литров
Fridge->>Fridge: Шаг 8.2: SQL: INSERT INTO fridge_db (FIFO Индекс)
deactivate Fridge
and Поток трекинга Process Mining
K->>Bupar: Шаг 9: Handler: AppendAuditLog()
activate Bupar
note over Bupar: Шаг 9.1: Запись шага 'PRODUCT_BATCH_PURCHASED'<br/>с индексом case_id
Bupar->>Bupar: SQL: INSERT INTO bupar_db
deactivate Bupar
end
1.3.2 Часть 2: Асинхронное NER-распознавание и веерная дистрибуция (Fan-Out Pipeline)
| Шаг | Действие | Параметры | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (KAFKA -> WORKER) |
ИИ-воркер purchase-ollama-worker вычитывает очищенный и проверенный текст чека из топика успешной цензуры. |
Топик: bpds.inventory.out.receipt.parsedContext: text, app_lang |
Kafka: RecordDeserializationException |
Шаг 2 (WORKER -> WORKER) |
Внутреннее действие: Воркер динамически адаптирует системный промпт LLM под переданный язык приложения (app_lang). |
Настройка контекста локализации промпта | TemplateRenderingError (ошибка синтаксиса промпта) |
Шаг 3 (WORKER -> LLAMA) |
Воркер отправляет запрос в контейнер Llama для извлечения сущностей (Named Entity Recognition). Используется режим JSON Structured Outputs. |
API Запрос с Pydantic-схемой ожидаемого массива продуктов | Ошибка LLM контейнера: Ollama: Timeout / OutOfMemoryДействие: Перенаправление чека в DLQ (Dead Letter Queue) |
Шаг 4 (LLAMA -> WORKER) |
Нейросеть возвращает строго валидированный, структурированный JSON-массив с распознанными продуктами, объемами и ценами. | Result: [{"name": "...", "weight_g": 500, "price": 120.0}, ...] |
ValidationError (если LLM нарушила Pydantic-контракт схемы) |
Шаг 5 (WORKER -> WORKER) |
Внутреннее действие: Воркер производит финальную валидацию сформированного паспорта расходов в оперативной памяти. | Проверка полей: validate_receipt_passport() |
InvalidPassportDataException |
Шаг 6 (WORKER -> KAFKA) |
Воркер публикует валидный паспорт чека в топик веерного распределения для транзакционных сервисов. | Топик: bpds.mdm.out.product.templatedPayload: Structured JSON Array |
Kafka: PreNotAvailable (брокер не отвечает) |
Шаг 7 (KAFKA -> BILLING) |
Параллельный поток 1 (Fan-Out): Сервис billing-service вычитывает паспорт чека для формирования финансовой аналитики. |
Хэндлер: ProcessBillingTransaction() |
Kafka: CommitFailedException |
Шаг 7.1 (BILLING -> BILLING) |
Внутреннее действие Billing: Сервис рассчитывает финальные суммы, налоги и сопоставляет идентификатор магазина (shop_id). |
Расчет транзакции | UnknownShopIdentifierException |
Шаг 7.2 (BILLING -> SQL) |
Финансовая транзакция атомарно записывается в базу данных биллинга. | SQL INSERT INTO billing_db (transaction_id, shop_id, total_amount) |
PostgreSQL: unique_violation (transaction_id) |
Шаг 8 (KAFKA -> FRIDGE) |
Параллельный поток 2 (Fan-Out): Сервис fridge-service параллельно забирает то же сообщение для зачисления физической еды пользователю. |
Хэндлер: MutateFridgeBalances() |
Kafka: MessageDeserializationError |
Шаг 8.1 (FRIDGE -> FRIDGE) |
Внутреннее действие Fridge: Сервис производит поштучный и пограммовый разбор позиций чека для инвентаризации холодильника. | Парсинг объемов (граммы / литры) | InvalidMeasurementUnitException |
Шаг 8.2 (FRIDGE -> SQL) |
Продукты записываются в базу данных холодильника с автоматическим расчетом и присвоением FIFO-индекса для контроля порчи. | SQL INSERT INTO fridge_db (product_id, quantity, fifo_index, expiry_date) |
PostgreSQL: lock_not_available (блокировка строки) |
Шаг 9 (KAFKA -> BUPAR) |
Параллельный поток 3 (Fan-Out): Сервис аудита процессов bupar-audit-service вычитывает событие для Process Mining трекинга. |
Хэндлер: AppendAuditLog() |
Kafka: BrokerTransportError |
Шаг 9.1 (BUPAR -> SQL) |
Сервис фиксирует прохождение этапа бизнес-процесса, записывая событие, привязанное к сквозному индексу цепочки. | SQL INSERT INTO bupar_db (case_id, activity, timestamp)Data: activity='PRODUCT_BATCH_PURCHASED' |
PostgreSQL: foreign_key_violation (case_id) |
1.4 Спецификация используемой СУБД-схемы склада (purchase_receipt_history)
Для фиксации результатов текстового ввода используется таблица продуктов в PostgreSQL.
CREATE TABLE purchase_receipts_history (
receipt_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Тот самый UUID чека из confirm
home_group_id VARCHAR(50) NOT NULL,
user_id UUID NOT NULL,
-- Сюда пишется итоговый валидированный JSON-пакет расходов со всеми позициями и ценами
receipt_payload JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
);1.5 Спецификация используемой СУБД-схемы склада (fridge_inventory)
Для фиксации результатов текстового ввода используется таблица продуктов в PostgreSQL.
CREATE TABLE fridge_inventory (
item_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Уникальный ID конкретного продукта в холодильнике
receipt_id UUID,
home_group_id VARCHAR(50) NOT NULL,
user_id UUID NOT NULL,
product_name VARCHAR(255) NOT NULL, -- Каноничное имя ("АПЕЛЬСИНЫ") или сорт ("Апельсины Испания")
quantity NUMERIC(10, 4) NOT NULL, -- Физический остаток данной партии (уменьшается при расходе)
unit VARCHAR(10) NOT NULL, -- кг, л, шт
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
);
-- Индекс для выгрузки актуального холодильника
CREATE INDEX idx_fridge_stock_group ON fridge_inventory(home_group_id);2 NEW Схема обработки запроса и интеграционный gRPC-контракт
2.1 Схема обработки запроса пользователя (Mermaid)
На диаграммах (Этап 1 и Этап 2) представлена асинхронная логика текстового добавления продуктов из чека. Бэкенд-шлюз FastAPI выполняет первичную валидацию длины строки, логирует бизнес-метрики сбоев через Promtail и мгновенно возвращает клиенту статус HTTP 202 Accepted, отправляя задачу в брокер сообщений Apache Kafka Cluster.
Вся тяжелая работа по оценке текста, обращению к LLM и дистрибуции данных изолирована в асинхронных воркерах: 1. censorship-control-worker производит проверку на спам и нецензурную лексику через локальную нейросеть Ollama, контролируя лимиты нарушений в кэше Redis по стратегии Fail-Close. 2. purchase-ollama-worker выполняет именованное распознавание сущностей (NER) через контейнер Llama в режиме Structured Outputs и запускает веерную рассылку (Fan-Out) готового паспорта чека в независимые СУБД биллинга, балансов холодильника (с FIFO-индексами) и логов процессного майнинга.
2.2 Спецификация интеграционного gRPC-контракта Цензуры (Внутренний Хелпер)
Описание структуры Protocol Buffers (censorship_control.proto) для внутреннего асинхронного взаимодействия воркера цензуры с локальным контейнером нейросети Ollama/Llama.
syntax = "proto3";
package security.censorship.v1;
service CensorshipControlService {
// Асинхронная оценка текста чека на ненормативную лексику и спам
rpc EvalTextConfidence (EvalTextConfidenceRequest) returns (EvalTextConfidenceResponse);
}
message EvalTextConfidenceRequest {
string text = 1; // Очищенный текстовый ввод из чека
string lang = 2; // Целевой язык локализации (app_lang) для адаптации промпта
}
message EvalTextConfidenceResponse {
bool is_profane = 1; // Флаг обнаружения мата, спама или запрещенного контента
float confidence_score = 2; // Уровень уверенности нейросети в результате (0.00 - 1.00)
}3 Спецификация ответов сервера, вилок исключений и таймаутов
3.1 Спецификация успешных ответов сервера (Success Response)
3.1.1 HTTP 202 Accepted (Ответ на Части А, Шаге 3)
Возвращается мобильному приложению шлюзом FastAPI Gateway мгновенно после успешной валидации длины строки и публикации сырого текста чека в топик брокера сообщений. Это означает, что запрос принят в асинхронный пайплайн обработки.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "RECEIPT_UPLOAD_ACCEPTED",
"data": {
"trace_id": "req-err-dir-99aa",
"message": "Текст чека успешно отправлен на цензурирование и NER-парсинг.",
"submitted_at": "2026-07-02T02:02:00Z"
}
}3.2 Спецификация ошибок и вилок исключений (Error Responses)
Все ошибки бэкенда возвращаются в едином стандартизированном формате RFC 7807 (Problem Details) или стандартном JSON-формате FastAPI HTTPException. Это позволяет Flutter-клиенту (Dio) однозначно парсить код ошибки и выводить корректный текст в модальных окнах или всплывающих уведомлениях.
3.2.1 Ошибка: Обнаружена обсценная лексика / Спам (Асинхронный Исход 5в / Шаг 9)
Формируется асинхронным воркером censorship-control-worker, если локальная нейросеть Ollama классифицировала текст чека как вредоносный (is_profane == true). Запрос терминируется, событие пушится в топик нарушений, а мобильное приложение получает уведомление.
{
"error_code": "ERR-PROFANITY-DETECTED",
"message": "Ввод отклонен: текст чека содержит нецензурную брань, оскорбления или недопустимый спам-контент.",
"details": {
"violation_count_current": 3,
"action": "Block further parsing pipeline, route incident to security log, and notify user to fix input."
}
}3.2.2 Ошибка валидации длины текста чека (HTTP 422 Unprocessable Entity — Часть А, Шаг 1.1)
Вызывается бэкенд-шлюзом FastAPI на самом раннем этапе до отправки в брокер, если сырая строка текста чека превышает лимит в 120 символов, что блокирует перегрузку контекста LLM. Лог ошибки параллельно уходит в Promtail.
{
"error_code": "IAD-VAL-422",
"message": "Превышена максимальная допустимая длина текстового ввода для распознавания чека.",
"details": [
{
"loc": ["body", "raw_text_input"],
"msg": "Длина текста чека должна быть строго меньше или равна 120 символам.",
"type": "value_error.string.max_length"
}
]
}3.3 Политика распределенных таймаутов и отказоустойчивости (Fail-Safe & Deadlines)
Для исключения каскадных сбоев, перегрузки памяти контейнеров ИИ, зависания топиков брокера Apache Kafka и предотвращения утечек данных, в рамках асинхронного пайплайна принудительно настраиваются следующие лимиты и режимы отказоустойчивости:
3.3.1 Взаимодействие на шлюзе и gRPC Deadlines (Таймауты инференса)
- Таймаут публикации в Kafka (Шлюз):
max.block.ms = 1000(1 секунда). Если за секунду шлюзFastAPIне смог опубликовать событие в топик первичного приема чеков, контур считает шину перегруженной и мгновенно возвращаетHTTP 503 Service Unavailable. - gRPC Deadline (Воркер -> Ollama):
timeout = 4.0s(4000 миллисекунд). Извлечение структурированных сущностей (NER) большой языковой моделью Llama требует времени на генерацию токенов. Ожидание инференса дольше 4 секунд интерпретируется как критическое зависание ИИ-контейнера.
3.3.2 Аварийная стратегия Fail-Safe и отказоустойчивость воркеров
Если асинхронные подсистемы вызывают сбои, поведение системы жестко разделяется по зонам ответственности (Безопасность против Бизнес-логики):
3.3.2.1 1. Зона безопасности: Взаимодействие Censor -> Redis Blacklist
- Применяется бескомпромиссная стратегия Fail-Close.
- Если база данных
Redisнедоступна (ошибка связи или таймаут подключенияConnectionRefusedError), воркер цензуры не имеет права рисковать периметром. - Цепочка обработки чека аварийно прерывается на Шаге 6. Запрос полностью аннулируется, а в лог
Promtailотправляется критический алерт безопасностиCRITICAL: Security subsystem failure.
3.3.2.2 2. Зона ИИ-аналитики: Взаимодействие Censor/Worker -> Ollama (Llama)
- Применяется стратегия Fail-Safe с переводом на ручной разбор (MDM Fallback).
- Если контейнер
Ollamaна Шаге 5 падает по таймауту 4 сек или выбрасывает сетевую ошибку, обработка чека не прерывается окончательно, чтобы не потерять данные пользователя. - Система перехватывает исключение и осуществляет аварийный сброс задачи в резервный топик модерации:
bpds.mdm.in.product.process. - Сырой текст чека транслируется через WebSocket на «Вкладку модерации» мобильного приложения, позволяя пользователю или оператору провести ручной разбор и канонизацию позиций без потери транзакции.