Files
LexiChain/docs/02-services.md
2026-05-13 21:08:27 +01:00

375 lines
13 KiB
Markdown

# 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 |