sequenceDiagram
autonumber
actor User as Пользователь (Экран Waste)
participant APP as Мобильное приложение (Flutter)
participant WS as Сервис Утилизации (FastAPI)
participant DB as База Данных (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
User->>APP: Вводит объем утилизации (0.5 кг) и причину, жмет "Списать"
APP->>WS: HTTP POST /api/v1/waste/items (JSON с fridge_item_id и весом)
activate WS
note over WS: Шаг 3: Декодирование JWT (Выделение HOME или OFFICE)
Note over WS, DB: Старт атомарной SQL-транзакции
WS->>DB: SELECT quantity FROM fridge_inventory WHERE item_id = fridge_item_id FOR UPDATE
activate DB
DB-->>WS: Возвращает текущий остаток на складе (например, 0.0 кг)
deactivate DB
alt Физического остатка ХВАТАЕТ
WS->>DB: UPDATE fridge_inventory SET quantity = quantity - waste_quantity
WS->>DB: INSERT INTO waste_history_log (Фиксация чистого акта мусора)
else Физического остатка НЕ ХВАТАЕТ / Остаток = 0
alt Контекст аккаунта == OFFICE
WS-->>APP: HTTP 400 Bad Request (ERR-WASTE-STOCK-SHORTAGE)
else Context аккаунта == HOME
note over WS: Шаг 9: Активация теневого лога (Генерация correlation_id)
WS->>DB: INSERT INTO waste_history_log (Запись акта утилизации с correlation_id)
WS->>DB: INSERT INTO fridge_shadow_debts (Фиксация задолженности по вводу веса)
end
end
Note over WS, DB: Коммит атомарной транзакции (COMMIT)
WS->>KAFKA: send_to_kafka_async("bupar.fridge.events.waste")
WS-->>APP: HTTP 200 OK (status: "WASTE_RECORDED", correlation_id="uuid-abc")
deactivate WS
note over APP: Экран обновляется, в реестре долгов появляется строка для ручного редактирования
Метод POST /api/v1/waste/items
Документация API: Регистрация утилизации продуктов (Waste Control) с поддержкой изолированных задолженностей
- IAD-MIGRATION-116 Backlog — Краткое Описание задачи 1.
- IAD-MIGRATION-117 Refinement (Уточнение) — Краткое Описание задачи 2.
- GW-1 Ready for Development — Подключение эндпоинта в шлюзе.
- AID-012 Ready for Development — создать метож ProcessVoiceStream.
- AID-013 Ready for Development — создать метож ProcessUploadText.
- INV-FRONTEND-222 Refinement (Уточнение) — Экран управления B2B-интеграциями
- INV-FRONTEND-223 Refinement (Уточнение) — Экран управления B2B-интеграциями
1 Функциональное назначение
Метод предназначен для фиксации факта порчи, сгорания или утилизации продуктов питания и дробных порций блюд в приложении.
Метод решает три задачи:
- Изменение количества продуктов в холодильнике: Уменьшает баланс выбранного
fridge_item_idна точный float-объем утилизации, поддерживая любые единицы измерения системы (кг, литры, шт) со скриншота интерфейса. - Выбор причин (Waste Control): Фиксирует причину списания («пропало», «истек срок», «сгорело», «не понравилось») для последующего анализа финансового и продуктового контекста командой MLOps.
- Обслуживание теневой экономики (Isolated Shadow Debt Ledger):
- В контексте
OFFICEдефицит запрещен. При нехватке веса возвращается блок. - В контексте
HOME, если пользователь выбрасывает продукт, которого технически нет на балансе (забыл внести чек ранее), система не уводит физический склад в минус (остатокquantityв холодильнике фиксируется на0.0). Вместо этого метод создает запись в изолированной таблице долговfridge_shadow_debtsи генерирует уникальныйcorrelation_id(UUID). Это позволяет пользователю позже, находясь во вкладке долгов, вручную привязать выброшенный продукт к новому чеку и скорректировать вес.
- В контексте
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
POST - Маршрут:
/api/v1/waste/items - Формат данных:
application/json
2.1 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Указывает на передачу строго типизированного JSON-пакета | application/json |
Authorization |
Да | Токен авторизации (Access Token). Выделяет home_group_id и account_type. |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для распределенного логирования | req-waste-register-11aa |
2.2 Спецификация тела запроса (Request Body)
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
fridge_item_id |
Integer | Да | ID списываемой записи инвентаря склада | 1042 |
waste_quantity |
Float | Да | Точный float-объем или порция, уходящая в мусор | 0.3500 |
waste_reason |
String (Enum) | Да | Каноничный код причины утилизации | "REASON_SPOILED" |
2.2.1 Перечень допустимых констант waste_reason (Enum):
REASON_SPOILED— пропало / сгнило.REASON_EXPIRED— истек срок годности.REASON_BURNT— сгорело при готовке.REASON_DISLIKED— не понравилось по вкусу.
2.2.2 Пример JSON-запроса (Payload — Сценарий: выбросили 0.350 кг испортившихся яблок при их нулевом остатке в приложении):
{
"fridge_item_id": 1042,
"waste_quantity": 0.350,
"waste_reason": "REASON_SPOILED"
}3 Диаграмма последовательности (Mermaid)
На диаграмме представлена логика фиксации утилизации. Если физического продукта на складе не хватает (экономика контура HOME), система сохраняет остаток холодильника равным 0.0, но генерирует сквозной correlation_id и фиксирует долг в изолированной таблице fridge_shadow_debts для последующего ручного редактирования пользователем.
4 Расшифровка шагов
| Шаг | Действие | Параметры / Запросы / DTO | Ошибки (Исключения / Статусы) |
|---|---|---|---|
Шаг 1 (App -> Nginx) |
Пользователь отправляет форму утилизации с указанием списываемых продуктов, типа пространства и причины списания (например, испортилось/пропало). | HTTP POST /api/v1/waste/itemsHeaders: Authorization: Bearer <JWT>Payload ( CreateWasteItemsRequestDTO):{ "space_type": "OFFICE", "spent_items": [{ "product_id": "prod-waste-uuid", "qty": 0.8 }], "waste_reason": "SPOILT" } |
DioException: send timeoutHTTP 400 Bad Request (передана невалидная структура JSON или неверный enum причины)HTTP 401 Unauthorized |
Шаг 2 (Nginx -> Gateway) |
Прокси-сервер выполняет базовую маршрутизацию на внутренний шлюз и осуществляет инъекцию сквозного трассировочного ID сессии. | HTTP POST (Внутреннее проксирование на FastAPI Gateway)Added Headers: X-Request-ID: "trace-waste-control-uuid" |
HTTP 502 Bad Gateway (контейнер backend-api API Gateway недоступен или перегружен)HTTP 504 Gateway Timeout |
Шаг 3 (Gateway -> Waste) |
API-шлюз валидирует параметры запроса через Pydantic-схему WasteItemsValidator и транслирует контракт во внутренний сервис утилизации. |
HTTP POST /internal/v1/waste/itemsHeaders: X-Request-ID: "trace-waste-control-uuid", Body: CreateWasteItemsRequestDTO |
HTTP 422 Unprocessable EntityPayload: ValidationErrorResponseDTOerror_code: "WASTE_VALIDATION_FAILED" |
Шаг 4 (Waste -> Fridge) |
Сервис отходов формирует высокопроизводительный gRPC-вызов к контуру холодильника для выполнения пакетной мутации остатков испорченных продуктов. | gRPC Запрос:mutateBalances(BalanceMutationRequest)Payload: spent_items: [{ "product_id": "prod-waste-uuid", "qty": 0.8 }], space_type: "OFFICE" |
gRPC Status: UNAVAILABLE (сервис fridge-service недоступен)gRPC Status: DEADLINE_EXCEEDED (таймаут обработки транзакции списания) |
Шаг 5 (Fridge -> Space_Check) |
Сервис холодильника принимает gRPC-запрос на списание в отходы и запускает внутреннее ветвление бизнес-логики на основе параметра space_type. |
Внутренний селектор стратегий списания остатков. Payload: spent_items: [...], space_type: "OFFICE\|HOME" |
RuntimeError: InvalidSpaceType (в коде приложения передан или не обработан неизвестный тип пространства) |
5 Примеры ответов сервера (Response Body)
5.1 1. Успешные ответы сервера (Success Responses)
5.1.1 HTTP 200 OK (Сценарий А: Физического остатка хватило)
Возвращается, если на складе было достаточное количество товара. Баланс в холодильнике уменьшился, запись ушла в чистую историю мусора.
{
"status": "WASTE_RECORDED",
"data": {
"fridge_item_id": 1042,
"mutated_quantity": 0.3500,
"remaining_quantity": 0.6500,
"unit": "кг",
"shadow_debt_generated": false,
"correlation_id": null,
"timestamp": "2026-07-02T01:12:00Z"
}
}5.1.2 HTTP 200 OK (Сценарий Б: Активация теневой экономики / Дефицит веса в HOME)
Возвращается в контуре HOME, если товара не хватило. Физический баланс холодильника заморожен на 0.0, но сгенерирован correlation_id и открыта изолированная строка долга.
{
"status": "WASTE_RECORDED_WITH_SHADOW_DEBT",
"data": {
"fridge_item_id": 1042,
"product_name": "Яблоки",
"frozen_fridge_quantity": 0.0000,
"unit": "кг",
"shadow_debt_generated": true,
"shadow_debt_quantity": 0.5000,
"correlation_id": "8a71d11e-9500-4b11-9a2c-d2b0d7b3dcba",
"timestamp": "2026-07-02T01:12:05Z"
}
}5.2 2. Спецификация структуры таблиц PostgreSQL
Для обеспечения чистой теневой экономики без коллизий автосхлопывания, долги по утилизации изолируются в отдельную таблицу fridge_shadow_debts.
-- Таблица истории физического мусора (Для КБЖУ и ИИ-анализа)
CREATE TABLE waste_history_log (
id BIGSERIAL PRIMARY KEY,
home_group_id VARCHAR(50) NOT NULL,
fridge_item_id INT NOT NULL,
waste_quantity NUMERIC(10, 4) NOT NULL,
waste_reason VARCHAR(30) NOT NULL,
correlation_id UUID DEFAULT NULL, -- Связующий ключ для теневых операций
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Изолированная таблица теневых долгов (Реестр для ручного редактирования)
CREATE TABLE fridge_shadow_debts (
debt_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
home_group_id VARCHAR(50) NOT NULL,
product_name VARCHAR(255) NOT NULL,
debt_quantity NUMERIC(10, 4) NOT NULL, -- Вес выброшенного "воздуха"
unit VARCHAR(20) NOT NULL, -- "кг", "шт", "л"
correlation_id UUID NOT NULL UNIQUE, -- Точный указатель на запись в waste_history_log
is_resolved BOOLEAN DEFAULT FALSE, -- Флаг ручного закрытия пользователем
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Индекс для вывода списка скрытых долгов во вкладку корректировок
CREATE INDEX idx_shadow_debts_group_resolved
ON fridge_shadow_debts(home_group_id)
WHERE is_resolved = FALSE;5.3 3. Вилки исключений и обработка ошибок
5.3.1 Ошибка дефицита на предприятии (HTTP 400 Bad Request — Шаг 7)
Выбрасывается строго в контексте аккаунта OFFICE, если товара на балансе не хватает для покрытия объема утилизации. Теневой лог для корпоративных клиентов заблокирован.
{
"error_code": "ERR-WASTE-STOCK-SHORTAGE",
"message": "Утилизация отклонена: в корпоративном контуре запрещено списывать в мусор отсутствующие по накладным позиции.",
"details": {
"reason": "Physical inventory quantity underflow in OFFICE mode.",
"failed_item_id": 1042,
"requested_waste": 0.5000,
"available_stock": 0.0000,
"unit": "кг"
}
}5.3.2 Ошибка валидации Pydantic (HTTP 422 Unprocessable Entity)
Выбрасывается бэкенд-шлюзом, если в поле waste_reason передан невалидный строковый код причины, отсутствующий в Enum.
{
"error_code": "ERR-VALIDATION-FAILED",
"message": "Передан некорректный тип причины утилизации продуктов.",
"details": [
{
"loc": ["body", "waste_reason"],
"msg": "Input should be 'REASON_SPOILED', 'REASON_EXPIRED', 'REASON_BURNT' or 'REASON_DISLIKED'",
"type": "enum_alternative_failed"
}
]
}