Update app.py
Browse files
app.py
CHANGED
@@ -3,7 +3,7 @@ import json
|
|
3 |
import uuid
|
4 |
import datetime
|
5 |
import logging
|
6 |
-
|
7 |
|
8 |
# Setup logging
|
9 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
@@ -18,9 +18,6 @@ except ImportError:
|
|
18 |
AutoModelForCausalLM = None
|
19 |
AutoTokenizer = None
|
20 |
|
21 |
-
# Initialize Flask app
|
22 |
-
app = Flask(__name__)
|
23 |
-
|
24 |
# Initialize AI model (distilbert as placeholder; replace with fine-tuned model or Mistral-7B)
|
25 |
model_name = "distilbert-base-uncased" # Lightweight model for demo
|
26 |
if TRANSFORMERS_AVAILABLE:
|
@@ -37,7 +34,7 @@ else:
|
|
37 |
model = None
|
38 |
|
39 |
# Database setup
|
40 |
-
conn = sqlite3.connect("erp.db"
|
41 |
cursor = conn.cursor()
|
42 |
|
43 |
# Create tables
|
@@ -86,14 +83,20 @@ def initialize_chart_of_accounts():
|
|
86 |
("1.2", "Current Assets", "Asset", "1", True, False),
|
87 |
("1.2.1", "Cash", "Asset", "1.2", True, True),
|
88 |
("1.2.2", "Laptop", "Asset", "1.2", True, True),
|
|
|
|
|
89 |
("2", "Liabilities", "Liability", None, True, False),
|
90 |
("2.1", "Accounts Payable", "Liability", "2", True, True),
|
|
|
91 |
("3", "Equity", "Equity", None, True, False),
|
92 |
-
("3.1", "Owner's
|
|
|
93 |
("4", "Revenue", "Revenue", None, True, False),
|
94 |
-
("4.1", "Sales", "Revenue", "4", True, True),
|
95 |
("5", "Expenses", "Expense", None, True, False),
|
96 |
-
("5.1", "
|
|
|
|
|
97 |
]
|
98 |
cursor.executemany("""
|
99 |
INSERT OR REPLACE INTO chart_of_accounts
|
@@ -103,7 +106,7 @@ def initialize_chart_of_accounts():
|
|
103 |
conn.commit()
|
104 |
logging.info("Chart of accounts initialized.")
|
105 |
|
106 |
-
#
|
107 |
def parse_prompt(prompt):
|
108 |
if model and tokenizer:
|
109 |
try:
|
@@ -111,7 +114,7 @@ def parse_prompt(prompt):
|
|
111 |
Parse the following accounting prompt into a JSON object with:
|
112 |
- debit: {{account, type, amount}}
|
113 |
- credit: {{account, type, amount}}
|
114 |
-
- payment_method: 'cash'
|
115 |
Prompt: {prompt}
|
116 |
"""
|
117 |
inputs = tokenizer(input_text, return_tensors="pt")
|
@@ -121,34 +124,77 @@ def parse_prompt(prompt):
|
|
121 |
except Exception as e:
|
122 |
logging.warning(f"Model parsing failed: {e}. Using fallback parser.")
|
123 |
|
124 |
-
# Fallback
|
125 |
-
prompt_lower = prompt.lower()
|
126 |
amount = None
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
|
|
135 |
if not amount:
|
136 |
logging.error("No amount found in prompt.")
|
137 |
return None
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
return {
|
153 |
"debit": {"account": debit_account, "type": debit_type, "amount": amount},
|
154 |
"credit": {"account": credit_account, "type": credit_type, "amount": amount},
|
@@ -169,20 +215,23 @@ def generate_journal_entry(prompt, follow_up_response=None):
|
|
169 |
|
170 |
# Handle ambiguous payment method
|
171 |
if not payment_method and not follow_up_response:
|
172 |
-
return {"status": "clarify", "message": "
|
173 |
|
174 |
# Determine credit account
|
175 |
credit_account = None
|
176 |
credit_type = None
|
177 |
-
if follow_up_response
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
180 |
elif payment_method == "cash":
|
181 |
-
credit_account = parsed["credit"]["account"]
|
182 |
-
credit_type = parsed["credit"]["type"]
|
183 |
elif payment_method == "credit":
|
184 |
-
credit_account = "Accounts Payable"
|
185 |
-
credit_type = "Liability"
|
186 |
else:
|
187 |
return "Invalid payment method specified."
|
188 |
|
@@ -249,91 +298,46 @@ def generate_t_account(account_name):
|
|
249 |
|
250 |
return t_account
|
251 |
|
252 |
-
#
|
253 |
-
|
254 |
-
<!DOCTYPE html>
|
255 |
-
<html>
|
256 |
-
<head>
|
257 |
-
<title>AI ERP System</title>
|
258 |
-
<style>
|
259 |
-
body { font-family: Arial, sans-serif; margin: 20px; }
|
260 |
-
h1 { color: #333; }
|
261 |
-
textarea, input { width: 100%; margin: 10px 0; }
|
262 |
-
pre { background: #f4f4f4; padding: 10px; white-space: pre-wrap; }
|
263 |
-
.error { color: red; }
|
264 |
-
</style>
|
265 |
-
</head>
|
266 |
-
<body>
|
267 |
-
<h1>AI ERP System</h1>
|
268 |
-
<h2>Enter Transaction Prompt</h2>
|
269 |
-
<form method="POST" action="/process_prompt">
|
270 |
-
<textarea name="prompt" rows="4" placeholder="e.g., Bought a laptop for $200 on cash"></textarea>
|
271 |
-
<input type="submit" value="Submit Prompt">
|
272 |
-
</form>
|
273 |
-
{% if result %}
|
274 |
-
<h2>Result</h2>
|
275 |
-
{% if result.status == 'clarify' %}
|
276 |
-
<p>{{ result.message }}</p>
|
277 |
-
<form method="POST" action="/process_follow_up">
|
278 |
-
<input type="hidden" name="original_prompt" value="{{ result.original_prompt }}">
|
279 |
-
<input type="text" name="follow_up" placeholder="Yes/No">
|
280 |
-
<input type="submit" value="Submit Response">
|
281 |
-
</form>
|
282 |
-
{% else %}
|
283 |
-
<pre>{{ result }}</pre>
|
284 |
-
{% endif %}
|
285 |
-
{% endif %}
|
286 |
-
<h2>View T-Account</h2>
|
287 |
-
<form method="POST" action="/t_account">
|
288 |
-
<input type="text" name="account_name" placeholder="e.g., Laptop">
|
289 |
-
<input type="submit" value="Generate T-Account">
|
290 |
-
</form>
|
291 |
-
{% if t_account %}
|
292 |
-
<h2>T-Account</h2>
|
293 |
-
<pre>{{ t_account }}</pre>
|
294 |
-
{% endif %}
|
295 |
-
</body>
|
296 |
-
</html>
|
297 |
-
"""
|
298 |
-
|
299 |
-
# Flask routes
|
300 |
-
@app.route("/", methods=["GET", "POST"])
|
301 |
-
def index():
|
302 |
initialize_chart_of_accounts()
|
303 |
-
|
304 |
-
|
305 |
-
@app.route("/process_prompt", methods=["POST"])
|
306 |
-
def process_prompt():
|
307 |
-
prompt = request.form.get("prompt")
|
308 |
-
if not prompt:
|
309 |
-
return render_template_string(HTML_TEMPLATE, result="No prompt provided.", t_account=None)
|
310 |
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
|
330 |
-
|
331 |
-
return render_template_string(HTML_TEMPLATE, result=None, t_account=t_account)
|
332 |
|
333 |
-
# Run the app
|
334 |
if __name__ == "__main__":
|
335 |
-
|
336 |
-
initialize_chart_of_accounts()
|
337 |
-
|
338 |
-
# Run Flask app (use port 7860 for Hugging Face Spaces)
|
339 |
-
app.run(host="0.0.0.0", port=7860, debug=False)
|
|
|
3 |
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 |
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:
|
|
|
34 |
model = None
|
35 |
|
36 |
# Database setup
|
37 |
+
conn = sqlite3.connect("erp.db")
|
38 |
cursor = conn.cursor()
|
39 |
|
40 |
# Create tables
|
|
|
83 |
("1.2", "Current Assets", "Asset", "1", True, False),
|
84 |
("1.2.1", "Cash", "Asset", "1.2", True, True),
|
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),
|
91 |
("3", "Equity", "Equity", None, True, False),
|
92 |
+
("3.1", "Owner's Equity", "Equity", "3", True, True),
|
93 |
+
("3.2", "Drawings", "Equity", "3", True, True),
|
94 |
("4", "Revenue", "Revenue", None, True, False),
|
95 |
+
("4.1", "Sales Revenue", "Revenue", "4", True, True),
|
96 |
("5", "Expenses", "Expense", None, True, False),
|
97 |
+
("5.1", "Rent Expense", "Expense", "5", True, True),
|
98 |
+
("5.2", "Salary Expense", "Expense", "5", True, True),
|
99 |
+
("5.3", "Office Supplies", "Expense", "5", True, True)
|
100 |
]
|
101 |
cursor.executemany("""
|
102 |
INSERT OR REPLACE INTO chart_of_accounts
|
|
|
106 |
conn.commit()
|
107 |
logging.info("Chart of accounts initialized.")
|
108 |
|
109 |
+
# Enhanced fallback parser
|
110 |
def parse_prompt(prompt):
|
111 |
if model and tokenizer:
|
112 |
try:
|
|
|
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")
|
|
|
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"),
|
147 |
+
"machinery": ("Machinery", "Asset"),
|
148 |
+
"building": ("Building", "Asset"),
|
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
|
163 |
+
debit_type = None
|
164 |
+
credit_account = None
|
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},
|
|
|
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 |
|
|
|
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()
|
|
|
|
|
|
|
|