williamagyapong commited on
Commit
4e56772
·
verified ·
1 Parent(s): 2844b15

Update utility.py

Browse files
Files changed (1) hide show
  1. 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, amount, description, among other details you are able to extract.
26
  """
27
- sample_response_template = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- - Amount: 1000
 
 
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
- - Amount: 300 USD
 
 
 
50
  - Category: Furniture
51
  """
52
- response = openai.chat.completions.create(
53
- model="gpt-4o",
 
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}. You can 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 could look like this: {sample_response_template}. Delineate multiple transactions with the label 'Transaction 1' before the start of the relevant information for each transaction. There should be only one intent even in the case of multiple transactions."},
56
  {"role": "user", "content": prompt}
57
  ]
58
  )
@@ -65,7 +91,68 @@ def generateResponse(prompt):
65
 
66
  return response
67
 
68
- # TODO Handle multiple multiple transactions from one user input.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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"] = 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
- print(len(transaction_sections))
120
- print(transaction_sections)
 
 
 
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
- detail_matches = re.findall(r"-\s*([\w\s]+):\s*([\d\w\s,.]+(?:\sUSD)?)", section)
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
- print(transaction)
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['Item'] # assumes unique item name
174
  # fetch the inventory
175
  inventory = fetch_transaction(user_phone, item_name)
176
 
177
  # Do the sales calculations
178
- new_stock_level = inventory['Quantity'] - transaction['Quantity']
179
- inventory['Quantity'] = new_stock_level
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):