xas

Finished
1import json
2from typing import Dict, List, Optional, Any
3
14class RestAPI:
2 def __init__(self, database: dict | None = None):
3 pass
5 """
6 A RESTful API for tracking IOUs between users.
7
8 This class manages user data and IOU transactions in memory.
9 """
10
11 def __init__(self, database: Optional[Dict[str, Any]] = None):
12 """
13 Initialize the RestAPI with an optional database.
14
15 Args:
16 database: Optional initial database containing users data
17 """
18 if database and 'users' in database:
19 # Initialize users from database
20 self.users = {}
21 for user_data in database['users']:
22 self.users[user_data['name']] = {
23 'name': user_data['name'],
24 'owes': user_data.get('owes', {}),
25 'owed_by': user_data.get('owed_by', {}),
26 'balance': user_data.get('balance', 0.0)
27 }
28 else:
29 # Initialize empty users dictionary
30 self.users = {}
31
32 def get(self, url: str, payload: Optional[str] = None) -> str:
33 """
34 Handle GET requests to the API.
35
36 Args:
37 url: The endpoint URL
38 payload: Optional JSON payload
39
40 Returns:
41 JSON string response
42 """
43 if url == '/users':
44 if payload is None:
45 # Return all users sorted by name
46 user_list = [self._get_user_details(name) for name in sorted(self.users.keys())]
47 return json.dumps({"users": user_list})
48 else:
49 # Return specific users
50 try:
51 data = json.loads(payload)
52 if 'users' not in data:
53 return json.dumps({"error": "Invalid payload"})
54
55 # Edge Case: Empty users list in payload
56 if not data['users']:
57 return json.dumps({"users": []})
58
59 # Edge Case: Non-existent users in payload
60 result = []
61 for name in sorted(data['users']):
62 if name in self.users:
63 result.append(self._get_user_details(name))
64
65 return json.dumps({"users": result})
66 except json.JSONDecodeError:
67 return json.dumps({"error": "Invalid JSON payload"})
68
69 return json.dumps({"error": "Invalid endpoint"})
70
71 def post(self, url: str, payload: Optional[str] = None) -> str:
72 """
73 Handle POST requests to the API.
74
75 Args:
76 url: The endpoint URL
77 payload: Optional JSON payload
78
79 Returns:
80 JSON string response
81 """
82 if payload is None:
83 return json.dumps({"error": "Payload required"})
84
85 try:
86 data = json.loads(payload)
87 except json.JSONDecodeError:
88 return json.dumps({"error": "Invalid JSON payload"})
89
90 if url == '/add':
91 # Create a new user
92 if 'user' not in data:
93 return json.dumps({"error": "Missing user field"})
94
95 user_name = data['user']
96
97 # Edge Case: User already exists
98 if user_name in self.users:
99 return json.dumps({"error": "User already exists"})
100
101 # Create new user
102 new_user = {
103 'name': user_name,
104 'owes': {},
105 'owed_by': {},
106 'balance': 0.0
107 }
108 self.users[user_name] = new_user
109
110 # Return the new user object
111 return json.dumps(self._get_user_details(user_name))
112
113 elif url == '/iou':
114 # Create a new IOU
115 required_fields = ['lender', 'borrower', 'amount']
116 for field in required_fields:
117 if field not in data:
118 return json.dumps({"error": f"Missing {field} field"})
119
120 lender_name = data['lender']
121 borrower_name = data['borrower']
122 amount = data['amount']
123
124 # Edge Case: Invalid amount type or negative amount
125 if not isinstance(amount, (int, float)) or amount < 0:
126 return json.dumps({"error": "Invalid amount"})
127
128 # Edge Case: Lender does not exist
129 if lender_name not in self.users:
130 return json.dumps({"error": "Lender does not exist"})
131
132 # Edge Case: Borrower does not exist
133 if borrower_name not in self.users:
134 return json.dumps({"error": "Borrower does not exist"})
135
136 # Edge Case: Lender and borrower are the same person
137 if lender_name == borrower_name:
138 return json.dumps({"error": "Cannot create IOU between same user"})
139
140 # Update IOUs with debt netting
141 self._update_iou_with_netting(lender_name, borrower_name, amount)
142
143 # Recalculate balances
144 self._recalculate_balance(lender_name)
145 self._recalculate_balance(borrower_name)
146
147 # Return updated user objects sorted by name
148 user_names = sorted([lender_name, borrower_name])
149 updated_users = [self._get_user_details(name) for name in user_names]
150
151 return json.dumps({"users": updated_users})
152
153 return json.dumps({"error": "Invalid endpoint"})
154
155 def _get_user_details(self, name: str) -> Dict[str, Any]:
156 """
157 Get user details with calculated balance.
158
159 Args:
160 name: Name of the user
161
162 Returns:
163 User object with all details
164 """
165 user = self.users[name].copy()
166 # Ensure balance is calculated correctly
167 user['balance'] = self._calculate_balance(name)
168 return user
169
170 def _calculate_balance(self, name: str) -> float:
171 """
172 Calculate user's balance (owed_by - owes).
173
174 Args:
175 name: Name of the user
176
177 Returns:
178 Calculated balance
179 """
180 user = self.users[name]
181 owed_by_total = sum(user['owed_by'].values())
182 owes_total = sum(user['owes'].values())
183 return round(owed_by_total - owes_total, 2)
184
185 def _update_iou_with_netting(self, lender_name: str, borrower_name: str, amount: float) -> None:
186 """
187 Update IOU relationships.
188
189 Args:
190 lender_name: Name of the lender
191 borrower_name: Name of the borrower
192 amount: Amount being lent
193 """
194 # Record the transaction normally
195 # Lender is owed by borrower
196 if borrower_name in self.users[lender_name]['owed_by']:
197 self.users[lender_name]['owed_by'][borrower_name] += amount
198 else:
199 self.users[lender_name]['owed_by'][borrower_name] = amount
200
201 # Borrower owes lender
202 if lender_name in self.users[borrower_name]['owes']:
203 self.users[borrower_name]['owes'][lender_name] += amount
204 else:
205 self.users[borrower_name]['owes'][lender_name] = amount
206
207 def _get_original_lender(self, lender_name: str, borrower_name: str) -> str:
208 """
209 Get the original lender for a relationship between two users.
210
211 Args:
212 lender_name: Current transaction lender
213 borrower_name: Current transaction borrower
214
215 Returns:
216 Name of the original lender for this relationship
217 """
218 # Check if there's already a relationship between these users
219 if borrower_name in self.users[lender_name].get('owed_by', {}):
220 return lender_name
221 elif lender_name in self.users[borrower_name].get('owed_by', {}):
222 return borrower_name
223 elif lender_name in self.users[borrower_name].get('owes', {}):
224 return borrower_name
225 elif borrower_name in self.users[lender_name].get('owes', {}):
226 return lender_name
227 else:
228 # No existing relationship, current lender is the original lender
229 return lender_name
230
231 def _net_reciprocal_debts(self, user1: str, user2: str) -> None:
232 """
233 Net reciprocal debts between two users.
234
235 Args:
236 user1: First user
237 user2: Second user
238 """
239 # Get amounts owed in both directions
240 user1_owes_user2 = self.users[user1]['owes'].get(user2, 0)
241 user2_owes_user1 = self.users[user2]['owes'].get(user1, 0)
242
243 if user1_owes_user2 > 0 and user2_owes_user1 > 0:
244 # Both users owe each other, net the debts
245 if user1_owes_user2 > user2_owes_user1:
246 # user1 owes more than user2 owes
247 net_amount = user1_owes_user2 - user2_owes_user1
248 # user1 still owes user2 the net amount
249 self.users[user1]['owes'][user2] = net_amount
250 # user2 no longer owes user1
251 del self.users[user2]['owes'][user1]
252 # Update owed_by accordingly
253 del self.users[user1]['owed_by'][user2]
254 if user2 in self.users[user2]['owed_by']:
255 self.users[user2]['owed_by'][user1] = net_amount
256 else:
257 self.users[user2]['owed_by'][user1] = net_amount
258 elif user2_owes_user1 > user1_owes_user2:
259 # user2 owes more than user1 owes
260 net_amount = user2_owes_user1 - user1_owes_user2
261 # user2 still owes user1 the net amount
262 self.users[user2]['owes'][user1] = net_amount
263 # user1 no longer owes user2
264 del self.users[user1]['owes'][user2]
265 # Update owed_by accordingly
266 del self.users[user2]['owed_by'][user1]
267 if user1 in self.users[user1]['owed_by']:
268 self.users[user1]['owed_by'][user2] = net_amount
269 else:
270 self.users[user1]['owed_by'][user2] = net_amount
271 else:
272 # Equal amounts, cancel both debts
273 del self.users[user1]['owes'][user2]
274 del self.users[user2]['owes'][user1]
275 if user2 in self.users[user1]['owed_by']:
276 del self.users[user1]['owed_by'][user2]
277 if user1 in self.users[user2]['owed_by']:
278 del self.users[user2]['owed_by'][user1]
279
4280
5 def get(self, url: str, payload: str | None = None) -> str:
6 pass
281
282 def _recalculate_balance(self, name: str) -> None:
283 """
284 Recalculate and update user's balance.
285
286 Args:
287 name: Name of the user
288 """
289 self.users[name]['balance'] = self._calculate_balance(name)
7290
8 def post(self, url: str, payload: str | None = None) -> str:
9 pass
291# Handled Edge Cases: Invalid endpoint, missing payload, invalid JSON, empty users list, non-existent users, user already exists, invalid amount, negative amount, lender does not exist, borrower does not exist, lender and borrower are the same
Test NameStatus
test_lender_owes_borrower
Fail
test_lender_owes_borrower_less_than_new_loan
Fail
test_lender_owes_borrower_same_as_new_loan
Fail
test_add_user
Pass
test_borrower_has_negative_balance
Pass
test_both_users_have_0_balance
Pass
test_get_single_user
Pass
test_lender_has_negative_balance
Pass
test_no_users
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.