sequenceDiagram
autonumber
actor User as Пользователь (У кухонного стола)
participant APP as Мобильное приложение (Flutter)
participant API as Бэкенд-шлюз (fridge-inventory-service)
participant DB as Реляционная СУБД (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
User->>APP: Сканирует QR-код на контейнере с только что сваренным супом
APP->>API: HTTP POST /api/v1/fridge/scan-tag (JSON Payload)
activate API
note over API: Шаг 3: Декодирование JWT, извлечение home_group_id
note over API: Шаг 4: Проверка валидности префикса URL смарт-метки
API->>DB: Запрос биохимического срока годности из мастер-данных рецепта/продукта
activate DB
DB-->>API: Возврат базового срока годности (например, 72 часа при CHILLED)
deactivate DB
Note over API, DB: Старт атомарной SQL-транзакции (BEGIN)
API->>DB: INSERT INTO fridge_containers (tag_id, home_group_id, recipe_id, quantity, expires_at) VALUES (...) ON CONFLICT (tag_id) DO UPDATE
activate DB
DB-->>API: Успешный Upsert контейнера, запуск таймера хранения
deactivate DB
Note over API, DB: Коммит атомарной транзакции (COMMIT)
API->>KAFKA: Отправка события "fridge.container.bound" для пуш-нотификаций о порче
API-->>APP: HTTP 201 Created (Метка успешно привязана, контейнер в сети)
deactivate API
note over APP: Экран обновляется, контейнер №772 горит зеленым цветом, запущен таймер свежести
Метод POST /api/v1/fridge/scan-tag
Документация API: Синхронная авторизация смарт-метки контейнера, привязка кулинарного соединения и инициализация таймера деградации ресурса
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод является основным связующим звеном между физической посудой (контейнеры, вакуумные пакеты, зип-лок лотки) и цифровым инвентарем умного холодильника. Он активируется, когда пользователь сканирует камерой мобильного приложения многоразовый QR-стикер, наклеенный на емкость с едой.
Метод решает три критические задачи:
- Распознавание и валидация смарт-метки: Шлюз верхнего уровня проверяет сигнатуру QR-кода на принадлежность кодировки к домену
foodapp://container/. - Атомарное связывание ресурсов (Context Coupling): Метод привязывает уникальный физический ID контейнера к конкретному готовому блюду из справочника рецептов (
recipe_id) или сырому продукту, определяя точный объем (quantity). - Запуск таймера порчи (Shelf-life tracking): На основе биохимических свойств мастер-карточки продукта (из Периодической таблицы), система выставляет точное время «радиоактивного распада» (порчи) еды с учетом условий хранения (охлаждение или глубокая заморозка).
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/fridge/scan-tag - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token). Выделяет home_group_id для проверки прав на холодильник. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для трассировки логов сопоставления меток | req-smart-tag-22cc |
2.2 Спецификация тела запроса (Request Body)
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
tag_raw_url |
String | Да | Сырая ссылка, считанная сканером с QR-метки на контейнере | "foodapp://container/c-772-alpha" |
recipe_id |
Integer | Нет | ID привязываемого рецепта (если контейнер заполняется готовым блюдом) | 1042 |
quantity |
Float | Да | Физический объем или вес закладываемой в контейнер еды | 0.5000 |
storage_mode |
String | Да | Режим хранения в холодильнике (CHILLED — полка, FROZEN — морозилка) |
"CHILLED" |
2.2.1 Пример сырого JSON-запроса (Payload):
{
"tag_raw_url": "foodapp://container/c-772-alpha",
"recipe_id": 1042,
"quantity": 0.5,
"storage_mode": "CHILLED"
}3 Схема обработки запроса пользователя (Mermaid)
4 Расшифровка шагов работы метода
Шаг 1 (
User -> APP): Пользователь переливает приготовленное блюдо (например, Борщ) в физический контейнер, на который наклеен многоразовый QR-стикер. В модулеFridgeмобильного приложения он нажимает кнопку «Привязать метку», вводит вес блюда0.5кг, выбирает режим храненияCHILLEDи сканирует QR-код камерой.Шаг 2 (
APP -> API): Приложение Flutter упаковывает параметры в JSON-пакет и отправляет запросHTTP POST /api/v1/fridge/scan-tag. В заголовках передаетсяX-Request-IDи токен авторизации сессии.Шаг 3 (
API -> API): Бэкенд-шлюзFastAPIдекодирует JWT-токен, извлекаетhome_group_idи валидирует права пользователя на внесение изменений в инвентарь данной домашней или офисной группы.Шаг 4 (
API -> API): Проверка префикса сигнатуры: Метод проверяет структуру строкиtag_raw_url. Строка должна строго начинаться с внутреннего системного префиксаfoodapp://container/. Если префикс нарушен, запрос немедленно отклоняется.Шаг 5 (
API -> DB): Шлюз делает точечный запрос в PostgreSQL к таблице мастер-данных рецептов (master_recipes) или продуктов (food_elements), чтобы извлечь базовые физико-химические константы порчи для Борща.Шаг 6 (
DB -> API): База данных возвращает временной лимит деградации белковых соединений блюда. Для Борща при температуре+4°C(режимCHILLED) этот лимит составляет 72 часа. При режимеFROZEN(-18°C) система автоматически применила бы повышающий коэффициент, увеличив срок до 30 дней.Шаг 7 (
API -> API): Сервер берет текущую метку времени (current_timestamp) и математически рассчитывает абсолютную точку порчи:expires_at = current_timestamp + 72 hours.Шаг 8 (
API -> DB): Шлюз открывает атомарную SQL-тран транзакцию вPostgreSQLи выполняет командуUpsertдля таблицы физических смарт-меток:INSERT INTO fridge_containers (tag_id, home_group_id, recipe_id, initial_quantity, current_quantity, storage_mode, expires_at, status) VALUES (\$1, \$2, \$3, \$4, \$4, \$5, \$6, 'ACTIVE') ON CONFLICT (tag_id) DO UPDATE SET recipe_id = EXCLUDED.recipe_id, current_quantity = EXCLUDED.current_quantity, storage_mode = EXCLUDED.storage_mode, expires_at = EXCLUDED.expires_at, status = 'ACTIVE', updated_at = NOW();Шаг 9 (
DB -> API): База успешно перезаписывает состояние многоразовой метки и возвращает подтверждение транзакции. Выполняется командаCOMMIT.Шаг 10 (
API -> KAFKA): Шлюз асинхронно публикует событие в топик Kafkabupar.fridge.events.container_bound. Данное сообщение перехватывается микросервисом уведомлений (notification-service) для планирования пуш-нотификации на смартфоны членов группы за 12 часов до истечения срока годности Борща.Шаг 11 (
API -> APP): Сервер возвращает статусHTTP 201 Createdи полную карточку привязанного смарт-контейнера. Во Flutter-приложении лоток визуализируется в активной цифровой сетке холодильника с таймером обратного отсчета времени свежести.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 Успешный ответ (Success Response)
5.1.1 HTTP 201 Created (Ответ на Шаге 11)
Возвращается мобильному приложению после успешного связывания физического контейнера с кулинарным рецептом и расчета даты порчи.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "CONTAINER_TAG_BOUND",
"data": {
"tag_id": "c-772-alpha",
"home_group_id": "group-993-beta",
"content": {
"recipe_id": 1042,
"name": "Борщ Домашний",
"quantity_kg": 0.5
},
"storage": {
"mode": "CHILLED",
"hours_remaining": 72.0,
"expires_at": "2026-07-06T18:57:00Z"
}
}
}5.2 Спецификация JSON-Схемы состояния смарт-контейнера (Шаг 11)
Внутренняя структура валидации объекта контейнера, используемая для отрисовки динамических карточек на экранах Flutter.
{
"\$schema": "https://json-schema.org",
"title": "SmartContainerState",
"type": "object",
"properties": {
"tag_id": {"type": "string"},
"recipe_id": {"type": ["integer", "null"]},
"current_quantity": {"type": "number"},
"storage_mode": {"type": "string", "enum": ["CHILLED", "FROZEN"]},
"expires_at": {"type": "string", "format": "date-time"},
"status": {"type": "string", "enum": ["ACTIVE", "EMPTY", "SPOILED"]}
},
"required": ["tag_id", "current_quantity", "storage_mode", "expires_at", "status"]
}5.3 Вилки исключений и обработка ошибок
5.3.1 Ошибка: Сканирование чужого или невалидного QR-кода (HTTP 400 Bad Request — Шаг 4)
Выбрасывается на Шаге 4, если считанная ссылка ведет на сторонний домен и не распознается внутренней системой кухонной разметки.
{
"error_code": "ERR-INVALID-CONTAINER-TAG",
"message": "Ввод отклонен: отсканированный QR-код не является сертифицированной смарт-меткой экосистемы.",
"details": {
"scanned_payload": "https://wikipedia.org",
"action": "Please scan only the dedicated reusable smart-stickers generated within your app profile."
}
}5.3.2 Ошибка: Попытка привязать контейнер к чужой группе (HTTP 403 Forbidden — Шаг 3)
Выбрасывается, если многоразовый контейнер в данный момент жестко закреплен за другой семьей/офисом (home_group_id в базе не совпадает с токеном), и не был предварительно очищен (списан через Consume или Waste).
{
"error_code": "ERR-CONTAINER-OWNED-BY-OTHER-GROUP",
"message": "Данный контейнер занят инвентарем другой группы. Изменение заблокировано.",
"details": {
"tag_id": "c-772-alpha",
"action": "The current holding group must empty or de-register this smart tag before it can be shared."
}
}6 Расшифровка шагов работы метода
Шаг 1 (
User -> APP): Пользователь берет с полки фабричный маркированный товар (например, пачку творога) и наводит камеру мобильного приложения на штрихкод EAN-13 или квадратный код маркировки DataMatrix. МодульPantryзахватывает картинку и декодирует её в сырую строку данных.Шаг 2 (
APP -> API): Приложение Flutter отправляет запрос методомHTTP POST /api/v1/pantry/scan-barcodeна бэкенд-шлюз. В теле запроса передается извлеченная символьная последовательностьbarcode_raw_string.Шаг 3 (
API -> API): Бэкенд-шлюзFastAPIдекодирует JWT-токен, извлекая идентификаторы сессии группы. Проверяется валидность токена и права доступа пользователя на модификацию складских остатков.Шаг 4 (
API -> API): Выделение GTIN: Специализированный парсер анализирует строку. Если отсканирован код DataMatrix, система вычленяет из него глобальный номер товарной продукции GTIN (первые 14 цифр после префикса применения01). Если это стандартный штрихкод, строка сразу преобразуется в чистый 13-значный EAN-код.Шаг 5 (
API -> DB): Шлюз выполняет высокоскоростной индексный запрос в локальную базу данных PostgreSQL, проверяя таблицуproduct_barcodes. Если данный GTIN уже сканировался кем-то в системе ранее, база мгновенно возвращает кэшированную связь с элементом Периодической таблицы (element_id), минуя внешние шлюзы (переход к Шагу 11).Шаг 6 (
API -> REG): В случае отсутствия записи в локальном кэше (Cache Miss), шлюз инициирует синхронный защищенный запрос к API национальной системы маркировки («Честный Знак», ГС1 РУС) для получения официального реестрового паспорта товара.Шаг 7 (
REG -> API): Сервер прослеживаемости возвращает верифицированную карточку SKU, содержащую бренд, каноническое наименование товара и точный вес нетто (например,200 г).Шаг 8 (
API -> DB): Шлюз открывает атомарную SQL-транзакцию вPostgreSQL. Запускается внутренний триграммный поиск для автоматического сопоставления текстового имени из реестра (например,"Творог рассыпчатый 5% 200г") с базовым элементом таблицы продуктов — изотопом творогаCc(Лакто-элементы).Шаг 9 (
API -> DB): Выполняется команда записи нового маппинга для предотвращения повторных внешних сетевых запросов:INSERT INTO product_barcodes (gtin, element_id, manufacturer_sku_name, standard_weight_g) VALUES (\$1, \$2, \$3, \$4) ON CONFLICT (gtin) DO NOTHING;Шаг 10 (
DB -> API): База данных подтверждает запись связей. Транзакция закрывается командойCOMMIT.Шаг 11 (
API -> APP): Сервер возвращает статусHTTP 200 OK(или201 Createdпри первой регистрации товара) и полную физико-химическую карточку изотопа. Во Flutter-приложении поля веса заполняются автоматически на основе данных производителя (200 г), а продукт привязывается к своей ячейке в Периодической системе.
7 Спецификация ответов сервера (Response Body) и ошибок
7.1 Успешный ответ (Success Response)
7.1.1 HTTP 200 OK (Ответ на Шаге 11 — Успешное распознавание)
Возвращается приложению, когда товар успешно идентифицирован в базе или во внешнем реестре и привязан к элементу периодической системы.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "BARCODE_IDENTIFIED",
"gtin": "04601234567890",
"product_details": {
"brand": "Простоквашино",
"commercial_name": "Творог рассыпчатый 5%",
"packaged_weight_g": 200.0
},
"mapped_periodic_element": {
"symbol": "Cc",
"element_name": "Творог",
"group_name": "Лакто-Элементы",
"macros_per_100g": {
"calories": 121.0,
"proteins": 16.5,
"fats": 5.0,
"carbs": 3.0
}
}
}7.2 Спецификация JSON-Схемы паспорта товара из Реестра Маркировки (Шаг 7)
Структура данных, возвращаемая государственным/международным API-шлюзом прослеживаемости товаров.
{
"\$schema": "https://json-schema.org",
"title": "NationalRegistrySkuPassport",
"type": "object",
"properties": {
"gtin": {"type": "string"},
"product_name_ru": {"type": "string"},
"brand_name": {"type": "string"},
"net_mass_grams": {"type": "number"},
"inner_contents_category": {"type": "string"}
},
"required": ["gtin", "product_name_ru", "net_mass_grams"]
}7.3 Вилки исключений и обработка ошибок
7.3.1 Ошибка: Сканируемый штрихкод поврежден или имеет неверный формат (HTTP 400 Bad Request — Шаг 4)
Выбрасывается на Шаге 4, если считанная лазером/камерой строка не проходит базовую валидацию контрольной суммы EAN-13 или содержит некорректные спецсимволы GS1.
{
"error_code": "ERR-INVALID-BARCODE-STRUCTURE",
"message": "Ввод отклонен: отсканированная последовательность знаков повреждена или не является валидным кодом EAN/DataMatrix.",
"details": {
"raw_length": 8,
"action": "Ensure the barcode on the package is clean, well-lit, and scan the code completely."
}
}7.3.2 Ошибка: Товар отсутствует во всех национальных базах данных (HTTP 404 Not Found — Шаг 6)
Выбрасывается, если GTIN штрихкода является корректным, но данный продукт импортирован неофициально или выпущен мелким локальным фермерским хозяйством, из-за чего он отсутствует в Честном Знаке и базах GS1. Приложение предложит занести продукт вручную.
{
"error_code": "ERR-SKU-NOT-FOUND-IN-REGISTRY",
"message": "Штрихкод отсутствует в глобальных справочниках маркировки. Перенаправление на ручной ввод.",
"details": {
"unknown_gtin": "04601234567890",
"action": "Redirecting user to manual text entry screen to manually map this new product to a Periodic Element."
}
}