Метод POST /api/v1/fridge/add-by-manual-text

Документация API: Ручное текстовое добавление продукта с интеграцией синхронного gRPC-контура NLP-цензуры и валидации

Published

July 2, 2026

1 Функциональное назначение

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

Метод решает три критические задачи:

  1. Синхронный gRPC-аудит (NLP Check): Транзакционный бэкенд-шлюз FastAPI перенаправляет сырую текстовую строку во внутренний микросервис CheckProduct для проверки на обсценную лексику (мат) и сопоставления текстового ввода с Master Data справочниками.
  2. Контроль полноты обязательных данных: Модель Pydantic жестко проверяет наличие физического объема/веса (quantity > 0.0001) и соответствие единицы измерения (unit) каноническому системному перечню (кг, л, шт). Дробные float-значения весов поддерживаются нативно для точного зачисления.
  3. Прямое зачисление (Direct Upsert): В случае успешного прохождения ИИ-цензуры и валидации структуры, продукт мгновенно добавляется в живой инвентарь fridge_inventory конкретной группы, минуя буферные таблицы чеков и модерации.

2 Протокол взаимодействия (HTTP Контракт)

  • Метод: POST
  • Маршрут: /api/v1/fridge/items/manual-text
  • Формат данных: application/json

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

2.2 Спецификация тела запроса (Request Body)

Поле Тип Обязательный Описание Пример значения
raw_text_input String Да Сырая текстовая строка, введенная пользователем вручную "Молоко Домик в деревне 3.2%"
quantity Float Да Точный физический float-объем, вес или штуки товара 1.5000
unit String Да Каноническая единица измерения из списка (кг, л, шт) "л"

2.2.1 Пример сырого JSON-запроса (Payload):

{
  "raw_text_input": "Молоко Домик в деревне 3.2%",
  "quantity": 1.5,
  "unit": "л"
}

3 Схема обработки запроса пользователя (Mermaid)

На диаграмме представлена логика ручного текстового добавления продукта. Бэкенд-шлюз выполняет синхронный gRPC-вызов к внутреннему NLP-микросервису CheckProduct для валидации на ненормативную лексику и сопоставления с глобальным каталогом, после чего производит атомарную операцию вставки в инвентарь PostgreSQL.

sequenceDiagram
    autonumber
    actor User as Пользователь (Экран Ручного ввода)
    participant APP as Мобильное приложение (Flutter)
    participant API as Бэкенд-шлюз (FastAPI)
    participant CP as Сервис Цензуры (gRPC CheckProduct)
    participant DB as Реляционная СУБД (PostgreSQL)
    participant KAFKA as Брокер событий (Kafka)

    User->>APP: Вводит текст, вес (1.5 л), нажимает "Добавить в холодильник"
    APP->>API: HTTP POST /api/v1/fridge/items/manual-text (JSON с raw_text_input)
    activate API
    
    note over API: Шаг 3: Декодирование JWT (Выделение home_group_id и user_id)
    note over API: Шаг 4: Первичная Pydantic-валидация полноты данных (quantity > 0)
    
    API->>CP: gRPC ValidateProductText(raw_text_input="Молоко Домик в деревне 3.2%")
    activate CP
    note over CP: Шаг 6: NLP-сканирование на мат и обсценную лексику
    note over CP: Шаг 7: Лемматизация и сопоставление с Master Data SKU
    CP-->>API: gRPC ValidateProductTextResponse(is_clean=true, matched_sku="Молоко")
    deactivate CP
    
    Note over API, DB: Старт атомарной SQL-транзакции
    API->>DB: INSERT INTO fridge_inventory ON CONFLICT (home_group_id, product_name, unit) DO UPDATE
    activate DB
    DB-->>API: Успешное выполнение Upsert (Запись добавлена/обновлена)
    deactivate DB
    Note over API, DB: Коммит атомарной транзакции (COMMIT)
    
    API->>KAFKA: send_to_kafka_async("fridge.events.manual_add")
    API-->>APP: HTTP 201 Created (Продукт успешно добавлен на склад)
    deactivate API
    note over APP: Поля ввода очищаются, в списке холодильника появляется +1.5 л Молока

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

  • Шаг 1 (User -> APP): Пользователь на экране ручного импорта ресурсов вбивает текстовое наименование товара (например, "Молоко Домик в деревне 3.2%"), указывает точный дробный объем 1.5 и выбирает единицу измерения "л". После завершения ввода он нажимает кнопку «Добавить в холодильник».

  • Шаг 2 (APP -> API): Мобильное приложение Flutter упаковывает структуру данных в JSON-тело и отправляет запрос HTTP POST /api/v1/fridge/items/manual-text. В заголовках передается обязательный Access Token сессии и сквозной идентификатор трассировки X-Request-ID.

  • Шаг 3 (API -> API): Бэкенд-шлюз FastAPI принимает входящий пакет, декодирует JWT-токен и извлекает параметры home_group_id и user_id. Метод универсален и работает как для контура HOME, так и для контура OFFICE.

  • Шаг 4 (API -> API): Валидация полноты минимальных данных: Модель Pydantic проверяет тело запроса на наличие обязательных полей. Если поле quantity имеет значение <= 0.0001 или строка raw_text_input передана пустой, бэкенд мгновенно прерывает выполнение операции, возвращая ошибку HTTP 422 Unprocessable Entity.

  • Шаг 5 (API -> CP): Бэкенд-шлюз инициирует внутренний синхронный gRPC-вызов к ИИ-микросервису цензуры и сопоставления: gRPC ValidateProductText(raw_text_input=...). Передача данных происходит в бинарном формате по протоколу HTTP/2, что гарантирует минимальные задержки (менее 5 мс).

  • Шаг 6 (CP -> CP): Микросервис CheckProduct запускает NLP-модель классификации текста (на базе предобученного трансформера BERT или регулярных выражений), проверяя сырую строку пользователя на наличие нецензурной брани, мата, оскорблений или спама.

  • Шаг 7 (CP -> CP): В случае успешного прохождения цензуры, NLP-модель выполняет лемматизацию и нормализацию строки для сопоставления текста с глобальной Master Data базой (например, преобразует "Молоко Домик в деревне 3.2%" в каноническую категорию "Молоко").

  • Шаг 8 (CP -> API): Микросервис возвращает бэкенд-шлюзу gRPC-ответ ValidateProductTextResponse, содержащий флаг успешной проверки is_clean: true и очищенное каноническое имя продукта.

    • Вилка исключений (Мат обнаружен): Если в тексте найден мат, сервис возвращает is_clean: false. Шлюз FastAPI прерывает выполнение и отдает во Flutter ошибку HTTP 400 Bad Request с кодом ERR-PROFANITY-DETECTED.
  • Шаг 9 (API -> DB): Шлюз открывает атомарную SQL-транзакцию в PostgreSQL и выполняет команду Upsert (вставка с обновлением при коллизии):

    INSERT INTO fridge_inventory (user_id, home_group_id, product_name, quantity, unit)
    VALUES (\$1, \$2, \$3, \$4, \$5)
    ON CONFLICT (home_group_id, product_name, unit) 
    DO UPDATE SET quantity = fridge_inventory.quantity + EXCLUDED.quantity;
  • Шаг 10 (DB -> API): База данных успешно фиксирует запись и возвращает подтверждение обновления строки. Транзакция закрывается командой COMMIT.

  • Шаг 11 (API -> KAFKA): Шлюз асинхронно публикует событие в топик Kafka bupar.fridge.events.manual_add, извещая смежные BI-системы и рекомендательный контур аффинити (Affinity) о ручном расширении состава холодильника для корректировки весов предпочтений.

  • Шаг 12 (API -> APP): Сервер возвращает мобильному приложению статус успешного завершения операции HTTP 201 Created. Во Flutter-приложении поля текстового ввода очищаются, экран обновляется, и в списке продуктов холодильника мгновенно отображается новая строка: Молоко — 1.5 л.

5 Спецификация ответов сервера (Response Body) и ошибок

5.1 Успешный ответ (Success Response)

5.1.1 HTTP 201 Created (Ответ на Шаге 12)

Возвращается мобильному приложению после успешного прохождения NLP-цензуры и фиксации Upsert-операции в реляционной базе данных инвентаря.

  • Заголовки ответа (Response Headers):
    • Content-Type: application/json
  • Тело ответа (Response Body):
{
  "status": "ITEM_MANUALLY_ADDED",
  "data": {
    "home_group_id": "group-772-alpha",
    "product_name": "Молоко",
    "added_quantity": 1.5,
    "unit": "л",
    "timestamp": "2026-07-02T02:02:00Z"
  }
}

5.2 Спецификация интеграционного gRPC-контракта Цензуры (Шаги 5–8)

Описание структуры Protocol Buffers (censorship_integration.proto) для внутреннего синхронного взаимодействия шлюза с NLP-микросервисом CheckProduct.

syntax = "proto3";

package masterdata.censorship.v1;

service CensorshipService {
  // Синхронная валидация ручного текстового ввода пользователя
  rpc ValidateProductText (ValidateProductTextRequest) returns (ValidateProductTextResponse);
}

message ValidateProductTextRequest {
  string raw_text_input = 1;
}

message ValidateProductTextResponse {
  bool is_clean = 1;         // Флаг отсутствия мата и обсценной лексики
  string matched_category = 2; // Каноническое имя SKU для вставки в БД
}

5.3 Вилки исключений и обработка ошибок

5.3.1 Ошибка: Обнаружена обсценная лексика / Мат (HTTP 400 Bad Request — Шаг 8)

Выбрасывается на Шаге 8, если внутренний NLP-сервис классифицировал входящую строку как нецензурную брань (is_clean=false). Транзакция в СУБД отменяется, а Flutter выводит предупреждение.

{
  "error_code": "ERR-PROFANITY-DETECTED",
  "message": "Ввод отклонен: текст содержит нецензурную брань, оскорбления или недопустимый спам-контент.",
  "details": {
    "raw_input_length": 27,
    "action": "Block input execution and prompt user to correct text naming clear of profanity."
  }
}

5.3.2 Ошибка валидации структуры и полноты данных (HTTP 422 Unprocessable Entity — Шаг 4)

Выбрасывается бэкенд-шлюзом FastAPI, если в поле количества передан нулевой или отрицательный float-объем, нарушающий физический смысл учета.

{
  "error_code": "ERR-VALIDATION-FAILED",
  "message": "Переданный JSON-пакет содержит некорректный объем или неверный тип единицы измерения.",
  "details": [
    {
      "loc": ["body", "quantity"],
      "msg": "Физический объем ручного зачисления должен быть строго больше 0.0001",
      "type": "value_error.number.not_gt"
    }
  ]
}