docs: add Iteration 5 — home screen dashboard

Describe GET /home/summary endpoint (plan, logged calories, expiring
products, cached recommendations) and HomeScreen layout with calorie
ring, today's meals, expiring banner, and quick actions.
Update Summary.md to include iteration 5 and fix provider references
from Gemini/Groq to OpenAI/GPT.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-02-22 15:07:30 +02:00
parent 1973f45b0a
commit d53e019d90
2 changed files with 327 additions and 11 deletions

278
docs/plans/Iteration_5.md Normal file
View File

@@ -0,0 +1,278 @@
# Итерация 5: Главный экран
**Цель:** превратить заглушку главного экрана в информационный дашборд — показать сводку на сегодня (плановые приёмы пищи, прогресс калорий), быстрые действия, предупреждение об истекающих продуктах и рекомендации рецептов.
**Зависимости:** Итерации 1 (рецепты), 2 (продукты), 4 (меню, дневник).
---
## Структура задач
```
5.1 Backend: endpoint сводки
└── GET /home/summary
5.2 Flutter: HomeScreen
├── 5.2.1 Приветствие + дата
├── 5.2.2 Прогресс калорий (ring)
├── 5.2.3 Плановые приёмы пищи на сегодня
├── 5.2.4 Строка быстрых действий
├── 5.2.5 Баннер истекающих продуктов
└── 5.2.6 Блок рекомендаций
5.3 Flutter: homeProvider
```
---
## 5.1 Backend: GET /home/summary
Единственный запрос при загрузке главного экрана. Агрегирует данные из нескольких таблиц — без AI-вызовов, без медленных операций.
### Запрос
```
GET /home/summary
Authorization: Bearer <jwt>
```
### Ответ
```json
{
"today": {
"date": "2026-02-22",
"daily_goal": 2000,
"logged_calories": 650,
"plan": [
{
"meal_type": "breakfast",
"recipe_title": "Овсяная каша с яблоком",
"recipe_image_url": "https://...",
"calories": 320
},
{
"meal_type": "lunch",
"recipe_title": "Куриный суп",
"recipe_image_url": "https://...",
"calories": 480
},
{
"meal_type": "dinner",
"recipe_title": null,
"recipe_image_url": null,
"calories": null
}
]
},
"expiring_soon": [
{ "name": "Куриная грудка", "expires_in_days": 1, "quantity": "300 г" },
{ "name": "Молоко", "expires_in_days": 2, "quantity": "0.5 л" }
],
"recommendations": [
{
"id": "uuid",
"title": "Стир-фрай с курицей",
"image_url": "https://...",
"calories": 510
},
{
"id": "uuid",
"title": "Омлет с овощами",
"image_url": "https://...",
"calories": 380
}
]
}
```
### Логика сборки
```
today.plan:
SELECT mi.meal_type, sr.title, sr.image_url, sr.nutrition->>'calories'
FROM menu_plans mp
JOIN menu_items mi ON mi.menu_plan_id = mp.id
LEFT JOIN saved_recipes sr ON sr.id = mi.recipe_id
WHERE mp.user_id = $1
AND mp.week_start::text = $2 -- понедельник текущей недели
AND mi.day_of_week = $3 -- номер дня (17)
ORDER BY CASE mi.meal_type WHEN 'breakfast' THEN 1
WHEN 'lunch' THEN 2
ELSE 3 END
Если план не существует — вернуть три слота с null.
today.logged_calories:
SELECT COALESCE(SUM(calories * portions), 0)
FROM meal_diary
WHERE user_id = $1 AND date = $2
today.daily_goal:
users.daily_calories (или 2000 по умолчанию)
expiring_soon:
SELECT name, quantity, unit,
EXTRACT(EPOCH FROM (expires_at - now())) / 86400 AS expires_in_days
FROM products
WHERE user_id = $1
AND expires_at IS NOT NULL
AND expires_at <= now() + INTERVAL '3 days'
AND expires_at >= now()
ORDER BY expires_at
LIMIT 5
recommendations:
SELECT id, title, image_url, nutrition->>'calories' AS calories
FROM saved_recipes
WHERE user_id = $1 AND source = 'recommendation'
ORDER BY saved_at DESC
LIMIT 3
```
Рекомендации берутся из последних результатов `GET /recommendations` (сохранённых в `saved_recipes` с `source='recommendation'`). Новый AI-вызов не производится — главный экран не тратит AI-квоту.
---
## 5.2 Flutter: HomeScreen
### Макет
```
┌─────────────────────────────────────┐
│ Доброе утро, Алексей! 22 февраля │
├─────────────────────────────────────┤
│ │
│ Калории сегодня │
│ ┌───────┐ │
│ │ 650 │ 650 / 2000 ккал │
│ │ ккал │ ▓▓▓▓░░░░░░░░░░ │
│ └───────┘ осталось 1350 │
│ │
├─────────────────────────────────────┤
│ Приёмы пищи сегодня │
│ ┌────────────────────────────────┐ │
│ │ 🌅 Завтрак Овсянка с яблоком │ │
│ │ 320 ккал ✓ │ │
│ ├────────────────────────────────┤ │
│ │ ☀ Обед Куриный суп │ │
│ │ 480 ккал ✓ │ │
│ ├────────────────────────────────┤ │
│ │ 🌙 Ужин Не запланировано │ │
│ │ → │ │
│ └────────────────────────────────┘ │
│ │
├─────────────────────────────────────┤
│ ⚠ Истекает срок │
│ Куриная грудка — 1 день │
│ Молоко — 2 дня → │
├─────────────────────────────────────┤
│ Быстрые действия │
│ [📷 Сканировать] [✨ Меню] [📖 Дневник] │
├─────────────────────────────────────┤
│ Рекомендуем приготовить │
│ ┌──────────┐ ┌──────────┐ │
│ │ [фото] │ │ [фото] │ │
│ │ Стир-фрай│ │ Омлет │ │
│ │ 510 ккал │ │ 380 ккал │ │
│ └──────────┘ └──────────┘ │
│ │
├─────────────────────────────────────┤
│ [Главная●] [Продукты] [Меню] [Рецепты] [Профиль] │
└─────────────────────────────────────┘
```
### 5.2.1 Приветствие
Время-зависимое:
- до 12:00 — «Доброе утро»
- до 18:00 — «Добрый день»
- до 24:00 — «Добрый вечер»
Имя берётся из профиля пользователя.
### 5.2.2 Прогресс калорий
Кольцевой индикатор (`CustomPaint` или пакет `percent_indicator`):
- Заполнен на `logged / goal * 100%`
- Центр: «N ккал»
- Подпись: «из N ккал», «осталось N ккал»
- Если цель не задана (daily_goal = 0) — скрыть блок
### 5.2.3 Плановые приёмы пищи
Карточка с тремя строками (завтрак / обед / ужин).
- Если рецепт есть — показать название + калории
- Если рецепта нет — «Не запланировано» + стрелка → `/menu`
- Тап по строке с рецептом → `RecipeDetailScreen`
### 5.2.4 Быстрые действия
Три кнопки в строку:
- **Сканировать** → `/scan`
- **Меню** → `/menu`
- **Дневник** → `/menu/diary?date=today`
### 5.2.5 Баннер истекающих продуктов
Показывается только если `expiring_soon` не пуст.
- Жёлтый/оранжевый фон
- Список: «Куриная грудка — 1 день», «Молоко — 2 дня»
- Тап → `/products` (с фильтром «Истекает»)
### 5.2.6 Рекомендации
Горизонтальный `ListView` с карточками:
- Фото (CachedNetworkImage)
- Название
- «≈N ккал»
- Тап → `RecipeDetailScreen`
Если `recommendations` пуст — показать кнопку «Получить рекомендации» → `/recipes`.
---
## 5.3 Flutter: homeProvider
```dart
// Модель
class HomeSummary {
final TodaySummary today;
final List<ExpiringSoon> expiringSoon;
final List<HomeRecipe> recommendations;
}
// Провайдер
final homeProvider = StateNotifierProvider
<HomeNotifier, AsyncValue<HomeSummary>>((ref) {
return HomeNotifier(ref.read(homeServiceProvider));
});
class HomeNotifier extends StateNotifier<AsyncValue<HomeSummary>> {
HomeNotifier(this._service) : super(const AsyncValue.loading()) {
load();
}
Future<void> load() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => _service.getSummary());
}
}
```
Провайдер обновляется:
- При первом открытии вкладки
- Через `ref.invalidate(homeProvider)` после сканирования/добавления продуктов или генерации меню
---
## Оценка нагрузки
| Действие | OpenAI | Pexels | БД запросов |
|---|---|---|---|
| Открытие главного экрана | 0 | 0 | 4 (plan, diary, expiring, recommendations) |
| Рекомендации (первый раз) | через `/recommendations` | — | — |
Главный экран не тратит AI-квоту. Рекомендации показываются из уже сохранённых рецептов.

View File

@@ -5,10 +5,11 @@
| # | Итерация | Цель | Зависит от | | # | Итерация | Цель | Зависит от |
|---|----------|------|------------| |---|----------|------|------------|
| 0 | Фундамент | Go-проект, БД, авторизация, Flutter-каркас | — | | 0 | Фундамент | Go-проект, БД, авторизация, Flutter-каркас | — |
| 1 | AI-рекомендации рецептов | Gemini генерирует рецепты, Pexels фото, сохранение рецептов | 0 | | 1 | AI-рекомендации рецептов | GPT генерирует рецепты, Pexels фото, сохранение рецептов | 0 |
| 2 | Управление продуктами | CRUD продуктов, сроки хранения, ingredient_mappings | 0 | | 2 | Управление продуктами | CRUD продуктов, сроки хранения, ingredient_mappings | 0 |
| 3 | Распознавание продуктов | OCR чека, фото продуктов, фото блюд (Gemini Vision) | 1, 2 | | 3 | Распознавание продуктов | OCR чека, фото продуктов, фото блюд (GPT-4o Vision) | 1, 2 |
| 4 | Планирование меню | Меню на неделю, AI-генерация, список покупок, дневник | 1, 2 | | 4 | Планирование меню | Меню на неделю, AI-генерация, список покупок, дневник | 1, 2 |
| 5 | Главный экран | Дашборд: калории, план на сегодня, истекающие продукты, рекомендации | 1, 2, 4 |
Дальнейшие итерации определяются приоритетами после MVP. Функциональность из TODO.md (дневник статистики, режим готовки, полировка) — следующий горизонт. Дальнейшие итерации определяются приоритетами после MVP. Функциональность из TODO.md (дневник статистики, режим готовки, полировка) — следующий горизонт.
@@ -36,11 +37,17 @@
┌────────────────────┐ ┌─────────────────────┐ ┌────────────────────┐ ┌─────────────────────┐
│ 3. Распознавание │ │ 4. Планирование │ │ 3. Распознавание │ │ 4. Планирование │
│ продуктов │ │ меню │ │ продуктов │ │ меню │
│ (Gemini Vision) │ │ (Gemini+Pexels) │ │ (GPT-4o Vision) │ │ (GPT+Pexels)
└────────────────────┘ └────────────────────┘ └────────────────────┘ └────────────────────┘
┌─────────────────────┐
│ 5. Главный экран │
│ (дашборд) │
└─────────────────────┘
``` ```
**Параллельная разработка:** итерации 1 и 2 могут выполняться параллельно. Итерации 3 и 4 — тоже параллельно после завершения 1 и 2. **Параллельная разработка:** итерации 1 и 2 могут выполняться параллельно. Итерации 3 и 4 — тоже параллельно после завершения 1 и 2. Итерация 5 — после 4.
--- ---
@@ -208,9 +215,9 @@
|----|-------|----------| |----|-------|----------|
| 4.1 | Таблицы menu_plans, menu_items | Миграции. menu_items → saved_recipes | | 4.1 | Таблицы menu_plans, menu_items | Миграции. menu_items → saved_recipes |
| 4.2 | Таблица meal_diary | Миграция. Записи приёмов пищи | | 4.2 | Таблица meal_diary | Миграция. Записи приёмов пищи |
| 4.3 | POST /ai/generate-menu | Gemini генерирует 21 рецепт, Pexels параллельно, сохранение в БД | | 4.3 | POST /ai/generate-menu | GPT генерирует 21 рецепт, Pexels параллельно, сохранение в БД |
| 4.4 | Menu CRUD | GET /menu?week=, PUT /menu/items/{id}, DELETE /menu/items/{id} | | 4.4 | Menu CRUD | GET /menu?week=, PUT /menu/items/{id}, DELETE /menu/items/{id} |
| 4.5 | Shopping list | POST /shopping-list/generate (SQL-агрегация без Gemini), GET, PATCH check | | 4.5 | Shopping list | POST /shopping-list/generate (SQL-агрегация без AI), GET, PATCH check |
#### Flutter #### Flutter
@@ -222,22 +229,53 @@
| 4.9 | DiaryScreen | Записи за день, «+ Добавить» | | 4.9 | DiaryScreen | Записи за день, «+ Добавить» |
### Результат итерации ### Результат итерации
- Пользователь получает меню на неделю одним запросом к Gemini - Пользователь получает меню на неделю одним запросом к GPT
- Все рецепты меню сохраняются в saved_recipes - Все рецепты меню сохраняются в saved_recipes
- Из меню автоматически формируется список покупок (то, чего нет в запасах) - Из меню автоматически формируется список покупок (то, чего нет в запасах)
- Ведётся дневник питания - Ведётся дневник питания
--- ---
## Итерация 5: Главный экран
> **Детальный план:** [Iteration_5.md](./Iteration_5.md)
**Цель:** дашборд — сводка на сегодня, прогресс калорий, плановые приёмы пищи, предупреждение об истекающих продуктах, рекомендации рецептов.
**Зависимости:** итерации 1, 2, 4.
### User Stories
#### Backend
| ID | Story | Описание |
|----|-------|----------|
| 5.1 | GET /home/summary | Агрегирует: план на сегодня, калории дневника, истекающие продукты, последние рекомендации |
#### Flutter
| ID | Story | Описание |
|----|-------|----------|
| 5.2 | HomeScreen | Приветствие, кольцо калорий, план на сегодня, быстрые действия, баннер истекающих, блок рекомендаций |
| 5.3 | homeProvider | StateNotifier, обновление после scan/menu-генерации |
### Результат итерации
- Открываешь приложение — сразу видишь баланс калорий и план на день
- Предупреждение если продукты скоро истекут
- Рекомендации без нового AI-вызова (из ранее сохранённых)
---
## Итоги ## Итоги
| Итерация | Цель | Ключевые API | | Итерация | Цель | Ключевые API |
|----------|------|-------------| |----------|------|-------------|
| 0. Фундамент | Auth, профиль, каркас | Firebase | | 0. Фундамент | Auth, профиль, каркас | Firebase |
| 1. AI-рекомендации | Рецепты + сохранение | Gemini, Pexels | | 1. AI-рекомендации | Рецепты + сохранение | GPT-4o-mini, Pexels |
| 2. Продукты | CRUD запасов, ingredient_mappings | — | | 2. Продукты | CRUD запасов, ingredient_mappings | — |
| 3. Распознавание | OCR чека, фото продуктов/блюда | Gemini Vision | | 3. Распознавание | OCR чека, фото продуктов/блюда | GPT-4o Vision |
| 4. Меню | Недельное меню, список покупок | Gemini, Pexels | | 4. Меню | Недельное меню, список покупок | GPT-4o-mini, Pexels |
| 5. Главный экран | Дашборд: калории, план, истекающие, рекомендации | — |
**MVP:** итерации 02 (авторизация + рекомендации + продукты) — пользователь получает персонализированные рецепты. **MVP:** итерации 02 (авторизация + рекомендации + продукты) — пользователь получает персонализированные рецепты.