Final Push
This commit is contained in:
374
docs/02-services.md
Normal file
374
docs/02-services.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user