# Project export: Berkeley Meal Builder

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: CruzHacks 2025
- Tagline: We made a personalized meal planning tool that will generate meals for students at UC Berkeley that will fit their dietary goals
- Devpost: https://devpost.com/software/berkeley-meal-builder
- GitHub: https://github.com/jacobjolani/CRUZHACKSBUILDER.git
- Video: https://www.youtube.com/embed/Mf4qdz7oZWY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — DreamBig4College (26 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 5 recognized source files, 11 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (8 of 8)

```
app.py
meal_planner.py
menu_data.json
nutrition_data.json
requirements.txt
scraper.py
static/styles.css
templates/index.html
```

### Dependencies

- requirements.txt: beautifulsoup4@==4.11.1, Flask@==2.3.2, requests@==2.28.2

### Recent commits (newest first)

- updates
- return
- new changes
- pulp
- meal change
- more meals
- menuitems
- logo changes
- logos
- css
- revert back to og
- distutils
- requirements
- mlmodeltest
- ml model
- scraper test
- og back
- reverted to og
- revert
- menu2

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

### requirements.txt

```
Flask==2.3.2
requests==2.28.2
beautifulsoup4==4.11.1

```

### app.py

```python
# app.py
from flask import Flask, render_template, request, jsonify
from meal_planner import calculate_meal_plan
import scraper

app = Flask(__name__)

# Home route serves the HTML page.
@app.route('/')
def index():
    return render_template('index.html')

# Optional endpoint to manually trigger the menu scrape.
@app.route('/scrape', methods=['GET'])
def scrape_menu():
    menu = scraper.scrape_menu()
    return jsonify(menu)

# API endpoint for meal plan generation.
@app.route('/api/mealplan', methods=['POST'])
def get_meal_plan():
    user_goals = request.get_json()
    suggestions = calculate_meal_plan(user_goals)
    return jsonify({"suggestions": suggestions})

if __name__ == "__main__":
    app.run(debug=True)

```

### scraper.py

```python
# scraper.py
import requests
from bs4 import BeautifulSoup
import json
from datetime import datetime

def scrape_menu():
    url = "https://dining.berkeley.edu/menus/"
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "html.parser")
    
    items = []
    # For demonstration, we assume each menu item is contained in an element with class "menu-item".
    # Adjust this selector according to the actual HTML structure.
    for element in soup.find_all(class_="menu-item"):
        text = element.get_text(strip=True)
        if text:
            items.append({"name": text})
    
    menu_data = {
        "date": datetime.now().strftime("%Y-%m-%d"),
        "items": items
    }
    
    with open("menu_data.json", "w") as f:
        json.dump(menu_data, f, indent=4)
    
    return menu_data

if __name__ == "__main__":
    menu = scrape_menu()
    print(f"Scraped {len(menu['items'])} items.")

```

### meal_planner.py

```python
# meal_planner.py
import json
import itertools

def load_menu_data():
    with open("menu_data.json", "r") as f:
        return json.load(f)

def load_nutrition_data():
    with open("nutrition_data.json", "r") as f:
        return json.load(f)

def calculate_meal_plan(user_goals, max_meals=4, top_n=3):
    """
    user_goals: dict with keys "carbs", "proteins", "fats", "calories"
    Returns a list of up to top_n suggestions.
    Each suggestion is a dict with:
      - "meals": list of meal items (each with nutritional info)
      - "total": combined macros totals
      - "diff": the sum-of-absolute-differences between the combo totals and the user goals.
    """
    menu_data = load_menu_data()
    nutrition_data = load_nutrition_data()
    
    # Create a mapping with lower-case keys for matching.
    nutrition_map = { key.lower(): value for key, value in nutrition_data.items() }
    
    available_items = []
    for item in menu_data.get("items", []):
        name = item.get("name")
        if name:
            normalized_name = name.lower()
            if normalized_name in nutrition_map:
                details = nutrition_map[normalized_name].copy()
                details["name"] = name  # Preserve original formatting
                available_items.append(details)
    
    if not available_items:
        return []
    
    suggestions = []
    # Evaluate all combinations with 1 to max_meals items.
    for r in range(1, max_meals + 1):
        for combo in itertools.combinations(available_items, r):
            totals = {"carbs": 0, "proteins": 0, "fats": 0, "calories": 0}
            for item in combo:
                for macro in totals:
                    totals[macro] += item.get(macro, 0)
            # Simple measure: sum of absolute differences between totals and user goals.
            diff = sum(abs(totals[macro] - user_goals.get(macro, 0)) for macro in totals)
            suggestions.append({
                "meals": [item for item in combo],
                "total": totals,
                "diff": diff
            })
    
    # Sort suggestions by how close they are (lower diff is better)
    suggestions.sort(key=lambda x: x["diff"])
    
    return suggestions[:top_n]

# For testing via command line
if __name__ == "__main__":
    sample_goals = {"carbs": 300, "proteins": 100, "fats": 70, "calories": 2000}
    suggestions = calculate_meal_plan(sample_goals)
    for idx, suggestion in enumerate(suggestions, start=1):
        print(f"Option {idx}:")
        for meal in suggestion["meals"]:
            print(f" - {meal['name']}: Carbs {meal.get('carbs')}, Proteins {meal.get('proteins')}, Fats {meal.get('fats')}, Calories {meal.get('calories')}")
        print("Totals:", suggestion["total"])
        print("Diff:", suggestion["diff"])
        print("-----")

```

### static/styles.css

```css
/* Reset default styling */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body {
    font-family: 'Roboto', sans-serif;
    background-color: #f7f7f7;
    color: #333;
    line-height: 1.6;
}

/* Container */
.container {
    width: 90%;
    max-width: 1000px;
    margin: 0 auto;
    padding: 20px;
}

/* Header styling with background image and overlay */
header {
    background: 
        linear-gradient(135deg, rgba(42,42,114,0.8), rgba(0,159,253,0.8)),
        url("../images/students_eating.jpg");
    background-size: cover;
    background-position: center;
    color: white;
    padding: 60px 0;
    text-align: center;
}
.header-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}
.logo-container {
    display: flex;
    align-items: center;
    gap: 20px;
}
.logo-container img {
    width: 150px;  /* Increased logo size */
    height: auto;
}
.logo-container h1 {
    font-size: 3em;
    margin: 0;
}
.tagline {
    font-size: 1.5em;
    font-weight: 500;
}

/* Main form section */
main .form-section {
    background: white;
    padding: 30px;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    margin-bottom: 40px;
}
.form-section h2 {
    margin-bottom: 20px;
    color: #2a2a72;
}

/* Form styling */
.form-group {
    margin-bottom: 15px;
}
.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: 500;
}
.form-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

/* Button styling */
.btn {
    display: inline-block;
    background-color: #009ffd;
    color: white;
    padding: 12px 20px;
    text-decoration: none;
    border: none;
    border-radius: 4px;
    font-size: 1em;
    cursor: pointer;
    transition: background-color 0.3s ease;
}
.btn:hover {
    background-color: #007ac1;
}

/* Meal Plan Results */
.meal-plan {
    background: white;
    padding: 25px;
    border-radius: 8px;
    margin-bottom: 30px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.meal-plan h2 {
    color: #2a2a72;
    margin-bottom: 15px;
}
.meal-item {
    border-top: 1px solid #eee;
    padding: 10px 0;
}
.meal-item:first-child {
    border-top: none;
}
.meal-item h3 {
    font-size: 1.3em;
    color: #007ac1;
    margin-bottom: 5px;
}
.meal-item p {
    font-size: 0.95em;
}
.meal-total {
    border-top: 2px solid #ccc;
    margin-top: 20px;
    padding-top: 15px;
    font-weight: bold;
}
.meal-total h3 {
    margin-bottom: 8px;
}

/* Footer */
footer {
    background: #2a2a72;
    color: white;
    text-align: center;
    padding: 20px 0;
}
.footer-container p {
    font-size: 0.9em;
}

```

### templates/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Daily Meal Planner</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
    <header>
        <div class="header-container container">
            <div class="logo-container">
                <img src="{{ url_for('static', filename='images/ucberkeley_logo.png') }}" alt="UC Berkeley Logo">
                <h1>Daily Meal Planner</h1>
            </div>
            <p class="tagline">A meal planner made for UC Berkeley students</p>
        </div>
    </header>

    <main>
        <div class="container">
            <section class="form-section">
                <h2>Enter Your Nutritional Goals</h2>
                <form id="goalForm">
                    <div class="form-group">
                        <label for="carbs">Carbs (g):</label>
                        <input type="number" id="carbs" name="carbs" required>
                    </div>
                    <div class="form-group">
                        <label for="proteins">Proteins (g):</label>
                        <input type="number" id="proteins" name="proteins" required>
                    </div>
                    <div class="form-group">
                        <label for="fats">Fats (g):</label>
                        <input type="number" id="fats" name="fats" required>
                    </div>
                    <div class="form-group">
                        <label for="calories">Calories:</label>
                        <input type="number" id="calories" name="calories" required>
                    </div>
                    <button type="submit" class="btn">Submit</button>
                </form>
            </section>

            <section id="results" class="results-section">
                <!-- The meal plan suggestions will appear here -->
            </section>
        </div>
    </main>

    <footer>
        <div class="container footer-container">
            <p>&copy; 2025 Daily Meal Planner. All Rights Reserved. Made for UC Berkeley students.</p>
        </div>
    </footer>

    <script>
        document.getElementById("goalForm").addEventListener("submit", function(event) {
            event.preventDefault();

            const formData = {
                carbs: parseInt(document.getElementById("carbs").value, 10),
                proteins: parseInt(document.getElementById("proteins").value, 10),
                fats: parseInt(document.getElementById("fats").value, 10),
                calories: parseInt(document.getElementById("calories").value, 10)
            };

            fetch("/api/mealplan", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(formData)
            })
            .then(response => response.json())
            .then(data => {
                const resultsDiv = document.getElementById("results");
                data.suggestions.forEach((suggestion, index) => {
                    let suggestionHTML = `<div class="meal-plan">
                        <h2>Meal Plan Option ${index + 1}</h2>`;
                    suggestion.meals.forEach(meal => {
                        suggestionHTML += `<div class="meal-item">
                                <h3>${meal.name}</h3>
                                <p>Carbs: ${meal.carbs} g</p>
                                <p>Proteins: ${meal.proteins} g</p>
                                <p>Fats: ${meal.fats} g</p>
                                <p>Calories: ${meal.calories}</p>
                            </div>`;
                    });
                    suggestionHTML += `<div class="meal-total">
                            <h3>Total Macros</h3>
                            <p>Carbs: ${suggestion.total.carbs} g</p>
                            <p>Proteins: ${suggestion.total.proteins} g</p>
                            <p>Fats: ${suggestion.total.fats} g</p>
                            <p>Calories: ${suggestion.total.calories}</p>
                        </div>
                    </div>`;
                    resultsDiv.innerHTML += suggestionHTML;
                });
                document.getElementById("goalForm").reset();
            })
            .catch(error => {
                console.error("Error:", error);
            });
        });
    </script>
</body>
</html>

```