# Project export: BeWear

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: Cal Hacks 12.0
- Tagline: The Ethical Fashion Intelligence Platform
- Devpost: https://devpost.com/software/bewear
- GitHub: https://github.com/haribary/ethical-source
- Video: https://www.youtube.com/embed/apDW__GB-SA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — haribary (15 commits), Adithya Srivastava (11 commits), JadenLee0810 (3 commits), Eric Yu (3 commits)

## Devpost submission (written by the team)

### Inspiration

Fast fashion contributes to unethical labor practices and environmental damage, yet it remains hard for consumers to identify which brands are sustainable. We wanted to make ethical awareness easy as taking a photo. BeWear was inspired by the idea of merging visual recognition and AI-driven transparency to empower everyday consumers to make responsible choices.

### What it does

BeWear allows users to snap a picture of any branded textile or clothing item (or manually enter the brand). The app then detects the brand from the image and retrieves an "ethical score" that is calculated using different variables representing the brand's sourcing, labor, environmental impact, transparency, and more. BeWear gives you an in-depth summary of the variables that went into calculating the "ethical score", and users have the option to use an agent to scrape for real-time events related to greenwashing.

### How we built it

Frontend: Vanilla JS Data Layer: Elastisearch single index, fuzzy matching, typo tolerance, >6000 brands ingested from scraped data Backend: Python SerpAPI(Google Lens API) for Visual brand identification ImgBB for image hosting for google Lens analysis Anthropic (Claude) API for brand extraction, analysis, and use in agent LangGraph for agent workflow orchestration for greenwashing investigation Tavily API for AI optimized web search SSE: real-time status streaming to frontend Agent decision logic ##

### Challenges we ran into

LangGraph state management: there was a bug where part of the state (articles and their respective links) was not mapping correctly, and the agent kept going to the same links. Scraping through 6000+ pages: populating the database with every single brand in the good on you website (essentially we create a MCP for good on you API) ##

### Accomplishments we're proud of

Completed a database of 6000+ brands Easy-to-use UI AI Powered Greenwashing detection ##

### What we learned

We learned how to manage stress under high constraints We learned how to fully integrate front and back end We learned how to obtain real-time updates using SSE We learned how to work efficiently by dividing work within the team ##

### What's next

We will turn BeWear into a browser extension allow it to assist the user during shopping User reviews ML Based similar brand detection

## README (from the GitHub repository)

# BeWear - Ethical Fashion Intelligence Platform

A full-stack AI-powered platform that analyzes fashion brands for ethical practices, identifies greenwashing claims, and empowers consumers to make informed fashion choices.

**Live brand analysis in seconds. AI-powered. No greenwashing.**

![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)
![Status](https://img.shields.io/badge/status-Active%20Development-brightgreen.svg)

## What is BeWear?

BeWear provides instant ethical analysis of fashion brands using three methods:

1. **Text Search** - Search by brand name (Nike, Zara, H&M, etc.)
2. **Image Upload** - Upload a product photo and we'll identify the brand and analyze it
3. **Camera Capture** - Snap a photo in-app of any fashion item
4. **Greenwashing Detection** - Investigate if a brand is making false ethical claims

### Sample Analysis

Input: "Nike"

Output:
```json
{
  "brand_name": "Nike",
  "overall_score": 75.5,
  "rating": "Good",
  "labor_score": 70.0,
  "environmental_score": 80.0,
  "transparency_score": 76.0,
  "supply_chain_score": 75.0,
  "certifications": ["Fair Trade", "GOTS"],
  "analysis": "Nike shows commitment to labor standards with documented policies for fair wages and worker safety. Their environmental efforts focus on sustainable materials and water reduction. However, transparency could be improved in supply chain disclosure..."
}
```

## Key Features

- ⚡ **Instant Analysis** - Get ethical scores within 2-3 seconds
- 📸 **Image Recognition** - Identify brands from product photos using Google Lens
- 🤖 **AI-Powered Insights** - Claude AI explains ethical practices in natural language
- 🔍 **Greenwashing Detection** - Investigates false environmental claims with web search
- 📊 **Comprehensive Scoring** - Labor, environmental, transparency, and supply chain metrics
- 🎯 **Autocomplete** - Real-time brand search suggestions
- 📱 **Mobile Responsive** - Works on phones, tablets, and desktops
- 🚀 **Real-time Updates** - Live status streaming for long-running investigations

## Technology Stack

### Backend
- **Framework**: FastAPI + Uvicorn (Python 3.11)
- **Database**: Elasticsearch 8.11.1+ (local or cloud)
- **AI**: Claude API (Anthropic) - Sonnet 3.5 for analysis, Haiku for quick tasks
- **Search**: Tavily API (greenwashing investigation)
- **Vision**: Google Lens via SerpAPI (image identification)
- **Streaming**: SSE (Server-Sent Events) for real-time updates

### Frontend
- **Framework**: Vanilla JavaScript (no build step)
- **Styling**: Tailwind CSS
- **Icons**: Font Awesome 6.5.1
- **Features**: Tab-based UI, drag-and-drop upload, camera streaming

### DevOps
- **Containerization**: Docker & Docker Compose
- **Package Management**: uv (Python)
- **Environment**: Python venv

## Quick Start

### 1. Prerequisites

- Docker and Docker Compose
- Python 3.11+
- API Keys:
  - `ANTHROPIC_API_KEY` - Get from [Anthropic Console](https://console.anthropic.com/)
  - `TAVILY_API_KEY` - Get from [Tavily](https://tavily.com/) (for greenwashing detection)
  - `SERP_API_KEY` (optional) - Get from [SerpAPI](https://serpapi.com/) for image analysis
  - `IMGBB_API_KEY` (optional) - Get from [ImgBB API](https://api.imgbb.com/) for image hosting

### 2. Setup

```bash
# Clone the repository
git clone https://github.com/haribary/ethical-source.git
cd ethical-source

# Start Elasticsearch
docker-compose up -d

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
cd backend_fastAPI
pip install -r requirements.txt

# Configure environment
cp .env.example .env
# Edit .env with your API keys

# Ingest sample data
python ingest_data.py

# Start backend server
python main.py
# Server runs on http://localhost:8000
```

### 3. Open Frontend

Open `frontend/index.html` in your browser, or serve it:

```bash
cd frontend
python -m http.server 8080
# Open http://localhost:8080 in your browser
```

## API Endpoints

### Text Analysis
```bash
POST /analyze/text
Content-Type: application/json

{"brand_name": "nike"}
```

Returns: Brand ethical analysis with scores, certifications, and AI insights

### Image Analysis
```bash
POST /analyze/image
Content-Type: multipart/form-data

file: <image file>
```

Returns: Brand analysis + Google Lens identified products

### Autocomplete/Search
```bash
GET /autocomplete?q=nik&limit=10
```

Returns: Brand suggestions with ratings

### Greenwashing Investigation (Server-Sent Events)
```bash
POST /analyze/greenwashing
Content-Type: application/json

{"brand_name": "nike"}
```

Streams real-time status updates, then returns:
- Greenwashing risk flag (LOW/MEDIUM/HIGH)
- Summary of findings
- Top articles with citations
- Relevance scores

### Health Check
```bash
GET /health
```

## Architecture Overview

### Data Flow - Text Analysis
```
User Input (Brand Name)
    ↓
Elasticsearch (Fuzzy Search)
    ↓
Brand Data Retrieved
    ↓
Claude AI (Analysis Generation)
    ↓
Formatted Response
    ↓
Frontend Display
```

### Data Flow - Image Analysis
```
User Upload (Image)
    ↓
Image Validation & Upload to ImgBB
    ↓
Google Lens (Product Identification)
    ↓
Claude Extracts Brand Name
    ↓
Elasticsearch Search
    ↓
Claude Analysis
    ↓
Response with Google Lens Results
```

### Data Flow - Greenwashing Detection
```
Brand Name
    ↓
Stream Status: "Looking up brand..."
    ↓
Elasticsearch Lookup
    ↓
Stream Status: "Initializing AI agent..."
    ↓
LangGraph Agent Creates 3 Search Queries
    ↓
Stream Status: "Searching news sources..."
    ↓
Tavily Web Search (Top 10 Results)
    ↓
Stream Status: "Analyzing articles..."
    ↓
Claude Analyzes Each Article
    ↓
Stream Status: "Evaluating greenwashing risk..."
    ↓
Rank by Relevance + Flag (LOW/MEDIUM/HIGH)
    ↓
Stream Final Result
```

## Data Model

### Elasticsearch Index: `ethical_brands`

```json
{
  "name": "Nike",
  "official_name": "Nike, Inc.",
  "overall_score": 75.5,
  "labor_rights_score": 70.0,
  "environmental_impact_score": 80.0,
  "transparency_score": 76.0,
  "supply_chain_ethics_score": 75.0,
  "rating_tier": "Good",
  "certifications": ["Fair Trade", "GOTS"],
  "metrics": [
    {
      "metric_id": "labor-001",
      "metric_name": "Fair Wages",
      "category": "labor_rights",
      "score": 70.5,
      "weight": 2.0,
      "description": "Assessment of fair wages practices"
    }
  ]
}
```

## Project Structure

```
ethical_src/
├── backend_fastAPI/
│   ├── main.py                  # FastAPI application (818 lines)
│   ├── greenwashing_agent.py   # LangGraph-based agent (407 lines)
│   ├── ingest_data.py          # Sample data ingestion
│   ├── requirements.txt        # Python dependencies
│   ├── .env                    # Configuration (create from .env.example)
│   ├── start.sh                # Quick start script
│   ├── README.md               # Backend documentation
│   └── QUICK_START.md          # Quick reference
│
├── frontend/
│   ├── index.html              # Main UI
│   ├── app.js                  # JavaScript logic
│   └── package.json            # Frontend metadata
│
├── docker-compose.yml          # Elasticsearch setup
└── LICENSE                     # MIT License
```

## Development

### Backend Development

```bash
cd backend_fastAPI
source venv/bin/activate

# Run with auto-reload
python main.py

# Run tests
python test_startup.py

# Example usage
python example_usage.py
```

### Frontend Development

No build step needed. Just edit files and refresh browser:

- `index.html` - UI structure and styling
- `app.js` - JavaScript logic and API calls

### Adding New Brands

Edit `backend_fastAPI/ingest_data.py` and add brands to the sample data:

```python
brands = [
    {
        "_id": "mybrand-001",
        "name": "MyBrand",
        "official_name": "MyBrand Inc.",
        "overall_score": 85.0,
        # ... other fields
    }
]
```

Then re-ingest:
```bash
python ingest_data.

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 20 recognized source files, 190 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
- LangChain (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (25 of 25)

```
.gitignore
backend_fastAPI/ARCHITECTURE.md
backend_fastAPI/example_usage.py
backend_fastAPI/greenwashing_agent.py
backend_fastAPI/ingest_data.py
backend_fastAPI/ingest_scraped_data.py
backend_fastAPI/main.py
backend_fastAPI/QUICK_START.md
backend_fastAPI/README.md
backend_fastAPI/requirements.txt
backend_fastAPI/start.sh
backend_fastAPI/test_startup.py
CHANGES_SUMMARY.md
docker-compose.yml
frontend/app.js
frontend/home.html
frontend/index.html
frontend/network_setup.txt
frontend/package.json
LICENSE
README.md
src/get_brands.py
src/google_lens_api.py
src/main.py
src/utils.py
```

### Dependencies

- backend_fastAPI/requirements.txt: anthropic@>=0.41.0, elasticsearch[async]@==8.11.1, fastapi@>=0.109.0, google-search-results@==2.4.2, httpx@==0.26.0, langchain-anthropic@==0.3.3, langgraph@==0.2.47, Pillow@==10.2.0, pydantic@>=2.7.4, pydantic-settings@>=2.1.0, pytest@==7.4.4, pytest-asyncio@==0.23.3, python-dotenv@==1.0.0, python-json-logger@==2.0.7, python-multipart@==0.0.6, requests@==2.31.0, sse-starlette@>=2.1.3, tavily-python@==0.5.0, uvicorn[standard]@>=0.27.0

### Recent commits (newest first)

- README.md added
- fixed sse greenwashing drop in tunnel
- Remove duplicates
- Pushed changes
- Fix greenwashing agent: SSE streaming, JSON parsing, top 10 analysis
- Merge branch 'main' of https://github.com/haribary/ethical-source into main
- Agent
- .
- Refactor Backend
- added setup guide for phone-computer network
- added camera functionality
- .
- .
- .
- ai
- ingested data elastisearch
- .
- .
- .
- Search done frontend

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

### CHANGES_SUMMARY.md

```markdown
# Greenwashing Agent Improvements

## Issues Fixed

### 1. JSON Parsing Error ✅
**Problem**: `ERROR:greenwashing_agent:Article analysis error: Expecting value: line 1 column 1 (char 0)`

**Root Cause**: Claude's API sometimes returns responses with markdown code blocks or additional formatting instead of pure JSON.

**Solution** (greenwashing_agent.py:199-218):
- Added robust JSON parsing that handles markdown code blocks
- Extracts JSON from ```json``` or ``` ``` blocks
- Gracefully skips articles that can't be parsed instead of crashing
- Logs failed parsing attempts for debugging

### 2. Top 10 Search Results Analysis ✅
**Problem**: Agent was analyzing 15 results inconsistently

**Solution**:
- **Search** (greenwashing_agent.py:113): Increased `max_results=10` per query
- **Analysis** (greenwashing_agent.py:183-188): Added sorting by Tavily relevance score and limiting to top 10
- Now consistently analyzes the 10 most relevant articles from all search results

### 3. Dynamic Agent Status Display ✅
**Problem**: Frontend showed static loading spinner with no visibility into what the agent was doing

**Solution**:

#### Backend (main.py:616-714)
- Converted `/analyze/greenwashing` to use Server-Sent Events (SSE)
- Streams real-time status updates as agent progresses:
  - 🔍 Looking up brand in database...
  - 🤖 Initializing AI agent...
  - 🔍 Generating search queries...
  - 📰 Searching news sources...
  - 🤖 Analyzing articles with AI...
  - ⚖️ Evaluating greenwashing risk...
- Final result sent as JSON event

#### Frontend (app.js:534-617)
- Changed from simple POST request to streaming fetch with ReadableStream
- Parses SSE messages in real-time
- Updates status box dynamically as messages arrive
- Shows different icons and messages for each stage

#### UI Improvements (index.html:156-189)
- Enhanced loading screen with larger spinner
- Beautiful gradient status box with animated icon
- Shows current action with visual progress bar
- Lists investigation scope for user transparency

## New User Experience

When investigating greenwashing:

1. **Before**: Static spinner, no feedback, user waits blindly
2. **After**: Live updates showing:
   - "🔍 Looking up brand in database..."
   - "🤖 Initializing AI agent..."
   - "🔍 Generating search queries..."
   - "📰 Searching news sources..."
   - "🤖 Analyzing articles with AI..."
   - "⚖️ Evaluating greenwashing risk..."

Just like ChatGPT, Claude, or other modern LLM platforms!

## Testing

Start your backend and test:

```bash
cd backend_fastAPI
python main.py
```

Then open the frontend and try investigating a brand like "H&M" or "Zara". You should see:
- ✅ No more JSON parsing errors
- ✅ Consistent analysis of top 10 results
- ✅ Live status updates showing agent progress

## Files Modified

1. `backend_fastAPI/greenwashing_agent.py` - Fixed JSON parsing, top 10 filtering
2. `backend_fastAPI/main.py` - Added SSE streaming endpoint
3. `frontend/app.js` - Stream-based status updates
4. `frontend/
[truncated — 34 more characters]
```

### backend_fastAPI/QUICK_START.md

```markdown
# Quick Start Guide

## 🎯 What You Need

This is the **simplified version** - no MCP server, no agent builder!

## 📁 Files Overview

| File | Purpose | Status |
|------|---------|--------|
| `main.py` | ✅ **USE THIS** - Clean FastAPI server | Ready to use |
| `unified_ethical_api.py` | ❌ Old (uses MCP server) | Ignore |
| `agent_builder_mcp.py` | ❌ Old (agent builder) | Ignore |
| `main_elasticsearch.py` | ❌ Old (ES only, no Claude) | Ignore |
| `requirements.txt` | ✅ Dependencies | Already installed |
| `.env` | ✅ Config file | **Fill in your keys!** |

## 🚀 Getting Started (3 Steps)

### Step 1: Configure Environment

Edit `.env` and add your API keys:

```bash
# Required for all features
ELASTIC_CLOUD_ID=your-elastic-deployment-id
ELASTIC_API_KEY=your-elastic-api-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key

# Required only for image analysis
SERP_API_KEY=your-serpapi-key
IMGBB_API_KEY=your-imgbb-key
```

### Step 2: Ingest Sample Data (Optional)

If your Elasticsearch is empty:

```bash
source ethical/bin/activate
python ingest_data.py
```

### Step 3: Start Server

```bash
./start.sh
```

Or manually:

```bash
source ethical/bin/activate
python main.py
```

Server runs on: **http://localhost:8000**

## 🧪 Test It

### Option 1: Quick Test
```bash
source ethical/bin/activate
python test_startup.py
```

### Option 2: Full Example
```bash
source ethical/bin/activate
python example_usage.py
```

### Option 3: Manual cURL

**Text analysis:**
```bash
curl -X POST http://localhost:8000/analyze/text \
  -H "Content-Type: application/json" \
  -d '{"brand_name": "nike"}'
```

**Image analysis:**
```bash
curl -X POST http://localhost:8000/analyze/image \
  -F "file=@path/to/image.jpg"
```

## 📊 API Endpoints

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/` | GET | API info |
| `/health` | GET | Health check |
| `/analyze/text` | POST | Analyze brand by name |
| `/analyze/image` | POST | Analyze brand from image |

## 🔄 How It Works

### Text Flow
```
Frontend sends "nike"
  ↓
Elasticsearch searches for "nike"
  ↓
Found: Nike brand data (scores, metrics, certifications)
  ↓
Claude analyzes the data
  ↓
Response: Natural language analysis + scores
```

### Image Flow
```
Frontend uploads image
  ↓
Upload to ImgBB → Get public URL
  ↓
Google Lens searches the URL
  ↓
Returns: ["Nike Air Max", "Nike Shoes", ...]
  ↓
Claude extracts brand name: "nike"
  ↓
Elasticsearch searches for "nike"
  ↓
Claude analyzes the data
  ↓
Response: Analysis + scores + original image matches
```

## 🎨 Frontend Integration

Your React/Next.js frontend should call:

### Text Analysis
```javascript
const response = await fetch('http://localhost:8000/analyze/text', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ brand_name: 'nike' })
});

const data = await response.json();
console.log(data.analysis); // Claude's analysis
console.log(data.overall_score); // 75.5
console.log(data.rating); // "Good"
```
[truncated — 1490 more characters]
```

### docker-compose.yml

```yaml
version: '3.8'

services:
  elasticsearch:
    image: elasticsearch:8.15.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - es_data:/usr/share/elasticsearch/data
    networks:
      - elastic

volumes:
  es_data:
    driver: local

networks:
  elastic:
    driver: bridge
```

### frontend/package.json

```
{
  "name": "ethical-fashion-frontend",
  "version": "1.0.0",
  "description": "Frontend for ethical fashion brand identifier",
  "main": "index.html",
  "scripts": {
    "start": "open index.html"
  },
  "author": "",
  "license": "MIT"
}
```

### backend_fastAPI/requirements.txt

```
# FastAPI Backend with Elasticsearch Requirements

# Core FastAPI
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
pydantic>=2.7.4  # Required by langchain-anthropic
pydantic-settings>=2.1.0
sse-starlette>=2.1.3  # Server-Sent Events for streaming

# Elasticsearch
elasticsearch[async]==8.11.1

# AI & ML
anthropic>=0.41.0  # Claude AI for analysis (required by langchain-anthropic)
langgraph==0.2.47  # LangGraph for agent workflows
tavily-python==0.5.0  # Tavily for web search
langchain-anthropic==0.3.3  # LangChain Claude integration

# Google Lens via SerpAPI
google-search-results==2.4.2  # SerpAPI for Google Lens

# Image processing
Pillow==10.2.0

# Environment and configuration
python-dotenv==1.0.0

# HTTP requests
requests==2.31.0

# Logging and monitoring
python-json-logger==2.0.7

# Testing (optional)
pytest==7.4.4
pytest-asyncio==0.23.3
httpx==0.26.0

# CORS and middleware
python-multipart==0.0.6
```

### src/main.py

```python
from src.get_brands import extract_brand_with_claude
from src.google_lens_api import get_top_titles
import os
from dotenv import load_dotenv
import json

load_dotenv()
SERP_API_KEY = os.getenv("SERP_API_KEY")
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")

image_url = "https://dimg.dillards.com/is/image/DillardsZoom/zoom/polo-ralph-lauren--classic-fit-performance-stretch-short-sleeve-polo-shirt/00000000_zi_c30485b2-e89d-448c-a62f-03cff0023548.jpg"
file_path = "data/brands_simple.json"

# Load brands
with open(file_path, 'r') as f:
    brands = json.load(f)

# Get titles
titles = get_top_titles(image_url, SERP_API_KEY, top_n=5)

print("Top 5 titles:")
for i, title in enumerate(titles, 1):
    print(f"  {i}. {title}")

# Extract brand
brand = extract_brand_with_claude(titles, ANTHROPIC_API_KEY)
print(f'\nCLAUDE BRAND: {brand}')

# Get dots
if brand in brands:
    dots = brands[brand]
    print(f'\nFound')
    print(f'  Score: {dots}/5')
else:
    print(f'\n✗ Brand "{brand}" not found in database')
```

### frontend/app.js

```javascript
const API_URL = 'http://localhost:8000';
const USE_MOCK = false;



// Elements
const tabImage = document.getElementById('tab-image');
const tabCamera = document.getElementById('tab-camera');
const tabText = document.getElementById('tab-text');
const imageSection = document.getElementById('image-section');
const cameraSection = document.getElementById('camera-section');
const textSection = document.getElementById('text-section');
const fileInput = document.getElementById('file-input');
const dropZone = document.getElementById('drop-zone');
const preview = document.getElementById('preview');
const analyzeImageBtn = document.getElementById('analyze-image-btn');
const cameraVideo = document.getElementById('camera-video');
const cameraCanvas = document.getElementById('camera-canvas');
const cameraPreview = document.getElementById('camera-preview');
const startCameraBtn = document.getElementById('start-camera-btn');
const captureBtn = document.getElementById('capture-btn');
const analyzeCameraBtn = document.getElementById('analyze-camera-btn');
const retakeBtn = document.getElementById('retake-btn');
const brandInput = document.getElementById('brand-input');
const analyzeTextBtn = document.getElementById('analyze-text-btn');
const loading = document.getElementById('loading');
const results = document.getElementById('results');
const error = document.getElementById('error');
const autocompleteDropdown = document.getElementById('autocomplete-dropdown');

let selectedFile = null;
let autocompleteTimeout = null;
let cameraStream = null;
let capturedPhoto = null;

// Score breakdown dropdown toggle
document.addEventListener('DOMContentLoaded', () => {
  const toggleBtn = document.getElementById('score-breakdown-toggle');
  const content = document.getElementById('score-breakdown-content');
  const arrow = document.getElementById('dropdown-arrow');

  if (toggleBtn) {
    toggleBtn.addEventListener('click', () => {
      const isHidden = content.classList.contains('hidden');
      
      if (isHidden) {
        content.classList.remove('hidden');
        arrow.classList.add('rotate-180');
      } else {
        content.classList.add('hidden');
        arrow.classList.remove('rotate-180');
      }
    });
  }
});

// Tab switching
tabImage.addEventListener('click', () => {
  imageSection.classList.remove('hidden');
  textSection.classList.add('hidden');
  tabImage.classList.add('bg-green-600', 'text-white');
  tabImage.classList.remove('bg-gray-200', 'text-gray-700');
  tabText.classList.remove('bg-green-600', 'text-white');
  tabText.classList.add('bg-gray-200', 'text-gray-700');
  cameraSection.classList.add('hidden');
  textSection.classList.add('hidden');
  stopCamera();
  setActiveTab(tabImage)
});

tabCamera.addEventListener('click', () => {
  cameraSection.classList.remove('hidden');
  imageSection.classList.add('hidden');
  textSection.classList.add('hidden');
  setActiveTab(tabCamera);
});

tabText.addEventListener('click', () => {
  textSection.classList.remove('hidden');
  imageSection.classList.add('hidden');
  tabText.classList.add('bg-green-600', 'text-white');
  tabText.classList.remove('bg-gray-200', 'text-gray-700');
  tabImage.classList.remove('bg-green-600', 'text-white');
  tabImage.classList.add('bg-gray-200', 'text-gray-700');
  cameraSection.classList.add('hidden');
  stopCamera();
  setActiveTab(tabText);
});

function setActiveTab(activeTab) {
  [tabImage, tabCamera, tabText].forEach(tab => {
    if (tab === activeTab) {
      tab.classList.add('bg-green-600', 'text-white');
      tab.classList.remove('bg-gray-200', 'text-gray-700');
    } else {
      tab.classList.remove('bg-green-600', 'text-white');
      tab.classList.add('bg-gray-200', 'text-gray-700');
    }
  });
}

// Image upload
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));

function handleFile(file) {
  if (file && file.type.startsWith('image/')) {
    selectedFile = file;
    const reader = new FileReader();
    reader.onload = (e) => {
      preview.src = e.target.result;
      preview.classList.remove('hidden');
      analyzeImageBtn.classList.remove('hidden');
    };
    reader.readAsDataURL(file);
  }
}

// Analyze image
analyzeImageBtn.addEventListener('click', async () => {
  if (!selectedFile) return;
  await analyzeImage(selectedFile);
});

// Analyze text
analyzeTextBtn.addEventListener('click', async () => {
  const brand = brandInput.value.trim();
  if (!brand) return;
  hideAutocomplete();
  await analyzeText(brand);
});

// Autocomplete on input
brandInput.addEventListener('input', (e) => {
  const query = e.target.value.trim();
  if (autocompleteTimeout) clearTimeout(autocompleteTimeout);
  if (query.length < 1) {
    hideAutocomplete();
    return;
  }
  autocompleteTimeout = setTimeout(() => fetchAutocomplete(query), 300);
});

// Handle Enter key
brandInput.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    const brand = brandInput.value.trim();
    if (brand) {
      hideAutocomplete();
      analyzeText(brand);
    }
  }
});

// Hide autocomplete when clicking outside
document.addEventListener('click', (e) => {
  if (!brandInput.contains(e.target) && !autocompleteDropdown.contains(e.target)) {
    hideAutocomplete();
  }
});

// IMAGE endpoint
async function analyzeImage(file) {
  hideAll();
  loading.classList.remove('hidden');

  try {
    console.log('📤 Sending image to /analyze/image');
    console.log('   File:', file.name, file.type, file.size, 'bytes');

    const formData = new FormData();
    formData.append('file', file);

    const response = await fetch(`${API_URL}/analyze/image`, {
      method: 'POST',
      body: formData
    });

    const result = await response.json();
    console.log('📥 Received:', result);

    if (response.ok) {
      displayResults(result);
    } else {
      showError(result.detail || 'Failed to analyze image');
    }
  } catch (err) {
    conso
[truncated — 22670 more characters]
```

### backend_fastAPI/main.py

```python
"""
Ethical Fashion Analysis API
Simple FastAPI backend with Elasticsearch + Claude

Flow 1 (Text): Frontend → Brand Name → ES Search → Claude Analysis → Response
Flow 2 (Image): Frontend → Image → ImgBB → Google Lens → Brand Extraction → ES Search → Claude Analysis → Response
"""

from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from datetime import datetime
import os
from dotenv import load_dotenv
import logging
import base64
import requests
from elasticsearch import AsyncElasticsearch
from PIL import Image
import io
import anthropic
from serpapi import GoogleSearch
from sse_starlette.sse import EventSourceResponse
from starlette.responses import StreamingResponse
import asyncio
import json
from greenwashing_agent import create_detective

# Load environment
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize FastAPI
app = FastAPI(
    title="Ethical Fashion API",
    description="Analyze fashion brands from text or images",
    version="1.0.0"
)

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize clients
claude_client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
es_client: Optional[AsyncElasticsearch] = None

# Configuration
ELASTICSEARCH_HOST = os.getenv("ELASTICSEARCH_HOST", "http://localhost:9200")
ELASTICSEARCH_API_KEY = os.getenv("ELASTICSEARCH_API_KEY")
ELASTICSEARCH_USER = os.getenv("ELASTICSEARCH_USER", "elastic")
ELASTICSEARCH_PASSWORD = os.getenv("ELASTICSEARCH_PASSWORD")
SERP_API_KEY = os.getenv("SERP_API_KEY")
IMGBB_API_KEY = os.getenv("IMGBB_API_KEY")
BRANDS_INDEX = "ethical_brands"


# ============================================================================
# SCORE CALCULATION FUNCTIONS
# ============================================================================

def convert_rating_to_score(rating: int) -> Optional[float]:
    """Convert 1-5 rating to 0-100 score"""
    if rating == -1:
        return None
    score_mapping = {5: 100.0, 4: 80.0, 3: 60.0, 2: 40.0, 1: 20.0}
    return score_mapping.get(rating, 0.0)


def calculate_overall_score(planet_score: int, people_score: int, animal_score: int) -> float:
    """Calculate weighted overall score: Planet(35%) + People(40%) + Animal(25%)"""
    planet = convert_rating_to_score(planet_score)
    people = convert_rating_to_score(people_score)
    animal = convert_rating_to_score(animal_score)
    
    if animal is None:
        overall = (planet * 0.45) + (people * 0.55)
    else:
        overall = (planet * 0.35) + (people * 0.40) + (animal * 0.25)
    
    return round(overall, 1)


def convert_overall_rating_to_tier(overall_rating: str) -> str:
    """Convert Good On You rating to tier"""
    rating_map = {
        "Great": "Excellent",
        "Good": "Good",
        "It's a Start": "Fair",
        "Not Good Enough": "Poor",
        "We Avoid": "Very Poor"
    }
    return rating_map.get(overall_rating, "Unknown")


def enrich_brand_with_calculations(brand_data: Dict[str, Any]) -> Dict[str, Any]:
    """Take raw brand data and add calculated scores"""
    # Get raw ratings from data (these are the 1-5 ratings from the database)
    planet_raw = brand_data.get('planet_score', 0)  # This is the 1-5 rating
    people_raw = brand_data.get('people_score', 0)  # This is the 1-5 rating
    animal_raw = brand_data.get('animal_score', -1) # This is the 1-5 rating
    
    # Convert to 0-100 scale for internal calculations
    planet_100 = convert_rating_to_score(planet_raw)
    people_100 = convert_rating_to_score(people_raw)
    animal_100 = convert_rating_to_score(animal_raw)
    
    # Calculate overall score (0-100)
    overall = calculate_overall_score(planet_raw, people_raw, animal_raw)
    
    # Add calculated fields to brand data
    brand_data['overall_score'] = overall
    brand_data['planet_score_100'] = planet_100 if planet_100 is not None else 0.0
    brand_data['people_score_100'] = people_100 if people_100 is not None else 0.0
    brand_data['animal_score_100'] = animal_100 if animal_100 is not None else 0.0
    brand_data['rating_tier'] = convert_overall_rating_to_tier(brand_data.get('overall', 'Unknown'))
    
    # IMPORTANT: Keep the original 1-5 ratings for display
    brand_data['planet_rating'] = planet_raw
    brand_data['people_rating'] = people_raw
    brand_data['animal_rating'] = animal_raw
    
    logger.info(f"Enriched brand: planet_rating={planet_raw}, people_rating={people_raw}, animal_rating={animal_raw}")
    
    return brand_data


# ============================================================================
# PYDANTIC MODELS
# ============================================================================

class TextAnalysisRequest(BaseModel):
    """Request for text-based brand search"""
    brand_name: str = Field(..., description="Name of the fashion brand")


class BrandAnalysisResponse(BaseModel):
    """Response with ethical analysis"""
    brand_name: str
    detection_method: str
    google_lens_titles: Optional[List[str]] = None
    analysis: str
    overall_score: float
    rating: str
    planet_score: float
    people_score: float
    animal_score: float
    planet_rating: int
    people_rating: int
    animal_rating: int
    certifications: List[str]
    description: Optional[str] = None
    analyzed_at: datetime


class BrandSuggestion(BaseModel):
    """Single brand suggestion"""
    name: str
    official_name: Optional[str]
    rating: str
    overall_score: float


class AutocompleteResponse(BaseModel):
    """Autocomplete response with suggestions"""
    query: str
    suggestions: List[BrandSuggestion]
    total: int


class GreenwashingRequest(BaseModel):
    """Request for greenwashing investigation"""
    brand_name: str = Field(..., descri
[truncated — 23856 more characters]
```

### backend_fastAPI/start.sh

```shell
#!/bin/bash

# Start the Ethical Fashion API

echo "🚀 Starting Ethical Fashion API..."
echo ""

# Activate virtual environment
source ethical/bin/activate

# Check if .env exists
if [ ! -f .env ]; then
    echo "⚠️  Warning: .env file not found!"
    echo "Please create .env file with your API keys"
    exit 1
fi

# Start the server
echo "Starting server on http://localhost:8000"
echo "Press Ctrl+C to stop"
echo ""

uvicorn main:app --reload --host 0.0.0.0 --port 8000

```

### src/google_lens_api.py

```python
from serpapi import GoogleSearch
def get_top_titles(image_url, api_key, top_n=5):
    params = {
        "engine": "google_lens",
        "url": image_url,
        "api_key": api_key
    }
    
    try:
        search = GoogleSearch(params)
        results = search.get_dict()
        
        if "visual_matches" in results:
            matches = results["visual_matches"][:top_n]
            titles = [match.get("title", "N/A") for match in matches]
            return titles
        else:
            return []
            
    except Exception as e:
        print(f"Error: {e}")
        return []





```

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