13 KiB
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
- AIService
- RAGService
- BlockchainService
- ContractService
- NotificationService
- EmailService
- StatsService
- CertificateService
- 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)
{
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 textembedding— 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
| 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 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 |