From 656a510c73ea1ce59793ddf5221b46e398c7ee44 Mon Sep 17 00:00:00 2001 From: Jp Date: Fri, 30 Jan 2026 15:03:43 +0800 Subject: [PATCH] first commit --- QUICK_START.md | 397 +++++++++++++ README_DOCKER.md | 50 ++ calorie_tracker_app/.dockerignore | 8 + calorie_tracker_app/.env.example | 7 + calorie_tracker_app/Dockerfile | 24 + calorie_tracker_app/README.md | 384 ++++++++++++ .../__pycache__/api_client.cpython-314.pyc | Bin 0 -> 9288 bytes .../__pycache__/app.cpython-314.pyc | Bin 0 -> 23821 bytes .../__pycache__/config.cpython-314.pyc | Bin 0 -> 840 bytes .../__pycache__/models.cpython-314.pyc | Bin 0 -> 16373 bytes .../__pycache__/utils.cpython-314.pyc | Bin 0 -> 10094 bytes calorie_tracker_app/api_client.py | 209 +++++++ calorie_tracker_app/app.py | 494 ++++++++++++++++ calorie_tracker_app/config.py | 16 + calorie_tracker_app/models.py | 186 ++++++ calorie_tracker_app/requirements.txt | 7 + calorie_tracker_app/run_app.bat | 9 + calorie_tracker_app/seed_data.py | 368 ++++++++++++ calorie_tracker_app/seed_db.bat | 6 + calorie_tracker_app/templates/add_meal.html | 221 +++++++ calorie_tracker_app/templates/base.html | 92 +++ calorie_tracker_app/templates/dashboard.html | 300 ++++++++++ calorie_tracker_app/templates/foods.html | 14 + calorie_tracker_app/templates/goals.html | 21 + calorie_tracker_app/templates/login.html | 32 + .../templates/meal_planner.html | 6 + calorie_tracker_app/templates/progress.html | 8 + calorie_tracker_app/templates/register.html | 36 ++ calorie_tracker_app/utils.py | 258 +++++++++ customized_build_plan.md | 546 ++++++++++++++++++ docker-compose.yml | 22 + instance/calorie_tracker.db | Bin 0 -> 69632 bytes 32 files changed, 3721 insertions(+) create mode 100644 QUICK_START.md create mode 100644 README_DOCKER.md create mode 100644 calorie_tracker_app/.dockerignore create mode 100644 calorie_tracker_app/.env.example create mode 100644 calorie_tracker_app/Dockerfile create mode 100644 calorie_tracker_app/README.md create mode 100644 calorie_tracker_app/__pycache__/api_client.cpython-314.pyc create mode 100644 calorie_tracker_app/__pycache__/app.cpython-314.pyc create mode 100644 calorie_tracker_app/__pycache__/config.cpython-314.pyc create mode 100644 calorie_tracker_app/__pycache__/models.cpython-314.pyc create mode 100644 calorie_tracker_app/__pycache__/utils.cpython-314.pyc create mode 100644 calorie_tracker_app/api_client.py create mode 100644 calorie_tracker_app/app.py create mode 100644 calorie_tracker_app/config.py create mode 100644 calorie_tracker_app/models.py create mode 100644 calorie_tracker_app/requirements.txt create mode 100644 calorie_tracker_app/run_app.bat create mode 100644 calorie_tracker_app/seed_data.py create mode 100644 calorie_tracker_app/seed_db.bat create mode 100644 calorie_tracker_app/templates/add_meal.html create mode 100644 calorie_tracker_app/templates/base.html create mode 100644 calorie_tracker_app/templates/dashboard.html create mode 100644 calorie_tracker_app/templates/foods.html create mode 100644 calorie_tracker_app/templates/goals.html create mode 100644 calorie_tracker_app/templates/login.html create mode 100644 calorie_tracker_app/templates/meal_planner.html create mode 100644 calorie_tracker_app/templates/progress.html create mode 100644 calorie_tracker_app/templates/register.html create mode 100644 calorie_tracker_app/utils.py create mode 100644 customized_build_plan.md create mode 100644 docker-compose.yml create mode 100644 instance/calorie_tracker.db diff --git a/QUICK_START.md b/QUICK_START.md new file mode 100644 index 0000000..4635149 --- /dev/null +++ b/QUICK_START.md @@ -0,0 +1,397 @@ +# πŸš€ Quick Start Guide - Calorie Tracker App + +## What You Have + +A complete **Flask web application** for tracking calories, macros, water intake, and weight with focus on Filipino foods! + +### βœ… Features Implemented + +1. **User Authentication** - Register/Login system +2. **Dashboard** - Beautiful overview with cards, charts, and progress bars +3. **Macro Tracking** - Calories, Protein, Carbs, Fat +4. **Filipino Food Database** - 25+ pre-loaded foods (Adobo, Sinigang, Tapsilog, etc.) +5. **Food Search** - Search Filipino and international foods +6. **API Integration** - API Ninjas for 1M+ foods +7. **Water Tracking** - Quick-add buttons (250ml, 500ml) +8. **Weight Tracking** - Daily logs with trends +9. **Smart Suggestions** - AI recommendations based on remaining macros +10. **Charts & Trends** - 7-day calorie and weight trends +11. **Meal Logging** - Add meals with multiple foods +12. **Responsive Design** - Works on mobile and desktop +13. **Filipino Flag Colors** - Red/Blue theme + +--- + +## πŸƒ How to Run It + +### Option 1: Quick Start (5 minutes) + +```bash +# 1. Open terminal in the calorie_tracker_app folder +cd calorie_tracker_app + +# 2. Create virtual environment +python -m venv venv + +# 3. Activate it +# On Mac/Linux: +source venv/bin/activate +# On Windows: +venv\Scripts\activate + +# 4. Install packages +pip install -r requirements.txt + +# 5. Run the app +# Windows (Double click run_app.bat OR run): +venv\Scripts\python app.py + +# Mac/Linux: +./venv/bin/python app.py +``` + +## ❓ Troubleshooting + +### "pip: command not found" +If you see this error, it means the virtual environment is not activated. +1. Make sure you are in the `calorie_tracker_app` folder. +2. Run `venv\Scripts\activate` (Windows) or `source venv/bin/activate` (Mac/Linux). +3. Try `python -m pip install -r requirements.txt` instead. + +### "ModuleNotFoundError" +If Python can't find modules (Flask, etc.), make sure you activated the virtual environment before running the app. + +The app will: +- Create the database automatically +- Start on http://localhost:5001 + +### Option 2: With API Key (Recommended) + +```bash +# Do steps 1-4 from above, then: + +# 5. Get free API key +# Go to: https://api-ninjas.com/api/nutrition +# Sign up (free) +# Copy your API key + +# 6. Create .env file +cp .env.example .env + +# 7. Edit .env and add your key +# API_NINJAS_KEY=your_key_here + +# 8. Seed Filipino foods +# Windows (Double click seed_db.bat OR run): +venv\Scripts\python seed_data.py + +# Mac/Linux: +./venv/bin/python seed_data.py + +# 9. Run the app +python app.py +``` + +--- + +## πŸ“± Using the App + +### First Time + +1. Go to http://localhost:5000 +2. Click "Register" +3. Create account (username + password) +4. Login +5. Go to "Goals" page +6. Enter: + - Age: 25 + - Gender: Male/Female + - Height: 170 cm + - Weight: 70 kg + - Activity: Moderate + - Goal: Recomp (for weight loss + muscle gain) + - Target weight: 65 kg +7. Click Save + +**The app automatically calculates:** +- Your BMR (Basal Metabolic Rate) +- Your TDEE (Total Daily Energy Expenditure) +- Calorie target (TDEE - 500 for weight loss) +- Macro targets (Protein: 154g, Carbs: 175g, Fat: 63g) + +### Daily Use + +#### Morning Routine +1. **Log Weight** - Enter on dashboard +2. **Log Water** - Click +250ml or +500ml + +#### Adding Meals +1. Click "Add Meal" +2. Select breakfast/lunch/dinner/snack +3. Search for food (e.g., "adobo", "kanin", "sinigang") +4. Click food to add +5. Adjust servings (0.5, 1, 1.5, 2, etc.) +6. See real-time nutrition summary +7. Click "Save Meal" + +#### Dashboard Shows +- Calories consumed vs target +- Macros (protein/carbs/fat) with progress bars +- Water intake (glass icons) +- Today's weight +- All meals logged +- Charts showing trends + +--- + +## πŸ› Filipino Foods Available + +### Search these (English or Tagalog): + +**Breakfast** +- Tapsilog (beef tapa + rice + egg) +- Longsilog (longganisa + rice + egg) +- Tocilog (tocino + rice + egg) + +**Main Dishes** +- Chicken Adobo / Adobong Manok +- Pork Sinigang / Sinigang na Baboy +- Chicken Tinola / Tinolang Manok +- Bicol Express +- Sisig +- Menudo +- Kare-Kare +- Lechon Kawali + +**Soups** +- Bulalo +- Nilaga + +**Vegetables** +- Pinakbet +- Laing +- Ginisang Monggo + +**Snacks** +- Pandesal +- Turon +- Bibingka +- Puto +- Lumpia + +**Rice** +- White Rice / Kanin +- Fried Rice / Sinangag + +--- + +## πŸ’‘ Tips for Success + +### Body Recomposition Strategy + +**Your goals: Weight loss + Muscle gain** + +1. **Protein Priority** + - Eat 2.2g protein per kg body weight + - Example: 70kg = 154g protein daily + - Spread across 4-5 meals + +2. **Calorie Deficit** + - 300-500 calories below TDEE + - App calculates this automatically + - Lose 0.5-1 kg per week + +3. **Track Daily** + - Weight: Same time every morning + - Food: Log everything (even snacks) + - Water: Aim for 2+ liters + +4. **Weekly Check-in** + - Look at 7-day average weight + - Adjust calories if needed + - Progress page shows trends + +### Food Search Tips + +1. **Try Filipino first** (no API needed): + - "adobo", "sinigang", "sisig" + - "kanin", "pandesal", "lumpia" + +2. **Search Tagalog names**: + - Works for Filipino foods + - "Adobong Manok", "Sinigang na Baboy" + +3. **International foods** (needs API): + - "chicken breast", "brown rice" + - "protein shake", "banana" + +4. **Be specific**: + - Instead of "lunch", search "chicken rice" + - Instead of "food", search actual dish + +### Maximize Free API Tier + +API Ninjas free tier: 50 requests/day + +**The app helps you save API calls:** +- Filipino foods = 0 API calls (local database) +- Cached foods = 0 API calls (saved from previous searches) +- Only new international foods use API + +**Tips:** +- Use Filipino foods when possible +- Foods are cached after first search +- Mark favorites to find them quickly + +--- + +## πŸ“Š Understanding Your Dashboard + +### Calorie Card +- **Green progress bar** = On track +- **Red progress bar** = Over target +- Shows remaining calories + +### Protein Card +- Goal: Hit target every day +- Important for muscle building +- Prevents muscle loss during deficit + +### Carbs & Fat Cards +- Carbs = Energy for workouts +- Fat = Hormones & satiety +- Both important, don't eliminate + +### Water Tracking +- 8 glasses = 2 liters (goal) +- Blue drops fill up +- Quick add: +250ml, +500ml buttons + +### Weight Card +- Log daily for best results +- Shows change from yesterday +- Green ⬇ = weight down (good for weight loss) +- Red ⬆ = weight up + +### Smart Suggestions +Based on what you've eaten: +- Need protein? β†’ Suggests Tinola, Grilled Fish +- Need carbs? β†’ Suggests Rice, Pandesal +- Need fat? β†’ Suggests Sisig, Bicol Express +- Balanced? β†’ No suggestions (you're good!) + +### Charts +- **Calorie Trend**: Shows if you're consistent +- **Weight Trend**: Shows if you're losing weight +- Both use 7-day data + +--- + +## πŸ”§ Troubleshooting + +### "No module named flask" +```bash +pip install -r requirements.txt +``` + +### "Database is locked" +```bash +# Stop the app, delete database, restart +rm calorie_tracker.db +python app.py +python seed_data.py +``` + +### Filipino foods not showing +```bash +python seed_data.py +``` + +### API not working +- Check .env file has API key +- App still works without API (Filipino foods + manual entry) + +### Port 5000 in use +Edit app.py, last line: +```python +app.run(debug=True, host='0.0.0.0', port=5001) # Change to 5001 +``` + +--- + +## πŸ“ Project Files + +``` +calorie_tracker_app/ +β”œβ”€β”€ app.py # Main application (Flask routes) +β”œβ”€β”€ models.py # Database tables +β”œβ”€β”€ api_client.py # API integration +β”œβ”€β”€ utils.py # Helper functions (BMR, TDEE calculations) +β”œβ”€β”€ seed_data.py # Filipino foods data +β”œβ”€β”€ config.py # Settings +β”œβ”€β”€ requirements.txt # Python packages +β”œβ”€β”€ .env.example # API key template +β”œβ”€β”€ README.md # Full documentation +└── templates/ # HTML pages + β”œβ”€β”€ dashboard.html # Main page βœ… + β”œβ”€β”€ add_meal.html # Add meals βœ… + β”œβ”€β”€ login.html # Login page βœ… + β”œβ”€β”€ register.html # Register page βœ… + β”œβ”€β”€ foods.html # Food database (basic) + β”œβ”€β”€ goals.html # Goals/settings (basic) + β”œβ”€β”€ progress.html # Charts (placeholder) + └── meal_planner.html # Future feature +``` + +--- + +## 🎯 What's Working Now + +βœ… Full user system (register/login) +βœ… Dashboard with all stats +βœ… Add meals with multiple foods +βœ… Food search (Filipino + API) +βœ… Water tracking +βœ… Weight tracking +βœ… Macro calculations +βœ… Smart suggestions +βœ… Charts and trends +βœ… Responsive design + +## 🚧 To Be Enhanced + +These are basic but functional: +- Foods page (shows foods, can be enhanced with filters) +- Goals page (works but could be prettier) +- Progress page (placeholder, can add more charts) +- Meal planner (not implemented yet) + +--- + +## πŸŽ‰ You're Ready! + +### Next Steps: + +1. **Run the app** (see instructions above) +2. **Register** an account +3. **Set your goals** (Goals page) +4. **Add your first meal** (try searching "tapsilog"!) +5. **Track for a week** to see trends + +### Pro Tip: +Log your meals right after eating. It's easier to remember and takes only 30 seconds! + +--- + +## πŸ“ž Need Help? + +1. Check README.md for detailed docs +2. Check this Quick Start guide +3. Look at the code comments +4. All functions are documented + +--- + +**Enjoy tracking your journey to better health! πŸ’ͺπŸ‡΅πŸ‡­** + +Made with ❀️ for Filipino food lovers! diff --git a/README_DOCKER.md b/README_DOCKER.md new file mode 100644 index 0000000..effa8f0 --- /dev/null +++ b/README_DOCKER.md @@ -0,0 +1,50 @@ +# 🐳 Self-Hosting with Docker (CasaOS) + +You can easily host this Calorie Tracker on your home server using Docker or CasaOS. + +## πŸ› οΈ Quick Setup for CasaOS + +1. **Download the files**: + * Download `docker-compose.yml` + * Download the `calorie_tracker_app` folder + +2. **Import to CasaOS**: + * Open CasaOS Dashboard + * Click **+** (Install a customized app) + * Click **Import** (top right) + * Select the `docker-compose.yml` file + * (Optional) Setup the `API_NINJAS_KEY` in the Settings + +3. **Install**: + * Click **Install** + * The app will start at `http://your-server-ip:5001` + +## 🐳 Manual Docker Compose + +1. Navigate to the project directory: + ```bash + cd calorie_tracker + ``` + +2. Run with Docker Compose: + ```bash + docker-compose up -d --build + ``` + +3. Access the app: + * Open `http://localhost:5001` + +## πŸ’Ύ Data Persistence + +* Database is stored in `./data/calorie_tracker.db` on your host machine. +* This ensures your data survives container restarts/updates. + +## βš™οΈ Environment Variables + +You can configure these in `docker-compose.yml` or CasaOS settings: + +| Variable | Description | Default | +| :--- | :--- | :--- | +| `API_NINJAS_KEY` | Key for nutrition API | (Empty) | +| `SECRET_KEY` | Flask secret key | `change-this...` | +| `DATABASE_URL` | Database path | `sqlite:////app/data/calorie_tracker.db` | diff --git a/calorie_tracker_app/.dockerignore b/calorie_tracker_app/.dockerignore new file mode 100644 index 0000000..aeb931f --- /dev/null +++ b/calorie_tracker_app/.dockerignore @@ -0,0 +1,8 @@ +venv/ +__pycache__/ +instance/ +.env +.git +.gitignore +*.db +*.pyc diff --git a/calorie_tracker_app/.env.example b/calorie_tracker_app/.env.example new file mode 100644 index 0000000..8be0d28 --- /dev/null +++ b/calorie_tracker_app/.env.example @@ -0,0 +1,7 @@ +# API Ninjas API Key +# Get your free API key from: https://api-ninjas.com/api +API_NINJAS_KEY=your_api_key_here + +# Flask Configuration +SECRET_KEY=your_secret_key_here_change_in_production +FLASK_ENV=development diff --git a/calorie_tracker_app/Dockerfile b/calorie_tracker_app/Dockerfile new file mode 100644 index 0000000..7bfaabb --- /dev/null +++ b/calorie_tracker_app/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.10-slim + +WORKDIR /app + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV FLASK_APP=app.py + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Create data directory +RUN mkdir -p /app/data + +# Expose port +EXPOSE 5001 + +# Run commands to init db, seed data, and start server +CMD ["sh", "-c", "flask init-db && flask seed-db && gunicorn -w 4 -b 0.0.0.0:5001 app:app"] diff --git a/calorie_tracker_app/README.md b/calorie_tracker_app/README.md new file mode 100644 index 0000000..be1ac26 --- /dev/null +++ b/calorie_tracker_app/README.md @@ -0,0 +1,384 @@ +# Calorie Tracker - Filipino Food Edition 🍽️ + +A web application for tracking calories, macros, and water intake with special focus on Filipino foods. Perfect for weight loss and muscle gain goals! + +## Features + +βœ… **Macro Tracking**: Track calories, protein, carbs, and fat +βœ… **Filipino Food Database**: Pre-loaded with 25+ common Filipino foods +βœ… **Water Intake Tracking**: Quick-add water logging +βœ… **Weight Tracking**: Daily weight logs with trend analysis +βœ… **Meal Planning**: Plan meals ahead (coming soon!) +βœ… **Smart Suggestions**: Get food recommendations based on remaining macros +βœ… **API Integration**: Search international foods via API Ninjas +βœ… **Beautiful UI**: Red/blue color scheme inspired by Filipino flag +βœ… **Charts & Trends**: Visualize your progress with Chart.js + +## Filipino Foods Included + +- **Kanin (Rice)**: White rice, Sinangag +- **Ulam (Main Dishes)**: Adobo, Sinigang, Sisig, Bicol Express, Kare-kare, Menudo, Lechon Kawali +- **Sabaw (Soups)**: Tinola, Nilaga, Bulalo +- **Gulay (Vegetables)**: Pinakbet, Laing, Ginisang Monggo +- **Almusal (Breakfast)**: Tapsilog, Longsilog, Tocilog +- **Meryenda (Snacks)**: Pandesal, Turon, Bibingka, Puto, Lumpia + +## 🐳 Docker Support + +Want to self-host this on CasaOS or your own server? +πŸ‘‰ **[Read the Docker Guide](../README_DOCKER.md)** + +## Tech Stack + + +- **Backend**: Flask (Python) +- **Database**: SQLite +- **Frontend**: HTML, Tailwind CSS, Vanilla JavaScript +- **Charts**: Chart.js +- **API**: API Ninjas Nutrition API + +## Installation + +### Prerequisites + +- Python 3.10 or higher +- pip (Python package manager) + +### Step 1: Clone or Download + +```bash +cd calorie_tracker_app +``` + +### Step 2: Create Virtual Environment + +```bash +python -m venv venv + +# On Windows: +venv\Scripts\activate + +# On Mac/Linux: +source venv/bin/activate +``` + +### Step 3: Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### Step 4: Get API Key (Optional but Recommended) + +1. Go to [API Ninjas](https://api-ninjas.com/api/nutrition) +2. Sign up for a free account +3. Get your API key (50 requests/day free tier) +4. Create `.env` file: + +```bash +cp .env.example .env +``` + +Edit `.env` and add your API key: +``` +API_NINJAS_KEY=your_api_key_here +SECRET_KEY=your_secret_key_here +``` + +### Step 5: Initialize Database + +Run the app for the first time: + +```bash +# Windows (Double click run_app.bat OR run): +venv\Scripts\python app.py + +# Mac/Linux: +./venv/bin/python app.py +``` + +This will create the database. Then in another terminal, seed Filipino foods: + +```bash +# Windows (Double click seed_db.bat OR run): +venv\Scripts\python seed_data.py + +# Mac/Linux: +./venv/bin/python seed_data.py +``` + +### Step 6: Run the Application + +```bash +# Windows (Double click run_app.bat OR run): +venv\Scripts\python app.py + +# Mac/Linux: +./venv/bin/python app.py +``` + +Open your browser and go to: `http://localhost:5001` + +## Usage + +### First Time Setup + +1. **Register**: Create an account at `/register` +2. **Login**: Sign in with your credentials +3. **Set Goals**: Go to Goals page and enter: + - Age, gender, height, weight + - Activity level + - Goal type (weight loss, muscle gain, or recomp) + - Target weight + +The app will automatically calculate your: +- BMR (Basal Metabolic Rate) +- TDEE (Total Daily Energy Expenditure) +- Calorie target +- Macro targets + +### Daily Tracking + +#### Add a Meal +1. Click "Add Meal" button +2. Select date, meal type, and time +3. Search for foods (Filipino or international) +4. Add foods and adjust servings +5. See real-time nutrition summary +6. Save meal + +#### Log Water +- Quick buttons on dashboard: +250ml, +500ml +- Or use custom amount + +#### Log Weight +- Enter weight on dashboard +- Track daily to see trends + +### Food Search + +The app searches in this order: +1. **Filipino foods** (local database) - fastest +2. **Cached foods** (previously searched) +3. **API Ninjas** (if API key configured) +4. **Manual entry** (if food not found) + +Search supports both English and Tagalog: +- "Adobo" or "Chicken Adobo" +- "Kanin" or "Rice" +- "Sinigang" works + +### Understanding the Dashboard + +#### Top Cards +- **Calories**: Shows consumed vs target with progress bar +- **Protein**: Your protein intake vs target +- **Carbs & Fat**: Dual progress bars +- **Water & Weight**: Quick water logging + today's weight + +#### Macro Distribution +- Pie chart showing protein/carbs/fat percentages +- Ideal for recomp: 30-35% protein, 40-45% carbs, 20-25% fat + +#### Smart Suggestions +Based on remaining macros, suggests: +- High protein foods if protein is low +- Carb sources if carbs are low +- Balanced meals if everything is balanced + +#### Today's Meals +- Shows all logged meals +- Color-coded by meal type +- Displays nutrition breakdown + +#### Weekly Trends +- 7-day calorie trend vs target +- 7-day weight trend + +## Database Schema + +### Tables +- `users` - User accounts +- `food_items` - All foods (Filipino + API) +- `meals` - Meal entries +- `meal_foods` - Foods in each meal +- `water_logs` - Water intake +- `weight_logs` - Weight tracking +- `meal_plans` - Future meal planning +- `user_goals` - Macro targets +- `daily_summary` - Daily totals +- `api_cache` - API response caching + +## API Rate Limits + +**Free Tier (API Ninjas)**: +- 50 requests per day +- App caches all results +- Once cached, no API calls needed + +**Tips to conserve API calls**: +- Use Filipino foods when possible (no API calls) +- Search once, use favorites +- API searches are cached for 30 days + +## Customization + +### Adding Custom Foods + +1. Go to Foods page +2. Click "Add Custom Food" +3. Enter nutrition information +4. Save + +### Adjusting Targets + +Go to Goals page to adjust: +- Daily calorie target +- Macro targets (protein/carbs/fat) +- Water intake goal +- Weight goal + +### Meal Templates (Coming Soon!) + +Save common meal combinations: +- "My typical breakfast" +- "Post-workout meal" +- "Quick lunch" + +## Tips for Body Recomposition + +### Protein Priority +- 2.0-2.4g per kg body weight +- Example: 70kg person = 140-168g daily +- Spread across all meals + +### Carb Timing +- Higher carbs on training days +- Lower carbs on rest days + +### Calorie Cycling +- Training days: Maintenance (+100 cal) +- Rest days: Deficit (-300 to -500 cal) + +### Track Weight Daily +- Weigh same time each day (morning, after bathroom) +- Look at weekly average, not daily fluctuations +- Aim for 0.5-1% body weight loss per week + +## Troubleshooting + +### Database Locked Error +```bash +# Stop the app, then: +rm calorie_tracker.db +python app.py +python seed_data.py +``` + +### API Not Working +- Check if API key is in `.env` file +- Verify API key is valid +- App works without API (Filipino foods + manual entry) + +### Foods Not Showing +```bash +python seed_data.py +``` + +### Port Already in Use +```bash +# Change port in app.py: +app.run(debug=True, host='0.0.0.0', port=5001) +``` + +## Future Enhancements + +- [ ] Meal planner with calendar view +- [ ] Barcode scanning (Open Food Facts API) +- [ ] Recipe builder +- [ ] Meal templates +- [ ] Export to CSV/PDF +- [ ] Photo food logging +- [ ] Workout integration +- [ ] Body measurements tracking +- [ ] Progress photos +- [ ] Mobile app version + +## Project Structure + +``` +calorie_tracker_app/ +β”œβ”€β”€ app.py # Main Flask application +β”œβ”€β”€ models.py # Database models +β”œβ”€β”€ config.py # Configuration +β”œβ”€β”€ api_client.py # API integration +β”œβ”€β”€ utils.py # Helper functions +β”œβ”€β”€ seed_data.py # Filipino foods data +β”œβ”€β”€ requirements.txt # Python dependencies +β”œβ”€β”€ .env.example # Environment variables template +β”œβ”€β”€ templates/ # HTML templates +β”‚ β”œβ”€β”€ base.html +β”‚ β”œβ”€β”€ dashboard.html +β”‚ β”œβ”€β”€ add_meal.html +β”‚ β”œβ”€β”€ login.html +β”‚ β”œβ”€β”€ register.html +β”‚ β”œβ”€β”€ foods.html +β”‚ β”œβ”€β”€ meal_planner.html +β”‚ β”œβ”€β”€ progress.html +β”‚ └── goals.html +└── static/ # CSS, JS, images + β”œβ”€β”€ css/ + β”œβ”€β”€ js/ + └── images/ +``` + +## Contributing + +This is a personal project, but suggestions are welcome! + +## License + +MIT License - Feel free to use and modify for your own needs. + +## Credits + +- **API Ninjas** for nutrition data +- **Tailwind CSS** for styling +- **Chart.js** for visualizations +- **Filipino food data** compiled from various nutrition sources + +## Support + +For issues or questions: +1. Check this README +2. Check the troubleshooting section +3. Review the code comments + +--- + +**Made with ❀️ for Filipino food lovers and fitness enthusiasts!** + +## Quick Start Summary + +```bash +# 1. Setup +python -m venv venv +source venv/bin/activate # or venv\Scripts\activate on Windows +pip install -r requirements.txt + +# 2. Configure (optional) +cp .env.example .env +# Add your API key to .env + +# 3. Initialize +python app.py # Creates database +python seed_data.py # Adds Filipino foods + +# 4. Run +python app.py + +# 5. Open browser +# Go to http://localhost:5000 +``` + +Enjoy tracking your nutrition! πŸŽ‰ diff --git a/calorie_tracker_app/__pycache__/api_client.cpython-314.pyc b/calorie_tracker_app/__pycache__/api_client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78f65b5d179a92c9dae04519c5841dcd9eda40a8 GIT binary patch literal 9288 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 literal 0 HcmV?d00001 diff --git a/calorie_tracker_app/__pycache__/app.cpython-314.pyc b/calorie_tracker_app/__pycache__/app.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67631ea3c0e28c3d0bd580db48260a5b33a4d7e9 GIT binary patch literal 23821 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 literal 0 HcmV?d00001 diff --git a/calorie_tracker_app/__pycache__/models.cpython-314.pyc b/calorie_tracker_app/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..221e5cb8f2db3bbf875d7795e8815a8f3cfa538f GIT binary patch literal 16373 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 I*2T&HA372-kN^Mx literal 0 HcmV?d00001 diff --git a/calorie_tracker_app/__pycache__/utils.cpython-314.pyc b/calorie_tracker_app/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..add78b40d0d78cacb066e72eccc27b64831eb5ca GIT binary patch literal 10094 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 M%DZ+NTJbyn8=L6_^8f$< literal 0 HcmV?d00001 diff --git a/calorie_tracker_app/api_client.py b/calorie_tracker_app/api_client.py new file mode 100644 index 0000000..1bfa772 --- /dev/null +++ b/calorie_tracker_app/api_client.py @@ -0,0 +1,209 @@ +import requests +import json +from models import db, APICache, FoodItem +from datetime import datetime, timedelta + +class NutritionAPI: + """API client for nutrition data with caching""" + + def __init__(self, api_key): + self.api_key = api_key + self.base_url = "https://api.api-ninjas.com/v1/nutrition" + self.headers = {'X-Api-Key': api_key} + self.cache_duration_days = 30 + + def search_food(self, query): + """ + Search for food nutrition data + Returns list of food items with nutrition info + """ + # Check cache first + cached = self._get_from_cache(query) + if cached: + return cached + + # Make API request + try: + response = requests.get( + self.base_url, + headers=self.headers, + params={'query': query}, + timeout=10 + ) + + if response.status_code == 200: + data = response.json() + + # Cache the response + self._save_to_cache(query, 'api_ninjas', data) + + # Parse and return standardized format + return self._parse_api_response(data) + else: + print(f"API Error: {response.status_code}") + return [] + + except requests.exceptions.RequestException as e: + print(f"API Request failed: {e}") + return [] + + def _get_from_cache(self, query): + """Check if query exists in cache and is not expired""" + cache_entry = APICache.query.filter_by(query=query.lower()).first() + + if cache_entry: + # Check if cache is still valid (30 days) + age = datetime.utcnow() - cache_entry.cached_at + if age.days < self.cache_duration_days: + data = json.loads(cache_entry.response_json) + return self._parse_api_response(data) + + return None + + def _save_to_cache(self, query, source, data): + """Save API response to cache""" + try: + cache_entry = APICache.query.filter_by(query=query.lower()).first() + + if cache_entry: + # Update existing cache + cache_entry.response_json = json.dumps(data) + cache_entry.cached_at = datetime.utcnow() + else: + # Create new cache entry + cache_entry = APICache( + query=query.lower(), + api_source=source, + response_json=json.dumps(data), + cached_at=datetime.utcnow() + ) + db.session.add(cache_entry) + + db.session.commit() + except Exception as e: + print(f"Cache save failed: {e}") + db.session.rollback() + + def _parse_api_response(self, data): + """Parse API response into standardized format""" + foods = [] + + for item in data: + food = { + 'name': item.get('name', '').title(), + 'calories': item.get('calories', 0), + 'protein_g': item.get('protein_g', 0), + 'carbs_g': item.get('carbohydrates_total_g', 0), + 'fat_g': item.get('fat_total_g', 0), + 'fiber_g': item.get('fiber_g', 0), + 'sugar_g': item.get('sugar_g', 0), + 'sodium_mg': item.get('sodium_mg', 0), + 'serving_size_g': item.get('serving_size_g', 100), + 'source': 'api_ninjas' + } + foods.append(food) + + return foods + + def save_food_to_db(self, food_data): + """Save a food item to the database""" + try: + # Check if food already exists + existing = FoodItem.query.filter_by( + name=food_data['name'], + source=food_data.get('source', 'api') + ).first() + + if existing: + return existing + + # Create new food item + food = FoodItem( + name=food_data['name'], + calories=food_data['calories'], + protein_g=food_data.get('protein_g', 0), + carbs_g=food_data.get('carbs_g', 0), + fat_g=food_data.get('fat_g', 0), + fiber_g=food_data.get('fiber_g', 0), + sugar_g=food_data.get('sugar_g', 0), + sodium_mg=food_data.get('sodium_mg', 0), + serving_size_g=food_data.get('serving_size_g', 100), + serving_description=food_data.get('serving_description', '1 serving'), + source=food_data.get('source', 'api'), + api_data=json.dumps(food_data) + ) + + db.session.add(food) + db.session.commit() + + return food + + except Exception as e: + print(f"Error saving food to DB: {e}") + db.session.rollback() + return None + +def search_all_sources(query, api_client): + """ + Search for food in all available sources + Priority: Local DB (Filipino) -> Local DB (cached) -> API + """ + results = [] + + # 1. Search Filipino foods first + filipino_foods = FoodItem.query.filter( + FoodItem.is_filipino == True, + db.or_( + FoodItem.name.ilike(f'%{query}%'), + FoodItem.name_tagalog.ilike(f'%{query}%') + ) + ).limit(5).all() + + for food in filipino_foods: + results.append({ + 'id': food.id, + 'name': food.name, + 'name_tagalog': food.name_tagalog, + 'calories': food.calories, + 'protein_g': food.protein_g, + 'carbs_g': food.carbs_g, + 'fat_g': food.fat_g, + 'serving_description': food.serving_description, + 'source': 'filipino', + 'category': food.category + }) + + # 2. Search other local foods + other_foods = FoodItem.query.filter( + FoodItem.is_filipino == False, + FoodItem.name.ilike(f'%{query}%') + ).limit(5).all() + + for food in other_foods: + results.append({ + 'id': food.id, + 'name': food.name, + 'calories': food.calories, + 'protein_g': food.protein_g, + 'carbs_g': food.carbs_g, + 'fat_g': food.fat_g, + 'serving_description': food.serving_description, + 'source': food.source + }) + + # 3. If not enough results, search API + if len(results) < 3 and api_client.api_key: + api_results = api_client.search_food(query) + for food_data in api_results[:5]: + results.append({ + 'name': food_data['name'], + 'calories': food_data['calories'], + 'protein_g': food_data['protein_g'], + 'carbs_g': food_data['carbs_g'], + 'fat_g': food_data['fat_g'], + 'serving_size_g': food_data['serving_size_g'], + 'source': 'api', + 'api_data': food_data + }) + + return results diff --git a/calorie_tracker_app/app.py b/calorie_tracker_app/app.py new file mode 100644 index 0000000..02198ea --- /dev/null +++ b/calorie_tracker_app/app.py @@ -0,0 +1,494 @@ +from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session +from flask_login import LoginManager, login_user, logout_user, login_required, current_user +from werkzeug.security import generate_password_hash, check_password_hash +from datetime import date, datetime, timedelta +import os + +from config import Config +from models import db, User, FoodItem, Meal, MealFood, WaterLog, WeightLog, MealPlan, PlannedFood, UserGoal +from api_client import NutritionAPI, search_all_sources +from utils import ( + calculate_daily_totals, calculate_water_total, get_weight_trend, + get_calorie_trend, update_daily_summary, calculate_bmr, calculate_tdee, + calculate_macro_targets, get_macro_percentages, suggest_foods_for_macros +) + +app = Flask(__name__) +app.config.from_object(Config) + +# Initialize extensions +db.init_app(app) +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = 'login' + +# Initialize API client +api_client = NutritionAPI(app.config['API_NINJAS_KEY']) + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + +# ==================== ROUTES ==================== + +@app.route('/') +@login_required +def index(): + """Dashboard - Today's summary""" + today = date.today() + + # Get daily totals + nutrition = calculate_daily_totals(current_user.id, today) + water = calculate_water_total(current_user.id, today) + + # Get user goals + goals = UserGoal.query.filter_by(user_id=current_user.id).first() + if not goals: + goals = UserGoal( + user_id=current_user.id, + target_protein_g=150, + target_carbs_g=200, + target_fat_g=60, + target_water_ml=2000 + ) + db.session.add(goals) + db.session.commit() + + # Get weight info + weight_log_today = WeightLog.query.filter_by(user_id=current_user.id, date=today).first() + weight_log_yesterday = WeightLog.query.filter_by( + user_id=current_user.id, + date=today - timedelta(days=1) + ).first() + + weight_change = None + if weight_log_today and weight_log_yesterday: + weight_change = weight_log_today.weight_kg - weight_log_yesterday.weight_kg + + # Calculate remaining + remaining = { + 'calories': current_user.target_daily_calories - nutrition['calories'], + 'protein': goals.target_protein_g - nutrition['protein'], + 'carbs': goals.target_carbs_g - nutrition['carbs'], + 'fat': goals.target_fat_g - nutrition['fat'], + 'water': goals.target_water_ml - water['total_ml'] + } + + # Get macro percentages + macro_percentages = get_macro_percentages( + nutrition['protein'], + nutrition['carbs'], + nutrition['fat'] + ) + + # Get trends + weight_trend = get_weight_trend(current_user.id, 7) + calorie_trend = get_calorie_trend(current_user.id, 7) + + # Suggestions + suggestions = suggest_foods_for_macros( + remaining['protein'], + remaining['carbs'], + remaining['fat'] + ) + + return render_template('dashboard.html', + nutrition=nutrition, + water=water, + goals=goals, + remaining=remaining, + macro_percentages=macro_percentages, + weight_today=weight_log_today, + weight_change=weight_change, + weight_trend=weight_trend, + calorie_trend=calorie_trend, + suggestions=suggestions, + today=today) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + """User login""" + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + + user = User.query.filter_by(username=username).first() + + if user and check_password_hash(user.password, password): + login_user(user) + return redirect(url_for('index')) + else: + flash('Invalid username or password', 'error') + + return render_template('login.html') + +@app.route('/register', methods=['GET', 'POST']) +def register(): + """User registration""" + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + name = request.form.get('name') + + if User.query.filter_by(username=username).first(): + flash('Username already exists', 'error') + return redirect(url_for('register')) + + user = User( + username=username, + password=generate_password_hash(password), + name=name + ) + + db.session.add(user) + db.session.commit() + + flash('Registration successful! Please login.', 'success') + return redirect(url_for('login')) + + return render_template('register.html') + +@app.route('/logout') +@login_required +def logout(): + """User logout""" + logout_user() + return redirect(url_for('login')) + +@app.route('/add-meal', methods=['GET', 'POST']) +@login_required +def add_meal(): + """Add a meal""" + if request.method == 'POST': + meal_date = request.form.get('date', date.today()) + meal_type = request.form.get('meal_type') + meal_time = request.form.get('time') + + # Convert date string to date object + if isinstance(meal_date, str): + meal_date = datetime.strptime(meal_date, '%Y-%m-%d').date() + + # Convert time string to time object + meal_time_obj = None + if meal_time: + meal_time_obj = datetime.strptime(meal_time, '%H:%M').time() + + # Create meal + meal = Meal( + user_id=current_user.id, + date=meal_date, + meal_type=meal_type, + time=meal_time_obj + ) + db.session.add(meal) + db.session.flush() # Get meal ID + + # Add foods to meal + food_ids = request.form.getlist('food_id[]') + quantities = request.form.getlist('quantity[]') + + for food_id, quantity in zip(food_ids, quantities): + if food_id and quantity: + meal_food = MealFood( + meal_id=meal.id, + food_id=int(food_id), + quantity=float(quantity) + ) + meal_food.calculate_nutrition() + db.session.add(meal_food) + + db.session.commit() + update_daily_summary(current_user.id, meal_date) + + flash('Meal added successfully!', 'success') + return redirect(url_for('index')) + + # GET request - show form + return render_template('add_meal.html', today=date.today()) + +@app.route('/api/search-food') +@login_required +def api_search_food(): + """API endpoint for food search (AJAX)""" + query = request.args.get('q', '') + + if len(query) < 2: + return jsonify([]) + + results = search_all_sources(query, api_client) + return jsonify(results) + +@app.route('/api/add-food', methods=['POST']) +@login_required +def api_add_food(): + """API endpoint to add a food from API to database""" + data = request.json + + # Save food to database + food = FoodItem( + name=data['name'], + calories=data['calories'], + protein_g=data.get('protein_g', 0), + carbs_g=data.get('carbs_g', 0), + fat_g=data.get('fat_g', 0), + serving_size_g=data.get('serving_size_g', 100), + serving_description=data.get('serving_description', '1 serving'), + source='api' + ) + + db.session.add(food) + db.session.commit() + + return jsonify({ + 'success': True, + 'food_id': food.id, + 'name': food.name + }) + +@app.route('/add-water', methods=['POST']) +@login_required +def add_water(): + """Add water intake""" + amount_ml = int(request.form.get('amount_ml', 250)) + log_date = request.form.get('date', date.today()) + + if isinstance(log_date, str): + log_date = datetime.strptime(log_date, '%Y-%m-%d').date() + + water_log = WaterLog( + user_id=current_user.id, + date=log_date, + amount_ml=amount_ml, + time=datetime.now().time() + ) + + db.session.add(water_log) + db.session.commit() + + update_daily_summary(current_user.id, log_date) + + flash(f'Added {amount_ml}ml of water!', 'success') + return redirect(url_for('index')) + +@app.route('/add-weight', methods=['POST']) +@login_required +def add_weight(): + """Add weight log""" + weight_kg = float(request.form.get('weight_kg')) + log_date = request.form.get('date', date.today()) + + if isinstance(log_date, str): + log_date = datetime.strptime(log_date, '%Y-%m-%d').date() + + # Check if weight log already exists for today + existing = WeightLog.query.filter_by(user_id=current_user.id, date=log_date).first() + + if existing: + existing.weight_kg = weight_kg + existing.time = datetime.now().time() + else: + weight_log = WeightLog( + user_id=current_user.id, + date=log_date, + weight_kg=weight_kg, + time=datetime.now().time() + ) + db.session.add(weight_log) + + db.session.commit() + update_daily_summary(current_user.id, log_date) + + flash(f'Weight logged: {weight_kg}kg', 'success') + return redirect(url_for('index')) + +@app.route('/meal-planner') +@login_required +def meal_planner(): + """Meal planner page""" + # Get date range (current week) + today = date.today() + start_date = today - timedelta(days=today.weekday()) # Monday + dates = [start_date + timedelta(days=i) for i in range(7)] + + # Get meal plans for the week + meal_plans = {} + for d in dates: + plans = MealPlan.query.filter_by(user_id=current_user.id, date=d).all() + meal_plans[d.isoformat()] = [ + { + 'id': p.id, + 'meal_type': p.meal_type, + 'is_completed': p.is_completed, + 'foods': [ + { + 'name': pf.food.name, + 'quantity': pf.quantity + } + for pf in p.foods + ], + 'totals': p.calculate_totals() + } + for p in plans + ] + + return render_template('meal_planner.html', dates=dates, meal_plans=meal_plans, today=today) + +@app.route('/foods') +@login_required +def foods(): + """Food database page""" + category = request.args.get('category', 'all') + search_query = request.args.get('q', '') + + query = FoodItem.query + + if category != 'all': + query = query.filter_by(category=category) + + if search_query: + query = query.filter( + db.or_( + FoodItem.name.ilike(f'%{search_query}%'), + FoodItem.name_tagalog.ilike(f'%{search_query}%') + ) + ) + + # Filipino foods first + filipino_foods = query.filter_by(is_filipino=True).all() + other_foods = query.filter_by(is_filipino=False).limit(20).all() + + categories = ['all', 'kanin', 'ulam', 'sabaw', 'gulay', 'meryenda', 'almusal'] + + return render_template('foods.html', + filipino_foods=filipino_foods, + other_foods=other_foods, + categories=categories, + current_category=category, + search_query=search_query) + +@app.route('/progress') +@login_required +def progress(): + """Progress tracking page""" + days = int(request.args.get('days', 30)) + + weight_trend = get_weight_trend(current_user.id, days) + calorie_trend = get_calorie_trend(current_user.id, days) + + # Calculate averages + if calorie_trend: + avg_calories = sum(d['calories'] for d in calorie_trend) / len(calorie_trend) + avg_protein = sum(d['protein'] for d in calorie_trend) / len(calorie_trend) + avg_carbs = sum(d['carbs'] for d in calorie_trend) / len(calorie_trend) + avg_fat = sum(d['fat'] for d in calorie_trend) / len(calorie_trend) + else: + avg_calories = avg_protein = avg_carbs = avg_fat = 0 + + # Weight change + weight_change = None + if len(weight_trend) >= 2: + weight_change = weight_trend[-1]['weight_kg'] - weight_trend[0]['weight_kg'] + + return render_template('progress.html', + weight_trend=weight_trend, + calorie_trend=calorie_trend, + avg_calories=avg_calories, + avg_protein=avg_protein, + avg_carbs=avg_carbs, + avg_fat=avg_fat, + weight_change=weight_change, + days=days) + +@app.route('/goals', methods=['GET', 'POST']) +@login_required +def goals(): + """Goals and settings page""" + if request.method == 'POST': + # Update user info + current_user.age = int(request.form.get('age', 25)) + current_user.gender = request.form.get('gender', 'male') + current_user.height_cm = float(request.form.get('height_cm', 170)) + current_user.weight_kg = float(request.form.get('weight_kg', 70)) + current_user.activity_level = request.form.get('activity_level', 'moderate') + + # Calculate targets + bmr = calculate_bmr( + current_user.weight_kg, + current_user.height_cm, + current_user.age, + current_user.gender + ) + tdee = calculate_tdee(bmr, current_user.activity_level) + + # Get goal type + goal_type = request.form.get('goal_type', 'recomp') + + # Adjust calories based on goal + if goal_type == 'weight_loss': + target_calories = tdee - 500 + elif goal_type == 'muscle_gain': + target_calories = tdee + 300 + else: # recomp + target_calories = tdee + + current_user.target_daily_calories = int(target_calories) + + # Update or create goals + user_goals = UserGoal.query.filter_by(user_id=current_user.id).first() + if not user_goals: + user_goals = UserGoal(user_id=current_user.id) + db.session.add(user_goals) + + user_goals.goal_type = goal_type + user_goals.target_weight_kg = float(request.form.get('target_weight_kg', 70)) + + # Calculate macros + macros = calculate_macro_targets(current_user.weight_kg, goal_type) + user_goals.target_protein_g = macros['protein_g'] + user_goals.target_carbs_g = macros['carbs_g'] + user_goals.target_fat_g = macros['fat_g'] + user_goals.target_water_ml = int(request.form.get('target_water_ml', 2000)) + + db.session.commit() + + flash('Goals updated successfully!', 'success') + return redirect(url_for('goals')) + + # GET request + user_goals = UserGoal.query.filter_by(user_id=current_user.id).first() + + # Calculate current BMR and TDEE + bmr = tdee = None + if current_user.weight_kg and current_user.height_cm and current_user.age: + bmr = calculate_bmr( + current_user.weight_kg, + current_user.height_cm, + current_user.age, + current_user.gender or 'male' + ) + tdee = calculate_tdee(bmr, current_user.activity_level or 'moderate') + + return render_template('goals.html', + user=current_user, + goals=user_goals, + bmr=bmr, + tdee=tdee) + +# ==================== DATABASE INITIALIZATION ==================== + +@app.cli.command() +def init_db(): + """Initialize the database""" + db.create_all() + print("Database initialized!") + +@app.cli.command() +def seed_db(): + """Seed the database with Filipino foods""" + from seed_data import seed_filipino_foods + seed_filipino_foods() + +if __name__ == '__main__': + with app.app_context(): + db.create_all() + app.run(debug=True, host='127.0.0.1', port=5001) diff --git a/calorie_tracker_app/config.py b/calorie_tracker_app/config.py new file mode 100644 index 0000000..99a6b7f --- /dev/null +++ b/calorie_tracker_app/config.py @@ -0,0 +1,16 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Config: + SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') + # Use environment variable for DB URI if available, otherwise default to local file + # For Docker, we'll map a volume to /app/data and use sqlite:////app/data/calorie_tracker.db + SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL', 'sqlite:///calorie_tracker.db') + SQLALCHEMY_TRACK_MODIFICATIONS = False + API_NINJAS_KEY = os.getenv('API_NINJAS_KEY', '') + + # User defaults + DEFAULT_WATER_GOAL_ML = 2000 + DEFAULT_CALORIE_TARGET = 2000 diff --git a/calorie_tracker_app/models.py b/calorie_tracker_app/models.py new file mode 100644 index 0000000..d900107 --- /dev/null +++ b/calorie_tracker_app/models.py @@ -0,0 +1,186 @@ +from flask_sqlalchemy import SQLAlchemy +from flask_login import UserMixin +from datetime import datetime, date + +db = SQLAlchemy() + +class User(UserMixin, db.Model): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password = db.Column(db.String(200), nullable=False) + name = db.Column(db.String(100)) + age = db.Column(db.Integer) + gender = db.Column(db.String(10)) + height_cm = db.Column(db.Float) + weight_kg = db.Column(db.Float) + activity_level = db.Column(db.String(20), default='moderate') + target_daily_calories = db.Column(db.Integer, default=2000) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + # Relationships + meals = db.relationship('Meal', backref='user', lazy=True, cascade='all, delete-orphan') + weight_logs = db.relationship('WeightLog', backref='user', lazy=True, cascade='all, delete-orphan') + water_logs = db.relationship('WaterLog', backref='user', lazy=True, cascade='all, delete-orphan') + meal_plans = db.relationship('MealPlan', backref='user', lazy=True, cascade='all, delete-orphan') + goals = db.relationship('UserGoal', backref='user', uselist=False, cascade='all, delete-orphan') + +class FoodItem(db.Model): + __tablename__ = 'food_items' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(200), nullable=False) + name_tagalog = db.Column(db.String(200)) + category = db.Column(db.String(50)) + calories = db.Column(db.Float, nullable=False) + protein_g = db.Column(db.Float, default=0) + carbs_g = db.Column(db.Float, default=0) + fat_g = db.Column(db.Float, default=0) + fiber_g = db.Column(db.Float, default=0) + sugar_g = db.Column(db.Float, default=0) + sodium_mg = db.Column(db.Float, default=0) + serving_size_g = db.Column(db.Float, default=100) + serving_description = db.Column(db.String(100)) + source = db.Column(db.String(20), default='manual') # 'manual', 'api', 'filipino' + is_filipino = db.Column(db.Boolean, default=False) + is_favorite = db.Column(db.Boolean, default=False) + api_data = db.Column(db.Text) + last_updated = db.Column(db.DateTime, default=datetime.utcnow) + + # Relationships + meal_foods = db.relationship('MealFood', backref='food', lazy=True) + planned_foods = db.relationship('PlannedFood', backref='food', lazy=True) + +class Meal(db.Model): + __tablename__ = 'meals' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date = db.Column(db.Date, nullable=False, default=date.today) + meal_type = db.Column(db.String(20), nullable=False) # breakfast, lunch, dinner, snack + time = db.Column(db.Time) + notes = db.Column(db.Text) + + # Relationships + foods = db.relationship('MealFood', backref='meal', lazy=True, cascade='all, delete-orphan') + + def calculate_totals(self): + """Calculate total nutrition for this meal""" + totals = { + 'calories': 0, + 'protein': 0, + 'carbs': 0, + 'fat': 0 + } + for mf in self.foods: + totals['calories'] += mf.calories_consumed + totals['protein'] += mf.protein_consumed + totals['carbs'] += mf.carbs_consumed + totals['fat'] += mf.fat_consumed + return totals + +class MealFood(db.Model): + __tablename__ = 'meal_foods' + id = db.Column(db.Integer, primary_key=True) + meal_id = db.Column(db.Integer, db.ForeignKey('meals.id'), nullable=False) + food_id = db.Column(db.Integer, db.ForeignKey('food_items.id'), nullable=False) + quantity = db.Column(db.Float, nullable=False, default=1.0) # servings + quantity_grams = db.Column(db.Float) + calories_consumed = db.Column(db.Float) + protein_consumed = db.Column(db.Float) + carbs_consumed = db.Column(db.Float) + fat_consumed = db.Column(db.Float) + + def calculate_nutrition(self): + """Calculate nutrition based on quantity""" + self.calories_consumed = self.food.calories * self.quantity + self.protein_consumed = self.food.protein_g * self.quantity + self.carbs_consumed = self.food.carbs_g * self.quantity + self.fat_consumed = self.food.fat_g * self.quantity + if self.food.serving_size_g: + self.quantity_grams = self.food.serving_size_g * self.quantity + +class WaterLog(db.Model): + __tablename__ = 'water_logs' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date = db.Column(db.Date, nullable=False, default=date.today) + amount_ml = db.Column(db.Integer, nullable=False) + time = db.Column(db.Time, default=datetime.now().time) + +class WeightLog(db.Model): + __tablename__ = 'weight_logs' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date = db.Column(db.Date, nullable=False, unique=True, default=date.today) + weight_kg = db.Column(db.Float, nullable=False) + body_fat_percentage = db.Column(db.Float) + notes = db.Column(db.Text) + time = db.Column(db.Time, default=datetime.now().time) + +class MealPlan(db.Model): + __tablename__ = 'meal_plans' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date = db.Column(db.Date, nullable=False) + meal_type = db.Column(db.String(20), nullable=False) + is_completed = db.Column(db.Boolean, default=False) + notes = db.Column(db.Text) + + # Relationships + foods = db.relationship('PlannedFood', backref='meal_plan', lazy=True, cascade='all, delete-orphan') + + def calculate_totals(self): + """Calculate total nutrition for this planned meal""" + totals = { + 'calories': 0, + 'protein': 0, + 'carbs': 0, + 'fat': 0 + } + for pf in self.foods: + totals['calories'] += pf.food.calories * pf.quantity + totals['protein'] += pf.food.protein_g * pf.quantity + totals['carbs'] += pf.food.carbs_g * pf.quantity + totals['fat'] += pf.food.fat_g * pf.quantity + return totals + +class PlannedFood(db.Model): + __tablename__ = 'planned_foods' + id = db.Column(db.Integer, primary_key=True) + meal_plan_id = db.Column(db.Integer, db.ForeignKey('meal_plans.id'), nullable=False) + food_id = db.Column(db.Integer, db.ForeignKey('food_items.id'), nullable=False) + quantity = db.Column(db.Float, nullable=False, default=1.0) + +class UserGoal(db.Model): + __tablename__ = 'user_goals' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, unique=True) + goal_type = db.Column(db.String(20), default='recomp') # weight_loss, muscle_gain, recomp + target_weight_kg = db.Column(db.Float) + weekly_goal_kg = db.Column(db.Float, default=0.5) + target_protein_g = db.Column(db.Integer, default=150) + target_carbs_g = db.Column(db.Integer, default=200) + target_fat_g = db.Column(db.Integer, default=60) + target_water_ml = db.Column(db.Integer, default=2000) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + +class DailySummary(db.Model): + __tablename__ = 'daily_summary' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + date = db.Column(db.Date, nullable=False, unique=True) + total_calories = db.Column(db.Float, default=0) + total_protein_g = db.Column(db.Float, default=0) + total_carbs_g = db.Column(db.Float, default=0) + total_fat_g = db.Column(db.Float, default=0) + total_water_ml = db.Column(db.Integer, default=0) + calories_remaining = db.Column(db.Float) + weight_kg = db.Column(db.Float) + notes = db.Column(db.Text) + +class APICache(db.Model): + __tablename__ = 'api_cache' + id = db.Column(db.Integer, primary_key=True) + query = db.Column(db.String(200), nullable=False, unique=True) + api_source = db.Column(db.String(50)) + response_json = db.Column(db.Text) + cached_at = db.Column(db.DateTime, default=datetime.utcnow) diff --git a/calorie_tracker_app/requirements.txt b/calorie_tracker_app/requirements.txt new file mode 100644 index 0000000..677620d --- /dev/null +++ b/calorie_tracker_app/requirements.txt @@ -0,0 +1,7 @@ +Flask==3.0.0 +Flask-SQLAlchemy==3.1.1 +Flask-Login==0.6.3 +python-dotenv==1.0.0 +requests==2.31.0 +Werkzeug==3.0.1 +gunicorn==21.2.0 diff --git a/calorie_tracker_app/run_app.bat b/calorie_tracker_app/run_app.bat new file mode 100644 index 0000000..b9b515f --- /dev/null +++ b/calorie_tracker_app/run_app.bat @@ -0,0 +1,9 @@ +@echo off +echo Starting Calorie Tracker App... +echo. +echo If you see "ModuleNotFoundError", make sure you ran: +echo pip install -r requirements.txt +echo. +echo Opening app on http://127.0.0.1:5001 +venv\Scripts\python app.py +pause \ No newline at end of file diff --git a/calorie_tracker_app/seed_data.py b/calorie_tracker_app/seed_data.py new file mode 100644 index 0000000..7b4f414 --- /dev/null +++ b/calorie_tracker_app/seed_data.py @@ -0,0 +1,368 @@ +from models import FoodItem, db + +def seed_filipino_foods(): + """Populate database with common Filipino foods""" + + filipino_foods = [ + # Rice (Kanin) + { + 'name': 'White Rice', + 'name_tagalog': 'Kanin', + 'category': 'kanin', + 'calories': 206, + 'protein_g': 4.3, + 'carbs_g': 45, + 'fat_g': 0.4, + 'serving_description': '1 cup cooked', + 'serving_size_g': 158 + }, + { + 'name': 'Fried Rice', + 'name_tagalog': 'Sinangag', + 'category': 'kanin', + 'calories': 280, + 'protein_g': 5, + 'carbs_g': 42, + 'fat_g': 10, + 'serving_description': '1 cup', + 'serving_size_g': 170 + }, + + # Main Dishes (Ulam) + { + 'name': 'Chicken Adobo', + 'name_tagalog': 'Adobong Manok', + 'category': 'ulam', + 'calories': 350, + 'protein_g': 35, + 'carbs_g': 5, + 'fat_g': 20, + 'serving_description': '1 serving (2 pieces)', + 'serving_size_g': 200 + }, + { + 'name': 'Pork Sinigang', + 'name_tagalog': 'Sinigang na Baboy', + 'category': 'sabaw', + 'calories': 280, + 'protein_g': 25, + 'carbs_g': 10, + 'fat_g': 15, + 'serving_description': '1 bowl', + 'serving_size_g': 350 + }, + { + 'name': 'Chicken Tinola', + 'name_tagalog': 'Tinolang Manok', + 'category': 'sabaw', + 'calories': 200, + 'protein_g': 28, + 'carbs_g': 8, + 'fat_g': 6, + 'serving_description': '1 bowl', + 'serving_size_g': 350 + }, + { + 'name': 'Bicol Express', + 'name_tagalog': 'Bicol Express', + 'category': 'ulam', + 'calories': 400, + 'protein_g': 20, + 'carbs_g': 10, + 'fat_g': 32, + 'serving_description': '1 serving', + 'serving_size_g': 200 + }, + { + 'name': 'Pork Sisig', + 'name_tagalog': 'Sisig', + 'category': 'ulam', + 'calories': 450, + 'protein_g': 25, + 'carbs_g': 8, + 'fat_g': 35, + 'serving_description': '1 serving', + 'serving_size_g': 180 + }, + { + 'name': 'Menudo', + 'name_tagalog': 'Menudo', + 'category': 'ulam', + 'calories': 320, + 'protein_g': 22, + 'carbs_g': 12, + 'fat_g': 20, + 'serving_description': '1 serving', + 'serving_size_g': 200 + }, + { + 'name': 'Kare-Kare', + 'name_tagalog': 'Kare-Kare', + 'category': 'ulam', + 'calories': 380, + 'protein_g': 24, + 'carbs_g': 18, + 'fat_g': 25, + 'serving_description': '1 serving', + 'serving_size_g': 250 + }, + { + 'name': 'Lechon Kawali', + 'name_tagalog': 'Lechon Kawali', + 'category': 'ulam', + 'calories': 500, + 'protein_g': 30, + 'carbs_g': 2, + 'fat_g': 42, + 'serving_description': '1 serving', + 'serving_size_g': 150 + }, + { + 'name': 'Pork Nilaga', + 'name_tagalog': 'Nilagang Baboy', + 'category': 'sabaw', + 'calories': 280, + 'protein_g': 28, + 'carbs_g': 12, + 'fat_g': 14, + 'serving_description': '1 bowl', + 'serving_size_g': 350 + }, + { + 'name': 'Beef Bulalo', + 'name_tagalog': 'Bulalo', + 'category': 'sabaw', + 'calories': 350, + 'protein_g': 32, + 'carbs_g': 8, + 'fat_g': 22, + 'serving_description': '1 bowl', + 'serving_size_g': 400 + }, + + # Vegetables (Gulay) + { + 'name': 'Pinakbet', + 'name_tagalog': 'Pinakbet', + 'category': 'gulay', + 'calories': 150, + 'protein_g': 5, + 'carbs_g': 20, + 'fat_g': 6, + 'serving_description': '1 cup', + 'serving_size_g': 200 + }, + { + 'name': 'Laing', + 'name_tagalog': 'Laing', + 'category': 'gulay', + 'calories': 180, + 'protein_g': 6, + 'carbs_g': 15, + 'fat_g': 12, + 'serving_description': '1 cup', + 'serving_size_g': 180 + }, + { + 'name': 'Ginisang Monggo', + 'name_tagalog': 'Ginisang Monggo', + 'category': 'gulay', + 'calories': 200, + 'protein_g': 12, + 'carbs_g': 30, + 'fat_g': 4, + 'serving_description': '1 cup', + 'serving_size_g': 220 + }, + + # Breakfast (Almusal) + { + 'name': 'Beef Tapa with Rice and Egg', + 'name_tagalog': 'Tapsilog', + 'category': 'almusal', + 'calories': 650, + 'protein_g': 45, + 'carbs_g': 60, + 'fat_g': 25, + 'serving_description': '1 plate', + 'serving_size_g': 400 + }, + { + 'name': 'Longganisa with Rice and Egg', + 'name_tagalog': 'Longsilog', + 'category': 'almusal', + 'calories': 700, + 'protein_g': 38, + 'carbs_g': 65, + 'fat_g': 32, + 'serving_description': '1 plate', + 'serving_size_g': 420 + }, + { + 'name': 'Tocino with Rice and Egg', + 'name_tagalog': 'Tocilog', + 'category': 'almusal', + 'calories': 680, + 'protein_g': 42, + 'carbs_g': 62, + 'fat_g': 28, + 'serving_description': '1 plate', + 'serving_size_g': 400 + }, + { + 'name': 'Fried Egg', + 'name_tagalog': 'Pritong Itlog', + 'category': 'almusal', + 'calories': 90, + 'protein_g': 6, + 'carbs_g': 1, + 'fat_g': 7, + 'serving_description': '1 egg', + 'serving_size_g': 50 + }, + + # Snacks (Meryenda) + { + 'name': 'Pandesal', + 'name_tagalog': 'Pandesal', + 'category': 'meryenda', + 'calories': 120, + 'protein_g': 3, + 'carbs_g': 22, + 'fat_g': 2, + 'serving_description': '1 piece', + 'serving_size_g': 40 + }, + { + 'name': 'Turon', + 'name_tagalog': 'Turon', + 'category': 'meryenda', + 'calories': 180, + 'protein_g': 2, + 'carbs_g': 35, + 'fat_g': 5, + 'serving_description': '1 piece', + 'serving_size_g': 80 + }, + { + 'name': 'Bibingka', + 'name_tagalog': 'Bibingka', + 'category': 'meryenda', + 'calories': 220, + 'protein_g': 5, + 'carbs_g': 38, + 'fat_g': 6, + 'serving_description': '1 piece', + 'serving_size_g': 100 + }, + { + 'name': 'Puto', + 'name_tagalog': 'Puto', + 'category': 'meryenda', + 'calories': 90, + 'protein_g': 2, + 'carbs_g': 18, + 'fat_g': 1, + 'serving_description': '1 piece', + 'serving_size_g': 40 + }, + { + 'name': 'Lumpia', + 'name_tagalog': 'Lumpia', + 'category': 'meryenda', + 'calories': 100, + 'protein_g': 4, + 'carbs_g': 10, + 'fat_g': 5, + 'serving_description': '1 piece', + 'serving_size_g': 50 + }, + { + 'name': 'Banana Cue', + 'name_tagalog': 'Banana Cue', + 'category': 'meryenda', + 'calories': 150, + 'protein_g': 1, + 'carbs_g': 32, + 'fat_g': 4, + 'serving_description': '1 piece', + 'serving_size_g': 100 + }, + + # Proteins + { + 'name': 'Grilled Tilapia', + 'name_tagalog': 'Inihaw na Tilapia', + 'category': 'ulam', + 'calories': 180, + 'protein_g': 32, + 'carbs_g': 0, + 'fat_g': 5, + 'serving_description': '1 whole fish', + 'serving_size_g': 150 + }, + { + 'name': 'Grilled Chicken', + 'name_tagalog': 'Inihaw na Manok', + 'category': 'ulam', + 'calories': 280, + 'protein_g': 40, + 'carbs_g': 0, + 'fat_g': 13, + 'serving_description': '1 breast', + 'serving_size_g': 150 + }, + { + 'name': 'Fried Bangus', + 'name_tagalog': 'Pritong Bangus', + 'category': 'ulam', + 'calories': 220, + 'protein_g': 28, + 'carbs_g': 0, + 'fat_g': 12, + 'serving_description': '1 piece', + 'serving_size_g': 120 + } + ] + + added_count = 0 + + for food_data in filipino_foods: + # Check if already exists + existing = FoodItem.query.filter_by( + name=food_data['name'], + is_filipino=True + ).first() + + if not existing: + food = FoodItem( + name=food_data['name'], + name_tagalog=food_data.get('name_tagalog'), + category=food_data['category'], + calories=food_data['calories'], + protein_g=food_data['protein_g'], + carbs_g=food_data['carbs_g'], + fat_g=food_data['fat_g'], + fiber_g=food_data.get('fiber_g', 0), + sugar_g=food_data.get('sugar_g', 0), + sodium_mg=food_data.get('sodium_mg', 0), + serving_size_g=food_data['serving_size_g'], + serving_description=food_data['serving_description'], + source='filipino', + is_filipino=True + ) + db.session.add(food) + added_count += 1 + + try: + db.session.commit() + print(f"Successfully added {added_count} Filipino foods to the database!") + except Exception as e: + db.session.rollback() + print(f"Error seeding Filipino foods: {e}") + +if __name__ == '__main__': + from app import app, db + + with app.app_context(): + seed_filipino_foods() diff --git a/calorie_tracker_app/seed_db.bat b/calorie_tracker_app/seed_db.bat new file mode 100644 index 0000000..253d927 --- /dev/null +++ b/calorie_tracker_app/seed_db.bat @@ -0,0 +1,6 @@ +@echo off +echo Seeding Filipino Foods database... +venv\Scripts\python seed_data.py +echo. +echo Done! +pause \ No newline at end of file diff --git a/calorie_tracker_app/templates/add_meal.html b/calorie_tracker_app/templates/add_meal.html new file mode 100644 index 0000000..c868b57 --- /dev/null +++ b/calorie_tracker_app/templates/add_meal.html @@ -0,0 +1,221 @@ +{% extends "base.html" %} + +{% block title %}Add Meal - Calorie Tracker{% endblock %} + +{% block content %} +
+

Add Meal

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + + +
+ + +
+

Selected Foods

+
+

No foods added yet. Search and add foods above.

+
+
+ + +
+

Meal Summary

+
+
+

Calories

+

0

+
+
+

Protein

+

0g

+
+
+

Carbs

+

0g

+
+
+

Fat

+

0g

+
+
+
+ + +
+ + Cancel + + +
+
+
+ + +{% endblock %} diff --git a/calorie_tracker_app/templates/base.html b/calorie_tracker_app/templates/base.html new file mode 100644 index 0000000..f338e5c --- /dev/null +++ b/calorie_tracker_app/templates/base.html @@ -0,0 +1,92 @@ + + + + + + {% block title %}Calorie Tracker{% endblock %} + + + + + + + {% if current_user.is_authenticated %} + + + {% endif %} + + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} + + {% endfor %} +
+ {% endif %} + {% endwith %} + + +
+ {% block content %}{% endblock %} +
+ + +
+

© 2026 Calorie Tracker - Filipino Food Edition

+
+ + {% block scripts %}{% endblock %} + + diff --git a/calorie_tracker_app/templates/dashboard.html b/calorie_tracker_app/templates/dashboard.html new file mode 100644 index 0000000..aca017f --- /dev/null +++ b/calorie_tracker_app/templates/dashboard.html @@ -0,0 +1,300 @@ +{% extends "base.html" %} + +{% block title %}Dashboard - Calorie Tracker{% endblock %} + +{% block content %} +
+ +
+
+

Today's Summary

+

{{ today.strftime('%A, %B %d, %Y') }}

+
+ +
+ + +
+ +
+
+
+

Calories

+

{{ nutrition.calories|round|int }}

+

/ {{ current_user.target_daily_calories }}

+
+ πŸ”₯ +
+ +
+ {% set cal_percent = (nutrition.calories / current_user.target_daily_calories * 100)|int if current_user.target_daily_calories > 0 else 0 %} +
+
+

+ {{ remaining.calories|round|int }} remaining +

+
+ + +
+
+
+

Protein

+

{{ nutrition.protein|round|int }}g

+

/ {{ goals.target_protein_g }}g

+
+ πŸ’ͺ +
+
+ {% set prot_percent = (nutrition.protein / goals.target_protein_g * 100)|int if goals.target_protein_g > 0 else 0 %} +
+
+

+ {{ remaining.protein|round|int }}g remaining +

+
+ + +
+
+
+ Carbs + {{ nutrition.carbs|round|int }}g / {{ goals.target_carbs_g }}g +
+
+ {% set carb_percent = (nutrition.carbs / goals.target_carbs_g * 100)|int if goals.target_carbs_g > 0 else 0 %} +
+
+
+
+
+ Fat + {{ nutrition.fat|round|int }}g / {{ goals.target_fat_g }}g +
+
+ {% set fat_percent = (nutrition.fat / goals.target_fat_g * 100)|int if goals.target_fat_g > 0 else 0 %} +
+
+
+
+ + +
+
+
+ Water + {{ water.total_ml }}ml / {{ goals.target_water_ml }}ml +
+
+ {% set glasses_filled = (water.total_ml / 250)|int %} + {% for i in range(8) %} + πŸ’§ + {% endfor %} +
+ +
+ + +
+
+
+
+ Weight + {% if weight_today %} +
+ {{ weight_today.weight_kg }}kg + {% if weight_change %} + + {{ weight_change|round(1) }}kg + + {% endif %} +
+ {% else %} +
+ + +
+ {% endif %} +
+
+
+
+ + +
+
+

Macro Distribution

+ +
+ + +
+

πŸ’‘ Smart Suggestions

+ {% if suggestions %} +
+ {% for suggestion in suggestions %} +
+

{{ suggestion.category }}

+

Try: {{ suggestion.examples|join(', ') }}

+
+ {% endfor %} +
+ {% else %} +

You're on track! Keep up the good work! πŸŽ‰

+ {% endif %} +
+
+ + +
+
+

Today's Meals

+ + Add Meal +
+ + {% if nutrition.meals %} +
+ {% for meal in nutrition.meals %} +
+
+
+
+ + {% if meal.type == 'breakfast' %}πŸŒ…{% elif meal.type == 'lunch' %}🌞{% elif meal.type == 'dinner' %}πŸŒ™{% else %}πŸͺ{% endif %} + +

{{ meal.type }}

+ {% if meal.time %} + {{ meal.time }} + {% endif %} +
+
+ {% for food in meal.foods %} +

β€’ {{ food.name }} ({{ food.quantity }}x) - {{ food.calories|round|int }} cal

+ {% endfor %} +
+
+
+

{{ meal.totals.calories|round|int }}

+

calories

+

+ P: {{ meal.totals.protein|round|int }}g | + C: {{ meal.totals.carbs|round|int }}g | + F: {{ meal.totals.fat|round|int }}g +

+
+
+
+ {% endfor %} +
+ {% else %} +
+

🍽️

+

No meals logged yet today.

+ Add your first meal +
+ {% endif %} +
+ + +
+ +
+

πŸ“ˆ Calorie Trend (7 Days)

+ +
+ + +
+

βš–οΈ Weight Trend (7 Days)

+ +
+
+
+ + +{% endblock %} diff --git a/calorie_tracker_app/templates/foods.html b/calorie_tracker_app/templates/foods.html new file mode 100644 index 0000000..4dd0c21 --- /dev/null +++ b/calorie_tracker_app/templates/foods.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Foods Database{% endblock %} +{% block content %} +

Filipino Foods Database

+
+{% for food in filipino_foods %} +
+

{{ food.name }}

+

{{ food.name_tagalog }}

+

{{ food.calories|round|int }} cal

+
+{% endfor %} +
+{% endblock %} diff --git a/calorie_tracker_app/templates/goals.html b/calorie_tracker_app/templates/goals.html new file mode 100644 index 0000000..5c37e53 --- /dev/null +++ b/calorie_tracker_app/templates/goals.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block title %}Goals & Settings{% endblock %} +{% block content %} +

Goals & Settings

+
+
+
+ + +
+
+ + +
+
+ +
+{% endblock %} diff --git a/calorie_tracker_app/templates/login.html b/calorie_tracker_app/templates/login.html new file mode 100644 index 0000000..86fbf34 --- /dev/null +++ b/calorie_tracker_app/templates/login.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block title %}Login - Calorie Tracker{% endblock %} + +{% block content %} +
+
+

🍽️ Calorie Tracker

+

Filipino Food Edition

+ +
+
+ + +
+ +
+ + +
+ + +
+ +

+ Don't have an account? Register +

+
+
+{% endblock %} diff --git a/calorie_tracker_app/templates/meal_planner.html b/calorie_tracker_app/templates/meal_planner.html new file mode 100644 index 0000000..776eabf --- /dev/null +++ b/calorie_tracker_app/templates/meal_planner.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} +{% block title %}Meal Planner{% endblock %} +{% block content %} +

Meal Planner

+

Coming soon! Plan your meals for the week ahead.

+{% endblock %} diff --git a/calorie_tracker_app/templates/progress.html b/calorie_tracker_app/templates/progress.html new file mode 100644 index 0000000..2f8b74e --- /dev/null +++ b/calorie_tracker_app/templates/progress.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block title %}Progress{% endblock %} +{% block content %} +

Your Progress

+
+ +
+{% endblock %} diff --git a/calorie_tracker_app/templates/register.html b/calorie_tracker_app/templates/register.html new file mode 100644 index 0000000..29bd8f4 --- /dev/null +++ b/calorie_tracker_app/templates/register.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Register - Calorie Tracker{% endblock %} + +{% block content %} +
+
+

Create Account

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ +

+ Already have an account? Login +

+
+
+{% endblock %} diff --git a/calorie_tracker_app/utils.py b/calorie_tracker_app/utils.py new file mode 100644 index 0000000..066a190 --- /dev/null +++ b/calorie_tracker_app/utils.py @@ -0,0 +1,258 @@ +from datetime import date, timedelta +from models import db, Meal, WaterLog, WeightLog, DailySummary, UserGoal + +def calculate_bmr(weight_kg, height_cm, age, gender): + """ + Calculate Basal Metabolic Rate using Mifflin-St Jeor Equation + """ + if gender.lower() == 'male': + bmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age) + 5 + else: # female + bmr = (10 * weight_kg) + (6.25 * height_cm) - (5 * age) - 161 + + return round(bmr) + +def calculate_tdee(bmr, activity_level): + """ + Calculate Total Daily Energy Expenditure + """ + multipliers = { + 'sedentary': 1.2, + 'light': 1.375, + 'moderate': 1.55, + 'active': 1.725, + 'very_active': 1.9 + } + + multiplier = multipliers.get(activity_level, 1.55) + return round(bmr * multiplier) + +def calculate_macro_targets(weight_kg, goal_type='recomp'): + """ + Calculate macro targets based on body weight and goal + """ + if goal_type == 'muscle_gain': + protein = weight_kg * 2.4 # High protein for muscle building + carbs = weight_kg * 3.5 # Higher carbs for energy + fat = weight_kg * 1.0 # Moderate fat + elif goal_type == 'weight_loss': + protein = weight_kg * 2.2 # High protein to preserve muscle + carbs = weight_kg * 2.0 # Lower carbs for deficit + fat = weight_kg * 0.8 # Lower fat + else: # recomp (body recomposition) + protein = weight_kg * 2.2 # High protein + carbs = weight_kg * 2.5 # Moderate carbs + fat = weight_kg * 0.9 # Moderate fat + + return { + 'protein_g': round(protein), + 'carbs_g': round(carbs), + 'fat_g': round(fat) + } + +def calculate_daily_totals(user_id, target_date=None): + """ + Calculate total nutrition consumed for a given date + """ + if target_date is None: + target_date = date.today() + + # Get all meals for the date + meals = Meal.query.filter_by(user_id=user_id, date=target_date).all() + + totals = { + 'calories': 0, + 'protein': 0, + 'carbs': 0, + 'fat': 0, + 'meals': [] + } + + for meal in meals: + meal_totals = meal.calculate_totals() + totals['calories'] += meal_totals['calories'] + totals['protein'] += meal_totals['protein'] + totals['carbs'] += meal_totals['carbs'] + totals['fat'] += meal_totals['fat'] + + totals['meals'].append({ + 'id': meal.id, + 'type': meal.meal_type, + 'time': meal.time.strftime('%H:%M') if meal.time else None, + 'totals': meal_totals, + 'foods': [ + { + 'name': mf.food.name, + 'quantity': mf.quantity, + 'calories': mf.calories_consumed + } + for mf in meal.foods + ] + }) + + return totals + +def calculate_water_total(user_id, target_date=None): + """ + Calculate total water intake for a given date + """ + if target_date is None: + target_date = date.today() + + water_logs = WaterLog.query.filter_by(user_id=user_id, date=target_date).all() + total = sum(log.amount_ml for log in water_logs) + + return { + 'total_ml': total, + 'logs': [ + { + 'id': log.id, + 'amount_ml': log.amount_ml, + 'time': log.time.strftime('%H:%M') if log.time else None + } + for log in water_logs + ] + } + +def get_weight_trend(user_id, days=7): + """ + Get weight trend for the past N days + """ + end_date = date.today() + start_date = end_date - timedelta(days=days-1) + + weight_logs = WeightLog.query.filter( + WeightLog.user_id == user_id, + WeightLog.date >= start_date, + WeightLog.date <= end_date + ).order_by(WeightLog.date).all() + + return [ + { + 'date': log.date.strftime('%Y-%m-%d'), + 'weight_kg': log.weight_kg + } + for log in weight_logs + ] + +def get_calorie_trend(user_id, days=7): + """ + Get calorie intake trend for the past N days + """ + end_date = date.today() + start_date = end_date - timedelta(days=days-1) + + trend = [] + current_date = start_date + + while current_date <= end_date: + totals = calculate_daily_totals(user_id, current_date) + trend.append({ + 'date': current_date.strftime('%Y-%m-%d'), + 'calories': round(totals['calories']), + 'protein': round(totals['protein']), + 'carbs': round(totals['carbs']), + 'fat': round(totals['fat']) + }) + current_date += timedelta(days=1) + + return trend + +def update_daily_summary(user_id, target_date=None): + """ + Update or create daily summary for a user + """ + if target_date is None: + target_date = date.today() + + # Calculate totals + nutrition = calculate_daily_totals(user_id, target_date) + water = calculate_water_total(user_id, target_date) + + # Get weight for the day + weight_log = WeightLog.query.filter_by(user_id=user_id, date=target_date).first() + weight = weight_log.weight_kg if weight_log else None + + # Get user's calorie target + from models import User + user = User.query.get(user_id) + target_calories = user.target_daily_calories if user else 2000 + + # Find or create summary + summary = DailySummary.query.filter_by(user_id=user_id, date=target_date).first() + + if not summary: + summary = DailySummary(user_id=user_id, date=target_date) + db.session.add(summary) + + # Update values + summary.total_calories = nutrition['calories'] + summary.total_protein_g = nutrition['protein'] + summary.total_carbs_g = nutrition['carbs'] + summary.total_fat_g = nutrition['fat'] + summary.total_water_ml = water['total_ml'] + summary.calories_remaining = target_calories - nutrition['calories'] + summary.weight_kg = weight + + try: + db.session.commit() + return summary + except Exception as e: + db.session.rollback() + print(f"Error updating daily summary: {e}") + return None + +def get_macro_percentages(protein_g, carbs_g, fat_g): + """ + Calculate macro distribution as percentages + """ + protein_cal = protein_g * 4 + carbs_cal = carbs_g * 4 + fat_cal = fat_g * 9 + total_cal = protein_cal + carbs_cal + fat_cal + + if total_cal == 0: + return {'protein': 0, 'carbs': 0, 'fat': 0} + + return { + 'protein': round((protein_cal / total_cal) * 100), + 'carbs': round((carbs_cal / total_cal) * 100), + 'fat': round((fat_cal / total_cal) * 100) + } + +def suggest_foods_for_macros(remaining_protein, remaining_carbs, remaining_fat): + """ + Suggest Filipino foods based on remaining macros + Returns category suggestions + """ + suggestions = [] + + # High protein needed + if remaining_protein > 30: + suggestions.append({ + 'category': 'High Protein Ulam', + 'examples': ['Grilled Tilapia', 'Chicken Tinola', 'Grilled Chicken'] + }) + + # High carbs needed + if remaining_carbs > 40: + suggestions.append({ + 'category': 'Carbs', + 'examples': ['White Rice', 'Pandesal', 'Sweet Potato'] + }) + + # High fat needed + if remaining_fat > 20: + suggestions.append({ + 'category': 'Healthy Fats', + 'examples': ['Sisig', 'Lechon Kawali', 'Bicol Express'] + }) + + # Balanced meal needed + if remaining_protein > 20 and remaining_carbs > 30: + suggestions.append({ + 'category': 'Balanced Meals', + 'examples': ['Tapsilog', 'Chicken Adobo with Rice', 'Sinigang'] + }) + + return suggestions diff --git a/customized_build_plan.md b/customized_build_plan.md new file mode 100644 index 0000000..4460e5b --- /dev/null +++ b/customized_build_plan.md @@ -0,0 +1,546 @@ +# Customized Calorie Tracker - Filipino Food Edition +## Web Application for Weight Loss & Muscle Gain + +--- + +## Your Specific Requirements + +βœ… **Interface**: Web Application (Flask-based) +βœ… **Goals**: Weight Loss + Muscle Gain (Body Recomposition) +βœ… **Tracking**: Calories, Macros (Protein/Carbs/Fat), Water Intake +βœ… **Precision**: Approximate tracking (user-friendly) +βœ… **Weight Tracking**: Daily weigh-ins with trend analysis +βœ… **Diet**: No restrictions, Filipino food focus +βœ… **Planning**: Meal planning ahead feature (not just logging) + +--- + +## Enhanced Database Schema + +### Additional Tables for Your Needs + +```sql +-- Water intake tracking +CREATE TABLE water_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + date DATE NOT NULL, + amount_ml INTEGER NOT NULL, + time TIME, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- Weight tracking +CREATE TABLE weight_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + date DATE NOT NULL UNIQUE, + weight_kg REAL NOT NULL, + body_fat_percentage REAL, + notes TEXT, + time TIME DEFAULT CURRENT_TIME, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- Meal plans (future meals) +CREATE TABLE meal_plans ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + date DATE NOT NULL, + meal_type TEXT, + is_completed BOOLEAN DEFAULT 0, + notes TEXT, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- Planned foods (linked to meal_plans) +CREATE TABLE planned_foods ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + meal_plan_id INTEGER NOT NULL, + food_id INTEGER NOT NULL, + quantity REAL NOT NULL, + FOREIGN KEY (meal_plan_id) REFERENCES meal_plans(id) ON DELETE CASCADE, + FOREIGN KEY (food_id) REFERENCES food_items(id) ON DELETE CASCADE +); + +-- Filipino food database (pre-populated) +CREATE TABLE filipino_foods ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name_english TEXT, + name_tagalog TEXT, + category TEXT, -- 'ulam', 'kanin', 'meryenda', 'sabaw', etc. + is_common BOOLEAN DEFAULT 1, + calories REAL, + protein_g REAL, + carbs_g REAL, + fat_g REAL, + serving_description TEXT +); + +-- User preferences and goals +CREATE TABLE user_goals ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER UNIQUE, + goal_type TEXT, -- 'weight_loss', 'muscle_gain', 'recomp' + target_weight_kg REAL, + weekly_goal_kg REAL, + target_protein_g INTEGER, + target_carbs_g INTEGER, + target_fat_g INTEGER, + target_water_ml INTEGER DEFAULT 2000, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); +``` + +--- + +## Body Recomposition Strategy + +### Macro Targets for Weight Loss + Muscle Gain + +**Protein Priority (Muscle Preservation/Growth)** +- Target: 2.0-2.4g per kg body weight +- Example: 70kg person = 140-168g protein/day + +**Moderate Carbs (Energy for Workouts)** +- Target: 2-3g per kg body weight +- Example: 70kg person = 140-210g carbs/day + +**Healthy Fats (Hormones & Satiety)** +- Target: 0.8-1.0g per kg body weight +- Example: 70kg person = 56-70g fat/day + +**Calorie Cycling Option** +- Training days: Maintenance or slight surplus (+100-200 cal) +- Rest days: Deficit (-300-500 cal) + +--- + +## Filipino Food Database + +### Pre-populated Common Filipino Foods + +**Kanin (Rice)** +- White rice (1 cup) - 206 cal, 45g carbs, 4g protein +- Fried rice - 280 cal, 40g carbs, 5g protein, 10g fat +- Sinangag - 250 cal, 42g carbs, 4g protein, 8g fat + +**Ulam (Main Dishes)** +- Adobo (chicken, 1 serving) - 350 cal, 35g protein, 5g carbs, 20g fat +- Sinigang (pork, 1 bowl) - 280 cal, 25g protein, 10g carbs, 15g fat +- Tinola (chicken soup) - 200 cal, 28g protein, 8g carbs, 6g fat +- Bicol Express - 400 cal, 20g protein, 10g carbs, 30g fat +- Sisig (pork) - 450 cal, 25g protein, 8g carbs, 35g fat +- Menudo - 320 cal, 22g protein, 12g carbs, 20g fat +- Kare-kare - 380 cal, 24g protein, 18g carbs, 25g fat +- Lechon kawali - 500 cal, 30g protein, 2g carbs, 42g fat + +**Gulay (Vegetables)** +- Pinakbet - 150 cal, 5g protein, 20g carbs, 6g fat +- Laing - 180 cal, 6g protein, 15g carbs, 12g fat +- Ginisang monggo - 200 cal, 12g protein, 30g carbs, 4g fat + +**Meryenda (Snacks)** +- Pandesal (1 piece) - 120 cal, 3g protein, 22g carbs, 2g fat +- Turon - 180 cal, 2g protein, 35g carbs, 5g fat +- Bibingka - 220 cal, 5g protein, 38g carbs, 6g fat +- Puto - 90 cal, 2g protein, 18g carbs, 1g fat +- Lumpia (2 pieces) - 200 cal, 8g protein, 20g carbs, 10g fat + +**Sabaw (Soups)** +- Bulalo - 350 cal, 32g protein, 8g carbs, 20g fat +- Nilaga - 280 cal, 28g protein, 12g carbs, 14g fat + +**Breakfast** +- Tapsilog - 650 cal, 45g protein, 60g carbs, 25g fat +- Longsilog - 700 cal, 38g protein, 65g carbs, 32g fat +- Tocilog - 680 cal, 42g protein, 62g carbs, 28g fat + +--- + +## Web Application Features + +### Pages Structure + +**1. Dashboard (Home)** +- Today's summary card + - Calories consumed vs target (progress bar) + - Macros breakdown (protein/carbs/fat) with color-coded bars + - Water intake tracker (glasses icon) + - Weight today vs yesterday +- Quick add meal button +- Quick add water button +- Weekly trend chart (calories & weight) + +**2. Meal Planner** +- Calendar view (7-day week view) +- Click date to plan meals +- Drag-and-drop Filipino foods +- Copy previous day's meals +- Templates for common Filipino meal combos +- Calculate totals before committing +- Mark meals as completed + +**3. Food Database** +- Search bar (English or Tagalog) +- Filter by category (Ulam, Kanin, Meryenda, etc.) +- Filipino food section (pre-populated) +- API search for international foods +- Add custom foods +- Favorite foods list +- Recent foods + +**4. Tracking Log** +- Today's meals (editable) +- Add meal form +- Food search autocomplete +- Quick portions (1/2 serving, 1 serving, 2 servings) +- Edit/delete entries + +**5. Progress** +- Weight chart (line graph) +- Body composition trends +- Weekly averages +- Monthly summaries +- Photo progress (optional upload) +- Measurements (waist, chest, arms) + +**6. Goals & Settings** +- Set target weight +- Calculate TDEE +- Set macro targets +- Choose goal (weight loss, muscle gain, recomp) +- Water intake goal +- Activity level +- Profile info + +--- + +## Tech Stack + +### Backend +``` +Flask==3.0.0 +Flask-SQLAlchemy==3.1.1 +Flask-Login==0.6.3 +python-dotenv==1.0.0 +requests==2.31.0 +``` + +### Frontend +``` +HTML5 +CSS3 / Tailwind CSS +JavaScript (Vanilla) +Chart.js (for graphs) +FullCalendar.js (for meal planner) +``` + +### Database +``` +SQLite3 (development) +PostgreSQL (production optional) +``` + +--- + +## UI Design Mockup + +### Color Scheme (Filipino-inspired) +- Primary: #D62828 (Red - like Filipino flag) +- Secondary: #003F87 (Blue) +- Success: #06D6A0 (Green - for hitting goals) +- Warning: #FFB703 (Yellow/Gold) +- Background: #F8F9FA (Light gray) + +### Dashboard Layout +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 🏠 Dashboard πŸ“… Meal Planner 🍽️ Foods πŸ“Š Progress β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Today: January 30, 2026 βš™οΈ Settings πŸ‘€ User β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Calories β”‚ β”‚ Macros β”‚ β”‚ +β”‚ β”‚ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ 1,645 β”‚ β”‚ Protein: 120g βœ“ β”‚ β”‚ +β”‚ β”‚ Target: 2,000 β”‚ β”‚ Carbs: 180g β”‚ β”‚ +β”‚ β”‚ Remaining: 355 β”‚ β”‚ Fat: 55g β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Water Intake β”‚ β”‚ Weight β”‚ β”‚ +β”‚ β”‚ πŸ’§πŸ’§πŸ’§πŸ’§πŸ’§βšͺβšͺβšͺ β”‚ β”‚ Today: 72.5 kg β”‚ β”‚ +β”‚ β”‚ 1,250 / 2,000 ml β”‚ β”‚ Change: -0.3 kg ⬇│ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Today's Meals [+ Add Meal]β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ πŸŒ… Breakfast (7:30 AM) 520 calβ”‚ +β”‚ β”‚ β€’ Tapsilog β”‚ +β”‚ β”‚ β€’ Coffee with milk β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ 🌞 Lunch (12:30 PM) 680 calβ”‚ +β”‚ β”‚ β€’ Chicken Adobo β”‚ +β”‚ β”‚ β€’ White rice (1.5 cups) β”‚ +β”‚ β”‚ β€’ Pinakbet β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ πŸͺ Snack (3:00 PM) 180 calβ”‚ +β”‚ β”‚ β€’ Turon (1 piece) β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ πŸŒ™ Dinner (Planned) [Edit] β”‚ +β”‚ β”‚ β€’ Sinigang na baboy β”‚ +β”‚ β”‚ β€’ White rice (1 cup) β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β”‚ β”‚ +β”‚ Weekly Trend β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ [Chart: Calories & Weight] β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## Meal Planning Workflow + +### Planning Mode +1. Navigate to Meal Planner +2. Select future date +3. Add meals for each time slot +4. Search Filipino foods or API +5. Adjust quantities +6. See macro totals update in real-time +7. Save meal plan + +### Execution Mode +1. Dashboard shows today's planned meals +2. Check off meals as you eat them +3. Edit portions if needed (ate more/less) +4. Actual consumption updates automatically +5. Quick add water after meals + +### Smart Features +- **Suggest meals based on remaining macros** + - Low on protein? Suggests Tinola, Grilled fish + - Need carbs? Suggests Rice, Pandesal + - Need fats? Suggests Sisig, Bicol Express + +- **Common Filipino meal combos** + - Silog meals (Tapsilog, Longsilog, etc.) + - Typical lunch: Ulam + Rice + Gulay + - Merienda ideas + +- **Copy from history** + - Yesterday's meals + - Last week's Tuesday + - Favorite meal combinations + +--- + +## API Integration Strategy + +### Primary: API Ninjas +- Use for international/branded foods +- Cache results in local database +- Fallback to Open Food Facts if not found + +### Secondary: Open Food Facts +- Barcode scanning capability +- Community database +- Free, no rate limits + +### Local Database Priority +1. Check Filipino foods table first (fastest) +2. Check cached API results +3. Query API Ninjas +4. Query Open Food Facts +5. Allow manual entry if not found + +--- + +## Water Intake Tracking + +### Quick Add Buttons +- Small glass (250ml) +- Medium glass (350ml) +- Large glass (500ml) +- Bottle (750ml) +- Custom amount + +### Visual Progress +- Glass icons fill up (8 glasses = 2L goal) +- Progress bar +- Notifications/reminders (optional) + +### Tracking Table +``` +Time | Amount | Type +---------|--------|------- +07:30 AM | 250ml | Glass +12:45 PM | 500ml | Bottle +03:00 PM | 350ml | Glass +``` + +--- + +## Implementation Phases + +### Phase 1: Core Backend (Week 1) +- [ ] Database setup with all tables +- [ ] User authentication (Flask-Login) +- [ ] API integration with caching +- [ ] Filipino food database population +- [ ] CRUD operations for meals, foods, water, weight + +### Phase 2: Dashboard & Logging (Week 2) +- [ ] Dashboard page with cards +- [ ] Today's summary calculations +- [ ] Add meal form +- [ ] Food search with autocomplete +- [ ] Water logging +- [ ] Weight logging + +### Phase 3: Meal Planner (Week 3) +- [ ] Calendar interface +- [ ] Plan future meals +- [ ] Copy meals functionality +- [ ] Meal templates +- [ ] Mark meals as completed +- [ ] Smart suggestions based on macros + +### Phase 4: Progress & Polish (Week 4) +- [ ] Progress page with charts +- [ ] Weight trend analysis +- [ ] Weekly/monthly reports +- [ ] Goals page +- [ ] Settings page +- [ ] Mobile responsive design +- [ ] Testing and bug fixes + +--- + +## Deployment + +### Recommended: Heroku (Free/Hobby Tier) +```bash +# Free tier includes: +# - 550 dyno hours/month +# - PostgreSQL database +# - HTTPS by default +``` + +### Alternative: PythonAnywhere +```bash +# Free tier includes: +# - One web app +# - 512MB storage +# - Good for personal projects +``` + +### Alternative: DigitalOcean App Platform +```bash +# $5/month +# - More reliable +# - Better performance +# - PostgreSQL included +``` + +--- + +## Special Features for Filipino Users + +### Language Support +- Search in English or Tagalog +- "Kanin" or "Rice" both work +- "Adobong manok" or "Chicken adobo" + +### Common Portions +- Tagayan (ladle) +- Tasa (cup) +- Kutsara (tablespoon) +- Standard plate serving + +### Meal Categories +- Almusal (Breakfast) +- Tanghalian (Lunch) +- Merienda (Snack) +- Hapunan (Dinner) + +### Cultural Considerations +- Family-style eating (estimate portions) +- Typical Filipino meal structure +- Common food pairings +- Celebration foods (adjust for occasions) + +--- + +## Starter Prompt for AI Code Generation + +``` +Create a Flask web application for calorie and macro tracking with these specifications: + +1. Database (SQLite with Flask-SQLAlchemy): + - Users, food_items, meals, meal_foods, water_logs, weight_logs + - meal_plans, planned_foods (for meal planning) + - filipino_foods (pre-populated) + - user_goals (macro targets) + +2. Filipino Food Support: + - Pre-populate database with 50+ common Filipino foods + - Categories: Ulam, Kanin, Meryenda, Sabaw, Gulay + - English and Tagalog names searchable + +3. Pages: + - Dashboard (today's summary, macros, water, weight) + - Meal Planner (calendar view for planning future meals) + - Food Database (search Filipino + API foods) + - Tracking Log (add/edit today's meals) + - Progress (weight chart, trends) + - Goals/Settings + +4. Features: + - Macro tracking (protein, carbs, fat) + - Water intake logging + - Daily weight tracking + - Meal planning (plan ahead, not just log) + - Copy previous meals + - Smart macro suggestions + - API Ninjas integration with caching + +5. Frontend: + - Tailwind CSS for styling + - Chart.js for graphs + - Red/Blue color scheme (Filipino flag inspired) + - Mobile responsive + +6. User Flow: + - Plan meals in advance + - Mark meals as completed when eaten + - Quick add water/weight + - See progress charts + +Include: +- User authentication (Flask-Login) +- Form validation +- Error handling +- Responsive design +- Sample data +- Setup instructions + +Generate complete code with folder structure. +``` + +--- + +## Next Steps + +1. **Review this plan** - Make sure it matches your vision +2. **Start with backend** - Database and API integration +3. **Build dashboard** - Core tracking interface +4. **Add meal planner** - Planning ahead feature +5. **Test and iterate** - Use it yourself, refine + +Ready to start building? Let's create the actual application! πŸš€ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1e361c9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.8' + +services: + calorie-tracker: + build: + context: ./calorie_tracker_app + container_name: calorie-tracker + restart: unless-stopped + ports: + - "5001:5001" + volumes: + - ./data:/app/data + environment: + - DATABASE_URL=sqlite:////app/data/calorie_tracker.db + - SECRET_KEY=change-this-secret-key-in-production + - API_NINJAS_KEY=${API_NINJAS_KEY} + networks: + - casaos-net + +networks: + casaos-net: + driver: bridge diff --git a/instance/calorie_tracker.db b/instance/calorie_tracker.db new file mode 100644 index 0000000000000000000000000000000000000000..097c16c04f5a8983d6035d15e6b9bb5a90c7c281 GIT binary patch literal 69632 zcmeI)-)`Gf90zc_-I^s`+o6FXgkT<wsM#>%=}u&5}5? zosDpTw3Z2pN8kuWV(-zi$W~5`HrU^CVtv}`vb6rq%S$VNvh#i? zEjLzvKKte3gR?g?z4XDE*U~pri>J+nU+2Tq8TmK1!*R=jb}p5@az*~?Vo2&O+HH|e zhdNfn^PE7~c=U#q~-Z84J_O11idzO;;gRkh1BgFkz*6Te$){S5+1SlKMD{F z++=M2OIXe?MOFDJjl$v(x7D{;@!G`i3meNqaD=R5;?oPLKdq)Zgf;f}dGknC($EW8 z?H^&s#)I^cbaOIe0R2odcYY@EG5cWOYPUv>Pl&Eh+r;g-oy1X(soUHStp|yu4hwjo z?3thJ@q?cix1oX%H=Y*9?cUDToqMLT%1v^aTxEG7d+UlUxgCdo9z1NZZqXvW(2KS$ z0pHSu&6UID#X&o3MgfgJy6KTt5U-sTLK&yOU>y0`t+3yv%171O^$)7G)w~+@Eg|un zG_;Q3dravAR?ng%ldQud;wTThM~*zq8wL8=oI$LgO+PB!y)Kt_EH=*Y8o0N0%bXU- zQD`EkBYMqIda_aSv{A>WpIyk_xHv7AvA(6n>q`eqnJ9{fgJv0b!13Cm5XCtC1*3R$ zYm81i8C#*fy1aX9cUzQkD(~&?ZkyE|PI;0oB$GQ>JCn*@x-=SbhgGWOHG{-zS`bNO zoG8gebrvw{p3}E@J?v87rk#*9Y1~nXBB9%+%5n8BIWiw#$2j+>mJU0RVtOIFb!l3q zOVC(aVk7gUn;KNRx1!Et&~hT>sLSMQXGG+2`iUBow%6-~i7r}DK0&%n?n$ta%4W0j zp*fzH1iaPg-0&hO-QNRCYVh~VsLfH680H$B?iW-zc9`za!1mp4=(2E*3a_byU{6$m;aq_YBMrCZcHNGb97T9CdBo=3 zY^s5mj_7PCapb7MKfMygIU1^?Kjwt!MoGs6mgft>El9B=R0G7JOdD-B^*BD;X_7Z4%4J;3h0l|p$!c82QyN;9gi*(!xFDR#17p@>{8Hb z(MPm3K8H9v8_XWZbt>`EwohMVj>JpFT3~K&A^UE2S__PH2Q!{vo?U96;g{P>>@BaP zCHBGs0SG_<0uX=z1Rwwb2tWV=5SV>|C!xF~tsUICAipvvzdl!G&ziz(^mD>*v#+ze z?d;}CzZ-7o`I1rIC~s(&jf!*6{!G7q^Tx-0Qn_nh&X?*%-OwwfQEI4agVc3fC$!O^ zl|ot7N_CqS*zGk^Xyoleg&5^}v1HhKp`aP;E=9qqsH9vkl?$p-EY=HZiK?ZFQD_tj z1-)XZ#e!~>h^CjzMAz~~+t5pSqv%wOdcjtSQz1nE(A)1Qw@I({Mw_&#^!;YrW4AHb z?S%ACc{OjWsoI*ZDyp$j%x|d0b)!<$jB= z0uX=z1Rwwb2tWV=5P$##xB#yIQ4b&h0SG_<0uX=z1Rwwb2tWV=voC<_|Jjc*N(cc6 zKmY;|fB*y_009U<00I!ezyC)afB*y_009U<00Izz00bZa0SL^#0G|KPevDB<2tWV= z5P$##AOHafKmY;|fB?V#msft2*b55;AOHafKmY;|fB*y_009U<00RGCfp_FZDgCOP znwyj7sy?;7c6amY@5Ar=t@Ojy)Gv88Z>*`>nyxCUu~E!#=#_O{FKC9!fB!G9{3Wp$ z76?E90uX=z1Rwwb2tWV=5P$##W>8>$KApzz|7S3;C=vu9009U<00Izz00bZa0SG`~ zLID5&-vkIYApijgKmY;|fB*y_009U<00J{8fam`+7*rGq0uX=z1Rwwb2tWV=5P$## iATS|->;DN5Y(fA65P$##AOHafKmY;|fB*z$P~dOle)y*V literal 0 HcmV?d00001