Smart Fitness App: Data Structure, Database Schema & Server Design Spec
ArtefaktTo jest artefakt użyty w odcinku 2: Planowanie specyfikacji systemu aplikacji (bez kodu). Możesz użyć tej specyfikacji jako punktu odniesienia do strukturyzacji kolekcji Cloud Firestore, punktów końcowych serwera i 3-poziomowej pamięci agenta.
Zaloguj się, aby uzyskać dostęp do tego artefaktu
Zaloguj się za pomocą profilu dewelopera w Google, aby wyświetlić i skopiować pełny artefakt używany w serii Build It!
Wymagany jest profil dewelopera
Nie kwalifikujesz się do utworzenia profilu dewelopera. Poproś administratora Workspace o włączenie tej funkcji.
Utwórz profil dewelopera
Utwórz bezpłatny profil dewelopera w Google, aby odblokować wszystkie prompty i artefakty w Build It!
Dołącz do Builder Central, aby odblokować
Odbierz bezpłatną plakietkę wspierającego programu Builder jednym kliknięciem, aby odblokować wszystkie prompty i artefakty Build It!
Stan: w toku (tworzenie wersji roboczej za pomocą asystenta planowania)
Docelowa baza danych: Google Cloud Firestore
Cel architektury: czysta, łatwa w utrzymaniu i przygotowana do zastosowań produkcyjnych przebudowa prototypu inteligentnego fitnessu AI Studio.
0. Podstawowy stos techniczny i decyzje dotyczące architektury systemu
Aby zapewnić stabilność i wydajność środowiska produkcyjnego oraz jasne granice, we wszystkich wdrożeniach zablokowana jest ta podstawowa architektura:
- Frontend Framework: Next.js (App Router, React 19) z buforowaniem zasobów offline w progresywnej aplikacji internetowej (PWA) (przy użyciu Serwist /
@ducanh2912/next-pwa). - Styling & UI Components: Tailwind CSS v4 + Tailwind Variants (for variant management) + Lucide React (icons).
- Architektura stanu klienta:
- Zustand (z oprogramowaniem pośredniczącym
persist): obsługuje lokalny, dotykowy i ulotny stan klienta (minutniki, aktywne bufory zestawów, wykres kroków i niezapisane dane z siłowni). - TanStack Query (React Query): obsługuje wszystkie asynchroniczne stany serwera, buforowanie dokumentów Firestore, ponowną walidację w tle, deduplikację żądań i optymistyczne mutacje.
- Zustand (z oprogramowaniem pośredniczącym
- Środowisko wykonawcze backendu i hosting interfejsów API: moduły obsługi tras Next.js (
app/api/...) hostowane w Google Cloud Run w tym samym regionie co Firestore i pamięć podręczna kontekstu Gemini, co zapewnia wewnętrzny czas oczekiwania poniżej 15 ms. - Uwierzytelnianie: Uwierzytelnianie Firebase (logowanie przez Google, adres e-mail i hasło oraz anonimowe połączenie podczas wprowadzania). Klient dołącza
Authorization: Bearer <Firebase_ID_Token>do wszystkich żądań do interfejsu API, a serwer weryfikuje tożsamość za pomocąfirebase-admin.auth().verifyIdToken(token). - Główna baza danych: Google Cloud Firestore.
1. Opis systemu i podstawowa ścieżka użytkownika
1.1 Zakres i zasady dotyczące architektury
Ta specyfikacja definiuje modele danych, schematy Cloud Firestore, umowy dotyczące punktów końcowych serwera i struktury pamięci agentów AI dla aplikacji Smart Fitness. Stanowi jedno źródło wiarygodnych danych na potrzeby planowania wszystkich treningów, rejestrowania sesji historycznych i autonomicznej pamięci wielopoziomowego coachingu, całkowicie oddzielone od platform prezentacji front-end (które są opisane w osobnej specyfikacji projektu UI/UX).
1.2 Podstawowe zasady dotyczące danych
- Separation of Concerns: ścisła granica między umowami dotyczącymi danych (backend/baza danych) a projektem interfejsu klienta.
- Hybrydowa architektura biblioteki: statyczna, niezmienna encyklopedia ćwiczeń (
exercises) połączona z dynamicznym tworzeniem harmonogramów tygodniowych przez AI (weekly_plans). - Zmiany etapowe z udziałem człowieka: zmiany treningu wygenerowane przez AI są zwracane jako propozycje etapowe. Zapisy w bazie danych są dokonywane wyłącznie po wyraźnej akceptacji użytkownika.
- Ciągła 3-poziomowa pamięć dynamiczna: AI samodzielnie rejestruje, aktualizuje i usuwa fakty dotyczące użytkownika, które są podzielone na stałe, wygasające stany fizjologiczne (z wyraźnym czasem życia) i przejściowy kontekst sytuacyjny.
- Szczegółowe odczyty pojedynczych dokumentów: dane są uporządkowane w zoptymalizowanych dokumentach (takich jak notatniki z planem tygodniowym i tygodniowe zasobniki czatów), aby zminimalizować opóźnienia odczytu i wyeliminować zbędne zapytania.
2. Punkty końcowe serwera i umowy dotyczące interfejsu API
Każdy punkt końcowy musi określać źródło odpowiedzi (
AGENT,DATABASElubHYBRID), aby punkty końcowe obsługiwane przez agenta można było odpowiednio skonfigurować podczas implementacji.
POST /api/coach/chat
- Cel: interakcja konwersacyjna z osobistym trenerem AI, odpowiadanie na pytania dotyczące treningu, wsparcie motywacyjne i generowanie proponowanych modyfikacji treningu (zarówno codziennych, jak i cotygodniowych).
- Aktywacja: użytkownik przesyła wiadomość na karcie Trener lub klika wbudowany element sugerowanej zmiany kontekstowej (np. „Zastąp wyciskanie Arnolda wyciskaniem hantli nad głowę”).
- Źródło odpowiedzi:
AGENT– wykorzystuje Gemini z uporządkowanym wywoływaniem narzędzi/funkcji i możliwościami strumieniowania. Nie zmienia bezpośrednio bazy danych, ale zwraca propozycję do sprawdzenia przez człowieka. - Nagłówki:
Authorization: Bearer <Firebase_ID_Token>,Content-Type: application/json
Ładunek żądania:
{
"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)"
}
}Ładunek odpowiedzi:
{
"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)"
}
]
}- Dual-Write to
weekly_chats: On each turn, the server persists the incoming user message (text) and the coach's response (text) into/users/{userId}/weekly_chats/{week_start_date}usingFieldValue.arrayUnion(). - Wzorzec etapowej mutacji: zwraca ładunek propozycji bez modyfikowania
/weekly_plans, dopóki użytkownik nie kliknie [Zaakceptuj abonament]. - Ciągłe autonomiczne zarządzanie pamięcią: wysyła
memory_mutations, aby tworzyć, aktualizować lub usuwać elementy z 3-poziomowego profilu użytkownika.
POST /api/workouts/log
- Cel: zapisuje ukończoną sesję treningową w stałym archiwum treningowym użytkownika, oblicza ostateczne wartości zagregowane dotyczące objętości i powtórzeń, sprawdza rekordy osobiste i oznacza zaplanowany dzień jako ukończony.
- Aktywator: użytkownik klika „Zakończ trening” na ekranie śledzenia treningu.
- Źródło odpowiedzi:
DATABASE– bezpośrednia transakcja w bazie danych / zapis w Cloud Firestore.
Ładunek żądania:
{
"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)"
}Ładunek odpowiedzi:
{
"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. Architektura bazy danych Cloud Firestore
Kolekcja: exercises
Hierarchia: kolekcja główna (/exercises/{exercise_id}) • Cel: statyczna encyklopedia ćwiczeń zawierająca multimedia, wskazówki dotyczące formy, wymagania sprzętowe i instrukcje.
{
"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)"]
}Kolekcja: weekly_plans
Hierarchia: /users/{userId}/weekly_plans/{week_start_date} • Cel: „Kołozeszyt” zawierający pełny tygodniowy harmonogram treningów, podsumowanie danych i treningi na poszczególne dni.
{
"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"
}
]
}
]
}
}
}Kolekcja: weekly_chats
Hierarchia: /users/{userId}/weekly_chats/{week_start_date} • Cel: „Weekly Chat Bucket” zawiera pełną historię rozmów z danego tygodnia szkolenia w jednym dokumencie.
{
"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)"
}
}
]
}Kolekcja: workout_logs
Hierarchia: /users/{userId}/workout_logs/{log_id} • Cel: stałe, niezmienne archiwum historyczne ukończonych sesji treningowych.
{
"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)"
}Kolekcja: user_profiles
Hierarchia: /users/{userId}/profile/main • Cel: „Living Client Chart” przechowuje 3-poziomowy profil pamięci użytkownika.
{
"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"
}
]
}Kolekcja: prs
Hierarchia: /users/{userId}/prs/{exercise_slug} • Cel: przechowuje rekordy życiowe, szacowane wartości maksymalne dla 1 powtórzenia i rekordy dla konkretnych powtórzeń.
{
"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. Cykl życia danych i potok agenta
4.1 Pipeline 1: Coaching, Workout Generation & Staged Modification
- Działanie użytkownika: przesyła dane wejściowe w formie swobodnej lub klika wygenerowany wcześniej element kontekstowy.
- Zestawienie kontekstu: serwer pobiera aktywny 3-poziomowy profil pamięci i plan na bieżący tydzień, usuwając wygasłe elementy pamięci.
- Wnioskowanie agenta: Gemini przetwarza prompt i kontekst, korzystając z uporządkowanego wywoływania funkcji/narzędzi.
- Autonomiczna mutacja pamięci: wyodrębnione fakty dotyczące użytkownika są stosowane w
/user_profiles/{userId}i synchronizowane zequipment_ids. - Staged Proposal Staging: proponowane modyfikacje treningu są zwracane z parametrem
proposal.status = "pending"w obiekcie/weekly_chats/{week_start_date}. - Rozwiązanie i trwałość użytkownika: kliknięcie [Accept Plan] powoduje zapisanie proponowanych danych w
/weekly_plansi oznaczenie propozycji jako"accepted".
4.2 Potok 2: wykonywanie i archiwizowanie treningów (dane podstawowe)
- Nagrywanie sesji na żywo: klient śledzi rzeczywiste podnoszone obciążenie, wykonane powtórzenia i flagi ukończenia w lokalnym stanie interfejsu.
- Przesyłanie sesji: klient wywołuje
POST /api/workouts/logpo kliknięciu „Zakończ trening”. - Obliczanie i weryfikacja wartości zbiorczych: serwer oblicza skumulowaną objętość sesji i sprawdza rekordy życiowe za pomocą wzoru Brzyckiego.
- Niezmienne utrwalanie: zapisuje niezmienny rekord w
/workout_logs, oznacza dzień"completed"w/weekly_plansi wstawia nowe PR do/prs.
4.3 Potok 3: autonomiczne pozyskiwanie i unieważnianie pamięci
- Pozyskiwanie: fakty są stale wyodrębniane ze wszystkich interakcji konwersacyjnych.
- Klasyfikacja: poziom 1 (podstawowe, bez daty ważności), poziom 2 (stan fizjologiczny, TTL 3–4 dni), poziom 3 (kontekst sytuacyjny, TTL na podstawie zdarzeń).
- Pruning & Invalidation (Przycinanie i unieważnianie): elementy, których ważność wygasła (
current_timestamp > expires_at), są odfiltrowywane z kontekstu promptu i usuwane w ramach leniwego czyszczenia.
5. Reguły zabezpieczeń Firestore i specyfikacje indeksów złożonych
5.1 Reguły zabezpieczeń (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 Indeksy zapytań złożonych (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. Historia zmian i zablokowane decyzje
| Data | Komponent lub decyzja | Typ | Zablokowane przez |
|---|---|---|---|
| Konfiguracja początkowa | Zainicjowano pusty szablon specyfikacji | Konfiguracja systemu | Zespół |
| 2026-09-04 | Dodano kolekcje Firestore: exercises i weekly_plans | Schemat | Zespół |
| 2026-09-04 | Dodano punkt końcowy POST /api/coach/chat i kolekcję chat_messages | Punkt końcowy i schemat | Zespół |
| 2026-09-04 | Zrefaktoryzowano pamięć czatu do schematu zasobnika weekly_chats | Schemat | Zespół |
| 2026-09-04 | Dodano punkt końcowy POST /api/workouts/log i kolekcję workout_logs | Punkt końcowy i schemat | Zespół |
| 2026-09-04 | Dodano 3-poziomowy schemat pamięci user_profiles i zaktualizowano POST /api/coach/chat za pomocą memory_mutations | Punkt końcowy i schemat | Zespół |
| 2026-09-04 | Wypełnione sekcje 1 (Zakres i zasady systemu) i 4 (Cykl życia danych i potoki) | Pełna specyfikacja | Zespół |
| 2026-09-22 | Zharmonizowane specyfikacje: stos Next.js + Tailwind + Zustand, uwierzytelnianie Bearer, podkolekcja PR, reguły Firestore i kanoniczne identyfikatory ćwiczeń | Pełna harmonizacja specyfikacji | Weryfikacja architektury |