Skip to content

Repository files navigation

RightSplit

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.


Table of Contents


Demo Video

A walkthrough of RightSplit is available here:

https://github.com/InnoxCodes/RightSplit/releases/tag/v1.0.0

Project Overview

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:

  1. Splits each item only among the people who consumed it.
  2. Materializes who owes the payer at the expense level.
  3. Aggregates net balances for the group.
  4. Suggests a small set of payment transactions to settle up.

Example

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.


Key Features

Backend

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

Frontend

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

Architecture

System diagram

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 responsibilities

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

Expense data flow

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)

Project Structure

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

Tech stack

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

Database Design

RightSplit uses seven tables. Money is stored as integer paise (1 rupee = 100 paise).

Core entities

users

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

groups

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

group_members

Many-to-many membership between users and groups.

Field Purpose
group_id Group reference
user_id User reference
joined_at Membership timestamp

Expense model (central design)

expenses

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

expense_items

Individual line items inside an expense.

Field Purpose
expense_id Parent expense
name Item label (Burger, Petrol, etc.)
amount_paise Item cost in paise

expense_item_participants

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

expense_shares

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_items break a bill into splittable parts.
  • expense_item_participants define who shares each part.
  • expense_shares define who owes the payer after item math is complete.

Settlement Algorithm

Settlement suggestions are generated dynamically from current balances. They are not stored in the database.

Steps

  1. Compute each member's net balance from expense_shares (incoming minus outgoing).
  2. Split users into creditors (positive balance) and debtors (negative balance).
  3. Sort both lists by magnitude.
  4. Repeatedly match the largest remaining debtor with the largest remaining creditor.
  5. Emit a settlement for the smaller of the two remaining amounts.
  6. Continue until all balances are cleared.

Example

Balances:

User1: +60000 paise
User2: -35000 paise
User3: -25000 paise

Suggested settlements:

User2 pays User1 35000 paise
User3 pays User1 25000 paise

Multi-creditor example

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.


Recommended Screenshots

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:

  • FastAPI Swagger UI FastAPI Swagger UI

  • Automated Pytest Run (45 Tests) Automated Pytest Run


API Endpoints

All routes below are implemented in the current codebase.

Health

Method Endpoint Description
GET /health Service health check

Users

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)

Groups

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

Expenses

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

Balances

Method Endpoint Description
GET /groups/{group_id}/balances Get net balances for all members (and users referenced in shares)

Settlements

Method Endpoint Description
GET /groups/{group_id}/settlements Get smart settlement suggestions

API Documentation

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.


Setup Instructions

Prerequisites

  • Python 3.12+
  • Node.js 18+ and npm
  • Optional: Docker and Docker Compose (API only)

Local development

1. Clone and enter the project

cd RightSplit

2. Backend setup

python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt

3. Frontend setup

From the project root:

npm run install:all

This installs root dev dependencies (concurrently) and frontend packages.

4. Run API and frontend together

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.

Run services separately (optional)

API only:

source .venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Frontend only (API must already be running on port 8000):

cd frontend && npm run dev

Override the API URL if needed:

# frontend/.env.local
VITE_API_BASE_URL=http://localhost:8000

Docker setup

Docker 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 down

Stop and remove persisted database volume:

docker compose down -v

Testing

The repository includes 45 automated tests (verified via pytest --collect-only).

Run tests

source .venv/bin/activate
python -m pytest

Verbose run:

python -m pytest -v

Test modules

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.


Design Decisions

Why item-level participation?

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.

Why materialize expense_shares?

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

Why generate settlements dynamically?

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.

Why SQLite?

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.


Future Improvements

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

Project Highlights

  • 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

License

This project was developed for educational and internship evaluation purposes. Add a license file here if you plan to open-source the repository.

About

Full-stack expense sharing application with item-level participation, smart settlements, FastAPI, React, Docker, and automated testing.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages