Digitizing and intelligently triaging investor service requests for financial institutions
- Project Overview
- Business Perspective
- System Architecture
- Ticket Workflow
- AI Pipeline
- Features
- Technology Stack
- Database Design
- Folder Structure
- API Documentation
- Authentication Flow
- Security
- Local Development
- Deployment
- Module-Wise Flow
- Business Walkthrough
- Future Enhancements
- Contributing
- License
FinnovaX is a production-grade, full-stack FinTech portal built for FinnovaX-style mutual fund and investor service management. It is designed to solve one of the most pressing operational challenges in asset management companies: manual, error-prone, slow investor grievance and service request processing.
Financial institutions process thousands of investor service requests daily โ nominee changes, bank account updates, KYC document submissions, and formal complaints. Traditionally, this means:
- Slow turnaround times โ Agents manually read long, emotional requests to categorize them
- Compliance risk โ Critical or fraudulent requests get buried under routine tickets
- Document errors โ Manual verification of KYC documents is slow and error-prone
- No audit trail โ Changes happen without immutable, role-attributed records
FinnovaX introduces AI at every step of the workflow:
- Intelligent Intake โ When an investor submits a complaint, FinBERT (a financial NLP model) analyzes the text for sentiment, fraud signals, and intent
- Automated Prioritization โ CRITICAL priority and fraud flags are applied automatically based on sentiment score and fraud keyword detection
- OCR Document Verification โ EasyOCR cross-references submitted KYC documents against the investor's declared information
- AI-Powered Summaries โ L1 agents receive a 3-bullet summary generated by Gemini/Ollama (hybrid approach) so they can triage in seconds instead of minutes
- Strict Maker-Checker Enforcement โ L1 makers can triage and escalate; only L2 checkers can finalize resolutions. No single actor can complete sensitive operations alone
- Intelligent Chatbot โ A strict RAG-based chatbot (Finnora) answers investor queries using ChromaDB as the knowledge base, never hallucinating beyond its trained FAQ data
| Role | Description |
|---|---|
| Investor | Submits and tracks service requests |
| L1 Maker | Triages, assigns priority, escalates or rejects |
| L2 Checker | Provides final approval or returns to L1 |
| Super Admin | Monitors system KPIs, manages users, views audit logs |
| Guest | Can interact with Finnora chatbot before registration |
For investors, clients, and non-technical stakeholders
Without FinnovaX: An investor calls the helpdesk. An agent takes notes manually. The request gets emailed to an operations team. Someone prints documents and physically verifies the cancelled cheque. If documents are incomplete, the investor is called back. Three to five business days later, maybe the update is done โ or maybe it's lost in an email chain.
With FinnovaX:
- Investor logs into the portal and selects Bank Account Update from the service menu
- The system shows exactly what documents are needed (Cancelled Cheque + Passbook First Page), and enforces an SLA of 10 business days
- Documents are uploaded โ OCR immediately extracts and validates the account number
- The AI analyzes the request text for urgency signals and assigns an initial priority
- An L1 Maker reviews the request on their desk, sees the AI summary, verifies the OCR match, and escalates to L2 with notes
- The L2 Checker reviews and finalizes โ the investor immediately receives an email notification
- Every action is recorded in an immutable AuditLog
Result: From 5 days and 3 phone calls to 10 business days guaranteed SLA with zero calls and full digital traceability.
| Service | SLA | Documents Required |
|---|---|---|
COMPLAINT |
7 business days | None |
BANK_UPDATE |
10 business days | Cancelled Cheque, Bank Passbook |
NOMINEE_UPDATE |
7 business days | Nominee ID Proof, Photograph |
ADDRESS_UPDATE |
7 business days | Address Proof, Declaration Letter |
EMAIL_UPDATE |
2 business days | None |
MOBILE_UPDATE |
2 business days | None |
KYC_UPDATE |
15 business days | KYC Document, Passport Photo |
flowchart TB
classDef userLayer fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#0f172a
classDef frontendLayer fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e
classDef backendLayer fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
classDef dbLayer fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f
classDef aiLayer fill:#f3e8ff,stroke:#9333ea,stroke-width:2px,color:#581c87
classDef externalLayer fill:#f1f5f9,stroke:#64748b,stroke-width:2px,color:#1e293b
classDef dbTable fill:#ffedd5,stroke:#ea580c,stroke-width:1px,color:#431407
subgraph L1 [System Users]
direction LR
U0([Guest]):::userLayer
U1([Investor]):::userLayer
U2([L1 Maker / L2 Checker]):::userLayer
U3([Super Admin]):::userLayer
end
subgraph L2 [React + Vite + Tailwind Frontend]
direction LR
F0[Auth Pages]:::frontendLayer
F1[Investor Portal]:::frontendLayer
F2[Admin Desks]:::frontendLayer
ChatWidget[Finnora Chatbot Widget]:::frontendLayer
end
subgraph L3 [Node.js + Express Backend]
direction TB
API_GW{Express Router}:::backendLayer
RBAC[RBAC Middleware]:::backendLayer
AuthService[Auth Service JWT + Refresh]:::backendLayer
TicketService[Ticket Service]:::backendLayer
DocService[Document Service]:::backendLayer
NotifService[Notification Service]:::backendLayer
MLProxy[ML Service Proxy]:::backendLayer
AutoClose[AutoClose Background Job]:::backendLayer
end
subgraph L4 [MongoDB Atlas]
direction LR
M_Users[(USERS)]:::dbTable
M_Tickets[(TICKETS)]:::dbTable
M_History[(AUDIT_LOGS)]:::dbTable
M_Notif[(NOTIFICATIONS)]:::dbTable
M_Chat[(CHAT_SESSIONS)]:::dbTable
M_RefreshTokens[(REFRESH_TOKENS)]:::dbTable
end
subgraph L5 [FastAPI AI Engine]
direction TB
AI_GW{FastAPI Router}:::aiLayer
RAG[RAG Service ChromaDB]:::aiLayer
Sentiment[Sentiment Service FinBERT]:::aiLayer
OCR[OCR Service EasyOCR]:::aiLayer
Summarizer[Summarizer Gemini/Ollama]:::aiLayer
end
subgraph L6 [External Services]
direction LR
Cloudinary[(Cloudinary Storage)]:::externalLayer
Gemini([Google Gemini API]):::externalLayer
SES([AWS SES / SMTP Email]):::externalLayer
GoogleAuth([Google Authenticator TOTP]):::externalLayer
end
U0 --> F0
U1 --> F1
U2 --> F2
U3 -.-> F2
F0 ==> API_GW
F1 ==> API_GW
F2 ==> API_GW
ChatWidget ==> API_GW
API_GW --> RBAC
RBAC --> AuthService
RBAC --> TicketService
RBAC --> DocService
RBAC --> MLProxy
AuthService <--> M_Users
AuthService <--> M_RefreshTokens
AuthService --- GoogleAuth
TicketService --> M_Tickets
TicketService --> M_History
TicketService --> NotifService
TicketService --> MLProxy
DocService --> Cloudinary
NotifService --> SES
NotifService --> M_Notif
MLProxy ==> AI_GW
AI_GW --> RAG
AI_GW --> Sentiment
AI_GW --> OCR
AI_GW --> Summarizer
RAG --> Gemini
Summarizer --> Gemini
AutoClose -.-> M_Tickets
sequenceDiagram
participant I as Investor
participant FE as Frontend
participant BE as Node.js Backend
participant AI as FastAPI AI Engine
participant L1 as L1 Maker
participant L2 as L2 Checker
participant N as Notification Service
I->>FE: Submit Service Request + Documents
FE->>BE: POST /api/tickets
BE->>AI: POST /sentiment/analyze (complaint text)
AI-->>BE: sentiment, score, priority, fraud_alert
BE->>AI: POST /ocr/verify-account (uploaded docs)
AI-->>BE: OCR match result
BE->>BE: Set status=OPEN, persist ticket
BE->>N: createNotification(TICKET_CREATED, email)
N-->>I: Email Confirmation
L1->>BE: GET /api/l1/tickets
L1->>BE: POST /api/l1/tickets/:id/assign
BE->>AI: POST /summarize/ticket
AI-->>BE: 3-bullet AI summary
L1->>BE: POST /api/l1/tickets/:id/escalate (with L1 notes)
BE->>BE: status = L2_APPROVAL
BE->>N: createNotification(STATUS_CHANGED)
N-->>I: Status Update Email
L2->>BE: GET /api/l2/tickets
L2->>BE: POST /api/l2/finalize (APPROVE)
BE->>BE: status = RESOLVED, resolvedAt set
BE->>N: createNotification(TICKET_RESOLVED, email)
N-->>I: Resolution Email
Note over BE: AutoCloseJob polls every minute
BE->>BE: RESOLVED > grace period โ CLOSED
OPEN โ IN_PROGRESS โ L1_REVIEW โ L2_APPROVAL โ RESOLVED โ CLOSED
โ โ
REJECTED RETURNED TO L1
flowchart LR
A[User Submits Ticket] --> B[Node.js Backend]
B --> C{ML Service Proxy}
C -->|POST /sentiment/analyze| D[FinBERT Pipeline]
D --> E{Score and Priority}
E -->|fraud_alert=true| F[CRITICAL + Fraud Flag]
E -->|score > 0.85| G[CRITICAL Priority]
E -->|score > 0.60| H[HIGH Priority]
E -->|normal| I[NORMAL Priority]
B -->|POST /ocr/verify-account| J[EasyOCR Service]
J --> K[Extract + Fuzzy Match Account Number]
K --> L[OCR Verified / Rejected]
B -->|L1 Requests Summary| M[Summarizer Service]
M -->|Try Ollama Local| N[Llama3.2 local]
N -->|Fails / not running| O[Gemini 2.5 Flash Fallback]
O --> P[3-Bullet JSON Summary]
Q[Investor Chat Query] --> R[Chat Controller]
R -->|POST /chatbot/ask| S[RAG Service]
S --> T[ChromaDB FAQ Vector Query]
T --> U[Context Retrieved]
U --> V[Gemini 2.5 Flash]
V --> W[Strict FAQ-grounded Response]
| Service | Model | Purpose | Fallback |
|---|---|---|---|
| Sentiment Analysis | ProsusAI/finbert |
Detect sentiment, fraud, intent, and assign priority | Keyword-based scoring |
| OCR Verification | EasyOCR |
Extract and fuzzy-match text from uploaded documents | Mock pass |
| Summarizer | Ollama llama3.2:1b โ Gemini 2.5-flash |
Generate 3-bullet actionable summaries for L1 agents | Plain text fallback |
| RAG Chatbot | ChromaDB + Gemini 2.5-flash |
FAQ-grounded chatbot, strict context-only responses | None (transparent failure) |
๐งโ๐ผ Investor Features
- Secure Registration & Login with OTP email verification
- Multi-Factor Authentication โ Google Authenticator TOTP or Email OTP
- 7 Service Request Types with guided forms and document checklists
- Real-time Ticket Tracking with full status history
- Document Upload with Cloudinary storage and OCR verification feedback
- In-App Notifications with email delivery for important events
- Ticket Comments with visibility controls (investor-visible vs internal)
- Profile Management with KYC, bank, nominee, and address fields
- Ticket Resubmission after rejection
- Finnora AI Chatbot โ interactive RAG-based support assistant
๐ง L1 Maker Features
- Filtered Ticket Queue โ filter by status, priority, service type
- Ticket Assignment โ claim ownership of a ticket
- AI Summary Generation โ one-click Gemini/Ollama 3-bullet summary
- Sentiment Score View โ see FinBERT confidence and fraud flags
- Document OCR Trigger โ verify uploaded documents via EasyOCR
- Escalate to L2 โ with mandatory notes
- Reject Ticket โ with mandatory rejection reason (triggers investor email)
- Internal Comments โ visible only to admin staff
โ L2 Checker Features
- L2 Approval Queue โ exclusively
L2_APPROVALstatus tickets - Finalize Resolution โ approve and resolve (sends investor email)
- Return to L1 โ send back with a note if further action is needed
- Full Ticket History โ view complete audit trail
๐ Super Admin Features
- System KPI Dashboard โ total tickets, open, resolved, CRITICAL, fraud flagged
- All Users Management โ activate/deactivate user accounts
- All Tickets View โ with full filtering and pagination
- Flagged Tickets โ dedicated view for CRITICAL + fraud tickets
- CSV Report Export โ download ticket data for compliance reporting
- Agent Activity Audit โ view every L1/L2 action chronologically
- OCR Document Verification โ trigger manual document OCR
๐ค AI Features
- FinBERT Sentiment Analysis โ financial-domain NLP for real sentiment detection
- Fraud Signal Detection โ keyword-based fraud alert with priority override
- EasyOCR Extraction โ GPU or CPU-accelerated document text extraction
- Account Number Fuzzy Matching โ tolerates minor OCR character recognition errors
- KYC Name + DOB Matching โ validates identity against government-issued ID
- Gemini 2.5 Flash Integration โ cloud LLM for summaries and chatbot responses
- Ollama Local Fallback โ security-first local LLM for offline/on-premise deployments
- ChromaDB RAG โ persistent vector store seeded with FinnovaX FAQ content
- Strict Chatbot Context โ L2 distance threshold (< 1.1) ensures no hallucination beyond KB
๐ Security Features
- JWT Access Tokens (15-minute expiry)
- Rotating Refresh Tokens (7-day expiry, SHA-256 hashed in DB)
- Token Replay Attack Detection โ reused refresh tokens trigger full session revocation
- Google Authenticator TOTP (speakeasy library, ยฑ30s clock drift tolerance)
- Email OTP 2FA as alternative
- Helmet.js โ HTTP security headers
- Rate Limiting โ 5000 req/15min globally, 10 attempts/15min for login
- CORS Whitelist โ only approved domains
- MongoDB Sanitize โ NoSQL injection prevention
- Role-Based Access Control โ strict per-route role enforcement
๐ Infrastructure Features
- Docker Compose โ full containerized deployment (GPU and CPU variants)
- MongoDB Atlas โ cloud-managed database with compound indexes
- Cloudinary โ media/document storage with CDN delivery
- AWS LocalStack โ S3/SES/SNS emulation for local development
- AutoClose Background Job โ automatically closes
RESOLVEDtickets after grace period - ChromaDB Seeding โ FAQ knowledge base auto-seeded on FastAPI startup
- Health Endpoints โ
/healthon Node.js and/healthon FastAPI
| Technology | Version | Purpose |
|---|---|---|
| React | 18 | UI Framework |
| Vite | Latest | Build Tool & Dev Server |
| Tailwind CSS | 3 | Utility-First Styling |
| shadcn/ui | Latest | Component Library |
| Framer Motion | Latest | Animations & Transitions |
| React Router DOM | 6 | Client-Side Routing |
| Lucide React | Latest | Icon System |
| Recharts | Latest | Analytics Charts |
| Sonner | Latest | Toast Notifications |
| date-fns | Latest | Date Formatting |
| Axios | Latest | HTTP Client |
| Technology | Version | Purpose |
|---|---|---|
| Node.js | 18+ | Runtime |
| Express.js | 4 | Web Framework |
| Mongoose | 8 | MongoDB ODM |
| JWT (jsonwebtoken) | 9 | Access Token Generation |
| bcrypt | 6 | Password Hashing |
| speakeasy | 2 | TOTP 2FA |
| qrcode | 1.5 | QR Code Generation for 2FA |
| otplib | 13 | OTP Support |
| Multer | 1.4 | File Upload Middleware |
| Cloudinary SDK | 2 | Document Storage |
| Helmet | 8 | HTTP Security Headers |
| express-rate-limit | 8 | Rate Limiting |
| express-mongo-sanitize | 2 | NoSQL Injection Prevention |
| AWS SDK v3 | 3 | S3/SES/SNS LocalStack Integration |
| nodemailer | 9 | SMTP Email Delivery |
| Resend | 6 | Alternative Email Provider |
| Twilio | 6 | SMS Notifications |
| Axios | 1.6 | ML Service HTTP Proxy |
| cookie-parser | 1.4 | Cookie Handling |
| dotenv | 16 | Environment Configuration |
| Technology | Purpose |
|---|---|
| FastAPI | AI Microservice Framework |
| uvicorn | ASGI Server |
| transformers (HuggingFace) | FinBERT Sentiment Model |
| PyTorch | GPU/CPU Inference |
| EasyOCR | Document Text Extraction |
| ChromaDB | RAG Vector Store |
| google-genai | Gemini 2.5 Flash API |
| httpx | Async HTTP Client (Ollama) |
| python-dotenv | Environment Configuration |
| Service | Purpose |
|---|---|
| MongoDB Atlas | Primary Database |
| Cloudinary | Document/Media Storage |
| Google Gemini API | Cloud LLM |
| Ollama (local) | On-Premise LLM (hybrid) |
| AWS SES (LocalStack) | Email Delivery (Dev) |
| AWS SNS (LocalStack) | SMS Delivery (Dev) |
| AWS S3 (LocalStack) | File Storage (Dev) |
| Docker Compose | Container Orchestration |
erDiagram
USERS {
ObjectId _id PK
String name
String email UK
String passwordHash
String phoneNumber
String role "INVESTOR|ADMIN_L1|ADMIN_L2|ADMIN_SUPER"
Boolean isActive
Object kyc "status, aadhaar, pan, dl, gstNumber"
Object bankAccount "accountNumber, ifsc, bankName"
Object nominee "name, relation, aadhaar"
Object address "street, city, state"
String resetPasswordToken
Date resetPasswordExpires
String otpCode
Date otpExpires
String dob
Boolean profileCompleted
String twoFactorSecret
Boolean twoFactorEnabled
String twoFactorType "NONE|GOOGLE|EMAIL|PHONE"
}
TICKETS {
ObjectId _id PK
ObjectId investorId FK
String title
String description
String investorName
String accountNumber
Array documents
Number aiSentimentScore
String assignedPriority "NORMAL|CRITICAL"
Array aiSummary
Boolean isPotentialFraud
String serviceType "COMPLAINT|BANK_UPDATE|..."
Object serviceMetadata
String l2ReturnNote
String l1Notes
String revisionReason
ObjectId assignedL1 FK
ObjectId assignedL2 FK
Array comments
Object slaTimeline "slaDays, deadline"
String status "OPEN|IN_PROGRESS|RESOLVED|..."
Date resolvedAt
}
AUDIT_LOGS {
ObjectId _id PK
ObjectId entityId FK
String entityType "Ticket|User"
String action
ObjectId performedBy FK
Object details
}
NOTIFICATIONS {
ObjectId _id PK
ObjectId userId FK
ObjectId ticketId FK
String type "TICKET_CREATED|RESOLVED|..."
String title
String message
Object channels "inApp, email, sms"
Date readAt
}
CHAT_SESSIONS {
ObjectId _id PK
ObjectId userId FK
String sessionId UK
Array messages "type, text, sources, sentiment, timestamp"
}
REFRESH_TOKENS {
ObjectId _id PK
ObjectId userId FK
String tokenHash
String familyId
Boolean isRevoked
Date expiresAt
}
USERS ||--o{ TICKETS : "raises"
USERS ||--o{ AUDIT_LOGS : "performs"
USERS ||--o{ NOTIFICATIONS : "receives"
USERS ||--o{ CHAT_SESSIONS : "has"
USERS ||--o{ REFRESH_TOKENS : "owns"
TICKETS ||--o{ AUDIT_LOGS : "tracked by"
TICKETS ||--o{ NOTIFICATIONS : "triggers"
USERS
Purpose: Central identity store for all system actors โ investors, L1/L2 agents, and super admins.
Key Indexes:
emailโ unique, used for login lookupsroleโ used to filter admin desksrole + isActiveโ used for agent performance queries
Notable Fields:
twoFactorSecretโ selected with+twoFactorSecretonly when needed (never returned by default)passwordHashโselect: false(never returned in API responses)refreshTokensโ legacy array, replaced byRefreshTokenmodelprofileCompletedโ gates investor dashboard access and triggers profile completion toast
TICKETS
Purpose: The core entity. Each service request is a ticket with a lifecycle from OPEN to CLOSED.
Lifecycle: OPEN โ IN_PROGRESS โ L1_REVIEW โ L2_APPROVAL โ RESOLVED โ CLOSED
Key Indexes:
{ investorId, createdAt }โ Investor's personal ticket list{ status, createdAt }โ L1 queue rendering{ assignedL1, status }โ L1 "my assigned tickets" filter{ isPotentialFraud }โ Admin fraud dashboard{ slaTimeline.deadline, status }โ future SLA breach scanning{ status, resolvedAt }โ AutoCloseJob
Notable Fields:
documents[].ocrExtractionโ storesextractedText,matchVerified, andconfidenceafter OCRcomments[].visibilityโ'INVESTOR_ADMIN'or'INTERNAL'to hide agent notes from investorsaiSummaryโ array of strings, generated by Gemini/Ollama per L1 request
AUDIT_LOGS
Purpose: Immutable, role-attributed record of every consequential action on the platform.
Every L1 escalation, L2 approval, rejection, and auto-close is written here. The Super Admin agent activity view queries this collection.
NOTIFICATIONS
Purpose: Stores every in-app notification. Triggers real-time email delivery for TICKET_CREATED, TICKET_RESOLVED, TICKET_REJECTED, DOCUMENT_REJECTED, and STATUS_CHANGED events.
Email Templates: Fully HTML-styled branded emails for each notification type, sent via AWS SES (LocalStack) or SMTP (Nodemailer).
CHAT_SESSIONS
Purpose: Persists chatbot conversation history. Supports both authenticated users (keyed by userId) and anonymous guests (keyed by sessionId from localStorage).
REFRESH_TOKENS
Purpose: Secure storage for rotating refresh tokens. Each token is stored as a SHA-256 hash, never the raw value. Contains a familyId to detect and respond to replay attacks by revoking all tokens in the same session family.
finnovax-nexus-portal/
โ
โโโ frontend/ # React + Vite application
โ โโโ src/
โ โโโ api/
โ โ โโโ client.js # Axios instance with auth interceptors
โ โโโ components/
โ โ โโโ admin/ # Admin-specific UI components
โ โ โ โโโ StatCard.jsx # KPI metric cards
โ โ โ โโโ DashboardHeader.jsx
โ โ โ โโโ DashboardToolbar.jsx
โ โ โโโ common/
โ โ โ โโโ ChatbotWidget.jsx # Finnora chatbot (cursor-tracking eyes, sound FX)
โ โ โ โโโ Sidebar.jsx
โ โ โ โโโ ThemeToggle.jsx # Dark/light ripple effect toggle
โ โ โโโ investor/
โ โ โ โโโ CreateTicketFlow.jsx # 7-step guided ticket creation wizard
โ โ โ โโโ MyTickets.jsx # Ticket list with filters
โ โ โ โโโ TicketDetail.jsx # Full ticket + timeline view
โ โ โ โโโ Profile.jsx # Investor profile + KYC management
โ โ โ โโโ Documents.jsx # Document upload + Cloudinary
โ โ โ โโโ NotificationsView.jsx # In-app notification center
โ โ โ โโโ ProfileCompletionModal.jsx
โ โ โโโ ui/ # shadcn/ui components + custom animations
โ โโโ config/
โ โ โโโ serviceTypes.js # Service request configuration (SLA, fields, documents)
โ โโโ context/
โ โ โโโ AuthContext.jsx # JWT auth state, user role management
โ โ โโโ ThemeContext.jsx # Dark/Light theme persistence
โ โโโ pages/
โ โ โโโ auth/
โ โ โ โโโ LoginPage.jsx
โ โ โ โโโ RegisterPage.jsx
โ โ โ โโโ ForgotPasswordPage.jsx
โ โ โโโ InvestorDashboard.jsx
โ โ โโโ L1MakerDesk.jsx # Full L1 triage dashboard
โ โ โโโ L2CheckerDesk.jsx # L2 approval dashboard
โ โ โโโ AdminDashboard.jsx # Super Admin metrics + management
โ โ โโโ LandingPage.jsx
โ โ โโโ ProfilePage.jsx
โ โโโ App.jsx # Root routing with RBAC route guards
โ
โโโ node_service/ # Node.js + Express backend
โ โโโ controllers/
โ โ โโโ auth/
โ โ โ โโโ login.controller.js # OTP-verified login
โ โ โ โโโ register.controller.js
โ โ โ โโโ session.controller.js # JWT refresh, logout, /me
โ โ โ โโโ password.controller.js # Forgot/reset/change password
โ โ โ โโโ profile.controller.js # Profile update + document upload
โ โ โ โโโ twoFactor.controller.js # TOTP + Email 2FA
โ โ โโโ l1.controller.js # Queue, assign, escalate, reject, summarize
โ โ โโโ l2.controller.js # L2 queue, finalize resolution
โ โ โโโ admin.controller.js # Metrics, users, all tickets, exports
โ โ โโโ ticket.controller.js # Create, get, comment, OCR, sentiment, resubmit
โ โ โโโ chat.controller.js # Chatbot session management + proxy
โ โ โโโ dashboard.controller.js # Investor dashboard stats
โ โ โโโ notification.controller.js
โ โโโ middleware/
โ โ โโโ auth.middleware.js # authenticate, authorize, optionalAuthenticate
โ โโโ models/
โ โ โโโ User.js
โ โ โโโ Ticket.js
โ โ โโโ AuditLog.js
โ โ โโโ Notification.js
โ โ โโโ ChatSession.js
โ โ โโโ RefreshToken.js
โ โโโ routes/
โ โ โโโ auth.routes.js
โ โ โโโ ticket.routes.js
โ โ โโโ l1.routes.js
โ โ โโโ l2.routes.js
โ โ โโโ admin.routes.js
โ โ โโโ chat.routes.js
โ โ โโโ dashboard.routes.js
โ โ โโโ notification.routes.js
โ โโโ services/
โ โ โโโ auth/
โ โ โ โโโ token.service.js # JWT + Refresh token generation + rotation
โ โ โ โโโ password.service.js # bcrypt hash + compare
โ โ โ โโโ otp.service.js # OTP generation + email delivery
โ โ โ โโโ cookie.service.js # Secure cookie setter
โ โ โ โโโ user.service.js # Public profile builder
โ โ โโโ mlService.js # ML proxy: sentiment, OCR, summarize
โ โ โโโ notificationService.js # In-app + email + SMS notifications
โ โ โโโ autoCloseService.js # Background job: RESOLVED โ CLOSED
โ โ โโโ sesService.js # AWS SES email delivery
โ โ โโโ snsService.js # AWS SNS SMS delivery
โ โ โโโ s3Service.js # AWS S3 file operations
โ โโโ server.js
โ
โโโ backend/ # Python FastAPI AI Engine
โ โโโ app/
โ โโโ main.py # FastAPI app factory, startup events
โ โโโ routes/
โ โ โโโ sentiment.py # POST /sentiment/analyze
โ โ โโโ ocr.py # POST /ocr/verify-account, /ocr/verify-kyc
โ โ โโโ chatbot.py # POST /chatbot/ask
โ โ โโโ summarizer.py # POST /summarize/ticket
โ โ โโโ health.py # GET /health
โ โโโ services/
โ โโโ sentiment_service.py # FinBERT pipeline + fraud detection
โ โโโ ocr_service.py # EasyOCR + fuzzy matching
โ โโโ rag_service.py # ChromaDB vector query
โ โโโ llm_service.py # Gemini + Ollama hybrid LLM
โ โโโ knowledge_base.py # ChromaDB FAQ seeding
โ
โโโ docker-compose.yml # GPU-accelerated full stack
โโโ docker-compose.cpu.yml # CPU-only variant
โโโ README.md
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
POST |
/register |
None | Register new investor account |
POST |
/login |
None | Initiate login (sends OTP if 2FA enabled) |
POST |
/verify-otp |
None | Verify OTP to complete login |
POST |
/refresh |
Cookie | Rotate refresh token, get new access token |
POST |
/logout |
Cookie | Revoke refresh token, clear cookies |
GET |
/me |
JWT | Get authenticated user profile |
POST |
/forgot-password |
None | Send password reset email |
POST |
/reset-password |
Token | Set new password via reset token |
POST |
/change-password |
JWT | Change password when logged in |
PUT |
/profile |
JWT | Update name, phone, address, bank, nominee, DOB |
POST |
/profile/documents |
JWT | Upload KYC documents (Cloudinary) |
DELETE |
/profile/documents/:docType |
JWT | Remove a KYC document |
POST |
/2fa/generate |
JWT | Generate TOTP secret + QR code URL |
POST |
/2fa/verify |
JWT | Verify TOTP token to enable Google Authenticator |
POST |
/2fa/email/generate |
JWT | Send OTP to email for Email 2FA setup |
POST |
/2fa/email/verify |
JWT | Verify email OTP to enable Email 2FA |
Example: POST /api/auth/register
Request Body:
{
"name": "Priya Sharma",
"email": "priya@example.com",
"password": "SecurePass123!",
"phoneNumber": "+919876543210"
}Response (201):
{
"message": "Registration successful.",
"user": {
"_id": "6683abc...",
"name": "Priya Sharma",
"email": "priya@example.com",
"role": "INVESTOR",
"profileCompleted": false
}
}| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
POST |
/ |
JWT (Investor/Admin) | Create new service request ticket |
GET |
/ |
JWT | Get tickets for logged-in user |
POST |
/sentiment |
JWT (Investor/L1/L2/Admin) | Preview sentiment analysis before submission |
GET |
/:id |
JWT | Get ticket + timeline details |
POST |
/:id/comments |
JWT | Add comment to ticket |
POST |
/:id/resubmit |
JWT | Resubmit a rejected ticket |
POST |
/:id/documents/:docId/ocr |
JWT (L1/Admin) | Run OCR verification on a document |
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
GET |
/tickets |
JWT (L1/Admin) | Get L1 queue with filters |
POST |
/tickets/:id/assign |
JWT (L1/Admin) | Assign ticket to current user |
POST |
/tickets/:id/escalate |
JWT (L1/Admin) | Escalate to L2 with notes |
POST |
/tickets/:id/reject |
JWT (L1/Admin) | Reject with reason |
POST |
/tickets/:id/summarize |
JWT (L1/Admin) | Generate AI summary via Gemini/Ollama |
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
GET |
/tickets |
JWT (L2/Admin) | Get L2 approval queue |
POST |
/finalize |
JWT (L2/Admin) | Approve (resolve) or return to L1 |
Example: POST /api/l2/finalize
Request Body:
{
"ticketId": "6683abc...",
"action": "APPROVE",
"notes": "Bank account verified. Documents are authentic. Approved."
}Response (200):
{
"message": "Ticket resolved successfully.",
"ticket": { "status": "RESOLVED", "resolvedAt": "2025-06-30T..." }
}| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
POST |
/verify-document |
JWT (L1/Admin) | Manual OCR document verification |
PUT |
/escalate/:id |
JWT (L1/Admin) | Escalate via admin panel |
PUT |
/reject/:id |
JWT (L1/Admin) | Reject via admin panel |
GET |
/metrics |
JWT (Admin) | System KPIs โ totals, trends |
GET |
/users |
JWT (Admin) | All users list |
PUT |
/users/:id/status |
JWT (Admin) | Activate / deactivate user |
GET |
/tickets |
JWT (Admin) | All tickets with filters |
GET |
/tickets/flagged |
JWT (Admin) | Fraud + CRITICAL tickets |
GET |
/reports/export |
JWT (Admin) | CSV report download |
GET |
/agents/activities |
JWT (Admin) | Audit trail for all agents |
| Method | Endpoint | Auth | Session |
|---|---|---|---|
GET |
/history |
Optional | x-session-id header |
POST |
/ask |
Optional | x-session-id header |
Note: The chatbot accepts an
x-session-idheader for anonymous guest sessions. Authenticated users are identified by their JWTuserId. This allows Finnora to be accessible on the Login and Register pages before account creation.
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/sentiment/analyze |
Analyze text for sentiment, priority, fraud |
POST |
/ocr/verify-account |
OCR + account number fuzzy match |
POST |
/ocr/verify-kyc |
OCR + name + DOB KYC match |
POST |
/summarize/ticket |
Generate 3-bullet ticket summary |
POST |
/chatbot/ask |
RAG chatbot query |
GET |
/health |
Health check |
GET |
/docs |
FastAPI Swagger UI |
sequenceDiagram
participant C as Client (Browser)
participant BE as Node.js Backend
participant DB as MongoDB Atlas
Note over C,BE: Registration
C->>BE: POST /api/auth/register
BE->>BE: bcrypt.hash(password)
BE->>DB: Save User (profileCompleted=false)
BE-->>C: 201 Created
Note over C,BE: Login (with 2FA)
C->>BE: POST /api/auth/login {email, password}
BE->>DB: Find user, compare bcrypt hash
BE->>BE: If 2FA enabled โ generate OTP
BE-->>C: 200 { requires2FA: true }
C->>BE: POST /api/auth/verify-otp { otp }
BE->>BE: Verify OTP or TOTP
BE->>DB: Create RefreshToken (hashed, 7d expiry)
BE-->>C: Set HttpOnly cookies (accessToken 15m, refreshToken 7d)
Note over C,BE: Token Refresh
C->>BE: POST /api/auth/refresh (cookie)
BE->>DB: Find RefreshToken by hash
BE->>BE: Check isRevoked, check expiry
BE->>DB: Revoke old token, create new token (same familyId)
BE-->>C: New accessToken cookie
Note over C,BE: Replay Attack
C->>BE: POST /api/auth/refresh (revoked token)
BE->>DB: Token is revoked โ ALERT
BE->>DB: Revoke ALL tokens for this user (entire family)
BE-->>C: 401 Security Alert
Every protected route passes through two middleware layers:
authenticateโ verifies the JWT access token, attachesreq.user = { id, role }authorize(...roles)โ checksreq.user.roleis in the allowed list, returns 403 if not
POST /api/l2/finalize
โ authenticate โ authorize('ADMIN_L2', 'ADMIN_SUPER') โ l2Controller.finalizeTicket
| Layer | Implementation |
|---|---|
| Passwords | bcrypt with salt rounds (never stored plaintext) |
| Access Tokens | JWT, 15-minute expiry, signed with JWT_SECRET |
| Refresh Tokens | SHA-256 hashed in DB, 7-day expiry, family-based replay detection |
| 2FA โ TOTP | speakeasy TOTP with ยฑ1 window (30s clock tolerance) |
| 2FA โ Email | bcrypt-hashed OTP stored in DB, 5-minute expiry |
| HTTP Headers | Helmet.js โ X-Frame-Options, CSP, HSTS, etc. |
| Rate Limiting | 5000 req/15min global; 10 login attempts/15min |
| CORS | Whitelist: localhost:5173, *.vercel.app, FRONTEND_URL env |
| NoSQL Injection | express-mongo-sanitize on all request bodies |
| Secrets | All secrets in .env, never committed |
| File Uploads | Multer handles in-memory, forwarded to Cloudinary (no local disk writes) |
| RBAC | Per-route role enforcement via authorize() middleware |
- Node.js 18+
- Python 3.11+
- Docker Desktop (optional, for LocalStack)
- MongoDB Atlas account (or local MongoDB)
- Cloudinary account
- Google Gemini API key
git clone https://github.com/Swayam42/finnovaX.git
cd finnovaXcd frontend
npm install
cp .env.example .env.local
# Set VITE_API_URL=http://localhost:5000/api
npm run dev
# โ http://localhost:5173cd node_service
npm install
cp .env.example .envEdit .env:
PORT=5000
NODE_ENV=development
MONGODB_URI=mongodb+srv://<user>:<pass>@cluster.mongodb.net/finnovax
JWT_SECRET=your_super_secret_key
JWT_REFRESH_SECRET=another_secret_key
FRONTEND_URL=http://localhost:5173
# Cloudinary
CLOUDINARY_URL=cloudinary://<api_key>:<api_secret>@<cloud_name>
# Email (SMTP via Gmail)
SMTP_USER=your_gmail@gmail.com
SMTP_PASS=your_gmail_app_password
# AI Engine
ML_SERVICE_URL=http://127.0.0.1:8000npm run dev
# โ http://localhost:5000cd backend
python -m venv venv
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .envEdit backend/.env:
GEMINI_API_KEY=your_gemini_api_key
# Optional: OLLAMA_URL=http://localhost:11434/api/generateuvicorn app.main:app --reload --port 8000
# โ http://localhost:8000
# โ Swagger Docs at http://localhost:8000/docsGPU Mode:
docker-compose up --build -dCPU Mode (no NVIDIA GPU):
docker-compose -f docker-compose.cpu.yml up --build -d# Creates default users (Investor, L1, L2, Admin) in MongoDB
node node_service/seed_user.jsDefault credentials after seeding:
| Role | Password | |
|---|---|---|
| Investor | investor@finnovax.com |
Test@1234 |
| L1 Maker | l1@finnovax.com |
Test@1234 |
| L2 Checker | l2@finnovax.com |
Test@1234 |
| Super Admin | admin@finnovax.com |
Test@1234 |
# In /frontend directory
npm run build
# Deploy dist/ to VercelEnvironment Variables (Vercel):
VITE_API_URL=https://your-backend.onrender.com/api
- Point to
node_service/as the root - Build command:
npm install - Start command:
node server.js
Environment Variables: All vars from .env.example + production values
- Point to
backend/as the root - Build command:
pip install -r requirements.txt - Start command:
uvicorn app.main:app --host 0.0.0.0 --port 8000 - Set
GEMINI_API_KEYenvironment variable
- Create a free M0 cluster
- Whitelist your deployment IPs
- Create a DB user with read/write access
- Copy the connection URI to
MONGODB_URI
- Create a free Cloudinary account
- Copy
CLOUDINARY_URLfrom dashboard
-
NODE_ENV=productionis set -
JWT_SECRETis a cryptographically random 64+ character string -
MONGODB_URIpoints to Atlas production cluster - CORS
FRONTEND_URLmatches your Vercel deployment URL -
GEMINI_API_KEYis valid and has quota -
SMTP_USERandSMTP_PASSare valid for email delivery - Cloudinary credentials are set
A glassmorphism-styled marketing page with animated marquees, feature highlights, and CTAs for registration. The Finnora chatbot widget is visible here โ guests can ask questions before creating an account.
- Investor fills in name, email, password, and phone number
- Account is created with
profileCompleted: falseandrole: 'INVESTOR' - On first login, a
ProfileCompletionModalis displayed blocking navigation until the investor completes their KYC, bank, and nominee details
- Two-step: credentials check โ OTP or TOTP verification
- JWT access token (15m) stored in
HttpOnlycookie - Refresh token (7d) stored in
HttpOnlycookie AuthContexton the frontend hydrates user state from/api/auth/me
A SidebarProvider layout with four views: My Tickets, Create Ticket, Profile & KYC, and Documents. The DotBackground animated pattern provides visual depth.
CreateTicketFlow.jsx โ a multi-step wizard:
- Step 1: Select service type (7 options, each with SLA and document requirements)
- Step 2: Fill in service-specific form fields (validated per service type)
- Step 3: Write description and title
- Step 4: Upload required documents
- Step 5: Live sentiment preview (calls
/api/tickets/sentimentbefore submitting) - Step 6: Confirm and submit
When the ticket is submitted via POST /api/tickets:
- Backend calls
mlService.analyzeSentiment(description)โ FinBERT assigns sentiment + priority - If documents uploaded,
mlService.verifyAccount(doc, accountNumber)โ EasyOCR validates - If fraud keywords detected (
hacked,stolen, etc.) โisPotentialFraud: true+CRITICAL - Notification sent to investor with styled HTML email
L1 agents see a paginated, filterable queue of OPEN, IN_PROGRESS, and L1_REVIEW tickets.
- Click a ticket โ see sentiment score, fraud flag, OCR result, documents
- Click "Get AI Summary" โ backend calls Gemini/Ollama, renders 3 bullets
- Assign to me โ
status: IN_PROGRESS - Escalate โ
status: L2_APPROVAL, every action logged inAuditLog - Reject โ
status: REJECTED, investor receives rejection email with reason
Exclusively shows L2_APPROVAL tickets. The L2 checker cannot modify tickets โ only:
- Approve (Finalize) โ
status: RESOLVED,resolvedAtset, investor receives resolution email - Return to L1 โ
status: IN_PROGRESS,l2ReturnNotestored, L1 notified
A background setInterval running every 60 seconds checks for RESOLVED tickets older than the grace period. When found, it atomically updates status to CLOSED and creates an AuditLog entry with action: 'SYSTEM_AUTO_CLOSED'.
Tabbed interface with:
- Overview โ KPI StatCards (open, resolved, critical, fraud)
- Tickets โ filterable all-ticket view with flagged view
- Users โ user management, activate/deactivate
- Reports โ CSV export
- Agent Activity โ chronological audit trail for all L1/L2 actions
- Floating widget accessible globally (all pages including Login/Register)
- Interactive SVG eyes track cursor movement, blink randomly
- Eyes close when a password field is focused anywhere on the page
- When opened, loads chat history via
GET /api/chat/history(usesx-session-idfor guests) - Messages rendered in WhatsApp-style bubbles with SVG tails
- Sound effects (Web Audio API) on send and receive
- Suggested questions displayed for new sessions
Actor: Investor Priya wants to update her bank account.
- Logs into FinnovaX portal
- Creates a ticket: Bank Account Update
- Fills in new bank details: HDFC Bank, account number
123456789, IFSCHDFC0001234 - Uploads: Cancelled Cheque image + Bank Passbook scan
- System runs OCR on the cheque โ extracts account number
123456789โ match verified - Ticket submitted (OPEN). Priya receives confirmation email.
- L1 Maker Rakesh assigns the ticket to himself
- Requests AI summary โ Gemini returns: "Investor requests bank update to HDFC. Documents provided. OCR match confirmed. Standard review required."
- Escalates to L2 with note: "Documents verified via OCR. Recommend approval."
- L2 Checker Anjali reviews and clicks Approve
- Priya receives email: "Good News! Your ticket has been resolved. ๐"
SLA Commitment: 10 business days
Actor: Investor submits a complaint with the description:
"Someone hacked into my account and I think my funds were stolen. Please help urgently. I will contact the police."
- Backend calls FinBERT โ
NEGATIVEsentiment, score0.94 - Keyword scan โ
hacked,stolendetected โisPotentialFraud: true - Priority overridden to
CRITICALregardless of model score - Ticket appears at the top of L1 queue with a red
โ ๏ธ FRAUD ALERT badge - L1 agent is immediately notified and can escalate with urgency
- Investor submits KYC Update without uploading a clear front scan
- L1 Maker rejects with note: "KYC document is blurry. Please resubmit with a clear scan."
- Investor receives rejection email with the rejection reason in a styled red block
- Investor logs in, views the rejected ticket, and clicks Resubmit
- Uploads a new, clear document
- Ticket re-enters the L1 queue with status
L1_REVIEW
Actor: Anjali is considering opening an investment account and visits the portal.
- Lands on the homepage โ sees Finnora chatbot widget in the corner (eyes watching cursor)
- Clicks the widget โ gets a welcome message
- Clicks suggested question: "What is the SLA for a KYC update?"
- Finnora queries ChromaDB โ finds relevant FAQ โ constructs answer via Gemini
- Responds: "KYC update requests have an SLA of 15 business days from the date of submission..."
- Anjali is confident and registers
| Feature | Description |
|---|---|
| Email OTP Login | Remove TOTP dependency, use magic link for mobile-first experience |
| Real-time Notifications | WebSocket or SSE push for live ticket status updates |
| SLA Breach Alerts | Cron job to email agents when SLA deadline is approaching |
| Mobile App | React Native companion app for investors |
| Fraud Score Dashboard | Visual analytics on fraud patterns by region/service type |
| Bulk Document Processing | OCR batch processing for high-volume periods |
| Agent Performance Analytics | Turnaround time metrics per L1/L2 agent |
| Multi-tenancy | Support multiple financial institutions on the same platform |
| Localization | Hindi, Tamil, and regional language support |
| WhatsApp Integration | Ticket status updates via WhatsApp Business API |
We welcome contributions! Please follow these guidelines:
main โ Production-ready code only
develop โ Integration branch
feature/xyz โ New features
fix/xyz โ Bug fixes
chore/xyz โ Tooling, deps, config
feat: Add OCR fuzzy matching for KYC documents
fix: Resolve 403 on L1 sentiment endpoint
chore: Update Gemini SDK to 0.2.0
docs: Add API documentation for chatbot routes
refactor: Extract token rotation to separate service
- Fork the repository
- Create a branch from
develop - Make your changes with tests
- Ensure no secrets are committed
- Open a PR to
developwith a clear description
- Node.js: Use
async/await, never.then()chains in controllers - React: Functional components only,
useCallbackfor expensive handlers - Python: Type annotations on all function signatures
- Commit your
.env.examplechanges when adding new environment variables
This project is licensed under the MIT License โ see the LICENSE file for details.