ZeeAI1 commited on
Commit
643635c
·
verified ·
1 Parent(s): 8453afa

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -0
app.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import json
3
+ import uuid
4
+ import datetime
5
+ import logging
6
+ import re
7
+ import gradio as gr
8
+
9
+ # Setup logging
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
+
12
+ # Disable model loading on free tier due to memory constraints
13
+ TRANSFORMERS_AVAILABLE = False
14
+ AutoModelForCausalLM = None
15
+ AutoTokenizer = None
16
+ tokenizer = None
17
+ model = None
18
+
19
+ # Database setup
20
+ conn = sqlite3.connect("erp.db", check_same_thread=False)
21
+ cursor = conn.cursor()
22
+
23
+ # Create tables
24
+ cursor.execute("""
25
+ CREATE TABLE IF NOT EXISTS chart_of_accounts (
26
+ account_id TEXT PRIMARY KEY,
27
+ account_name TEXT NOT NULL,
28
+ account_type TEXT NOT NULL,
29
+ parent_id TEXT,
30
+ allow_budgeting BOOLEAN,
31
+ allow_posting BOOLEAN,
32
+ FOREIGN KEY (parent_id) REFERENCES chart_of_accounts(account_id)
33
+ )
34
+ """)
35
+ cursor.execute("""
36
+ CREATE TABLE IF NOT EXISTS journal_entries (
37
+ entry_id TEXT PRIMARY KEY,
38
+ date TEXT NOT NULL,
39
+ debit_account_id TEXT NOT NULL,
40
+ credit_account_id TEXT NOT NULL,
41
+ amount REAL NOT NULL,
42
+ description TEXT,
43
+ FOREIGN KEY (debit_account_id) REFERENCES chart_of_accounts(account_id),
44
+ FOREIGN KEY (credit_account_id) REFERENCES chart_of_accounts(account_id)
45
+ )
46
+ """)
47
+ conn.commit()
48
+
49
+ # Debit/Credit rules
50
+ ACCOUNT_RULES = {
51
+ "Asset": {"increase": "Debit", "decrease": "Credit"},
52
+ "Liability": {"increase": "Credit", "decrease": "Debit"},
53
+ "Equity": {"increase": "Credit", "decrease": "Debit"},
54
+ "Revenue": {"increase": "Credit", "decrease": "Debit"},
55
+ "Expense": {"increase": "Debit", "decrease": "Credit"}
56
+ }
57
+
58
+ # Initialize chart of accounts
59
+ def initialize_chart_of_accounts():
60
+ accounts = [
61
+ ("1", "Assets", "Asset", None, True, False),
62
+ ("1.1", "Fixed Assets", "Asset", "1", True, False),
63
+ ("1.1.1", "Plant", "Asset", "1.1", True, True),
64
+ ("1.1.2", "Machinery", "Asset", "1.1", True, True),
65
+ ("1.1.3", "Building", "Asset", "1.1", True, True),
66
+ ("1.2", "Current Assets", "Asset", "1", True, False),
67
+ ("1.2.1", "Cash", "Asset", "1.2", True, True),
68
+ ("1.2.2", "Laptop", "Asset", "1.2", True, True),
69
+ ("1.2.3", "Inventory", "Asset", "1.2", True, True),
70
+ ("1.2.4", "Accounts Receivable", "Asset", "1.2", True, True),
71
+ ("1.2.5", "Bank", "Asset", "1.2", True, True),
72
+ ("2", "Liabilities", "Liability", None, True, False),
73
+ ("2.1", "Accounts Payable", "Liability", "2", True, True),
74
+ ("2.2", "Loan Payable", "Liability", "2", True, True),
75
+ ("3", "Equity", "Equity", None, True, False),
76
+ ("3.1", "Owner's Equity", "Equity", "3", True, True),
77
+ ("3.2", "Drawings", "Equity", "3", True, True),
78
+ ("4", "Revenue", "Revenue", None, True, False),
79
+ ("4.1", "Sales Revenue", "Revenue", "4", True, True),
80
+ ("5", "Expenses", "Expense", None, True, False),
81
+ ("5.1", "Rent Expense", "Expense", "5", True, True),
82
+ ("5.2", "Salary Expense", "Expense", "5", True, True),
83
+ ("5.3", "Office Supplies", "Expense", "5", True, True)
84
+ ]
85
+ cursor.executemany("""
86
+ INSERT OR REPLACE INTO chart_of_accounts
87
+ (account_id, account_name, account_type, parent_id, allow_budgeting, allow_posting)
88
+ VALUES (?, ?, ?, ?, ?, ?)
89
+ """, accounts)
90
+ conn.commit()
91
+ logging.info("Chart of accounts initialized.")
92
+
93
+ # Enhanced flexible parser
94
+ def parse_prompt(prompt, state):
95
+ logging.info(f"Parsing prompt: {prompt}")
96
+ if model and tokenizer:
97
+ try:
98
+ input_text = f"""
99
+ Parse the following accounting prompt into a JSON object with:
100
+ - debit: {{account, type, amount}}
101
+ - credit: {{account, type, amount}}
102
+ - payment_method: 'cash', 'credit', 'bank', or null
103
+ Prompt: {prompt}
104
+ """
105
+ inputs = tokenizer(input_text, return_tensors="pt")
106
+ outputs = model.generate(**inputs, max_length=300)
107
+ response = tokenizer.decode(outputs[0], skip_special_tokens=True)
108
+ return json.loads(response), state
109
+ except Exception as e:
110
+ logging.error(f"Model parsing failed: {e}")
111
+
112
+ prompt_lower = prompt.lower().strip()
113
+ amount = None
114
+ match = re.search(r'\$?(\d{1,3}(?:,\d{3})*|\d+)(?:\.\d{2})?', prompt_lower)
115
+ if match:
116
+ try:
117
+ amount = float(match.group().replace(',', '').replace('$', ''))
118
+ except ValueError:
119
+ return {"error": "Invalid amount format detected. Please use a valid number (e.g., $200 or 200)."}, state
120
+
121
+ if not amount and not state.get("pending_prompt"):
122
+ return {"error": "No amount found in prompt. Please include an amount (e.g., $200)."}, state
123
+
124
+ account_mappings = {
125
+ "laptop": ("Laptop", "Asset"),
126
+ "inventory": ("Inventory", "Asset"),
127
+ "machinery": ("Machinery", "Asset"),
128
+ "building": ("Building", "Asset"),
129
+ "plant": ("Plant", "Asset"),
130
+ "office supplies": ("Office Supplies", "Expense"),
131
+ "cash": ("Cash", "Asset"),
132
+ "bank": ("Bank", "Asset"),
133
+ "receivable": ("Accounts Receivable", "Asset"),
134
+ "payable": ("Accounts Payable", "Liability"),
135
+ "loan": ("Loan Payable", "Liability"),
136
+ "equity": ("Owner's Equity", "Equity"),
137
+ "drawings": ("Drawings", "Equity"),
138
+ "sales": ("Sales Revenue", "Revenue"),
139
+ "rent": ("Rent Expense", "Expense"),
140
+ "salary": ("Salary Expense", "Expense"),
141
+ "wages": ("Salary Expense", "Expense")
142
+ }
143
+
144
+ debit_account = None
145
+ debit_type = None
146
+ credit_account = None
147
+ credit_type = None
148
+ payment_method = None
149
+
150
+ if state.get("pending_prompt"):
151
+ follow_up = prompt_lower
152
+ if follow_up in ["cash", "credit", "bank"]:
153
+ payment_method = follow_up
154
+ parsed = state["pending_parsed"]
155
+ debit_account = parsed["debit"]["account"]
156
+ debit_type = parsed["debit"]["type"]
157
+ amount = parsed["debit"]["amount"]
158
+ if payment_method == "cash":
159
+ credit_account, credit_type = "Cash", "Asset"
160
+ elif payment_method == "bank":
161
+ credit_account, credit_type = "Bank", "Asset"
162
+ elif payment_method == "credit":
163
+ credit_account, credit_type = "Accounts Payable", "Liability"
164
+ state = {} # Clear state after successful follow-up
165
+ else:
166
+ return {"error": "Please specify 'cash', 'credit', or 'bank'."}, state
167
+ else:
168
+ # Detect transaction type and accounts
169
+ for keyword, (account, acc_type) in account_mappings.items():
170
+ if keyword in prompt_lower:
171
+ if keyword in ["bought", "purchased"]:
172
+ for asset in ["laptop", "inventory", "machinery", "building", "plant", "office supplies"]:
173
+ if asset in prompt_lower:
174
+ debit_account, debit_type = account_mappings[asset]
175
+ break
176
+ if not debit_account:
177
+ debit_account, debit_type = "Inventory", "Asset" # Default asset
178
+ elif keyword in ["paid", "spent"]:
179
+ for expense in ["rent", "salary", "wages", "office supplies"]:
180
+ if expense in prompt_lower:
181
+ debit_account, debit_type = account_mappings[expense]
182
+ break
183
+ if not debit_account:
184
+ debit_account, debit_type = "Expense", "Expense" # Generic expense
185
+ # Detect payment method in initial prompt
186
+ if "cash" in prompt_lower:
187
+ credit_account, credit_type = "Cash", "Asset"
188
+ payment_method = "cash"
189
+ elif "bank" in prompt_lower:
190
+ credit_account, credit_type = "Bank", "Asset"
191
+ payment_method = "bank"
192
+ elif "credit" in prompt_lower:
193
+ credit_account, credit_type = "Accounts Payable", "Liability"
194
+ payment_method = "credit"
195
+ elif keyword in ["sold", "sales"]:
196
+ debit_account, debit_type = "Accounts Receivable", "Asset"
197
+ credit_account, credit_type = account_mappings["sales"]
198
+ elif keyword == "drawings":
199
+ debit_account, debit_type = "Drawings", "Equity"
200
+ credit_account, credit_type = "Cash", "Asset"
201
+ elif keyword == "loan":
202
+ debit_account, debit_type = "Cash", "Asset"
203
+ credit_account, credit_type = "Loan Payable", "Liability"
204
+ break
205
+
206
+ if debit_account and not credit_account and not payment_method:
207
+ return {
208
+ "status": "clarify",
209
+ "message": "Was this transaction made with cash, credit, or bank?",
210
+ "pending_parsed": {"debit": {"account": debit_account, "type": debit_type, "amount": amount}}
211
+ }, {"pending_prompt": prompt, "pending_parsed": {"debit": {"account": debit_account, "type": debit_type, "amount": amount}}}
212
+
213
+ if debit_account and credit_account:
214
+ return {
215
+ "debit": {"account": debit_account, "type": debit_type, "amount": amount},
216
+ "credit": {"account": credit_account, "type": credit_type, "amount": amount},
217
+ "payment_method": payment_method
218
+ }, state
219
+ return {"error": "Couldn’t interpret the transaction. Try 'Paid rent $400' or 'Bought a laptop for $200'."}, state
220
+
221
+ # Generate journal entry
222
+ def generate_journal_entry(parsed, state):
223
+ logging.info(f"Generating journal entry with parsed: {parsed}")
224
+ if "error" in parsed:
225
+ return parsed["error"], state
226
+ if parsed.get("status") == "clarify":
227
+ return parsed["message"], state
228
+
229
+ debit_account = parsed["debit"]["account"]
230
+ amount = parsed["debit"]["amount"]
231
+ credit_account = parsed["credit"]["account"]
232
+ credit_type = parsed["credit"]["type"]
233
+
234
+ cursor.execute("SELECT account_id, account_type, allow_posting FROM chart_of_accounts WHERE account_name = ?", (debit_account,))
235
+ debit_result = cursor.fetchone()
236
+ cursor.execute("SELECT account_id, account_type, allow_posting FROM chart_of_accounts WHERE account_name = ?", (credit_account,))
237
+ credit_result = cursor.fetchone()
238
+
239
+ if not debit_result or not credit_result:
240
+ return "One or both accounts not found.", state
241
+ if not debit_result[2] or not credit_result[2]:
242
+ return "Posting not allowed for one or both accounts.", state
243
+ if debit_result[1] != parsed["debit"]["type"] or credit_result[1] != credit_type:
244
+ return "Account type mismatch.", state
245
+
246
+ entry_id = str(uuid.uuid4())
247
+ date = datetime.datetime.now().isoformat()
248
+ description = state.get("pending_prompt", "Transaction")
249
+ try:
250
+ cursor.execute("""
251
+ INSERT INTO journal_entries (entry_id, date, debit_account_id, credit_account_id, amount, description)
252
+ VALUES (?, ?, ?, ?, ?, ?)
253
+ """, (entry_id, date, debit_result[0], credit_result[0], amount, description))
254
+ conn.commit()
255
+ logging.info(f"Journal entry created: Debit {debit_account} ${amount}, Credit {credit_account} ${amount}")
256
+ except sqlite3.Error as e:
257
+ logging.error(f"Database error: {e}")
258
+ return "Database error occurred.", state
259
+
260
+ return f"Journal Entry Recorded:\nDebit: {debit_account} ${amount}\nCredit: {credit_account} ${amount}", state
261
+
262
+ # Generate T-account
263
+ def generate_t_account(account_name):
264
+ cursor.execute("SELECT account_id FROM chart_of_accounts WHERE account_name = ?", (account_name,))
265
+ account_id = cursor.fetchone()
266
+ if not account_id:
267
+ logging.error(f"Account {account_name} not found.")
268
+ return "Account not found."
269
+
270
+ account_id = account_id[0]
271
+ try:
272
+ cursor.execute("""
273
+ SELECT date, amount, description, 'Debit' as type FROM journal_entries WHERE debit_account_id = ?
274
+ UNION
275
+ SELECT date, amount, description, 'Credit' as type FROM journal_entries WHERE credit_account_id = ?
276
+ ORDER BY date
277
+ """, (account_id, account_id))
278
+ entries = cursor.fetchall()
279
+ logging.info(f"Retrieved {len(entries)} entries for T-account: {account_name}")
280
+ except sqlite3.Error as e:
281
+ logging.error(f"SQL error in generate_t_account: {e}")
282
+ return "Error retrieving T-account data."
283
+
284
+ t_account = f"T-Account for {account_name}\n{'='*50}\n{'Debit':<20} | {'Credit':<20} | Description\n{'-'*50}\n"
285
+ debit_total = 0
286
+ credit_total = 0
287
+ for date, amount, desc, entry_type in entries:
288
+ if entry_type == "Debit":
289
+ t_account += f"${amount:<19} | {'':<20} | {desc}\n"
290
+ debit_total += amount
291
+ else:
292
+ t_account += f"{'':<20} | ${amount:<19} | {desc}\n"
293
+ credit_total += amount
294
+ t_account += f"{'-'*50}\nTotal Debit: ${debit_total:<10} | Total Credit: ${credit_total}\n"
295
+
296
+ return t_account
297
+
298
+ # Gradio chat function
299
+ def chat_function(message, history, state=None):
300
+ if state is None:
301
+ state = {}
302
+ initialize_chart_of_accounts()
303
+ logging.info("Initialized state and chart of accounts")
304
+
305
+ logging.info(f"Received message: {message}")
306
+
307
+ if message.lower().startswith("t-account "):
308
+ account_name = message[10:].strip()
309
+ if account_name:
310
+ response = generate_t_account(account_name)
311
+ else:
312
+ response = "Please provide an account name (e.g., 't-account Cash')."
313
+ else:
314
+ parsed, state = parse_prompt(message, state)
315
+ response, state = generate_journal_entry(parsed, state)
316
+
317
+ if history is not None:
318
+ history.append({"role": "user", "content": message})
319
+ history.append({"role": "assistant", "content": response})
320
+ else:
321
+ history = [
322
+ {"role": "user", "content": message},
323
+ {"role": "assistant", "content": response}
324
+ ]
325
+
326
+ return history, state, "" # Clear the textbox
327
+
328
+ # Gradio interface
329
+ with gr.Blocks() as demo:
330
+ gr.Markdown("# AI ERP System")
331
+ gr.Markdown("Enter accounting prompts naturally (e.g., 'Paid rent $400' or 't-account Cash'). I’ll guide you like an experienced accountant if clarification is needed.")
332
+ chatbot = gr.Chatbot(type="messages")
333
+ msg = gr.Textbox(placeholder="Type your prompt here...", lines=1, submit_btn=None)
334
+ clear = gr.Button("Clear")
335
+
336
+ state = gr.State({})
337
+
338
+ msg.submit(chat_function, [msg, chatbot, state], [chatbot, state, msg])
339
+ clear.click(lambda: ([], {}, ""), None, [chatbot, state, msg], queue=False)
340
+
341
+ # Launch Gradio
342
+ if __name__ == "__main__":
343
+ demo.launch(server_name="0.0.0.0", server_port=7860)