Skip to content

apache/fineract-business-intelligence

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

51 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Apache Fineract Business Intelligence

The analytics pipeline for Apache Fineract: the open-source core banking platform for financial inclusion.

This project reads data from a Fineract PostgreSQL database, transforms it through a layered dbt pipeline, and serves interactive dashboards in Apache Superset. It is designed to be downstream and separate from Fineract: the only connection is a read-only credential to the Fineract database. Everything else β€” the analytics warehouse, transformations, and dashboards β€” runs independently.


Dashboards

Dashboard What it shows
Portfolio Health Gross Loan Portfolio, active borrowers vs loans, PAR ratio, NPA ratio, disbursement and collection trends, portfolio composition by branch and product
Delinquency & PAR Portfolio At Risk by DPD bucket (PAR 30/60/90/NPA), delinquency trend over time, bucket migration

Key assumption: Fineract database

This project connects to a real Apache Fineract PostgreSQL database. It does not manage or start the Fineract application β€” you run Fineract separately and point this project at its database.

For local development, clone and run Fineract locally (see Setup below). In production, set the SOURCE_* environment variables to point at your existing Fineract PostgreSQL instance.

The extractor connects via a read-only credential (SOURCE_REPLICA_USER) created by bootstrap_source.sh. It never writes to Fineract.


Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Apache Fineract PostgreSQL  (your Fineract database)    β”‚
β”‚  fineract_default β†’ bi_connector_source (read-only views)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ read-only (fineract_reader)
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Extractor  (Python)                                     β”‚
β”‚  Incremental watermark-based CDC                        β”‚
β”‚  β†’ raw.raw_m_* tables in Analytics Warehouse            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Analytics Warehouse  (PostgreSQL)                       β”‚
β”‚  raw β†’ staging (views) β†’ facts (incremental) β†’ marts    β”‚
β”‚  Transformation engine: dbt                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ read-only (analytics_reader)
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Apache Superset  :8088                                  β”‚
β”‚  Row-level security β€” branch managers see their office   β”‚
β”‚  Admin sees all offices                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Pipeline loop (runs automatically inside the extractor container):

backfill once on startup
  └─► loop every PIPELINE_INTERVAL_SECONDS (default: 1 hour):
        1. extractor incremental  β€” pull changed rows from Fineract DB
        2. dbt build              β€” rebuild marts (only if step 1 succeeded)
        3. superset refresh       β€” sync chart metadata (only if step 2 succeeded)

Prerequisites

Tool Version Notes
Docker Desktop 24+ Must be running
Docker Compose v2 (plugin) docker compose version
Git any
Git Bash any For running .sh scripts on Windows

No local Python, Java, or database tools required.


Setup

Step 1 β€” Start the Fineract database

Clone the Fineract repository and start its PostgreSQL container:

git clone -b develop https://github.com/apache/fineract.git
cd fineract
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml up -d db

Wait ~10 seconds for the container to become healthy.

Step 2 β€” Pull and tag the Fineract image

Fineract publishes a pre-built image to Docker Hub β€” no local build needed:

docker pull apache/fineract:latest
docker tag apache/fineract:latest fineract:latest

Step 3 β€” Start Fineract (runs Flyway migrations)

cd "C:\Users\<you>\Desktop\fineract"
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml up -d fineract

Watch the logs until Flyway finishes creating all m_* tables (2–5 minutes):

cd "C:\Users\<you>\Desktop\fineract"
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml logs -f fineract

Wait for this line then press Ctrl+C:

Started FineractApplication in X.XXX seconds

Step 4 β€” Verify tables exist

cd "C:\Users\<you>\Desktop\fineract"
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml exec db psql -U root -d fineract_default -c '\dt m_*'

You should see 100+ tables like m_loan, m_client, m_office, etc.

Step 5 β€” Seed demo data

Load demo data into the Fineract DB. This gives you:

  • 25 clients, 71 loans across 3 offices and 4 products
  • Staggered vintages from 1 month to 36 months ago β€” enough history for trend charts
  • PAR-30, PAR-60, PAR-90, and NPA loans β€” every delinquency bucket populated

Run from PowerShell (from the fineract-business-intelligence directory):

Get-Content "C:\Users\<you>\Desktop\fineract-business-intelligence\warehouse\seed\seed_fineract_source.sql" | docker exec -i fineract-db-1 psql -U root -d fineract_default

Skip this step if pointing at a real Fineract instance that already has data.

Step 6 β€” Clone and configure this project

cd "C:\Users\<you>\Desktop"
git clone https://github.com/apache/fineract-business-intelligence.git
cd fineract-business-intelligence
Copy-Item .env.example .env

The defaults in .env work for local development without any edits.

Step 7 β€” Bootstrap the source database

One-time step. Run from Git Bash inside the fineract-business-intelligence directory:

bash scripts/bootstrap_source.sh

This creates the bi_connector_source schema with compatibility views on the Fineract DB and grants read-only access to fineract_reader. Safe to re-run β€” all operations are idempotent.

Expected output:

[bootstrap-source] Connection OK
[bootstrap-source] Compatibility views created in schema 'bi_connector_source'
[bootstrap-source] Creating replica user if not exists...
[bootstrap-source] Read access granted to 'fineract_reader'
[bootstrap-source] === Source bootstrap complete. You can now run the pipeline. ===

Step 8 β€” Start the BI stack

cd "C:\Users\<you>\Desktop\fineract-business-intelligence"
docker compose up -d warehouse superset dbt extractor

This starts 4 services:

Service Role Port
warehouse Analytics PostgreSQL warehouse 5434
extractor ETL pipeline β€” runs automatically on schedule β€”
dbt Transformation container β€”
superset Dashboard UI 8088

The extractor waits 30 seconds for Superset to initialise, runs a full backfill, then loops every hour. Watch it:

docker compose logs -f extractor

Wait for:

Done. PASS=81 WARN=0 ERROR=0
[pipeline] Pipeline run complete in Xs

Step 9 β€” Open Superset

http://localhost:8088
Role Username Password (default) Sees
Admin admin admin_dev_only All offices
North Branch Manager north_manager north_manager_dev_only North Branch only
South Branch Manager south_manager south_manager_dev_only South Branch only

Navigate to Dashboards β†’ Portfolio Health and Delinquency & PAR.


Keeping Dashboards Fresh

Automatic (default)

The extractor runs the full pipeline every hour automatically. No action needed.

After changing data in Fineract

Force an immediate update instead of waiting for the next hour:

docker compose restart extractor

Force full pipeline run manually

docker compose logs --tail=30 extractor   # check current status first

Then from Git Bash:

docker exec fineract-bi-extractor bash -c "bash /app/scripts/run_pipeline.sh backfill"

Check all container logs

docker compose logs --tail=30 warehouse superset dbt extractor

What to look for:

Container Healthy sign
warehouse database system is ready to accept connections
superset Portfolio Health dashboard created
dbt (idle β€” no output expected)
extractor PASS=81 WARN=0 ERROR=0 + Pipeline run complete

After a Machine Reboot

Fineract DB and the BI stack are separate β€” restart both:

# 1. Restart Fineract DB
cd "C:\Users\<you>\Desktop\fineract"
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml up -d db fineract

# 2. Restart BI stack
cd "C:\Users\<you>\Desktop\fineract-business-intelligence"
docker compose up -d warehouse superset dbt extractor

No need to re-run bootstrap or re-seed β€” data is persisted in Docker volumes.


Production Deployment

Connecting to a remote Fineract database

Set these in .env:

SOURCE_DB_HOST=<your-fineract-db-host>
SOURCE_DB_HOST_PORT=5432
SOURCE_DB_NAME=fineract_default
SOURCE_BOOTSTRAP_USER=<admin-user>
SOURCE_BOOTSTRAP_PASSWORD=<secret>
SOURCE_REPLICA_USER=fineract_reader
SOURCE_REPLICA_PASSWORD=<secret>
SOURCE_DB_SCHEMA=bi_connector_source

Run bootstrap once against the remote database (from Git Bash):

bash scripts/bootstrap_source.sh

Then start the BI stack β€” it connects to the remote Fineract DB directly via SOURCE_DB_HOST.

Recommended production settings

# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
SUPERSET_SECRET_KEY=<64-char-hex>

# Run pipeline daily after COB completes
PIPELINE_INTERVAL_SECONDS=86400

# All passwords via your secrets manager
WAREHOUSE_ADMIN_PASSWORD=<secret>
WAREHOUSE_LOADER_PASSWORD=<secret>
WAREHOUSE_READER_PASSWORD=<secret>
SUPERSET_ADMIN_PASSWORD=<secret>
SUPERSET_NORTH_MANAGER_PASSWORD=<secret>
SUPERSET_SOUTH_MANAGER_PASSWORD=<secret>

Data Pipeline Details

Layer architecture

fineract_default.public.*          Fineract source tables
        β”‚
        β”‚  bi_connector_source.*   Compatibility views (bootstrap_source.sh)
        β”‚                          Normalises schema differences across Fineract versions
        β”‚
        β–Ό
raw.raw_m_*                        Raw layer β€” exact copy of source rows
                                   + tenant_id + source_loaded_at
        β”‚
        β–Ό
staging.stg_m_*                    Staging views β€” rename columns, cast types,
                                   drop PII (date_of_birth β†’ age_band),
                                   add pseudonymous client_hash
        β”‚
        β–Ό
analytics.fact_loan_snapshot       Daily grain: one row per (loan, date)
analytics.fact_delinquency_event   One row per delinquency tag lifecycle event
        β”‚
        β–Ό
analytics.mart_portfolio_health    Grain: office Γ— product Γ— currency Γ— date
analytics.mart_delinquency_par     Grain: office Γ— product Γ— bucket Γ— date

PII handling

  • date_of_birth is dropped in stg_m_client and replaced with age_band (6 cohorts)
  • client_id is replaced downstream by client_hash = MD5(tenant_id || '::' || id)
  • All presentation marts are aggregated at office Γ— product level β€” no individual client rows reach Superset
  • Row-level security in Superset restricts branch managers to their own office data

Watermark-based incremental extraction

The extractor tracks a per-table last_modified_on_utc cursor in meta.watermarks. Each incremental run fetches only rows changed since the last successful extraction. A 10-minute lookback window (EXTRACT_LOOKBACK_SECONDS=600) handles clock skew and late-arriving updates.


Project Structure

fineract-business-intelligence/
β”œβ”€β”€ compose.yaml                    Docker Compose β€” 4-service BI stack
β”œβ”€β”€ .env.example                    Environment template (copy to .env)
β”‚
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ common.sh                   Shared helpers (docker checks, env loading)
β”‚   β”œβ”€β”€ bootstrap_source.sh         One-time: create views + grants on Fineract DB
β”‚   └── run_pipeline.sh             Full pipeline: extractor β†’ dbt β†’ superset
β”‚
β”œβ”€β”€ extractor/                      Python ETL service
β”‚   β”œβ”€β”€ cli.py                      Entry point: backfill | incremental
β”‚   β”œβ”€β”€ extractor.py                Extraction logic (11 tables, watermark-based)
β”‚   β”œβ”€β”€ config.py                   Config from environment variables
β”‚   └── watermark_manager.py        Per-table cursor tracking
β”‚
β”œβ”€β”€ dbt/                            dbt transformation project (fineract_bi)
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ staging/                stg_* views (clean + rename)
β”‚   β”‚   └── marts/
β”‚   β”‚       β”œβ”€β”€ dimensions/         dim_office, dim_client, dim_product, …
β”‚   β”‚       β”œβ”€β”€ facts/              fact_loan_snapshot, fact_delinquency_event
β”‚   β”‚       └── presentations/      mart_portfolio_health, mart_delinquency_par
β”‚   └── macros/
β”‚       └── safe_divide.sql         NULL-safe division macro
β”‚
β”œβ”€β”€ warehouse/
β”‚   β”œβ”€β”€ schema/                     DDL for raw, staging, mart, meta schemas
β”‚   └── seed/
β”‚       └── seed_fineract_source.sql  Demo data (25 clients, 71 loans)
β”‚
└── docker/
    β”œβ”€β”€ postgres-warehouse/         Warehouse init scripts (roles, permissions)
    β”œβ”€β”€ extractor/                  Extractor Dockerfile (includes Docker CLI)
    β”œβ”€β”€ dbt/                        dbt Dockerfile (dbt-postgres pinned <2.0)
    └── superset/
        β”œβ”€β”€ Dockerfile
        β”œβ”€β”€ init_superset.sh        First-run: DB migrate, admin user, dashboards
        β”œβ”€β”€ refresh_superset_assets.sh
        β”œβ”€β”€ bootstrap_superset_assets.py
        └── superset_config.py

Contributing

Running dbt manually

# Enter the dbt container
docker compose exec dbt bash

# Run all models
dbt build

# Run a specific model
dbt run --select mart_portfolio_health

# Run tests only
dbt test

# Full rebuild (drops and recreates incremental tables)
dbt build --full-refresh

Adding a new dbt model

  1. Add SQL in the appropriate dbt/models/ subdirectory
  2. Add schema tests in the corresponding _*.yml file
  3. Add Apache license header to the SQL file
  4. Run dbt build --select <your_model> to verify

Code standards

  • All source files must carry the Apache License 2.0 header
  • SQL: snake_case identifiers, explicit column lists (no SELECT * in models)
  • Python: type annotations, dataclasses for config, no hardcoded credentials
  • Shell: set -euo pipefail, source common.sh for shared helpers

Troubleshooting

Extractor fails: role "fineract_reader" does not exist

Bootstrap has not been run, or the Fineract DB was recreated. Re-run from Git Bash:

bash scripts/bootstrap_source.sh

Then restart the extractor:

docker compose restart extractor

Extractor cannot reach the Fineract DB

The extractor connects to the Fineract DB container by name (fineract-db-1) on the shared Docker network. If your Fineract DB container has a different name, update SOURCE_DB_HOST in .env:

SOURCE_DB_HOST=<your-fineract-db-container-name-or-host>

Dashboards show "No data"

The pipeline has not completed yet. Check:

docker compose logs --tail=30 extractor

Look for Pipeline run complete. If it shows ERROR, check the specific error and consult the relevant section below.

Force an immediate run:

docker exec fineract-bi-extractor bash -c "bash /app/scripts/run_pipeline.sh backfill"

dbt fails: adapter is not yet supported by dbt Fusion

dbt 2.0 dropped PostgreSQL support. The dbt image pin (dbt-postgres<2.0.0a1) should prevent this, but if it happens rebuild the image:

docker compose build dbt
docker compose up -d --force-recreate dbt

dbt test fails: unique_fact_delinquency_event_delinquency_event_key

Duplicate rows in the incremental table from a previous run. Fix with a full-refresh of that model:

docker exec fineract-bi-dbt bash -c "cd /app/dbt && dbt build --full-refresh --select fact_delinquency_event"

Shell scripts fail with \r': command not found

Windows Git added CRLF line endings. Fix with:

sed -i 's/\r//' scripts/bootstrap_source.sh scripts/common.sh scripts/run_pipeline.sh

Warehouse container exits with code 126

An init script has CRLF line endings. Fix and restart:

sed -i 's/\r//' docker/postgres-warehouse/initdb/002_create_warehouse_roles.sh
docker compose down -v
docker compose up -d warehouse superset dbt extractor

Full clean slate

# Stop everything and remove volumes
docker compose down -v

# Restart from Step 8
docker compose up -d warehouse superset dbt extractor

docker compose down -v deletes all warehouse data and Superset metadata. The Fineract DB (managed separately) is unaffected. Re-run bootstrap (Step 7) before starting the BI stack again.


License

Apache License 2.0 β€” see LICENSE.

This project is part of the Apache Fineract ecosystem, started as part of Google Summer of Code 2026.

About

Apache fineract

Resources

License

Code of conduct

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors