sequenceDiagram
autonumber
actor User as Пользователь (Экран Ручного ввода)
participant APP as Мобильное приложение (Flutter)
participant API as Бэкенд-шлюз (FastAPI)
participant CP as Сервис Цензуры (gRPC CensorshipService)
participant DB as Реляционная СУБД (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
User->>APP: Вводит в строку текст, нажимает "Добавить в холодильник"
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_category="Молоко")
deactivate CP
alt is_clean == false (Вилка: Мат обнаружен)
API-->>APP: HTTP 400 Bad Request (JSON с кодом ERR-PROFANITY-DETECTED)
else is_clean == true (Успешный контур)
Note over API, DB: Старт атомарной SQL-транзакции
API->>DB: INSERT INTO purchase_receipts_history...
API->>DB: INSERT INTO fridge_inventory...
activate DB
DB-->>API: Успешное выполнение INSERT (Запись добавлена/обновлена)
DB-->>API: Успешное выполнение INSERT (Запись добавлена/обновлена)
deactivate DB
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: send_to_kafka_async("bpds.fridge.events.manual_add")
API-->>APP: HTTP 201 Created (Продукт успешно добавлен на склад)
deactivate API
note over APP: Поля ввода очищаются, в списке холодильника появляется +1.5 л Молока
end
POST /api/v1/fridge/items/manual-text
Метод ручного текстового ввода ресурсов. Документация микросервисов домена fridge
- PURCHSASE-2XX Backlog — Краткое Описание задачи 1.
- PURCHSASE-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Контракт и валидация данных
Этот документ описывает внутреннюю логику, последовательность синхронизации и спецификацию контракта для базового метода домена Purchase. Метод реализует логику прямого синхронного добавления продуктов в холодильник на основе текстового ввода пользователя, обходя асинхронные цепочки отложенной модерации.
1.1 Функциональное назначение метода
Метод является главной входной точкой для добавления продуктов в холодильник, когда пользователь не использует фото- или видео-сканирование чеков, а самостоятельно вбивает наименование товара текстом в поисковую строку мобильного приложения FoodLifecycleApp.
Метод решает три критические задачи:
- Синхронный gRPC-аудит (NLP Check): Транзакционный бэкенд-шлюз
FastAPIперенаправляет сырую текстовую строку во внутренний микросервисCensorshipService(методCheckProduct/ValidateProductText) для проверки на обсценную лексику (мат) и сопоставления текстового ввода с Master Data справочниками. - Контроль полноты обязательных данных: Модель
Pydanticпроверяет наличие текстовой строки длиной не более 120 символов (примерно два или три продукта) - Прямое добавление (Direct Update): В случае успешного прохождения ИИ-цензуры и валидации структуры, продукт добавляется в (холодильник) таблицу
fridge_inventoryконкретной группы, не сохраняясь в буферные таблицы чеков и модерации. Также запись добавляется в таблицу чековpurchase_receipts_history
1.2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/fridge/items/manual-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.3 Спецификация используемой СУБД-схемы склада (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.4 Спецификация используемой СУБД-схемы склада (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 Схема обработки запроса и интеграционный gRPC-контракт
2.1 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена внутренняя логика текстового добавления продукта. Бэкенд-шлюз FastAPI выполняет синхронный gRPC-вызов к внутреннему NLP-микросервису цензуры для валидации на ненормативную лексику и сопоставления с глобальным каталогом, после чего производит атомарную операцию вставки в инвентарь PostgreSQL.
2.2 Расшифровка шагов пайплайна
Шаг 1 (
User -> APP): Пользователь на экране ручного импорта ресурсов вбивает текстовое наименование товара, указывает дробный объем1.5, единицу измерения например литр"л"и цену в одной строке. После завершения ввода он нажимает кнопку «Добавить в холодильник».Шаг 2 (
APP -> API): Мобильное приложение упаковывает структуру данных в 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 проверяет тело запроса на наличие обязательных полей. Если строкаraw_text_inputпередана пустой длина текста превышает 120 символов или количество товара меньше0.0001, бэкенд мгновенно прерывает выполнение операции.Шаг 5 (
API -> CP): Бэкенд-шлюз инициирует внутренний синхронный gRPC-вызов к ИИ-микросервису цензуры и сопоставления:gRPC ValidateProductText(raw_text_input=...). Передача данных происходит в бинарном формате по протоколу HTTP/2, что гарантирует минимальные задержки (менее 5 мс).Шаг 6 (
CP -> CP): МикросервисCensorshipServiceзапускает NLP-модель классификации текста (на базе трансформера BERT), проверяя строку на наличие нецензурной брани, мата, оскорблений или спама.Шаг 7 (
CP -> CP): В случае успешного прохождения цензуры, NLP-модель выполняет лемматизацию и нормализацию строки для сопоставления текста с глобальной Master Data базой (например, преобразует"Молоко Домик в деревне 3.2%"в каноническую категорию"Молоко").Шаг 8 (
CP -> API): Микросервис возвращает бэкенд-шлюзу gRPC-ответValidateProductTextResponse, содержащий флаг проверкиis_cleanи очищенное каноническое имя продуктаmatched_category.Шаг 9 (
API -> DB): Шлюз открывает атомарную SQL-транзакцию вPostgreSQLи выполняет команду вставки новой записи:INSERT INTO purchase_receipt_history (receipt_draft_id, user_id, home_group_id, activity, timestamp) VALUES (\$1, \$2, \$3, \$4, \$5)Шаг 10 (
DB -> API): База данных успешно фиксирует запись и возвращает подтверждение обновления строки. Транзакция закрывается командойCOMMIT.Шаг 11 (
API -> DB): Шлюз открывает атомарную SQL-транзакцию вPostgreSQLи выполняет команду вставки новой записи:INSERT INTO fridge_inventory (user_id, home_group_id, product_name, quantity, unit) VALUES (\$1, \$2, \$3, \$4, \$5)Шаг 12 (
DB -> API): База данных успешно фиксирует запись и возвращает подтверждение обновления строки. Транзакция закрывается командойCOMMIT.Шаг 13 (
API -> KAFKA): Шлюз асинхронно публикует событие в топик Kafkabpds.fridge.events.manual_add, извещая смежные BI-системы и рекомендательный контур аффинити (Affinity) о добавлении продукта в цифровой холодильник для корректировки весов предпочтений.Шаг 14 (
API -> APP): Сервер возвращает мобильному приложению статус успешного завершения операцииHTTP 201 Created. В мобильном приложении поля ввода очищаются, экран обновляется, и в списке продуктов холодильника отображается новая строка.
2.3 Спецификация интеграционного gRPC-контракта Цензуры (Внутренний Хелпер)
Описание структуры Protocol Buffers (censorship_integration.proto) для внутреннего синхронного взаимодействия шлюза с переиспользуемым NLP-микросервисом.
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 для вставки в БД
}3 Спецификация ответов сервера, вилок исключений и таймаутов
3.1 Спецификация успешных ответов сервера (Success Response)
3.1.1 HTTP 201 Created (Ответ на Шаге 12)
Возвращается мобильному приложению после успешного прохождения NLP-цензуры и фиксации Insert операции в реляционной базе данных инвентаря.
- Заголовки ответа (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"
}
}3.2 Спецификация ошибок и вилок исключений (Error Responses)
Все ошибки бэкенда возвращаются в едином стандартизированном формате RFC 7807 (Problem Details) или стандартном JSON-формате FastAPI HTTPException. Это позволяет Flutter-клиенту (Dio) однозначно парсить код ошибки и выводить корректный текст в модальных окнах.
3.2.1 Ошибка: Обнаружена обсценная лексика / Мат (HTTP 400 Bad Request — Шаг 8)
Вызывается если внутренний NLP-сервис классифицировал входящую строку как нецензурную брань (is_clean=false). Транзакция в СУБД не открывается, а Мобильное приложение выводит предупреждение (модалка).
{
"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."
}
}3.2.2 Ошибка валидации структуры и полноты данных (HTTP 422 Unprocessable Entity — Шаг 4)
Вызывается бэкенд-шлюзом FastAPI, если в поле количества передан нулевой или отрицательный объем, нарушающий физический смысл учета. Мобильное приложение выводит предупреждение (модалка)
{
"error_code": "ERR-VALIDATION-FAILED",
"message": "Переданный JSON-пакет содержит некорректный объем или неверный тип единицы измерения.",
"details": [
{
"loc": ["body", "quantity"],
"msg": "Физический объем ручного зачисления должен быть строго больше 0.0001",
"type": "value_error.number.not_gt"
}
]
}3.3 Политика распределенных таймаутов и отказоустойчивости (Fail-Safe & Deadlines)
Для исключения каскадных сбоев, забивания пула соединений Uvicorn/FastAPI и зависания пользовательского интерфейса, на стыке шлюза и ИИ-микросервиса принудительно настраиваются следующие лимиты:
3.3.1 gRPC Deadline (Таймаут соединения)
- Значение:
timeout = 0.5s(500 миллисекунд). - Обоснование: Передача бинарных фреймов по HTTP/2 внутри локального кластера занимает до 5 мс, а инференс легковесной NLP-модели BERT — до 40 мс. Ожидание ответа дольше 500 мс интерпретируется как деградация ИИ-контура.
3.3.2 Аварийная стратегия Fail-Safe (Режим ограниченного доверия)
Если CensorshipService вызывает статус StatusCode.UNAVAILABLE или падает по таймауту 500 мс, FastAPI перехватывает ошибку сети и переключает внутреннюю последовательность на резервный сценарий: 1. Операция не блокируется для конечного человека, чтобы сохранить работоспособность приложения (Offline-First опыт). 2. В системный лог пишется критическое предупреждение [Moderation Warning] для Grafana/ELK. 3. Бэкенд берет исходную сырую строку raw_text_input и выполняет аварийный INSERT в fridge_inventory напрямую без канонизации. Вместо системной категории записывается исходный текст пользователя, а количество выставляется из Pydantic-модели. 4. Пользователю гарантированно возвращается успешный ответ HTTP 201 Created.