splitkaro 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- splitkaro-0.1.0/LICENSE +21 -0
- splitkaro-0.1.0/PKG-INFO +323 -0
- splitkaro-0.1.0/README.md +292 -0
- splitkaro-0.1.0/pyproject.toml +60 -0
- splitkaro-0.1.0/setup.cfg +4 -0
- splitkaro-0.1.0/src/splitkaro/__init__.py +24 -0
- splitkaro-0.1.0/src/splitkaro/__main__.py +5 -0
- splitkaro-0.1.0/src/splitkaro/cli.py +445 -0
- splitkaro-0.1.0/src/splitkaro/csvio.py +125 -0
- splitkaro-0.1.0/src/splitkaro/ledger.py +115 -0
- splitkaro-0.1.0/src/splitkaro/models.py +205 -0
- splitkaro-0.1.0/src/splitkaro/money.py +96 -0
- splitkaro-0.1.0/src/splitkaro/report.py +69 -0
- splitkaro-0.1.0/src/splitkaro/storage.py +128 -0
- splitkaro-0.1.0/src/splitkaro/upi.py +33 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/PKG-INFO +323 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/SOURCES.txt +24 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/dependency_links.txt +1 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/entry_points.txt +2 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/requires.txt +3 -0
- splitkaro-0.1.0/src/splitkaro.egg-info/top_level.txt +1 -0
- splitkaro-0.1.0/tests/test_cli.py +139 -0
- splitkaro-0.1.0/tests/test_ledger.py +105 -0
- splitkaro-0.1.0/tests/test_models.py +84 -0
- splitkaro-0.1.0/tests/test_money.py +68 -0
- splitkaro-0.1.0/tests/test_storage_csv_upi.py +138 -0
splitkaro-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kanhaiya Kumar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
splitkaro-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splitkaro
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bill splitter for friends, flatmates and trips: split shared expenses, see who owes whom, and settle up with the fewest payments and UPI links.
|
|
5
|
+
Author: Kanhaiya Kumar
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/K70281095/splitkaro
|
|
8
|
+
Project-URL: Issues, https://github.com/K70281095/splitkaro/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/K70281095/splitkaro/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: bill-splitter,split-expenses,expense-splitter,group-expenses,settle-up,splitwise-alternative,upi,india,roommates,travel,personal-finance,cli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# splitkaro: split expenses & settle up with UPI
|
|
33
|
+
|
|
34
|
+
**A bill splitter for friends, flatmates and trips: track shared expenses, see who owes whom, and settle up with the fewest payments and one-tap UPI links.**
|
|
35
|
+
|
|
36
|
+
[](https://github.com/K70281095/splitkaro/actions/workflows/ci.yml)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
*Split karo* is Hinglish for "split it". It is the phrase every group says at the end of a trip,
|
|
42
|
+
right before the *hisaab* (the accounts) gets complicated.
|
|
43
|
+
|
|
44
|
+
Every trip, every shared flat and every office lunch ends with the same questions:
|
|
45
|
+
|
|
46
|
+
- Who paid for what, and who still owes whom?
|
|
47
|
+
- The hotel was ₹9,000, but a couple shared one room. How do we split that fairly?
|
|
48
|
+
- Five people owe each other money. Do we really need ten transfers?
|
|
49
|
+
- Somebody paid in dollars. What is that in rupees?
|
|
50
|
+
|
|
51
|
+
`splitkaro` answers all of them from your terminal. It keeps the group's records in one readable
|
|
52
|
+
JSON file, does every calculation with exact decimal arithmetic so the totals always add up to
|
|
53
|
+
the paisa, and works out the smallest set of payments that settles everyone, with UPI links you
|
|
54
|
+
can open in GPay, PhonePe, Paytm or BHIM.
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
$ splitkaro balances
|
|
58
|
+
Asha gets back ₹2,618.00
|
|
59
|
+
Ravi owes ₹682.00
|
|
60
|
+
Meera owes ₹1,936.00
|
|
61
|
+
|
|
62
|
+
$ splitkaro settle --upi
|
|
63
|
+
2 payment(s) settle everything:
|
|
64
|
+
Meera pays Asha ₹1,936.00
|
|
65
|
+
upi://pay?pa=asha%40okbank&pn=Asha&am=1936.00&cu=INR&tn=splitkaro%3A+Goa+trip
|
|
66
|
+
Ravi pays Asha ₹682.00
|
|
67
|
+
upi://pay?pa=asha%40okbank&pn=Asha&am=682.00&cu=INR&tn=splitkaro%3A+Goa+trip
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Contents
|
|
73
|
+
|
|
74
|
+
- [Features](#features)
|
|
75
|
+
- [Install](#install)
|
|
76
|
+
- [A full example: a Goa trip](#a-full-example-a-goa-trip)
|
|
77
|
+
- [Ways to split](#ways-to-split)
|
|
78
|
+
- [Command reference](#command-reference)
|
|
79
|
+
- [Your data](#your-data)
|
|
80
|
+
- [Using splitkaro from Python](#using-splitkaro-from-python)
|
|
81
|
+
- [How it works](#how-it-works)
|
|
82
|
+
- [Roadmap](#roadmap)
|
|
83
|
+
- [Contributing](#contributing)
|
|
84
|
+
- [License](#license)
|
|
85
|
+
|
|
86
|
+
## Features
|
|
87
|
+
|
|
88
|
+
| | |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **Four ways to split** | Equally, by exact amounts, by percentage, or by shares (a couple counts as 2). |
|
|
91
|
+
| **Several payers** | "I paid ₹1,000 and you paid ₹500" for the same bill works naturally. |
|
|
92
|
+
| **Fewest payments** | Suggests at most *n − 1* transfers for *n* people, and pairs up exact matches first. |
|
|
93
|
+
| **UPI links** | One `upi://pay` link per payment; the payer just confirms it in their app. |
|
|
94
|
+
| **Exact to the paisa** | Decimal arithmetic and fair rounding: ₹100 split three ways is 33.34 + 33.33 + 33.33. |
|
|
95
|
+
| **Foreign currency** | Record an expense in USD or EUR; it is converted with the rate you set. |
|
|
96
|
+
| **Reports** | Spending by category and month, and who paid vs. who consumed. |
|
|
97
|
+
| **Statements** | A running, line-by-line history of one person's balance. |
|
|
98
|
+
| **CSV import/export** | Move data to and from spreadsheets. Imports are all-or-nothing. |
|
|
99
|
+
| **Your data, your file** | Plain JSON you can read, back up, sync or put under git. No account, no server, no ads. |
|
|
100
|
+
| **Zero dependencies** | Pure Python standard library, Python 3.9+. |
|
|
101
|
+
|
|
102
|
+
## Install
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install splitkaro
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or straight from GitHub:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install git+https://github.com/K70281095/splitkaro.git
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Check it works:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
splitkaro --version
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## A full example: a Goa trip
|
|
121
|
+
|
|
122
|
+
Asha, Ravi and Meera go to Goa. Asha travels with her partner, so on the hotel she counts as two.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
splitkaro init "Goa trip"
|
|
126
|
+
splitkaro member add Asha Ravi Meera
|
|
127
|
+
splitkaro member upi Asha asha@okbank # so others can pay her with one tap
|
|
128
|
+
|
|
129
|
+
# Hotel: Asha paid, split by shares (Asha 2, Ravi 1, Meera 1)
|
|
130
|
+
splitkaro add "Hotel (2 nights)" 9000 --paid-by Asha --shares Asha=2,Ravi=1,Meera=1 --category stay
|
|
131
|
+
|
|
132
|
+
# Dinner: Ravi paid, split equally among everyone (the default)
|
|
133
|
+
splitkaro add "Dinner at the shack" 2400 --paid-by Ravi --category food
|
|
134
|
+
|
|
135
|
+
# Scooters: two people paid, and only they used them
|
|
136
|
+
splitkaro add "Scooter rental" 1500 --paid-by Asha=1000 --paid-by Meera=500 --among Asha,Meera --category travel
|
|
137
|
+
|
|
138
|
+
# Parasailing was paid in dollars
|
|
139
|
+
splitkaro rate USD 83.20
|
|
140
|
+
splitkaro add "Parasailing" 30 --currency USD --paid-by Meera --category fun
|
|
141
|
+
|
|
142
|
+
# Groceries: everyone took different things
|
|
143
|
+
splitkaro add "Groceries" 1260 --paid-by Ravi --exact Asha=500,Ravi=460,Meera=300 --category food
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Where does everyone stand, and how do they square up?
|
|
147
|
+
|
|
148
|
+
```console
|
|
149
|
+
$ splitkaro settle
|
|
150
|
+
2 payment(s) settle everything:
|
|
151
|
+
Meera pays Asha ₹1,936.00
|
|
152
|
+
Ravi pays Asha ₹682.00
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Where did the money go?
|
|
156
|
+
|
|
157
|
+
```console
|
|
158
|
+
$ splitkaro report
|
|
159
|
+
Goa trip: 5 expense(s), ₹16,656.00 in total
|
|
160
|
+
|
|
161
|
+
By category
|
|
162
|
+
stay ₹9,000.00 54%
|
|
163
|
+
food ₹3,660.00 22%
|
|
164
|
+
fun ₹2,496.00 15%
|
|
165
|
+
travel ₹1,500.00 9%
|
|
166
|
+
|
|
167
|
+
By month
|
|
168
|
+
2026-11 ₹16,656.00
|
|
169
|
+
|
|
170
|
+
member paid share
|
|
171
|
+
Asha ₹10,000.00 ₹7,382.00
|
|
172
|
+
Ravi ₹3,660.00 ₹4,342.00
|
|
173
|
+
Meera ₹2,996.00 ₹4,932.00
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Meera wants to see exactly how she ended up owing ₹1,936:
|
|
177
|
+
|
|
178
|
+
```console
|
|
179
|
+
$ splitkaro statement meera
|
|
180
|
+
2026-11-01 -₹2,250.00 -₹2,250.00 #1 Hotel (2 nights)
|
|
181
|
+
2026-11-01 -₹800.00 -₹3,050.00 #2 Dinner at the shack
|
|
182
|
+
2026-11-02 -₹250.00 -₹3,300.00 #3 Scooter rental
|
|
183
|
+
2026-11-02 ₹1,664.00 -₹1,636.00 #4 Parasailing
|
|
184
|
+
2026-11-03 -₹300.00 -₹1,936.00 #5 Groceries
|
|
185
|
+
|
|
186
|
+
Meera owes ₹1,936.00
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Once the money has moved, record it, and everyone is back to zero:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
splitkaro pay Meera Asha 1936
|
|
193
|
+
splitkaro pay Ravi Asha 682
|
|
194
|
+
splitkaro settle # Everyone is settled up. 🎉
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The same trip is available as [`examples/goa-trip.csv`](examples/goa-trip.csv), which you can
|
|
198
|
+
load with `splitkaro import examples/goa-trip.csv`.
|
|
199
|
+
|
|
200
|
+
## Ways to split
|
|
201
|
+
|
|
202
|
+
| Option | Example | Meaning |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| *(none)* | `--paid-by Ravi` | Equally among **all** members. |
|
|
205
|
+
| `--among` | `--among Asha,Meera` | Equally among the listed members. |
|
|
206
|
+
| `--exact` | `--exact Asha=500,Ravi=460,Meera=300` | Fixed amounts; must add up to the total. |
|
|
207
|
+
| `--percent` | `--percent Asha=50,Ravi=25,Meera=25` | Percentages; must add up to 100. |
|
|
208
|
+
| `--shares` | `--shares Asha=2,Ravi=1,Meera=1` | Proportional weights, e.g. couples, or nights stayed. |
|
|
209
|
+
|
|
210
|
+
Whoever paid is given with `--paid-by NAME`, or `--paid-by NAME=AMOUNT` repeated when several
|
|
211
|
+
people paid the same bill. Names are case-insensitive.
|
|
212
|
+
|
|
213
|
+
## Command reference
|
|
214
|
+
|
|
215
|
+
| Command | What it does |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `splitkaro init NAME [--currency INR]` | Start a group (creates `splitkaro.json`). |
|
|
218
|
+
| `splitkaro member add NAME... [--upi ID]` | Add members. |
|
|
219
|
+
| `splitkaro member upi NAME ID` | Set a member's UPI ID. |
|
|
220
|
+
| `splitkaro member list` / `remove NAME` | List members, or remove one who has no history. |
|
|
221
|
+
| `splitkaro add DESC AMOUNT --paid-by ...` | Record an expense (`--category`, `--date`, `--currency`, `--note`). |
|
|
222
|
+
| `splitkaro list [--month YYYY-MM] [--category C] [--member M]` | List expenses. |
|
|
223
|
+
| `splitkaro delete ID` | Delete an expense. |
|
|
224
|
+
| `splitkaro balances [--json]` | Who gets money back, who owes. |
|
|
225
|
+
| `splitkaro settle [--upi] [--json]` | Fewest payments to settle everything. |
|
|
226
|
+
| `splitkaro pay FROM TO AMOUNT` | Record a payment between members. |
|
|
227
|
+
| `splitkaro statement NAME` | Running history of one member's balance. |
|
|
228
|
+
| `splitkaro report [filters] [--json]` | Totals by category, month and member. |
|
|
229
|
+
| `splitkaro rate CUR VALUE` | Exchange rate: 1 CUR = VALUE in the group currency. |
|
|
230
|
+
| `splitkaro export FILE` / `import FILE` | CSV out and in (`-` for stdout/stdin). |
|
|
231
|
+
|
|
232
|
+
Every command accepts `--file PATH` (or the `SPLITKARO_FILE` environment variable) to work with
|
|
233
|
+
more than one group, for example `splitkaro -f flat.json balances`.
|
|
234
|
+
|
|
235
|
+
Errors are printed as `splitkaro: <message>` with exit code `1`, and nothing is saved when a command
|
|
236
|
+
fails.
|
|
237
|
+
|
|
238
|
+
## Your data
|
|
239
|
+
|
|
240
|
+
A group lives in a single JSON file, `splitkaro.json` by default (shortened here):
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"version": 1,
|
|
245
|
+
"name": "Goa trip",
|
|
246
|
+
"currency": "INR",
|
|
247
|
+
"members": [{ "name": "Asha", "upi": "asha@okbank" }, { "name": "Ravi", "upi": null }],
|
|
248
|
+
"expenses": [
|
|
249
|
+
{
|
|
250
|
+
"id": 2,
|
|
251
|
+
"date": "2026-11-01",
|
|
252
|
+
"description": "Dinner at the shack",
|
|
253
|
+
"amount": "2400.00",
|
|
254
|
+
"paid_by": { "Ravi": "2400.00" },
|
|
255
|
+
"split": { "kind": "equal", "among": ["Asha", "Ravi", "Meera"], "values": {} }
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- Amounts are stored as strings, never floats, so nothing is ever rounded away.
|
|
262
|
+
- Files are written atomically: a crash or a full disk cannot leave a half-written file.
|
|
263
|
+
- There is no account, server or tracking. Share the file however your group likes.
|
|
264
|
+
|
|
265
|
+
The CSV format is documented in [docs/csv-format.md](docs/csv-format.md).
|
|
266
|
+
|
|
267
|
+
## Using splitkaro from Python
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
import datetime as dt
|
|
271
|
+
from decimal import Decimal
|
|
272
|
+
|
|
273
|
+
from splitkaro import Expense, Group, Split, balances, settle
|
|
274
|
+
|
|
275
|
+
group = Group("Flat 302")
|
|
276
|
+
for name in ("Asha", "Ravi", "Meera"):
|
|
277
|
+
group.add_member(name)
|
|
278
|
+
|
|
279
|
+
group.add_expense(Expense(
|
|
280
|
+
id=0, description="Electricity", amount=Decimal("3120"),
|
|
281
|
+
paid_by={"Ravi": Decimal("3120")}, split=Split.equal(["Asha", "Ravi", "Meera"]),
|
|
282
|
+
date=dt.date(2026, 11, 5), currency="INR", category="bills",
|
|
283
|
+
))
|
|
284
|
+
|
|
285
|
+
for t in settle(balances(group)):
|
|
286
|
+
print(f"{t.sender} pays {t.receiver} {t.amount}")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## How it works
|
|
290
|
+
|
|
291
|
+
- **Rounding:** amounts are split with the largest-remainder method on whole paise, so the parts
|
|
292
|
+
always add up to the exact total and nobody is ever short-changed by more than one paisa.
|
|
293
|
+
- **Balances:** each member's balance is what they paid minus their share, plus payments made
|
|
294
|
+
minus payments received. Balances always add up to exactly zero.
|
|
295
|
+
- **Settling:** exact matches (someone owes exactly what someone else is owed) are paired first;
|
|
296
|
+
the rest is settled greedily between the largest debtor and the largest creditor. This never
|
|
297
|
+
needs more than *n − 1* payments.
|
|
298
|
+
|
|
299
|
+
The details, with worked examples, are in [docs/how-it-works.md](docs/how-it-works.md).
|
|
300
|
+
|
|
301
|
+
## Roadmap
|
|
302
|
+
|
|
303
|
+
- [ ] Recurring expenses (rent, Wi-Fi, maid) added automatically each month
|
|
304
|
+
- [ ] UPI QR codes in the terminal
|
|
305
|
+
- [ ] Simplify across groups (one payment per pair of people for several trips)
|
|
306
|
+
- [ ] Budgets per category with warnings
|
|
307
|
+
- [ ] Hindi and other Indian language output
|
|
308
|
+
- [ ] A small web or mobile front end on top of the same file format
|
|
309
|
+
|
|
310
|
+
Ideas and pull requests are welcome, see the [issues](https://github.com/K70281095/splitkaro/issues).
|
|
311
|
+
|
|
312
|
+
## Contributing
|
|
313
|
+
|
|
314
|
+
Bug reports, ideas, translations and code are all welcome. Please read
|
|
315
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). To report a
|
|
316
|
+
security problem, see [SECURITY.md](SECURITY.md).
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
[MIT](LICENSE) © 2026 Kanhaiya Kumar
|
|
321
|
+
|
|
322
|
+
*UPI is a system of the National Payments Corporation of India. splitkaro only creates standard
|
|
323
|
+
`upi://pay` links and never handles money, bank details or PINs.*
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# splitkaro: split expenses & settle up with UPI
|
|
2
|
+
|
|
3
|
+
**A bill splitter for friends, flatmates and trips: track shared expenses, see who owes whom, and settle up with the fewest payments and one-tap UPI links.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/K70281095/splitkaro/actions/workflows/ci.yml)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
*Split karo* is Hinglish for "split it". It is the phrase every group says at the end of a trip,
|
|
11
|
+
right before the *hisaab* (the accounts) gets complicated.
|
|
12
|
+
|
|
13
|
+
Every trip, every shared flat and every office lunch ends with the same questions:
|
|
14
|
+
|
|
15
|
+
- Who paid for what, and who still owes whom?
|
|
16
|
+
- The hotel was ₹9,000, but a couple shared one room. How do we split that fairly?
|
|
17
|
+
- Five people owe each other money. Do we really need ten transfers?
|
|
18
|
+
- Somebody paid in dollars. What is that in rupees?
|
|
19
|
+
|
|
20
|
+
`splitkaro` answers all of them from your terminal. It keeps the group's records in one readable
|
|
21
|
+
JSON file, does every calculation with exact decimal arithmetic so the totals always add up to
|
|
22
|
+
the paisa, and works out the smallest set of payments that settles everyone, with UPI links you
|
|
23
|
+
can open in GPay, PhonePe, Paytm or BHIM.
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
$ splitkaro balances
|
|
27
|
+
Asha gets back ₹2,618.00
|
|
28
|
+
Ravi owes ₹682.00
|
|
29
|
+
Meera owes ₹1,936.00
|
|
30
|
+
|
|
31
|
+
$ splitkaro settle --upi
|
|
32
|
+
2 payment(s) settle everything:
|
|
33
|
+
Meera pays Asha ₹1,936.00
|
|
34
|
+
upi://pay?pa=asha%40okbank&pn=Asha&am=1936.00&cu=INR&tn=splitkaro%3A+Goa+trip
|
|
35
|
+
Ravi pays Asha ₹682.00
|
|
36
|
+
upi://pay?pa=asha%40okbank&pn=Asha&am=682.00&cu=INR&tn=splitkaro%3A+Goa+trip
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Contents
|
|
42
|
+
|
|
43
|
+
- [Features](#features)
|
|
44
|
+
- [Install](#install)
|
|
45
|
+
- [A full example: a Goa trip](#a-full-example-a-goa-trip)
|
|
46
|
+
- [Ways to split](#ways-to-split)
|
|
47
|
+
- [Command reference](#command-reference)
|
|
48
|
+
- [Your data](#your-data)
|
|
49
|
+
- [Using splitkaro from Python](#using-splitkaro-from-python)
|
|
50
|
+
- [How it works](#how-it-works)
|
|
51
|
+
- [Roadmap](#roadmap)
|
|
52
|
+
- [Contributing](#contributing)
|
|
53
|
+
- [License](#license)
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
| | |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **Four ways to split** | Equally, by exact amounts, by percentage, or by shares (a couple counts as 2). |
|
|
60
|
+
| **Several payers** | "I paid ₹1,000 and you paid ₹500" for the same bill works naturally. |
|
|
61
|
+
| **Fewest payments** | Suggests at most *n − 1* transfers for *n* people, and pairs up exact matches first. |
|
|
62
|
+
| **UPI links** | One `upi://pay` link per payment; the payer just confirms it in their app. |
|
|
63
|
+
| **Exact to the paisa** | Decimal arithmetic and fair rounding: ₹100 split three ways is 33.34 + 33.33 + 33.33. |
|
|
64
|
+
| **Foreign currency** | Record an expense in USD or EUR; it is converted with the rate you set. |
|
|
65
|
+
| **Reports** | Spending by category and month, and who paid vs. who consumed. |
|
|
66
|
+
| **Statements** | A running, line-by-line history of one person's balance. |
|
|
67
|
+
| **CSV import/export** | Move data to and from spreadsheets. Imports are all-or-nothing. |
|
|
68
|
+
| **Your data, your file** | Plain JSON you can read, back up, sync or put under git. No account, no server, no ads. |
|
|
69
|
+
| **Zero dependencies** | Pure Python standard library, Python 3.9+. |
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install splitkaro
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or straight from GitHub:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install git+https://github.com/K70281095/splitkaro.git
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Check it works:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
splitkaro --version
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## A full example: a Goa trip
|
|
90
|
+
|
|
91
|
+
Asha, Ravi and Meera go to Goa. Asha travels with her partner, so on the hotel she counts as two.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
splitkaro init "Goa trip"
|
|
95
|
+
splitkaro member add Asha Ravi Meera
|
|
96
|
+
splitkaro member upi Asha asha@okbank # so others can pay her with one tap
|
|
97
|
+
|
|
98
|
+
# Hotel: Asha paid, split by shares (Asha 2, Ravi 1, Meera 1)
|
|
99
|
+
splitkaro add "Hotel (2 nights)" 9000 --paid-by Asha --shares Asha=2,Ravi=1,Meera=1 --category stay
|
|
100
|
+
|
|
101
|
+
# Dinner: Ravi paid, split equally among everyone (the default)
|
|
102
|
+
splitkaro add "Dinner at the shack" 2400 --paid-by Ravi --category food
|
|
103
|
+
|
|
104
|
+
# Scooters: two people paid, and only they used them
|
|
105
|
+
splitkaro add "Scooter rental" 1500 --paid-by Asha=1000 --paid-by Meera=500 --among Asha,Meera --category travel
|
|
106
|
+
|
|
107
|
+
# Parasailing was paid in dollars
|
|
108
|
+
splitkaro rate USD 83.20
|
|
109
|
+
splitkaro add "Parasailing" 30 --currency USD --paid-by Meera --category fun
|
|
110
|
+
|
|
111
|
+
# Groceries: everyone took different things
|
|
112
|
+
splitkaro add "Groceries" 1260 --paid-by Ravi --exact Asha=500,Ravi=460,Meera=300 --category food
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Where does everyone stand, and how do they square up?
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
$ splitkaro settle
|
|
119
|
+
2 payment(s) settle everything:
|
|
120
|
+
Meera pays Asha ₹1,936.00
|
|
121
|
+
Ravi pays Asha ₹682.00
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Where did the money go?
|
|
125
|
+
|
|
126
|
+
```console
|
|
127
|
+
$ splitkaro report
|
|
128
|
+
Goa trip: 5 expense(s), ₹16,656.00 in total
|
|
129
|
+
|
|
130
|
+
By category
|
|
131
|
+
stay ₹9,000.00 54%
|
|
132
|
+
food ₹3,660.00 22%
|
|
133
|
+
fun ₹2,496.00 15%
|
|
134
|
+
travel ₹1,500.00 9%
|
|
135
|
+
|
|
136
|
+
By month
|
|
137
|
+
2026-11 ₹16,656.00
|
|
138
|
+
|
|
139
|
+
member paid share
|
|
140
|
+
Asha ₹10,000.00 ₹7,382.00
|
|
141
|
+
Ravi ₹3,660.00 ₹4,342.00
|
|
142
|
+
Meera ₹2,996.00 ₹4,932.00
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Meera wants to see exactly how she ended up owing ₹1,936:
|
|
146
|
+
|
|
147
|
+
```console
|
|
148
|
+
$ splitkaro statement meera
|
|
149
|
+
2026-11-01 -₹2,250.00 -₹2,250.00 #1 Hotel (2 nights)
|
|
150
|
+
2026-11-01 -₹800.00 -₹3,050.00 #2 Dinner at the shack
|
|
151
|
+
2026-11-02 -₹250.00 -₹3,300.00 #3 Scooter rental
|
|
152
|
+
2026-11-02 ₹1,664.00 -₹1,636.00 #4 Parasailing
|
|
153
|
+
2026-11-03 -₹300.00 -₹1,936.00 #5 Groceries
|
|
154
|
+
|
|
155
|
+
Meera owes ₹1,936.00
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Once the money has moved, record it, and everyone is back to zero:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
splitkaro pay Meera Asha 1936
|
|
162
|
+
splitkaro pay Ravi Asha 682
|
|
163
|
+
splitkaro settle # Everyone is settled up. 🎉
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The same trip is available as [`examples/goa-trip.csv`](examples/goa-trip.csv), which you can
|
|
167
|
+
load with `splitkaro import examples/goa-trip.csv`.
|
|
168
|
+
|
|
169
|
+
## Ways to split
|
|
170
|
+
|
|
171
|
+
| Option | Example | Meaning |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| *(none)* | `--paid-by Ravi` | Equally among **all** members. |
|
|
174
|
+
| `--among` | `--among Asha,Meera` | Equally among the listed members. |
|
|
175
|
+
| `--exact` | `--exact Asha=500,Ravi=460,Meera=300` | Fixed amounts; must add up to the total. |
|
|
176
|
+
| `--percent` | `--percent Asha=50,Ravi=25,Meera=25` | Percentages; must add up to 100. |
|
|
177
|
+
| `--shares` | `--shares Asha=2,Ravi=1,Meera=1` | Proportional weights, e.g. couples, or nights stayed. |
|
|
178
|
+
|
|
179
|
+
Whoever paid is given with `--paid-by NAME`, or `--paid-by NAME=AMOUNT` repeated when several
|
|
180
|
+
people paid the same bill. Names are case-insensitive.
|
|
181
|
+
|
|
182
|
+
## Command reference
|
|
183
|
+
|
|
184
|
+
| Command | What it does |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `splitkaro init NAME [--currency INR]` | Start a group (creates `splitkaro.json`). |
|
|
187
|
+
| `splitkaro member add NAME... [--upi ID]` | Add members. |
|
|
188
|
+
| `splitkaro member upi NAME ID` | Set a member's UPI ID. |
|
|
189
|
+
| `splitkaro member list` / `remove NAME` | List members, or remove one who has no history. |
|
|
190
|
+
| `splitkaro add DESC AMOUNT --paid-by ...` | Record an expense (`--category`, `--date`, `--currency`, `--note`). |
|
|
191
|
+
| `splitkaro list [--month YYYY-MM] [--category C] [--member M]` | List expenses. |
|
|
192
|
+
| `splitkaro delete ID` | Delete an expense. |
|
|
193
|
+
| `splitkaro balances [--json]` | Who gets money back, who owes. |
|
|
194
|
+
| `splitkaro settle [--upi] [--json]` | Fewest payments to settle everything. |
|
|
195
|
+
| `splitkaro pay FROM TO AMOUNT` | Record a payment between members. |
|
|
196
|
+
| `splitkaro statement NAME` | Running history of one member's balance. |
|
|
197
|
+
| `splitkaro report [filters] [--json]` | Totals by category, month and member. |
|
|
198
|
+
| `splitkaro rate CUR VALUE` | Exchange rate: 1 CUR = VALUE in the group currency. |
|
|
199
|
+
| `splitkaro export FILE` / `import FILE` | CSV out and in (`-` for stdout/stdin). |
|
|
200
|
+
|
|
201
|
+
Every command accepts `--file PATH` (or the `SPLITKARO_FILE` environment variable) to work with
|
|
202
|
+
more than one group, for example `splitkaro -f flat.json balances`.
|
|
203
|
+
|
|
204
|
+
Errors are printed as `splitkaro: <message>` with exit code `1`, and nothing is saved when a command
|
|
205
|
+
fails.
|
|
206
|
+
|
|
207
|
+
## Your data
|
|
208
|
+
|
|
209
|
+
A group lives in a single JSON file, `splitkaro.json` by default (shortened here):
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"version": 1,
|
|
214
|
+
"name": "Goa trip",
|
|
215
|
+
"currency": "INR",
|
|
216
|
+
"members": [{ "name": "Asha", "upi": "asha@okbank" }, { "name": "Ravi", "upi": null }],
|
|
217
|
+
"expenses": [
|
|
218
|
+
{
|
|
219
|
+
"id": 2,
|
|
220
|
+
"date": "2026-11-01",
|
|
221
|
+
"description": "Dinner at the shack",
|
|
222
|
+
"amount": "2400.00",
|
|
223
|
+
"paid_by": { "Ravi": "2400.00" },
|
|
224
|
+
"split": { "kind": "equal", "among": ["Asha", "Ravi", "Meera"], "values": {} }
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
- Amounts are stored as strings, never floats, so nothing is ever rounded away.
|
|
231
|
+
- Files are written atomically: a crash or a full disk cannot leave a half-written file.
|
|
232
|
+
- There is no account, server or tracking. Share the file however your group likes.
|
|
233
|
+
|
|
234
|
+
The CSV format is documented in [docs/csv-format.md](docs/csv-format.md).
|
|
235
|
+
|
|
236
|
+
## Using splitkaro from Python
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
import datetime as dt
|
|
240
|
+
from decimal import Decimal
|
|
241
|
+
|
|
242
|
+
from splitkaro import Expense, Group, Split, balances, settle
|
|
243
|
+
|
|
244
|
+
group = Group("Flat 302")
|
|
245
|
+
for name in ("Asha", "Ravi", "Meera"):
|
|
246
|
+
group.add_member(name)
|
|
247
|
+
|
|
248
|
+
group.add_expense(Expense(
|
|
249
|
+
id=0, description="Electricity", amount=Decimal("3120"),
|
|
250
|
+
paid_by={"Ravi": Decimal("3120")}, split=Split.equal(["Asha", "Ravi", "Meera"]),
|
|
251
|
+
date=dt.date(2026, 11, 5), currency="INR", category="bills",
|
|
252
|
+
))
|
|
253
|
+
|
|
254
|
+
for t in settle(balances(group)):
|
|
255
|
+
print(f"{t.sender} pays {t.receiver} {t.amount}")
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## How it works
|
|
259
|
+
|
|
260
|
+
- **Rounding:** amounts are split with the largest-remainder method on whole paise, so the parts
|
|
261
|
+
always add up to the exact total and nobody is ever short-changed by more than one paisa.
|
|
262
|
+
- **Balances:** each member's balance is what they paid minus their share, plus payments made
|
|
263
|
+
minus payments received. Balances always add up to exactly zero.
|
|
264
|
+
- **Settling:** exact matches (someone owes exactly what someone else is owed) are paired first;
|
|
265
|
+
the rest is settled greedily between the largest debtor and the largest creditor. This never
|
|
266
|
+
needs more than *n − 1* payments.
|
|
267
|
+
|
|
268
|
+
The details, with worked examples, are in [docs/how-it-works.md](docs/how-it-works.md).
|
|
269
|
+
|
|
270
|
+
## Roadmap
|
|
271
|
+
|
|
272
|
+
- [ ] Recurring expenses (rent, Wi-Fi, maid) added automatically each month
|
|
273
|
+
- [ ] UPI QR codes in the terminal
|
|
274
|
+
- [ ] Simplify across groups (one payment per pair of people for several trips)
|
|
275
|
+
- [ ] Budgets per category with warnings
|
|
276
|
+
- [ ] Hindi and other Indian language output
|
|
277
|
+
- [ ] A small web or mobile front end on top of the same file format
|
|
278
|
+
|
|
279
|
+
Ideas and pull requests are welcome, see the [issues](https://github.com/K70281095/splitkaro/issues).
|
|
280
|
+
|
|
281
|
+
## Contributing
|
|
282
|
+
|
|
283
|
+
Bug reports, ideas, translations and code are all welcome. Please read
|
|
284
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). To report a
|
|
285
|
+
security problem, see [SECURITY.md](SECURITY.md).
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
[MIT](LICENSE) © 2026 Kanhaiya Kumar
|
|
290
|
+
|
|
291
|
+
*UPI is a system of the National Payments Corporation of India. splitkaro only creates standard
|
|
292
|
+
`upi://pay` links and never handles money, bank details or PINs.*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "splitkaro"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Bill splitter for friends, flatmates and trips: split shared expenses, see who owes whom, and settle up with the fewest payments and UPI links."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Kanhaiya Kumar" }]
|
|
14
|
+
keywords = ["bill-splitter", "split-expenses", "expense-splitter", "group-expenses", "settle-up", "splitwise-alternative", "upi", "india", "roommates", "travel", "personal-finance", "cli"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: End Users/Desktop",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Office/Business :: Financial",
|
|
29
|
+
"Topic :: Utilities",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["ruff>=0.6"]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
splitkaro = "splitkaro.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/K70281095/splitkaro"
|
|
41
|
+
Issues = "https://github.com/K70281095/splitkaro/issues"
|
|
42
|
+
Changelog = "https://github.com/K70281095/splitkaro/blob/main/CHANGELOG.md"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 110
|
|
49
|
+
target-version = "py39"
|
|
50
|
+
src = ["src", "tests"]
|
|
51
|
+
|
|
52
|
+
[tool.ruff.format]
|
|
53
|
+
# Code samples in the docs are laid out for reading, not by the formatter.
|
|
54
|
+
exclude = ["*.md"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint.isort]
|
|
60
|
+
known-first-party = ["splitkaro", "helpers"]
|