Skip to content
digidwebPublic

About

A REST API for controlling geographic concentration risk in loan portfolios

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Loan Risk ๐Ÿ’ฐ

A REST API for controlling geographic concentration risk in loan portfolios.

Loan Risk is a backend application designed to evaluate and control new loans against geographic concentration limits defined for a loan portfolio.

The project focuses on backend engineering practices such as Domain-Driven Design, Service Objects, Transactional Consistency, Configurable Business Rules, Financial Data Modeling, and Automated Testing.

โœจ Features

  • ๐Ÿ’ฐ Create and manage loans records
  • ๐Ÿ—บ๏ธ Calculate geographic concentration across the loan portfolio
  • โš ๏ธ Reject loans that would exceed concentration limits
  • ๐Ÿ“Š Expose current portfolio concentration through the API
  • โš™๏ธ Store concentration rules as database records
  • ๐Ÿ’ต Precise monetary calculations using PostgreSQL decimal
  • ๐Ÿ”’ Use database transactions for financial operations
  • ๐Ÿงช Automated tests with RSpec
  • ๐Ÿ—๏ธ Service Object architecture for business rules
  • ๐Ÿณ Docker support

๐Ÿ› ๏ธ Tech Stack

Backend

  • Ruby
  • Rails
  • Rails API-only
  • Active Record

Database

  • PostgreSQL

Architecture

  • Service Objects
  • Domain-driven design principles
  • REST API

Testing

  • RSpec

Infrastructure & Developement

  • Docker

๐ŸŽฏ Business Problem

Loan portfolios can become excessively concentrated in a specific geographic region.

For example, suppose a portfolio has a maximum concentration limit of 20% per state.

If the portfolio currently contains:

Total portfolio: R$ 100,000

Sรฃo Paulo (SP): R$ 15,000
Current concentration: 15%

A new loan of:

R$ 10,000 โ†’ SP

would result in:

Projected SP exposure: R$ 25,000
Projected concentration: 25%
Allowed limit: 20%

The API therefore rejects the new loan because accepting it would violate the configured concentration limit.

This business rule is the core of the application.

๐Ÿ›ก๏ธ Core Business Rule

When a loan is created, the system:

New Loan
   โ”‚
   โ–ผ
Calculate projected portfolio
   โ”‚
   โ–ผ
Calculate geographic concentration
   โ”‚
   โ–ผ
Compare against configured limit
   โ”‚
   โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚               โ”‚
   โ–ผ               โ–ผ
Within limit    Exceeds limit
   โ”‚               โ”‚
   โ–ผ               โ–ผ
Approve         Reject
   โ”‚
   โ–ผ
Persist loan

The concentration calculation considers only loans with:

status: "active"

Cancelled loans are excluded from the portfolio concentration calculation.

๐Ÿ“‹ Business Assumptions

The application currently follows these business assumptions:

  • The total portfolio value is the sum of loans with status: "active".
  • Only loans with active status are included in concentration calculations.
  • Cancelled loans are excluded from concentration calculations.
  • A state-specific concentration rule overrides the default rule.
  • A loan exactly at the configured concentration limit is considered approved.

๐Ÿ—๏ธ Architecture

The application uses a domain-oriented architecture with Service Objects to keep business rules separate from controllers and persistence concerns.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      HTTP Client     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      Controller      โ”‚
โ”‚                      โ”‚
โ”‚  Handles HTTP/JSON   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚    Service Object    โ”‚
โ”‚                      โ”‚
โ”‚    Business rules    โ”‚
โ”‚  Concentration logic โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚        Models        โ”‚
โ”‚                      โ”‚
โ”‚      Validations     |
โ”‚      Associations    |    
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚      PostgreSQL      |
|                      |
|     Persistence      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The main responsibility boundaries are:

  • Controller โ†’ HTTP requests and responses
  • Service โ†’ Business rules and risk calculation
  • Model โ†’ Data integrity and validations
  • Database โ†’ Persistent rules and loan data

This separation makes the business logic easier to test, maintain, and evolve independently from the HTTP layer.

๐Ÿง  Technical Highlights

Domain-driven business rules

The core business requirement is geographic concentration risk.

When a new loan is created, the application evaluates how the loan would affect the portfolio's concentration in the corresponding state.

Rule Precedence

The application supports a default concentration rule and more specific rules.

A more specific rule takes precedence over the default:

Specific state rule       
        โ†“
  Default rule

This allows the risk policy to be configured without embedding every possible rule directly into the application code.

Business Rules as Data

A key design decision in Loan Risk is to store concentration rules in the database instead of hardcoding them in Ruby.

For example:

scope_type:  "state"
scope_value: "SP"
limit:       20%

Changing the limit from:

20% โ†’ 30%

becomes a data operation rather than a code change.

This means that changing a business configuration does not require modifying the application code or deploying a new version.

The rule model also supports:

  • scope_type
  • scope_value

This allows the system to evolve toward rules based on other dimensions, such as:

  • Region
  • Product
  • Customer segment
  • Portfolio
  • Other business criteria

Financial Data Modeling

Monetary values are stored using:

decimal(15, 2)

rather than floating-point types.

This is intentional because floating-point arithmetic can introduce binary rounding errors that are inappropriate for financial calculations.

For example, a monetary value such as:

10000.00

should be represented precisely rather than relying on floating-point arithmetic.

Transactional Consistency

Loan creation and concentration validation are financial operations where consistency is critical. The application is designed around relational database guarantees and ACID transactions provided by PostgreSQL.

The goal is to ensure that the concentration check and the resulting persistence are handled consistently rather than allowing the portfolio state to become invalid between operations.

๐Ÿ”Œ API

Create a loan

POST /loans
Content-Type: application/json

{
  "amount": 10000,
  "state_code": "SP"
}

If the new loan respects the applicable concentration limit, the API returns 201 Created.

If the loan would exceed the limit, the API returns 422 Unprocessable Entity with an explanatory error.

Example:

{
  "error": "Concentration limit exceeded for SP"
}

Request:

{
  "amount": 10000,
  "state_code": "SP"
}

Approved response

HTTP/1.1 201 Created
{
  "id": 1,
  "amount": 10000.0,
  "state_code": "SP",
  "status": "active",
  "created_at": "2024-01-01T10:00:00.000Z"
}

Rejected response

HTTP/1.1 422 Unprocessable Entity
{
  "error": "Concentration limit exceeded for SP: 25.0% projected, allowed limit is 20.0%"
}

List loans

GET /loans

Get a loan

GET /loans/:id

Get current portfolio concentration

GET /loans/concentration

Example response:

{
  "portfolio_total": 100000.0,
  "states": [
    {
      "state": "SP",
      "amount": 60000.0,
      "concentration": 60.0,
      "limit": 20.0,
      "within_limit": false
    }
  ]
}

๐Ÿงช Testing

The project uses RSpec to test the application's behavior at different layers.

Run the complete test suite:

bundle exec rspec

Run model specs:

bundle exec rspec spec/models

Run service specs:

bundle exec rspec spec/services

Run request specs:

bundle exec rspec spec/requests

The test structure mirrors the application's architecture, making it possible to test:

Models
   โ†“
Services
   โ†“
HTTP Requests

This helps ensure that both individual business rules and complete API flows are covered.

๐Ÿš€ Getting Started

Prerequisites

Make sure you have installed:

  • Ruby 3.x
  • Rails 7.1
  • PostgreSQL
  • Bundler

Or run with Docker in a containerized environment.

1. Clone the repository

git clone https://github.com/digidweb/loan-risk.git
cd loan-risk

2. Install dependencies

bundle install

3. Create the database

rails db:create

4. Run migrations

rails db:migrate

5. Load the initial rules

rails db:seed

The seed data creates the initial concentration rules.

6. Start the API

rails server

The API will be available at:

http://localhost:3000

๐Ÿณ Running with Docker

The repository also includes a Dockerfile for containerized development.

1. Build the image:

docker build -t loan-risk .

2. Run the container:

docker run -p 3000:3000 loan-risk

For a complete production-like environment, PostgreSQL should be provided as a separate service/container and configured through environment variables.

โš™๏ธ Engineering Focus

The main purpose of this project is to demonstrate how a relatively simple business requirement can be translated into a maintainable backend architecture.

The application intentionally separates:

HTTP concerns
     โ†“
Business rules
     โ†“
Domain entities
     โ†“
Database persistence

This makes it possible to evolve the business rules without coupling them directly to the API controllers or database implementation.

๐Ÿ”ฎ Potential Improvements

If continuing the project, the following improvements would be:

  • Add pagination to GET /loans
  • Add API authentication and authorization
  • Add rate limiting
  • Add domain events for integrations
  • Add caching for portfolio concentration calculations
  • Add an administrative interface for managing risk rules
  • Add GitHub Actions for automated tests and code quality checks
  • Add API documentation with OpenAPI/Swagger

๐Ÿ“Œ Portfolio Context

Loan Risk is part of a portfolio focused on Ruby on Rails backend and full-stack development.

The project demonstrates practical experience with:

Ruby on Rails ยท REST APIs ยท PostgreSQL ยท Active Record ยท Service Objects ยท Domain Modeling ยท RSpec ยท Docker

More importantly, it demonstrates the ability to translate a business requirement into a maintainable backend architecture:

Business requirement
        โ†“
Domain model
        โ†“
Business rules
        โ†“
Service Object
        โ†“
Database / Persistence
        โ†“
API response
        โ†“
Automated tests

The project goes beyond basic CRUD by focusing on business rules, data integrity, separation of responsibilities, and testable application architecture.

About

A REST API for controlling geographic concentration risk in loan portfolios

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages