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.
| 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 |
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.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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)
| 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.
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 dbWait ~10 seconds for the container to become healthy.
Fineract publishes a pre-built image to Docker Hub β no local build needed:
docker pull apache/fineract:latest
docker tag apache/fineract:latest fineract:latestcd "C:\Users\<you>\Desktop\fineract"
$env:PWD = (Get-Location).Path.Replace('\', '/')
docker compose -f docker-compose-postgresql.yml up -d fineractWatch 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 fineractWait for this line then press Ctrl+C:
Started FineractApplication in X.XXX seconds
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.
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_defaultSkip this step if pointing at a real Fineract instance that already has data.
cd "C:\Users\<you>\Desktop"
git clone https://github.com/apache/fineract-business-intelligence.git
cd fineract-business-intelligence
Copy-Item .env.example .envThe defaults in .env work for local development without any edits.
One-time step. Run from Git Bash inside the fineract-business-intelligence directory:
bash scripts/bootstrap_source.shThis 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. ===
cd "C:\Users\<you>\Desktop\fineract-business-intelligence"
docker compose up -d warehouse superset dbt extractorThis 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 extractorWait for:
Done. PASS=81 WARN=0 ERROR=0
[pipeline] Pipeline run complete in Xs
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.
The extractor runs the full pipeline every hour automatically. No action needed.
Force an immediate update instead of waiting for the next hour:
docker compose restart extractordocker compose logs --tail=30 extractor # check current status firstThen from Git Bash:
docker exec fineract-bi-extractor bash -c "bash /app/scripts/run_pipeline.sh backfill"docker compose logs --tail=30 warehouse superset dbt extractorWhat 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 |
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 extractorNo need to re-run bootstrap or re-seed β data is persisted in Docker volumes.
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_sourceRun bootstrap once against the remote database (from Git Bash):
bash scripts/bootstrap_source.shThen start the BI stack β it connects to the remote Fineract DB directly via SOURCE_DB_HOST.
# 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>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
date_of_birthis dropped instg_m_clientand replaced withage_band(6 cohorts)client_idis replaced downstream byclient_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
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.
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
# 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- Add SQL in the appropriate
dbt/models/subdirectory - Add schema tests in the corresponding
_*.ymlfile - Add Apache license header to the SQL file
- Run
dbt build --select <your_model>to verify
- 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, sourcecommon.shfor shared helpers
Bootstrap has not been run, or the Fineract DB was recreated. Re-run from Git Bash:
bash scripts/bootstrap_source.shThen restart the extractor:
docker compose restart extractorThe 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>The pipeline has not completed yet. Check:
docker compose logs --tail=30 extractorLook 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 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 dbtDuplicate 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"Windows Git added CRLF line endings. Fix with:
sed -i 's/\r//' scripts/bootstrap_source.sh scripts/common.sh scripts/run_pipeline.shAn init script has CRLF line endings. Fix and restart:
sed -i 's/\r//' docker/postgres-warehouse/initdb/002_create_warehouse_roles.shdocker compose down -v
docker compose up -d warehouse superset dbt extractor# Stop everything and remove volumes
docker compose down -v
# Restart from Step 8
docker compose up -d warehouse superset dbt extractor
docker compose down -vdeletes all warehouse data and Superset metadata. The Fineract DB (managed separately) is unaffected. Re-run bootstrap (Step 7) before starting the BI stack again.
Apache License 2.0 β see LICENSE.
This project is part of the Apache Fineract ecosystem, started as part of Google Summer of Code 2026.