Метод POST /api/products

Документация API: Асинхронное зачисление и ИИ-генерация карточки элемента «Периодической системы продуктов» через событийно-ориентированный контур Kafka

Published

July 3, 2026

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

Метод является главной входной точкой для регистрации новых кулинарных ресурсов в экосистеме. Он обрабатывает текстовые запросы от мобильного приложения на Dart (Flutter) или текстовые строки, полученные в результате распознавания магазинных чеков (OCR).

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

  1. Быстрый неблокирующий прием (Async Acknowledge): Шлюз FastAPI мгновенно фиксирует факт запроса в базе данных со статусом PENDING, избавляя пользователя от необходимости ожидать завершения работы ИИ-генератора.
  2. Событийное квотирование нагрузки (Kafka Queueing): Задача инкапсулируется в сообщение и отправляется в брокер сообщений Kafka (Redpanda). Это защищает ИИ-воркеры от пиковых нагрузок и упорядочивает транзакции.
  3. Автоматическое ИИ-обогащение Master Data: Изолированный Python-контур (Consumer) перехватывает задачу, обращается к LLM по технологии Structured Outputs и сохраняет детальный физико-химический паспорт продукта в гибком поле JSONB базы данных PostgreSQL.

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

  • Метод: POST
  • Маршрут: /api/products
  • Формат данных: application/json

2.1 Спецификация заголовков (HTTP Headers)

Заголовок Обязательный Описание Пример значения
Content-Type Да Указывает на передачу строго типизированного JSON-пакета application/json
X-Request-ID Да Сквозной ID для трассировки логов между FastAPI и ИИ-воркером через Kafka req-atomic-egg-77bb

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

Поле Тип Обязательный Описание Пример значения
name String Да Сырая текстовая строка (наименование продукта или CV-тег) "Яйцо куриное С1"

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

{
  "name": "Яйцо куриное С1"
}

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

sequenceDiagram
    autonumber
    actor User as Пользователь (Экран добавления)
    participant APP as Мобильное приложение (Flutter)
    participant API as Бэкенд-шлюз (FastAPI)
    participant DB as Реляционная СУБД (PostgreSQL)
    participant KAFKA as Брокер событий (Kafka/Redpanda)
    participant WK as ИИ-Воркер (Python Consumer)
    participant AI as Сервис ИИ (OpenAI API gpt-4o)

    User->>APP: Вводит текст ("Яйцо"), нажимает "Добавить"
    APP->>API: HTTP POST /api/products (JSON с name)
    activate API
    
    note over API: Шаг 3: Первичная Pydantic-валидация и нормализация строки
    API->>DB: SELECT status FROM food_elements WHERE input_name = 'яйцо'
    activate DB
    DB-->>API: Элемент отсутствует (null)
    deactivate DB
    
    Note over API, DB: Старт атомарной SQL-транзакции
    API->>DB: INSERT INTO food_elements (input_name, status) VALUES ('яйцо', 'PENDING')
    activate DB
    DB-->>API: Успешная фиксация PENDING-записи
    deactivate DB
    Note over API, DB: Коммит атомарной транзакции (COMMIT)
    
    API->>KAFKA: Отправка сообщения в топик "product-generation"
    API-->>APP: HTTP 202 Accepted (Задача принята к исполнению)
    deactivate API
    note over APP: Пользователь видит продукт со статусом "Синтез карточки элемента..."
    
    %% Асинхронный контур
    activate WK
    KAFKA->>WK: Извлечение сообщения {"input_name": "яйцо"}
    note over WK: Шаг 11: Формирование строгого JSON-промпта с Pydantic-схемой
    WK->>AI: Запрос Structured Outputs (gpt-4o)
    activate AI
    AI-->>WK: Валидный JSON (Паспорт элемента Eg: БЖУ, валентность, фазы)
    deactivate AI
    
    WK->>DB: UPDATE food_elements SET chemical_card = JSONB, status = 'READY' WHERE input_name = 'яйцо'
    activate DB
    DB-->>WK: Успешное сохранение мастер-данных
    deactivate DB
    deactivate WK
    
    note over APP: При следующем пуллинге/обновлении экрана продукт переходит в статус READY с полной БЖУ-карточкой

4 Расшифровка шагов работы метода

  • Шаг 1 (User -> APP): Пользователь в интерфейсе приложения вбивает наименование нового продукта (например, "Креветки" или "Яйцо куриное"), который еще не заведен в глобальную периодическую систему продуктов, и нажимает кнопку «Добавить».

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

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

  • Шаг 4 (API -> DB): Шлюз отправляет быстрый индексный запрос в PostgreSQL для проверки наличия элемента.

    • Вилка кэширования (Элемент найден): Если элемент уже существует со статусом READY, база мгновенно возвращает готовое содержимое JSONB. Шлюз отдает его во Flutter со статусом HTTP 200 OK, завершая цепочку.
    • Вилка генерации (Элемент обратно отсутствует): Если элемент не найден, шлюз открывает атомарную транзакцию и выполняет первичный INSERT со статусом PENDING.
  • Шаг 5 (DB -> API): База данных успешно фиксирует запись-заглушку и возвращает подтверждение. Транзакция закрывается командой COMMIT.

  • Шаг 6 (API -> KAFKA): Шлюз (в роли Kafka Producer) асинхронно упаковывает задачу в JSON {"input_name": "креветки"} и публикует событие в брокер сообщений Kafka (топик product-generation).

  • Шаг 7 (API -> APP): Сервер мгновенно возвращает мобильному приложению статус успешной постановки в очередь HTTP 202 Accepted. Во Flutter-приложении элемент появляется в интерфейсе со специальным анимированным индикатором “Синтез карточки элемента…”.

  • Шаг 8 (KAFKA -> WK): Изолированный фоновый микросервис AI Worker (Kafka Consumer) вычитывает сообщение из топика product-generation для обработки. Разделение очередей защищает систему от превышения лимитов (Rate Limits) со стороны ИИ-провайдера.

  • Шаг 9 (WK -> WK): ИИ-Воркер формирует системный промпт и накладывает на него строгую структуру данных Pydantic (технология Structured Outputs), гарантируя получение валидной физико-химической схемы без риска галлюцинаций.

  • Шаг 10 (WK -> AI): Воркер инициирует HTTPS-запрос к API-модели (например, gpt-4o). Модель выполняет комплексный экспертный анализ продукта, рассчитывая БЖУ, группу, период, температурные переходы и кулинарную валентность.

  • Шаг 11 (AI -> WK): Нейросеть возвращает строго структурированный JSON-паспорт элемента, который нативно проходит валидацию через Pydantic-модель внутри воркера.

  • Шаг 12 (WK -> DB): Воркер открывает транзакцию в PostgreSQL и выполняет команду обновления данных:

    UPDATE food_elements 
    SET chemical_card = \$1, status = 'READY' 
    WHERE input_name = \$2;

    База сохраняет JSON-документ в поле JSONB, индекс триграмм обновляется. При следующем обновлении экрана (пуллинге) Flutter-приложение забирает из базы полностью сформированную карточку элемента READY.

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

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

5.1.1 HTTP 202 Accepted (Ответ на Шаге 7)

Возвращается мобильному приложению Flutter мгновенно, подтверждая успешный запуск асинхронной транзакции и публикацию задачи в Kafka.

  • Заголовки ответа (Response Headers):
    • Content-Type: application/json
  • Тело ответа (Response Body):
{
  "status": "PENDING",
  "message": "Product added to processing queue."
}

5.1.2 HTTP 200 OK (Прямой кэш-ответ на Шаге 4)

Возвращается, если продукт уже присутствует в глобальном справочнике. Поле chemical_card извлекается из JSONB-хранилища PostgreSQL.

{
  "status": "READY",
  "card": {
    "symbol": "Eg",
    "official_name": "Яйцо",
    "group_name": "Протеины",
    "period": 3,
    "calories_per_100g": 157.0,
    "proteins": 12.7,
    "fats": 11.5,
    "carbs": 0.7,
    "cooking_valence": 2,
    "chemical_props": {
      "phase_transitions": {
        "62C-65C": "Начало денатурации белка, потеря прозрачности",
        "65C-70C": "Загустевание желтка, сохранение пластичности",
        "above_80C": "Полное затвердевание. Риск выделения сероводорода"
      },
      "activators": ["Кислота (H+ для пашот-эффекта)"],
      "inhibitors": ["Жир (блокирует аэрацию белков)"]
    },
    "common_ocr_synonyms": ["яйцо", "яйца куриные", "egg", "яйцо кур с1"]
  }
}

5.2 Спецификация интеграционного gRPC-контракта Стриминга (Шаг 10)

Описание внутренней внутренней структуры Protocol Buffers (ai_generation.proto) для обмена данными между ИИ-воркером и LLM-шлюзом в структурированном JSON-режиме.

syntax = "proto3";

package masterdata.periodic.v1;

service FoodSynthesisService {
  // Получение строго типизированной карты химических свойств продукта
  rpc SynthesizeFoodCard (FoodSynthesisRequest) returns (FoodSynthesisResponse);
}

message FoodSynthesisRequest {
  string product_name = 1;
}

message FoodSynthesisResponse {
  string json_structured_output = 1; // Сериализованная Pydantic-модель MasterFoodCard
}

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

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

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

{
  "error_code": "ERR-VALIDATION-FAILED",
  "message": "Переданный JSON-пакет содержит пустое или некорректное имя продукта.",
  "details": [
    {
      "loc": ["body", "name"],
      "msg": "Поле наименования элемента не может быть пустой строкой",
      "type": "value_error.str.min_length"
    }
  ]
}

5.3.2 Ошибка краха ИИ-синтеза воркера (Фоновая фиксация — Шаг 12)

Если на Шаге 10 провайдер ИИ выдал ошибку (Rate Limit / Timeout) или Pydantic-валидатор воркера зафиксировал сбой структуры ответа, транзакция ИИ-коммита отменяется, а статус в БД переводится в аварийный режим.

{
  "error_code": "ERR-ATOMIC-SYNTHESIS-FAILED",
  "message": "Асинхронный синтез элемента прерван: ошибка генерации карточки ИИ-провайдером.",
  "details": {
    "input_name": "яйцо куриное с1",
    "status": "FAILED",
    "action": "Task put back in Dead Letter Queue for reprocessing or manual admin auditing."
  }
}