# 02 — Services Reference

All domain services live in `lib/services/`. They are plain TypeScript classes with static methods — no instantiation needed. Services are **server-only** (never imported in client components).

---

## Table of Contents

1. [AIService](#1-aiservice)
2. [RAGService](#2-ragservice)
3. [BlockchainService](#3-blockchainservice)
4. [ContractService](#4-contractservice)
5. [NotificationService](#5-notificationservice)
6. [EmailService](#6-emailservice)
7. [StatsService](#7-statsservice)
8. [CertificateService](#8-certificateservice)
9. [StorageService](#9-storageservice)

---

## 1. AIService

**File:** `lib/services/ai.service.ts`  
**Sub-modules:** `lib/services/ai/` (parser, normalizer, prompt builder, key manager)

### Purpose
Extracts structured information from uploaded contract documents (PDF or image). Implements a multi-model fallback chain with automatic retry and JSON repair.

### Pipeline

```
File URL
  ↓
Download bytes + resolve MIME type
  ↓
Pre-validation (is this actually a contract?)
  ↓
Build adaptive prompt (with context from previous analyses)
  ↓
[Attempt 1..N]
  │
  ├── Try Gemini primary model (key rotation across AI_API_KEY1/2/3)
  ├── Try Gemini secondary model (if AI_MODEL_SECONDARY_GEMINI is set)
  ├── Try Gemini with lenient settings (last Gemini attempt)
  │
  └── If all Gemini models fail → Mistral Fallback
        ├── PDF: extract text via pdf-parse → ground Mistral on text
        └── Image: send to Pixtral vision model directly
  ↓
Parse JSON → normalize → validate
  ↓
On parse failure: call Mistral repair model → emergency field extraction
  ↓
Return NormalizedAnalysis
```

### Key Methods

| Method | Description |
|--------|-------------|
| `analyzeContract(fileUrl, options?)` | Main entry point — full extraction pipeline |
| `askAboutContract(question, contract, userId?)` | RAG-grounded Q&A on an analyzed contract |
| `preValidateContract(input)` | Quick pre-check to reject non-contract files |
| `buildAdaptiveContext(userId?)` | Builds few-shot examples from user's previous contracts |

### Configuration

| Environment Variable | Default | Description |
|---------------------|---------|-------------|
| `AI_API_KEY1` / `AI_API_KEY2` / `AI_API_KEY3` | — | Gemini API keys (rotated automatically) |
| `AI_MODEL_PRIMARY` | `gemini-2.5-flash-preview-04-17` | Primary Gemini extraction model |
| `AI_MODEL_SECONDARY_GEMINI` | — | Optional secondary Gemini model |
| `AI_EMBEDDING_MODEL` | `text-embedding-004` | Gemini embedding model for RAG |
| `MISTRAL_API_KEY` | — | Mistral API key (activates fallback) |
| `AI_MODEL_FALLBACK` | `mistral-large-latest` | Mistral text fallback model |
| `AI_MODEL_MISTRAL_VISION` | `pixtral-large-latest` | Mistral multimodal model for images |
| `AI_MODEL_MISTRAL_OCR` | `mistral-ocr-latest` | Mistral OCR model |
| `AI_FORCE_FALLBACK_TEST` | — | Set to `"1"` to bypass Gemini for testing |

### Sub-modules

| File | Purpose |
|------|---------|
| `ai/analysis.prompt.ts` | Builds the Gemini extraction prompt with few-shot examples |
| `ai/analysis.parser.ts` | Robust JSON parser with fence stripping and repair |
| `ai/analysis.normalizer.ts` | Normalizes raw AI output into `NormalizedAnalysis` shape |
| `ai/analysis.types.ts` | TypeScript types for AI input/output |
| `ai/key-manager.ts` | Round-robin Gemini API key rotation with quota exhaustion detection |

### Output Schema (`NormalizedAnalysis`)

```typescript
{
  title: string | null
  type: ContractType | null           // enum: INSURANCE_AUTO, LOAN, etc.
  provider: string | null
  policyNumber: string | null
  startDate: Date | null
  endDate: Date | null
  premium: number | null
  summary: string
  extractedText: string
  keyPoints: {
    guarantees: string[]
    exclusions: string[]
    franchise: string | null
    importantDates: string[]
    explainability: Array<{
      field: string
      why: string
      sourceSnippet: string
      sourceHints: { page: string|null, section: string|null, confidence: number }
    }>
  }
  keyPeople: Array<{ name: string, role: string|null, email: string|null, phone: string|null }>
  relevantDates: Array<{ date: string, description: string, type: string }>
}
```

---

## 2. RAGService

**File:** `lib/services/rag.service.ts`

### Purpose
Retrieval-Augmented Generation (RAG) — splits contract text into chunks, generates embeddings, stores them in PostgreSQL, and retrieves the most relevant chunks when answering user questions.

### How It Works

```
analyzeContract completes
  ↓
RAGService.indexContract(contractId, extractedText)
  ├── Split text into 512-token chunks with 64-token overlap
  ├── Generate embeddings via Gemini text-embedding-004
  └── Store in ContractRagChunk table

User asks question
  ↓
RAGService.query(contractId, question)
  ├── Embed the question via text-embedding-004
  ├── Load all chunks for this contract from DB
  ├── Compute cosine similarity between question embedding and each chunk
  └── Return top-K most relevant chunks
```

### Key Methods

| Method | Description |
|--------|-------------|
| `indexContract(contractId, text)` | Chunks, embeds, and stores contract text |
| `query(contractId, question, topK?)` | Returns most similar chunks to a question |
| `deleteIndex(contractId)` | Removes all RAG chunks for a contract |

### Storage
Chunks are stored in the `ContractRagChunk` model:
- `content` — raw chunk text
- `embedding` — float array (Gemini embedding vector)
- `contentHash` — SHA-256 of content (deduplication)
- `chunkIndex` — position in original document

---

## 3. BlockchainService

**File:** `lib/services/blockchain.service.ts`

### Purpose
Server-side Ethereum integration. Computes SHA-256 hashes of documents and registers them on the `DocumentRegistry` smart contract, providing tamper-proof, timestamped proof-of-deposit.

> **Important:** This service uses a **server-side wallet** (`JsonRpcProvider + Wallet`). Users do NOT need MetaMask or any wallet.

### How It Works

```
Contract file URL
  ↓
BlockchainService.hashDocument(fileUrl)
  ├── Downloads raw file bytes from UploadThing CDN
  └── Computes SHA-256 → returns 0x-prefixed bytes32 string

  ↓
BlockchainService.registerOnChain(documentHash, fileName)
  ├── Calls contract.registerDocument(bytes32) via server wallet
  ├── Waits for transaction to be mined (1 confirmation)
  ├── Gets block timestamp → this becomes the legal proof date
  └── Returns BlockchainProof { txHash, blockNumber, blockTimestamp, ... }
```

### Configuration

| Environment Variable | Description |
|---------------------|-------------|
| `BLOCKCHAIN_NETWORK` | `"hardhat"` (dev) or `"sepolia"` (production) |
| `BLOCKCHAIN_RPC_URL` | JSON-RPC endpoint (Sepolia public RPC or Alchemy/Infura) |
| `BLOCKCHAIN_PRIVATE_KEY` | Private key of the signing wallet (holds Sepolia ETH) |
| `BLOCKCHAIN_CONTRACT_ADDRESS` | Deployed `DocumentRegistry` contract address |

### Key Methods

| Method | Description |
|--------|-------------|
| `hashDocument(fileUrl)` | Downloads file and computes SHA-256 |
| `registerOnChain(hash, fileName)` | Registers hash on the smart contract |
| `hashAndRegister(fileUrl, fileName)` | Convenience: hash + register in one call |
| `verifyOnChain(documentHash)` | Read-only verification of a hash |
| `getNetworkStats()` | Returns network status, block number, total docs |
| `isConfigured()` | Returns false if env vars are missing (graceful degradation) |

### Graceful Degradation
If `BLOCKCHAIN_CONTRACT_ADDRESS` or `BLOCKCHAIN_PRIVATE_KEY` are not set, `isConfigured()` returns `false` and the app skips blockchain registration silently. The rest of the analysis pipeline completes normally.

---

## 4. ContractService

**File:** `lib/services/contract.service.ts`

### Purpose
CRUD operations and lifecycle management for Contract records. Acts as the single point of interaction with the `Contract` Prisma model.

### Status Machine

```
UPLOADED → PROCESSING → COMPLETED
                   └──→ FAILED
```

| Status | Meaning |
|--------|---------|
| `UPLOADED` | File received, waiting for analysis |
| `PROCESSING` | AI analysis in progress |
| `COMPLETED` | Analysis done, blockchain registered |
| `FAILED` | Analysis failed (error stored) |

### Key Methods

| Method | Description |
|--------|-------------|
| `create(userId, fileData)` | Creates a new Contract record (status: UPLOADED) |
| `updateWithAIResults(id, analysis)` | Saves AI-extracted fields, sets status COMPLETED |
| `updateBlockchainProof(id, proof)` | Saves blockchain transaction details |
| `setProcessing(id)` | Sets status to PROCESSING |
| `setFailed(id, error)` | Sets status to FAILED with error message |
| `getById(id, userId)` | Fetches a contract, validates ownership |
| `listForUser(userId, filters?)` | Returns all contracts for a user |
| `deleteContract(id, userId)` | Deletes contract + cascades to chunks/notifications |

---

## 5. NotificationService

**File:** `lib/services/notification.service.ts`

### Purpose
Creates, reads, and manages in-app notifications. Also schedules deadline-based alerts for contracts nearing expiration.

### Notification Types

| Type | Trigger |
|------|---------|
| `SUCCESS` | Contract analysis completed |
| `ERROR` | Analysis failed |
| `INFO` | Blockchain registration, general events |
| `WARNING` | Contract nearing expiry (30 days) |
| `DEADLINE` | Contract expires in < 7 days |

### Key Methods

| Method | Description |
|--------|-------------|
| `createNotification(userId, data)` | Creates an in-app notification |
| `getUnread(userId)` | Returns all unread notifications |
| `markAsRead(id, userId)` | Marks a notification as read |
| `markAllAsRead(userId)` | Marks all as read |
| `checkDeadlines(userId)` | Scans contracts nearing expiry and creates alerts |
| `deleteExpired()` | Purges expired notifications |

---

## 6. EmailService

**File:** `lib/services/email.service.ts`

### Purpose
Sends transactional emails using Nodemailer over SMTP. Falls back to Ethereal (test inbox) when running outside production and no SMTP credentials are configured.

### Configuration

| Variable | Description |
|----------|-------------|
| `EMAIL_HOST` | SMTP server hostname (e.g., `smtp-relay.brevo.com`) |
| `EMAIL_PORT` | SMTP port (`587` for STARTTLS, `465` for SSL) |
| `EMAIL_SECURE` | `true` for SSL/TLS, `false` for STARTTLS |
| `EMAIL_USER` | SMTP username |
| `EMAIL_PASS` | SMTP password |
| `MAIL_FROM` | From address, e.g., `LexiChain <no-reply@yourdomain.com>` |

### Email Types Sent

| Email | Trigger |
|-------|---------|
| Contract analysis complete | AI analysis finishes successfully |
| Analysis failed | AI analysis encounters an error |
| Contract expiry warning | Contract expires within 30 days |
| Contract expiry imminent | Contract expires within 7 days |
| Blockchain registration | Document hash registered on-chain |

### Dev Fallback
If `EMAIL_HOST` is not set and `NODE_ENV !== production`, the service creates an [Ethereal](https://ethereal.email/) test account automatically. The preview URL is printed to the server console.

---

## 7. StatsService

**File:** `lib/services/stats.service.ts`

### Purpose
Computes aggregated analytics for the dashboard. All queries run server-side via Prisma.

### Metrics Provided

| Metric | Description |
|--------|-------------|
| Total contracts | Count of all user contracts |
| By status | Breakdown: UPLOADED / PROCESSING / COMPLETED / FAILED |
| By type | Distribution across ContractType enum values |
| By month | Monthly upload trend (last 12 months) |
| Expiring soon | Contracts expiring within 30/90 days |
| Average premium | Average premium across completed contracts |
| Blockchain coverage | % of contracts with on-chain proof |
| Recent activity | Last N contracts with status |

---

## 8. CertificateService

**File:** `lib/services/certificate.service.ts`

### Purpose
Generates downloadable PDF certificates of document registration using PDFKit. The certificate includes:
- Contract metadata (title, type, dates, provider)
- Document SHA-256 hash
- Blockchain transaction hash and block number
- Registration timestamp (block timestamp)
- QR code linking to Etherscan (Sepolia)

### Key Methods

| Method | Description |
|--------|-------------|
| `generateCertificate(contract)` | Returns a PDF Buffer for a completed+blockchain contract |
| `generateCertificateStream(contract)` | Returns a Node.js stream for streaming response |

---

## 9. StorageService

**File:** `lib/services/storage.service.ts`

### Purpose
Thin wrapper around UploadThing. Provides utilities for:
- Deleting uploaded files (cleanup on contract delete)
- Building file access URLs
- Validating file constraints

### Configuration
UploadThing is configured in `lib/upload.ts` and exposed via `app/api/uploadthing/`.

| Variable | Description |
|----------|-------------|
| `UPLOADTHING_TOKEN` | UploadThing authentication token |
| `UPLOADTHING_APP_ID` | UploadThing application ID |
