# Project export: Harmoniq

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: Regulatory intelligence for clinical trials.
- Devpost: https://devpost.com/software/harmoniq-06o1e9
- GitHub: https://github.com/vardhanshorewala/harmoniq
- Video: https://www.youtube.com/embed/mIZSg8rKyew?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Rox: Best Use of Rox; Regeneron: Runner-Up)
- Team: 2 GitHub contributor(s) — Vardhan Shorewala (31 commits), Jathin Pranav Singaraju (9 commits)

## Devpost submission (written by the team)

### Inspiration

The $960M Problem: When the FDA updates a regulation (like 21 CFR Part 11), pharmaceutical companies face a critical question: Which of our 50+ active clinical trials are now non-compliant? The current process: 130-160 days of manual review by regulatory affairs teams $6M revenue loss per day of trial delay Total cost: $780M-$960M per major regulatory change Error rate: ~15-20% of violations missed in manual review We spoke with regulatory affairs teams at top pharma companies and clinical research students at Harvard Medical School, MIT, and Stanford — all confirmed the same bottleneck: compliance checking is still done with PDFs, spreadsheets, and manual cross-referencing. Harmoniq automates this entire workflow in under 10 seconds.

### What it does

Harmoniq is an AI agent system that continuously monitors clinical trial protocols for regulatory compliance: Core Capabilities Regulation Ingestion Upload messy FDA/EMA PDFs (21 CFR Part 11, ICH-GCP E6(R2)) LLM agent extracts structured requirements from unstructured text Builds knowledge graph connecting related clauses Regulation Ingestion Upload messy FDA/EMA PDFs (21 CFR Part 11, ICH-GCP E6(R2)) LLM agent extracts structured requirements from unstructured text Builds knowledge graph connecting related clauses Protocol Analysis Upload protocol document (PDF/Markdown) System splits into paragraphs and checks each against regulations Uses HippoRAG (NeurIPS 2024) for graph-enhanced retrieval Protocol Analysis Upload protocol document (PDF/Markdown) System splits into paragraphs and checks each against regulations Uses HippoRAG (NeurIPS 2024) for graph-enhanced retrieval Compliance Reports Paragraph-level violation detection Severity scoring (critical/high/medium/low) Missing elements identified Confidence scores for each finding Compliance Reports Paragraph-level violation detection Severity scoring (critical/high/medium/low) Missing elements identified Confidence scores for each finding Example Workflow

### How we built it

System Architecture Tech Stack Backend (Python FastAPI) FastAPI — Async API framework Pydantic — Type-safe data validation ChromaDB — Vector database (persistent storage) NetworkX — Graph operations + Personalized PageRank sentence-transformers — Local embedding model (all-MiniLM-L6-v2) pypdf — PDF text extraction httpx — Async HTTP client for LLM APIs LLM Infrastructure Model: Anthropic Claude 3.5 Sonnet (claude-3-5-sonnet-20241022) Provider: Direct Anthropic API / Lava Payments proxy Prompts: Custom system prompts for extraction, relationship detection, and compliance reasoning Frontend (Next.js + TypeScript) Next.js 14 — React framework with App Router TypeScript — Type safety Tailwind CSS — Styling shadcn/ui — Component library React Flow — Interactive knowledge graph visualization Deployment Backend: Python 3.11, Poetry dependency management Frontend: Vercel Database: Local ChromaDB (portable to cloud) Knowledge Graph Structure We construct a multi-edge knowledge graph where nodes are regulatory requirements and edges represent relationships: 3 Edge Types: NEARBY (weight = 1.0) Sequential connections (e.g., REQ-001 → REQ-002) Captures document flow and context NEARBY (weight = 1.0) Sequential connections (e.g., REQ-001 → REQ-002) Captures document flow and context SIMILAR_TO (weight = cosine similarity) Vector embedding similarity > 0.75 Finds semantically related clauses SIMILAR_TO (weight = cosine similarity) Vector embedding similarity > 0.75 Finds semantically related clauses RELATED_TO (weight = LLM confidence) LLM-detected logical relationships Example: "validation required" → "audit trails required" RELATED_TO (weight = LLM confidence) LLM-detected logical relationships Example: "validation required" → "audit trails required" Graph Statistics (21 CFR Part 11): Nodes: 703 requirement clauses Edges: 876 total (30% LLM, 50% similarity, 20% sequential) Average degree: 2.5 connections per node HippoRAG Retrieval Algorithm Traditional RAG uses only vector similarity. HippoRAG adds graph traversal: Standard RAG: [ \text{Results} = \text{TopK}(\text{cosine}(\vec{q}, \vec{d}_i)) ] HippoRAG: [ \text{Seeds} = \text{TopK}(\text{cosine}(\vec{q}, \vec{d}i)) \quad (k=5) ] [ \text{PPR}\text{seeds}(v) = \text{PageRank}(G, \text{personalization}=\text{Seeds}) ] [ \text{Results} = \text{TopK}(\text{PPR scores}) ] Why this works: Vector search finds direct matches ("validation") Graph propagation finds related concepts ("audit trails", "documentation") Result: 40% more relevant clauses retrieved vs. pure vector search

### Challenges we ran into

1. Messy PDF Extraction FDA PDFs have inconsistent formatting (tables, multi-column, footnotes) Solution: pypdf + custom paragraph detection heuristics 2. LLM Hallucination in Requirement Extraction Early versions "invented" requirements not in source documents Solution: Strict JSON schema validation + confidence thresholds 3. False Positive Explosion Initial compliance agent flagged 80%+ violations (too strict) Solution: Added lenient prompt engineering with explicit "assume compliant unless obviously wrong" instructions 4. Graph Construction Speed Computing all pairwise similarities for 700 nodes = 245K comparisons Solution: Sparse similarity matrix (only store > 0.75 threshold) 5. Real-time Performance Full compliance check on 50-page protocol took 2+ minutes Solution: Parallel LLM calls (15 paragraphs checked simultaneously)

### Accomplishments we're proud of

✅ Extracted 703 structured requirements from a 45-page messy FDA PDF using pure LLM agents ✅ Built a working knowledge graph with 876 edges (validated against expert annotations) ✅ Implemented HippoRAG (NeurIPS 2024) — one of the first production deployments ✅ Achieved <1 second query latency for graph-enhanced retrieval ✅ Created an interactive graph visualization showing regulation relationships in real-time ✅ Validated with domain experts — students from Harvard Medical School, MIT, and Stanford confirmed clinical relevance

### What we learned

Technical Insights LLMs are exceptional at structure extraction — Claude 3.5 can reliably parse messy regulatory text into JSON with 95%+ accuracy LLMs are exceptional at structure extraction — Claude 3.5 can reliably parse messy regulatory text into JSON with 95%+ accuracy Graphs >> Pure Vector Search — HippoRAG retrieved 40% more relevant clauses by following semantic relationships Graphs >> Pure Vector Search — HippoRAG retrieved 40% more relevant clauses by following semantic relationships Prompt engineering is everything — Our compliance agent went from 80% false positives to <10% by adding "lenient" instructions Prompt engineering is everything — Our compliance agent went from 80% false positives to <10% by adding "lenient" instructions Domain embeddings aren't always necessary — all-MiniLM-L6-v2 (general-purpose) performed surprisingly well on regulatory text Domain embeddings aren't always necessary — all-MiniLM-L6-v2 (general-purpose) performed surprisingly well on regulatory text Domain Insights Regulatory compliance is a graph problem — Regulations reference each other constantly ("see Part 11", "as defined in §312") Regulatory compliance is a graph problem — Regulations reference each other constantly ("see Part 11", "as defined in §312") Pharma teams trust explainability — Every violation needs a citation back to source regulation (we provide clause IDs) Pharma teams trust explainability — Every violation needs a citation back to source regulation (we provide clause IDs) The real bottleneck is cross-referencing — Manual review isn't slow because of reading, it's slow because of looking up related clauses The real bottleneck is cross-referencing — Manual review isn't slow because of reading, it's slow because of looking up related clauses

### What's next

Immediate Roadmap (3 months) [ ] Multi-jurisdiction support — Add EMA, PMDA, TGA regulations [ ] Real-time regulation monitoring — Auto-detect FDA guideline updates via web scraping [ ] Confidence calibration — Fine-tune violation probability scores against labeled dataset [ ] Batch processing — Upload 50 protocols at once for portfolio-wide compliance Long-term Vision (12 months) [ ] Automated amendment generation — LLM agent proposes protocol changes to fix violations [ ] Regulatory change impact analysis — "FDA just updated 21 CFR 11 → 12 of your trials are affected" [ ] CRO/Sponsor integrations — API + SDK for Veeva Vault, Medidata Rave, Oracle Siebel CTMS [ ] Human-in-the-loop audit — Export compliance reports to regulatory affairs teams for review Expansion Opportunities Medical devices — FDA 21 CFR Part 820 (QSR), ISO 13485 Drug manufacturing — FDA 21 CFR Part 211 (cGMP) Preclinical research — GLP compliance (FDA 21 CFR Part 58) Impact Potential For Pharmaceutical Companies Current State: 130-160 days manual compliance review $780M-$960M cost per regulatory change 15-20% error rate With Harmoniq: <1 day automated compliance audit $950M saved per regulatory change <5% error rate (validated against expert review) For the Industry Faster drug approvals → Patients get treatments sooner Reduced regulatory risk → Fewer trial halts due to compliance issues Knowledge democratization → Smaller biotech companies can compete with big pharma Try it out GitHub: Github link Demo: Website link Video: YouTube link Test with sample data: Built With Python · FastAPI · Next.js · TypeScript · Tailwind CSS · Anthropic Claude · ChromaDB · NetworkX · LangChain · HippoRAG · React Flow · Vercel Team We're a team of engineers and researchers passionate about applying AI to high-stakes, real-world problems. Special thanks to the regulatory affairs professionals and clinical research students who validated our approach. Harmoniq — Bringing harmony to clinical trial compliance, one regulation at a time.

## README (from the GitHub repository)

# Harmoniq

**Intelligent Clinical Trial Compliance Platform**

Harmoniq automatically maps regulatory requirements (FDA, EMA, PMDA) to clinical trial protocols using hybrid HippoRAG/GraphRAG retrieval with knowledge graphs. When regulations change, instantly identify which protocols are impacted—eliminating weeks of manual document review.

---

## Problem Statement

**Current Reality for Clinical Research Organizations:**

- **160+ days lost** when FDA/EMA regulations change
- **Manual review** of hundreds of pages across thousands of protocol versions
- **$6M+ per day** in lost drug development time
- Traditional vector search fails to capture regulatory relationships and dependencies

**Example Scenario:**
```
FDA updates 21 CFR Part 50 (Informed Consent)
↓
Current Solution: Weeks of manual auditing
Harmoniq Solution: Answers in <15 seconds
```

---

## Solution Architecture

### Hybrid HippoRAG/GraphRAG Retrieval

Harmoniq implements a **hybrid retrieval system** combining:

1. **Vector Search (RAG)**: Semantic similarity via ChromaDB embeddings
2. **Graph Propagation (GraphRAG)**: Relationship traversal via knowledge graphs
3. **Diffusion-Based PageRank**: Multi-hop relevance propagation through regulatory dependencies

**Why Hybrid > Traditional RAG:**

| Approach | Finds |
|----------|-------|
| **Traditional RAG** | Documents with similar keywords |
| **GraphRAG** | Connected regulations (single-hop) |
| **Hybrid HippoRAG** | Similar keywords + multi-hop dependencies + indirect relationships |

**Technical Implementation:**

```python
# Phase 1: Dense Retrieval (Vector Search)
query_embedding = embed(protocol_text)
seed_nodes = chromadb.query(query_embedding, top_k=10)

# Phase 2: Graph Diffusion (Multi-Hop PageRank)
# Propagate relevance through 3 edge types:
# - RELATED_TO: LLM-extracted semantic relationships (weight: 1.0)
# - SIMILAR_TO: Embedding similarity >0.75 (weight: 0.1)
# - NEARBY: Sequential document structure (weight: 0.3)

ppr_scores = nx.pagerank(
    graph,
    personalization={seed: 1/len(seeds) for seed in seed_nodes},
    alpha=0.85,  # Damping factor for multi-hop propagation
    max_iter=100
)

# Phase 3: Fusion & Re-ranking
ranked_results = sorted(ppr_scores.items(), key=lambda x: x[1], reverse=True)[:10]
```

**Diffusion Process:**
- **Single-hop**: Traditional graph search (immediate neighbors)
- **Multi-hop**: PageRank diffuses relevance across multiple edges
- **Damping factor (α=0.85)**: Balances local vs. global graph structure
- **Personalization vector**: Seeds from vector search guide diffusion

This approach finds:
- Direct matches (vector similarity)
- Related requirements (LLM-identified relationships)
- Dependency chains (multi-hop graph walks)
- Contextual regulations (nearby in document structure)

---

## Agent System Architecture

Harmoniq uses a **multi-agent orchestration system** where specialized LLM agents handle different stages of the compliance pipeline.

### Agent Workflow

```mermaid
graph TB
    A[Upload Regulation PDF] --> B[Agent 1: Parse & Extract]
    B --> C[Agent 2: Find Relationships]
    C --> D[Storage: Vectors + Graph]
    
    E[Upload Protocol PDF] --> F[Agent 3: Compliance Check]
    D --> F
    F --> G[Agent 4: Fix Generator]
    
    G --> H[Compliance Report + Fixes]
    
    style B fill:#e1f5ff
    style C fill:#e1f5ff
    style F fill:#ffe1e1
    style G fill:#ffe1e1
```

### Agent Details

#### **Agent 1: Regulation Parser**
**Purpose:** Extract structured requirements from messy PDFs

**Input:** Raw regulation PDF (21 CFR Part 11, ICH-GCP, etc.)

**Process:**
1. Extract text chunks (handle tables, multi-column layouts, footnotes)
2. Send to LLM with prompt: *"Extract atomic regulatory requirements"*
3. LLM returns structured JSON:
```json
{
  "requirements": [
    {
      "id": "FDA-CHUNK0-REQ-001",
      "text": "Systems must be validated to ensure accuracy",
      "section": "validation",
      "severity": "critical"
    }
  ]
}
```

**Output:** 25-50 structured requirements per regulation document

---

#### **Agent 2: Relationship Extractor**
**Purpose:** Build knowledge graph by finding semantic relationships

**Input:** List of extracted requirements from Agent 1

**Process:**
1. Send all requirements to LLM with prompt: *"Which requirements are logically related?"*
2. LLM identifies relationships:
   - "Validation required" → RELATED_TO → "Audit trails required"
   - "IRB approval" → RELATED_TO → "Informed consent"
3. Returns triplets with confidence scores

**Output:** 
- 100-200 RELATED_TO edges (LLM-detected)
- Knowledge graph structure (NetworkX)
- Vector embeddings stored in ChromaDB

**Graph Construction:**
```python
# Agent 2 execution
for req in requirements:
    # 1. Generate embedding
    embedding = sentence_transformer.encode(req.text)
    chromadb.add(req.id, embedding)
    graph.add_node(req.id, text=req.text)

# 2. LLM extracts relationships
triplets = llm_agent.extract_relationships(requirements)
for triplet in triplets:
    graph.add_edge(triplet.subject, triplet.object, 
                   relation="RELATED_TO", weight=1.0)

# 3. Add similarity edges
similarity_matrix = cosine_similarity(embeddings)
for i, j where similarity > 0.75:
    graph.add_edge(req[i], req[j], 
                   relation="SIMILAR_TO", weight=0.1)

# 4. Add sequential edges
for i in range(len(requirements) - 1):
    graph.add_edge(req[i], req[i+1], 
                   relation="NEARBY", weight=0.3)
```

---

#### **Agent 3: Compliance Checker**
**Purpose:** Analyze protocol paragraphs against regulations

**Input:** 
- Protocol paragraph (1-3 pages)
- Top-10 retrieved regulations (from HippoRAG)

**Process:**
1. HippoRAG retrieves relevant regulations:
   ```python
   # Vector search (seeds)
   seeds = chromadb.query(paragraph_embedding, top_k=5)
   
   # Graph propagation
   ppr_scores = nx.pagerank(graph, personalization=seeds)
   top_10_regulations = sorted(ppr_scores)[:10]
   ```

2. LLM analyzes compliance:
   - Prompt: *"Does this paragraph comply with these regulations?"*
   - Returns JSON for EACH regulation:
   ```json
   {
     "regulation_id": "FDA-CHUNK0-REQ-001",
     "is_compliant": false,
     "probability": 0.92,
     "severity": "critical",
     "explanation": "Protocol does not mention validation",
     "missing_elements": ["validation plan", "validation report"]
   }
   ```

3. Filters low-confidence violations (< 0.85 threshold)

**Output:** 0-10 violations per paragraph

---

#### **Agent 4: Fix Generator**
**Purpose:** Propose targeted amendments to fix violations

**Input:** 
- Violations from Agent 3
- Original protocol text

**Process:**
1. For each violation, LLM generates 1-2 minimal fixes:
   - **Replace:** Original text → New compliant text
   - **Add:** Insert new compliance language
   - **Delete:** Remove conflicting statement

2. Returns structured diffs:
```json
{
  "violation_id": "FDA-CHUNK0-REQ-001",
  "fix_type": "add",
  "location": "Section 3.2 - Data Management",
  "original": "",
  "proposed": "All electronic systems will be validated per 21 CFR Part 11 requirements. Validation documentation will be maintained for the duration of the trial."
}
```

**Output:** Actionable amendments sorted by severity

---

### Complete Compliance Flow

```mermaid
sequenceDiagram
    participant User
    participant API
    participant Agent1
    participant Agent2
    participant Storage
    participant Agent3
    participant Agent4
    
    Note over User,Storage: PHASE 1: REGULATION INGESTION
    User->>API: Upload FDA Regulation PDF
    API->>Agent1: Extract Requirements
    Agent1->>Agent1: Parse PDF → 25 requirements
    Agent1-->>API: Structured Requirements JSON
    API->>Agent2: Find Relationships
    Agent2->>Agent2: LLM analyzes semantic links
    Agent2-->>API: 87 relationship triplets
    API->>Storage: Store vectors + build graph
    Storage-->>User: ✓ Regulation loaded (703 nodes, 876 edges)
    
    Note over User,Agent4: PHASE 2: 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 38 recognized source files, 283 KB.
- 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
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (51 of 51)

```
.cursor/commands/env.md
.gitignore
backend-fastapi/.dockerignore
backend-fastapi/.env.example
backend-fastapi/.gitignore
backend-fastapi/app/__init__.py
backend-fastapi/app/agents/__init__.py
backend-fastapi/app/agents/compliance_agent.py
backend-fastapi/app/agents/lava_agent.py
backend-fastapi/app/agents/prompts/general_assistant.txt
backend-fastapi/app/agents/prompts/parse_regulation.txt
backend-fastapi/app/agents/violation_fix_agent.py
backend-fastapi/app/api/__init__.py
backend-fastapi/app/api/routes/__init__.py
backend-fastapi/app/api/routes/health.py
backend-fastapi/app/api/routes/regulations.py
backend-fastapi/app/chroma/__init__.py
backend-fastapi/app/chroma/client.py
backend-fastapi/app/core/__init__.py
backend-fastapi/app/core/config.py
backend-fastapi/app/graph/__init__.py
backend-fastapi/app/graph/graph_builder.py
backend-fastapi/app/main.py
backend-fastapi/app/models/__init__.py
backend-fastapi/app/models/regulation.py
backend-fastapi/app/schemas/__init__.py
backend-fastapi/app/services/__init__.py
backend-fastapi/app/services/regulation_service.py
backend-fastapi/docker-compose.yml
backend-fastapi/Dockerfile
backend-fastapi/poetry.lock
backend-fastapi/pyproject.toml
backend-fastapi/README.md
DEVPOST.md
harmoniq-frontend/.env.example
harmoniq-frontend/.gitignore
harmoniq-frontend/eslint.config.js
harmoniq-frontend/next.config.js
harmoniq-frontend/package.json
harmoniq-frontend/postcss.config.js
harmoniq-frontend/prettier.config.js
harmoniq-frontend/README.md
harmoniq-frontend/src/app/api/usage/route.ts
harmoniq-frontend/src/app/dashboard/page.tsx
harmoniq-frontend/src/app/layout.tsx
harmoniq-frontend/src/app/page.tsx
harmoniq-frontend/src/app/usage/page.tsx
harmoniq-frontend/src/env.js
harmoniq-frontend/src/styles/globals.css
harmoniq-frontend/tsconfig.json
README.md
```

### Dependencies

- backend-fastapi/pyproject.toml: chromadb@>=0.4.0, fastapi@>=0.104.0, httpx@>=0.25.0, networkx@>=3.2, numpy@>=1.24.0, pydantic@>=2.4.0, pydantic-settings@>=2.0.0, pymupdf@>=1.23.0, pypdf@>=3.17.0, python-dotenv@>=1.0.0, python-multipart@^0.0.20, scikit-learn@>=1.3.0, sentence-transformers@>=2.2.0, uvicorn@>=0.24.0
- harmoniq-frontend/package.json: @eslint/eslintrc@^3.3.1, @t3-oss/env-nextjs@^0.12.0, @tailwindcss/postcss@^4.0.15, @tailwindcss/typography@^0.5.19, @types/node@^20.14.10, @types/react@^19.0.0, @types/react-dom@^19.0.0, @types/three@^0.180.0, d3-force@^3.0.0, d3-force-3d@^3.0.6, eslint@^9.23.0, eslint-config-next@^15.2.3, next@^15.2.3, postcss@^8.5.3, prettier@^3.5.3, prettier-plugin-tailwindcss@^0.6.11, react@^19.0.0, react-dom@^19.0.0, react-force-graph@^1.48.1, react-force-graph-2d@^1.29.0, react-force-graph-3d@^1.29.0, react-markdown@^10.1.0, remark-gfm@^4.0.1, tailwindcss@^4.0.15, three@^0.180.0, typescript@^5.8.2, typescript-eslint@^8.27.0, zod@^3.24.2

### Recent commits (newest first)

- Updated readmes and zipped database
- Updated readmes and zipped database
- fix: add --no-root to poetry install
- fix: upgrade Dockerfile to Python 3.12 to match pyproject.toml
- fix: remove package-mode for Poetry 1.7.1 compatibility
- Update Dockerfile
- added raw data
- added unstructured functionality
- Updated readmes and zipped database
- Updated readmes and zipped database
- hotwired new endpoints to frontend
- created new ui changes
- Merge pull request #3 from vardhanshorewala/feature/readme
- lava endpoint integ fix
- changed ui
- openrouter to lava
- bug fixes for the view
- changed diff logic for view
- fixed up routing for storage
- fixed up backend and hotwired to frontend

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

### DEVPOST.md

```markdown
# Harmoniq

## Inspiration

**The $960M Problem:**

When the FDA updates a regulation (like 21 CFR Part 11), pharmaceutical companies face a critical question: *Which of our 50+ active clinical trials are now non-compliant?*

The current process:
- **130-160 days** of manual review by regulatory affairs teams
- **$6M revenue loss per day** of trial delay
- **Total cost:** $780M-$960M per major regulatory change
- **Error rate:** ~15-20% of violations missed in manual review

We spoke with **regulatory affairs teams at top pharma companies** and **clinical research students at Harvard Medical School, MIT, and Stanford** — all confirmed the same bottleneck: compliance checking is still done with **PDFs, spreadsheets, and manual cross-referencing**.

**Harmoniq automates this entire workflow in under 10 seconds.**

---

## What it does

Harmoniq is an AI agent system that continuously monitors clinical trial protocols for regulatory compliance:

### **Core Capabilities**

1. **Regulation Ingestion**
   - Upload messy FDA/EMA PDFs (21 CFR Part 11, ICH-GCP E6(R2))
   - LLM agent extracts structured requirements from unstructured text
   - Builds knowledge graph connecting related clauses

2. **Protocol Analysis**
   - Upload protocol document (PDF/Markdown)
   - System splits into paragraphs and checks each against regulations
   - Uses **HippoRAG** (NeurIPS 2024) for graph-enhanced retrieval

3. **Compliance Reports**
   - Paragraph-level violation detection
   - Severity scoring (critical/high/medium/low)
   - Missing elements identified
   - Confidence scores for each finding

### **Example Workflow**

```
Input: FDA 21 CFR Part 11 (Electronic Records) — 45 pages PDF

→ Agent extracts 25 requirements in ~30 seconds
→ Builds 703-node knowledge graph with 876 edges
→ Stores vector embeddings (384-dim)

Query: "Check protocol informed consent section"

→ Vector search finds 5 seed clauses
→ Personalized PageRank propagates through graph
→ Returns top-10 relevant regulations in <1 second
→ LLM agent evaluates compliance for each clause
→ Output: 2 critical violations, 3 warnings, 5 compliant
```

---

## How we built it

### **System Architecture**

```
┌─────────────────┐
│  Regulation PDF │
└────────┬────────┘
         │
    ┌────▼─────────────────────────────┐
    │  Agent 1: Extract Requirements   │
    │  (Claude 3.5 Sonnet)            │
    └────────┬─────────────────────────┘
             │
    ┌────────▼─────────────────────────┐
    │  Agent 2: Find Relationships     │
    │  (Semantic + LLM reasoning)      │
    └────────┬─────────────────────────┘
             │
       ┌─────▼──────┐     ┌──────────────┐
       │  ChromaDB  │     │  NetworkX    │
       │  Vectors   │     │  Graph       │
       └─────┬──────┘     └──────┬───────┘
             │                    │
             └────────┬───────────┘
                      │
            ┌─────────▼──────────┐
            │  HippoRAG Retrieval│
            │  (Vector + Graph)  │
            └
[truncated — 7939 more characters]
```

### backend-fastapi/pyproject.toml

```
[tool.poetry]
name = "backend-fastapi"
version = "0.1.0"
description = "Clinical trial compliance checker with HippoRAG"
authors = ["Vardhan Shorewala <vardhanshorewala@berkeley.edu>"]
readme = "README.md"
packages = []

[tool.poetry.dependencies]
python = ">=3.12,<4"
fastapi = ">=0.104.0"
uvicorn = {extras = ["standard"], version = ">=0.24.0"}
pydantic = ">=2.4.0"
pydantic-settings = ">=2.0.0"
python-dotenv = ">=1.0.0"
httpx = ">=0.25.0"
chromadb = ">=0.4.0"
networkx = ">=3.2"
pypdf = ">=3.17.0"
sentence-transformers = ">=2.2.0"
numpy = ">=1.24.0"
scikit-learn = ">=1.3.0"
python-multipart = "^0.0.20"
pymupdf = ">=1.23.0"

[build-system]
requires = ["poetry-core>=2.0.0"]
build-backend = "poetry.core.masonry.api"

```

### backend-fastapi/docker-compose.yml

```yaml
version: '3.8'

services:
  backend:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: harmoniq-backend
    ports:
      - "8000:8000"
    environment:
      - LAVA_API_KEY=${LAVA_API_KEY}
      - HOST=0.0.0.0
      - PORT=8000
    volumes:
      # Mount data directory for persistence
      - ./data:/app/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/regulations/test"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - harmoniq-network

  # Optional: Add frontend service
  # frontend:
  #   build:
  #     context: ../harmoniq-frontend
  #     dockerfile: Dockerfile
  #   container_name: harmoniq-frontend
  #   ports:
  #     - "3000:3000"
  #   environment:
  #     - NEXT_PUBLIC_API_URL=http://backend:8000/api
  #   depends_on:
  #     - backend
  #   restart: unless-stopped
  #   networks:
  #     - harmoniq-network

networks:
  harmoniq-network:
    driver: bridge


```

### harmoniq-frontend/package.json

```
{
  "name": "harmoniq-frontend",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "next build",
    "check": "next lint && tsc --noEmit",
    "dev": "next dev --turbo",
    "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
    "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "preview": "next build && next start",
    "start": "next start",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@t3-oss/env-nextjs": "^0.12.0",
    "@tailwindcss/typography": "^0.5.19",
    "@types/three": "^0.180.0",
    "d3-force": "^3.0.0",
    "d3-force-3d": "^3.0.6",
    "next": "^15.2.3",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-force-graph": "^1.48.1",
    "react-force-graph-2d": "^1.29.0",
    "react-force-graph-3d": "^1.29.0",
    "react-markdown": "^10.1.0",
    "remark-gfm": "^4.0.1",
    "three": "^0.180.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.3.1",
    "@tailwindcss/postcss": "^4.0.15",
    "@types/node": "^20.14.10",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "eslint": "^9.23.0",
    "eslint-config-next": "^15.2.3",
    "postcss": "^8.5.3",
    "prettier": "^3.5.3",
    "prettier-plugin-tailwindcss": "^0.6.11",
    "tailwindcss": "^4.0.15",
    "typescript": "^5.8.2",
    "typescript-eslint": "^8.27.0"
  },
  "ct3aMetadata": {
    "initVersion": "7.39.3"
  },
  "packageManager": "npm@10.7.0"
}

```

### backend-fastapi/Dockerfile

```
# Multi-stage build for optimized production image
FROM python:3.12-slim as builder

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Install Poetry
RUN pip install --no-cache-dir poetry==1.7.1

# Copy dependency files
COPY pyproject.toml poetry.lock ./

# Configure poetry to not create virtual env (we're in a container)
RUN poetry config virtualenvs.create false

# Install dependencies (--no-root skips installing the project itself)
RUN poetry install --no-dev --no-root --no-interaction --no-ansi

# Production stage
FROM python:3.12-slim

WORKDIR /app

# Install runtime dependencies
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Copy installed packages from builder
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# Copy application code
COPY app ./app

# Copy and unzip data
COPY data.zip ./
RUN apt-get update && apt-get install -y unzip && \
    unzip -q data.zip && \
    rm data.zip && \
    apt-get remove -y unzip && \
    apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/*

# Create a non-root user
RUN useradd -m -u 1000 harmoniq && \
    chown -R harmoniq:harmoniq /app

USER harmoniq

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/api/regulations/test || exit 1

# Run uvicorn with production settings
CMD ["uvicorn", "app.main:app", \
     "--host", "0.0.0.0", \
     "--port", "8000", \
     "--workers", "4", \
     "--log-level", "info"]


```

### backend-fastapi/app/main.py

```python
"""Main FastAPI application entry point"""

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.api.routes import health
from app.core.config import settings

app = FastAPI(
    title=settings.PROJECT_NAME,
    version=settings.VERSION,
    description="Harmoniq Backend API",
    docs_url="/api/docs",
    redoc_url="/api/redoc",
    openapi_url="/api/openapi.json",
)

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

# Include routers
app.include_router(health.router, prefix="/api", tags=["health"])

# Import regulations router
from app.api.routes import regulations
app.include_router(regulations.router, prefix="/api/regulations", tags=["regulations"])


@app.get("/")
async def root():
    """Root endpoint"""
    return {
        "message": "Welcome to Harmoniq API",
        "version": settings.VERSION,
        "docs": "/api/docs",
    }


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "app.main:app",
        host=settings.HOST,
        port=settings.PORT,
        reload=settings.DEBUG,
    )


```

### harmoniq-frontend/src/app/layout.tsx

```typescript
import "~/styles/globals.css";

import { type Metadata } from "next";
import { Geist } from "next/font/google";

export const metadata: Metadata = {
  title: "Harmoniq - Regulatory Intelligence Agent",
  description: "Side-by-Side Regulatory Comparator for Clinical Documents",
  icons: [{ rel: "icon", url: "/favicon.ico" }],
};

const geist = Geist({
  subsets: ["latin"],
  variable: "--font-geist-sans",
});

export default function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" className={`${geist.variable}`} suppressHydrationWarning>
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

```

### harmoniq-frontend/src/app/page.tsx

```typescript
"use client";

import { useState, useRef } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";

export default function HomePage() {
  const router = useRouter();
  const [isExpanded, setIsExpanded] = useState(false);
  const [showForm, setShowForm] = useState(false);
  const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
  const [selectedStandard, setSelectedStandard] = useState("all");
  const [selectedRegion, setSelectedRegion] = useState("us");
  const [analysisName, setAnalysisName] = useState("");
  const [analysisDescription, setAnalysisDescription] = useState("");
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [currentStep, setCurrentStep] = useState(0);
  const progressStepsRef = useRef<HTMLDivElement>(null);

  const handleChatClick = () => {
    if (!isExpanded) {
      setIsExpanded(true);
      setTimeout(() => setShowForm(true), 500);
    }
  };

  const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files || []);
    if (files.length > 0) {
      addFiles(files);
    }
  };

  const addFiles = (newFiles: File[]) => {
    const validFiles: File[] = [];
    const allowedTypes = [
      "image/png",
      "image/jpeg",
      "image/jpg",
      "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // XLSX
      "text/csv",
      "application/pdf",
    ];

    const currentPdfCount = uploadedFiles.filter(
      (file) => file.type === "application/pdf",
    ).length;

    for (const file of newFiles) {
      // Check if file type is allowed
      if (!allowedTypes.includes(file.type)) {
        alert(
          `File type ${file.type} is not supported. Please upload PNG, JPG, XLSX, CSV, or PDF files only.`,
        );
        continue;
      }

      // Check PDF limit
      if (file.type === "application/pdf") {
        if (currentPdfCount >= 1) {
          alert(
            "Only one PDF file is allowed. Please remove the existing PDF before uploading a new one.",
          );
          continue;
        }
      }

      // Check if file already exists
      if (
        !uploadedFiles.some(
          (existingFile) =>
            existingFile.name === file.name && existingFile.size === file.size,
        )
      ) {
        validFiles.push(file);
      }
    }

    if (validFiles.length > 0) {
      setUploadedFiles((prev) => [...prev, ...validFiles]);
    }
  };

  const removeFile = (index: number) => {
    setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
  };

  const handleFileClick = () => {
    fileInputRef.current?.click();
  };

  const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    const files = Array.from(event.dataTransfer.files);
    if (files.length > 0) {
      addFiles(files);
    }
  };

  const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
  };

  const [isAnalyzing, setIsAnalyzing] = useState(false);
  const [analysisError, setAnalysisError] = useState<string | null>(null);

  const analysisSteps = [
    { id: 1, name: "Parsing document structure", duration: 800 },
    { id: 2, name: "Extracting compliance requirements", duration: 1000 },
    { id: 3, name: "Mapping regulatory framework", duration: 900 },
    { id: 4, name: "Cross-referencing regulations", duration: 1100 },
    { id: 5, name: "Generating compliance report", duration: 0 }, // stays until complete
  ];

  const handleSubmit = async () => {
    const pdfFiles = uploadedFiles.filter(
      (file) => file.type === "application/pdf",
    );
    if (pdfFiles.length === 0) {
      alert("Please upload at least one PDF file for analysis.");
      return;
    }
    if (pdfFiles.length > 1) {
      alert("Only one PDF file is allowed for analysis.");
      return;
    }

    const pdfFile = pdfFiles[0];
    if (!pdfFile) {
      alert("PDF file not found.");
      return;
    }

    setIsAnalyzing(true);
    setAnalysisError(null);
    setCurrentStep(0);

    // Scroll to progress steps after a short delay to allow rendering
    setTimeout(() => {
      progressStepsRef.current?.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }, 100);

    try {
      // Simulate step progression
      for (let i = 0; i < analysisSteps.length - 1; i++) {
        setCurrentStep(i + 1);
        await new Promise((resolve) =>
          setTimeout(resolve, analysisSteps[i]?.duration || 1000),
        );
      }

      // Start last step
      setCurrentStep(analysisSteps.length);

      // Map frontend region names to backend country codes
      const regionToCountry: Record<string, string> = {
        us: "USA",
        europe: "EU",
        japan: "JAPAN",
      };
      const countryCode = regionToCountry[selectedRegion] || "USA";

      // Create FormData to send PDF to backend
      const formData = new FormData();
      formData.append("file", pdfFile);
      formData.append("country", countryCode);
      formData.append("top_k", "10");
      formData.append("num_chunks", "12");
      formData.append("compliance_focus", selectedStandard);

      // Send to backend for compliance checking
      const response = await fetch(
        "http://localhost:8000/api/regulations/check-pdf-compliance",
        {
          method: "POST",
          body: formData,
        },
      );

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.detail || "Failed to analyze PDF");
      }

      const result = await response.json();

      // Store compliance results and selected country in sessionStorage
      sessionStorage.setItem("complianceResults", JSON.stringify(result));
      sessionStorage.setItem("selectedCountry", countryCode);

      // Convert PDF to Markdown for display
      const markdownFormData = new FormData();
      markdownFormData.append("file", pdfFile);

      const markdownResponse =
[truncated — 33234 more characters]
```

### harmoniq-frontend/src/app/api/usage/route.ts

```typescript
import { NextRequest, NextResponse } from "next/server";
import { env } from "~/env";

export async function GET(request: NextRequest) {
  try {
    // Get query parameters
    const searchParams = request.nextUrl.searchParams;
    const start = searchParams.get("start");
    const end = searchParams.get("end");
    const connectionId = searchParams.get("connection_id");
    const productId = searchParams.get("product_id");

    console.log("Usage API called with params:", { start, end, connectionId, productId });
    console.log("Start date parsed:", start ? new Date(start).toISOString() : 'none');
    console.log("End date parsed:", end ? new Date(end).toISOString() : 'none');

    // Validate required parameters
    if (!start) {
      return NextResponse.json(
        { error: { message: "Start date is required", code: "missing_start_date" } },
        { status: 400 }
      );
    }

    // Check for API key
    if (!env.LAVA_API_KEY) {
      console.error("LAVA_API_KEY is not configured!");
      return NextResponse.json(
        { error: { message: "Lava API key not configured on server. Please set LAVA_API_KEY in your .env.local file", code: "missing_api_key" } },
        { status: 500 }
      );
    }

    console.log("Using API key:", env.LAVA_API_KEY.substring(0, 10) + "...");
    console.log("API key length:", env.LAVA_API_KEY.length);

    // Build query parameters for Lava API
    const params = new URLSearchParams({ start });
    if (end) params.append("end", end);
    if (connectionId) params.append("connection_id", connectionId);
    if (productId) params.append("product_id", productId);

    const apiUrl = `https://api.lavapayments.com/v1/usage?${params.toString()}`;
    console.log("Calling Lava API:", apiUrl);

    // Call Lava API
    const response = await fetch(apiUrl, {
      headers: {
        Authorization: `Bearer ${env.LAVA_API_KEY}`,
      },
      cache: "no-store", // Don't cache usage data
    });

    console.log("Lava API response status:", response.status);

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Lava API error response:", errorText);
      
      let errorData;
      try {
        errorData = JSON.parse(errorText);
      } catch {
        errorData = { error: { message: errorText || "Failed to fetch usage data" } };
      }
      
      return NextResponse.json(
        { error: errorData.error || { message: "Failed to fetch usage data", details: errorText } },
        { status: response.status }
      );
    }

    const data = await response.json();
    console.log("Lava API success, items count:", data.items?.length || 0);
    console.log("Lava API totals:", data.totals);
    if (data.items?.length > 0) {
      console.log("First item:", data.items[0]);
    }
    return NextResponse.json(data);
  } catch (error) {
    console.error("Error fetching usage data:", error);
    return NextResponse.json(
      { 
        error: { 
          message: error instanceof Error ? error.message : "Internal server error", 
          code: "internal_error" 
        } 
      },
      { status: 500 }
    );
  }
}


```

### harmoniq-frontend/src/app/usage/page.tsx

```typescript
"use client";

import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";

interface UsageItem {
  date: string;
  start: string;
  end: string;
  total_requests: number;
  total_usage_tokens: number;
  total_usage_cost: string;
  total_fee_amount: string;
  total_service_charge_amount: string;
  total_request_cost: string;
  total_wallet_cost: string;
  total_merchant_cost: string;
}

interface UsageTotals {
  total_requests: number;
  total_usage_tokens: number;
  total_usage_cost: string;
  total_fee_amount: string;
  total_service_charge_amount: string;
  total_request_cost: string;
  total_wallet_cost: string;
  total_merchant_cost: string;
}

interface UsageResponse {
  items: UsageItem[];
  totals: UsageTotals;
}

export default function UsagePage() {
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [usageData, setUsageData] = useState<UsageResponse | null>(null);
  const [showFilters, setShowFilters] = useState(false);
  
  // Date range state
  const [startDate, setStartDate] = useState(() => {
    const date = new Date();
    date.setDate(date.getDate() - 30); // Default to last 30 days
    date.setHours(0, 0, 0, 0); // Start of day
    return date.toISOString();
  });
  const [endDate, setEndDate] = useState(() => {
    const date = new Date();
    date.setHours(23, 59, 59, 999); // End of day
    return date.toISOString();
  });
  
  // Filter state
  const [connectionId, setConnectionId] = useState("");
  const [productId, setProductId] = useState("");

  // Fetch usage data via Next.js API route
  const fetchUsageData = async () => {
    setIsLoading(true);
    setError(null);

    try {
      const params = new URLSearchParams({
        start: startDate,
        end: endDate,
      });

      if (connectionId) params.append("connection_id", connectionId);
      if (productId) params.append("product_id", productId);

      console.log("Fetching usage data with params:", { startDate, endDate, connectionId, productId });

      // Call our internal API route instead of Lava directly
      const response = await fetch(`/api/usage?${params.toString()}`);

      console.log("API response status:", response.status);

      if (!response.ok) {
        const errorData = await response.json();
        console.error("API error:", errorData);
        throw new Error(errorData.error?.message || "Failed to fetch usage data");
      }

      const data: UsageResponse = await response.json();
      console.log("Usage data received:", data);
      setUsageData(data);
    } catch (err) {
      console.error("Error fetching usage data:", err);
      setError(err instanceof Error ? err.message : "Failed to fetch usage data");
    } finally {
      setIsLoading(false);
    }
  };

  // Format currency
  const formatCurrency = (value: string) => {
    const num = parseFloat(value);
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
      minimumFractionDigits: 2,
      maximumFractionDigits: 4,
    }).format(num);
  };

  // Format number with commas
  const formatNumber = (value: number) => {
    return new Intl.NumberFormat("en-US").format(value);
  };

  // Format date
  const formatDate = (dateString: string) => {
    return new Date(dateString).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric",
    });
  };

  // Auto-fetch on mount
  useEffect(() => {
    if (!usageData && !isLoading) {
      fetchUsageData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <main className="relative min-h-screen overflow-x-hidden bg-[#0a0a0f]">
      {/* Animated background gradients */}
      <div className="fixed inset-0 -z-10">
        <div className="absolute inset-0 bg-linear-to-br from-blue-950/20 via-transparent to-purple-950/20" />
        <div className="animated-gradient absolute inset-0" />
      </div>

      {/* Header */}
      <div className="sticky top-0 z-50 border-b border-blue-500/10 bg-[#0a0f1e]/80 backdrop-blur-xl">
        <div className="mx-auto max-w-7xl px-6 py-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-4">
              <button
                onClick={() => router.push("/")}
                className="flex items-center gap-2 text-gray-400 transition-colors hover:text-blue-400"
              >
                <svg
                  className="h-5 w-5"
                  fill="none"
                  stroke="currentColor"
                  viewBox="0 0 24 24"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={2}
                    d="M10 19l-7-7m0 0l7-7m-7 7h18"
                  />
                </svg>
              </button>
              <div className="w-[120px]">
                <Image
                  src="/full-logo.png"
                  alt="Harmoniq Logo"
                  width={120}
                  height={40}
                  className="h-10 w-[120px] object-contain"
                  quality={100}
                  unoptimized
                />
              </div>
              <h1 className="ml-2 text-2xl font-bold text-white">
                Usage Statistics
              </h1>
            </div>
            <div className="h-10 w-10 overflow-hidden rounded-full border border-blue-500/15 bg-[#0a0f1e] transition-all duration-300 hover:border-blue-500/40 hover:bg-blue-600/10">
              <div className="flex h-full w-full items-center justify-center text-sm font-bold text-blue-400">
                JP
              </div>
            </div>
          </div>
        </div>
      </div>

      <div className="mx-auto max-w-7xl px-6 py-8">
        {/* Filters Section */}
        <div classN
[truncated — 20558 more characters]
```

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