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) |