girishwangikar commited on
Commit
72fd761
Β·
verified Β·
1 Parent(s): 58e32b2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +471 -0
app.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import gspread
4
+ from google.oauth2.service_account import Credentials
5
+ import json
6
+ import os
7
+ from datetime import datetime, timedelta
8
+ import plotly.graph_objects as go
9
+ import plotly.express as px
10
+ from io import BytesIO
11
+
12
+ # Google Sheets Setup
13
+ SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
14
+
15
+ def get_google_sheets_client():
16
+ """Initialize Google Sheets client"""
17
+ try:
18
+ # Get credentials from environment variable
19
+ creds_json = os.environ.get('GOOGLE_SHEETS_CREDENTIALS')
20
+ if not creds_json:
21
+ raise ValueError("GOOGLE_SHEETS_CREDENTIALS not found in environment")
22
+
23
+ creds_dict = json.loads(creds_json)
24
+ creds = Credentials.from_service_account_info(creds_dict, scopes=SCOPES)
25
+ client = gspread.authorize(creds)
26
+ return client
27
+ except Exception as e:
28
+ raise Exception(f"Failed to initialize Google Sheets client: {str(e)}")
29
+
30
+ def get_sheet():
31
+ """Get the Google Sheet"""
32
+ client = get_google_sheets_client()
33
+ sheet_id = os.environ.get('SHEET_ID')
34
+ if not sheet_id:
35
+ raise ValueError("SHEET_ID not found in environment")
36
+ return client.open_by_key(sheet_id)
37
+
38
+ def initialize_sheets():
39
+ """Initialize sheets with headers if empty"""
40
+ try:
41
+ spreadsheet = get_sheet()
42
+
43
+ # Initialize Transactions sheet
44
+ try:
45
+ trans_sheet = spreadsheet.worksheet("Transactions")
46
+ except:
47
+ trans_sheet = spreadsheet.add_worksheet(title="Transactions", rows="1000", cols="10")
48
+
49
+ if not trans_sheet.row_values(1):
50
+ trans_sheet.update('A1:F1', [['Date', 'Farmer Name', 'Bank Account', 'Amount', 'Type', 'Timestamp']])
51
+
52
+ # Initialize Metadata sheet
53
+ try:
54
+ meta_sheet = spreadsheet.worksheet("Metadata")
55
+ except:
56
+ meta_sheet = spreadsheet.add_worksheet(title="Metadata", rows="1000", cols="10")
57
+
58
+ if not meta_sheet.row_values(1):
59
+ meta_sheet.update('A1:B1', [['Type', 'Value']])
60
+ meta_sheet.update('A2:B2', [['farmer', '']])
61
+ meta_sheet.update('A3:B3', [['bank', '']])
62
+
63
+ return True
64
+ except Exception as e:
65
+ print(f"Error initializing sheets: {str(e)}")
66
+ return False
67
+
68
+ def get_farmers():
69
+ """Get list of registered farmers"""
70
+ try:
71
+ spreadsheet = get_sheet()
72
+ meta_sheet = spreadsheet.worksheet("Metadata")
73
+ data = meta_sheet.get_all_values()
74
+
75
+ farmers = []
76
+ for row in data:
77
+ if len(row) >= 2 and row[0] == 'farmer' and row[1]:
78
+ farmers.extend([f.strip() for f in row[1].split(',') if f.strip()])
79
+
80
+ return sorted(list(set(farmers)))
81
+ except Exception as e:
82
+ print(f"Error getting farmers: {str(e)}")
83
+ return []
84
+
85
+ def get_banks():
86
+ """Get list of registered bank accounts"""
87
+ try:
88
+ spreadsheet = get_sheet()
89
+ meta_sheet = spreadsheet.worksheet("Metadata")
90
+ data = meta_sheet.get_all_values()
91
+
92
+ banks = []
93
+ for row in data:
94
+ if len(row) >= 2 and row[0] == 'bank' and row[1]:
95
+ banks.extend([b.strip() for b in row[1].split(',') if b.strip()])
96
+
97
+ return sorted(list(set(banks)))
98
+ except Exception as e:
99
+ print(f"Error getting banks: {str(e)}")
100
+ return []
101
+
102
+ def register_farmer(farmer_name):
103
+ """Register a new farmer"""
104
+ try:
105
+ if not farmer_name or not farmer_name.strip():
106
+ return "❌ Please enter a farmer name"
107
+
108
+ farmer_name = farmer_name.strip()
109
+ farmers = get_farmers()
110
+
111
+ if farmer_name in farmers:
112
+ return f"❌ Farmer '{farmer_name}' is already registered"
113
+
114
+ spreadsheet = get_sheet()
115
+ meta_sheet = spreadsheet.worksheet("Metadata")
116
+ data = meta_sheet.get_all_values()
117
+
118
+ # Find the farmer row
119
+ farmer_row = None
120
+ for idx, row in enumerate(data):
121
+ if len(row) >= 1 and row[0] == 'farmer':
122
+ farmer_row = idx + 1
123
+ break
124
+
125
+ if farmer_row:
126
+ current_farmers = data[farmer_row - 1][1] if len(data[farmer_row - 1]) > 1 else ""
127
+ new_farmers = f"{current_farmers},{farmer_name}" if current_farmers else farmer_name
128
+ meta_sheet.update_cell(farmer_row, 2, new_farmers)
129
+
130
+ return f"βœ… Farmer '{farmer_name}' registered successfully!"
131
+ except Exception as e:
132
+ return f"❌ Error registering farmer: {str(e)}"
133
+
134
+ def register_bank(bank_name):
135
+ """Register a new bank account"""
136
+ try:
137
+ if not bank_name or not bank_name.strip():
138
+ return "❌ Please enter a bank account name"
139
+
140
+ bank_name = bank_name.strip()
141
+ banks = get_banks()
142
+
143
+ if bank_name in banks:
144
+ return f"❌ Bank account '{bank_name}' is already registered"
145
+
146
+ spreadsheet = get_sheet()
147
+ meta_sheet = spreadsheet.worksheet("Metadata")
148
+ data = meta_sheet.get_all_values()
149
+
150
+ # Find the bank row
151
+ bank_row = None
152
+ for idx, row in enumerate(data):
153
+ if len(row) >= 1 and row[0] == 'bank':
154
+ bank_row = idx + 1
155
+ break
156
+
157
+ if bank_row:
158
+ current_banks = data[bank_row - 1][1] if len(data[bank_row - 1]) > 1 else ""
159
+ new_banks = f"{current_banks},{bank_name}" if current_banks else bank_name
160
+ meta_sheet.update_cell(bank_row, 2, new_banks)
161
+
162
+ return f"βœ… Bank account '{bank_name}' registered successfully!"
163
+ except Exception as e:
164
+ return f"❌ Error registering bank: {str(e)}"
165
+
166
+ def add_transaction(date, farmer_name, bank_account, amount, trans_type):
167
+ """Add a new transaction"""
168
+ try:
169
+ if not all([date, farmer_name, bank_account, amount, trans_type]):
170
+ return "❌ Please fill in all fields"
171
+
172
+ farmer_name = farmer_name.strip()
173
+ bank_account = bank_account.strip()
174
+
175
+ # Check if farmer exists
176
+ farmers = get_farmers()
177
+ if farmer_name not in farmers:
178
+ return f"❌ Farmer '{farmer_name}' not found. Please register the farmer first or check spelling."
179
+
180
+ # Check if bank exists
181
+ banks = get_banks()
182
+ if bank_account not in banks:
183
+ return f"❌ Bank account '{bank_account}' not found. Please register the bank first."
184
+
185
+ try:
186
+ amount = float(amount)
187
+ except:
188
+ return "❌ Amount must be a valid number"
189
+
190
+ # Format amount based on type
191
+ if trans_type == "Outgoing (Lent to Farmer)":
192
+ amount = -abs(amount)
193
+ else: # Incoming
194
+ amount = abs(amount)
195
+
196
+ spreadsheet = get_sheet()
197
+ trans_sheet = spreadsheet.worksheet("Transactions")
198
+
199
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
200
+ trans_sheet.append_row([date, farmer_name, bank_account, amount, trans_type, timestamp])
201
+
202
+ return f"βœ… Transaction added successfully! Amount: {amount}"
203
+ except Exception as e:
204
+ return f"❌ Error adding transaction: {str(e)}"
205
+
206
+ def calculate_interest(principal, start_date, end_date):
207
+ """Calculate 2% monthly interest prorated daily"""
208
+ try:
209
+ start = datetime.strptime(start_date, "%Y-%m-%d")
210
+ end = datetime.strptime(end_date, "%Y-%m-%d")
211
+ days = (end - start).days
212
+
213
+ # 2% per month = 2/30 = 0.0667% per day
214
+ daily_rate = 0.02 / 30
215
+ interest = principal * daily_rate * days
216
+ return interest
217
+ except:
218
+ return 0
219
+
220
+ def get_farmer_report(farmer_name):
221
+ """Generate complete report for a farmer with interest calculations"""
222
+ try:
223
+ if not farmer_name or not farmer_name.strip():
224
+ return None, "❌ Please enter a farmer name", None, None, None
225
+
226
+ farmer_name = farmer_name.strip()
227
+
228
+ spreadsheet = get_sheet()
229
+ trans_sheet = spreadsheet.worksheet("Transactions")
230
+ data = trans_sheet.get_all_values()
231
+
232
+ if len(data) <= 1:
233
+ return None, f"❌ No transactions found for farmer '{farmer_name}'", None, None, None
234
+
235
+ # Create DataFrame
236
+ df = pd.DataFrame(data[1:], columns=data[0])
237
+ df = df[df['Farmer Name'] == farmer_name]
238
+
239
+ if df.empty:
240
+ return None, f"❌ No transactions found for farmer '{farmer_name}'", None, None, None
241
+
242
+ df['Amount'] = pd.to_numeric(df['Amount'], errors='coerce')
243
+ df['Date'] = pd.to_datetime(df['Date'], errors='coerce')
244
+ df = df.sort_values('Date')
245
+
246
+ # Calculate running balance with interest
247
+ balance = 0
248
+ total_lent = 0
249
+ total_returned = 0
250
+ last_date = None
251
+
252
+ transactions_with_interest = []
253
+
254
+ for idx, row in df.iterrows():
255
+ amount = row['Amount']
256
+ current_date = row['Date'].strftime("%Y-%m-%d")
257
+
258
+ # Calculate interest on outstanding balance since last transaction
259
+ if balance < 0 and last_date: # Negative balance means money is owed to us
260
+ interest = calculate_interest(abs(balance), last_date, current_date)
261
+ balance -= interest # Increase what's owed
262
+ transactions_with_interest.append({
263
+ 'Date': current_date,
264
+ 'Description': 'Interest Accrued',
265
+ 'Bank Account': '-',
266
+ 'Amount': -interest,
267
+ 'Balance': balance
268
+ })
269
+
270
+ # Add transaction
271
+ balance += amount
272
+
273
+ if amount < 0: # Outgoing (lent)
274
+ total_lent += abs(amount)
275
+ else: # Incoming (returned)
276
+ total_returned += amount
277
+
278
+ transactions_with_interest.append({
279
+ 'Date': current_date,
280
+ 'Description': row['Type'],
281
+ 'Bank Account': row['Bank Account'],
282
+ 'Amount': amount,
283
+ 'Balance': balance
284
+ })
285
+
286
+ last_date = current_date
287
+
288
+ # Calculate interest up to today
289
+ if balance < 0 and last_date:
290
+ today = datetime.now().strftime("%Y-%m-%d")
291
+ interest = calculate_interest(abs(balance), last_date, today)
292
+ balance -= interest
293
+ transactions_with_interest.append({
294
+ 'Date': today,
295
+ 'Description': 'Interest Accrued (to today)',
296
+ 'Bank Account': '-',
297
+ 'Amount': -interest,
298
+ 'Balance': balance
299
+ })
300
+
301
+ # Create detailed transaction DataFrame
302
+ detailed_df = pd.DataFrame(transactions_with_interest)
303
+
304
+ # Summary statistics
305
+ remaining_balance = balance
306
+
307
+ summary = f"""
308
+ πŸ“Š **Farmer Report: {farmer_name}**
309
+
310
+ πŸ’° **Total Lent:** β‚Ή{total_lent:,.2f}
311
+ πŸ’΅ **Total Returned:** β‚Ή{total_returned:,.2f}
312
+ πŸ“ˆ **Interest Accrued:** β‚Ή{(abs(remaining_balance) - (total_lent - total_returned)):,.2f}
313
+ πŸ”’ **Current Balance:** β‚Ή{abs(remaining_balance):,.2f} {'(Farmer owes you)' if remaining_balance < 0 else '(You owe farmer)'}
314
+ """
315
+
316
+ # Create visualizations
317
+ # 1. Distribution by bank
318
+ bank_dist = df.groupby('Bank Account')['Amount'].apply(lambda x: abs(x[x < 0].sum())).reset_index()
319
+ bank_dist.columns = ['Bank Account', 'Amount Lent']
320
+
321
+ fig_bank = go.Figure(go.Bar(
322
+ x=bank_dist['Amount Lent'],
323
+ y=bank_dist['Bank Account'],
324
+ orientation='h',
325
+ marker_color='lightblue'
326
+ ))
327
+ fig_bank.update_layout(
328
+ title=f"Money Lent to {farmer_name} by Bank Account",
329
+ xaxis_title="Amount (β‚Ή)",
330
+ yaxis_title="Bank Account",
331
+ height=300
332
+ )
333
+
334
+ # 2. Payment status pie chart
335
+ paid_pct = (total_returned / total_lent * 100) if total_lent > 0 else 0
336
+ remaining_pct = 100 - paid_pct
337
+
338
+ fig_pie = go.Figure(data=[go.Pie(
339
+ labels=['Returned', 'Outstanding (with interest)'],
340
+ values=[total_returned, abs(remaining_balance)],
341
+ marker_colors=['lightgreen', 'lightcoral']
342
+ )])
343
+ fig_pie.update_layout(
344
+ title=f"Payment Status for {farmer_name}",
345
+ height=300
346
+ )
347
+
348
+ return detailed_df, summary, fig_bank, fig_pie, remaining_balance
349
+
350
+ except Exception as e:
351
+ return None, f"❌ Error generating report: {str(e)}", None, None, None
352
+
353
+ # Initialize sheets on startup
354
+ initialize_sheets()
355
+
356
+ # Gradio Interface
357
+ with gr.Blocks(title="Farmer Ledger Tracker", theme=gr.themes.Soft()) as app:
358
+ gr.Markdown("# 🌾 Farmer Ledger Tracker")
359
+ gr.Markdown("Track loans to farmers with 2% monthly interest (prorated daily)")
360
+
361
+ with gr.Tabs():
362
+ # Tab 1: Add Transactions
363
+ with gr.Tab("πŸ“ Add Transaction"):
364
+ gr.Markdown("### Register New Entities")
365
+
366
+ with gr.Row():
367
+ with gr.Column():
368
+ new_farmer_input = gr.Textbox(label="New Farmer Name", placeholder="Enter farmer name")
369
+ register_farmer_btn = gr.Button("Register Farmer", variant="primary")
370
+ farmer_status = gr.Textbox(label="Status", interactive=False)
371
+
372
+ with gr.Column():
373
+ new_bank_input = gr.Textbox(label="New Bank Account", placeholder="Enter bank name")
374
+ register_bank_btn = gr.Button("Register Bank Account", variant="primary")
375
+ bank_status = gr.Textbox(label="Status", interactive=False)
376
+
377
+ gr.Markdown("### Add Transaction")
378
+
379
+ with gr.Row():
380
+ trans_date = gr.Textbox(label="Date (YYYY-MM-DD)", placeholder="2024-01-15")
381
+ trans_farmer = gr.Dropdown(label="Farmer Name", choices=get_farmers(), allow_custom_value=True)
382
+
383
+ with gr.Row():
384
+ trans_bank = gr.Dropdown(label="Bank Account", choices=get_banks(), allow_custom_value=True)
385
+ trans_amount = gr.Number(label="Amount (β‚Ή)", value=0)
386
+
387
+ trans_type = gr.Radio(
388
+ label="Transaction Type",
389
+ choices=["Outgoing (Lent to Farmer)", "Incoming (Farmer Paid Back)"],
390
+ value="Outgoing (Lent to Farmer)"
391
+ )
392
+
393
+ add_trans_btn = gr.Button("Add Transaction", variant="primary", size="lg")
394
+ trans_status = gr.Textbox(label="Status", interactive=False)
395
+
396
+ # Refresh button for dropdowns
397
+ refresh_btn = gr.Button("πŸ”„ Refresh Farmer & Bank Lists", size="sm")
398
+
399
+ def refresh_lists():
400
+ return gr.Dropdown(choices=get_farmers()), gr.Dropdown(choices=get_banks())
401
+
402
+ # Event handlers for Tab 1
403
+ register_farmer_btn.click(
404
+ fn=register_farmer,
405
+ inputs=[new_farmer_input],
406
+ outputs=[farmer_status]
407
+ )
408
+
409
+ register_bank_btn.click(
410
+ fn=register_bank,
411
+ inputs=[new_bank_input],
412
+ outputs=[bank_status]
413
+ )
414
+
415
+ add_trans_btn.click(
416
+ fn=add_transaction,
417
+ inputs=[trans_date, trans_farmer, trans_bank, trans_amount, trans_type],
418
+ outputs=[trans_status]
419
+ )
420
+
421
+ refresh_btn.click(
422
+ fn=refresh_lists,
423
+ outputs=[trans_farmer, trans_bank]
424
+ )
425
+
426
+ # Tab 2: View Reports
427
+ with gr.Tab("πŸ“Š Farmer Reports"):
428
+ gr.Markdown("### Generate Farmer Report")
429
+
430
+ report_farmer = gr.Dropdown(
431
+ label="Select Farmer",
432
+ choices=get_farmers(),
433
+ allow_custom_value=True
434
+ )
435
+
436
+ generate_report_btn = gr.Button("Generate Report", variant="primary", size="lg")
437
+
438
+ report_summary = gr.Markdown()
439
+
440
+ with gr.Row():
441
+ bank_chart = gr.Plot(label="Distribution by Bank Account")
442
+ pie_chart = gr.Plot(label="Payment Status")
443
+
444
+ report_df = gr.Dataframe(
445
+ label="Transaction History (with Interest Calculations)",
446
+ interactive=False,
447
+ wrap=True
448
+ )
449
+
450
+ download_note = gr.Markdown("πŸ’‘ **Tip:** You can copy the table data or take a screenshot to save as PDF")
451
+
452
+ # Refresh farmer list in reports tab
453
+ refresh_report_btn = gr.Button("πŸ”„ Refresh Farmer List", size="sm")
454
+
455
+ def refresh_report_list():
456
+ return gr.Dropdown(choices=get_farmers())
457
+
458
+ # Event handlers for Tab 2
459
+ generate_report_btn.click(
460
+ fn=get_farmer_report,
461
+ inputs=[report_farmer],
462
+ outputs=[report_df, report_summary, bank_chart, pie_chart, gr.State()]
463
+ )
464
+
465
+ refresh_report_btn.click(
466
+ fn=refresh_report_list,
467
+ outputs=[report_farmer]
468
+ )
469
+
470
+ if __name__ == "__main__":
471
+ app.launch()