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