# Project export: SCOPE.ai

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: AI-powered contract analysis tool for government Statements of Work (SOWs). Detects overlapping tasks and discrepancies in government contracts.
- Devpost: https://devpost.com/software/scope-ai
- GitHub: https://github.com/anishsrinivasa/CalHacksSubmission
- Team: 3 GitHub contributor(s) — anishsrinivasa (4 commits), skolhe74 (3 commits), Claude (1 commits)

## Devpost submission (written by the team)

### Overview

Problem Statement Government agencies waste billions annually on: Duplicate contracts: Multiple agencies buying the same services without coordination Weak KPIs: Vague metrics like "improve satisfaction" without measurable targets Scope creep: Open-ended language leading to budget overruns Missing elements: Lack of acceptance criteria, assumptions, or success metrics Solution An AI-powered web application that analyzes SOW documents to: Extract structured data (tasks, KPIs, deliverables, metadata) Identify weak or unmeasurable KPIs Flag scope creep language and red flags Detect missing critical elements Generate SMART KPI alternatives (Phase 3) Compare across contracts for duplication (Phase 4) Architecture ┌─────────────────┐ ┌─────────────────┐ │ Next.js │ HTTPS │ FastAPI │ │ Frontend │ ◄─────► │ Backend │ │ │ │ (Railway) │ └─────────────────┘ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Claude API │ │ (Anthropic) │ └─────────────────┘ Tech Stack Backend: Python 3.12 FastAPI for REST API Anthropic Claude API (Haiku model) PyMuPDF (PDF parsing) python-docx (DOCX parsing) Frontend: Next.js 15 React 18 TypeScript Tailwind CSS Axios for API calls Deployment: Backend: Railway (https://railway.app) Features Current (MVP) File Upload: Drag-and-drop interface for PDF, DOCX, TXT files Data Extraction: Automatically extracts: Contract metadata (ID, contractor, value, dates) Objectives and tasks KPIs and deliverables Scope and personnel requirements Risk Analysis: Identifies: Weak KPIs (missing targets, baselines, timelines) Scope creep language Missing critical elements Red flags and inconsistencies Beautiful Dashboard: Color-coded severity levels, downloadable results Coming Soon SMART KPI Generator: AI-generated specific, measurable alternatives Cross-Contract Analysis: Detect duplicate or overlapping work Batch Processing: Analyze multiple SOWs at once Historical Comparison: Track improvements over time Project Structure calhacks/ ├── main.py # FastAPI backend server ├── sow_extractor.py # Data extraction ├── risk_analyzer.py # Risk analysis ├── rag_analyzer.py # RAG-based overlap detection ├── overlap_analyzer.py # Overlap detection logic ├── vector_db_setup.py # Vector database initialization ├── annotated_examples.json # Training data for RAG ├── requirements.txt # Python dependencies ├── railway.json # Railway deployment config ├── sample_nyserda_sow.txt # Sample SOW for testing │ ├── frontend/ # Next.js frontend │ ├── app/ │ │ ├── page.tsx # Main page │ │ ├── layout.tsx # Root layout │ │ └── globals.css # Global styles │ ├── components/ │ │ ├── FileUpload.tsx # Upload interface │ │ ├── ResultsDashboard.tsx # Results display │ │ └── LoginModal.tsx # Login interface │ ├── package.json # Node dependencies │ └── next.config.js # Next.js configuration │ └── chroma_db/ # Vector database storage Local Development Prerequisites Python 3.12+ Node.js 18+ Anthropic API key (Get one here) Backend Setup pip install -r requirements.txt echo "ANTHROPIC_API_KEY=your-key-here" > .env python vector_db_setup.py python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000 Backend will be available at: http://localhost:8000 Frontend Setup cd frontend npm install npm run dev Frontend will be available at: http://localhost:3000 Deployment See DEPLOYMENT.md for detailed deployment instructions. Quick Summary: Deploy Backend to Railway Push to GitHub Connect repo to Railway Add ANTHROPIC_API_KEY environment variable Get backend URL Connect GitHub repo Set NEXT_PUBLIC_API_URL to Railway backend URL Deploy Cost Analysis Service Free Tier Usage Railway $5/month credit Backend hosting Claude API Pay per use ~$0.02-0.05 per analysis Total: Free for <100 analyses/month Testing Test with Sample SOW Upload the included sample_nyserda_sow.txt file through the UI or test API directly: curl -X POST http://localhost:8000/api/analyze \ -F "file=@sample_nyserda_sow.txt" Expected Results The analyzer should find: 14+ issues total Weak KPIs like "reduce processing time to target levels" Scope creep: "ongoing support as needed" Missing elements: specific acceptance criteria How It Works Pass 1: Extraction Uses Claude API to extract structured data from raw SOW text Identifies: metadata, tasks, KPIs, deliverables, scope Returns: JSON with all extracted fields Pass 2: Risk Analysis Analyzes extracted data for issues Categories: weak KPIs, scope creep, missing elements, red flags Assigns severity: HIGH, MEDIUM, LOW Pass 3: Enhancement (Coming Soon) Generates SMART alternatives for weak KPIs Provides specific, measurable, achievable recommendations Pass 4: Overlap Detection (Coming Soon) Compares across multiple SOWs Identifies duplicate or overlapping work Calculates potential savings API Documentation POST /api/analyze Upload and analyze a SOW document. Request: POST /api/analyze Content-Type: multipart/form-data file: Response: { "success": true, "filename": "contract.pdf", "contract_id": "SOW-2024-001", "contractor": "Example Corp", "summary": { "total_findings": 14, "high_severity": 3, "medium_severity": 7, "low_severity": 4, "tasks_found": 8, "kpis_found": 12 }, "extracted_data": { ... }, "analysis": { "weak_kpis": [...], "scope_creep": [...], "missing_elements": [...], "red_flags": [...] } } GET / Health check endpoint. Response: { "status": "online", "service": "SOW Analyzer API", "version": "1.0.0" } Contributing This is a hackathon project. Contributions welcome! Fork the repository Create a feature branch Make your changes Submit a pull request License MIT License - see LICENSE file for details Acknowledgments Built using assistance from Claude AI by Anthropic for certain analysis function(s) Frontend powered by Next.js Backend powered by FastAPI Deployed on Railway

## README (from the GitHub repository)

# SOW Analyzer

AI-powered analysis tool for government contract Statements of Work (SOWs). Detects waste, weak KPIs, scope creep, and missing critical elements.

![Status](https://img.shields.io/badge/status-ready%20to%20deploy-green)
![License](https://img.shields.io/badge/license-MIT-blue)

## Problem Statement

Government agencies waste billions annually on:
- **Duplicate contracts**: Multiple agencies buying the same services without coordination
- **Weak KPIs**: Vague metrics like "improve satisfaction" without measurable targets
- **Scope creep**: Open-ended language leading to budget overruns
- **Missing elements**: Lack of acceptance criteria, assumptions, or success metrics

## Solution

An AI-powered web application that analyzes SOW documents to:
1. Extract structured data (tasks, KPIs, deliverables, metadata)
2. Identify weak or unmeasurable KPIs
3. Flag scope creep language and red flags
4. Detect missing critical elements
5. Generate SMART KPI alternatives (Phase 3)
6. Compare across contracts for duplication (Phase 4)

## Architecture

```
┌─────────────────┐         ┌─────────────────┐
│   Next.js       │  HTTPS  │   FastAPI       │
│   Frontend      │ ◄─────► │   Backend       │
│   (Vercel)      │         │   (Railway)     │
└─────────────────┘         └────────┬────────┘
                                     │
                                     ▼
                            ┌─────────────────┐
                            │  Claude API     │
                            │  (Anthropic)    │
                            └─────────────────┘
```

### Tech Stack

**Backend:**
- Python 3.12
- FastAPI for REST API
- Anthropic Claude API (Haiku model)
- PyMuPDF (PDF parsing)
- python-docx (DOCX parsing)

**Frontend:**
- Next.js 15
- React 18
- TypeScript
- Tailwind CSS
- Axios for API calls

**Deployment:**
- Backend: Railway (https://railway.app)
- Frontend: Vercel (https://vercel.com)

## Features

### Current (MVP)

- **File Upload**: Drag-and-drop interface for PDF, DOCX, TXT files
- **Data Extraction**: Automatically extracts:
  - Contract metadata (ID, contractor, value, dates)
  - Objectives and tasks
  - KPIs and deliverables
  - Scope and personnel requirements
- **Risk Analysis**: Identifies:
  - Weak KPIs (missing targets, baselines, timelines)
  - Scope creep language
  - Missing critical elements
  - Red flags and inconsistencies
- **Beautiful Dashboard**: Color-coded severity levels, downloadable results

### Coming Soon

- **SMART KPI Generator**: AI-generated specific, measurable alternatives
- **Cross-Contract Analysis**: Detect duplicate or overlapping work
- **Batch Processing**: Analyze multiple SOWs at once
- **Historical Comparison**: Track improvements over time

## Project Structure

```
calhacks/
├── main.py                  # FastAPI backend server
├── sow_extractor.py         # Data extraction
├── risk_analyzer.py         # Risk analysis
├── rag_analyzer.py          # RAG-based overlap detection
├── overlap_analyzer.py      # Overlap detection logic
├── vector_db_setup.py       # Vector database initialization
├── annotated_examples.json  # Training data for RAG
├── requirements.txt         # Python dependencies
├── railway.json             # Railway deployment config
├── sample_nyserda_sow.txt   # Sample SOW for testing
│
├── frontend/                # Next.js frontend
│   ├── app/
│   │   ├── page.tsx        # Main page
│   │   ├── layout.tsx      # Root layout
│   │   └── globals.css     # Global styles
│   ├── components/
│   │   ├── FileUpload.tsx       # Upload interface
│   │   ├── ResultsDashboard.tsx # Results display
│   │   └── LoginModal.tsx       # Login interface
│   ├── package.json        # Node dependencies
│   └── next.config.js      # Next.js configuration
│
└── chroma_db/              # Vector database storage
```

## Local Development

### Prerequisites

- Python 3.12+
- Node.js 18+
- Anthropic API key ([Get one here](https://console.anthropic.com/))

### Backend Setup

```bash
# Install dependencies
pip install -r requirements.txt

# Create .env file
echo "ANTHROPIC_API_KEY=your-key-here" > .env

# Initialize vector database
python vector_db_setup.py

# Run server
python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
```

Backend will be available at: http://localhost:8000

### Frontend Setup

```bash
cd frontend

# Install dependencies
npm install

# Run development server
npm run dev
```

Frontend will be available at: http://localhost:3000

## Deployment

See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions.

**Quick Summary:**

1. **Deploy Backend to Railway**
   - Push to GitHub
   - Connect repo to Railway
   - Add `ANTHROPIC_API_KEY` environment variable
   - Get backend URL

2. **Deploy Frontend to Vercel**
   - Connect GitHub repo to Vercel
   - Set `NEXT_PUBLIC_API_URL` to Railway backend URL
   - Deploy

## Cost Analysis

| Service | Free Tier | Usage |
|---------|-----------|-------|
| Railway | $5/month credit | Backend hosting |
| Vercel | Unlimited deploys | Frontend hosting |
| Claude API | Pay per use | ~$0.02-0.05 per analysis |

**Total**: Free for <100 analyses/month

## Testing

### Test with Sample SOW

Upload the included `sample_nyserda_sow.txt` file through the UI or test API directly:

```bash
curl -X POST http://localhost:8000/api/analyze \
  -F "file=@sample_nyserda_sow.txt"
```

### Expected Results

The analyzer should find:
- 14+ issues total
- Weak KPIs like "reduce processing time to target levels"
- Scope creep: "ongoing support as needed"
- Missing elements: specific acceptance criteria

## How It Works

### Pass 1: Extraction
- Uses Claude API to extract structured data from raw SOW text
- Identifies: metadata, tasks, KPIs, deliverables, scope
- Returns: JSON with all extracted fields

### Pass 2: Risk Analysis
- Analyzes extracted data for issues
- Categories: weak KPIs, scope creep, missing elements, red flags
- Assigns severity: HIGH, MEDIUM, LOW

### Pass 3: Enhancement (Coming Soon)
- Generates SMART alternatives for weak KPIs
- Provides specific, measurable, achievable recommendations

### Pass 4: Overlap Detection (Coming Soon)
- Compares across multiple SOWs
- Identifies duplicate or overlapping work
- Calculates potential savings

## API Documentation

### POST /api/analyze

Upload and analyze a SOW document.

**Request:**
```
POST /api/analyze
Content-Type: multipart/form-data

file: <PDF/DOCX/TXT file>
```

**Response:**
```json
{
  "success": true,
  "filename": "contract.pdf",
  "contract_id": "SOW-2024-001",
  "contractor": "Example Corp",
  "summary": {
    "total_findings": 14,
    "high_severity": 3,
    "medium_severity": 7,
    "low_severity": 4,
    "tasks_found": 8,
    "kpis_found": 12
  },
  "extracted_data": { ... },
  "analysis": {
    "weak_kpis": [...],
    "scope_creep": [...],
    "missing_elements": [...],
    "red_flags": [...]
  }
}
```

### GET /

Health check endpoint.

**Response:**
```json
{
  "status": "online",
  "service": "SOW Analyzer API",
  "version": "1.0.0"
}
```

## Contributing

This is a hackathon project. Contributions welcome!

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request

## License

MIT License - see LICENSE file for details

## Acknowledgments

- Built using assistance from [Claude AI](https://anthropic.com) by Anthropic for certain analysis function(s)
- Frontend powered by [Next.js](https://nextjs.org)
- Backend powered by [FastAPI](https://fastapi.tiangolo.com)
- Deployed on [Railway](https://railway.app) and [Vercel](https://vercel.com)

## Contact

For questions or feedback, please open an issue on GitHub.

---

Built to detect waste and improve government contracting.


## Detected evidence (automated analysis)

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

## Codebase structure (from repository index)

### Files (31 of 31)

```
.env.example
.gitignore
annotated_examples.json
chroma_db/chroma.sqlite3
DEPLOYMENT.md
frontend/.gitignore
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.tsx
frontend/components/FileUpload.tsx
frontend/components/LoginModal.tsx
frontend/components/ResultsDashboard.tsx
frontend/next.config.js
frontend/package.json
frontend/postcss.config.mjs
frontend/tailwind.config.ts
frontend/tsconfig.json
frontend/vercel.json
main.py
overlap_analyzer.py
QUICKSTART.md
rag_analyzer.py
RAG_SETUP.md
railway.json
README.md
requirements.txt
risk_analyzer.py
sample_nyserda_sow.txt
sow_extractor.py
TECH_STACK.md
vector_db_setup.py
```

### Dependencies

- frontend/package.json: @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.4.17, axios@^1.6.0, eslint@^8, eslint-config-next@^15.0.2, geist@^1.5.1, next@^15.0.2, postcss@^8, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^3.4.1, typescript@^5
- requirements.txt: anthropic@==0.39.0, chromadb@>=0.4.0, fastapi@==0.104.1, pymupdf@==1.23.8, python-docx@==1.1.0, python-dotenv@==1.0.0, python-multipart@==0.0.6, sentence-transformers@>=2.2.0, torch@>=2.0.0, uvicorn[standard]@==0.24.0

### Recent commits (newest first)

- Fix TypeScript error: add overlap_analysis to AnalysisResult type
- Update acknowledgments section
- Remove emojis from documentation
- Clean up documentation for submission - Remove outdated docs (FILE_STRUCTURE, STATUS, AI_PIPELINE_PASSES) - Update README and QUICKSTART with correct paths and ports - Remove Get Started button from navigation
- Add RAG analyzer and UI enhancements
- Complete SOW Analyzer MVP
- Add SOW risk analyzer backend implementation
- first commit

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

### QUICKSTART.md

```markdown
# Quick Start Guide

## Try It Now (Locally)

Your application is **already running** and ready to test!

### Current Status

**Backend**: Running at http://localhost:8000
**Frontend**: Running at http://localhost:3010

### Test the Application

1. **Open your browser**
   ```
   http://localhost:3010
   ```

2. **Upload a sample SOW**
   - Use the sample file: `sample_nyserda_sow.txt`
   - Or drag & drop any PDF/DOCX/TXT SOW document
   - Max file size: 10MB

3. **Wait for analysis**
   - Analysis takes 30-60 seconds
   - You'll see a loading spinner
   - Results will display automatically

4. **Review the results**
   - Summary cards show issue counts
   - Weak KPIs section shows problematic metrics
   - Scope creep warnings highlight risky language
   - Download results as JSON

### Expected Results (Sample SOW)

When you upload `sample_nyserda_sow.txt`, you should see:

- **Total Issues**: ~14 findings
- **High Severity**: 3-4 issues
- **Medium Severity**: 6-8 issues
- **Low Severity**: 3-5 issues

#### Sample Weak KPIs Found:
- "Reduce processing time to target levels" → Missing specific target
- "Achieve system availability during business hours" → No uptime percentage
- "Improve citizen satisfaction" → No baseline or target

#### Sample Scope Creep:
- "ongoing support as needed"
- "reasonable assistance to agency staff"

### Test the API Directly

```bash
# Health check
curl http://localhost:8000/

# Analyze a file
curl -X POST http://localhost:8000/api/analyze \
  -F "file=@sample_nyserda_sow.txt"
```

## Deploy to Production

Once you're happy with local testing:

1. **Push to GitHub**
   ```bash
   git init
   git add .
   git commit -m "Initial commit - SOW Analyzer"
   git remote add origin https://github.com/YOUR_USERNAME/sow-analyzer.git
   git push -u origin main
   ```

2. **Deploy Backend to Railway**
   - Visit https://railway.app/new
   - Connect your GitHub repo
   - Add `ANTHROPIC_API_KEY` environment variable
   - Deploy → Copy backend URL

3. **Deploy Frontend to Vercel**
   - Visit https://vercel.com/new
   - Connect your GitHub repo
   - Set root directory to `frontend`
   - Add `NEXT_PUBLIC_API_URL` = your Railway URL
   - Deploy

See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed instructions.

## Troubleshooting

### Backend won't start
```bash
pip install -r requirements.txt
python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000
```

### Frontend won't start
```bash
cd frontend
npm install
npm run dev
```

### "Analysis failed" error
- Check that backend is running (http://localhost:8000/)
- Verify ANTHROPIC_API_KEY is set in `.env`
- Check that API key has credits remaining

### CORS errors
- Ensure backend is running on port 8000
- Frontend automatically points to http://localhost:8000 in dev mode

## Next Steps

1. Test locally with sample SOW
2. Verify all features work
3. Deploy to Railway and Vercel
4. Share your deployed URL!

---

**Need help?** Check the main [README.md](README.md) or [DEPLOYMENT.md](DEP
[truncated — 12 more characters]
```

### DEPLOYMENT.md

```markdown
# Deployment Guide

## Overview
This application consists of two parts:
- **Backend**: FastAPI server (deployed on Railway)
- **Frontend**: Next.js application (deployed on Vercel)

## Quick Start (Web-based Deployment)

This guide uses the web interfaces - no CLI installation needed!

## Backend Deployment (Railway)

### Prerequisites
- Railway account (sign up at https://railway.app)
- Anthropic API key (from your account)

### Steps

1. **Go to Railway Dashboard**
   - Visit https://railway.app
   - Click "Start a New Project"
   - Select "Deploy from GitHub repo" or "Empty Project"

2. **If using Empty Project:**
   - Click "Add Service" → "Empty Service"
   - In the service settings, go to "Settings" tab
   - Under "Source", click "Connect Repo" or "Upload Files"
   - Upload your `backend` folder

3. **Configure Environment Variables**
   - In your Railway project, click on your service
   - Go to "Variables" tab
   - Add the following variables:
     ```
     ANTHROPIC_API_KEY=sk-ant-api03-...
     ```

4. **Configure Deployment**
   - Railway will auto-detect Python and use the `railway.json` config
   - It will automatically install dependencies from `requirements.txt`
   - Start command: `uvicorn main:app --host 0.0.0.0 --port $PORT`

5. **Generate a Public URL**
   - Go to "Settings" tab
   - Click "Generate Domain" under "Networking"
   - Copy your backend URL (e.g., `https://your-app.railway.app`)

### Configuration
The backend uses `railway.json` for configuration:
- Build: NIXPACKS (auto-detects Python)
- Start command: `uvicorn main:app --host 0.0.0.0 --port $PORT`
- Restart policy: ON_FAILURE with max 10 retries

## Frontend Deployment (Vercel)

### Prerequisites
- Vercel account (sign up at https://vercel.com)
- Backend deployed and URL obtained from Railway

### Steps

1. **Go to Vercel Dashboard**
   - Visit https://vercel.com
   - Click "Add New..." → "Project"

2. **Import Your Project**
   - Option A: Import from Git (recommended)
     - Connect your GitHub/GitLab/Bitbucket account
     - Select your repository
     - Select the `frontend` folder as the root directory
   - Option B: Deploy from local
     - Use drag & drop to upload your `frontend` folder

3. **Configure Project**
   - Framework Preset: **Next.js** (should auto-detect)
   - Root Directory: `./frontend` (if deploying from repo root)
   - Build Command: `npm run build` (auto-detected)
   - Output Directory: `.next` (auto-detected)

4. **Set Environment Variables**
   - In the deployment settings, add environment variables:
     - Key: `NEXT_PUBLIC_API_URL`
     - Value: `https://your-backend-url.railway.app` (use your actual Railway URL)
   - Apply to: **All environments** or just **Production**

5. **Deploy**
   - Click "Deploy"
   - Wait for deployment to complete (usually 1-2 minutes)
   - Vercel will give you a URL like: `https://your-app.vercel.app`

6. **Test Your Application**
   - Visit your Vercel URL
   - Upload a sample SOW document
   - Verify the 
[truncated — 2635 more characters]
```

### requirements.txt

```
anthropic==0.39.0
python-dotenv==1.0.0
pymupdf==1.23.8
python-docx==1.1.0
fastapi==0.104.1
uvicorn[standard]==0.24.0
python-multipart==0.0.6
chromadb>=0.4.0
sentence-transformers>=2.2.0
torch>=2.0.0

```

### frontend/package.json

```
{
  "name": "sow-analyzer-frontend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "axios": "^1.6.0",
    "geist": "^1.5.1",
    "next": "^15.0.2",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.4.17",
    "eslint": "^8",
    "eslint-config-next": "^15.0.2",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### main.py

```python
"""
FastAPI Backend for SOW Analyzer
"""
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import os
import json
from typing import List
import tempfile
from datetime import datetime

# Import our analysis modules
from sow_extractor import extract_from_file
from risk_analyzer import analyze_sow

# Try to import RAG analyzer (may fail if dependencies not installed)
try:
    from rag_analyzer import analyze_sow_with_rag
    RAG_AVAILABLE = True
    print("[OK] RAG analysis available")
except ImportError as e:
    RAG_AVAILABLE = False
    print(f"[WARNING] RAG analysis not available: {e}")
    print(f"   Install with: pip install chromadb sentence-transformers torch")
    print(f"   Using basic analysis for now...")

# Try to import overlap analyzer
try:
    from overlap_analyzer import analyze_overlap
    OVERLAP_AVAILABLE = True
    print("[OK] Overlap analysis available")
except ImportError as e:
    OVERLAP_AVAILABLE = False
    print(f"[WARNING] Overlap analysis not available: {e}")

app = FastAPI(
    title="SOW Analyzer API",
    description="AI-powered analysis of government contract Statements of Work",
    version="1.0.0"
)

# Enable CORS for frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # In production, specify your Vercel domain
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/")
def read_root():
    """Health check endpoint"""
    return {
        "status": "online",
        "service": "SOW Analyzer API",
        "version": "1.0.0",
        "endpoints": {
            "analyze": "/api/analyze",
            "health": "/health"
        }
    }


@app.get("/health")
def health_check():
    """Detailed health check"""
    return {
        "status": "healthy",
        "timestamp": datetime.utcnow().isoformat(),
        "api_key_configured": bool(os.getenv("ANTHROPIC_API_KEY"))
    }


@app.post("/api/analyze")
async def analyze_sow_file(files: List[UploadFile] = File(...)):
    """
    Analyze one or more SOW files

    Accepts: PDF, DOCX, or TXT files
    Returns: Extraction data + Risk analysis + Overlap analysis (if multiple files)
    """
    # Handle both single and multiple files
    if not isinstance(files, list):
        files = [files]

    allowed_extensions = ['.pdf', '.docx', '.txt']
    temp_files = []
    results = []

    try:
        # Process each file
        for idx, file in enumerate(files):
            # Validate file type
            file_ext = os.path.splitext(file.filename)[1].lower()

            if file_ext not in allowed_extensions:
                raise HTTPException(
                    status_code=400,
                    detail=f"Unsupported file type '{file.filename}'. Allowed: {', '.join(allowed_extensions)}"
                )

            # Validate file size (10MB limit)
            content = await file.read()
            if len(content) > 10 * 1024 * 1024:
                raise HTTPException(
                    status_code=400,
                    detail=f"File '{file.filename}' too large. Maximum size: 10MB"
                )

            # Save uploaded file temporarily
            with tempfile.NamedTemporaryFile(delete=False, suffix=file_ext) as tmp:
                tmp.write(content)
                tmp_path = tmp.name
                temp_files.append(tmp_path)

            # Step 1: Extract structured data
            print(f"[{idx+1}/{len(files)}] Extracting data from {file.filename}...")
            extracted_data = extract_from_file(tmp_path)

            # Read raw text for overlap analysis
            with open(tmp_path, 'r', encoding='utf-8', errors='ignore') as f:
                raw_text = f.read() if file_ext == '.txt' else extracted_data.get('raw_text', '')

            # Step 2: Analyze for risks using RAG (with fallback to basic analysis)
            if RAG_AVAILABLE:
                print(f"   Analyzing with RAG (searching vector database)...")
                try:
                    analysis = analyze_sow_with_rag(extracted_data)
                    print(f"   [OK] RAG analysis complete")
                except Exception as e:
                    print(f"   [WARNING] RAG analysis error: {str(e)}")
                    print(f"   Falling back to basic analysis...")
                    analysis = analyze_sow(extracted_data)
                    print(f"   [OK] Basic analysis complete")
            else:
                print(f"   Analyzing with basic analyzer...")
                analysis = analyze_sow(extracted_data)
                print(f"   [OK] Basic analysis complete")

            # Calculate summary statistics
            all_findings = []
            for category in ['weak_kpis', 'scope_creep', 'missing_elements',
                             'inconsistencies', 'deliverable_issues', 'red_flags']:
                findings = analysis.get(category, [])
                all_findings.extend(findings)

            high_count = sum(1 for f in all_findings if f.get('severity') == 'HIGH')
            medium_count = sum(1 for f in all_findings if f.get('severity') == 'MEDIUM')
            low_count = sum(1 for f in all_findings if f.get('severity') == 'LOW')

            # Store result for this file
            results.append({
                "filename": file.filename,
                "contract_id": extracted_data.get("metadata", {}).get("contract_id"),
                "contractor": extracted_data.get("metadata", {}).get("contractor"),
                "summary": {
                    "total_findings": len(all_findings),
                    "high_severity": high_count,
                    "medium_severity": medium_count,
                    "low_severity": low_count,
                    "tasks_found": len(extracted_data.get("tasks", [])),
                    "kpis_found": len(extracted_data.get("kpis", [])),
                    "deliverables_found": len(ex
[truncated — 3901 more characters]
```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { GeistSans } from "geist/font/sans";
import "./globals.css";

export const metadata: Metadata = {
  title: "SOW Analyzer - AI-Powered Contract Analysis",
  description: "Analyze government Statements of Work for waste, weak KPIs, and scope creep",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${GeistSans.className} antialiased min-h-screen`}>
        {children}
      </body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';
import FileUpload from '@/components/FileUpload';
import ResultsDashboard from '@/components/ResultsDashboard';
import LoginModal from '@/components/LoginModal';

export type AnalysisResult = {
  success: boolean;
  filename: string;
  contract_id?: string;
  contractor?: string;
  summary: {
    total_findings: number;
    high_severity: number;
    medium_severity: number;
    low_severity: number;
    tasks_found: number;
    kpis_found: number;
    deliverables_found: number;
  };
  extracted_data: any;
  analysis: any;
  overlap_analysis?: {
    overlap_percentage: number;
    explanation: string;
    overlapping_areas?: string[];
    redundant_spend?: number;
    max_budget?: number;
    confidence: string;
  };
};

export default function Home() {
  const [analysisResult, setAnalysisResult] = useState<AnalysisResult | null>(null);
  const [isAnalyzing, setIsAnalyzing] = useState(false);
  const [currentTaskIndex, setCurrentTaskIndex] = useState(0);
  const [progress, setProgress] = useState(0);
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [showLoginModal, setShowLoginModal] = useState(false);

  const tasks = [
    'Extracting data',
    'Analyzing KPIs',
    'Detecting issues'
  ];

  // Check sessionStorage on mount to restore login state
  useEffect(() => {
    const loggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
    setIsLoggedIn(loggedIn);
  }, []);

  useEffect(() => {
    if (isAnalyzing) {
      // Cycle through tasks
      const taskInterval = setInterval(() => {
        setCurrentTaskIndex((prev) => (prev + 1) % tasks.length);
      }, 2000); // Change task every 2 seconds

      // Animate progress bar
      const progressInterval = setInterval(() => {
        setProgress((prev) => {
          if (prev >= 95) return prev; // Cap at 95% until complete
          return prev + 1;
        });
      }, 500); // Increase progress every 500ms

      return () => {
        clearInterval(taskInterval);
        clearInterval(progressInterval);
      };
    } else {
      // Reset when not analyzing
      setCurrentTaskIndex(0);
      setProgress(0);
    }
  }, [isAnalyzing]);

  const handleAnalysisComplete = (result: AnalysisResult) => {
    setProgress(100); // Complete the progress bar
    setTimeout(() => {
      setAnalysisResult(result);
      setIsAnalyzing(false);
    }, 300);
  };

  const handleAnalysisStart = () => {
    setIsAnalyzing(true);
    setAnalysisResult(null);
    setCurrentTaskIndex(0);
    setProgress(0);
  };

  const handleReset = () => {
    setAnalysisResult(null);
    setIsAnalyzing(false);
    setCurrentTaskIndex(0);
    setProgress(0);
  };

  const scrollToSection = (sectionId: string) => {
    const element = document.getElementById(sectionId);
    if (element) {
      element.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  };

  return (
    <main className="min-h-screen bg-govtech-bg">
      {/* Header - Sticky with Navigation */}
      <header className="sticky top-0 z-50 bg-govtech-bg/95 backdrop-blur-sm border-b border-govtech-border">
        <div className="max-w-7xl mx-auto px-8 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-12">
              <div className="flex items-center gap-3">
                <Image
                  src="/logo.svg"
                  alt="Scope.ai Logo"
                  width={32}
                  height={32}
                />
                <h1 className="text-xl font-semibold text-govtech-text-primary tracking-tight">
                  Scope.ai
                </h1>
              </div>

              {!analysisResult && !isAnalyzing && (
                <nav className="hidden md:flex items-center gap-6">
                  <button
                    onClick={() => scrollToSection('target')}
                    className="text-sm text-govtech-text-secondary hover:text-govtech-text-primary transition-colors focus:outline-none focus-visible:outline-none active:outline-none"
                  >
                    Target
                  </button>
                  <button
                    onClick={() => scrollToSection('features')}
                    className="text-sm text-govtech-text-secondary hover:text-govtech-text-primary transition-colors focus:outline-none focus-visible:outline-none active:outline-none"
                  >
                    Features
                  </button>
                  <button
                    onClick={() => scrollToSection('efficiency')}
                    className="text-sm text-govtech-text-secondary hover:text-govtech-text-primary transition-colors focus:outline-none focus-visible:outline-none active:outline-none"
                  >
                    Efficiency
                  </button>
                </nav>
              )}
            </div>

            {analysisResult && (
              <button
                onClick={handleReset}
                className="px-5 py-2.5 text-sm font-medium text-black bg-govtech-primary hover:bg-govtech-primary-hover transition-all rounded-lg h-10 focus:outline-none"
              >
                New Analysis
              </button>
            )}

            {!isLoggedIn && !analysisResult && (
              <button
                onClick={() => setShowLoginModal(true)}
                className="px-5 py-2.5 text-sm font-medium text-black bg-govtech-primary hover:bg-govtech-primary-hover transition-all rounded-lg h-10 focus:outline-none"
              >
                Sign In
              </button>
            )}
          </div>
        </div>
      </header>

      {/* Hero Section - Compact */}
      {!analysisResult && !isAnalyzing && (
        <div className="bg-govtech-card border-b border-govtech-border">
          <div className="max-w-7xl mx-auto px-8 py-12 text-center">
            <h2 className="
[truncated — 19366 more characters]
```

### vector_db_setup.py

```python
import chromadb
from sentence_transformers import SentenceTransformer
import json
import os

# Initialize embedding model
print("Loading embedding model...")
embedder = SentenceTransformer('all-MiniLM-L6-v2')
print("[OK] Embedding model loaded")

# Initialize ChromaDB client (NEW API - no Settings needed)
chroma_client = chromadb.PersistentClient(path="./chroma_db")

# Create or get collection
collection_name = "government_contracts"
try:
    collection = chroma_client.get_collection(name=collection_name)
    print(f"[OK] Using existing collection: {collection_name}")
except:
    collection = chroma_client.create_collection(name=collection_name)
    print(f"[OK] Created new collection: {collection_name}")

def initialize_vector_db():
    """Load annotated examples into vector database"""
    
    # Load annotated examples
    examples_file = "annotated_examples.json"
    
    if not os.path.exists(examples_file):
        print(f"[ERROR] Error: {examples_file} not found!")
        return False

    with open(examples_file, 'r') as f:
        data = json.load(f)

    examples = data.get('examples', [])

    if not examples:
        print("[ERROR] No examples found in JSON!")
        return False
    
    print(f"Loading {len(examples)} annotated examples...")
    
    # Clear existing data (optional - comment out if you want to keep old data)
    try:
        chroma_client.delete_collection(name=collection_name)
        collection = chroma_client.create_collection(name=collection_name)
        print("[OK] Cleared old data")
    except:
        pass
    
    # Add each example to the collection
    for idx, example in enumerate(examples):
        # Generate embedding for the problematic section
        text = example['problematic_section']
        embedding = embedder.encode(text).tolist()
        
        # Prepare metadata
        metadata = {
            'id': example['id'],
            'issue_type': example['issue_type'],
            'severity': example['severity'],
            'explanation': example['explanation'],
            'actual_outcome': example['actual_outcome'],
            'estimated_cost': example['estimated_cost'],
            'correct_version': example['correct_version'],
            'contract_source': example['contract_source']
        }
        
        # Add to collection
        collection.add(
            embeddings=[embedding],
            documents=[text],
            metadatas=[metadata],
            ids=[f"example_{idx}"]
        )
        
        if (idx + 1) % 5 == 0:
            print(f"  Loaded {idx + 1}/{len(examples)} examples...")
    
    print(f"[OK] Successfully loaded {len(examples)} examples into vector DB")
    return True

def search_similar_patterns(query_text, n_results=3):
    """Search for similar patterns in the vector database"""

    # Get or create collection
    try:
        coll = chroma_client.get_collection(name=collection_name)
    except Exception as e:
        print(f"[WARNING] Collection not found: {e}")
        print(f"   Run 'python vector_db_setup.py' first to initialize")
        return []

    # Generate embedding for query
    query_embedding = embedder.encode(query_text).tolist()

    # Search in collection
    results = coll.query(
        query_embeddings=[query_embedding],
        n_results=n_results
    )

    # Format results to match expected structure
    formatted_results = []
    if results and results['ids'] and len(results['ids'][0]) > 0:
        for i in range(len(results['ids'][0])):
            formatted_results.append({
                'id': results['ids'][0][i],
                'problematic_section': results['documents'][0][i],
                'similarity_score': 1 - results['distances'][0][i],  # Convert distance to similarity
                'issue_type': results['metadatas'][0][i]['issue_type'],
                'severity': results['metadatas'][0][i]['severity'],
                'explanation': results['metadatas'][0][i]['explanation'],
                'actual_outcome': results['metadatas'][0][i]['actual_outcome'],
                'estimated_cost': results['metadatas'][0][i]['estimated_cost'],
                'correct_version': results['metadatas'][0][i]['correct_version'],
                'contract_source': results['metadatas'][0][i]['contract_source']
            })

    return formatted_results

def get_collection_stats():
    """Get statistics about the collection"""
    try:
        count = collection.count()
        return {
            'total_examples': count,
            'collection_name': collection_name
        }
    except Exception as e:
        return {'error': str(e)}
```

### overlap_analyzer.py

```python
"""
Overlap Analysis - Detect redundant work across multiple SOWs
"""
import os
import json
import re
from typing import List, Dict, Optional
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

# Initialize Anthropic client
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

OVERLAP_PROMPT = """You are analyzing multiple government contract Statements of Work (SOWs) for overlapping/redundant work.

<SOW_1_METADATA>
Filename: {filename_1}
</SOW_1_METADATA>

<SOW_1_TEXT>
{sow_text_1}
</SOW_1_TEXT>

<SOW_2_METADATA>
Filename: {filename_2}
</SOW_2_METADATA>

<SOW_2_TEXT>
{sow_text_2}
</SOW_2_TEXT>

Analyze the tasks, deliverables, and scope of work in both SOWs.

Calculate the percentage of overlapping/redundant work between them. Consider:
- Similar tasks or deliverables described with different terminology
- Duplicate services or functions
- Common technical requirements
- Overlapping personnel roles or responsibilities

Return ONLY valid JSON (no additional text):

{{
  "overlap_percentage": 0-100,
  "explanation": "Brief 2-3 sentence summary of what work overlaps",
  "overlapping_areas": ["Area 1", "Area 2", "Area 3"],
  "confidence": "HIGH|MEDIUM|LOW"
}}

If there is minimal or no overlap (< 15%), set overlap_percentage to the actual low value and explain why.
"""


def extract_budget_from_text(text: str) -> Optional[float]:
    """
    Extract budget/contract value from SOW text using regex patterns

    Args:
        text: Full text of the SOW

    Returns:
        Budget value as float, or None if not found
    """
    # Common patterns for budget mentions in government contracts
    patterns = [
        r'contract\s+value[:\s]+\$?([\d,]+(?:\.\d{2})?)',
        r'total\s+contract\s+value[:\s]+\$?([\d,]+(?:\.\d{2})?)',
        r'not\s+to\s+exceed[:\s]+\$?([\d,]+(?:\.\d{2})?)',
        r'maximum\s+contract\s+value[:\s]+\$?([\d,]+(?:\.\d{2})?)',
        r'total\s+obligated\s+amount[:\s]+\$?([\d,]+(?:\.\d{2})?)',
        r'ceiling\s+price[:\s]+\$?([\d,]+(?:\.\d{2})?)',
    ]

    for pattern in patterns:
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            # Remove commas and convert to float
            value_str = match.group(1).replace(',', '')
            try:
                return float(value_str)
            except ValueError:
                continue

    return None


def analyze_overlap(sow_data_list: List[Dict]) -> Dict:
    """
    Analyze overlap between multiple SOWs

    Args:
        sow_data_list: List of dictionaries, each containing:
            - filename: Name of the SOW file
            - raw_text: Full text content
            - extracted_data: Structured extraction from Pass 1

    Returns:
        Dictionary with overlap analysis results
    """
    if len(sow_data_list) < 2:
        return None

    # For MVP, we'll just compare the first two SOWs
    # Future: compare all pairs and create matrix
    sow1 = sow_data_list[0]
    sow2 = sow_data_list[1]

    print(f"\n[Overlap] Analyzing overlap between {sow1['filename']} and {sow2['filename']}...")

    # Prepare prompt
    prompt = OVERLAP_PROMPT.format(
        filename_1=sow1['filename'],
        sow_text_1=sow1['raw_text'][:15000],  # Limit to avoid token limits
        filename_2=sow2['filename'],
        sow_text_2=sow2['raw_text'][:15000]
    )

    try:
        # Call Claude for overlap analysis
        message = client.messages.create(
            model="claude-3-haiku-20240307",
            max_tokens=2048,
            temperature=0,
            messages=[
                {
                    "role": "user",
                    "content": prompt
                }
            ]
        )

        response_text = message.content[0].text

        # Extract JSON
        json_start = response_text.find('{')
        json_end = response_text.rfind('}') + 1
        if json_start != -1 and json_end > json_start:
            json_text = response_text[json_start:json_end]
        else:
            json_text = response_text

        overlap_result = json.loads(json_text)

        # Extract budgets from both SOWs
        budget_1 = extract_budget_from_text(sow1['raw_text'])
        budget_2 = extract_budget_from_text(sow2['raw_text'])

        # Calculate redundant spend (use higher budget)
        max_budget = None
        if budget_1 and budget_2:
            max_budget = max(budget_1, budget_2)
        elif budget_1:
            max_budget = budget_1
        elif budget_2:
            max_budget = budget_2

        redundant_spend = None
        if max_budget:
            overlap_pct = overlap_result.get('overlap_percentage', 0) / 100
            redundant_spend = max_budget * overlap_pct

        # Add calculated fields
        overlap_result['sow_1_filename'] = sow1['filename']
        overlap_result['sow_2_filename'] = sow2['filename']
        overlap_result['budget_1'] = budget_1
        overlap_result['budget_2'] = budget_2
        overlap_result['max_budget'] = max_budget
        overlap_result['redundant_spend'] = redundant_spend

        print(f"[OK] Overlap analysis complete: {overlap_result['overlap_percentage']}% overlap")

        return overlap_result

    except Exception as e:
        print(f"[WARNING] Overlap analysis error: {e}")
        return {
            "overlap_percentage": 0,
            "explanation": f"Error during overlap analysis: {str(e)}",
            "overlapping_areas": [],
            "confidence": "LOW",
            "error": str(e)
        }


if __name__ == "__main__":
    # Test with sample data
    print("Overlap analyzer module loaded")

```

### sow_extractor.py

```python
"""
Pass 1: SOW Extraction - Convert unstructured SOW to structured JSON
"""
import os
import json
from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv()

# Initialize Anthropic client
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

EXTRACTION_PROMPT = """You are analyzing a government contract Statement of Work (SOW).

Government SOWs typically follow one of these structures:
- NYSERDA format: Background, Definitions, Task 0 (Project Management), Task 1-N, Task X (Final Report)
- NOAA format: 9 sections including General, Tasks, Personnel, Deliverables table
- Custom variations of the above

Extract the following into structured JSON:

CONTRACT METADATA:
- Contract/Project number (if present)
- Contractor name (if present)
- Project title
- Contract value/budget (if mentioned)
- Period of performance

BACKGROUND/OBJECTIVES:
- What problem is being solved?
- What are the high-level goals?

TASKS/REQUIREMENTS:
For each task, extract:
- Task number/ID (e.g., "Task 0", "Task 1", "2.1")
- Task title
- Task description (what contractor shall do)
- Deliverables for this task
- Schedule/due date
- Page/section reference

KPIS/PERFORMANCE METRICS:
Extract any measurable outcomes, success criteria, or performance targets mentioned:
- The metric text exactly as written
- What it measures
- Target/baseline (if specified)
- Timeframe (if specified)
- Measurement method (if specified)
- Location in document

DELIVERABLES:
- Deliverable name/description
- Associated task
- Due date
- Format (report, presentation, data, etc.)
- Distribution/recipients

SCOPE BOUNDARIES:
- What's explicitly in scope?
- What's explicitly out of scope? (if mentioned)

CONTRACTOR PERSONNEL REQUIREMENTS:
- Required qualifications, certifications, clearances
- Key personnel positions
- Project manager requirements
- Staffing levels (if specified)

OTHER REQUIREMENTS:
- Security/clearance requirements
- Travel requirements
- Reporting frequency (progress reports, metrics, meetings)
- Government furnished resources
- Section 508 / accessibility requirements
- Acceptance criteria

For each extracted item, include:
- Exact quote from document (if applicable)
- Page number or section reference (if discernible)
- Confidence level (high/medium/low)

If a section is completely absent, note it as "NOT_FOUND".

Return ONLY valid JSON with no additional text. Use this structure:

{
  "metadata": {
    "contract_id": "string or null",
    "contractor": "string or null",
    "project_title": "string or null",
    "value": "string or null",
    "duration": "string or null"
  },
  "background": {
    "problem_statement": "string or NOT_FOUND",
    "reference": "string or null",
    "confidence": "high/medium/low"
  },
  "objectives": [
    {
      "text": "string",
      "reference": "string",
      "confidence": "high/medium/low"
    }
  ],
  "tasks": [
    {
      "task_id": "string",
      "title": "string or null",
      "description": "string",
      "deliverables": ["string"],
      "schedule": "string or null",
      "reference": "string",
      "confidence": "high/medium/low"
    }
  ],
  "kpis": [
    {
      "text": "string",
      "measures": "string or null",
      "target": "string or null",
      "baseline": "string or null",
      "timeframe": "string or null",
      "measurement_method": "string or null",
      "reference": "string",
      "confidence": "high/medium/low"
    }
  ],
  "deliverables": [
    {
      "name": "string",
      "associated_task": "string or null",
      "due_date": "string or null",
      "format": "string or null",
      "distribution": "string or null",
      "reference": "string",
      "confidence": "high/medium/low"
    }
  ],
  "scope": {
    "in_scope": ["string"],
    "out_of_scope": ["string"] or "NOT_FOUND"
  },
  "personnel_requirements": {
    "qualifications": ["string"] or "NOT_FOUND",
    "key_personnel": ["string"] or "NOT_FOUND",
    "project_manager_required": true or false,
    "clearance_level": "string or null"
  },
  "other_requirements": {
    "security": "string or NOT_FOUND",
    "travel": "string or NOT_FOUND",
    "progress_reporting": "string or NOT_FOUND",
    "government_furnished": ["string"] or "NOT_FOUND",
    "section_508": "string or NOT_FOUND",
    "acceptance_criteria": "string or NOT_FOUND"
  }
}

<document>
{document_text}
</document>"""


def extract_sow_data(document_text: str, model: str = "claude-3-haiku-20240307") -> dict:
    """
    Extract structured data from SOW document text using Claude

    Args:
        document_text: Full text of the SOW document
        model: Claude model to use

    Returns:
        Dictionary with extracted structured data
    """
    prompt = EXTRACTION_PROMPT.replace("{document_text}", document_text)

    print(f"Calling Claude API for extraction...")
    print(f"Document length: {len(document_text)} characters")

    message = client.messages.create(
        model=model,
        max_tokens=4096,
        temperature=0,
        messages=[
            {
                "role": "user",
                "content": prompt
            }
        ]
    )

    response_text = message.content[0].text

    # Parse JSON response - handle cases where Claude adds explanatory text before/after JSON
    try:
        # Try direct parsing first
        extracted_data = json.loads(response_text)
        print(f"[OK] Extraction successful")
        return extracted_data
    except json.JSONDecodeError as e:
        # If direct parsing fails, try to extract JSON from the response
        print(f"[ERROR] Direct JSON parsing failed, attempting to extract JSON from response...")

        # Look for JSON object in the response by finding matching braces
        json_start = response_text.find('{')

        if json_start == -1:
            print(f"[ERROR] Could not find JSON in response")
            print(f"Response: {response_text[:500]}...")
            raise

        # Count braces to find the matching closing 
[truncated — 3492 more characters]
```

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