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()
|
|
|
|
|
|
|
|
|
|
|
|