Spaces:
Sleeping
Sleeping
| 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) |