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 and new Dropdown instances return ( participants_text, gr.Dropdown(choices=participants_list, label="Payer", interactive=True), gr.Dropdown(choices=participants_list, label="Participants", multiselect=True, interactive=True) ) def remove_participant(participant): global participants_set participant = participant.strip() 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 and new Dropdown instances return ( participants_text, gr.Dropdown(choices=participants_list, label="Payer", interactive=True), gr.Dropdown(choices=participants_list, label="Participants", multiselect=True, interactive=True) ) # 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 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 "\n".join(simplify_debts(balances)) # Reset App def reset(): global expenses, participants_set expenses = [] participants_set = set() participants_list = [] participants_text = "" return ( pd.DataFrame(expenses), "", participants_text, gr.Dropdown(choices=participants_list, label="Payer", interactive=True), gr.Dropdown(choices=participants_list, label="Participants", multiselect=True, interactive=True) ) # 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, participants_display, payer, participants] ) # Launch the app if __name__ == "__main__": app.launch(share=True)