POST /api/v1/fridge/items/upload-text

Метод ручного текстового ввода ресурсов. Документация микросервисов домена fridge

1 Контракт и валидация данных

Этот документ описывает внутреннюю логику, последовательность синхронизации и спецификацию контракта для базового метода домена Purchase. Метод реализует логику добавления продуктов в холодильник на основе текстового ввода пользователя.

1.1 Функциональное назначение метода

Метод является главной входной точкой для добавления продуктов в холодильник, когда пользователь не использует фото- или видео-сканирование чеков, а самостоятельно вбивает наименование товара текстом в поисковую строку мобильного приложения FoodLifecycleApp.

Метод решает следующие критические задачи:

  1. Асинхронный аудит (NLP Check): Транзакционный бэкенд-шлюз FastAPI перенаправляет сырую текстовую строку во внутренний микросервис Censorship Service для проверки на обсценную лексику (мат) и сопоставления текстового ввода с Master Data справочниками.
  2. Контроль полноты обязательных данных: Модель Pydantic проверяет наличие текстовой строки длиной не более 120 символов (примерно два или три продукта)
  3. Прямое добавление (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) обработки запроса пользователя

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/>&nbsp;&nbsp;"trace_id": "X-Request-ID-12345",<br/>&nbsp;&nbsp;"domain": "INVENTORY",<br/>&nbsp;&nbsp;"error_code": "IAD-VAL-422",<br/>&nbsp;&nbsp;"error_type": "BUSINESS_VALIDATION_ERROR",<br/>&nbsp;&nbsp;"user_id": "usr_9876",<br/>&nbsp;&nbsp;"message": "Length validation failed",<br/>&nbsp;&nbsp;"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

1.3 Расшифровка шагов

1.3.1 Часть А: Синхронный прием и валидация на шлюзе (Inbound API Контур)

Шаг Действие Параметры Ошибки (Исключения / Статусы)
Шаг 1 (APP -> GW) Сетевой слой мобильного приложения отправляет сырой распознанный текст чека на шлюз. HTTP POST /api/v1/purchase/upload-text
Body: {"raw_text_input": "...", "app_lang": "ru"}
DioException: connection timeout
Шаг 1.1 (GW -> GW) FastAPI Gateway проверяет длину входящей строки. Если длина превышает лимит в 120 символов, шлюз инициирует прерывание. Валидация: len(raw_text_input) <= 120 Превышение длины: HTTP 422 Unprocessable Entity
UI: 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.upload
Payload: raw_text_input, user_id
Сбой брокера / Таймаут: HTTP 503 Service Unavailable
UI: Toast/Push «Упс! Что-то пошло не так…»
Шаг 3 (GW -> APP) Шлюз мгновенно возвращает клиенту статус принятия задачи в очередь, обрывая синхронное ожидание. HTTP 202 Accepted
Body: {"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 == true
Payload: violation_count
Kafka: MessageSizeTooLargeException

1.3.3 Часть В: Сценарий Б (Работа пользователя с черновиком на модерации)

Шаг Действие Параметры Ошибки (Исключения / Статусы)
Шаг 10 (APP -> GW) Пользователь на «Вкладке модерации» редактирует текст руками и отправляет измененный черновик на повторную проверку. HTTP POST /api/v1/purchase/draft-reconcile
Body: {"draft_id": "...", "edited_text": "..."}
DioException: send timeout
Шаг 11 (GW -> GW) Шлюз проводит строгую валидацию измененного Pydantic-контракта черновика. Валидация Pydantic-схемы Сломан JSON/Контракт: HTTP 400 Bad Request
Шаг 12 (GW -> KAFKA) Шлюз отправляет отредактированный текст в тот же входящий топик брокера, явно помечая источник изменений. Топик: bpds.inventory.in.receipt.upload
Metadata: {"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

Процесс ИИ-распознавания (NER) и веерной асинхронной дистрибуции данных чека

1.3.5 Часть Д: Асинхронное NER-распознавание и веерная дистрибуция (Fan-Out Pipeline)

Шаг Действие Параметры Ошибки (Исключения / Статусы)
Шаг 1 (KAFKA -> WORKER) ИИ-воркер purchase-ollama-worker вычитывает очищенный и проверенный текст чека из топика успешной цензуры. Топик: bpds.inventory.out.receipt.parsed
Context: 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.templated
Payload: 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.2.3 Ошибка авторизации периметра (HTTP 401 Unauthorized — Триггер Interceptor)

Вызывается на шлюзе при любом входящем запросе, если Access Token просрочен, изменен злоумышленником или заблокирован в Redis Token Blacklist.

{
  "error_code": "IAD_JWT_ACCESS_EXPIRED",
  "message": "Срок действия токена доступа истек или сессия невалидна. Требуется обновление.",
  "details": {
    "action": "Trigger transparent token refresh pipeline using refresh_token underneath Dio Interceptor."
  }
}

3.2.4 Ошибка недоступности брокера сообщений или подсистем безопасности (HTTP 503 Service Unavailable)

Вызывается шлюзом FastAPI в двух случаях: если кластер Apache Kafka недоступен при попытке записи (Часть А, Шаг 2), либо если произошел критический сбой связи с Redis в режиме Fail-Close во время проверки черного списка.

{
  "error_code": "ERR-INFRASTRUCTURE-OFFLINE",
  "message": "Серверная подсистема временно недоступна. Пожалуйста, повторите попытку позже.",
  "details": {
    "reason": "Kafka broker transport timeout or Redis Security perimeter failure (Fail-Close enforcement)."
  }
}

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 на «Вкладку модерации» мобильного приложения, позволяя пользователю или оператору провести ручной разбор и канонизацию позиций без потери транзакции.