ZeeAI1 commited on
Commit
6df50ac
·
verified ·
1 Parent(s): d3256dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -115
app.py CHANGED
@@ -4,6 +4,7 @@ import uuid
4
  import datetime
5
  import logging
6
  import re
 
7
 
8
  # Setup logging
9
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -18,8 +19,8 @@ except ImportError:
18
  AutoModelForCausalLM = None
19
  AutoTokenizer = None
20
 
21
- # Initialize AI model (distilbert as placeholder; replace with fine-tuned model or Mistral-7B)
22
- model_name = "distilbert-base-uncased" # Lightweight model for demo
23
  if TRANSFORMERS_AVAILABLE:
24
  try:
25
  tokenizer = AutoTokenizer.from_pretrained(model_name)
@@ -34,7 +35,7 @@ else:
34
  model = None
35
 
36
  # Database setup
37
- conn = sqlite3.connect("erp.db")
38
  cursor = conn.cursor()
39
 
40
  # Create tables
@@ -85,6 +86,7 @@ def initialize_chart_of_accounts():
85
  ("1.2.2", "Laptop", "Asset", "1.2", True, True),
86
  ("1.2.3", "Inventory", "Asset", "1.2", True, True),
87
  ("1.2.4", "Accounts Receivable", "Asset", "1.2", True, True),
 
88
  ("2", "Liabilities", "Liability", None, True, False),
89
  ("2.1", "Accounts Payable", "Liability", "2", True, True),
90
  ("2.2", "Loan Payable", "Liability", "2", True, True),
@@ -107,40 +109,37 @@ def initialize_chart_of_accounts():
107
  logging.info("Chart of accounts initialized.")
108
 
109
  # Enhanced fallback parser
110
- def parse_prompt(prompt):
111
  if model and tokenizer:
112
  try:
113
  input_text = f"""
114
  Parse the following accounting prompt into a JSON object with:
115
  - debit: {{account, type, amount}}
116
  - credit: {{account, type, amount}}
117
- - payment_method: 'cash', 'credit', or null
118
  Prompt: {prompt}
119
  """
120
  inputs = tokenizer(input_text, return_tensors="pt")
121
  outputs = model.generate(**inputs, max_length=300)
122
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
123
- return json.loads(response)
124
  except Exception as e:
125
  logging.warning(f"Model parsing failed: {e}. Using fallback parser.")
126
 
127
  # Fallback parser
128
  prompt_lower = prompt.lower().strip()
129
  amount = None
130
- # Extract amount
131
  match = re.search(r'\$[\d,.]+', prompt_lower)
132
  if match:
133
  try:
134
  amount = float(match.group().replace('$', '').replace(',', ''))
135
  except ValueError:
136
- logging.error("Invalid amount format.")
137
- return None
138
 
139
  if not amount:
140
- logging.error("No amount found in prompt.")
141
- return None
142
 
143
- # Map keywords to accounts and types
144
  account_mappings = {
145
  "laptop": ("Laptop", "Asset"),
146
  "inventory": ("Inventory", "Asset"),
@@ -149,14 +148,17 @@ def parse_prompt(prompt):
149
  "plant": ("Plant", "Asset"),
150
  "office supplies": ("Office Supplies", "Expense"),
151
  "cash": ("Cash", "Asset"),
 
152
  "receivable": ("Accounts Receivable", "Asset"),
153
  "sold goods": ("Sales Revenue", "Revenue"),
154
  "sales": ("Sales Revenue", "Revenue"),
155
  "rent": ("Rent Expense", "Expense"),
156
  "salary": ("Salary Expense", "Expense"),
157
  "paid": ("Cash", "Asset"),
158
- "bought": ("Laptop", "Asset"), # Default to Laptop; refine below
159
- "purchased": ("Laptop", "Asset")
 
 
160
  }
161
 
162
  debit_account = None
@@ -165,75 +167,81 @@ def parse_prompt(prompt):
165
  credit_type = None
166
  payment_method = None
167
 
168
- # Determine debit account
169
- for keyword, (account, acc_type) in account_mappings.items():
170
- if keyword in prompt_lower:
171
- if keyword in ["bought", "purchased"]:
172
- # Check for specific asset/expense
173
- for asset in ["laptop", "inventory", "machinery", "building", "plant", "office supplies"]:
174
- if asset in prompt_lower:
175
- debit_account, debit_type = account_mappings[asset]
176
- break
177
- if not debit_account:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  debit_account, debit_type = account, acc_type
179
- elif keyword in ["rent", "salary", "office supplies"]:
180
- debit_account, debit_type = account, acc_type
181
- elif keyword in ["sold goods", "sales"]:
182
- debit_account, debit_type = "Accounts Receivable", "Asset"
183
- credit_account, credit_type = account, acc_type
184
- break
 
185
 
186
- # Determine credit account and payment method
187
- if "cash" in prompt_lower:
188
- credit_account, credit_type = "Cash", "Asset"
189
- payment_method = "cash"
190
- elif "credit" in prompt_lower:
191
- credit_account, credit_type = "Accounts Payable", "Liability"
192
- payment_method = "credit"
193
- elif debit_account and not credit_account:
194
- # Ambiguous payment method
195
- return {"debit": {"account": debit_account, "type": debit_type, "amount": amount}, "credit": None, "payment_method": None}
 
 
 
 
 
196
 
197
  if debit_account and credit_account:
198
  return {
199
  "debit": {"account": debit_account, "type": debit_type, "amount": amount},
200
  "credit": {"account": credit_account, "type": credit_type, "amount": amount},
201
  "payment_method": payment_method
202
- }
203
- logging.error("Prompt not recognized.")
204
- return None
205
 
206
  # Generate journal entry
207
- def generate_journal_entry(prompt, follow_up_response=None):
208
- parsed = parse_prompt(prompt)
209
- if not parsed:
210
- return "Unable to parse prompt. Please provide more details."
 
211
 
212
  debit_account = parsed["debit"]["account"]
213
  amount = parsed["debit"]["amount"]
214
- payment_method = parsed.get("payment_method")
215
-
216
- # Handle ambiguous payment method
217
- if not payment_method and not follow_up_response:
218
- return {"status": "clarify", "message": "Was this bought on cash or credit? (cash/credit)", "original_prompt": prompt}
219
-
220
- # Determine credit account
221
- credit_account = None
222
- credit_type = None
223
- if follow_up_response:
224
- follow_up_lower = follow_up_response.lower()
225
- if follow_up_lower == "cash":
226
- credit_account, credit_type = "Cash", "Asset"
227
- elif follow_up_lower == "credit":
228
- credit_account, credit_type = "Accounts Payable", "Liability"
229
- else:
230
- return "Invalid response. Please specify 'cash' or 'credit'."
231
- elif payment_method == "cash":
232
- credit_account, credit_type = parsed["credit"]["account"], parsed["credit"]["type"]
233
- elif payment_method == "credit":
234
- credit_account, credit_type = "Accounts Payable", "Liability"
235
- else:
236
- return "Invalid payment method specified."
237
 
238
  # Validate accounts
239
  cursor.execute("SELECT account_id, account_type, allow_posting FROM chart_of_accounts WHERE account_name = ?", (debit_account,))
@@ -242,25 +250,24 @@ def generate_journal_entry(prompt, follow_up_response=None):
242
  credit_result = cursor.fetchone()
243
 
244
  if not debit_result or not credit_result:
245
- return "One or both accounts not found in chart of accounts."
246
  if not debit_result[2] or not credit_result[2]:
247
- return "Posting not allowed for one or both accounts."
248
-
249
- # Validate account types
250
  if debit_result[1] != parsed["debit"]["type"] or credit_result[1] != credit_type:
251
- return "Account type mismatch."
252
 
253
  # Create journal entry
254
  entry_id = str(uuid.uuid4())
255
  date = datetime.datetime.now().isoformat()
 
256
  cursor.execute("""
257
  INSERT INTO journal_entries (entry_id, date, debit_account_id, credit_account_id, amount, description)
258
  VALUES (?, ?, ?, ?, ?, ?)
259
- """, (entry_id, date, debit_result[0], credit_result[0], amount, prompt))
260
  conn.commit()
261
  logging.info(f"Journal entry created: Debit {debit_account} ${amount}, Credit {credit_account} ${amount}")
262
 
263
- return f"Journal Entry Created: Debit {debit_account} ${amount}, Credit {credit_account} ${amount}"
264
 
265
  # Generate T-account
266
  def generate_t_account(account_name):
@@ -298,46 +305,41 @@ def generate_t_account(account_name):
298
 
299
  return t_account
300
 
301
- # Main CLI loop
302
- def main():
 
 
 
 
303
  initialize_chart_of_accounts()
304
- print("AI ERP System: Enter accounting prompts or 't-account <account_name>' to view T-accounts. Type 'exit' to quit.")
305
 
306
- pending_prompt = None
307
- while True:
308
- try:
309
- prompt = input("> ").strip()
310
- if prompt.lower() == "exit":
311
- break
312
- if prompt.lower().startswith("t-account "):
313
- account_name = prompt[10:].strip()
314
- if account_name:
315
- print(generate_t_account(account_name))
316
- else:
317
- print("Please specify an account name.")
318
- continue
319
-
320
- # Handle follow-up response
321
- if pending_prompt:
322
- result = generate_journal_entry(pending_prompt, prompt)
323
- pending_prompt = None
324
- else:
325
- result = generate_journal_entry(prompt)
326
-
327
- if isinstance(result, dict) and result["status"] == "clarify":
328
- print(result["message"])
329
- pending_prompt = result["original_prompt"]
330
- else:
331
- print(result)
332
-
333
- except KeyboardInterrupt:
334
- print("\nExiting...")
335
- break
336
- except Exception as e:
337
- logging.error(f"Error processing prompt: {e}")
338
- print("An error occurred. Please try again.")
339
 
340
- conn.close()
 
341
 
 
342
  if __name__ == "__main__":
343
- main()
 
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')
 
19
  AutoModelForCausalLM = None
20
  AutoTokenizer = None
21
 
22
+ # Initialize AI model (t5-small for better accounting parsing; fallback to distilbert)
23
+ model_name = "t5-small" # Smaller model for free tier
24
  if TRANSFORMERS_AVAILABLE:
25
  try:
26
  tokenizer = AutoTokenizer.from_pretrained(model_name)
 
35
  model = None
36
 
37
  # Database setup
38
+ conn = sqlite3.connect("erp.db", check_same_thread=False)
39
  cursor = conn.cursor()
40
 
41
  # Create tables
 
86
  ("1.2.2", "Laptop", "Asset", "1.2", True, True),
87
  ("1.2.3", "Inventory", "Asset", "1.2", True, True),
88
  ("1.2.4", "Accounts Receivable", "Asset", "1.2", True, True),
89
+ ("1.2.5", "Bank", "Asset", "1.2", True, True),
90
  ("2", "Liabilities", "Liability", None, True, False),
91
  ("2.1", "Accounts Payable", "Liability", "2", True, True),
92
  ("2.2", "Loan Payable", "Liability", "2", True, True),
 
109
  logging.info("Chart of accounts initialized.")
110
 
111
  # Enhanced fallback parser
112
+ def parse_prompt(prompt, state):
113
  if model and tokenizer:
114
  try:
115
  input_text = f"""
116
  Parse the following accounting prompt into a JSON object with:
117
  - debit: {{account, type, amount}}
118
  - credit: {{account, type, amount}}
119
+ - payment_method: 'cash', 'credit', 'bank', or null
120
  Prompt: {prompt}
121
  """
122
  inputs = tokenizer(input_text, return_tensors="pt")
123
  outputs = model.generate(**inputs, max_length=300)
124
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
125
+ return json.loads(response), state
126
  except Exception as e:
127
  logging.warning(f"Model parsing failed: {e}. Using fallback parser.")
128
 
129
  # Fallback parser
130
  prompt_lower = prompt.lower().strip()
131
  amount = None
 
132
  match = re.search(r'\$[\d,.]+', prompt_lower)
133
  if match:
134
  try:
135
  amount = float(match.group().replace('$', '').replace(',', ''))
136
  except ValueError:
137
+ return {"error": "Invalid amount format."}, state
 
138
 
139
  if not amount:
140
+ return {"error": "No amount found in prompt."}, state
 
141
 
142
+ # Account mappings
143
  account_mappings = {
144
  "laptop": ("Laptop", "Asset"),
145
  "inventory": ("Inventory", "Asset"),
 
148
  "plant": ("Plant", "Asset"),
149
  "office supplies": ("Office Supplies", "Expense"),
150
  "cash": ("Cash", "Asset"),
151
+ "bank": ("Bank", "Asset"),
152
  "receivable": ("Accounts Receivable", "Asset"),
153
  "sold goods": ("Sales Revenue", "Revenue"),
154
  "sales": ("Sales Revenue", "Revenue"),
155
  "rent": ("Rent Expense", "Expense"),
156
  "salary": ("Salary Expense", "Expense"),
157
  "paid": ("Cash", "Asset"),
158
+ "bought": ("Laptop", "Asset"),
159
+ "purchased": ("Laptop", "Asset"),
160
+ "owner's draw": ("Drawings", "Equity"),
161
+ "loan": ("Loan Payable", "Liability")
162
  }
163
 
164
  debit_account = None
 
167
  credit_type = None
168
  payment_method = None
169
 
170
+ # Check if this is a follow-up response
171
+ if state.get("pending_prompt"):
172
+ follow_up = prompt_lower
173
+ if follow_up in ["cash", "credit", "bank"]:
174
+ payment_method = follow_up
175
+ parsed = state["pending_parsed"]
176
+ debit_account = parsed["debit"]["account"]
177
+ debit_type = parsed["debit"]["type"]
178
+ amount = parsed["debit"]["amount"]
179
+ if payment_method == "cash":
180
+ credit_account, credit_type = "Cash", "Asset"
181
+ elif payment_method == "bank":
182
+ credit_account, credit_type = "Bank", "Asset"
183
+ elif payment_method == "credit":
184
+ credit_account, credit_type = "Accounts Payable", "Liability"
185
+ state = {} # Clear state
186
+ else:
187
+ return {"error": "Please respond with 'cash', 'credit', or 'bank'."}, state
188
+
189
+ else:
190
+ # Parse new prompt
191
+ for keyword, (account, acc_type) in account_mappings.items():
192
+ if keyword in prompt_lower:
193
+ if keyword in ["bought", "purchased"]:
194
+ for asset in ["laptop", "inventory", "machinery", "building", "plant", "office supplies"]:
195
+ if asset in prompt_lower:
196
+ debit_account, debit_type = account_mappings[asset]
197
+ break
198
+ if not debit_account:
199
+ debit_account, debit_type = account, acc_type
200
+ elif keyword in ["rent", "salary", "office supplies"]:
201
  debit_account, debit_type = account, acc_type
202
+ elif keyword in ["sold goods", "sales"]:
203
+ debit_account, debit_type = "Accounts Receivable", "Asset"
204
+ credit_account, credit_type = account, acc_type
205
+ elif keyword == "owner's draw":
206
+ debit_account, debit_type = "Drawings", "Equity"
207
+ credit_account, credit_type = "Cash", "Asset"
208
+ break
209
 
210
+ if "cash" in prompt_lower:
211
+ credit_account, credit_type = "Cash", "Asset"
212
+ payment_method = "cash"
213
+ elif "bank" in prompt_lower:
214
+ credit_account, credit_type = "Bank", "Asset"
215
+ payment_method = "bank"
216
+ elif "credit" in prompt_lower:
217
+ credit_account, credit_type = "Accounts Payable", "Liability"
218
+ payment_method = "credit"
219
+ elif debit_account and not credit_account:
220
+ return {
221
+ "status": "clarify",
222
+ "message": "Was this bought on cash, credit, or bank?",
223
+ "pending_parsed": {"debit": {"account": debit_account, "type": debit_type, "amount": amount}}
224
+ }, {"pending_prompt": prompt, "pending_parsed": {"debit": {"account": debit_account, "type": debit_type, "amount": amount}}}
225
 
226
  if debit_account and credit_account:
227
  return {
228
  "debit": {"account": debit_account, "type": debit_type, "amount": amount},
229
  "credit": {"account": credit_account, "type": credit_type, "amount": amount},
230
  "payment_method": payment_method
231
+ }, state
232
+ return {"error": "Prompt not recognized. Try 'Bought a laptop for $200' or 'Paid rent $400'."}, state
 
233
 
234
  # Generate journal entry
235
+ def generate_journal_entry(parsed, state):
236
+ if "error" in parsed:
237
+ return parsed["error"], state
238
+ if parsed.get("status") == "clarify":
239
+ return parsed["message"], state
240
 
241
  debit_account = parsed["debit"]["account"]
242
  amount = parsed["debit"]["amount"]
243
+ credit_account = parsed["credit"]["account"]
244
+ credit_type = parsed["credit"]["type"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  # Validate accounts
247
  cursor.execute("SELECT account_id, account_type, allow_posting FROM chart_of_accounts WHERE account_name = ?", (debit_account,))
 
250
  credit_result = cursor.fetchone()
251
 
252
  if not debit_result or not credit_result:
253
+ return "One or both accounts not found.", state
254
  if not debit_result[2] or not credit_result[2]:
255
+ return "Posting not allowed for one or both accounts.", state
 
 
256
  if debit_result[1] != parsed["debit"]["type"] or credit_result[1] != credit_type:
257
+ return "Account type mismatch.", state
258
 
259
  # Create journal entry
260
  entry_id = str(uuid.uuid4())
261
  date = datetime.datetime.now().isoformat()
262
+ description = state.get("pending_prompt", "Transaction")
263
  cursor.execute("""
264
  INSERT INTO journal_entries (entry_id, date, debit_account_id, credit_account_id, amount, description)
265
  VALUES (?, ?, ?, ?, ?, ?)
266
+ """, (entry_id, date, debit_result[0], credit_result[0], amount, description))
267
  conn.commit()
268
  logging.info(f"Journal entry created: Debit {debit_account} ${amount}, Credit {credit_account} ${amount}")
269
 
270
+ return f"Journal Entry Created: Debit {debit_account} ${amount}, Credit {credit_account} ${amount}", {}
271
 
272
  # Generate T-account
273
  def generate_t_account(account_name):
 
305
 
306
  return t_account
307
 
308
+ # Gradio chat function
309
+ def chat_function(message, history, state=None):
310
+ if state is None:
311
+ state = {}
312
+
313
+ # Initialize chart of accounts
314
  initialize_chart_of_accounts()
 
315
 
316
+ # Handle T-account request
317
+ if message.lower().startswith("t-account "):
318
+ account_name = message[10:].strip()
319
+ if account_name:
320
+ return generate_t_account(account_name), state
321
+ return "Please specify an account name.", state
322
+
323
+ # Parse prompt and generate entry
324
+ parsed, state = parse_prompt(message, state)
325
+ response, state = generate_journal_entry(parsed, state)
326
+
327
+ return response, state
328
+
329
+ # Gradio interface
330
+ with gr.Blocks() as demo:
331
+ gr.Markdown("# AI ERP System")
332
+ gr.Markdown("Enter accounting prompts like 'Bought a laptop for $200' or 't-account Laptop'. The system will ask for clarification if needed.")
333
+ chatbot = gr.Chatbot()
334
+ msg = gr.Textbox(placeholder="Type your prompt here...")
335
+ clear = gr.Button("Clear")
336
+
337
+ # Maintain state
338
+ state = gr.State({})
 
 
 
 
 
 
 
 
 
 
339
 
340
+ msg.submit(chat_function, [msg, chatbot, state], [chatbot, state])
341
+ clear.click(lambda: None, None, chatbot, queue=False)
342
 
343
+ # Launch Gradio
344
  if __name__ == "__main__":
345
+ demo.launch(server_name="0.0.0.0", server_port=7860)