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:
278
docs/plans/Iteration_5.md
Normal file
278
docs/plans/Iteration_5.md
Normal 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 -- номер дня (1–7)
|
||||
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-квоту. Рекомендации показываются из уже сохранённых рецептов.
|
||||
@@ -5,10 +5,11 @@
|
||||
| # | Итерация | Цель | Зависит от |
|
||||
|---|----------|------|------------|
|
||||
| 0 | Фундамент | Go-проект, БД, авторизация, Flutter-каркас | — |
|
||||
| 1 | AI-рекомендации рецептов | Gemini генерирует рецепты, Pexels фото, сохранение рецептов | 0 |
|
||||
| 1 | AI-рекомендации рецептов | GPT генерирует рецепты, Pexels фото, сохранение рецептов | 0 |
|
||||
| 2 | Управление продуктами | CRUD продуктов, сроки хранения, ingredient_mappings | 0 |
|
||||
| 3 | Распознавание продуктов | OCR чека, фото продуктов, фото блюд (Gemini Vision) | 1, 2 |
|
||||
| 3 | Распознавание продуктов | OCR чека, фото продуктов, фото блюд (GPT-4o Vision) | 1, 2 |
|
||||
| 4 | Планирование меню | Меню на неделю, AI-генерация, список покупок, дневник | 1, 2 |
|
||||
| 5 | Главный экран | Дашборд: калории, план на сегодня, истекающие продукты, рекомендации | 1, 2, 4 |
|
||||
|
||||
Дальнейшие итерации определяются приоритетами после MVP. Функциональность из TODO.md (дневник статистики, режим готовки, полировка) — следующий горизонт.
|
||||
|
||||
@@ -36,11 +37,17 @@
|
||||
┌────────────────────┐ ┌─────────────────────┐
|
||||
│ 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.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.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
|
||||
|
||||
@@ -222,22 +229,53 @@
|
||||
| 4.9 | DiaryScreen | Записи за день, «+ Добавить» |
|
||||
|
||||
### Результат итерации
|
||||
- Пользователь получает меню на неделю одним запросом к Gemini
|
||||
- Пользователь получает меню на неделю одним запросом к GPT
|
||||
- Все рецепты меню сохраняются в 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 |
|
||||
|----------|------|-------------|
|
||||
| 0. Фундамент | Auth, профиль, каркас | Firebase |
|
||||
| 1. AI-рекомендации | Рецепты + сохранение | Gemini, Pexels |
|
||||
| 1. AI-рекомендации | Рецепты + сохранение | GPT-4o-mini, Pexels |
|
||||
| 2. Продукты | CRUD запасов, ingredient_mappings | — |
|
||||
| 3. Распознавание | OCR чека, фото продуктов/блюда | Gemini Vision |
|
||||
| 4. Меню | Недельное меню, список покупок | Gemini, Pexels |
|
||||
| 3. Распознавание | OCR чека, фото продуктов/блюда | GPT-4o Vision |
|
||||
| 4. Меню | Недельное меню, список покупок | GPT-4o-mini, Pexels |
|
||||
| 5. Главный экран | Дашборд: калории, план, истекающие, рекомендации | — |
|
||||
|
||||
**MVP:** итерации 0–2 (авторизация + рекомендации + продукты) — пользователь получает персонализированные рецепты.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user