Smart Fitness App: Data Structure, Database Schema & Server Design Spec
АртефактЭто артефакт, используемый в Эпизоде 2: Планирование системной спецификации вашего приложения (без кода). Вы можете использовать эту спецификацию в качестве справочника по структурированию коллекций Cloud Firestore, конечных точек сервера и трехуровневой памяти агента.
Чтобы получить доступ к этому объекту, войдите в аккаунт.
Войдите в профиль разработчика Google, чтобы посмотреть и скопировать полный артефакт, используемый в серии Build It!.
Требуется профиль разработчика
Вы не можете создать профиль разработчика. Попросите администратора Workspace включить эту функцию.
Создать профиль разработчика
Создайте бесплатный профиль разработчика Google, чтобы получить доступ ко всем подсказкам и артефактам Build It!.
Присоединяйтесь к Builder Central, чтобы получить доступ
Получите бесплатный значок участника Builder, чтобы разблокировать все запросы и артефакты Build It!
Статус: в процессе (составление черновика с помощью Planning Companion).
Целевая база данных: Google Cloud Firestore.
Цель архитектуры: чистая, поддерживаемая, готовая к производству переработка прототипа умного фитнеса AI Studio.
0. Основные решения по техническому стеку и архитектуре системы
Чтобы обеспечить стабильность, производительность и четкие границы, для всех реализаций заблокирована следующая базовая архитектура:
- Фреймворк фронтенда: Next.js (App Router, React 19) с кешированием офлайн-объектов прогрессивного веб-приложения (с помощью Serwist/
@ducanh2912/next-pwa). - Стилизация и компоненты пользовательского интерфейса: Tailwind CSS v4 + Tailwind Variants (для управления вариантами) + Lucide React (значки).
- Архитектура состояния клиента:
- Zustand (с промежуточным ПО
persist) обрабатывает локальное, тактильное и эфемерное состояние клиента (таймеры секундомера, активные буферы наборов, график шагов и несохраненные данные о тренировках). - TanStack Query (React Query). Обрабатывает все асинхронные состояния сервера, кеширование документов Firestore, фоновую повторную проверку, дедупликацию запросов и оптимистичные мутации.
- Zustand (с промежуточным ПО
- Среда выполнения бэкенда и хостинг API. Обработчики маршрутов Next.js (
app/api/...), размещенные в Google Cloud Run в том же регионе, что и Firestore и кеш контекста Gemini, чтобы внутренняя задержка не превышала 15 мс. - Аутентификация: Firebase Authentication (вход с аккаунтом Google, по адресу электронной почты и паролю, а также анонимная интеграция). Клиент прикрепляет
Authorization: Bearer <Firebase_ID_Token>ко всем запросам к API, а сервер проверяет идентификационные данные с помощьюfirebase-admin.auth().verifyIdToken(token). - Основная база данных: Google Cloud Firestore.
1. Обзор системы и основной путь пользователя
1.1. Область применения и принципы архитектуры
В этой спецификации определены модели данных, схемы Cloud Firestore, контракты конечных точек сервера и структуры памяти ИИ-агента для приложения Smart Fitness. Он устанавливает единый источник достоверных данных для планирования тренировок, ведения журнала занятий и автономного многоуровневого коучинга, полностью отделенный от фреймворков интерфейса (которые описаны в отдельной спецификации дизайна UI/UX).
1.2. Основные принципы работы с данными
- Разделение ответственности. Строгая граница между контрактами данных (серверная часть/база данных) и дизайном пользовательского интерфейса клиента.
- Гибридная архитектура библиотеки. Статическая неизменяемая энциклопедия упражнений (
exercises) в сочетании с динамическим составлением еженедельных расписаний с помощью ИИ (weekly_plans). - Поэтапные изменения с участием человека в контуре: Изменения тренировок, созданные ИИ, возвращаются в виде поэтапных предложений; запись в базу данных происходит только после явного согласия пользователя.
- Непрерывная трехуровневая живая память. ИИ автономно фиксирует, обновляет и удаляет факты о пользователе, распределяя их по трем категориям: постоянные данные, временные физиологические состояния (с явным TTL) и преходящий ситуационный контекст.
- Детализированное чтение отдельных документов. Данные структурируются в оптимизированные документы (например, блокноты с планами на неделю и еженедельные чаты), чтобы минимизировать задержку чтения и исключить избыточные запросы.
2. Конечные точки сервера и контракты API
Для каждой конечной точки необходимо указать источник ответа (
AGENT,DATABASEилиHYBRID), чтобы конечные точки, поддерживаемые агентом, можно было правильно настроить во время реализации.
POST /api/coach/chat
- Назначение. Разговорное взаимодействие с ИИ-личным тренером, ответы на вопросы, мотивационная поддержка и генерация предложений по изменению тренировок (как ежедневных корректировок, так и еженедельных пересмотров).
- Триггер: Пользователь отправляет сообщение на вкладке "Тренер" или нажимает на чип с контекстной рекомендацией (например, "Заменить жим Арнольда на жим гантелей от плеч").
- Response Source:
AGENT– использует Gemini со структурированным вызовом функций/инструментов и возможностями потоковой передачи. Не изменяет базу данных напрямую, а возвращает промежуточное предложение для проверки человеком в контуре. - Заголовки:
Authorization: Bearer <Firebase_ID_Token>,Content-Type: application/json
Полезная нагрузка запроса:
{
"message": "string (user input or selected prompt chip text)",
"context": {
"active_tab": "string (enum: today | week | coach)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"selected_day": "string (e.g. monday, optional)"
}
}Полезная нагрузка ответа
{
"message": "string (conversational coaching response displayed in chat bubble)",
"proposal": {
"id": "string (unique proposal ID, nullable)",
"type": "string (enum: NONE | UPDATE_DAY | UPDATE_WEEK)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (concise summary of changes, e.g. 'Swapped Arnold Press ➔ DB Shoulder Press', nullable)",
"data": "map (full proposed workout day object or weekly plan object, nullable)"
},
"memory_mutations": [
{
"operation": "string (enum: CREATE | UPDATE | DELETE)",
"target_tier": "string (enum: tier_1_staples | tier_2_physiological | tier_3_situational)",
"memory_id": "string (required for UPDATE and DELETE)",
"entry": "map (memory payload with fact, category, expires_at, etc., required for CREATE and UPDATE)",
"reason": "string (concise rationale for why memory was mutated)"
}
]
}- Двойная запись в
weekly_chats. На каждом шаге сервер сохраняет входящее сообщение пользователя (text) и ответ тренера (text) в/users/{userId}/weekly_chats/{week_start_date}с помощьюFieldValue.arrayUnion(). - Шаблон поэтапного изменения. Возвращает полезную нагрузку предложения без изменения
/weekly_plans, пока пользователь не нажмет [Accept Plan]. - Непрерывное автономное управление памятью. Вызывает
memory_mutationsдля создания, обновления или удаления элементов из трехуровневого профиля пользователя.
POST /api/workouts/log
- Назначение. Сохраняет завершенную тренировку в постоянный архив пользователя, вычисляет итоговые агрегированные данные по объему и повторениям, проверяет наличие личных рекордов и помечает запланированный день как завершенный.
- Триггер. Пользователь нажимает "Завершить тренировку" на экране отслеживания тренировки.
- Источник ответа:
DATABASE– прямая транзакция базы данных/запись в Cloud Firestore.
Полезная нагрузка запроса:
{
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "string (ISO timestamp)",
"completed_at": "string (ISO timestamp)",
"duration_seconds": "number (e.g. 3120)",
"exercises_logged": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"sets": [
{
"set_number": "number (e.g. 1)",
"weight_lbs": "number (e.g. 60)",
"reps_completed": "number (e.g. 12)",
"completed": "boolean"
}
]
}
],
"user_notes": "string (optional subjective feedback)",
"perceived_exertion": "number (optional 1-10 RPE rating)"
}Полезная нагрузка ответа
{
"success": "boolean",
"log_id": "string (newly created workout log document ID)",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"new_prs": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"metric": "string (e.g. '10 Rep Max' or 'Estimated 1RM')",
"value": "string (e.g. '65 lbs' or '105 lbs')"
}
]
}3. Архитектура базы данных Cloud Firestore
Подборка: exercises
Иерархия: корневая коллекция (/exercises/{exercise_id}). • Назначение: статичная энциклопедия упражнений, содержащая мультимедиа, подсказки по форме, требования к оборудованию и инструкции.
{
"id": "string (canonical slug matching document ID, e.g. incline-db-bench-press)",
"name": "string (e.g. Incline DB Bench Press)",
"category": "string (e.g. Strength, Hypertrophy, Cardio, Mobility)",
"target_muscles": {
"primary": ["string (e.g. Upper Chest)"],
"secondary": ["string (e.g. Front Deltoids, Triceps)"]
},
"media": {
"video_url": "string (URL to demonstration video)",
"poster_url": "string (URL to video poster / thumbnail image)"
},
"instructions": ["string (step-by-step execution guidance)"],
"form_tips": ["string (critical cues and common pitfalls)"],
"equipment_required": ["string (e.g. Dumbbells, Incline Bench)"]
}Подборка: weekly_plans
Иерархия: /users/{userId}/weekly_plans/{week_start_date} • Назначение: "Блокнот на спирали", содержащий полное расписание тренировок на неделю, сводные показатели и тренировки на отдельные дни.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"theme": "string (e.g. Hypertrophy Focus & Upper Body Overload)",
"summary": {
"total_time_minutes": "number (raw minutes, e.g. 180)",
"total_volume_lbs": "number (raw tonnage in lbs, e.g. 38400)",
"frequency": "number (active workout days, e.g. 4)"
},
"suggested_edits": ["string (pre-generated AI prompt suggestion)"],
"days": {
"monday": {
"date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. Monday)",
"is_rest_day": "boolean",
"status": "string (enum: scheduled | completed | skipped)",
"completed_log_id": "string (nullable)",
"completed_at": "string (ISO timestamp, nullable)",
"title": "string (e.g. Upper Body Hypertrophy)",
"focus_tag": "string (e.g. Push & Chest Dominant)",
"duration_minutes": "number",
"estimated_volume_lbs": "number",
"coach_note": "string",
"muscle_focus": [{ "muscle": "string", "intensity": "number (0-100)" }],
"suggested_edits": ["string"],
"blocks": [
{
"label": "string (e.g. Main Compound Block)",
"exercises": [
{
"exercise_id": "string",
"name": "string",
"target_sets": "number",
"target_reps": "string or number",
"rest_seconds": "number"
}
]
}
]
}
}
}Подборка: weekly_chats
Иерархия: /users/{userId}/weekly_chats/{week_start_date} • Назначение. "Еженедельный чат" содержит полную историю чата за определенную неделю обучения в одном документе.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"messages": [
{
"id": "string (unique message ID)",
"sender": "string (enum: user | coach)",
"text": "string (conversational message body)",
"created_at": "timestamp (server timestamp)",
"proposal": {
"id": "string (proposal UID, nullable)",
"type": "string (enum: UPDATE_DAY | UPDATE_WEEK, nullable)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (nullable)",
"status": "string (enum: pending | accepted | dismissed)",
"data": "map (workout or week snapshot payload, nullable)"
}
}
]
}Подборка: workout_logs
Иерархия: /users/{userId}/workout_logs/{log_id} • Назначение. Постоянный, неизменяемый архив завершенных тренировок.
{
"id": "string (document ID)",
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "timestamp",
"completed_at": "timestamp",
"duration_seconds": "number",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"exercises_logged": [
{
"exercise_id": "string",
"exercise_name": "string",
"sets": [
{
"set_number": "number",
"weight_lbs": "number",
"reps_completed": "number",
"completed": "boolean"
}
]
}
],
"user_notes": "string (nullable)",
"perceived_exertion": "number (1-10 RPE, nullable)"
}Подборка: user_profiles
Иерархия/users/{userId}/profile/main • Назначение. "Живая карта клиента", в которой хранится трехуровневый профиль памяти пользователя.
{
"user_id": "string (document owner UID)",
"display_name": "string (e.g. Alex)",
"email": "string (e.g. alex@example.com)",
"preferences": {
"units": "string (enum: lbs | kg)",
"haptics": "boolean",
"sound": "boolean"
},
"equipment_ids": ["string (e.g. 'dumbbells', 'bench_adjustable', 'pull_up_bar')"],
"onboarding_status": "string (enum: not_started | in_progress | completed)",
"created_at": "timestamp",
"tier_1_staples": [
{
"id": "string (e.g. mem_01)",
"category": "string (enum: equipment | preference | goal | chronic_condition)",
"fact": "string (e.g. Home gym with dumbbells up to 50 lbs and adjustable bench)",
"source": "string (e.g. conversational_onboarding | manual_entry)"
}
],
"tier_2_physiological": [
{
"id": "string (e.g. mem_02)",
"body_part": "string (e.g. hamstrings, shoulder)",
"condition": "string (e.g. soreness, acute_fatigue, minor_strain)",
"fact": "string (e.g. Hamstrings are moderately sore from hill sprints)",
"effective_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
],
"tier_3_situational": [
{
"id": "string (e.g. mem_03)",
"event": "string (e.g. golf_competition, travel)",
"fact": "string (e.g. Major golf tournament on Saturday; avoid excessive arm/lat fatigue on Friday)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
]
}Подборка: prs
Иерархия: /users/{userId}/prs/{exercise_slug} • Назначение: хранение личных рекордов за все время, расчетных показателей 1-Rep Max и рекордов по количеству повторений.
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"estimated_1rm": "number (calculated using Brzycki formula, e.g. 105)",
"max_weight_set": {
"weight_lbs": "number (e.g. 80)",
"reps": "number (e.g. 10)",
"achieved_at": "timestamp"
},
"rep_records": {
"10": 80,
"8": 85,
"5": 95
},
"last_updated": "timestamp"
}4. Жизненный цикл данных и конвейер агента
4.1. Конвейер 1. Тренировки, создание тренировок и поэтапное изменение
- Действие пользователя. Вводит запрос в свободной форме или нажимает на автоматически сгенерированный чип контекстного редактирования.
- Сбор контекста. Сервер извлекает активный трехуровневый профиль памяти и план на текущую неделю, удаляя устаревшие элементы памяти.
- Вывод агента. Gemini обрабатывает запрос и контекст, используя структурированный вызов инструмента или функции.
- Автономное изменение памяти. Извлеченные факты о пользователе применяются к
/user_profiles/{userId}и синхронизируются сequipment_ids. - Staged Proposal Staging. Предлагаемые изменения тренировки возвращаются с
proposal.status = "pending"внутри/weekly_chats/{week_start_date}. - Решение пользователя и сохранение данных. Нажатие кнопки [Accept Plan] фиксирует предложенные данные в
/weekly_plansи помечает предложение как"accepted".
4.2 Конвейер 2. Выполнение и архивирование тренировок (фундаментальная истина)
- Запись тренировки в реальном времени. Клиент отслеживает фактический поднятый вес, количество повторений и флаги завершения в локальном состоянии пользовательского интерфейса.
- Отправка данных о тренировке. Клиент вызывает
POST /api/workouts/logпри нажатии кнопки "Завершить тренировку". - Агрегированный расчет и проверка. Сервер рассчитывает совокупный объем тренировки и проверяет наличие личных рекордов по формуле Бржицки.
- Неизменяемое сохранение. Записывает неизменяемую запись в
/workout_logs, отмечает день"completed"в/weekly_plansи добавляет новые PR в/prs.
4.3. Конвейер 3. Автономное добавление и аннулирование памяти
- Сбор. Факты извлекаются непрерывно из всех разговоров.
- Классификация. Уровень 1 (основные, без срока действия), уровень 2 (физиологическое состояние, TTL 3–4 дня), уровень 3 (ситуационный контекст, TTL на основе событий).
- Удаление и аннулирование. Элементы с истекшим сроком действия (
current_timestamp > expires_at) удаляются из контекста запроса и очищаются в фоновом режиме.
5. Правила безопасности Firestore и спецификации составных индексов
5.1. Правила безопасности (firestore.rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
match /profile/{document=**} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_plans/{planId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_chats/{chatId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /prs/{exerciseSlug} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /workout_logs/{logId} {
allow read, create: if request.auth != null && request.auth.uid == userId;
allow update, delete: if false;
}
}
match /exercises/{exerciseId} {
allow read: if request.auth != null;
allow write: if false;
}
}
}5.2. Индексы составных запросов (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "completed_at", "order": "DESCENDING" }]
},
{
"collectionGroup": "weekly_plans",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "week_start_date", "order": "DESCENDING" }]
},
{
"collectionGroup": "prs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "last_updated", "order": "DESCENDING" }]
}
]
}6. Журнал изменений и заблокированные решения
| Дата | Компонент/решение | Тип | Заблокировано пользователем |
|---|---|---|---|
| Первоначальная настройка | Инициализирован пустой шаблон спецификации. | Настройка системы | Команда |
| 2026-09-04 | Добавлены коллекции Firestore: exercises и weekly_plans. | Схема | Команда |
| 2026-09-04 | Добавлена конечная точка POST /api/coach/chat и коллекция chat_messages. | Конечная точка и схема | Команда |
| 2026-09-04 | Проведен рефакторинг хранилища чатов в схему сегментов weekly_chats. | Схема | Команда |
| 2026-09-04 | Добавлены конечная точка POST /api/workouts/log и коллекция workout_logs. | Конечная точка и схема | Команда |
| 2026-09-04 | Добавлена трехуровневая схема памяти user_profiles и обновлен метод POST /api/coach/chat с memory_mutations | Конечная точка и схема | Команда |
| 2026-09-04 | Заполненные разделы 1 (Область применения и принципы системы) и 4 (Жизненный цикл данных и конвейеры) | Полная спецификация | Команда |
| 2026-09-22 | Гармонизированные спецификации: стек Next.js + Tailwind + Zustand, аутентификация Bearer, подколлекция PR, правила Firestore и канонические слаги упражнений | Полная гармонизация спецификаций | Проверка архитектуры |