commit 656a510c73ea1ce59793ddf5221b46e398c7ee44 Author: Jp Date: Fri Jan 30 15:03:43 2026 +0800 first commit 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 0000000..78f65b5 Binary files /dev/null and b/calorie_tracker_app/__pycache__/api_client.cpython-314.pyc differ 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 0000000..67631ea Binary files /dev/null and b/calorie_tracker_app/__pycache__/app.cpython-314.pyc differ diff --git a/calorie_tracker_app/__pycache__/config.cpython-314.pyc b/calorie_tracker_app/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..eaf1edf Binary files /dev/null and b/calorie_tracker_app/__pycache__/config.cpython-314.pyc differ 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 0000000..221e5cb Binary files /dev/null and b/calorie_tracker_app/__pycache__/models.cpython-314.pyc differ 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 0000000..add78b4 Binary files /dev/null and b/calorie_tracker_app/__pycache__/utils.cpython-314.pyc differ 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 %} +
+ + + + + {% 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 0000000..097c16c Binary files /dev/null and b/instance/calorie_tracker.db differ