Split smarter. Pay fewer times.
A FinTech MVP that minimises the number of cash handovers required to settle shared group expenses using a greedy debt-simplification algorithm.
# 1. Install dependencies
pip install -r requirements.txt
# 2. Run the server
python app.py
# 3. Open in browser
open http://127.0.0.1:5000settle-kar/
├── app.py # Flask application & all API routes
├── models.py # SQLAlchemy ORM models
├── algorithm.py # Debt simplification engine
├── requirements.txt
└── static/
├── index.html # Single-page frontend
└── app.js # Vanilla JS — API calls, state, UI rendering
The debt simplification engine in algorithm.py uses a greedy heap-based approach that reduces any set of pairwise debts to the theoretical minimum of n − 1 transactions.
-
Calculate net balances
For every user:net = Σ(amount they paid for others) − Σ(amount they owe others)- Positive net → creditor (is owed money)
- Negative net → debtor (owes money)
-
Separate into two max-heaps
One heap for creditors (sorted largest credit first), one for debtors (sorted largest debt first). Python'sheapqis min-heap only, so we negate values. -
Greedy matching loop
while both heaps non-empty: pop largest_creditor, largest_debtor transfer = min(|credit|, |debt|) emit transaction: debtor → creditor: transfer PKR push back any residual balance -
Output — an ordered list of
(from, to, amount)tuples.
| Person | Paid | Owes | Net |
|---|---|---|---|
| Ali | 900 | 400 | +500 |
| Bilal | 300 | 400 | −100 |
| Carla | 0 | 400 | −400 |
Optimised result (2 transactions, not 3):
Carla → Ali: PKR 400
Bilal → Ali: PKR 100
All endpoints are prefixed with /api.
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /users |
{name, phone} |
Register a new user |
| POST | /users/login |
{phone} |
Simulated auth — look up by phone |
| GET | /users |
— | List all users |
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /groups |
{name, creator_id} |
Create a group |
| GET | /groups?user_id=X |
— | List groups for a user |
| GET | /groups/<id> |
— | Get group detail |
| POST | /groups/<id>/add_user |
{user_id} |
Add a member |
| Method | Endpoint | Body | Description |
|---|---|---|---|
| POST | /expenses |
{group_id, payer_id, amount, description, split_with?} |
Log an expense (equal split) |
| GET | /groups/<id>/expenses |
— | List expenses for a group |
| Method | Endpoint | Description |
|---|---|---|
| GET | /groups/<id>/optimize |
Run the algorithm; returns optimised transaction list |
users (id, name, phone_hash, phone_salt, phone_masked)
groups (id, name, created_at)
group_members (id, group_id, user_id) ← unique constraint
expenses (id, group_id, payer_id, amount, description, timestamp)
expense_splits (id, expense_id, user_id, owed_amount)
Phone numbers are never stored in plaintext. Each is stored as a salted SHA-256 hash. The salt is stored alongside to allow re-derivation during login. A masked display version (e.g. 030****89) is stored for UI purposes.
- Data isolation: every SQL query is scoped by
group_id. Cross-group data leakage is structurally impossible. - No plaintext phone numbers: salted SHA-256 hash storage.
- SECRET_KEY: read from
SECRET_KEYenvironment variable; falls back to a random value per process (suitable for MVP, use a stable env var in staging). - For production: replace SQLite with PostgreSQL, add JWT authentication, use HTTPS, and rate-limit the login endpoint.
- Register two or three users via the Register tab.
- Create a group (e.g. "Trip to Lahore").
- Add members via phone number search.
- Log 2–3 expenses with different payers.
- Tap the "Settle Up ✦" tab — watch the algorithm collapse the debts into the minimum number of arrows.
- Tap "Mark Settled" on each transaction to simulate completion.