PPalladium AI
HomeFeaturesPricingDevelopersBlog
PPalladium AI
Docs
Sign InSign Up
Back to Blog
ProductMay 31, 20266 min read

Inside the Loan Management Module: From Application to Approval

A detailed walkthrough of how Palladium AI handles the full loan lifecycle — application submission, review queues, status transitions, role-based access, and the data model that makes it all real-time.


The problem with loan management in cooperatives

In most member-led finance organisations, loan requests still arrive on paper forms or through informal WhatsApp threads. A credit officer manually transcribes the request into a spreadsheet, another person approves it verbally, and the record only makes it into a ledger after disbursement — if at all. There is no single view of the pipeline, no audit trail of who approved what, and no easy way for a member to check the status of their own application.

The Palladium AI loan management module was designed to close that gap. It gives members a self-service channel to apply, gives credit officers a live review queue, and gives administrators a full audit trail — all within the same multi-tenant workspace.

Core concepts

Loan record

Every loan application is stored as a document in the loans table. Each record captures the applicant's name, the requested amount, the team it belongs to, a source identifier for idempotent imports, and a status field that drives the workflow.

Loan status lifecycle

A loan moves through three explicit states:

pending— Submitted, awaiting review
approved— Approved by a team admin
rejected— Declined by a team admin

All state transitions are persisted immediately via a Convex mutation and reflected in the UI without a page reload.

Team scoping

Every loan is attached to a specific team. Queries are always filtered by teamId, so members of one cooperative can never see loan records belonging to another. This isolation is enforced at the backend query layer, not just in the UI.

Role-based access

The module exposes different capabilities depending on the authenticated user's role within the team:

RoleApply for a loanView all loansApprove / Reject
memberYesYes (team scope)No
adminYesYes (team scope)Yes
platform_ownerYesYes (all teams)Yes

These checks are duplicated in both the UI (to hide or show the approve/reject controls) and in the Convex mutation handlers (to enforce them server-side regardless of what the client sends).

Applying for a loan

Any authenticated team member can open the loan application dialog from the credit desk page. The form captures a single input: the requested loan amount. On submission the frontend calls the loans.apply mutation which:

  1. Verifies the caller is authenticated.
  2. Confirms team membership (platform owners bypass this check so they can test from the admin console without joining every team).
  3. Validates that the amount is a finite positive number.
  4. Resolves the applicant's display name from their user record so the credit officer sees a real name rather than an opaque user ID.
  5. Inserts the loan with status pending and timestamps for both createdAt and updatedAt.

Because Convex queries are reactive, the new application appears in every connected browser session on the team instantly — no polling required.

The credit officer review queue

The credit desk page (/dashboard/[teamId]/loans) renders a live table of all applications for the team, ordered newest first. Three summary cards at the top give a quick snapshot:

  • Total applications — the full count of records in the team.
  • Pending review — applications still waiting for a decision.
  • Approved — loans that have been greenlit.

Each row in the table shows the applicant name, the requested amount, a colour-coded status badge, and — for admin users — inline approve and reject action buttons. Clicking either button fires the loans.updateStatus mutation and the badge updates immediately in the UI.

Data model in detail

The loans table schema exposes every field that drives the module:

FieldTypePurpose
teamIdstring (team ID)Scopes the record to one workspace
sourceIdstringUnique key for idempotent legacy imports
firstName / lastNamestringResolved from the user's display name
loanAmountnumberRequested principal in local currency units
statusstring (optional)pending | approved | rejected
createdAtnumberUnix ms timestamp of application
updatedAtnumberUnix ms timestamp of last status change

Two indexes are defined: by_team for efficient per-team list queries and by_source_id to support the legacy data migration pipeline which upserts records from the previous Supabase-backed system without creating duplicates.

Legacy data migration

For cooperatives migrating from a previous system, the module includes a protected loans.upsertFromLegacy mutation. It accepts an array of loan rows and a migration token, then inserts or updates each record using sourceId as the idempotency key. Running the migration script multiple times is safe — already-migrated records are patched in place rather than duplicated.

What's coming next

The current module covers the core apply-review-decide loop. The next iteration will layer in:

  • Repayment schedules — structured instalment plans linked to each approved loan, with payment recording and balance tracking.
  • Risk scoring — an AI-assisted model that surfaces a repayment probability score alongside each application to support credit officers.
  • Disbursement records — a confirmation step after approval that logs the actual disbursement date, channel, and reference.
  • Notifications — in-app and email alerts to applicants when their loan status changes.
  • Reporting exports — downloadable CSV and PDF summaries formatted to match regulator templates used by SACCOs in East Africa.

All articlesGet started free

Built by Palladium AI. Multi-tenant finance infrastructure with open foundations on GitHub.