Skip to content

Repository files navigation

Sahaay LOS — Open Source Loan Origination System

Contact: mail2ganesh.cse@gmail.com

GitHub: mail2ganeshcse/LOS-System-Opensource

Sahaay LOS is an India-first, open-source Loan Origination System (LOS) starter for loan officers, processors, underwriters, compliance teams, closers, and operations administrators. It provides a modern React and FastAPI foundation for digital lending workflows, customer onboarding, application processing, document management, underwriting, compliance, notifications, and AI-assisted review.

The current release provides a polished, responsive React workspace and a typed FastAPI service boundary. It uses deterministic demonstration data so the entire product can be explored without external credentials or a running database.

About Sahaay LOS

Sahaay LOS is designed as a modular, self-hostable LOS system for banks, NBFCs, fintech teams, and lending technology developers. The repository demonstrates an end-to-end loan origination workflow with independently structured APIs, optional third-party integrations, database migration guidance, user management, S3-compatible document storage, and configurable AI models. It can also serve as a reference implementation for teams evaluating open-source digital lending and underwriting software.

Important: This repository is an application starter, not legal, compliance, underwriting, or lending advice. RBI/NHB requirements, policy rules, disclosures, and integrations must be reviewed by qualified Indian lending and compliance professionals before production use.

Features

Authentication

  • Responsive team login page
  • Email and password validation
  • Password visibility control
  • Remember-device selection
  • Searchable user directory with editable profiles, roles, branches, status, and authentication ownership
  • Working sidebar My Profile entry with session-safe profile updates when the API is offline
  • Dedicated Administrator dashboard with user search, role/status/authentication filters, access-governance queues, invitations, profile editing, suspension/reactivation, and recovery controls
  • Invitation flow for LOS-managed and SSO-managed accounts
  • Single-use, expiring reset links for LOS-managed accounts
  • Provider-specific recovery notifications for Microsoft Entra ID, Google Workspace, and configured Okta accounts
  • Email-first recovery with an independently configured SMS secondary channel
  • Non-enumerating public forgot-password response and server-only SMTP/SMS credentials
  • SSO entry point
  • Pre-filled demonstration credentials

Operations workspace

  • Portfolio overview with pipeline, approval, SLA, and turn-time metrics
  • Application search, stage filters, risk indicators, and pagination
  • Interactive application workspace with Summary, Intelligence, Documents, and Activity tabs
  • Optional live OpenAI assessment with a backend-only secret and environment-controlled model
  • Live/demo AI status, schema-validated output, and deterministic fallback when AI is disabled
  • AI-assisted sanction likelihood, file readiness, and predicted turn-time indicators
  • Explainable decision factors, anomaly alerts, generated conditions, and next-best-action guidance
  • Actionable My Tasks and team queues with create, start, reassign, complete, reopen, owner, priority, due-time, and linked-application controls
  • Working document viewer with review status, preview, and demo download
  • Optional private S3 uploads and downloads using short-lived signed requests
  • Document inbox with classification and confidence indicators
  • Named three-stage review workflow: loan officer condition review, underwriting decision, and compliance confirmation
  • Clear in-app notifications after customer, task, document, review, profile, and recovery actions
  • Borrower and co-borrower customer directory with working add-customer, profile, follow-up, messaging, and application-start actions
  • RBI/NHB-oriented compliance control dashboard
  • Portfolio analytics and product-mix visualisation
  • Team workload and SLA performance directory
  • Administrator user-management and access-governance dashboard
  • Responsive desktop, tablet, and mobile navigation

API starter

  • FastAPI application with generated OpenAPI documentation
  • Validated Pydantic request and response schemas
  • Application list, search, and detail endpoints
  • Portfolio summary endpoint
  • Audit-event ingestion boundary
  • Safe AI status and application-assessment endpoints
  • OpenAI Responses API adapter with strict structured output and minimized input
  • S3 storage status, signed-upload, and scoped signed-download endpoints
  • User directory, profile update, invitation, and administrative password-reset endpoints
  • Public password-reset request and confirmation endpoints
  • Async PostgreSQL engine configuration
  • Local PostgreSQL service through Docker Compose

Technology stack

Layer Technology
Frontend React, TypeScript, Vite
UI icons Lucide React
Backend FastAPI, Python, Pydantic
Database boundary PostgreSQL, SQLAlchemy asyncio, asyncpg
Private documents Amazon S3, Boto3, presigned POST/GET
Recovery notifications SMTP email plus optional generic HTTP SMS gateway
Local infrastructure Docker Compose

Architecture

Browser
  └── React + TypeScript workspace
        ├── Login and session demo
        ├── Operations modules
        ├── Document review and upload UI
        ├── Deterministic UI fixtures
        └── Optional live application intelligence

FastAPI service
  ├── /health
  ├── /api/v1/applications
  ├── /api/v1/portfolio/summary
  ├── /api/v1/audit-events
  ├── /api/v1/migration-manifests/validate
  ├── /api/v1/ai/status
  ├── /api/v1/ai/application-assessments
  ├── /api/v1/documents/storage/status
  ├── /api/v1/documents/uploads
  ├── /api/v1/documents/downloads
  ├── /api/v1/users
  ├── /api/v1/users/{user_id}/password-reset
  ├── /api/v1/auth/password-reset-requests
  └── /api/v1/auth/password-reset-confirmations
        ├── PostgreSQL boundary
        ├── Optional private S3 bucket
        ├── Optional SMTP email and HTTP SMS delivery
        ├── SSO-provider recovery routing
        └── Optional OpenAI Responses API adapter

The frontend uses local demonstration records for its primary workflow. Application Intelligence can optionally call the backend AI adapter; all model credentials and provider configuration remain server-side.

Backend API modularity

Every backend router is named for one use case and can be mounted without importing another API router. backend/app/api/v1/router.py only composes the independent modules:

applications.py          application search and detail
audit.py                 audit-event ingestion
portfolio.py             portfolio summary
migration_manifests.py   legacy-mapping validation
ai.py                    application intelligence
documents.py             private document storage
user_management.py       directory, profiles, roles, status, invitations
password_recovery.py     local resets and SSO-provider recovery notifications

User management and password recovery share only the UserRepository persistence contract; neither router nor service imports the other. Standalone router tests ensure every module starts and responds independently while the composed application preserves the existing /api/v1 URLs.

Project structure

sahaay-los/
├── backend/
│   ├── alembic/              # Versioned PostgreSQL schema migrations
│   ├── app/
│   │   ├── api/v1/           # Independently mountable use-case routers
│   │   ├── core/             # Environment configuration
│   │   ├── db/               # Metadata, engine and transactions
│   │   ├── integrations/     # External provider adapters: AI, S3, email, and SMS
│   │   ├── legacy_migration/ # Manifest validation and migration planning
│   │   ├── models/           # SQLAlchemy persistence models
│   │   ├── repositories/     # Shared persistence contracts; no HTTP imports
│   │   ├── schemas/          # Pydantic contracts separated by use case
│   │   ├── services/         # Independent use-case orchestration
│   │   └── main.py           # FastAPI application factory
│   ├── tests/
│   ├── alembic.ini
│   ├── requirements.txt
│   └── requirements-dev.txt
├── docs/
│   ├── architecture/         # Backend boundaries and folder rules
│   ├── integrations/         # Third-party provider onboarding
│   ├── migrations/           # Schema and legacy migration runbooks
│   └── sdlc/                 # API/database delivery lifecycle
├── src/
│   ├── App.tsx               # Login, shell, dashboard, and loan drawer
│   ├── api.ts                # Typed frontend API client
│   ├── documents.ts          # Typed demonstration document records
│   ├── operations.ts         # Typed customer and task workflow records
│   ├── users.ts              # Typed demonstration users and auth ownership
│   ├── WorkspacePages.tsx    # Sidebar module pages
│   ├── data.ts               # Typed demonstration loan records
│   ├── main.tsx              # React entry point
│   └── styles.css            # Responsive design system
├── docker-compose.yml        # Local PostgreSQL service
├── index.html
├── package.json
├── tsconfig.json
└── README.md

Prerequisites

  • Node.js 20 or newer
  • npm 10 or newer
  • Python 3.11 or newer
  • Docker Desktop or another Docker Compose-compatible runtime, if using PostgreSQL

PostgreSQL is optional for the current demonstration because the API returns fixture records until a persistence repository is added.

Quick start

1. Install frontend dependencies

npm install

2. Start the frontend

npm run dev

Open http://localhost:5173.

Use the pre-filled credentials displayed on the login page:

Email:    priya.sharma@sahaay.in
Password: Sahaay@2026

These credentials are for the local UI demonstration only. They are not sent to or verified by the FastAPI service.

3. Start the API

In another terminal:

python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
uvicorn backend.app.main:app --reload --port 8000

Windows PowerShell activation:

.venv\Scripts\Activate.ps1

Available locally:

4. Enable live AI (optional)

The application works in deterministic demo mode without a model key. To activate live application assessments, copy the environment template, inject a sandbox key into the ignored .env, and explicitly enable the feature:

AI_ENABLED=true
AI_PROVIDER=openai
AI_MODEL=gpt-5.6-luna
OPENAI_API_KEY=your-sandbox-key

Restart FastAPI, then verify GET /api/v1/ai/status. Keep the key on the backend—never create a VITE_ secret. See AI model integration and activation for the complete setup, security controls, API example, and model-switch process.

5. Configure private S3 document storage (optional)

Document preview and review work in demonstration mode without S3. To enable real browser-to-S3 uploads and signed downloads, create an ignored .env file and configure:

DOCUMENT_STORAGE_ENABLED=true
S3_BUCKET_NAME=your-private-los-document-bucket
AWS_REGION=ap-south-1
S3_KEY_PREFIX=los-documents
S3_PRESIGNED_URL_EXPIRY_SECONDS=900
S3_MAX_UPLOAD_BYTES=20971520

Credentials are backend-only and have two supported modes:

Environment Recommended configuration
ECS, EKS, or EC2 production workload Attach a least-privilege IAM role and leave AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN empty. Boto3 uses its default credential provider chain.
Local sandbox Set a scoped, temporary AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and, when issued, AWS_SESSION_TOKEN in the ignored .env.
Approved S3-compatible sandbox Additionally set S3_ENDPOINT_URL; leave it empty for AWS S3.

Never place AWS credentials in VITE_ variables, React code, source control, API requests, screenshots, or documentation. Never use an AWS account root access key.

The workload needs only object access under the configured prefix. Replace the bucket name in this example IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LOSPrivateDocumentObjects",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::your-private-los-document-bucket/los-documents/applications/*"
    }
  ]
}

Keep Block Public Access enabled. The bucket must allow the frontend origin to submit the signed POST and follow signed GET links. Example development CORS configuration:

[
  {
    "AllowedOrigins": ["http://localhost:5173"],
    "AllowedMethods": ["GET", "POST"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 300
  }
]

Use only the deployed HTTPS frontend origin in production. Restart FastAPI and verify safe configuration metadata:

curl http://localhost:8000/api/v1/documents/storage/status

The response reports the region, credential mode, size limit, and whether the bucket is configured. It never returns the bucket name, access key, secret key, session token, or signed URLs from previous requests.

Implementation references: Boto3 presigned URL guide, AWS credential provider chain, and AWS access-key configuration.

6. Configure password-recovery notifications (optional)

Profile management and simulated reset flows work without external notification credentials. Configure SMTP as the primary channel in the ignored .env file:

APP_ENVIRONMENT=production
APP_PUBLIC_URL=https://los.your-company.example
EMAIL_DELIVERY_ENABLED=true
SMTP_HOST=smtp.your-company.example
SMTP_PORT=587
SMTP_USERNAME=los-mailer
SMTP_PASSWORD=inject-from-your-secret-manager
SMTP_FROM_EMAIL=no-reply@your-company.example
SMTP_USE_TLS=true
PASSWORD_RESET_EXPIRY_MINUTES=30

Enable SMS as an independent secondary channel:

SMS_DELIVERY_ENABLED=true
SMS_PROVIDER=generic_http
SMS_BASE_URL=https://sms-gateway.example.com/v1/messages
SMS_API_KEY=inject-from-your-secret-manager
SMS_SENDER_ID=LOSAPP
SMS_REQUEST_TIMEOUT_SECONDS=15
SMS_DLT_ENTITY_ID=registered-entity-id
SMS_DLT_TEMPLATE_ID=registered-template-id

The generic adapter sends a server-side authenticated JSON request containing recipient, message, sender_id, and the optional DLT identifiers. Adapt http.py if the selected gateway uses different field names or authentication. Registered mobile numbers must be stored in E.164 form, for example +919876543210; the masked demonstration values cannot be sent to a live gateway.

Set the identity-team-approved recovery location for each SSO provider:

ENTRA_PASSWORD_RESET_URL=https://passwordreset.microsoftonline.com/
GOOGLE_PASSWORD_RESET_URL=https://accounts.google.com/signin/recovery
OKTA_PASSWORD_RESET_URL=https://your-tenant.okta.com/signin/forgot-password
Account type Reset behavior
LOS-managed (auth_method=local) Generates one cryptographically random, single-use LOS link and sends it by email plus SMS when SMS is enabled.
SSO-managed (auth_method=sso) Creates no LOS token. Email and optional SMS point to the configured Entra ID, Google Workspace, or Okta recovery URL.
Unknown public email Returns the same generic response as a known account and sends nothing.

The channels are independent: one failure is returned as partial while the successful channel remains valid; a reset token is revoked if every channel fails. When email is simulated, the administrator profile action returns a development preview link. The public endpoint never returns a token or account-existence information. Restart the API after configuration changes.

See Identity, user management, email, and SMS recovery for setup, provider switching, endpoint examples, security controls, and production persistence requirements.

7. Start PostgreSQL (optional)

docker compose up -d postgres
docker compose ps

Default local connection:

postgresql+asyncpg://sahaay:sahaay@localhost:5432/sahaay

Override it with an environment variable when needed:

export DATABASE_URL='postgresql+asyncpg://user:password@host:5432/database'

Stop the database with:

docker compose down

Add -v only when you intentionally want to delete the local database volume.

Available commands

Command Purpose
npm run dev Start the Vite development server
npm run build Type-check and create a production frontend build
npm test Run interaction tests for navigation, customers, tasks, staged reviews, documents, storage, profiles, and recovery
npm run preview Preview the production build locally
uvicorn backend.app.main:app --reload --port 8000 Start the API in development mode
docker compose up -d postgres Start local PostgreSQL
alembic -c backend/alembic.ini upgrade head Apply LOS schema migrations
python -m pytest backend/tests Run backend API, standalone-router, integration, and migration tests

API reference

Method Endpoint Description
GET /health Service health check
GET /api/v1/applications List applications; accepts an optional search query
GET /api/v1/applications/{application_id} Fetch one application
GET /api/v1/portfolio/summary Return portfolio-level metrics
POST /api/v1/audit-events Validate and accept an audit event
POST /api/v1/migration-manifests/validate Safely validate a legacy mapping manifest
GET /api/v1/ai/status Return safe AI enablement metadata without secrets
POST /api/v1/ai/application-assessments Generate a schema-validated advisory application assessment
GET /api/v1/documents/storage/status Return safe S3 enablement metadata without credentials or bucket name
POST /api/v1/documents/uploads Create a short-lived, size/type-restricted presigned S3 POST
POST /api/v1/documents/downloads Create a short-lived signed GET scoped to one application's object prefix
GET /api/v1/users Search or list user profiles and authentication ownership
POST /api/v1/users Create an invited LOS-managed or SSO-managed user
GET /api/v1/users/me Return the current demonstration profile
PATCH /api/v1/users/me Update the current demonstration profile
GET /api/v1/users/{user_id} Return one user profile
PATCH /api/v1/users/{user_id} Update profile, role, branch, or status
POST /api/v1/users/{user_id}/password-reset Send local or SSO recovery through email and optional SMS
GET /api/v1/auth/password-recovery/status Return safe email/SMS channel configuration without credentials
POST /api/v1/auth/password-reset-requests Accept forgot-password without revealing account existence
POST /api/v1/auth/password-reset-confirmations Consume a local single-use token and set a strong password hash

Example requests:

curl http://localhost:8000/health
curl 'http://localhost:8000/api/v1/applications?search=Aarav'
curl http://localhost:8000/api/v1/applications/HL-2026-0842

Example audit event:

curl -X POST http://localhost:8000/api/v1/audit-events \
  -H 'Content-Type: application/json' \
  -d '{
    "actor": "priya.sharma@sahaay.in",
    "action": "underwriting_review_started",
    "entity_id": "HL-2026-0842",
    "metadata": {"source": "operations_workspace"}
  }'

Create an upload ticket:

curl -X POST http://localhost:8000/api/v1/documents/uploads \
  -H 'Content-Type: application/json' \
  -d '{
    "application_id": "HL-2026-0842",
    "file_name": "salary-statement.pdf",
    "content_type": "application/pdf",
    "size_bytes": 482113
  }'

The response contains upload_url, form_fields, object_key, and expiration metadata. Submit every returned field plus the file as multipart/form-data directly to upload_url. The application UI performs these two steps automatically. The signed POST enforces content type, AES-256 server-side encryption, the configured maximum size, a generated object key, and a short expiration.

Create a download ticket only for an object key previously recorded against the same application:

curl -X POST http://localhost:8000/api/v1/documents/downloads \
  -H 'Content-Type: application/json' \
  -d '{
    "application_id": "HL-2026-0842",
    "object_key": "los-documents/applications/HL-2026-0842/generated-id/salary-statement.pdf",
    "download_name": "salary-statement.pdf"
  }'

Signed URLs are bearer capabilities until they expire. Return them only to an authenticated and authorised user, do not log them, and keep the expiry short. Before production, persist document metadata and application ownership, validate antivirus/content inspection results, audit access, enforce retention/legal holds, and require server-side authorisation before issuing any ticket.

Request a reset without revealing whether the account exists:

curl -X POST http://localhost:8000/api/v1/auth/password-reset-requests \
  -H 'Content-Type: application/json' \
  -d '{"email":"priya.sharma@sahaay.in"}'

Administrators can select a profile in Team → User management and request recovery notifications. LOS-managed accounts receive one expiring application link; SSO-managed accounts receive their configured identity-provider recovery link. The response reports masked email/SMS destinations and per-channel status.

Third-party integrations

Integration credentials, provider selection, adapter contracts, consent requirements, and onboarding steps are documented separately:

The environment template contains placeholders only. Never commit real API keys, client secrets, passwords, private keys, bureau credentials, or Aadhaar-related licence material.

Architecture, migrations, and SDLC

Schema evolution and legacy-data movement are deliberately separate. Alembic owns the new PostgreSQL schema; the legacy migration process uses read-only sources, versioned mapping manifests, dry runs, checkpointed loads, reconciliation, and controlled cutover.

Validation

Build and type-check the frontend:

npm test
npm run build

The frontend interaction suite verifies every sidebar module, the explicit Review application action, document opening/review, the disabled S3 uploader state, local profile editing/reset, SSO recovery routing, and the generic forgot-password response.

Run the backend test suite:

python -m pip install -r backend/requirements-dev.txt
python -m pytest backend/tests

Validate the Alembic chain and example legacy mapping:

alembic -c backend/alembic.ini upgrade head --sql > /tmp/sahaay-schema.sql
python -m backend.app.legacy_migration.cli validate \
  --manifest docs/migrations/examples/legacy-manifest.example.json

Demonstration behaviour

  • The login form accepts any syntactically valid email with a non-empty password.
  • Team user profiles, password hashes, and reset tokens are in-memory demonstration records; they are lost when the API restarts.
  • Profile editing, invitation creation, local reset confirmation, and SSO recovery routing call working API endpoints when the API is running.
  • Disabled SMTP is simulated only in development and skipped in production; SMS is skipped until independently enabled. The administrator UI shows each channel status and exposes local preview links only in development.
  • The demonstration login does not yet verify the password hash changed by the reset-confirmation API.
  • Sidebar navigation is client-side state, not URL routing.
  • Searches, filters, task completion, loan drawers, and notification feedback run locally.
  • Application review buttons open the selected application workspace directly.
  • Document rows open a working preview/review dialog; demonstration documents download as clearly labelled sample files.
  • When S3 is enabled, the upload dialog obtains a signed ticket and sends the file directly to the private bucket without exposing AWS credentials.
  • Refreshing the browser resets the demonstration session and local UI changes.
  • SSO sign-in itself and exports remain demonstration actions until their external providers are connected.

Production requirements

The following work is intentionally outside the demonstration scope and should be completed before deployment.

Identity and access

  • Replace demonstration authentication with OIDC/OAuth2 or an approved enterprise identity provider.
  • Enforce server-side sessions, MFA, role-based access control, and branch/entity scoping.
  • Persist users and hashed passwords in the database; move reset-token digests to a durable TTL store and revoke older tokens on each request.
  • Protect all /users administration endpoints with explicit administrator permissions and immutable audit events.
  • Add rate limiting, abuse detection, email/SMS delivery monitoring, token revocation, and session invalidation after local password changes.
  • Keep the SSO password lifecycle with the identity provider and validate tenant-approved recovery URLs before deployment.
  • Add PostgreSQL row-level security where multi-entity isolation is required.

Data and auditability

  • Connect the existing SQLAlchemy models and migrations to production repositories, transactions, backups, and restore drills.
  • Store audit events in an append-only or WORM-backed system.
  • Record model version, input fingerprint, explanation, recommendation, reviewer, and override reason for every AI-assisted decision.
  • Encrypt sensitive data in transit and at rest and manage keys through a cloud KMS.

India-specific integrations

  • CKYC registry
  • PAN verification through an authorised provider
  • Aadhaar e-KYC through an appropriately licensed KUA/sub-KUA workflow
  • CIBIL, Experian, Equifax, and CRIF India credit bureau adapters
  • RBI Account Aggregator consent and financial-information workflows
  • Licensed e-sign/e-stamp providers
  • India-resident OCR and document-processing infrastructure
  • Email, SMS, WhatsApp, and push notification providers

Each adapter should implement consent capture, idempotency, retries, timeouts, error mapping, reconciliation, and immutable audit logging.

AI governance

  • AI recommendations must remain advisory until approved under the organisation's model-risk framework.
  • Lending decisions require deterministic policy controls and authorised human review.
  • Validate training data provenance, bias testing, explainability, drift monitoring, and retraining approval.
  • Never send borrower PII to an AI or OCR provider without contractual, security, consent, and data-residency approval.

Compliance

Validate current RBI directions, KYC requirements, Fair Practices Code obligations, Key Facts Statement formats, CIC reporting, floating-rate disclosures, grievance handling, and data-residency controls with legal counsel. Regulations and circulars change over time; do not treat repository text or demonstration labels as a final compliance checklist.

Suggested delivery roadmap

  1. Foundation: authentication, tenant model, RBAC, migrations, repositories, and immutable audit events.
  2. Origination: borrower application, co-borrowers, task workflow, document storage, and communications.
  3. Verification: KYC, bureau, account aggregator, appraisal, and property/legal integrations.
  4. Decisioning: versioned rules engine, pricing, exception workflow, and manual overrides.
  5. Closing: disclosures, e-sign, funding checklists, post-closing QC, and reporting.
  6. Intelligence: governed OCR, anomaly detection, explainable scoring, copilot, and predictive operations.

Troubleshooting

Port already in use

Start Vite or FastAPI on another port:

npm run dev -- --port 5174
uvicorn backend.app.main:app --reload --port 8001

Frontend changes are not visible

Stop and restart the Vite server, then perform a hard browser refresh.

PostgreSQL connection fails

Check the container and health state:

docker compose ps
docker compose logs postgres

Python import errors

Confirm the virtual environment is active and reinstall dependencies:

pip install -r backend/requirements.txt

Status

This repository is a functional product prototype and architecture starter. It is suitable for demonstrations, workflow validation, interface testing, and continued in-house development. It is not ready for handling real borrower data or making production lending decisions without the controls described above.

About

Sahaay LOS: open-source Loan Origination System starter with React, FastAPI, AI-assisted underwriting, KYC, tasks, documents, S3, and PostgreSQL.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages