File size: 6,882 Bytes
e365713
 
 
 
7e66899
 
65a5e9b
e365713
7e66899
65a5e9b
7e66899
 
 
 
65a5e9b
280b1e8
0f94921
280b1e8
 
 
7e66899
 
65a5e9b
 
 
280b1e8
0f94921
280b1e8
 
 
b4a43e4
7e66899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4a43e4
7e66899
 
 
 
 
 
 
 
 
 
 
 
ce45647
7e66899
 
ce45647
7e66899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b4a43e4
7e66899
 
 
 
 
e365713
7e66899
 
 
 
 
 
 
0f94921
7e66899
 
 
0f94921
 
7e66899
0f94921
 
 
7e66899
 
 
 
 
 
 
 
 
 
280b1e8
7e66899
 
 
 
 
280b1e8
 
7e66899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280b1e8
7e66899
0f94921
7e66899
 
 
280b1e8
7e66899
 
 
 
 
 
 
 
 
 
 
 
 
 
1aaa270
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import gradio as gr
import pandas as pd
import numpy as np

# Global variables
expenses = []
participants_set = set()

def add_participant(participant):
    global participants_set
    if not participant or not participant.strip():
        raise gr.Error("Participant name cannot be empty")
    
    clean_name = participant.strip()
    participants_set.add(clean_name)
    participants_list = sorted(list(participants_set))  # Sort for consistent display
    participants_text = "\n".join(participants_list)
    
    # Return updated participant list for dropdowns
    return participants_text, participants_list, participants_list

def remove_participant(participant):
    global participants_set
    if participant in participants_set:
        participants_set.remove(participant)
    participants_list = sorted(list(participants_set))  # Sort for consistent display
    participants_text = "\n".join(participants_list)
    
    # Return updated participant list for dropdowns
    return participants_text, participants_list, participants_list

# Add expenses
def add_expense(description, amount, payer, participants_list):
    global expenses
    
    # Validate inputs
    if not description or not description.strip():
        raise gr.Error("Description cannot be empty")
    
    if amount <= 0:
        raise gr.Error("Amount must be a positive number")
    
    if not payer:
        raise gr.Error("Payer cannot be empty")
    
    if not participants_list:
        raise gr.Error("Participants cannot be empty")
    
    # Ensure all are unique
    unique_participants = list(set(participants_list + [payer]))
    
    amount = float(amount)
    expense = {
        "Description": description,
        "Amount": amount,
        "Payer": payer,
        "Participants": ", ".join(unique_participants),
        "Split Amount": round(amount / len(unique_participants), 2),
    }
    expenses.append(expense)
    return pd.DataFrame(expenses)

# Optimize Balances (same as previous implementation)
def optimize_balances():
    global expenses
    
    # Create a comprehensive balance sheet
    balances = {}
    
    for expense in expenses:
        payer = expense["Payer"]
        participants_list = expense["Participants"].split(", ")
        total_amount = expense["Amount"]
        split_amount = total_amount / len(participants_list)
        
        # Payer gets credit for the entire amount
        balances[payer] = balances.get(payer, 0) + total_amount
        
        # Participants owe their share
        for participant in participants_list:
            if participant != payer:
                balances[participant] = balances.get(participant, 0) - split_amount
    
    # Simplify debts
    def simplify_debts(balances):
        # Round balances to avoid floating-point errors
        rounded_balances = {k: round(v, 2) for k, v in balances.items()}
        
        # Separate creditors and debtors
        debtors = {k: v for k, v in rounded_balances.items() if v < -0.01}
        creditors = {k: v for k, v in rounded_balances.items() if v > 0.01}
        
        transactions = []
        
        # Continue until all debts are settled
        while debtors and creditors:
            # Find the most negative debtor and the largest creditor
            debtor = min(debtors, key=debtors.get)
            creditor = max(creditors, key=creditors.get)
            
            # Amount to settle is the minimum of absolute debt and credit
            settle_amount = min(abs(debtors[debtor]), creditors[creditor])
            
            # Round to 2 decimal places
            settle_amount = round(settle_amount, 2)
            
            # Add transaction
            transactions.append(f"{debtor} pays {creditor} ${settle_amount:.2f}")
            
            # Update balances
            debtors[debtor] += settle_amount
            creditors[creditor] -= settle_amount
            
            # Remove if balance is effectively zero
            if abs(debtors[debtor]) < 0.01:
                del debtors[debtor]
            if abs(creditors[creditor]) < 0.01:
                del creditors[creditor]
        
        return transactions if transactions else ["No transactions needed"]
    
    return simplify_debts(balances)

# Reset App
def reset():
    global expenses
    expenses = []
    return pd.DataFrame(expenses), []

# Gradio Interface
with gr.Blocks(theme='soft') as app:
    gr.Markdown("# Expense Splitter App")
    
    # Participant Management
    with gr.Row():
        with gr.Column():
            participant_input = gr.Textbox(label="Participant Name", placeholder="Enter a participant name")
            with gr.Row():
                add_participant_btn = gr.Button("Add Participant")
                remove_participant_btn = gr.Button("Remove Participant")
        
            participants_display = gr.Textbox(
                label="Current Participants", 
                lines=10, 
                interactive=False, 
                placeholder="Participants will appear here..."
            )
    
    # Expense Adding
    with gr.Row():
        with gr.Column():
            description = gr.Textbox(label="Description", placeholder="e.g., Dinner")
            amount = gr.Number(label="Amount", value=0, precision=2)
            
            payer = gr.Dropdown(
                label="Payer", 
                choices=[],
                interactive=True
            )
            
            participants = gr.Dropdown(
                label="Participants", 
                multiselect=True,
                choices=[],
                interactive=True
            )
            
            add_btn = gr.Button("Add Expense")
            
        with gr.Column():
            expense_table = gr.Dataframe(
                headers=["Description", "Amount", "Payer", "Participants", "Split Amount"],
                datatype=["str", "number", "str", "str", "number"],
                type="pandas"
            )
    
    # Button Interactions
    add_participant_btn.click(
        add_participant, 
        inputs=participant_input, 
        outputs=[participants_display, payer, participants]
    )

    remove_participant_btn.click(
        remove_participant, 
        inputs=participant_input, 
        outputs=[participants_display, payer, participants]
    )
    
    add_btn.click(
        add_expense, 
        inputs=[description, amount, payer, participants], 
        outputs=expense_table
    )
    
    with gr.Row():
        optimize_btn = gr.Button("Optimize Balances")
        result = gr.Textbox(label="Transactions", lines=5)
        reset_btn = gr.Button("Reset")
    
    optimize_btn.click(optimize_balances, inputs=[], outputs=result)
    reset_btn.click(reset, inputs=[], outputs=[expense_table, result])

# Launch the app
if __name__ == "__main__":
    app.launch(share=True)