Spaces:
Sleeping
Sleeping
williamagyapong
commited on
Update utility.py
Browse files- utility.py +112 -51
utility.py
CHANGED
@@ -14,17 +14,36 @@ openai.api_key = o_api_key
|
|
14 |
# Authenticate to Firesotre with the JSON account key
|
15 |
db = firestore.Client.from_service_account_json("firestore-key.json")
|
16 |
|
|
|
17 |
# Generate AI response from user input
|
18 |
-
def generateResponse(prompt):
|
19 |
#----- Call API to classify and extract relevant transaction information
|
20 |
# These templates help provide a unified response format for use as context clues when
|
21 |
# parsing the AI generated response into a structured data format
|
22 |
relevant_info_template = """
|
23 |
-
Intent: The CRUD operation
|
24 |
-
Transaction Type: The type of transaction
|
25 |
-
Details: as a sublist of the key details like name of item,
|
26 |
"""
|
27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
The information provided indicates that you want to **create/record** a new transaction.
|
29 |
|
30 |
**Extracted Information**:
|
@@ -37,8 +56,11 @@ def generateResponse(prompt):
|
|
37 |
**Details**:
|
38 |
- Item: Car
|
39 |
- Purpose: Business
|
40 |
-
-
|
|
|
|
|
41 |
- Tax: 200
|
|
|
42 |
- Note: A new car for business
|
43 |
|
44 |
Transaction 2:
|
@@ -46,13 +68,17 @@ def generateResponse(prompt):
|
|
46 |
**Transaction Type**: Expense
|
47 |
**Details**:
|
48 |
- Item: Office Chair
|
49 |
-
-
|
|
|
|
|
|
|
50 |
- Category: Furniture
|
51 |
"""
|
52 |
-
response =
|
53 |
-
model=
|
|
|
54 |
messages=[
|
55 |
-
{"role": "system", "content": f"You are a helpful assistant that classifies transactions written in natural language into CRUD operations (Create, Read, Update, and Delete) and extracts relevant information. Format the relevant information extracted from the transaction text in this format: {relevant_info_template}.
|
56 |
{"role": "user", "content": prompt}
|
57 |
]
|
58 |
)
|
@@ -65,7 +91,68 @@ def generateResponse(prompt):
|
|
65 |
|
66 |
return response
|
67 |
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
def parse_ai_response(response_text):
|
70 |
# Initialize the structured data dictionary
|
71 |
data = {
|
@@ -85,24 +172,8 @@ def parse_ai_response(response_text):
|
|
85 |
if transaction_type_match:
|
86 |
data["transaction_type"] = transaction_type_match.group(1)
|
87 |
|
88 |
-
# Extract details
|
89 |
-
details = {}
|
90 |
-
detail_matches = re.findall(r"-\s*([\w\s]+):\s*([\d\w\s,.]+(?:\sUSD)?)", response_text)
|
91 |
-
for field, value in detail_matches:
|
92 |
-
# Clean up the field name and value
|
93 |
-
field = field.strip().lower().replace(" ", "_") # Convert to snake_case
|
94 |
-
value = value.strip()
|
95 |
-
|
96 |
-
# Convert numeric values where possible
|
97 |
-
if "USD" in value:
|
98 |
-
value = float(value.replace(" USD", "").replace(",", ""))
|
99 |
-
elif value.isdigit():
|
100 |
-
value = int(value)
|
101 |
-
|
102 |
-
details[field] = value
|
103 |
-
|
104 |
# Store details in the structured data
|
105 |
-
data["details"] =
|
106 |
|
107 |
return data
|
108 |
|
@@ -116,13 +187,16 @@ def parse_multiple_transactions(response_text):
|
|
116 |
# Remove the first section if it's not a valid transaction
|
117 |
if not re.search(r"\*\*Transaction Type\*\*", transaction_sections[0], re.IGNORECASE):
|
118 |
transaction_sections.pop(0)
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
121 |
# Extract intent: with support for a single intent per user prompt
|
122 |
intent_match = re.search(r"\*\*Intent\*\*:\s*(\w+)", response_text)
|
123 |
if intent_match:
|
124 |
intent = intent_match.group(1)
|
125 |
-
|
126 |
for section in transaction_sections:
|
127 |
# Initialize transaction data
|
128 |
transaction_data = {
|
@@ -131,7 +205,6 @@ def parse_multiple_transactions(response_text):
|
|
131 |
"details": {},
|
132 |
"created_at": datetime.now().isoformat()
|
133 |
}
|
134 |
-
# transaction_data["intent"] = intent
|
135 |
|
136 |
# Extract transaction type
|
137 |
transaction_type_match = re.search(r"\*\*Transaction Type\*\*:\s*(\w+)", section)
|
@@ -139,28 +212,15 @@ def parse_multiple_transactions(response_text):
|
|
139 |
transaction_data["transaction_type"] = transaction_type_match.group(1)
|
140 |
|
141 |
# Extract details
|
142 |
-
details =
|
143 |
-
|
144 |
-
for field, value in detail_matches:
|
145 |
-
field = field.strip().lower().replace(" ", "_") # Convert to snake_case
|
146 |
-
value = value.strip()
|
147 |
-
# Convert numeric values where possible
|
148 |
-
if "USD" in value:
|
149 |
-
value = float(value.replace(" USD", "").replace(",", ""))
|
150 |
-
elif value.isdigit():
|
151 |
-
value = int(value)
|
152 |
-
details[field] = value
|
153 |
-
|
154 |
-
transaction_data["details"] = details
|
155 |
transactions.append(transaction_data)
|
156 |
|
157 |
return transactions
|
158 |
|
159 |
def create_inventory(user_phone, transaction_data):
|
160 |
-
print(transaction_data)
|
161 |
for transaction in transaction_data:
|
162 |
-
|
163 |
-
item_name = transaction['Item'] # assumes unique item name
|
164 |
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
165 |
# transaction_data['transaction_id'] # let's default to random generated document ids
|
166 |
doc_ref.set(transaction)
|
@@ -170,13 +230,13 @@ def create_inventory(user_phone, transaction_data):
|
|
170 |
def create_sale(user_phone, transaction_data):
|
171 |
|
172 |
for transaction in transaction_data:
|
173 |
-
item_name = transaction['
|
174 |
# fetch the inventory
|
175 |
inventory = fetch_transaction(user_phone, item_name)
|
176 |
|
177 |
# Do the sales calculations
|
178 |
-
new_stock_level = inventory['
|
179 |
-
inventory['
|
180 |
|
181 |
# update the inventory
|
182 |
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
@@ -189,6 +249,7 @@ def create_sale(user_phone, transaction_data):
|
|
189 |
# print("Transaction created successfully!")
|
190 |
return True
|
191 |
|
|
|
192 |
# Update logic:
|
193 |
# -
|
194 |
def update_transaction(user_phone, transaction_id, update_data):
|
|
|
14 |
# Authenticate to Firesotre with the JSON account key
|
15 |
db = firestore.Client.from_service_account_json("firestore-key.json")
|
16 |
|
17 |
+
|
18 |
# Generate AI response from user input
|
19 |
+
def generateResponse(prompt,model='Meta-Llama-3.1-70B-Instruct'):
|
20 |
#----- Call API to classify and extract relevant transaction information
|
21 |
# These templates help provide a unified response format for use as context clues when
|
22 |
# parsing the AI generated response into a structured data format
|
23 |
relevant_info_template = """
|
24 |
+
Intent: The CRUD operation, one of create, read, update, or delete
|
25 |
+
Transaction Type: The type of transaction such as purchases, sales
|
26 |
+
Details: as a sublist of the key details like name of item, quantity, cost price, currency, unit for the quantity, description, among other details you are able to extract.
|
27 |
"""
|
28 |
+
sample_single_transaction_template = """
|
29 |
+
The information provided indicates that you want to **create/record** a new transaction.
|
30 |
+
**Extracted Information**:
|
31 |
+
|
32 |
+
**Intent**: Create
|
33 |
+
|
34 |
+
**Transaction Type**: Purchase
|
35 |
+
**Details**:
|
36 |
+
- Item: Car
|
37 |
+
- Purpose: Business
|
38 |
+
- Quantity: 1
|
39 |
+
- Unit: None
|
40 |
+
- Cost: 10000
|
41 |
+
- Tax: 200
|
42 |
+
- Currency: USD
|
43 |
+
- Note: A new car for business
|
44 |
+
"""
|
45 |
+
|
46 |
+
sample_multi_transaction_template = """
|
47 |
The information provided indicates that you want to **create/record** a new transaction.
|
48 |
|
49 |
**Extracted Information**:
|
|
|
56 |
**Details**:
|
57 |
- Item: Car
|
58 |
- Purpose: Business
|
59 |
+
- Quantity: 1
|
60 |
+
- Unit: None
|
61 |
+
- Cost: 10000
|
62 |
- Tax: 200
|
63 |
+
- Currency: USD
|
64 |
- Note: A new car for business
|
65 |
|
66 |
Transaction 2:
|
|
|
68 |
**Transaction Type**: Expense
|
69 |
**Details**:
|
70 |
- Item: Office Chair
|
71 |
+
- Quantity: 2
|
72 |
+
- Unit: None
|
73 |
+
- Cost: 300
|
74 |
+
- Currency: USD
|
75 |
- Category: Furniture
|
76 |
"""
|
77 |
+
response = client.chat.completions.create(
|
78 |
+
model = model,
|
79 |
+
# model="gpt-4o",
|
80 |
messages=[
|
81 |
+
{"role": "system", "content": f"You are a helpful assistant that classifies transactions written in natural language into CRUD operations (Create, Read, Update, and Delete) and extracts relevant information. You should be able to recognize the currency being used and any quantity units into separate fields. Format the relevant information extracted from the transaction text in this format: {relevant_info_template}. Use markdown syntax to present a nicely formated and readable response to the user, but make sure the user does not see the markdown keyword. Keywords and field names must be in bold face. A sample response for a single transaction could look like this: {sample_single_transaction_template}, while multiple transactions could look like this: {sample_multi_transaction_template}. There should be only one intent even in the case of multiple transactions."},
|
82 |
{"role": "user", "content": prompt}
|
83 |
]
|
84 |
)
|
|
|
91 |
|
92 |
return response
|
93 |
|
94 |
+
|
95 |
+
def parse_value(value):
|
96 |
+
"""
|
97 |
+
Parses a value string into the appropriate data type and detects currency.
|
98 |
+
Handles various currencies, percentages, numbers, and text.
|
99 |
+
"""
|
100 |
+
value = value.strip()
|
101 |
+
try:
|
102 |
+
# Match currency codes or symbols dynamically
|
103 |
+
currency_match = re.search(r"([A-Z]{3}|\$|€|£)", value)
|
104 |
+
currency = currency_match.group(1) if currency_match else None
|
105 |
+
|
106 |
+
# Remove currency symbols or codes for numeric conversion
|
107 |
+
cleaned_value = re.sub(r"[A-Z]{3}|\$|€|£", "", value).replace(",", "").strip()
|
108 |
+
|
109 |
+
# Handle percentages
|
110 |
+
if "%" in cleaned_value:
|
111 |
+
return float(cleaned_value.replace("%", "")), currency
|
112 |
+
# Handle plain numbers (integers or floats)
|
113 |
+
elif cleaned_value.replace(".", "", 1).isdigit():
|
114 |
+
return float(cleaned_value) if "." in cleaned_value else int(cleaned_value), currency
|
115 |
+
# Return as text if no numeric parsing is possible
|
116 |
+
return value, currency
|
117 |
+
except ValueError:
|
118 |
+
# Fallback to original value if parsing fails
|
119 |
+
return value, None
|
120 |
+
|
121 |
+
def extract_transaction_details(text):
|
122 |
+
"""
|
123 |
+
Extracts transaction details from a given text input.
|
124 |
+
Handles both bold (**field**) and non-bold (field) formats.
|
125 |
+
"""
|
126 |
+
details = {}
|
127 |
+
transaction_currency = None # Default currency field
|
128 |
+
|
129 |
+
# Regex to match key-value pairs
|
130 |
+
detail_matches = re.findall(
|
131 |
+
r"-\s*\*{0,2}([\w\s]+)\*{0,2}:\s*([\w\s,.$%-]+?)(?:\s*[\n]|$)", # Stop matching before newline or end of string
|
132 |
+
text,
|
133 |
+
re.DOTALL
|
134 |
+
)
|
135 |
+
# print("Detail matches:", detail_matches) # Debugging
|
136 |
+
|
137 |
+
for field, value in detail_matches:
|
138 |
+
# Standardize the field name (convert to snake_case)
|
139 |
+
field = field.strip().lower().replace(" ", "_")
|
140 |
+
|
141 |
+
# Parse the value and dynamically detect currency
|
142 |
+
parsed_value, detected_currency = parse_value(value)
|
143 |
+
if detected_currency and not transaction_currency:
|
144 |
+
transaction_currency = detected_currency # Set the transaction-level currency if not already set
|
145 |
+
|
146 |
+
details[field] = parsed_value
|
147 |
+
|
148 |
+
# Add currency as a separate field
|
149 |
+
if transaction_currency:
|
150 |
+
details["currency"] = transaction_currency
|
151 |
+
|
152 |
+
return details
|
153 |
+
|
154 |
+
|
155 |
+
# Parsing single transactions
|
156 |
def parse_ai_response(response_text):
|
157 |
# Initialize the structured data dictionary
|
158 |
data = {
|
|
|
172 |
if transaction_type_match:
|
173 |
data["transaction_type"] = transaction_type_match.group(1)
|
174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
# Store details in the structured data
|
176 |
+
data["details"] = extract_transaction_details(response_text)
|
177 |
|
178 |
return data
|
179 |
|
|
|
187 |
# Remove the first section if it's not a valid transaction
|
188 |
if not re.search(r"\*\*Transaction Type\*\*", transaction_sections[0], re.IGNORECASE):
|
189 |
transaction_sections.pop(0)
|
190 |
+
# if len(transaction_sections) == 1: # process single transaction
|
191 |
+
# transaction_data = parse_ai_response(response_text)
|
192 |
+
# transactions.append(transaction_data)
|
193 |
+
# return transactions
|
194 |
+
|
195 |
# Extract intent: with support for a single intent per user prompt
|
196 |
intent_match = re.search(r"\*\*Intent\*\*:\s*(\w+)", response_text)
|
197 |
if intent_match:
|
198 |
intent = intent_match.group(1)
|
199 |
+
|
200 |
for section in transaction_sections:
|
201 |
# Initialize transaction data
|
202 |
transaction_data = {
|
|
|
205 |
"details": {},
|
206 |
"created_at": datetime.now().isoformat()
|
207 |
}
|
|
|
208 |
|
209 |
# Extract transaction type
|
210 |
transaction_type_match = re.search(r"\*\*Transaction Type\*\*:\s*(\w+)", section)
|
|
|
212 |
transaction_data["transaction_type"] = transaction_type_match.group(1)
|
213 |
|
214 |
# Extract details
|
215 |
+
transaction_data["details"] = extract_transaction_details(section)
|
216 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
transactions.append(transaction_data)
|
218 |
|
219 |
return transactions
|
220 |
|
221 |
def create_inventory(user_phone, transaction_data):
|
|
|
222 |
for transaction in transaction_data:
|
223 |
+
item_name = transaction['details']['item'] # assumes unique item name
|
|
|
224 |
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
225 |
# transaction_data['transaction_id'] # let's default to random generated document ids
|
226 |
doc_ref.set(transaction)
|
|
|
230 |
def create_sale(user_phone, transaction_data):
|
231 |
|
232 |
for transaction in transaction_data:
|
233 |
+
item_name = transaction['details']['item'] # assumes item names are unique per transactions
|
234 |
# fetch the inventory
|
235 |
inventory = fetch_transaction(user_phone, item_name)
|
236 |
|
237 |
# Do the sales calculations
|
238 |
+
new_stock_level = inventory['details']['quantity'] - transaction['details']['quantity']
|
239 |
+
inventory['details']['quantity'] = new_stock_level
|
240 |
|
241 |
# update the inventory
|
242 |
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
|
|
249 |
# print("Transaction created successfully!")
|
250 |
return True
|
251 |
|
252 |
+
|
253 |
# Update logic:
|
254 |
# -
|
255 |
def update_transaction(user_phone, transaction_id, update_data):
|