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