# IVU Testing & Performance Monitoring Script

## Context

Barclays IVU (Independent Validation Unit) Model compliance requires a structured methodology for testing and reporting on AI model performance. Per Section 7.1 of the requirements, the process involves ingesting 20 predefined assets, running them 5 times each, and comparing reports across Quality, Consistency, Accuracy, Completeness, and Response Time. This runs every 6 months to detect model drift and benchmark past responses.

This is a standalone Python CLI tool that connects to the running ModComms backend via WebSocket, runs batch analyses, scores results, and generates comparative reports. It does NOT live inside the site itself.

## Directory Structure

```
testing/
├── ivu_runner.py              # Main CLI entry point
├── config.py                  # CLI args + env var configuration
├── models.py                  # Pydantic data models for IVU results
├── utils.py                   # CSV parsing, file I/O, MIME detection
├── ws_client.py               # Async WebSocket client
├── scoring/
│   ├── __init__.py
│   ├── ai_scorer.py           # Claude-based Quality/Accuracy/Completeness scorer (Anthropic API)
│   └── consistency_scorer.py  # Programmatic consistency comparison across runs
├── reporting/
│   ├── __init__.py
│   ├── pdf_report.py          # Per-run PDF report (reportlab)
│   ├── comparative_report.py  # Final HTML + PDF comparative summary
│   └── styles.py              # Shared brand colors and styling constants
├── assets/                    # Where the 20 test proof files go
│   └── .gitkeep
├── output/                    # Default output directory (gitignored)
│   └── .gitkeep
├── requirements.txt           # Standalone deps
└── sample_assets.csv          # Example CSV template
```

## Implementation Steps

### Step 1: Data Models (`testing/models.py`)
Define Pydantic models mirroring backend schemas from `backend/app/models/schemas.py`:
- `SubReviewResult` - mirrors `SubReview` (ragStatus, feedback, issues)
- `AgentReviewResult` - mirrors `AgentReview` (4 agent reviews + lead summary + overallStatus)
- `SingleRunResult` - one iteration result (agent_review, elapsed_seconds, iteration, error)
- `AssetTestResult` - all runs for one asset + computed scores
- `IVUTestSuite` - top-level model for the entire test run

### Step 2: Configuration (`testing/config.py`)
argparse-based CLI with settings:
- `--csv` (required) - path to CSV file
- `--assets-dir` (default: `./assets`) - directory containing proof files
- `--backend-url` (default: `ws://localhost:8000/ws/analyze`) - WebSocket endpoint
- `--iterations` (default: `5`) - runs per asset
- `--output-dir` (default: `./output/{timestamp}`)
- `--anthropic-api-key` (from env `ANTHROPIC_API_KEY`) - for Claude-based scoring
- `--access-token` (default: `ivu-test-token` for DISABLE_AUTH=true)
- `--delay-between-runs` (default: `5` seconds)
- `--baseline` - path to previous `results.json` for drift comparison
- `--skip-scoring` / `--skip-pdf` flags

### Step 3: CSV Parsing & Utilities (`testing/utils.py`)
CSV columns: `campaign_name`, `brand_guidelines`, `client_lead`, `agency`, `agency_lead`, `proof_name`, `channel`, `sub_channel`, `proof_type`, `proof_file_name`

Validates all required columns exist and all referenced files exist in assets directory before starting.

### Step 4: WebSocket Client (`testing/ws_client.py`)
Async client using `websockets` library that:
- Reads file, base64-encodes it, detects MIME type
- Sends `{"type": "analyze", ...}` message (intentionally omits `campaign_id` and `proof_name` to avoid persisting test runs to the production database - see `handlers.py:211`)
- Listens for `agent_started`, `agent_completed`, `model_fallback`, `complete`, `error` messages
- Returns structured result with timing
- 30s connection timeout, 300s analysis timeout, 3 retries with exponential backoff on connection failures

### Step 5: AI-Based Scoring (`testing/scoring/ai_scorer.py`)
Uses **Claude (Anthropic API)** as an independent judge to evaluate each run's output. This is a stronger IVU approach than using Gemini to score itself - a completely separate AI provider evaluates the analysis quality.
- Sends the proof image + agent review text to Claude with a detailed rubric
- Uses `anthropic` Python SDK with Claude Sonnet for cost-effective scoring
- Returns structured JSON scores (1-10) for:
  - **Quality**: Clarity, coherence, professional tone, no hallucinations, grammatical correctness
  - **Accuracy**: Information matches visible proof content, correct facts/numbers, appropriate RAG status
  - **Completeness**: All agent perspectives covered, legal/brand/channel aspects addressed, no omissions
- Requires `ANTHROPIC_API_KEY` env var (separate from the Gemini key used by the backend)

### Step 6: Consistency Scoring (`testing/scoring/consistency_scorer.py`)
Programmatic comparison across iterations (no AI needed):
- **RAG Status Consistency (25%)**: Unanimous RAG status across runs per agent
- **Overall Status Consistency (25%)**: Same overallStatus across all runs
- **Issue Overlap (25%)**: Fuzzy Jaccard similarity of issue lists using `difflib.SequenceMatcher`
- **Feedback Similarity (25%)**: Normalized text comparison of feedback across runs

### Step 7: Per-Run PDF Reports (`testing/reporting/pdf_report.py`)
Uses `reportlab` to generate PDFs matching the frontend's `PDFReport.tsx` layout:
- Cover page with Oliver brand, campaign info, iteration number
- Overall summary with RAG status and lead agent summary
- 4 agent review sections with RAG badges, feedback text, and key actions lists
- Brand colors: Navy `#001f5a`, Blue `#00a3e0`, RAG colors from PDFReport.tsx

### Step 8: Comparative Report (`testing/reporting/comparative_report.py`)
Generates both HTML and PDF:
- **Executive Summary**: Run metadata, aggregate pass/fail rates, average scores
- **Score Dashboard Table**: One row per asset with avg Quality, Accuracy, Completeness, Consistency, Response Time
- **Per-Asset Detail**: Score breakdown by iteration, RAG distribution, response time stats
- **Model Drift Analysis**: If baseline provided, delta per asset per metric, flagged regressions (>1 point drop)

### Step 9: Main Runner (`testing/ivu_runner.py`)
Orchestrates the full flow:
1. Parse CSV → validate files exist
2. For each asset (20):
   - For each iteration (5):
     - Run WebSocket analysis → collect result + timing
     - Generate per-run PDF
     - Score with AI scorer (Quality, Accuracy, Completeness)
     - Delay between runs
   - Compute consistency score across all iterations
3. Save raw `results.json` (for future baseline comparisons)
4. Generate comparative report (HTML + PDF)
5. Handle Ctrl+C gracefully (save partial results)

## Output Structure
```
output/run_2026-03-03_14-30-00/
├── results.json                    # Machine-readable results (future baseline)
├── comparative_report.html         # Browser-viewable report
├── comparative_report.pdf          # PDF comparative report
├── per_run_pdfs/
│   ├── Asset01_ProofName_run1.pdf
│   ├── Asset01_ProofName_run2.pdf
│   └── ...                         # 20 assets × 5 runs = 100 PDFs
└── logs/
    └── ivu_runner.log              # Execution log
```

## Dependencies (`testing/requirements.txt`)
```
websockets>=12.0
anthropic>=0.40.0
pydantic>=2.5.0
reportlab>=4.0
```

## Key Files Referenced
- `backend/app/websocket/handlers.py` - WebSocket protocol (lines 42-58 for file handling, 211 for persistence gate)
- `backend/app/models/schemas.py` - Canonical SubReview/AgentReview schemas to mirror
- `frontend/components/PDFReport.tsx` - PDF layout, brand colors, and structure to replicate
- `frontend/services/geminiService.ts` - WebSocket client reference implementation
- `backend/app/services/gemini_service.py` - Gemini API client pattern (backend reference only)

## Verification
1. Place test assets in `testing/assets/`, create a CSV with 2-3 assets
2. Start backend with `DISABLE_AUTH=true`
3. Run: `cd testing && python ivu_runner.py --csv sample_assets.csv --iterations 2`
4. Verify per-run PDFs are generated in output directory
5. Verify `results.json` contains all scores and timing data
6. Verify comparative report HTML opens in browser with correct data
7. Run again with `--baseline output/{previous}/results.json` to verify drift detection
