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 л Молока
Метод POST /api/v1/fridge/add-by-manual-text
Документация API: Ручное текстовое добавление продукта с интеграцией синхронного gRPC-контура NLP-цензуры и валидации
1 Функциональное назначение
Метод является главной входной точкой для ручного зачисления ресурсов на склад, когда пользователь не использует фото-сканирование чеков, а самостоятельно вбивает наименование товара текстом в поисковую строку мобильного приложения Flutter.
Метод решает три критические задачи:
- Синхронный gRPC-аудит (NLP Check): Транзакционный бэкенд-шлюз FastAPI перенаправляет сырую текстовую строку во внутренний микросервис
CheckProductдля проверки на обсценную лексику (мат) и сопоставления текстового ввода с Master Data справочниками. - Контроль полноты обязательных данных: Модель Pydantic жестко проверяет наличие физического объема/веса (
quantity > 0.0001) и соответствие единицы измерения (unit) каноническому системному перечню (кг, л, шт). Дробные float-значения весов поддерживаются нативно для точного зачисления. - Прямое зачисление (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.
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): Шлюз асинхронно публикует событие в топик Kafkabupar.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"
}
]
}