Метод POST /api/v1/recipes/import-qr

Документация API: Синхронный перехват внешнего рецептурного QR-кода, извлечение контента и запуск ИИ-сегментации кулинарной формулы

Published

July 3, 2026

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

Метод предназначен для бесшовного импорта кулинарных рецептов и комплексных меню во внутренний каталог пользователя. Он активируется при сканировании камерой мобильного приложения QR-кодов со страниц партнерских кулинарных книг, гастрономических блогов, упаковок полуфабрикатов или из печатных/электронных меню ресторанов.

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

  1. Валидация и парсинг внешнего источника: Шлюз проверяет входящую строку recipe_url на соответствие разрешенным внутренним префиксам или доверенным доменам интеграции (foodapp://recipe/, ://partner-chef.com).
  2. Извлечение неструктурированного контента (Scraping/Fetch): Микросервис осуществляет мгновенное скачивание текстового тела рецепта или состава ресторанного блюда по мета-ссылке из QR-кода.
  3. Инициализация контура ИИ-синтеза формулы: Текст передается в локальный или облачный LLM-контур для разложения на атомарные ингредиенты, сопоставления с Периодической таблицей продуктов и автоматического расчета итогового БЖУ.

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

  • Метод: POST
  • Маршрут: /api/v1/cookbook/import-qr
  • Формат данных: application/json

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

Заголовок Обязательный Описание Пример значения
Content-Type Да Указывает на передачу строго типизированного JSON-пакета application/json
Authorization Да Токен авторизации (Access Token). Выделяет user_id автора для сохранения в личный Cookbook. Bearer eyJhbGciOiJIUzI1Ni...
X-Request-ID Да Сквозной ID запроса для связывания сессии парсинга с логами ИИ-воркера req-recipe-qr-33cc

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

Поле Тип Обязательный Описание Пример значения
recipe_raw_url String Да Сырая ссылка, считанная сканером с внешнего рецептурного QR-кода "foodapp://recipe/pasta-carbonara"

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

{
  "recipe_raw_url": "foodapp://recipe/pasta-carbonara"
}

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

sequenceDiagram
    autonumber
    actor User as Пользователь (В ресторане или с книгой)
    participant APP as Мобильное приложение (Flutter)
    participant API as Бэкенд-шлюз (recipe-mdm-service)
    participant EXT as Внешний Сервер (Партнерский блог / CMS)
    participant AI as Сервис ИИ (gpt-4o / Local LLM)
    participant DB as Реляционная СУБД (PostgreSQL)

    User->>APP: Сканирует QR-код блюда в меню или книги рецептов
    APP->>API: HTTP POST /api/v1/cookbook/import-qr (JSON с recipe_raw_url)
    activate API
    
    note over API: Шаг 3: Декодирование JWT и авторизация сессии
    note over API: Шаг 4: Проверка домена и структуры ссылки по белому списку
    
    API->>EXT: HTTP GET /recipes/pasta-carbonara (Запрос сырого текста)
    activate EXT
    EXT-->>API: HTTP 200 OK (Сырой HTML/Текст: "Взять спагетти, гуанчале...")
    deactivate EXT
    
    API->>AI: Передача текста на деconstruction по Pydantic-схеме (Structured Outputs)
    activate AI
    note over AI: Выделение граммовок, шагов, сопоставление с Периодической Таблицей продуктов
    AI-->>API: Валидный JSON (Ингредиенты, веса, хронология, БЖУ)
    deactivate AI
    
    Note over API, DB: Старт атомарной SQL-транзакции
    API->>DB: INSERT INTO user_recipes (user_id, title, steps, chemical_graph) VALUES (...)
    activate DB
    DB-->>API: Успешная фиксация новой формулы в Cookbook
    deactivate DB
    Note over API, DB: Коммит атомарной транзакции (COMMIT)
    
    API-->>APP: HTTP 201 Created (Формула успешно импортирована и рассчитана)
    deactivate API
    note over APP: Экран обновляется, пользователь видит интерактивную карточку блюда с рассчитанным БЖУ

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

  • Шаг 1 (User -> APP): Пользователь, находясь в ресторане или читая партнерскую кулинарную книгу, наводит камеру мобильного приложения на рецептурный QR-код. Вкладка Cookbook или Meals инициализирует захват видеопотока и считывает сырую ссылку.

  • Шаг 2 (APP -> API): Приложение Flutter сериализует считанную строку в JSON-объект и отправляет запрос методом HTTP POST /api/v1/cookbook/import-qr на бэкенд-шлюз экосистемы.

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

  • Шаг 4 (API -> API): Контроль белого списка (Whitelist Check): Сервер валидирует регулярным выражением строку recipe_raw_url. Ссылка должна принадлежать либо внутреннему URI-протоколу foodapp://, либо списку официально авторизованных доменов ресторанных сетей и издательств.

  • Шаг 5 (API -> EXT): Микросервис recipe-mdm-service инициирует асинхронный исходящий HTTP-запрос к веб-серверу партнера или внутренней CMS для скачивания сырого содержимого (текстовое описание, HTML-код страницы или JSON-карточка блюда из меню).

  • Шаг 6 (EXT -> API): Внешний сервер отдает контент. Шлюз очищает полученный текст от рекламных тегов и скриптов, оставляя только содержательную кулинарную спецификацию.

  • Шаг 7 (API -> AI): Очищенный массив текста перенаправляется в LLM-контур. Запрос выполняется с применением технологии Structured Outputs: нейросеть обязана деконструировать вольный текст рецепта на жесткие реляционные узлы, перевести абстрактные меры (ложки, щепотки, чашки) в граммы/миллилитры и сопоставить продукты с ID Периодической таблицы.

  • Шаг 8 (AI -> API): ИИ возвращает строго структурированный JSON-паспорт блюда. Математический модуль бэкенда автоматически пересчитывает суммарное БЖУ и плотность калорий на основе весов ингредиентов.

  • Шаг 9 (API -> DB): Шлюз открывает атомарную SQL-транзакцию в PostgreSQL и фиксирует рецепт:

    INSERT INTO user_recipes (user_id, title, total_calories, macro_totals, steps_json, chemical_graph_json)
    VALUES (\$1, \$2, \$3, \$4, \$5, \$6)
    RETURNING id;
  • Шаг 10 (DB -> API): База данных подтверждает успешность записи. Изменения фиксируются командой COMMIT.

  • Шаг 11 (API -> APP): Сервер возвращает статус HTTP 201 Created и отдает клиенту готовую структурированную карточку. Экран Flutter обновляется, предоставляя пользователю возможность мгновенно запустить процесс приготовления через кнопку Cook или зачесть калории через кнопку Consume (для ресторанного меню).

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

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

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

Возвращается приложении сразу после завершения ИИ-деконструкции внешнего источника и записи рецепта в базу данных.

  • Заголовки ответа (Response Headers):
    • Content-Type: application/json
  • Тело ответа (Response Body):
{
  "status": "EXTERNAL_RECIPE_IMPORTED",
  "recipe_id": 44021,
  "title": "Паста Карбонара (Импорт)",
  "source_domain": "partner-chef.com",
  "nutritional_summary": {
    "calories_per_100g": 284.5,
    "proteins_g": 11.2,
    "fats_g": 14.8,
    "carbs_g": 26.3
  },
  "is_compatible_with_pantry": true
}

5.2 Спецификация JSON-Схемы импортируемого кулинарного графа (Шаг 8)

Схема данных, по которой ИИ-модель отдает деконструированную структуру рецепта бэкенду.

{
  "\$schema": "https://json-schema.org",
  "title": "ImportedRecipeGraph",
  "type": "object",
  "properties": {
    "title": {"type": "string"},
    "ingredients": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "mapped_element_id": {"type": "integer"},
          "weight_grams": {"type": "number"},
          "importance": {"type": "string", "enum": ["BASE", "OPTIONAL"]}
        },
        "required": ["mapped_element_id", "weight_grams", "importance"]
      }
    },
    "cooking_steps": {
      "type": "array",
      "items": {"type": "string"}
    }
  },
  "required": ["title", "ingredients", "cooking_steps"]
}

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

5.3.1 Ошибка: Ссылка ведет на незарегистрированный или опасный домен (HTTP 400 Bad Request — Шаг 4)

Выбрасывается на Шаге 4, если домен из QR-кода отсутствует в белом списке разрешенных партнеров интеграции.

{
  "error_code": "ERR-UNTRUSTED-RECIPE-SOURCE",
  "message": "Импорт заблокирован: указанный домен не сертифицирован для автоматического парсинга формул.",
  "details": {
    "requested_url": "https://malicious-site.net",
    "action": "Ensure you are scanning official QR codes from verified cookbooks or restaurant menus."
  }
}

5.3.2 Ошибка: Сбой парсинга структуры внешнего сайта (HTTP 422 Unprocessable Entity — Шаг 6)

Выбрасывается, если целевая страница изменила верстку или содержит пустой текст, из-за чего ИИ-контур не смог выделить логические блоки ингредиентов.

{
  "error_code": "ERR-RECIPE-DECONSTRUCTION-FAILED",
  "message": "Не удалось распознать структуру рецепта: текст не содержит четкого перечня ингредиентов.",
  "details": {
    "action": "The system failed to extract raw ingredient tables. Please insert this recipe text manually via Admin panel."
  }
}