Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from transformers import BertTokenizer, BertModel
|
3 |
+
import spacy
|
4 |
+
from typing import Dict, List, Tuple
|
5 |
+
import sqlite3
|
6 |
+
from datetime import datetime
|
7 |
+
import uuid
|
8 |
+
|
9 |
+
class AccountingNLP:
|
10 |
+
def __init__(self):
|
11 |
+
# Initialize BERT tokenizer and model
|
12 |
+
self.tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
|
13 |
+
self.model = BertModel.from_pretrained('bert-base-uncased')
|
14 |
+
# Initialize spaCy for entity extraction
|
15 |
+
self.nlp = spacy.load("en_core_web_sm")
|
16 |
+
# Initialize accounting knowledge base
|
17 |
+
self.accounting_rules = self.load_accounting_rules()
|
18 |
+
self.connection = sqlite3.connect('accounting_db.sqlite')
|
19 |
+
self.create_tables()
|
20 |
+
|
21 |
+
def load_accounting_rules(self) -> Dict:
|
22 |
+
"""Load GAAP/IFRS rules and chart of accounts"""
|
23 |
+
return {
|
24 |
+
'accounts': {
|
25 |
+
'cash': {'type': 'Asset', 'normal_balance': 'Debit'},
|
26 |
+
'office_supplies': {'type': 'Asset', 'normal_balance': 'Debit'},
|
27 |
+
'accounts_payable': {'type': 'Liability', 'normal_balance': 'Credit'}
|
28 |
+
},
|
29 |
+
'rules': {
|
30 |
+
'purchase': {
|
31 |
+
'debit': ['office_supplies'],
|
32 |
+
'credit': ['cash', 'accounts_payable']
|
33 |
+
}
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
def create_tables(self):
|
38 |
+
"""Create database tables for general ledger and journal entries"""
|
39 |
+
cursor = self.connection.cursor()
|
40 |
+
cursor.execute('''
|
41 |
+
CREATE TABLE IF NOT EXISTS journal_entries (
|
42 |
+
entry_id TEXT PRIMARY KEY,
|
43 |
+
date TEXT,
|
44 |
+
account TEXT,
|
45 |
+
debit REAL,
|
46 |
+
credit REAL,
|
47 |
+
description TEXT
|
48 |
+
)
|
49 |
+
''')
|
50 |
+
cursor.execute('''
|
51 |
+
CREATE TABLE IF NOT EXISTS general_ledger (
|
52 |
+
account TEXT,
|
53 |
+
balance REAL,
|
54 |
+
last_updated TEXT
|
55 |
+
)
|
56 |
+
''')
|
57 |
+
self.connection.commit()
|
58 |
+
|
59 |
+
def process_input(self, text: str) -> Dict:
|
60 |
+
"""Process natural language input and extract intent and entities"""
|
61 |
+
doc = self.nlp(text)
|
62 |
+
entities = {
|
63 |
+
'amount': None,
|
64 |
+
'account': None,
|
65 |
+
'date': None,
|
66 |
+
'payment_method': None
|
67 |
+
}
|
68 |
+
|
69 |
+
# Extract entities
|
70 |
+
for ent in doc.ents:
|
71 |
+
if ent.label_ == "MONEY":
|
72 |
+
entities['amount'] = float(ent.text.replace('$', ''))
|
73 |
+
elif ent.label_ == "DATE":
|
74 |
+
entities['date'] = ent.text
|
75 |
+
elif ent.text.lower() in self.accounting_rules['accounts']:
|
76 |
+
entities['account'] = ent.text.lower()
|
77 |
+
|
78 |
+
# Simple intent classification
|
79 |
+
intent = 'record_transaction' if 'record' in text.lower() else 'query'
|
80 |
+
|
81 |
+
return {'intent': intent, 'entities': entities, 'raw_text': text}
|
82 |
+
|
83 |
+
def generate_journal_entry(self, processed_input: Dict) -> List[Dict]:
|
84 |
+
"""Generate double-entry journal entries"""
|
85 |
+
if processed_input['intent'] != 'record_transaction':
|
86 |
+
return []
|
87 |
+
|
88 |
+
entities = processed_input['entities']
|
89 |
+
entry_id = str(uuid.uuid4())
|
90 |
+
date = entities['date'] or datetime.now().strftime('%Y-%m-%d')
|
91 |
+
entries = []
|
92 |
+
|
93 |
+
if entities['account'] in self.accounting_rules['rules']['purchase']['debit']:
|
94 |
+
# Debit entry
|
95 |
+
entries.append({
|
96 |
+
'entry_id': entry_id,
|
97 |
+
'date': date,
|
98 |
+
'account': entities['account'],
|
99 |
+
'debit': entities['amount'],
|
100 |
+
'credit': 0.0,
|
101 |
+
'description': processed_input['raw_text']
|
102 |
+
})
|
103 |
+
# Credit entry (assuming cash payment for simplicity)
|
104 |
+
entries.append({
|
105 |
+
'entry_id': entry_id,
|
106 |
+
'date': date,
|
107 |
+
'account': 'cash',
|
108 |
+
'debit': 0.0,
|
109 |
+
'credit': entities['amount'],
|
110 |
+
'description': processed_input['raw_text']
|
111 |
+
})
|
112 |
+
|
113 |
+
return entries
|
114 |
+
|
115 |
+
def validate_transaction(self, entries: List[Dict]) -> Tuple[bool, str]:
|
116 |
+
"""Validate journal entries for double-entry compliance"""
|
117 |
+
total_debit = sum(entry['debit'] for entry in entries)
|
118 |
+
total_credit = sum(entry['credit'] for entry in entries)
|
119 |
+
|
120 |
+
if total_debit != total_credit:
|
121 |
+
return False, "Debits and credits must balance"
|
122 |
+
if not entries:
|
123 |
+
return False, "No valid entries generated"
|
124 |
+
return True, "Valid transaction"
|
125 |
+
|
126 |
+
def update_ledger(self, entries: List[Dict]):
|
127 |
+
"""Update general ledger with validated entries"""
|
128 |
+
cursor = self.connection.cursor()
|
129 |
+
for entry in entries:
|
130 |
+
cursor.execute('''
|
131 |
+
INSERT INTO journal_entries (entry_id, date, account, debit, credit, description)
|
132 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
133 |
+
''', (
|
134 |
+
entry['entry_id'],
|
135 |
+
entry['date'],
|
136 |
+
entry['account'],
|
137 |
+
entry['debit'],
|
138 |
+
entry['credit'],
|
139 |
+
entry['description']
|
140 |
+
))
|
141 |
+
|
142 |
+
# Update general ledger balance
|
143 |
+
cursor.execute('''
|
144 |
+
INSERT OR REPLACE INTO general_ledger (account, balance, last_updated)
|
145 |
+
VALUES (?,
|
146 |
+
(SELECT COALESCE((SELECT balance FROM general_ledger WHERE account = ?), 0)
|
147 |
+
+ ? - ?),
|
148 |
+
?)
|
149 |
+
''', (
|
150 |
+
entry['account'],
|
151 |
+
entry['account'],
|
152 |
+
entry['debit'],
|
153 |
+
entry['credit'],
|
154 |
+
entry['date']
|
155 |
+
))
|
156 |
+
|
157 |
+
self.connection.commit()
|
158 |
+
|
159 |
+
def generate_response(self, processed_input: Dict, entries: List[Dict]) -> str:
|
160 |
+
"""Generate natural language response"""
|
161 |
+
if processed_input['intent'] == 'record_transaction':
|
162 |
+
is_valid, message = self.validate_transaction(entries)
|
163 |
+
if is_valid:
|
164 |
+
self.update_ledger(entries)
|
165 |
+
return f"Successfully recorded transaction: {processed_input['raw_text']}"
|
166 |
+
return f"Error: {message}"
|
167 |
+
return "Query processing not implemented"
|
168 |
+
|
169 |
+
def process(self, text: str) -> str:
|
170 |
+
"""Main processing pipeline"""
|
171 |
+
processed_input = self.process_input(text)
|
172 |
+
entries = self.generate_journal_entry(processed_input)
|
173 |
+
return self.generate_response(processed_input, entries)
|
174 |
+
|
175 |
+
# Example usage
|
176 |
+
if __name__ == "__main__":
|
177 |
+
accounting_ai = AccountingNLP()
|
178 |
+
result = accounting_ai.process("Record a $500 office supplies purchase paid by check")
|
179 |
+
print(result)
|