# Project export: Pare

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 2026
- Tagline: The Dining Hall Dilemma: See waste. Predict demand. Reduce excess.
- Devpost: https://devpost.com/software/pare-98vjwq
- GitHub: https://github.com/javierreyno/treehacks2026.git
- Video: https://www.youtube.com/embed/Yy3MjKZ6a5k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Javier Reynoso (3 commits)

## Devpost submission (written by the team)

### Inspiration

As students, we see it every day: untouched broccoli, half-eaten pasta, and full servings thrown away in dining halls. Multiplied across thousands of students, this adds up to an enormous environmental and operational problem. In fact, on average, universities waste 650,000 pounds of food annually. The core issue is visibility. Once food enters the trash bin, dining halls lose all insight into what was wasted, how much, and why. Without data, dining halls can’t adjust supply, leading to systemic overproduction and unnecessary waste. We wanted to make food waste measurable, actionable, and preventable.

### What it does

Pare has two core components: Computer Vision Waste Detection Using computer vision, Pare scans a student's plate before food is discarded. Our model identifies and estimates the quantity of uneaten food, automatically logging waste data to a real-time dashboard. This transforms previously invisible waste into structured, actionable data. Computer Vision Waste Detection Using computer vision, Pare scans a student's plate before food is discarded. Our model identifies and estimates the quantity of uneaten food, automatically logging waste data to a real-time dashboard. This transforms previously invisible waste into structured, actionable data. Predictive Waste Analytics Pare uses a gradient boosted decision tree model to analyze historical waste patterns and identify trends. Our system can answer questions such as: Predictive Waste Analytics Pare uses a gradient boosted decision tree model to analyze historical waste patterns and identify trends. Our system can answer questions such as: Which foods are most frequently wasted? How does waste vary by day, weather, or academic schedule? How much of each food should be prepared to minimize waste? These insights enable dining hall staff to make data-driven decisions, reducing both food waste and operational costs.

### What's next

Ultimately, we hope to deploy Pare in real dining halls to help universities reduce food waste, lower costs, and operate more sustainably.

## README (from the GitHub repository)

# treehacks2026

## Detected evidence (automated analysis)

Indexed codebase: 25 recognized source files, 149 KB.
- Anthropic (technology) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (43 of 43)

```
project/.gitignore
project/backend/.env.example
project/backend/.gcloudignore
project/backend/agent.py
project/backend/app.py
project/backend/cloudbuild.yaml
project/backend/CONTEXT_TRACKING.md
project/backend/COST_QUICK_REFERENCE.md
project/backend/COST_STRUCTURE.md
project/backend/data_generator.py
project/backend/data/daily_context.csv
project/backend/data/menu_schedule.csv
project/backend/data/minimal_dataset.csv
project/backend/data/waste_logs_backup.csv
project/backend/data/waste_logs.csv
project/backend/DEPLOY.md
project/backend/deploy.sh
project/backend/Dockerfile
project/backend/model_output/label_encoders.joblib
project/backend/model_output/model_metadata.json
project/backend/model_output/waste_predictor.json
project/backend/predictor.py
project/backend/requirements.txt
project/backend/test_costs.py
project/backend/train_model.py
project/backend/vision.py
project/backend/weather.py
project/DEPLOY.md
project/DEPLOYMENT.md
project/frontend/.env.example
project/frontend/DEPLOY.md
project/frontend/index.html
project/frontend/package.json
project/frontend/public/logo_text.png:Zone.Identifier
project/frontend/public/pare_logo.png:Zone.Identifier
project/frontend/src/App.jsx
project/frontend/src/main.jsx
project/frontend/vercel.json
project/frontend/vite.config.js
project/INSTALL_GCLOUD.sh
project/README.md
project/RPI_SETUP.md
README.md
```

### Dependencies

- project/backend/requirements.txt: anthropic@==0.43.0, fastapi@==0.109.0, google-cloud-bigquery@==3.14.1, httpx@==0.26.0, joblib@==1.3.2, numpy@==1.26.3, pandas@==2.2.0, python-dotenv@==1.0.0, python-multipart@==0.0.6, scikit-learn@==1.4.0, sse-starlette@==2.0.0, uvicorn@==0.27.0, xgboost@==2.1.0
- project/frontend/package.json: @types/react@^18.2.43, @vitejs/plugin-react@^4.2.1, autoprefixer@^10.4.16, lucide-react@^0.263.1, postcss@^8.4.32, react@^18.2.0, react-dom@^18.2.0, recharts@^2.10.0, tailwindcss@^3.4.0, vite@^5.0.8

### Recent commits (newest first)

- update
- Add real-time RPi upload notifications to frontend chat
- Add deployment configurations and documentation
- Initial commit
- Initial commit

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

### project/DEPLOY.md

```markdown
# PlateIQ Deployment Guide

Step-by-step instructions for getting PlateIQ running locally and deployed.

---

## PHASE 1: Local Development (first 30 minutes)

### 1.1 Create the GitHub repo

```bash
# On GitHub: Create new repo "plateiq" (private)
# Then locally:
git clone https://github.com/YOUR_TEAM/plateiq.git
cd plateiq
# Copy all the scaffold files into this directory
```

### 1.2 Set up the backend

```bash
cd backend

# Create virtual environment
python3 -m venv venv
source venv/bin/activate          # Mac/Linux
# venv\Scripts\activate           # Windows

# Install dependencies
pip install -r requirements.txt

# Create your .env file
cp .env.example .env
# Edit .env and add your real ANTHROPIC_API_KEY
```

### 1.3 Add your data files

Copy these into `backend/data/`:
- `waste_logs.csv`     (3,910 rows — synthetic waste data)
- `daily_context.csv`  (99 rows — weather + events)
- `menu_schedule.csv`  (1,355 rows — what was served when)

### 1.4 Train the model

```bash
cd backend
python train_model.py
# This creates backend/model_output/ with:
#   waste_predictor.json     (the XGBoost model)
#   label_encoders.joblib    (categorical encoders)
#   model_metadata.json      (feature names + metrics)
```

### 1.5 Run the backend

```bash
cd backend
uvicorn app:app --reload --port 8000

# Test it works:
# Open http://localhost:8000 → should see {"status":"ok"}
# Open http://localhost:8000/api/dashboard/summary → should see waste stats
```

### 1.6 Set up the frontend

```bash
# New terminal
cd frontend
npm install

# Create .env
echo "VITE_API_URL=http://localhost:8000" > .env

npm run dev
# Opens at http://localhost:5173
```

### 1.7 Test the full loop

1. Open http://localhost:5173
2. Go to Dashboard tab → should see waste stats and charts
3. Go to Chat tab → type "What are the highest waste items?" → should get agent response
4. Go to Upload tab → upload any food photo → should get vision analysis

**If all three work, your local stack is complete.**

---

## PHASE 2: Deploy Backend to Google Cloud Run

### 2.1 Install Google Cloud CLI

```bash
# Mac
brew install google-cloud-sdk

# Or download from https://cloud.google.com/sdk/docs/install
```

### 2.2 Set up Google Cloud project

```bash
# Login
gcloud auth login

# Create project (or use existing one)
gcloud projects create plateiq-treehacks --name="PlateIQ"
gcloud config set project plateiq-treehacks

# Enable required APIs
gcloud services enable run.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable bigquery.googleapis.com

# Set default region
gcloud config set run/region us-central1
```

### 2.3 Deploy to Cloud Run

```bash
cd backend

# This single command builds the Docker container and deploys it:
gcloud run deploy plateiq-backend \
  --source . \
  --region us-central1 \
  --allow-unauthenticated \
  --memory 1Gi \
  --set-env-vars="ANTHROPIC_API_KEY=sk-ant-xxxxx"

# It will:
# 1. Build your Dockerfile in Google Cloud Build
# 2. Push the contain
[truncated — 3888 more characters]
```

### project/DEPLOYMENT.md

```markdown
# PlateIQ Deployment Guide

Complete guide to deploying your PlateIQ application with:
- **Backend**: Google Cloud Run (FastAPI + ML models)
- **Frontend**: Vercel (React + Vite)
- **RPi Integration**: Camera module sending images to backend

## 📋 Prerequisites

### Accounts Needed
- [ ] Google Cloud account with billing enabled
- [ ] Vercel account (free tier is fine)
- [ ] Anthropic API account (Claude API key)

### Tools to Install
```bash
# Google Cloud CLI
# Install from: https://cloud.google.com/sdk/docs/install
gcloud --version

# Verify Docker (should already be installed)
docker --version

# Vercel CLI (optional, for CLI deployment)
npm install -g vercel
```

## 🚀 Deployment Order

Deploy in this order to ensure proper configuration:

1. **Backend first** → Get backend URL
2. **Frontend second** → Use backend URL in config
3. **Update backend CORS** → Allow frontend URL
4. **Configure RPi** → Point to production backend

---

## 1️⃣ Deploy Backend to Google Cloud Run

### Step 1: Setup Google Cloud

```bash
# Login to Google Cloud
gcloud auth login

# Set your project (or create new one)
gcloud config set project YOUR_PROJECT_ID

# Enable required APIs
gcloud services enable cloudbuild.googleapis.com run.googleapis.com
```

### Step 2: Deploy Backend

```bash
cd backend

# Run the deployment script
chmod +x deploy.sh
./deploy.sh
```

This will:
- Build Docker container
- Push to Google Container Registry
- Deploy to Cloud Run
- Output your backend URL (save this!)

### Step 3: Set Environment Variables

Go to [Cloud Run Console](https://console.cloud.google.com/run):
1. Click on `plateiq-backend` service
2. Click "Edit & Deploy New Revision"
3. Add these environment variables:

```
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxx
GOOGLE_CLOUD_PROJECT=your-project-id
BIGQUERY_DATASET=plateiq
FRONTEND_URL=*
```

(We'll update `FRONTEND_URL` after deploying frontend)

4. Click "Deploy"

### ✅ Test Backend

```bash
# Replace with your actual URL
BACKEND_URL="https://plateiq-backend-xxx.run.app"

# Test health endpoint
curl $BACKEND_URL/health

# Expected: {"healthy": true}
```

**Save your backend URL!** You'll need it for the frontend.

📖 **Detailed guide**: See [`backend/DEPLOY.md`](backend/DEPLOY.md)

---

## 2️⃣ Deploy Frontend to Vercel

### Option A: Via Vercel Dashboard (Recommended)

#### Step 1: Push to GitHub

```bash
# From project root
git add .
git commit -m "Setup deployment configs"
git push origin main
```

#### Step 2: Import to Vercel

1. Go to https://vercel.com/new
2. Import your GitHub repository
3. Configure:
   - **Root Directory**: `frontend`
   - **Framework Preset**: Vite
   - **Build Command**: `npm run build`
   - **Output Directory**: `dist`

#### Step 3: Add Environment Variable

In Vercel project settings → Environment Variables:
```
VITE_API_URL=https://plateiq-backend-xxx.run.app
```
Replace with your actual Cloud Run backend URL from Step 1.

#### Step 4: Deploy

Click "Deploy" and wait for build to complete!
[truncated — 8250 more characters]
```

### project/backend/requirements.txt

```
fastapi==0.109.0
uvicorn==0.27.0
python-multipart==0.0.6
anthropic==0.43.0
xgboost==2.1.0
scikit-learn==1.4.0
pandas==2.2.0
numpy==1.26.3
joblib==1.3.2
google-cloud-bigquery==3.14.1
python-dotenv==1.0.0
httpx==0.26.0
sse-starlette==2.0.0

```

### project/backend/Dockerfile

```
FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code + model artifacts + data
COPY . .

# Cloud Run sets PORT env var
ENV PORT=8080
EXPOSE 8080

# Start FastAPI
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]

```

### project/frontend/package.json

```
{
  "name": "plateiq-frontend",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "recharts": "^2.10.0",
    "lucide-react": "^0.263.1"
  },
  "devDependencies": {
    "@types/react": "^18.2.43",
    "@vitejs/plugin-react": "^4.2.1",
    "autoprefixer": "^10.4.16",
    "postcss": "^8.4.32",
    "tailwindcss": "^3.4.0",
    "vite": "^5.0.8"
  }
}

```

### project/frontend/src/main.jsx

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

```

### project/backend/app.py

```python
"""PlateIQ Backend v3 — FastAPI with exact food list."""

import os
from dotenv import load_dotenv
load_dotenv()

import json, base64
from datetime import datetime
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from vision import analyze_plate_image
from predictor import WastePredictor
from agent import PlateIQAgent
from weather import get_current_weather, is_exam_week

# ═══════════════════════════════════════════════════════════════════
# TIER-BASED COST CONFIGURATION
# ═══════════════════════════════════════════════════════════════════
# Costs are per portion (150g serving) and include ingredients + prep

ITEM_COSTS = {
    # Proteins (highest cost - meat/fish)
    "fish": 5.50,
    "grilled_chicken": 3.50,
    "fried_chicken": 3.00,
    "ground_beef": 3.25,
    "tofu": 2.00,
    "plant_based_protein": 2.50,
    "beans": 0.75,
    "lentils": 0.60,

    # Entrees (prepared items)
    "pizza": 2.50,
    "burgers": 3.00,
    "mac_and_cheese": 1.75,
    "chicken_tenders": 2.75,
    "french_fries": 1.00,

    # Carbs (bulk starches)
    "white_rice": 0.40,
    "brown_rice": 0.50,
    "fried_rice": 1.25,
    "pasta": 0.75,
    "quinoa": 1.50,
    "mashed_potatoes": 0.80,
    "roasted_potatoes": 0.90,
    "noodles": 0.85,

    # Vegetables (produce + prep)
    "broccoli": 1.20,
    "carrots": 0.60,
    "green_beans": 1.00,
    "spinach": 1.40,
    "mixed_vegetables": 1.10,
    "roasted_vegetables": 1.30,
    "salad_greens": 1.25,
    "brussels_sprouts": 1.80,

    # Salad bar (raw produce)
    "lettuce": 0.50,
    "tomatoes": 0.75,
    "cucumbers": 0.70,
    "salad_mix": 1.00,

    # Desserts (baked goods)
    "cookies": 0.50,
    "cake": 1.50,
    "ice_cream": 1.25,
    "brownies": 0.75,
}

# Fallback costs by category if item not found
CATEGORY_FALLBACK_COSTS = {
    "proteins": 3.00,
    "entrees": 2.25,
    "carbs": 0.85,
    "vegetables": 1.10,
    "salad_bar": 0.80,
    "desserts": 1.00,
}

def get_item_cost(item_name: str, category: str = "unknown") -> float:
    """Get cost per portion for an item, with category fallback."""
    return ITEM_COSTS.get(item_name, CATEGORY_FALLBACK_COSTS.get(category, 2.00))

app = FastAPI(title="PlateIQ API", version="3.0.0")
allowed_origins = os.environ.get("FRONTEND_URL", "*").split(",")
app.add_middleware(CORSMiddleware, allow_origins=allowed_origins, allow_credentials=True,
                   allow_methods=["*"], allow_headers=["*"])

predictor = WastePredictor(model_dir="./model_output")
agent = PlateIQAgent()

# Store recent uploads so the frontend can poll for RPi results
recent_uploads = []


class ChatRequest(BaseModel):
    message: str
    conversation_history: list = []

class PredictRequest(BaseModel):
    items: list[dict]
    temp_f: float
    is_raining: bool = False
    day_of_week: str
    quarter: str = "Winter"
    week_in_quarter: int = 5
    is_exam_week: bool = False


@app.get("/")
async def root():
    return {"status": "ok", "service": "PlateIQ", "version": "3.0"}

@app.get("/health")
async def health():
    return {"healthy": True}


@app.post("/api/plates/upload")
async def upload_plate(
    file: UploadFile = File(...),
    meal_period: str = None,
    quarter: str = "Winter",
    week_in_quarter: int = 5,
    # Optional manual overrides for weather context
    temp_f: float = None,
    is_raining: bool = None
):
    """
    Upload a plate image for waste analysis.
    Meal period is auto-detected based on time if not provided.
    Weather context (temp_f, is_raining) is auto-detected if not provided.
    """
    contents = await file.read()
    b64 = base64.b64encode(contents).decode("utf-8")
    vision = await analyze_plate_image(b64, file.content_type or "image/jpeg")

    # DEBUG: Log vision results
    print("\n=== VISION ANALYSIS DEBUG ===")
    print(f"Items detected: {len(vision['items'])}")
    for item in vision["items"]:
        print(f"  - {item.get('item')}: {item.get('pct_remaining')}% remaining, {item.get('est_weight_g')}g estimated")
    print(f"Raw response: {vision.get('raw_response', 'N/A')[:200]}...")
    print("============================\n")

    # Get current context (weather, traffic, etc.)
    today = datetime.now()
    day_of_week = today.strftime("%A")

    # Auto-detect meal period based on time if not provided
    if meal_period is None:
        hour = today.hour
        if hour < 11:
            meal_period = "breakfast"
        elif hour >= 11 and hour < 14:
            meal_period = "lunch"
        elif hour >= 17:
            meal_period = "dinner"
        else:
            meal_period = "lunch"  # Default for afternoon (2-5pm)

    # Fetch weather if not manually provided
    if temp_f is None or is_raining is None:
        weather = await get_current_weather()
        temp_f = temp_f or weather["temp_f"]
        is_raining = is_raining if is_raining is not None else weather["is_raining"]

    # Auto-detect exam week
    exam_week = is_exam_week(week_in_quarter, quarter)

    # Build context for predictions
    context = {
        "temp_f": temp_f,
        "is_raining": is_raining,
        "day_of_week": day_of_week,
        "is_exam_week": exam_week
    }

    enriched = []
    for item in vision["items"]:
        # Use food category as station (since we removed station parameter)
        station = item.get("category", "unknown")

        pred = predictor.predict(
            item=item["item"],
            station=station,
            meal_period=meal_period,
            temp_f=temp_f,
            day_of_week=day_of_week,
            quarter=quarter,
            week_in_quarter=week_in_quarter
        )
        # Determine risk from ACTUAL observed waste, not model prediction
        actual_pct = item.get("pct_remaining", 0) / 100
        risk = "high" if actual_pct > 0.40 else "medium" if actual_pct > 0.25 else "low"

        enriched.append({
  
[truncated — 20796 more characters]
```

### project/frontend/src/App.jsx

```javascript
import { useState, useEffect, useRef } from "react";

const API_URL = import.meta.env.VITE_API_URL || "";

const BRAND = "#278a47";
const BRAND_LIGHT = "#4db86a";

// Strip excessive markdown formatting from agent responses
function cleanResponse(text) {
  return text
    .replace(/#{1,4}\s*/g, "")           // remove markdown headers
    .replace(/\*\*\*(.+?)\*\*\*/g, "$1") // bold+italic
    .replace(/\*\*(.+?)\*\*/g, "$1")     // bold
    .replace(/\*(.+?)\*/g, "$1")         // italic
    .replace(/^[-]{3,}$/gm, "")          // horizontal rules
    .replace(/\n{3,}/g, "\n\n")          // collapse excessive newlines
    .trim();
}

export default function App() {
  const [view, setView] = useState("chat"); // "chat" | "dashboard" | "upload"
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [dashboardData, setDashboardData] = useState(null);
  const [correlations, setCorrelations] = useState(null);
  const [weeklySuggestions, setWeeklySuggestions] = useState(null);
  const messagesEndRef = useRef(null);
  const lastUploadTimestamp = useRef(null);

  // Auto-scroll chat
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  // Poll for new RPi uploads and show them in chat
  useEffect(() => {
    const poll = setInterval(async () => {
      try {
        const url = lastUploadTimestamp.current
          ? `${API_URL}/api/plates/recent?since=${encodeURIComponent(lastUploadTimestamp.current)}`
          : `${API_URL}/api/plates/recent`;
        const res = await fetch(url);
        const data = await res.json();
        const uploads = data.uploads || [];
        if (uploads.length > 0) {
          // On first poll, show only the most recent upload as a catch-up;
          // on subsequent polls, show all new uploads
          const isFirstPoll = !lastUploadTimestamp.current;
          const newUploads = isFirstPoll ? uploads.slice(-1) : uploads;
          lastUploadTimestamp.current = uploads[uploads.length - 1].timestamp;
          for (const upload of newUploads) {
            if (upload.items && upload.items.length > 0) {
              const summary = upload.items
                .map((i) => {
                  const item = i.item.replace(/_/g, " ");
                  const pct = i.pct_remaining || 0;
                  const weight = i.est_weight_g || 0;
                  return `${item}: ${pct}% food remaining on plate (~${weight}g waste) [${i.risk_level} risk]`;
                })
                .join("\n");
              setMessages((prev) => [
                ...prev,
                { role: "assistant", content: `Plate scanned!\n\n${summary}\n\nTotal waste: ${upload.summary.estimated_waste_g}g` },
              ]);
              loadDashboardData();
            }
          }
        }
      } catch (e) {
        // Polling failure is non-critical
      }
    }, 5000);
    return () => clearInterval(poll);
  }, []);

  // Load dashboard data
  const loadDashboardData = () => {
    fetch(`${API_URL}/api/dashboard/summary`)
      .then((r) => r.json())
      .then(setDashboardData)
      .catch(console.error);
    fetch(`${API_URL}/api/dashboard/correlations`)
      .then((r) => r.json())
      .then(setCorrelations)
      .catch(console.error);
    fetch(`${API_URL}/api/dashboard/weekly-suggestions`)
      .then((r) => r.json())
      .then(setWeeklySuggestions)
      .catch(console.error);
  };

  useEffect(() => {
    if (view === "dashboard") {
      loadDashboardData();
    }
  }, [view]);

  // Send chat message
  const sendMessage = async () => {
    if (!input.trim() || loading) return;
    const userMsg = { role: "user", content: input };
    setMessages((prev) => [...prev, userMsg]);
    setInput("");
    setLoading(true);

    try {
      const res = await fetch(`${API_URL}/api/chat`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          message: input,
          conversation_history: messages,
        }),
      });
      const data = await res.json();
      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: data.response, tools: data.tools_used },
      ]);
    } catch (err) {
      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: "Sorry, I couldn't connect to the server." },
      ]);
    }
    setLoading(false);
  };

  // Upload plate photo
  const uploadPhoto = async (file) => {
    const formData = new FormData();
    formData.append("file", file);

    // Auto-detect meal period based on current time
    const hour = new Date().getHours();
    let meal_period;
    if (hour < 11) {
      meal_period = "breakfast";
    } else if (hour >= 11 && hour < 14) {
      meal_period = "lunch";
    } else if (hour >= 17) {
      meal_period = "dinner";
    } else {
      meal_period = "lunch"; // Default for afternoon (2-5pm)
    }
    formData.append("meal_period", meal_period);

    setLoading(true);
    try {
      const res = await fetch(`${API_URL}/api/plates/upload`, {
        method: "POST",
        body: formData,
      });
      const data = await res.json();

      // Always reload dashboard data after upload (so it's ready when user switches to dashboard)
      loadDashboardData();

      // Add result as a chat message
      const summary = data.items
        .map((i) => {
          const item = i.item.replace(/_/g, " ");
          const pct = i.pct_remaining || 0;
          const weight = i.est_weight_g || 0;
          return `${item}: ${pct}% food remaining on plate (~${weight}g waste) [${i.risk_level} risk]`;
        })
        .join("\n");
      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: `Plate analyzed!\n\n${summary}\n\nTotal waste: ${data.summary.estimated_waste_g}g\n\nDashboard updated.` },
      ]);
      setView("chat");
    } catch (err) {
      
[truncated — 14138 more characters]
```

### project/INSTALL_GCLOUD.sh

```shell
#!/bin/bash
# Google Cloud SDK Installation Script for Ubuntu/Debian
# Run this script to install gcloud CLI

set -e

echo "🔧 Installing Google Cloud SDK..."

# Add Google Cloud package repository
echo "📦 Adding Google Cloud repository..."
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg

echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list

# Update and install
echo "📥 Installing gcloud CLI..."
sudo apt-get update
sudo apt-get install -y google-cloud-cli

# Verify installation
echo ""
echo "✅ Installation complete!"
gcloud version

echo ""
echo "🚀 Next steps:"
echo "   1. Run: gcloud auth login"
echo "   2. Run: cd backend && ./deploy.sh"

```

### project/frontend/vite.config.js

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

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
      },
    },
  },
})

```

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