# Project export: FitStreak

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: FitStreak combines AI-powered fitness analysis with streak-based motivation. By analyzing your data from wearables, we turn complex health data into actionable insights that keep you moving.
- Devpost: https://devpost.com/software/name-iskuvt
- GitHub: https://github.com/leojia22/Treehacks25.git
- Team: 3 GitHub contributor(s) — CJ (14 commits), jacobrljarvis (9 commits), leojia22 (3 commits)

## Devpost submission (written by the team)

### Inspiration

FitStreak is inspired by the challenge of maintaining long-term fitness habits. While many fitness apps focus solely on tracking metrics, we recognized that building and maintaining streaks can be a powerful motivator for consistent exercise and healthy habits. The gamification of fitness through streaks, combined with AI-powered insights, creates a unique approach to health and wellness.

### What it does

FitStreak is a comprehensive fitness tracking and motivation platform that: Connects with wearable devices to track daily activities, sleep patterns, and body composition Uses AI to analyze fitness data and provide personalized insights and recommendations Implements a streak-based motivation system that encourages daily goal completion Tracks multiple fitness metrics including distance, time, and calories burned Provides detailed analysis of sleep patterns and body composition Offers a clean, modern interface for goal setting and progress monitoring

### How we built it

FitStreak is built using a modern tech stack: Frontend: React.js with Redux for state management Backend: Python Flask server Integration with Terra API for fitness data collection OpenAI integration for intelligent fitness analysis Firebase for authentication and user management Real-time streak tracking and goal monitoring system Responsive design for both desktop and mobile use

### Challenges we ran into

Implementing accurate streak tracking that accounts for timezone differences and edge cases Integrating multiple data sources (Garmin, sleep data, body composition) into a cohesive analysis Creating meaningful AI-powered insights that are both accurate and actionable Ensuring real-time synchronization between fitness data and streak updates Building a reliable authentication system that maintains user privacy

### Accomplishments we're proud of

Successfully created an AI-powered fitness analysis system that provides personalized insights Implemented a robust streak tracking system that motivates users Built a clean and intuitive user interface that makes fitness tracking enjoyable Developed a scalable architecture that can handle multiple users and data sources

### What we learned

Integration of multiple APIs (Terra, OpenAI) in a production environment Best practices for handling real-time fitness data Techniques for effective state management in a complex React application Methods for secure user authentication and data privacy Strategies for processing and analyzing fitness metrics effectively

### What's next

Integration with additional fitness devices and platforms Agentic workout recommendations based on user progress Social features to allow users to compete and share achievements More detailed analytics and progress visualization tools Mobile app development for iOS and Android Implementation of personalized coaching features Integration with nutrition tracking platforms

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 93 KB.
- CSS (language) — detected in the code
- Firebase (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code

## Codebase structure (from repository index)

### Files (42 of 42)

```
.gitignore
client/.gitignore
client/eslint.config.js
client/index.html
client/package.json
client/README.md
client/src/App.jsx
client/src/components/BodyComposition.css
client/src/components/BodyComposition.jsx
client/src/components/GarminAnalysis.css
client/src/components/GarminAnalysis.jsx
client/src/components/Login.css
client/src/components/Login.jsx
client/src/components/PrivateRoute.jsx
client/src/components/SleepAnalysis.css
client/src/components/SleepAnalysis.jsx
client/src/components/SuggestedGoals.css
client/src/components/SuggestedGoals.jsx
client/src/components/TerraAuth.jsx
client/src/main.jsx
client/src/pages/EditPlan.css
client/src/pages/EditPlan.jsx
client/src/pages/Homepage.css
client/src/pages/Homepage.jsx
client/src/services/firebase.js
client/src/store/authSlice.js
client/src/store/fitnessSlice.js
client/src/store/store.js
client/vite.config.js
goals.json
metrics.json
package.json
requirements.txt
server/backend.py
server/cert.pem
server/goals.json
server/key.pem
server/main.py
server/metrics.json
server/requirements.txt
server/streak.json
streak.json
```

### Dependencies

- client/package.json: @eslint/js@^9.19.0, @reduxjs/toolkit@^2.5.1, @types/react@^19.0.8, @types/react-dom@^19.0.3, @vitejs/plugin-react@^4.3.4, axios@^1.7.9, date-fns@^4.1.0, eslint@^9.19.0, eslint-plugin-react@^7.37.4, eslint-plugin-react-hooks@^5.0.0, eslint-plugin-react-refresh@^0.4.18, firebase@^11.3.1, globals@^15.14.0, react@^19.0.0, react-dom@^19.0.0, react-redux@^9.2.0, react-router-dom@^7.1.5, vite@^6.1.0
- package.json: flask-cors@^0.0.1, react-router-dom@^7.1.5
- requirements.txt: flask@==3.0.0, flask-cors@==4.0.0, openai@==1.12.0, python-dotenv@==1.0.0, terra-python@==0.0.17
- server/requirements.txt: flask, flask-cors, terra-python

### Recent commits (newest first)

- fixed minor openAI api issue.
- Merge pull request #1 from leojia22/cj
- fixed most major bugs such as metric value updates, saving user defined goals.
- Fixed custom defined goal saving but metric updates via Postman broke.
- Update streak increment logic to increment on every update request
- Streak fixed, it can be incremented and is displayed properly. Other fixes too.
- fixed streak bug where it increased every reload. Did this by adding check for new day.
- added functionality for Postman to send POST request to update goal metrics (miles, mins, cals). Streak is bugged, every refreshes increases the streak.
- Added initial streak.json with default streak count
- Added streak functionality with increment and reset endpoints. Updated frontend to use local backend for streak management.
- Changed recommendations to be based off average of 2 week data.
- Added AI recommended goals.
- Fixed Reccomendation text output formatting. Changed recs to be concise paragraphs.
- Connected AI to front end. Added AI analyses for Garmin data.
- changed ui
- Add Garmin data AI analysis endpoint and dependencies
- auth token req working
- pushed
- working working
- working now actually

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
flask==3.0.0
flask-cors==4.0.0
openai==1.12.0
terra-python==0.0.17
python-dotenv==1.0.0

```

### package.json

```
{
  "dependencies": {
    "flask-cors": "^0.0.1",
    "react-router-dom": "^7.1.5"
  }
}

```

### server/requirements.txt

```
flask
terra-python
flask-cors
```

### client/package.json

```
{
  "name": "my-react-app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^2.5.1",
    "axios": "^1.7.9",
    "date-fns": "^4.1.0",
    "firebase": "^11.3.1",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-redux": "^9.2.0",
    "react-router-dom": "^7.1.5"
  },
  "devDependencies": {
    "@eslint/js": "^9.19.0",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "@vitejs/plugin-react": "^4.3.4",
    "eslint": "^9.19.0",
    "eslint-plugin-react": "^7.37.4",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.18",
    "globals": "^15.14.0",
    "vite": "^6.1.0"
  }
}

```

### client/src/main.jsx

```javascript
// index.jsx or main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store/store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```

### client/src/App.jsx

```javascript
import React, { useEffect } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { authService } from './services/firebase';
import { setUser, clearUser } from './store/authSlice';

// Pages
import Homepage from './pages/Homepage';
import EditPlan from './pages/EditPlan';
import Login from './components/Login';

// Components
import PrivateRoute from './components/PrivateRoute';
import TerraAuth from './components/TerraAuth';

function App() {
  const dispatch = useDispatch();
  const user = useSelector((state) => state.auth.user);

  useEffect(() => {
    const unsubscribe = authService.subscribeToAuthChanges((user) => {
      if (user) {
        dispatch(setUser({
          uid: user.uid,
          email: user.email,
          displayName: user.displayName
        }));
      } else {
        dispatch(clearUser());
      }
    });

    return () => unsubscribe();
  }, [dispatch]);

  return (
    <BrowserRouter>
      <div className="app">
        {/* 🔹 Routes */}
        <Routes>
          {/* Public Routes */}
          <Route 
            path="/login" 
            element={user ? <Navigate to="/" /> : <Login />} 
          />

          {/* Protected Routes */}
          <Route
            path="/"
            element={
              <PrivateRoute>
                <Homepage />
              </PrivateRoute>
            }
          />
          
          <Route
            path="/edit-plan"
            element={
              <PrivateRoute>
                <EditPlan />
              </PrivateRoute>
            }
          />

          <Route
            path="/terra-auth"
            element={
              <PrivateRoute>
                <TerraAuth />
              </PrivateRoute>
            }
          />

          {/* Catch all route - redirect to home */}
          <Route
            path="*"
            element={<Navigate to="/" />}
          />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

export default App;

```

### client/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### client/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
  </body>
</html>

```

### client/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'

export default [
  { ignores: ['dist'] },
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
      parserOptions: {
        ecmaVersion: 'latest',
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    settings: { react: { version: '18.3' } },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...js.configs.recommended.rules,
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      ...reactHooks.configs.recommended.rules,
      'react/jsx-no-target-blank': 'off',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
]

```

### server/backend.py

```python
import logging
import os
import re
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS, cross_origin
from terra.base_client import Terra
import datetime
import requests
import openai
import json

logging.basicConfig(level=logging.INFO)

_LOGGER = logging.getLogger("app")
app = Flask(__name__)

# Initialize OpenAI
openai.api_key = os.getenv('OPENAI_API_KEY')

# Use only Flask-CORS for handling CORS
CORS(app, resources={
    r"/*": {
        "origins": ["http://localhost:3000", "http://localhost:5173"],  # Add Vite's default port
        "methods": ["GET", "POST", "OPTIONS", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"],
        "expose_headers": "*"
    }
})

# Add response headers for all routes
@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
    return response

dev_id = '4actk-fitstreak-testing-wfRE9vBU8U'
api_key = 'A-vwB8CGUoNrQvbP0SUM-dD4mABGFq7Z'
@app.route('/authToken', methods=['POST', 'OPTIONS'])
def auth_token():
    print("Received request to /authToken")
    print("Method:", request.method)
    print("Headers:", dict(request.headers))

    if request.method == "OPTIONS":
        # Create a proper response for OPTIONS request
        response = jsonify({"message": "OPTIONS request received"})
        response.headers.add('Access-Control-Allow-Origin', '*')
        response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
        response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
        return response
    
    url = 'https://api.tryterra.co/v2/auth/generateAuthToken'
    
    data = request.get_json()
    print("Received data:", data)
    reference_id = data.get("reference_id")

    headers = {
        "accept": "application/json",
        "x-api-key": api_key,
        "dev-id": dev_id,
        "Content-Type": "application/json"
    }

    payload = {"reference_id": reference_id}

    try:
        response = requests.post(url, json=payload, headers=headers)
        return response.text, 200, {'Content-Type': 'application/json'}  # Let Flask-CORS handle CORS headers
    except Exception as e:
        print("Error:", str(e))
        return jsonify({"error": str(e)}), 500  # Let Flask-CORS handle CORS headers

# Sample Garmin data for 2 weeks
SAMPLE_GARMIN_DATA = {
    "data": [
        {
            "steps": 8500,
            "calories_data": {"total_burned_calories": 2100},
            "distance_data": {"distance_meters": 7500},
            "date": "2025-02-15"
        },
        {
            "steps": 9200,
            "calories_data": {"total_burned_calories": 2300},
            "distance_data": {"distance_meters": 8000},
            "date": "2025-02-14"
        },
        {
            "steps": 7800,
            "calories_data": {"total_burned_calories": 1900},
            "distance_data": {"distance_meters": 7000},
            "date": "2025-02-13"
        },
        {
            "steps": 10500,
            "calories_data": {"total_burned_calories": 2500},
            "distance_data": {"distance_meters": 9000},
            "date": "2025-02-12"
        },
        {
            "steps": 6500,
            "calories_data": {"total_burned_calories": 1800},
            "distance_data": {"distance_meters": 6000},
            "date": "2025-02-11"
        },
        {
            "steps": 12000,
            "calories_data": {"total_burned_calories": 2800},
            "distance_data": {"distance_meters": 10500},
            "date": "2025-02-10"
        },
        {
            "steps": 8800,
            "calories_data": {"total_burned_calories": 2200},
            "distance_data": {"distance_meters": 7800},
            "date": "2025-02-09"
        },
        {
            "steps": 9500,
            "calories_data": {"total_burned_calories": 2400},
            "distance_data": {"distance_meters": 8500},
            "date": "2025-02-08"
        },
        {
            "steps": 7200,
            "calories_data": {"total_burned_calories": 1850},
            "distance_data": {"distance_meters": 6500},
            "date": "2025-02-07"
        },
        {
            "steps": 11000,
            "calories_data": {"total_burned_calories": 2600},
            "distance_data": {"distance_meters": 9500},
            "date": "2025-02-06"
        },
        {
            "steps": 8300,
            "calories_data": {"total_burned_calories": 2050},
            "distance_data": {"distance_meters": 7300},
            "date": "2025-02-05"
        },
        {
            "steps": 9800,
            "calories_data": {"total_burned_calories": 2450},
            "distance_data": {"distance_meters": 8800},
            "date": "2025-02-04"
        },
        {
            "steps": 7500,
            "calories_data": {"total_burned_calories": 1950},
            "distance_data": {"distance_meters": 6800},
            "date": "2025-02-03"
        },
        {
            "steps": 10800,
            "calories_data": {"total_burned_calories": 2550},
            "distance_data": {"distance_meters": 9200},
            "date": "2025-02-02"
        }
    ]
}

@app.route('/analyze_garmin_data', methods=['GET'])
def analyze_garmin_data():
    try:
        # Calculate averages from all data points
        total_days = len(SAMPLE_GARMIN_DATA['data'])
        avg_steps = sum(day['steps'] for day in SAMPLE_GARMIN_DATA['data']) / total_days
        avg_calories = sum(day['calories_data']['total_burned_calories'] for day in SAMPLE_GARMIN_DATA['data']) / total_days
        avg_distance = sum(day['distance_data']['distance_meters'] for day in SAMPLE_GARMIN_DATA['data']) / total_days / 1000  # Convert to km
        
        # Calculate min and max values for context
        
[truncated — 19381 more characters]
```

[20 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]