Independent engineering project / Loan Origination & Credit Workflow

Loan Origination & Credit Workflow

A versioned loan-decision workflow combining declarative rules, explainable scorecards and simulated external checks. Decision traces and what-if runs make underwriting changes reviewable.

Contribution
Independent design, implementation and evaluation
Languages
C#
Engineering focus
Decision traces / Ruleset what-if simulation / Amortisation and APR/IRR maths / Affordability stress testing

Scope. Self-directed reference implementation with fictional data and local infrastructure; not client work, not a production deployment, and not evidence of compliance certification.

Implementation & evaluation

The project README is reproduced below, with links to the implementation, design records and operating instructions. Scenario narratives describe an independent project, not a customer deployment.

Read on GitHub

Rift Valley Credit Loan Origination & Credit Workflow Platform

Portfolio Classification

Self-directed engineering case study. This is a fictional, synthetic-data reference implementation for Rift Valley Credit Ltd (fictional), not client work and not a live lending system.

Executive Summary

A modular .NET 10 loan-origination platform for SME and personal products in KES and USD. Its central feature is a deterministic, versioned rules engine that returns an auditable decision trace instead of an opaque score. The platform models application workflow, document checks, KYC and bureau simulators, affordability, human underwriting, offers, and idempotent disbursement simulation.

Business Problem

Credit operations must explain why an application was declined, referred, or offered years after the event. Static code-only policy logic and undocumented manual overrides make that difficult. This implementation binds every application to immutable product and ruleset versions, records inputs and rule results, and preserves the decision record alongside the workflow history.

Functional Requirements

  • Individual and SME customers, synthetic identity/contact data, income, obligations, dependants, KYC state, and fuzzy duplicate detection.
  • Versioned product catalogue with principal/term limits, rate method, fees, currency, documents, collateral, and bound eligibility rulesets.
  • Explicit lifecycle and SLA timers: Draft → Submitted → DocumentsPending → KycInProgress → Screening → Underwriting → Offered → Accepted → Disbursed, with terminal decline, withdrawal, and expiry paths.
  • Local object-store document upload, content/size checks, reviewer verification, expiry, and mandatory-checklist gating.
  • Deterministic KYC/bureau/disbursement simulators, explainable scorecard, four-eyes underwriting, immutable offer acceptance, and reconciliation.
  • Persisted rules traces, regulatory-style decision records, what-if simulation, and append-only hash-chained audit entries.

Non-Functional Requirements

SQLite is the default and no external infrastructure is required. The API uses JWT scope policies, RFC 7807 responses, correlation IDs, headers, rate limiting, pagination, health checks, and OpenTelemetry console telemetry. All demo applicant data is synthetic.

Architecture

The application is a modular monolith with dependency direction Api → Infrastructure → Application → Domain. Domain calculation and decision logic have no EF Core or ASP.NET dependency. Application owns ports; Infrastructure implements SQLite and local deterministic adapters.

Architecture Diagram

flowchart LR
  Client[Credit operations / applicant client] --> Api[ASP.NET Core Minimal API]
  Api --> App[Application use cases and ports]
  App --> Domain[Domain: workflow, rules, affordability, scorecard]
  App --> Repo[ILoanRepository]
  App --> Kyc[IKycProvider]
  App --> Bureau[IBureauProvider]
  App --> Store[IObjectStore]
  App --> Pay[IDisbursementProvider]
  Repo --> Sqlite[(SQLite)]
  Kyc --> Sim[Deterministic local simulators]
  Bureau --> Sim
  Pay --> Sim
  Store --> Files[Local filesystem outside web root]

Technology Stack

  • .NET 10 / ASP.NET Core Minimal APIs / OpenAPI and Swagger UI in Development
  • EF Core 10 with SQLite default persistence
  • JWT bearer authentication and scope policies
  • OpenTelemetry ASP.NET Core/custom business metrics with console exporter
  • xUnit, WebApplicationFactory, and SQLite in-memory integration tests

Domain Model

The main records are CustomerProfile, immutable LoanProductVersion, LoanApplication, LoanDocument, RuleSetDefinition, DecisionTrace, RiskAssessment, UnderwritingQueueItem, LoanOffer, DisbursementRecord, AuditEntry, and DecisionRecord. See database schema.

Core Workflows

stateDiagram-v2
  [*] --> Draft
  Draft --> Submitted
  Draft --> Withdrawn
  Submitted --> DocumentsPending
  Submitted --> KycInProgress
  DocumentsPending --> KycInProgress
  KycInProgress --> Screening
  KycInProgress --> Declined
  Screening --> Underwriting
  Screening --> DocumentsPending
  Screening --> Declined
  Underwriting --> Offered
  Underwriting --> DocumentsPending
  Underwriting --> Declined
  Offered --> Accepted
  Offered --> Expired
  Accepted --> Disbursed
  Disbursed --> [*]
  Declined --> [*]
  Withdrawn --> [*]
  Expired --> [*]
sequenceDiagram
  participant C as Credit client
  participant A as API/Application
  participant D as Documents/KYC
  participant R as Rules engine
  participant U as Underwriter
  C->>A: Create and submit application
  A->>D: Validate/verify required documents
  D-->>A: Checklist complete
  A->>D: Run deterministic KYC and bureau lookup
  A->>R: Evaluate bound ruleset + facts
  R-->>A: Decision trace and effects
  A->>U: Queue referral with risk/SLA priority
  U->>A: Delegated decision / second approval
  A-->>C: Versioned offer and schedule
  C->>A: Accept offer
  A->>D: Idempotent simulated disbursement

The system rejects illegal state edges, recalculates SLA due time on each transition, and records every transition with actor, reason, timestamp, and correlation ID.

Security Model

Local development tokens are issued only in Development/Testing and contain explicit scopes: loans:apply, loans:underwrite, loans:approve, and loans:admin. Sensitive changes are audited. Uploads are content/size constrained and stored outside the web root. Production boot rejects the checked-in development signing key. Details: security review.

Reliability & Failure Handling

The KYC simulator categorizes timeouts as transient and the application retries once. Disbursement references are idempotent, pending transfers can be completed by callback, failed transfers require a new retry reference, and reversal callbacks preserve reconciliation evidence. Queue claims expire. Optimistic application versions detect concurrent changes.

Observability

Every response has X-Correlation-Id; audit entries use the same ID. OpenTelemetry exposes ASP.NET request traces and custom decision-latency, approval, referral, and SLA-breach meters to the console exporter. /health/live and /health/ready distinguish liveness from SQLite readiness.

Testing Strategy

Unit tests pin rounding-sensitive schedules, IRR/APR, affordability stress boundaries, scorecard factors, rule combinators/outcomes/traces, workflow edges, documents, KYC, authorities, offers, and disbursement behavior. Integration tests use an open SQLite in-memory connection and exercise authentication, authorization, validation, and the full HTTP lifecycle. Actual output is retained in docs/test-results.md.

Local Development

Set-Location C:\Users\rukwaropaul\Downloads\DEV\Projects\14-loan-origination-platform
dotnet restore
dotnet run --project src\LoanOrigination.Api

The Development profile listens on http://localhost:5014, initializes SQLite with EnsureCreated, and idempotently seeds fictional products and applicants. Invoke .\scripts\demo.ps1 in a second shell after startup.

Running with Docker

Docker configuration created but Docker is unavailable on the build host; the compose stack has not been started or verified. The Docker files are convenience artifacts only and are explicitly marked UNVERIFIED.

API Documentation

OpenAPI is served at /openapi/v1.json; Swagger UI is at /docs in Development. Main groups are /api/v1/customers, /products, /applications, /rulesets, /underwriting/queue, /offers, /disbursements, and /audit. Get a local Development token from /api/v1/auth/token.

Example Usage

$token = (Invoke-RestMethod http://localhost:5014/api/v1/auth/token -Method Post `
  -ContentType application/json -Body '{"subject":"demo","scopes":["loans:apply","loans:underwrite","loans:approve","loans:admin"]}').accessToken
$headers = @{ Authorization = "Bearer $token"; "X-Correlation-Id" = "readme-demo-001" }
Invoke-RestMethod "http://localhost:5014/api/v1/products?page=1&pageSize=20" -Headers $headers
Invoke-RestMethod http://localhost:5014/api/v1/applications -Method Post -Headers $headers `
  -ContentType application/json -Body '{"customerId":"<seeded-customer-guid>","productCode":"SME-FLEX","productVersion":1,"requestedPrincipal":100000,"requestedTermMonths":12,"currency":"KES"}'

An application response includes a version-bound rulesetId, rulesetVersion, event history, KYC state, and—after screening—the persisted decisionTrace with each input read and rule result.

Performance / Load Testing

No load test or performance claim is included. This portfolio project has not been benchmarked; SQLite and local object storage are selected for zero-infrastructure reproducibility rather than throughput.

Trade-offs

JSON payload snapshots in normalized lookup tables make historical aggregate persistence easy to inspect but are not a substitute for a production warehouse or field-level data-retention program. SQLite and local adapters reduce setup cost but do not model vendor latency, regional failover, key management, or high-contention behavior. EnsureCreated is intentional for this case study; a production deployment would use reviewed migrations.

Architecture Decisions

Five concise decisions cover declarative rules, transparent scorecard rationale, immutable version binding, four-eyes authority, and money/rounding: ADRs.

Known Limitations

  • KYC, bureau, object storage, and payment rails are deterministic in-process/local adapters; no real provider call is implemented.
  • The simulator callback is API-policy protected rather than a real signed provider webhook.
  • Audit hashes detect accidental alteration in the application path but are not an externally anchored tamper-evident ledger.
  • There is no front end, encryption-at-rest configuration, background worker, or production identity provider.

Future Improvements

Introduce production adapters behind existing ports (Smile ID/Trulioo-style KYC, bureau, bank/mobile-money rails, Blob Storage), signed callback verification, OIDC/JWKS, immutable external audit anchoring, migration scripts, retained decision exports, configurable SLA policies, and a credit-operations UI.

Portfolio Talking Points

This project demonstrates a deliberate alternative to fake credit “AI”: every decision is reproducible using stored facts, bound versions, score contributions, and rule trace entries. It also shows the hard operational edges—rounding final installments, manual override audit, authority limits, document gating, provider timeout, retry, idempotence, callbacks, and reversal reconciliation.

Upwork Portfolio Description

Loan Origination & Credit Workflow Platform — self-directed engineering case study
Problem: lenders need explainable, auditable decisions across automated policy and human underwriting.
Built: a .NET 10 modular loan workflow with SQLite, deterministic provider simulators, versioned products/rules, transparent affordability/scorecard calculations, and idempotent disbursement simulation.
Engineering focus: decision reproducibility, money rounding, policy traceability, four-eyes controls, workflow reliability, and HTTP verification.
This is a self-directed portfolio project, not client work.

Source & documentation

Continue into the implementation.

The source repository contains setup instructions, design decisions, evaluation guidance and the project's stated limitations.

Browse supporting documentation 18 documents / 5 decision records