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
- 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-интеграциями
1 Контракт и валидация данных
Этот документ описывает внутреннюю логику, последовательность синхронизации и спецификацию контракта для базового метода домена Purchase. Метод реализует логику добавления продуктов в холодильник на основе текстового ввода пользователя.
1.1 Функциональное назначение метода
Метод является главной входной точкой для добавления продуктов в холодильник, когда пользователь не использует фото- или видео-сканирование чеков, а самостоятельно вбивает наименование товара текстом в поисковую строку мобильного приложения FoodLifecycleApp.
Метод решает следующие критические задачи:
- Асинхронный аудит (NLP Check): Транзакционный бэкенд-шлюз
FastAPIперенаправляет сырую текстовую строку во внутренний микросервисCensorship Serviceдля проверки на обсценную лексику (мат) и сопоставления текстового ввода с Master Data справочниками. - Контроль полноты обязательных данных: Модель
Pydanticпроверяет наличие текстовой строки длиной не более 120 символов (примерно два или три продукта) - Прямое добавление (Direct Update): В случае успешного прохождения ИИ-цензуры и валидации структуры, продукт добавляется в (холодильник) таблицу
fridge_inventoryконкретной группы, не сохраняясь в буферные таблицы чеков и модерации. Также запись добавляется в таблицу чековpurchase_receipts_history
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 Расшифровка шагов
1.3.1 Часть А: Синхронный прием и валидация на шлюзе (Inbound API Контур)
| Шаг | Действие | Параметры | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (APP -> GW) |
Сетевой слой мобильного приложения отправляет сырой распознанный текст чека на шлюз. | HTTP POST /api/v1/purchase/upload-textBody: {"raw_text_input": "...", "app_lang": "ru"} |
DioException: connection timeout |
Шаг 1.1 (GW -> GW) |
FastAPI Gateway проверяет длину входящей строки. Если длина превышает лимит в 120 символов, шлюз инициирует прерывание. |
Валидация: len(raw_text_input) <= 120 |
Превышение длины: HTTP 422 Unprocessable EntityUI: Toast/Push «Упс! Что-то пошло не так…» |
Шаг 1.2 (GW -> PROMTAIL) |
В случае ошибки валидации шлюз генерирует бизнес-метрику сбоя в stdout stream. Promtail подхватывает и отправляет лог коллектору. |
JSON: {"trace_id": "X-Request-ID-12345", "domain": "INVENTORY", "error_code": "IAD-VAL-422", "error_type": "BUSINESS_VALIDATION_ERROR", "context": {"max_len": 120, "actual_len": 145}} |
Promtail: WriteLogException (лог теряется, но клиент уже получил 422) |
Шаг 2 (GW -> KAFKA) |
При успешной валидации шлюз пытается атомарно опубликовать событие в топик первичной обработки чеков брокера. | Топик: bpds.inventory.in.receipt.uploadPayload: raw_text_input, user_id |
Сбой брокера / Таймаут: HTTP 503 Service UnavailableUI: Toast/Push «Упс! Что-то пошло не так…» |
Шаг 3 (GW -> APP) |
Шлюз мгновенно возвращает клиенту статус принятия задачи в очередь, обрывая синхронное ожидание. | HTTP 202 AcceptedBody: {"status": "accepted", "trace_id": "..."} |
HTTP 500 Internal Server Error (ошибка сериализации ответа шлюза) |
1.3.2 Часть Б: Асинхронная обработка и цензурирование (Worker Pipeline)
| Шаг | Действие | Параметры | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 4 (KAFKA -> CENSOR) |
Фоновый воркер censorship-control-worker асинхронно вычитывает новые сообщения из брокера через внутренний хэндлер. |
Метод: ProcessUploadText() |
Kafka: CommitFailedException (повторная вычитка батча) |
Шаг 5 (CENSOR -> OLLAMA) |
Воркер отправляет текст на анализ в локальную нейросеть через gRPC для проверки на спам и нецензурную лексику. | gRPC: EvalTextConfidence(text, lang) |
Падение/Таймаут Ollama: Воркер делает Fallback и перенаправляет сырой чек на ручной разбор \(\rightarrow\) Пуш в bpds.mdm.in.product.process |
Шаг 6 (CENSOR -> REDIS) |
Воркер вызывает атомарный Lua-скрипт в Redis, инкрементируя счетчик нарушений пользователя и проверяя локальные словари. | Вызов: Redis.EVAL(lua_script, keys=[user_id]) |
Падение Redis (Connection Refused): Режим отказоустойчивости \(\rightarrow\) Игнорируем счетчик нарушений, воркер идет дальше по цепочке |
| Шаг 7 (Исход 5а) | Алгоритмическое ветвление: Если текст чист и уверенность ИИ высока, воркер отправляет успешный результат в топик парсинга инвентаря. | Топик: bpds.inventory.out.receipt.parsedУсловие: is_profane == false && Confidence >= 0.85 |
Kafka: BrokerNotAvailable |
| Шаг 8 (Исход 5б) | Алгоритмическое ветвление: Если текст чист, но ИИ сомневается, воркер пушит черновик в MDM-топик. Данные летят на модерацию. | Топик: bpds.mdm.in.product.processУсловие: is_profane == false && Confidence < 0.85 |
UI через WebSocket: Трансляция draft_data на «Вкладку модерации» для ручной правки |
| Шаг 9 (Исход 5в) | Алгоритмическое ветвление: Если обнаружен мат или спам, воркер отправляет инцидент в топик нарушений политики безопасности. | Топик: bpds.aid.out.profanity.violateУсловие: is_profane == truePayload: violation_count |
Kafka: MessageSizeTooLargeException |
1.3.3 Часть В: Сценарий Б (Работа пользователя с черновиком на модерации)
| Шаг | Действие | Параметры | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 10 (APP -> GW) |
Пользователь на «Вкладке модерации» редактирует текст руками и отправляет измененный черновик на повторную проверку. | HTTP POST /api/v1/purchase/draft-reconcileBody: {"draft_id": "...", "edited_text": "..."} |
DioException: send timeout |
Шаг 11 (GW -> GW) |
Шлюз проводит строгую валидацию измененного Pydantic-контракта черновика. | Валидация Pydantic-схемы | Сломан JSON/Контракт: HTTP 400 Bad Request |
Шаг 12 (GW -> KAFKA) |
Шлюз отправляет отредактированный текст в тот же входящий топик брокера, явно помечая источник изменений. | Топик: bpds.inventory.in.receipt.uploadMetadata: {"source": "USER_EDIT"} |
Сбой брокера: HTTP 503 Service Unavailable |
Шаг 13 (KAFKA -> CENSOR) |
Воркер повторно вычитывает сообщение и запускает цикл цензуры. Если текст отредактирован чисто, генерируется финальное событие. | Топик: bpds.inventory.out.receipt.parsedУсловие (Исход 8a): is_profane == false |
Kafka: TargetTopicPartitionLocked |
1.3.4 Диаграмма последовательности: (Этап 2 — NER-парсинг и Fan-Out распределение)
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.5 Часть Д: Асинхронное 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 на «Вкладку модерации» мобильного приложения, позволяя пользователю или оператору провести ручной разбор и канонизацию позиций без потери транзакции.