Hostel Mess Bill Splitter
Divide a month's mess spending by the meals each member actually ate.
1Introduction: the problem it solves
A hostel mess buys vegetables, rice and gas all month, and at month end the bill is split equally. That is unfair to whoever went home for a week, and everybody knows it, which is why the argument happens every month.
Splitting by meals eaten is obviously fairer and nobody does it by hand, because it means totalling a register of daily meal counts. That is exactly what a database is for.
A hostel mess, a PG kitchen, a shared flat, or a school trip's food fund.
Why it is worth computerising
A mess bill divided equally is simple to calculate and unfair to anybody who was away, and everyone living there knows it. The argument at the end of every month is not really about money; it is about the fact that nobody has the figures to settle it.
The figures exist — the daily meal register has them — but totalling a month of them by hand and dividing the bazaar spending in proportion is precisely the work nobody volunteers for. A database does the totalling, and the result has a property the equal split never had: the shares add back exactly to what was spent, and every member can check their own count against the register.
Objectives
- To record what the mess actually spends, item by item, through the month
- To record how many meals each member ate on each day
- To work out a rate per meal rather than a rate per person
- To give every member a share proportional to what they ate
- To ensure the shares add back exactly to what was spent
2How the job is done today
Before writing anything it is worth asking how the work is handled at present, and why each of those answers falls short. These were examined:
Simple, and the reason the argument happens. Anyone who was away for a week pays for meals they did not eat.
They exist, and they want the whole mess on smartphones with an account each. A three-person hostel kitchen will not do that.
The right data, collected by hand. The totalling and the division are exactly the work nobody wants at month end.
3Functionalities
- Three tables: members, monthly expenses, and a daily meal register
- Records each member's meals for each day
- Totals the month's spending with SUM()
- Works out a rate per meal instead of a rate per person
- Gives each member a share proportional to what they ate
- The shares add back to the total exactly, which is what stops the argument
The functions that provide them
| Function | Arguments | What it does |
|---|---|---|
record_meals() | member_id, day, meals | Record how many meals one member ate on one day. |
month_bill() | month | Total the month's bazaar spending and divide it by meals eaten. |
4Technical details
| Language | Python 3 |
| Storage | MySQL, through mysql-connector-python |
| Modules used |
|
5How the data is stored
Meals are stored per member per day rather than as a monthly total. It is more rows, and it is worth it: a disputed bill can be checked against a single day, and a member who joins mid-month needs no special handling.
Table: members
| Field | Type | Description |
|---|---|---|
member_id | INT (primary key, auto) | Unique number for each member of the mess |
name | VARCHAR(30) (required) | Member's name |
room | VARCHAR(6) | Room number in the hostel |
Table: expenses
| Field | Type | Description |
|---|---|---|
expense_id | INT (primary key, auto) | Unique number for each purchase |
month | VARCHAR(7) | Month the spending belongs to, as YYYY-MM |
item | VARCHAR(30) | What was bought |
amount | DECIMAL(10,2) | What it cost |
Table: meals
| Field | Type | Description |
|---|---|---|
entry_id | INT (primary key, auto) | Unique number for each day's entry |
member_id | INT | Which member — refers to members |
meal_date | DATE | The day |
count | INT | How many meals that member ate that day |
Sample rows in members
| name | room |
|---|---|
Ananya Sen | B-12 |
Rohit Das | B-14 |
Meera Rai | C-03 |
Sample rows in expenses
| month | item | amount |
|---|---|---|
2026-08 | Vegetables | 4200.00 |
2026-08 | Rice and atta | 5100.00 |
2026-08 | Milk and eggs | 2700.00 |
2026-08 | Gas cylinder | 1150.00 |
Sample rows in meals
| member_id | meal_date | count |
|---|---|---|
1 | 2026-08-01 | 2 |
2 | 2026-08-01 | 2 |
3 | 2026-08-01 | 1 |
1 | 2026-08-02 | 2 |
2 | 2026-08-02 | 0 |
3 | 2026-08-02 | 2 |
6How it works, step by step
record_meals() INSERTs one row per member per day.
SUM(amount) over the month's expenses gives what the mess actually spent.
Total spending divided by total meals is the cost of one meal.
A JOIN with GROUP BY gives each member's meal count, and count x rate is their share.
7Source code
# ---------------------------------------------------------------------------
# mess.py
#
# Splits a hostel mess bill by the meals each member actually ate, using
# Python with MySQL.
#
# Three tables: the members, what the mess spent, and a register of meals eaten
# per member per day. Storing meals daily rather than as a monthly total is
# what lets a disputed figure be checked against a single day.
# ---------------------------------------------------------------------------
import mysql.connector
db = mysql.connector.connect(host="localhost", user="root",
passwd="mess", database="lambdalab_mess")
cur = db.cursor()
def record_meals(member_id, day, meals):
"""Record how many meals one member ate on one day."""
cur.execute("INSERT INTO meals (member_id, meal_date, count) VALUES (%s, %s, %s)",
(member_id, day, meals))
db.commit()
def month_bill(month):
"""Total the month's bazaar spending and divide it by meals eaten."""
# SUM() adds a whole column inside the database and returns one number, so
# the rows never have to travel into Python at all.
cur.execute("SELECT SUM(amount) FROM expenses WHERE month = %s", (month,))
total = cur.fetchone()[0] # [0] because the row holds one value
# LIKE '2026-08%' matches every date inside that month. A proper date range
# would be better, and is listed under future scope.
cur.execute("SELECT SUM(count) FROM meals WHERE meal_date LIKE %s", (month + "%",))
all_meals = cur.fetchone()[0]
# The whole idea of the program in one line: a rate per MEAL, not per head.
rate = total / all_meals
print("LAMBDALAB HOSTEL MESS — {}".format(month))
print("Bazaar spending : Rs {:.2f}".format(total))
print("Meals eaten : {}".format(all_meals))
print("Rate per meal : Rs {:.2f}\n".format(rate))
# GROUP BY gives one row per member with their own total, and the JOIN
# brings in the name to go with the id.
cur.execute("SELECT m.name, SUM(t.count) FROM meals t"
" JOIN members m ON t.member_id = m.member_id"
" WHERE t.meal_date LIKE %s GROUP BY m.name ORDER BY m.name", (month + "%",))
print("{:<18}{:>8}{:>12}".format("MEMBER", "MEALS", "SHARE"))
# Because every meal is charged at the same rate, the shares add back
# exactly to what was spent — which is what settles the argument.
for name, meals in cur.fetchall():
print("{:<18}{:>8}{:>12.2f}".format(name, meals, meals * rate))
# --- the program itself ----------------------------------------------------
month_bill("2026-08")
db.close()CREATE DATABASE IF NOT EXISTS lambdalab_mess;
USE lambdalab_mess;
CREATE TABLE members (
member_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
room VARCHAR(6)
);
CREATE TABLE expenses (
expense_id INT PRIMARY KEY AUTO_INCREMENT,
month VARCHAR(7),
item VARCHAR(30),
amount DECIMAL(10,2)
);
CREATE TABLE meals (
entry_id INT PRIMARY KEY AUTO_INCREMENT,
member_id INT,
meal_date DATE,
count INT DEFAULT 0,
FOREIGN KEY (member_id) REFERENCES members(member_id)
);
INSERT INTO members (name, room) VALUES
('Ananya Sen', 'B-12'), ('Rohit Das', 'B-14'), ('Meera Rai', 'C-03');
INSERT INTO expenses (month, item, amount) VALUES
('2026-08', 'Vegetables', 4200.00),
('2026-08', 'Rice and atta', 5100.00),
('2026-08', 'Milk and eggs', 2700.00),
('2026-08', 'Gas cylinder', 1150.00);
INSERT INTO meals (member_id, meal_date, count) VALUES
(1, '2026-08-01', 2), (2, '2026-08-01', 2), (3, '2026-08-01', 1),
(1, '2026-08-02', 2), (2, '2026-08-02', 0), (3, '2026-08-02', 2),
(1, '2026-08-03', 2), (2, '2026-08-03', 2), (3, '2026-08-03', 2);The full report as a PDF — cover page, certificate, index, code, output, future scope and references, ready to print and fill in. Or the code on its own as a zip.
8Sample output
This is a real run, not a mock-up — the transcript below is what the program actually printed.
C:\LambdaLab\Projects\Mess-Bill> python mess.py
LAMBDALAB HOSTEL MESS — 2026-08
Bazaar spending : Rs 13150.00
Meals eaten : 15
Rate per meal : Rs 876.67
MEMBER MEALS SHARE
Ananya Sen 6 5260.00
Meera Rai 5 4383.33
Rohit Das 4 3506.67schema.sql into MySQL and change the user and password in the connect() call to your own. The output above is what the queries return against the sample rows in that schema.9Testing
Every case below was actually run and the result recorded as it appeared — including the ones expected to fail.
| Test case | Expected | Actual |
|---|---|---|
| Total spending for the month | Rs 13150.00 | Rs 13150.00 |
| Total meals eaten | 15 | 15 meals |
| Rate per meal | 13150 / 15 = Rs 876.67 | Rs 876.67 |
| Shares add back to the total | The three shares sum to 13150.00 | Rs 13150.00 |
| A member who ate nothing that month | Share of zero, no error | Rs 0.00 |
Two kinds of case are in there on purpose. The boundary cases test the edge of a rule, where a program is most often wrong by one. The failure cases check that it refuses cleanly and says why, instead of quietly producing a wrong answer.
10Advantages
Set against the ways the job is done today:
- The split is proportional to what each member actually ate
- The shares add back exactly to what was spent, which ends the argument
- A disputed figure can be checked against a single day's entry
- A member joining mid-month needs no special treatment
- The rate per meal is a figure everybody can see and question
11Future scope
What this version does not do
A report should be honest about its own limits, and each of these is the reason for one of the additions below:
- Guest meals are not charged to the member who invited them
- Payments received are not recorded, so dues are not tracked
- The month is matched with LIKE on a date column, which a proper date range would do better
- The leftover paisa in the division is not assigned to anybody
Proposed enhancements
This is also where you make the project yours. Pick one or two of these, or something nobody here thought of:
- A guest meal charged to the member who invited them
- Print each member's slip separately
- Chart the rate per meal across months, which shows inflation plainly
- Let members mark themselves absent in advance so the cook can buy less
- Track who has paid and what is outstanding
12What you may have to teach yourself
CBSE expects some self-learning in a project, and says so. For this one, that means:
- SUM() and GROUP BY together, which is the heart of every report query
- LIKE '2026-08%' for matching a month, and why a DATE column would let you do better
- Rounding: whose share carries the leftover paisa? Decide it and say so in the report
13Conclusion
The month's bill is divided by meals eaten rather than by heads counted, and the shares add back exactly to what was spent.
The argument this was written to settle was never about arithmetic; it was about not having the figures. Collecting meals daily, one row at a time, is what made the fair split possible at all.
14References
Every report needs a bibliography. This one used:
- Computer Science with Python, Class XII — the NCERT / CBSE prescribed textbook, for the chapters on database concepts, SQL and interfacing Python with a database
- Computer Science with Python, Class XI — for functions, lists, dictionaries and string handling
- Python 3 documentation — https://docs.python.org/3/
- MySQL 8.0 Reference Manual and the Connector/Python Developer Guide — https://dev.mysql.com/doc/
- CBSE Senior School Curriculum, Computer Science (Subject Code 083) — the project guidelines this report follows
- LambdaLab — https://www.lambdalab.in