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: Экран обновляется, пользователь видит интерактивную карточку блюда с рассчитанным БЖУ
Метод POST /api/v1/recipes/import-qr
Документация API: Синхронный перехват внешнего рецептурного QR-кода, извлечение контента и запуск ИИ-сегментации кулинарной формулы
1 Функциональное назначение
Метод предназначен для бесшовного импорта кулинарных рецептов и комплексных меню во внутренний каталог пользователя. Он активируется при сканировании камерой мобильного приложения QR-кодов со страниц партнерских кулинарных книг, гастрономических блогов, упаковок полуфабрикатов или из печатных/электронных меню ресторанов.
Метод решает три критические задачи:
- Валидация и парсинг внешнего источника: Шлюз проверяет входящую строку
recipe_urlна соответствие разрешенным внутренним префиксам или доверенным доменам интеграции (foodapp://recipe/,://partner-chef.com). - Извлечение неструктурированного контента (Scraping/Fetch): Микросервис осуществляет мгновенное скачивание текстового тела рецепта или состава ресторанного блюда по мета-ссылке из QR-кода.
- Инициализация контура ИИ-синтеза формулы: Текст передается в локальный или облачный 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)
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."
}
}