# Project export: EcoBite

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

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: Track your food waste in a gamified way— EcoBite aims at combating global food waste by allowing its users to upload images of their food, eaten or uneaten, to determine the weight of what is wasted.
- Devpost: https://devpost.com/software/ecobite-qcujae
- GitHub: https://github.com/SirKentut/EcoBite
- Demo: https://app.flutterflow.io/share/eco-bite-9o3s7v
- Video: https://www.youtube.com/embed/g1RofQA9R_o?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Flutterflow: Best Use of FlutterFlow ($2,000 Cash + meet w/ founders [1st] & $1,500 Cash [2nd] & iPad 10.9"s [3rd]))
- Team: 2 GitHub contributor(s) — Russell Semsem (10 commits), SirKentut (2 commits)

## Devpost submission (written by the team)

### Inspiration

In the USA alone, food waste makes up 22% of landfilled solid waste, and “globally, food loss and waste represent 8 percent of anthropogenic greenhouse gas emissions” (EPA 2021). Our inspiration for EcoBite was an interest in this incredibly relevant issue within our world, as well as the Stanford Sustainability Challenges, specifically Challenge #3: the development of a program for determining the weight of food in an image. From a technical standpoint, this challenge aligned greatly with our interests: full-stack application development the use of AI / large language models generalized machine learning Our team felt we could each bring our strengths into this project, with the significant added benefit of creating a tool that could make an impact in combating food waste and therefore helping our planet’s environment. We also resonated greatly with the message of Stanford’s Ecopreneurship program, of accelerating environmental and sustainability efforts fueled by passions within the field of climate change or tech.

### What it does

EcoBite is a mobile app meant for users to take pictures of their leftover food for the main purpose of estimating the food’s weight. After this action on the user’s end, the app will analyze the weight of the food in the image along with classifying its type. On top of being able to record and display this data to users, EcoBite keeps track of individual progress when it comes to food waste, allowing for milestones to be reached when individuals to a good job not creating as much food waste. This information is recorded and held in a backend to be analyzed for generalized food waste trends, all of it location-based. This process of recording images is also great for businesses that produce much food waste in their day to day in order to better keep track of food costs as well as intercept food waste.

### How we built it

EcoBite utilizes a variety of sponsor technologies to complete this project. For a professional responsive frontend, we leveraged FlutterFlow to rapidly develop a polished mobile application that enables users to track their food weight, view detailed analytics of their consumption patterns, and participate in an engaging points-based reward system. FlutterFlow's intuitive low-code platform streamlined our development process, enabling us to rapidly create a polished mobile app with professional UI components and functionality that would have otherwise required significantly more development time. Various API endpoints were created to retrieve any necessary information the user may need. For the backend, we used Flask as the AI APIs perform best in a Python environment. Google Gemini was used to perform essential functionalities such as food classification and segmentation, volume estimation, and weight calculations. Our team learned prompt engineering principles to optimize these AI interactions, ensuring accurate and consistent responses while minimizing token usage and processing time. For obtaining the density of different food items, we used the Food and Agriculture of the United Nations' Official Density Database as our primary source. If the database did not contain a food item, we relied on Perplexity's Sonar Pro model to determine density values.

### Challenges we ran into

On top of using Google Gemini for estimating the density of food via prompt engineering, our team wanted to use a food volume estimator deep neural net trained on many food images to assist with the classification of food items and estimation of their weights. There were many great examples of older work being done in this field, however a lot of the code was incredibly difficult into modern python / usability standards to this day, so our team ran out of time when it came to implementing this model alongside the AI model. This neural net would have been trained on 100100 images of food, using ResNet50 for image classification.

### Accomplishments we're proud of

Our team is most proud of our development of EcoBite using a tool none of us had worked with previously: FlutterFlow. Thanks to TreeHacks for hosting the company as a sponsor, we were given a perfect opportunity to pick up the tool and became highly motivated to try our best to master it over the weekend. Overall it was an amazing to learn how to use, all employees present helping us out greatly throughout countless steps of our app development process; end-to-end! Thanks to all of the assistance, as well as FlutterFlow’s user friendliness, we have a great looking mobile application that was incredibly straightforward to learn how to build.

### What we learned

Thanks to Keegan Cooke, the head of Stanford's Sustainability Challenge #3 and Director of Stanford Ecopreneurship, our team gained valuable insights about the challenge and received a food scale for our preparations. Through our research, we discovered previous attempts to estimate food volume (rather than weight) using image classification. Along the way, our team quickly picked up and mastered several technical tools, including the Perplexity API, Google Gemini API, FlutterFlow for app development, and Flask for backend implementation.

### What's next

We believe a great product is one that listens to its users. We look to gain as much feedback from users on their experience using EcoBite and what features have been useful and those in need of improvement. Through our beta testing phase, we will actively collect user feedback through in-app surveys, user interviews, and analytics tracking to understand usage patterns and pain points. This data will guide our development roadmap, ensuring we prioritize features that provide the most value to our users while continuously refining the app's functionality and user experience to better serve our community's needs. Some future goals include expanding to helping out businesses interested in keeping track of their food waste for the sake of improving sustainability and helping with professional costs. We would also love to expand the backend model used from AI tools to an actual existing estimation model trained on real images, both ones found in existing databases and those taken by users of the application. A big goal of EcoBite would be to create a data expansion pipeline wherein which the food density estimator could continuously train on new datapoints. EcoBite aims to counter food waste and instill better practices in how we prepare and consume food. Through our gamified approach and real-time food tracking, we empower users to make more conscious decisions about their portions and develop sustainable habits that benefit both their personal well-being, community, and environment.

## README (from the GitHub repository)

# EcoBite
Track your food waste in a gamefied way
![IMG_6023](https://github.com/user-attachments/assets/a3e4a5c0-65df-4eec-951e-76bc0de39207)
![IMG_8865](https://github.com/user-attachments/assets/95b6c973-9c4a-4a92-8323-d89a724e76fb)
![IMG_7932](https://github.com/user-attachments/assets/b8fcd142-b548-49b7-9c43-b567b6d8994e)
![IMG_3828](https://github.com/user-attachments/assets/2e086bc1-fa16-4073-98d9-bdf396d90a3c)


## Detected evidence (automated analysis)

Indexed codebase: 5 recognized source files, 13 KB.
- Express (technology) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (9 of 9)

```
backend/.gitignore
backend/app.js
backend/package.json
flask/.gitignore
flask/app.py
flask/data/food_density_reference.csv
flask/services/__init__.py
flask/services/density_service.py
README.md
```

### Dependencies

- backend/package.json: express@^4.21.2

### Recent commits (newest first)

- Update README.md
- Add files via upload
- Merge pull request #3 from SirKentut/feature/densityquery
- Added async function get density from flask server
- added density database from fao/infoods to use as reference when querying densities
- Added Perplexity Sonar model for searching densities of given food item names
- created flask server for team to implement their models and use them as APIs
- Merge pull request #2 from SirKentut/feature/SystemSkeleton
- system skeleton added
- Merge pull request #1 from SirKentut/feature/initExpress
- Remove node_modules from Git tracking
- Init express server
- Create README.md

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

### backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.21.2"
  }
}

```

### flask/app.py

```python
# app.py

from flask import Flask, jsonify, request
from datetime import datetime
from services.density_service import density
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize Flask app
app = Flask(__name__)

# Register the blueprint
app.register_blueprint(density, url_prefix='/density')

# Basic error handling
class APIError(Exception):
    """Base class for API errors"""
    def __init__(self, message, status_code=400):
        super().__init__()
        self.message = message
        self.status_code = status_code

@app.errorhandler(APIError)
def handle_api_error(error):
    response = jsonify({'error': error.message})
    response.status_code = error.status_code
    return response

# Routes
@app.route('/')
def home():
    return jsonify({
        'message': 'Welcome to the Flask API for Food Density Service.',
        'status': 'healthy',
        'timestamp': datetime.now().isoformat()
    })

@app.route('/echo', methods=['POST'])
def echo():
    if not request.is_json:
        raise APIError('Content-Type must be application/json')
    
    data = request.get_json()
    return jsonify({
        'message': 'Echo response',
        'data': data,
        'timestamp': datetime.now().isoformat()
    })

# Health check endpoint
@app.route('/health')
def health_check():
    return jsonify({
        'status': 'Healthy: TreeHacks 2025',
        'timestamp': datetime.now().isoformat()
    })

if __name__ == '__main__':
    # Enable hot reloading and run on localhost
    app.run(host='0.0.0.0', port=5000, debug=True)
```

### backend/app.js

```javascript
const express = require('express');

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());  // For parsing JSON bodies

// 1. Flutter Client -> Backend Server
// Endpoint to receive food image and return weight
app.post('/analyze-food', async (req, res) => {
    console.log('Received request:', req.body);

    // TODO: (Subject to change) / suggested async functions below
    // 1. Receive food image

    // 2. Process through OpenAI/other classification (step 2 in diagram)

    // 3. Get volume from Volume Estimation System (step 3)

    // Step 4: Get densities for all detected foods
    const densityResults = await getFoodDensity(detectedFoods);
    console.log('Density results:', densityResults);
    
    // 5. Calculate weight (step 5)

    res.json({
        weight: {
            lbs: 0,
            grams: 0,
            breakdown: [
                {
                    foodItem: "apple",
                    weight: {
                        lbs: 0.5,
                        grams: 226.796
                    },
                    volume: "250ml",
                    density: 0.9 // g/ml
                },
                {
                    foodItem: "sandwich",
                    weight: {
                        lbs: 2,
                        grams: 907.184
                    },
                    volume: "1000ml",
                    density: 0.9 // g/ml
                }
            ]
        }
    });
});

// 2. Backend -> Food Classification Service
// This could be an internal function or separate service
async function classifyFood(foodImage) {
    // TODO: Integrate with OpenAI or other classification models
}

// 3. Backend -> Volume Estimation System
// This could be an API call to your volume estimation service
async function getVolumeEstimation(foodData) {
    // TODO: Call volume estimation system
}

// 4. Database interaction for food densities// Add the density service function
async function getFoodDensity(foods) {
    try {
        const response = await fetch('http://localhost:5000/density/process-foods', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ foods: foods })
        });
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data.foods;
    } catch (error) {
        console.error('Error getting food density:', error.message);
        throw error;
    }
}

// 5. Weight calculation
function calculateWeight(volume, density) {
    return volume * density;
}

// Health check endpoint
app.get('/ping', (req, res)=>{
    res.status(200);
    res.send("Pong: TreeHacks 2025");
});

app.listen(PORT, (error) =>{
    if(!error)
        console.log("Server is Successfully Running, and App is listening on port " + PORT)
    else 
        console.log("Error occurred, server can't start", error);
    }
);

```

### flask/services/density_service.py

```python
from flask import Blueprint, jsonify, request
from openai import OpenAI
from dotenv import load_dotenv
import os
import logging
from typing import Optional, Dict, List, Union, Tuple
from functools import lru_cache
import pandas as pd

# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()

# Create blueprint for density service
density = Blueprint('density', __name__)

# Initialize Perplexity client
client = OpenAI(
    api_key=os.getenv('PERPLEXITY_API_KEY'),
    base_url="https://api.perplexity.ai"
)

@lru_cache(maxsize=1)
def load_reference_file() -> Tuple[Optional[Dict[str, float]], Optional[str]]:
    """
    Load the reference CSV file containing food density values.
    Returns a tuple of (density_dict, reference_text).
    density_dict maps food names to their density values.
    reference_text is the formatted text to be used in the API prompt.
    """
    try:
        # Use relative path from the Flask app root
        csv_path = 'data/food_density_reference.csv'
        logger.info(f"Attempting to load CSV from relative path: {csv_path}")
        
        df = pd.read_csv(csv_path)
        logger.info(f"Successfully loaded CSV with {len(df)} rows")
        logger.info(f"CSV columns: {df.columns.tolist()}")
        logger.info("First few rows of DataFrame:")
        logger.info(df.head())
        
        # Process the CSV data to create a dictionary and reference text
        density_dict = {}
        reference_lines = []
        
        for _, row in df.iterrows():
            # Skip empty rows or headers
            if pd.isna(row['Food name']) or pd.isna(row['Density']):
                continue
                
            # Get the food name and clean it
            food_name = str(row['Food name']).strip().strip('"').lower()
            density_str = str(row['Density']).strip()
            
            # Skip category headers and empty food names
            if not food_name or food_name.endswith(',') or pd.isna(density_str):
                continue
                
            try:
                # Handle range values (e.g., "0.56-0.72")
                if '-' in density_str:
                    low, high = map(float, density_str.split('-'))
                    density_value = (low + high) / 2
                else:
                    density_value = float(density_str)
                
                density_dict[food_name] = density_value
                reference_lines.append(f"{food_name}: {density_value} g/ml")
                logger.debug(f"Added density for {food_name}: {density_value}")
            except (ValueError, TypeError) as e:
                logger.debug(f"Failed to parse density for {food_name}: {str(e)}")
                continue
                
        logger.info(f"Successfully loaded {len(density_dict)} reference density values")
        if density_dict:
            # Log some sample entries
            sample_entries = list(density_dict.items())[:5]
            logger.info("Sample entries from parsed data:")
            for food, density in sample_entries:
                logger.info(f"  {food}: {density} g/ml")
                
        reference_text = "\n".join(reference_lines)
        return density_dict, reference_text
        
    except Exception as e:
        logger.error(f"Failed to load reference file: {str(e)}")
        return None, None
    
def get_density(food_name: str) -> Tuple[Optional[float], str]:
    """
    Get food density by first checking the reference CSV, then falling back to Perplexity API.
    Returns a tuple of (density_value, source) where source is either "reference" or "api"
    """
    # Load reference data
    density_dict, reference_text = load_reference_file()
    
    # Check for exact match in reference data
    if density_dict and food_name.lower() in density_dict:
        density = density_dict[food_name.lower()]
        logger.info(f"Found exact match in reference data for {food_name}: {density}")
        return round(density, 3), "reference"
    
    # # Check for contains match in reference data
    # if density_dict:
    #     # Get all matching foods that contain the search term
    #     matches = [(name, value) for name, value in density_dict.items() 
    #               if food_name.lower() in name]
        
    #     # If we found matches, use the first one
    #     if matches:
    #         food_name, density = matches[0]
    #         logger.info(f"Found contains match in reference data for {food_name}: {density}")
    #         return round(density, 3)

    # If no exact match, query Perplexity API with reference data
    system_content = (
        "You are a precise scientific assistant specializing in food science and density measurements. "
        "Your responses must follow these rules:\n"
        "1. Provide only a single numerical value in g/ml (grams per milliliter)\n"
        "2. Round all values to 3 decimal places\n"
        "3. If a food has multiple forms (e.g., raw vs cooked), assume its most common consumed form\n"
        "4. If uncertain, provide your best estimate based on similar foods\n"
        "5. Do not include units, explanations, or any other text\n"
        "6. If the query is invalid or non-food, respond with '0.000'"
    )
    
    if reference_text:
        system_content += f"\n\nRefer to the following reference densities for guidance:\n\n{reference_text}"

    messages = [
        {
            "role": "system",
            "content": system_content
        },
        {
            "role": "user",
            "content": f"What is the density of {food_name}?"
        }
    ]

    try:
        response = client.chat.completions.create(
            model="sonar-pro",
            messages=messages,
        )
        
        density_str = response.choices[0].message.content.strip()
        try:
            density = round(float(density_str), 3)
            logger.info(f"Successful
[truncated — 2181 more characters]
```