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) с поддержкой изолированных теневых логов задолженностей
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод предназначен для фиксации факта порчи, сгорания или утилизации продуктов питания и дробных порций блюд во Flutter-интерфейсе.
Метод решает три ключевые задачи:
- Калибровка складских остатков: Уменьшает баланс выбранного
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 Расшифровка шагов
- Шаг 1 (
User -> APP): Пользователь на экране утилизации выбирает продукт (например, яблоки) и указывает, что выбрасывает0.5 кгсгнившего товара по причинеREASON_SPOILED. При этом физический баланс яблок в приложении равен0.0 кг(пользователь ранее забыл внести чек покупки). Пользователь нажимает кнопку «Списать». - Шаг 2 (
APP -> WS): Приложение через клиентDioотправляет запросHTTP POST /api/v1/waste/items. В теле JSON уходят идентификатор целевой записи, float-объем мусора и строковый Enum-код причины. - Шаг 3 (
WS -> WS): Микросервис Утилизации (FastAPI) декодирует Access Token, проверяя уровень прав и извлекая параметрaccount_type. Бэкенд запоминает бизнес-контекст (HOMEилиOFFICE). - Шаг 4 (
WS -> DB): Сервис открывает атомарную SQL-транзакцию вPostgreSQLи блокирует строку инвентаря склада:SELECT quantity FROM fridge_inventory WHERE item_id = $1 FOR UPDATE. - Шаг 5 (
DB -> WS): База данных возвращает текущий физический объем товара. В нашем кейсе он равен0.0 кг. - Шаг 6 (
WS -> WS): Алгоритм бэкенда сверяет остаток с объемом списания. Так как физического веса на складе не хватает для покрытия мусора (0.0 < 0.5), система выполняет разветвление логики. - Шаг 7 (
WS -> APP): Ветка OFFICE: Строгий корпоративный контур. Бэкенд мгновенно вызываетROLLBACKтранзакции и возвращает мобильному приложению ошибкуHTTP 400 Bad Requestс кодомERR-WASTE-STOCK-SHORTAGE(запрещено выбрасывать то, чего нет на балансе предприятия). - Шаг 8 (
WS -> DB): Ветка HOME (Теневая экономика): База данных оставляет физический баланс холодильника нетронутым (quantityостается равен0.0 кг, склад не уходит в физический минус). Вместо этого система генерирует уникальный UUID, который становится сквознымcorrelation_id. - Шаг 9 (
WS -> DB): Бэкенд выполняет запись в таблицу истории мусораwaste_history_log, фиксируя, что0.5 кгяблок были реально уничтожены, и привязывает к строке сгенерированныйcorrelation_id. - Шаг 10 (
WS -> DB): Параллельно в рамках этой же транзакции бэкенд делает запись в изолированную таблицу теневого долга:INSERT INTO fridge_shadow_debts (home_group_id, product_name, debt_quantity, unit, correlation_id) VALUES (...). Эта строка фиксирует задолженность по вводу веса. - Шаг 11 (
WS -> API): SQL-транзакция успешно фиксируется в PostgreSQL командойCOMMIT. Данные вступают в силу. - Шаг 12 (
WS -> KAFKA): Сервис Утилизации отправляет асинхронное сообщение в топик Kafkabupar.fridge.events.waste. Сообщение содержит ID операции иcorrelation_idдля аналитиков MLOps. - Шаг 13 (
WS -> APP): Сервер возвращает мобильному приложению статусHTTP 200 OKи отдает сгенерированныйcorrelation_id. Во Flutter стейт-менеджер обновляет интерфейс: физический холодильник не изменился, но в специальной вкладке скрытых долгов появляется интерактивная строка: «Выброшено без чека: Яблоки — 0.5 кг», ожидающая последующего ручного сопоставления и редактирования пользователем при импорте новых ресурсов.
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"
}
]
}