=== BEGIN ===
language:
- en
- fr
- rw
license: mit
tags:
- fintech
- tabular-classification
- credit-scoring
- logistic-regression
- calibrated-classifier
- aims-ktt-hackathon
- ikimina
- rwanda
library_name: scikit-learn
pipeline_tag: tabular-classification
Ikimina Digital Trust & Reliability Index — T1.1
A calibrated Logistic Regression classifier that scores members of Rwandan Ikimina (rotating-savings groups) on a 0–100 reliability scale from 12 months of paper-record-derived contribution, penalty, and borrowing history. Submission for the AIMS KTT Fellowship Hackathon 2026, Tier 1 challenge T1.1.
Model summary
- Architecture:
StandardScaler → LogisticRegression(L2, class_weight='balanced') wrapped in CalibratedClassifierCV(method='sigmoid', cv=5) (Platt scaling).
- Input: 12 engineered features (see below).
- Output: calibrated probability of default within 6 months; post-processed to an integer in
[0, 100] via reliability = round((1 - P(default)) × 100).
- Tier bands (brief-mandated):
0–40 = high risk, 41–70 = watch, 71–100 = low risk.
Why Logistic Regression over XGBoost
The T1.1 brief's default label is logistic on four linear features (missed_count_total, late_penalty_unpaid, borrowed/repaid_ratio, tenure). A 5-fold CV diagnostic on the training set showed:
| Model | Train AUC | 5-fold CV AUC | Holdout AUC (n=100, 12 positives) |
|---|
| LogReg (L2, balanced), 12 features | 0.76 | 0.70 ± 0.08 | 0.61 |
| XGBoost (depth=5, n=500) | 1.00 (memorised) | 0.60 ± 0.09 | 0.55 |
Logistic regression is the Bayes-optimal classifier for a logistically-generated label. Tree-based non-linearities don't exist in the ground-truth process — XGBoost memorised the 400 training rows without improving generalisation. LogReg is one of the three blessed model choices in the brief.
Features
| # | Name | Description |
|---|
| 1 | total_missed | Sum of missed weeks across 12 months (brief ground-truth feature) |
| 2 | contribution_volatility_sigma | Std-dev of monthly on-time rate |
| 3 | on_time_streak | Longest consecutive run of perfect-attendance months |
| 4 | recency_weighted_miss_rate | Exponential decay (λ=0.85) over monthly miss counts |
| 5 | penalty_paid_ratio | Penalty-paid / expected recorded penalties (discipline proxy) |
| 6 | late_penalty_unpaid_est | Estimated unpaid penalties (brief ground-truth feature) |
| 7 | borrow_repay_ratio | borrowed / max(repaid, 1), clipped [0.5, 3.0] (brief ground-truth feature) |
| 8 | role_seniority | Officer flag (secretary/treasurer = 2, member = 0) |
| 9 | tenure_months | Months since join_date (brief ground-truth feature) |
| 10 | group_avg_contrib_ratio | Member's weekly contribution / group average |
| 11 | group_size | Members in the group (12 or 13 by design) |
| 12 | urban_flag | Whether the member's group is in Kigali (3 districts) |
Intended use
- Give a bank or microfinance institution (MFI) a calibrated trust signal derived from an Ikimina secretary's paper records.
- Delivered via USSD on a feature phone (
*654*member_id#), with per-decision feature attribution for regulatory explainability.
- See the GitHub repo for the full reference USSD flow in Kinyarwanda + French, consent capture, and SS7 privacy analysis.
Not intended for: automated loan approval without human review; any use that transmits personally-identifiable information over the USSD or SS7 layer; any deployment without a native-Kinyarwanda speaker reviewing the SMS response templates.
Evaluation
Holdout: last 100 member_ids (12 actual positives). Training: first 400 member_ids.
| Metric | Value | Interpretation |
|---|
| 5-fold CV AUC on training | 0.7032 ± 0.0830 | True model ceiling |
| Holdout ROC-AUC | 0.6136 | High variance with 12 positives (± ≈0.13 std error) |
| Holdout Brier score | 0.1091 | Naive-constant baseline = 0.1056 |
| Holdout log-loss | 0.3764 | — |
| Watch-tier actual default rate | 33.3% (n=12) | Model-flagged-watch members default at 3.6× the low-risk rate |
| Low-risk-tier actual default rate | 9.2% (n=87) | Tier ordering is practically useful |
Explainability
For any prediction, the scorer surfaces the top-3 features by coef × scaled_feature — the exact log-odds contribution of each feature. This is not a SHAP approximation; for a linear model, coefficients give the ground truth. A bank can print these contributions next to a decline decision to satisfy regulatory "explanation of adverse action" requirements.
Training data
Synthetic, generated from the T1.1 brief's seeded recipe (NumPy, seed 42):
- 500 members × 12 months of contribution / missed / penalty / borrowing history
- 40 groups × 6 attributes (size, avg contribution, founded year, district, urban flag)
- 500 binary labels (
defaulted_within_6m) with ~14% positive rate
- AR(1) ρ=0.4 on latent monthly miss-rate deviation; balanced 12–13 members per group; 1 secretary + 1 treasurer enforced per group
Generator script + data regeneration instructions live in the GitHub repo.
Limitations
- Tiny dataset. 400 training rows / 58 positives. Holdout AUC has ±0.13 std error. The reported 5-fold CV AUC is the more reliable ceiling.
- Synthetic, not production. Real Ikimina records may have label noise, missing weeks, and reporting bias that this synthetic dataset doesn't capture.
- Kinyarwanda not validated. The USSD response templates in
ussd_flow.md are auto-translated. Must be reviewed by a native Kinyarwanda Umurenge SACCO speaker before deployment.
- Single-point predictions. Model returns one score per (member, group). A real deployment would need time-series drift monitoring.
- No PII handling. The scorer takes a bare integer
member_id — any PII lookup/consent/privacy concerns are the caller's responsibility. The reference USSD flow documents the SS7 pseudonymisation model.
Quick start
1import joblib
2import pandas as pd
3
4model = joblib.load("logreg_calibrated.joblib")
5
6# Load feature order
7import json
8feature_order = json.load(open("feature_order.json"))
9
10# `features_dict` is a dict keyed by feature name (see feature table above).
11X = pd.DataFrame([[features_dict[f] for f in feature_order]], columns=feature_order)
12p_default = model.predict_proba(X)[0, 1]
13score = round((1 - p_default) * 100)
14# tier: 0-40 high risk, 41-70 watch, 71-100 low risk
Full feature-construction pipeline is in the GitHub repo (features.py). Use build_single_feature_vector(member_record, group_record) for the canonical path.
Citation / source
- GitHub repo: (to be added after first push)
- Live demo: http://207.180.242.232:7860/ (Gradio UI, Docker-isolated)
- 4-minute walkthrough video: (to be added after recording)
Author
Ahmed Eldaw — AIMS KTT Fellowship Hackathon 2026 candidate
License
MIT
=== END ===