Item-level expense sharing with smart settlement suggestions
RightSplit is a full-stack expense-sharing application for groups where different people participate in different line items on the same bill. Instead of assuming every member shares every cost equally, the system tracks participation per item, calculates exact rupee shares in integer paise, and produces group balances plus optimized settlement suggestions.
Built as a software engineering internship project with a production-style layered backend, a React frontend, automated API tests, and Docker support for the API.
- Demo Video
- Project Overview
- Key Features
- Architecture
- Project Structure
- Database Design
- Settlement Algorithm
- Screenshots
- API Endpoints
- API Documentation
- Setup Instructions
- Testing
- Design Decisions
- Future Improvements
- Project Highlights
A walkthrough of RightSplit is available here:
https://github.com/InnoxCodes/RightSplit/releases/tag/v1.0.0
Shared expenses in real life are rarely uniform. On a trip or group dinner, one person may order a drink, another may skip an appetizer, and a third may pay the full bill upfront. Splitting the total equally across all members often produces unfair results and forces manual corrections.
RightSplit addresses this by modeling each bill as an expense made up of multiple items, where each item has its own set of participants. The system then:
- Splits each item only among the people who consumed it.
- Materializes who owes the payer at the expense level.
- Aggregates net balances for the group.
- Suggests a small set of payment transactions to settle up.
Marriott Lunch
| Item | Amount | Participants |
|---|---|---|
| Burger | ₹300 | Mohit, Ankit |
| Pizza | ₹500 | Mohit, Rahul |
| Drink | ₹200 | Rahul, Ankit |
Mohit paid the bill. RightSplit calculates per-item shares, records debts to Mohit, updates group balances, and suggests who should pay whom to settle the group.
| Area | Capabilities |
|---|---|
| User management | Create, list, and delete users; unique phone numbers; UPI ID and phone validation |
| Group management | Create groups, list groups, list members, delete groups |
| Invite system | Unique invite codes per group; join via invite code |
| Expense management | Create, read, update, and delete expenses |
| Item-level participation | Multiple items per expense; per-item participant lists |
| Balance engine | Net balances derived from materialized expense shares |
| Settlement engine | Greedy creditor/debtor matching to reduce transaction count |
| Safety rules | Block deleting users or removing members when referenced in expenses; prevent duplicate group membership |
| Infrastructure | Dockerized API, SQLite persistence, foreign key enforcement |
| Testing | 45 automated API tests across users, groups, expenses, balances, settlements, and deletions |
| Area | Capabilities |
|---|---|
| Dashboard | Create users, create groups, join groups, browse existing users and groups |
| Invite links | Shareable /join/{inviteCode} URLs; copy invite link from group page |
| Join flow | Join as an existing user or create a new profile on the invite page |
| Group details | View members, expenses, balances, and settlement suggestions |
| Expense creation | Multi-item form with per-item participant selection |
| Expense editing | Inline edit on group page: add/remove items, change participants and payer |
| Deletion UX | Confirm dialogs; hover-to-reveal remove controls on users, members, and expenses |
| Money display | Consistent INR formatting from paise values |
| Responsive layout | Panel-based UI with mobile-friendly breakpoints |
React + Vite (Frontend)
|
v
Axios API client (/api proxy in development)
|
v
FastAPI (HTTP layer)
|
v
Service layer (business rules, calculations)
|
v
Repository layer (queries and persistence)
|
v
SQLAlchemy ORM
|
v
SQLite
| Layer | Responsibility |
|---|---|
| Frontend | Routing, forms, state, and presentation; calls REST endpoints |
| API client | Axios instance, error handling, base URL configuration |
| FastAPI endpoints | HTTP routing, status codes, dependency injection |
| Services | Validation, share calculation, balance and settlement logic |
| Repositories | Database access; keep SQL out of route handlers |
| Models | SQLAlchemy table definitions and relationships |
| Schemas | Pydantic request/response validation |
User
-> Group (via group_members)
-> Expense (paid_by_user_id)
-> ExpenseItem (line items)
-> ExpenseItemParticipant (per-user item share)
-> ExpenseShare (who owes the payer)
-> Group balances
-> Settlement suggestions (computed, not stored)
RightSplit/
├── app/
│ ├── api/v1/endpoints/ # FastAPI route handlers
│ ├── core/ # Database engine and session setup
│ ├── models/ # SQLAlchemy models
│ ├── repositories/ # Data access layer
│ ├── schemas/ # Pydantic schemas
│ ├── services/ # Business logic
│ └── main.py # Application entry point
├── frontend/
│ └── src/
│ ├── api/ # Axios API modules
│ ├── components/ # Reusable UI components
│ ├── pages/ # Route-level screens
│ ├── styles/ # Global CSS
│ └── utils/ # Money and lookup helpers
├── scripts/
│ └── dev-api.sh # Local API startup helper
├── tests/ # Pytest suite
├── docker-compose.yml # API container orchestration
├── Dockerfile # API image definition
├── package.json # Root scripts (run API + frontend)
├── requirements.txt # Python dependencies
└── README.md
| Layer | Technologies |
|---|---|
| Frontend | React, Vite, React Router, Axios |
| Backend | FastAPI, Uvicorn |
| Database | SQLite |
| ORM | SQLAlchemy 2.0 |
| Validation | Pydantic |
| Testing | Pytest, FastAPI TestClient |
| Dev tooling | Concurrently (run API + UI together) |
| Containerization | Docker, Docker Compose |
RightSplit uses seven tables. Money is stored as integer paise (1 rupee = 100 paise).
Stores people who can join groups, pay expenses, and participate in items.
| Field | Purpose |
|---|---|
id |
Primary key |
name |
Display name |
phone_number |
Unique identifier for the user |
upi_id |
Payment handle |
created_at |
Record timestamp |
Represents a shared expense context (trip, household, event).
| Field | Purpose |
|---|---|
id |
Primary key |
name |
Group title |
created_by_user_id |
Creator reference |
invite_code |
Unique join token |
created_at |
Record timestamp |
Many-to-many membership between users and groups.
| Field | Purpose |
|---|---|
group_id |
Group reference |
user_id |
User reference |
joined_at |
Membership timestamp |
Top-level bill paid by one group member.
| Field | Purpose |
|---|---|
group_id |
Owning group |
title |
Expense label (e.g. Marriott Lunch) |
paid_by_user_id |
Who paid upfront |
total_amount_paise |
Sum of all item amounts |
Individual line items inside an expense.
| Field | Purpose |
|---|---|
expense_id |
Parent expense |
name |
Item label (Burger, Petrol, etc.) |
amount_paise |
Item cost in paise |
Why this table exists: It is the core of item-level fairness. Each row records which user participated in which item and their exact share after splitting that item's amount among its participants only.
| Field | Purpose |
|---|---|
expense_item_id |
Item reference |
user_id |
Participating user |
share_amount_paise |
Calculated share for that user on that item |
Why this table exists: It materializes group-level debt after all items are processed. Each row means from_user owes to_user a specific amount for one expense. Balances are computed from these rows, which provides a clear audit trail and fast aggregation without recomputing every item on each balance request.
| Field | Purpose |
|---|---|
expense_id |
Source expense |
from_user_id |
Debtor |
to_user_id |
Creditor (typically the payer) |
amount_paise |
Debt amount |
Relationship summary
expense_itemsbreak a bill into splittable parts.expense_item_participantsdefine who shares each part.expense_sharesdefine who owes the payer after item math is complete.
Settlement suggestions are generated dynamically from current balances. They are not stored in the database.
- Compute each member's net balance from
expense_shares(incoming minus outgoing). - Split users into creditors (positive balance) and debtors (negative balance).
- Sort both lists by magnitude.
- Repeatedly match the largest remaining debtor with the largest remaining creditor.
- Emit a settlement for the smaller of the two remaining amounts.
- Continue until all balances are cleared.
Balances:
User1: +60000 paise
User2: -35000 paise
User3: -25000 paise
Suggested settlements:
User2 pays User1 35000 paise
User3 pays User1 25000 paise
User1: +50000 paise
User2: +30000 paise
User3: -40000 paise
User4: -40000 paise
Suggested settlements:
User3 pays User1 40000 paise
User4 pays User1 10000 paise
User4 pays User2 30000 paise
Total settled amounts match total outstanding balances.
For a complete visual walkthrough of the application, the following screenshots are recommended to be captured:
- Dashboard: Displays user creation, group creation, and join group modals, along with the statistics summary (Total Users, Total Groups) and rich group cards.
- Group Details: Displays the list of group members, expense ledger, calculated net balances, and the invite link panel.
- Add Expense: Displays the multi-item form demonstrating item-level splits and participant selection per line item.
- Balances & Settlements: Displays the net balances grid and the smart settlement suggestions panel.
- Docker Running: Displays container status indicating successful API service orchestration.
Reference assets included in this repository:
All routes below are implemented in the current codebase.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Service health check |
| Method | Endpoint | Description |
|---|---|---|
| POST | /users |
Create a user |
| GET | /users |
List all users |
| DELETE | /users/{user_id} |
Delete a user (blocked if referenced in groups or expenses) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /groups |
Create a group (creator is added as a member) |
| GET | /groups |
List all groups |
| DELETE | /groups/{group_id} |
Delete a group and cascade related data |
| GET | /groups/{group_id}/members |
List group members |
| DELETE | /groups/{group_id}/members/{user_id} |
Remove a member (blocked if referenced in group expenses) |
| POST | /groups/join/{invite_code} |
Join a group with a user ID |
| Method | Endpoint | Description |
|---|---|---|
| POST | /expenses |
Create an expense with items and participants |
| GET | /expenses/{expense_id} |
Get expense details |
| PUT | /expenses/{expense_id} |
Update expense title, payer, items, and participants |
| DELETE | /expenses/{expense_id} |
Delete an expense |
| GET | /groups/{group_id}/expenses |
List expenses for a group |
| Method | Endpoint | Description |
|---|---|---|
| GET | /groups/{group_id}/balances |
Get net balances for all members (and users referenced in shares) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /groups/{group_id}/settlements |
Get smart settlement suggestions |
FastAPI provides interactive Swagger UI documentation when the API is running:
http://localhost:8000/docs
Use this interface to explore request bodies, validation rules, and response schemas.
- Python 3.12+
- Node.js 18+ and npm
- Optional: Docker and Docker Compose (API only)
cd RightSplitpython3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtFrom the project root:
npm run install:allThis installs root dev dependencies (concurrently) and frontend packages.
npm run dev| Service | URL |
|---|---|
| Web app | http://localhost:5173 |
| API | http://localhost:8000 |
| Swagger docs | http://localhost:8000/docs |
npm start is an alias for npm run dev.
In development, the frontend proxies /api/* to the backend, which avoids most CORS issues.
API only:
source .venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Frontend only (API must already be running on port 8000):
cd frontend && npm run devOverride the API URL if needed:
# frontend/.env.local
VITE_API_BASE_URL=http://localhost:8000Docker Compose runs the API only. Start the frontend separately with npm run dev if you want the UI.
Build and run:
docker compose up --build| Item | Value |
|---|---|
| API URL | http://localhost:8000 |
| SQLite volume | rightsplit_sqlite_data |
Stop containers:
docker compose downStop and remove persisted database volume:
docker compose down -vThe repository includes 45 automated tests (verified via pytest --collect-only).
source .venv/bin/activate
python -m pytestVerbose run:
python -m pytest -v| File | Coverage |
|---|---|
tests/test_users.py |
User creation, duplicate phone validation, user deletion rules |
tests/test_groups.py |
Group creation, invite join flow, membership listing |
tests/test_expenses.py |
Expense creation, validation, rounding, share generation, expense updates |
tests/test_balances.py |
Single and multi-expense balances, zero-balance members, balance sum invariants |
tests/test_settlements.py |
Settlement correctness, no self-payments, amount conservation |
tests/test_deletions.py |
Expense, group, and member deletion behavior |
tests/test_goa_trip_group.py |
Multi-expense scenarios with 3–4 items, mixed payers, realistic group flows |
Tests use an isolated SQLite database per test session via pytest fixtures in tests/conftest.py.
Expense-level equal splitting cannot represent real-world consumption patterns. Item-level participants allow a single bill to contain different sharing rules per line item without manual adjustment.
Recomputing debts from raw items on every balance request would work but couples read paths to write logic. Materialized shares provide:
- A stable financial audit trail per expense
- Faster balance aggregation
- A clear boundary between item math and group-level debt
Settlement suggestions are derived from current balances. Persisting them would create stale data whenever expenses change. Dynamic generation keeps suggestions always aligned with the latest group state.
SQLite keeps local setup simple for development, demos, and internship evaluation. The repository and service layers are database-agnostic enough to migrate to PostgreSQL later with limited changes.
The following items are not implemented yet:
- OCR receipt scanning and automatic item extraction
- PostgreSQL deployment profile
- Authentication and authorization
- UPI payment deep links or payment status tracking
- Push or email notifications for new expenses and settlements
- Advanced split types (percentage, exact amount, weighted shares)
- Expense categories and tagging
- Group analytics and spending dashboards
- Item-level expense modeling — Fair splits when participants differ per line item
- Paise-safe financial math — Integer-based calculations with deterministic rounding
- Optimized settlement generation — Greedy matching to reduce payment count
- Layered backend architecture — Endpoints, services, repositories, and models with clear boundaries
- 45 automated API tests — Including multi-expense group scenarios
- Full React frontend — Dashboard, invite links, expense CRUD, balances, and settlements
- Dockerized API — Repeatable backend deployment with persisted SQLite volume
- Production-oriented validation — Membership checks, duplicate prevention, and safe deletion rules
This project was developed for educational and internship evaluation purposes. Add a license file here if you plan to open-source the repository.

