hlydecker commited on
Commit
b4a43e4
·
verified ·
1 Parent(s): 63522cb

pre-alpha PoC 0.2.0

Browse files
Files changed (1) hide show
  1. app.py +98 -128
app.py CHANGED
@@ -2,138 +2,108 @@ import gradio as gr
2
  import pandas as pd
3
  import numpy as np
4
 
5
- # Global variables
6
- expenses = []
 
 
 
7
 
8
- # Add expenses
9
- def add_expense(description, amount, payer, participants):
10
- global expenses
11
-
12
- # Validate inputs
13
- if not description or not description.strip():
14
- raise gr.Error("Description cannot be empty")
15
-
16
- if amount <= 0:
17
- raise gr.Error("Amount must be a positive number")
18
-
19
- if not payer or not payer.strip():
20
- raise gr.Error("Payer cannot be empty")
21
-
22
- if not participants or not participants.strip():
23
- raise gr.Error("Participants cannot be empty")
24
-
25
- # Process participants
26
- participants_list = [p.strip() for p in participants.split(",")]
27
- participants_list.append(payer) # Ensure payer is included
28
- unique_participants = list(set(participants_list)) # Remove duplicates
29
-
30
- amount = float(amount)
31
- expense = {
32
- "Description": description,
33
- "Amount": amount,
34
- "Payer": payer,
35
- "Participants": ", ".join(unique_participants),
36
- "Split Amount": round(amount / len(unique_participants), 2),
37
- }
38
- expenses.append(expense)
39
- return pd.DataFrame(expenses)
40
-
41
- # Optimize Balances
42
- def optimize_balances():
43
- global expenses
44
-
45
- # Create a comprehensive balance sheet
46
- balances = {}
47
-
48
- for expense in expenses:
49
- payer = expense["Payer"]
50
- participants = expense["Participants"].split(", ")
51
- total_amount = expense["Amount"]
52
- split_amount = total_amount / len(participants)
53
-
54
- # Payer gets credit for the entire amount
55
- balances[payer] = balances.get(payer, 0) + total_amount
56
-
57
- # Participants owe their share
58
- for participant in participants:
59
- if participant != payer:
60
- balances[participant] = balances.get(participant, 0) - split_amount
61
-
62
- # Simplify debts
63
- def simplify_debts(balances):
64
- # Round balances to avoid floating-point errors
65
- rounded_balances = {k: round(v, 2) for k, v in balances.items()}
66
-
67
- # Separate creditors and debtors
68
- debtors = {k: v for k, v in rounded_balances.items() if v < -0.01}
69
- creditors = {k: v for k, v in rounded_balances.items() if v > 0.01}
70
 
71
- transactions = []
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- # Continue until all debts are settled
74
- while debtors and creditors:
75
- # Find the most negative debtor and the largest creditor
76
- debtor = min(debtors, key=debtors.get)
77
- creditor = max(creditors, key=creditors.get)
78
-
79
- # Amount to settle is the minimum of absolute debt and credit
80
- settle_amount = min(abs(debtors[debtor]), creditors[creditor])
81
-
82
- # Round to 2 decimal places
83
- settle_amount = round(settle_amount, 2)
84
-
85
- # Add transaction
86
- transactions.append(f"{debtor} pays {creditor} ${settle_amount:.2f}")
87
-
88
- # Update balances
89
- debtors[debtor] += settle_amount
90
- creditors[creditor] -= settle_amount
91
-
92
- # Remove if balance is effectively zero
93
- if abs(debtors[debtor]) < 0.01:
94
- del debtors[debtor]
95
- if abs(creditors[creditor]) < 0.01:
96
- del creditors[creditor]
97
 
98
- return transactions if transactions else ["No transactions needed"]
99
-
100
- return simplify_debts(balances)
101
 
102
- # Reset App
103
- def reset():
104
- global expenses
105
- expenses = []
106
- return pd.DataFrame(expenses), []
 
 
 
 
 
 
 
 
 
107
 
108
- # Gradio Interface
109
- with gr.Blocks() as app:
110
- gr.Markdown("# Expense Splitter App")
111
-
112
- with gr.Row():
113
- with gr.Column():
114
- description = gr.Textbox(label="Description", placeholder="e.g., Dinner")
115
- amount = gr.Number(label="Amount", value=0, precision=2)
116
- payer = gr.Textbox(label="Payer", placeholder="e.g., Alice")
117
- participants = gr.Textbox(label="Participants", placeholder="e.g., Alice, Bob, Charlie")
118
- add_btn = gr.Button("Add Expense")
119
-
120
- with gr.Column():
121
- expense_table = gr.Dataframe(
122
- headers=["Description", "Amount", "Payer", "Participants", "Split Amount"],
123
- datatype=["str", "number", "str", "str", "number"],
124
- type="pandas"
125
- )
126
-
127
- add_btn.click(add_expense, inputs=[description, amount, payer, participants], outputs=expense_table)
128
-
129
- with gr.Row():
130
- optimize_btn = gr.Button("Optimize Balances")
131
- result = gr.Textbox(label="Transactions", lines=5)
132
- reset_btn = gr.Button("Reset")
133
-
134
- optimize_btn.click(optimize_balances, inputs=[], outputs=result)
135
- reset_btn.click(reset, inputs=[], outputs=[expense_table, result])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- # Launch the app
138
- if __name__ == "__main__":
139
- app.launch(share=True)
 
 
2
  import pandas as pd
3
  import numpy as np
4
 
5
+ class ExpenseSplitterApp:
6
+ def __init__(self):
7
+ # Store participants as a set for unique values
8
+ self.participants = set()
9
+ self.expenses = []
10
 
11
+ def add_participant(self, name):
12
+ # Validate and add participant
13
+ if not name or not name.strip():
14
+ raise gr.Error("Participant name cannot be empty")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ clean_name = name.strip()
17
+ self.participants.add(clean_name)
18
+ return list(self.participants)
19
+
20
+ def remove_participant(self, name):
21
+ # Remove participant if exists
22
+ if name in self.participants:
23
+ self.participants.remove(name)
24
+ return list(self.participants)
25
+
26
+ def add_expense(self, description, amount, payer, participants):
27
+ # Use existing logic, but with participant validation
28
+ if payer not in self.participants:
29
+ raise gr.Error(f"{payer} is not in the participant list")
30
 
31
+ for p in participants.split(","):
32
+ p = p.strip()
33
+ if p not in self.participants:
34
+ raise gr.Error(f"{p} is not in the participant list")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ # Rest of existing add_expense logic...
 
 
37
 
38
+ def create_interface(self):
39
+ with gr.Blocks(theme='soft') as app:
40
+ # Participant Management Section
41
+ with gr.Accordion("Participant Management", open=True):
42
+ with gr.Row():
43
+ participant_input = gr.Textbox(label="Participant Name")
44
+ add_participant_btn = gr.Button("Add Participant")
45
+ remove_participant_btn = gr.Button("Remove Participant")
46
+
47
+ participant_list = gr.Dropdown(
48
+ label="Current Participants",
49
+ multiselect=True,
50
+ interactive=True
51
+ )
52
 
53
+ # Participant Management Interactions
54
+ add_participant_btn.click(
55
+ self.add_participant,
56
+ inputs=participant_input,
57
+ outputs=participant_list
58
+ )
59
+ remove_participant_btn.click(
60
+ self.remove_participant,
61
+ inputs=participant_input,
62
+ outputs=participant_list
63
+ )
64
+
65
+ # Expense Adding Section
66
+ with gr.Accordion("Add Expense", open=True):
67
+ with gr.Row():
68
+ description = gr.Textbox(label="Description")
69
+ amount = gr.Number(label="Amount", precision=2)
70
+
71
+ with gr.Row():
72
+ payer = gr.Dropdown(
73
+ label="Payer",
74
+ choices=[],
75
+ interactive=True
76
+ )
77
+ participants = gr.Dropdown(
78
+ label="Participants",
79
+ multiselect=True,
80
+ choices=[],
81
+ interactive=True
82
+ )
83
+
84
+ # Dynamically update dropdowns when participants change
85
+ participant_list.change(
86
+ lambda x: (x, x),
87
+ inputs=participant_list,
88
+ outputs=[payer, participants]
89
+ )
90
+
91
+ add_expense_btn = gr.Button("Add Expense")
92
+ expense_table = gr.Dataframe(
93
+ headers=["Description", "Amount", "Payer", "Participants", "Split Amount"]
94
+ )
95
+
96
+ add_expense_btn.click(
97
+ self.add_expense,
98
+ inputs=[description, amount, payer, participants],
99
+ outputs=expense_table
100
+ )
101
+
102
+ # Existing balance optimization and reset buttons...
103
+
104
+ return app
105
 
106
+ # Create and launch the app
107
+ app_instance = ExpenseSplitterApp()
108
+ demo = app_instance.create_interface()
109
+ demo.launch(share=True)