AIEmailAutomationUtility 0.0.33__py3-none-any.whl → 0.0.35__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,10 @@ from email.message import Message
4
4
  import datetime
5
5
  import time
6
6
  import loggerutility as logger
7
- from flask import Flask,request
8
7
  import json
9
8
  from email.mime.multipart import MIMEMultipart
10
9
  from email.mime.text import MIMEText
10
+ from bs4 import BeautifulSoup
11
11
 
12
12
  class Email_Draft:
13
13
  def draft_email(self, email_config, email_details, response_content):
@@ -60,6 +60,7 @@ class Email_Draft:
60
60
  # Extract response content
61
61
  body = response_content.get('body', '')
62
62
  table_html = response_content.get('table_', '')
63
+ aligned_table_html = self.align_numeric_cells_right(table_html)
63
64
  signature = response_content.get('signature', '')
64
65
  email_body = email_details['body']
65
66
 
@@ -72,7 +73,7 @@ class Email_Draft:
72
73
  html_content = (
73
74
  "<html><body>"
74
75
  f"<p>{body_html}</p>"
75
- f"{table_html}"
76
+ f"{aligned_table_html}"
76
77
  f"<b>GST Extra as per applicable.</b>"
77
78
  f"<p>{signature_html}</p>"
78
79
  "<hr>"
@@ -174,3 +175,26 @@ class Email_Draft:
174
175
 
175
176
  except Exception as e:
176
177
  logger.log(f"Error in Draft_Save: {str(e)}")
178
+
179
+ def align_numeric_cells_right(self,table_html):
180
+ soup = BeautifulSoup(table_html, 'html.parser')
181
+ rows = soup.find_all('tr')
182
+
183
+ for row in rows[1:]: # Skip header row
184
+ cells = row.find_all('td')
185
+ for cell in cells:
186
+ text = cell.get_text(strip=True)
187
+ # Check if the content is numeric (int or float)
188
+ try:
189
+ float(text.replace(',', '')) # Handle values like '7,500.00'
190
+ # If 'text-align: right;' is not present, add it
191
+ style = cell.get('style', '')
192
+ if 'text-align: right' not in style:
193
+ if style:
194
+ style += '; '
195
+ style += 'text-align: right;'
196
+ cell['style'] = style
197
+ except ValueError:
198
+ continue # Not numeric
199
+
200
+ return str(soup)
@@ -277,7 +277,7 @@ class Email_Read:
277
277
  attachments = []
278
278
  status, data = mail.fetch(email_id, '(RFC822 UID)')
279
279
 
280
- if status == 'OK':
280
+ if status == 'OK' and data[0]!= None:
281
281
  raw_email = data[0][1]
282
282
  msg = email.message_from_bytes(raw_email)
283
283
 
@@ -16,6 +16,8 @@ import csv
16
16
  import os
17
17
  from email import message_from_string
18
18
  import re
19
+ import weaviate
20
+ from weaviate.gql.get import HybridFusion
19
21
 
20
22
  class Process_Category:
21
23
 
@@ -168,8 +170,28 @@ class Process_Category:
168
170
  elif category == "Quotation":
169
171
  action_taken = f"Mail drafted for products rate"
170
172
  responseMethod, parameters = self.get_JsonArray_values(category, file_JsonArray)
171
- logger.log(f"Inside Quotation")
173
+ logger.log(f"Parameters are ::: {parameters}")
172
174
 
175
+
176
+ enterpriseName = parameters["Enterprise_Name"]
177
+ schema_name = parameters["Schema_Name"].capitalize().replace("-","_")
178
+ entity_type = parameters["Entity_Type"]
179
+ server_url = ""
180
+
181
+ schemaName_Updated = enterpriseName + "_" + schema_name + "_" + entity_type
182
+ logger.log(f'\nschemaName_Updated ::: \t{schemaName_Updated}')
183
+
184
+ environment_weaviate_server_url = os.getenv('weaviate_server_url')
185
+ logger.log(f"environment_weaviate_server_url ::: [{environment_weaviate_server_url}]")
186
+
187
+ if environment_weaviate_server_url != None and environment_weaviate_server_url != '':
188
+ server_url = environment_weaviate_server_url
189
+ logger.log(f"\nProcess_cat class Quotation server_url:::\t{server_url} \t{type(server_url)}","0")
190
+ else:
191
+ if 'server_url' in parameters.keys():
192
+ server_url = parameters['server_url']
193
+ logger.log(f"\nProcess_cat class Quotation server_url:::\t{server_url} \t{type(server_url)}","0")
194
+
173
195
  # Step 4: Identify customer from email using AI
174
196
  customer_data = self.identify_customer(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Customer_Assistant_Id"])
175
197
  logger.log(f"Identified customer is ::: {customer_data}")
@@ -190,6 +212,8 @@ class Process_Category:
190
212
  products = self.identify_products(email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Product_Assistant_Id"])
191
213
 
192
214
  logger.log(f'Identified Products are ::: {products}')
215
+ products=self.products_item_code_lookup(products, openai_api_key, schemaName_Updated, server_url)
216
+ logger.log(f'Identified Products after Lookup are ::: {products}')
193
217
 
194
218
  product_determination=products
195
219
 
@@ -198,6 +222,8 @@ class Process_Category:
198
222
  item_no = product.get("item_no", "").strip()
199
223
  make = product.get("make", "").strip()
200
224
  rate = None
225
+ discount = "NA"
226
+ price_pickup_source = "NA"
201
227
  found_rate = False
202
228
 
203
229
  logger.log(f"item no is ::: {item_no}")
@@ -233,8 +259,8 @@ class Process_Category:
233
259
  else:
234
260
  logger.log(f"Process_Category - Quotation [0] No base price found for item '{item_no}' .")
235
261
  product["rate"] = "NA"
236
- product["Price Pickup Source"]="NA"
237
- product["Discount"]="NA"
262
+ product["price_pickup_source"]="NA"
263
+ product["discount"]="NA"
238
264
  continue
239
265
 
240
266
  # Condition 1: Exact match in special_rate_customer_wise
@@ -270,7 +296,7 @@ class Process_Category:
270
296
 
271
297
  if discount_result:
272
298
  discount_percent = discount_result[0]
273
- Discount= discount_percent
299
+ discount= discount_percent
274
300
  rate = raw_price * (1 - int(discount_percent) / 100)
275
301
  rate = round(rate, 2)
276
302
  found_rate = True
@@ -295,7 +321,7 @@ class Process_Category:
295
321
 
296
322
  if past_sales_result:
297
323
  most_common_make, past_discount = past_sales_result
298
- Discount=past_discount
324
+ discount=past_discount
299
325
  if isinstance(raw_price, (int, float)):
300
326
  rate = raw_price * (1 - int(past_discount) / 100)
301
327
  rate = round(rate, 2)
@@ -319,7 +345,7 @@ class Process_Category:
319
345
 
320
346
  if general_discount_result:
321
347
  general_discount_percent = general_discount_result[0]
322
- Discount=general_discount_percent
348
+ discount=general_discount_percent
323
349
  rate = raw_price * (1 - int(general_discount_percent) / 100)
324
350
  rate = round(rate, 2)
325
351
  found_rate = True
@@ -331,11 +357,11 @@ class Process_Category:
331
357
  rate = raw_price
332
358
  logger.log(f"Process_Category - Quotation [5] No discounts applied. Using base price for item '{item_no}': {rate}")
333
359
  price_pickup_source="PRICE_LIST"
334
- Discount = "NA"
360
+ discount = "NA"
335
361
 
336
362
  product["rate"] = rate
337
- product["Price Pickup Source"]=price_pickup_source
338
- product["Discount"]=Discount
363
+ product["price_pickup_source"]=price_pickup_source
364
+ product["discount"]=discount
339
365
 
340
366
  db_connection.close()
341
367
 
@@ -542,7 +568,7 @@ class Process_Category:
542
568
  logger.log("Inside identify_products")
543
569
 
544
570
  if model_type == "OpenAI":
545
- prompt = f"""/* Extract complete item pricing information from the following mixed-format email content. The email may contain a combination of:- descriptive product listings- tabular structured price info- semi-structured lines with HSN, quantity, material, etc. Give me price in INR and information of all items in following format requested_description, item_no, make, description, price, Quantity, price unit, inventory unit strictly in JSON format. If the item is not available in the assistant files return requested description from email data and rest of the columns as NA. Do not include any instruction as the output will be directly in a program. */ /n {email_body}. """
571
+ prompt = f"""/* Extract complete item pricing information from the following mixed-format email content. The email may contain a combination of:- descriptive product listings- tabular structured price info- semi-structured lines with HSN, quantity, material, etc. Give me price in INR and information of all items in following format requested_description, item_no, make, description, price, quantity, price_unit, inventory_unit strictly in JSON array format(Always return the result as a JSON **array**, even if there's only one item). If the item is not available in the assistant files return requested description from email data and rest of the columns as NA. Do not include any instruction as the output will be directly in a program. */ /n {email_body}. """
546
572
  emailreplyassistant = EmailReplyAssistant()
547
573
  ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
548
574
 
@@ -566,6 +592,9 @@ class Process_Category:
566
592
  if ai_result["status"] == "Success":
567
593
  logger.log(f"ai_result ::: {ai_result}")
568
594
  product_data = json.loads(ai_result["message"])
595
+ # If it's a dict, wrap in list
596
+ if isinstance(product_data, dict):
597
+ product_data = json.dumps([product_data], indent=4)
569
598
  else:
570
599
  product_data = []
571
600
  return product_data
@@ -577,7 +606,7 @@ class Process_Category:
577
606
  product_table = "Products:\n"
578
607
  for product in products:
579
608
  rate = product.get("rate", "NA")
580
- quantity = product.get("Quantity", "NA")
609
+ quantity = product.get("quantity", "NA")
581
610
  try:
582
611
  total = float(rate) * float(quantity)
583
612
  except:
@@ -586,14 +615,14 @@ class Process_Category:
586
615
  f'- Requested Description: {product.get("requested_description", "-")}, '
587
616
  f'Item Code: {product.get("item_no", "-")}, '
588
617
  f'Item Description: {product.get("description", "-")}, '
589
- f'Manufacturer: {product.get("make", "-")}, '
618
+ f'Make: {product.get("make", "-")}, '
619
+ f'Inventory Unit: {product.get("inventory_unit", "-")}, '
590
620
  f'Price: {product.get("price", "-")}, '
591
- f'Inventory Unit: {product.get("inventory unit", "-")}, '
592
- f'Discount: {product.get("Discount", "-")}, '
621
+ f'Discount: {product.get("discount", "-")}, '
593
622
  f'Rate: {rate}, '
594
623
  f'Quantity: {quantity}, '
595
624
  f'Total: {total}, '
596
- f'Price Pickup Source: {product.get("Price Pickup Source", "-")}, '
625
+ f'Price Pickup Source: {product.get("price_pickup_source", "-")}, '
597
626
  f'Availability: ""\n'
598
627
  )
599
628
 
@@ -606,12 +635,11 @@ class Process_Category:
606
635
 
607
636
  {product_table}
608
637
  Ensure the table has the following columns in this exact order:
609
- Sr. No., Requested Description, Item Code, Item Description, Manufacturer, Price, Inventory Unit, Discount, Rate, Quantity, Total, Price Pickup Source, Availability
638
+ Sr. No., Requested Description, Item Code, Item Description, Make, Inventory Unit, Price, Discount, Rate, Quantity, Total, Price Pickup Source, Availability
610
639
 
611
640
  - If any value is missing, use a dash ("-") instead.
612
- - "Total" is calculated as Rate × Quantity.
613
641
  - "Availability" should be a blank column.
614
- Original Email Subject: {subject}
642
+ Original Email Subject: {subject}
615
643
 
616
644
  Return only the following JSON String format:
617
645
  {{
@@ -976,3 +1004,71 @@ class Process_Category:
976
1004
  return payload.decode(part.get_content_charset() or 'utf-8')
977
1005
  return "No HTML content found."
978
1006
 
1007
+ def products_item_code_lookup(self, products, openai_api_key, schemaName_Updated, server_url):
1008
+ try:
1009
+ logger.log(f'\nproduct_Json : {products}')
1010
+ logger.log(f'\nopenai_api_key : {openai_api_key}')
1011
+ logger.log(f'\nschemaName_Updated : {schemaName_Updated}')
1012
+ logger.log(f'\nserver_url : {server_url}')
1013
+ alphaValue = 0.54
1014
+
1015
+ client = weaviate.Client(server_url,additional_headers={"X-OpenAI-Api-Key": openai_api_key})
1016
+ logger.log(f'Connection is establish : {client.is_ready()}')
1017
+
1018
+ schemaClasslist = [i['class'] for i in client.schema.get()["classes"]]
1019
+ logger.log(f'schemaClasslist : {schemaClasslist}')
1020
+
1021
+ for product in products:
1022
+ item_no = product.get("item_no", "NA")
1023
+ item_name = product.get("requested_description", "NA")
1024
+ if item_no == "NA":
1025
+ inputQuery = item_name.upper().replace("N/A","").replace("."," ").replace(","," ").replace("-"," ").replace("_"," ")
1026
+ logger.log(f'inputQuery : {inputQuery}')
1027
+
1028
+ if schemaName_Updated in schemaClasslist:
1029
+ logger.log(f'Inside schemaClasslist')
1030
+ response = (
1031
+ client.query
1032
+ .get(schemaName_Updated, ["description", "answer","phy_attrib_2","phy_attrib_3","phy_attrib_4"])
1033
+ .with_hybrid(
1034
+ alpha = alphaValue,
1035
+ query = inputQuery.strip() ,
1036
+ fusion_type = HybridFusion.RELATIVE_SCORE
1037
+ )
1038
+ .with_additional('score')
1039
+ .with_limit(10)
1040
+ .do()
1041
+ )
1042
+ logger.log(f"Input ::: {item_name}")
1043
+ if response != {}:
1044
+ response_List = response['data']['Get'][schemaName_Updated]
1045
+ product['description'] = response_List[0]['description']
1046
+ product['item_no'] = response_List[0]['answer']
1047
+ # product['cas_no'] = response_List[0]['phy_attrib_1']
1048
+ product['make'] = response_List[0]['phy_attrib_2']
1049
+ product["price"] = response_List[0]['phy_attrib_3']
1050
+ product["inventory_unit"] = response_List[0]['phy_attrib_4']
1051
+
1052
+ for index in range(len(response_List)):
1053
+ description = response_List[index]['description']
1054
+ description = description.upper().replace("N/A","").replace("."," ").replace(","," ").replace("-"," ").replace("_"," ")
1055
+
1056
+ descr_replaced = description.replace(" ", "")
1057
+ inputQuery_replaced = inputQuery.replace(" ", "")
1058
+
1059
+ if descr_replaced == inputQuery_replaced:
1060
+ logger.log(f"\n Input::: '{inputQuery_replaced}' MATCHEDD with description ::: '{descr_replaced}' \n")
1061
+ product['description'] = response_List[index]['description']
1062
+ product['item_no'] = response_List[index]['answer']
1063
+ # product['cas_no'] = response_List[index]['phy_attrib_1']
1064
+ product['make'] = response_List[index]['phy_attrib_2']
1065
+ product["price"] = response_List[index]['phy_attrib_3']
1066
+ product["inventory_unit"] = response_List[index]['phy_attrib_4']
1067
+ break
1068
+ else:
1069
+ logger.log(f"\n Input '{inputQuery_replaced}' not matched with returned response description '{descr_replaced}'\n ")
1070
+
1071
+ return products
1072
+
1073
+ except Exception as error:
1074
+ raise str(error)
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: AIEmailAutomationUtility
3
- Version: 0.0.33
4
- Summary: Added new columns and Modified the draft prompt to return draft in consistent format for all mails
3
+ Version: 0.0.35
4
+ Summary: Aligned numeric cells in the Email Draft table and ensured that the Identify Products function returns a JSON array only.
5
5
  Author: Proteus Technology PVT. LTD.
6
6
  Author-email: <apps@baseinformation.com>
7
7
  Keywords: python,first package
@@ -13,4 +13,4 @@ Classifier: Operating System :: MacOS :: MacOS X
13
13
  Classifier: Operating System :: Microsoft :: Windows
14
14
  License-File: LICENCE.txt
15
15
 
16
- Added new columns and Modified the draft prompt to return draft in consistent format for all mails
16
+ Aligned numeric cells in the Email Draft table and ensured that the Identify Products function returns a JSON array only.
@@ -1,15 +1,15 @@
1
1
  AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=R_wJna3-ITsVxQEccryhM93T_Nf_Oxo8DXnS-sDN8VE,6679
2
2
  AIEmailAutomationUtility/Email_Classification.py,sha256=Ar0g4Ff8HOT7xICktd3nP_C_vCyeY-xCpUjVCVRWAyc,9417
3
3
  AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=BWNRt2X-E2HCogBaKDfl7cZZNSkZUeIsVs8iXjFjH88,3218
4
- AIEmailAutomationUtility/Email_Draft.py,sha256=JYZijUh_zan2asyMYQwIBwIpGNJ5SSQGma5AL1meaXk,7808
5
- AIEmailAutomationUtility/Email_Read.py,sha256=Ehzx9SKjzCgWY9K4rWCnwokDzo6M0Nbhi2XLKYpeEis,31451
4
+ AIEmailAutomationUtility/Email_Draft.py,sha256=TGLqV0yzXLIdBnRcEY3CgmcDJVgiP3LN1vLPXTBj5rY,8831
5
+ AIEmailAutomationUtility/Email_Read.py,sha256=_JwHTJZmxjuMPTbn90D9YStW7NSJd3t4R_cwUZ1tn20,31470
6
6
  AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
7
- AIEmailAutomationUtility/Process_Category.py,sha256=9X3b2BEw31T6RmJ66g0zMP8WYVlVqi8CDGMIsR6ATbU,47741
7
+ AIEmailAutomationUtility/Process_Category.py,sha256=RfOdZq4GnHlWVprSrKf_PQrx48QuFhav3CUwnEzOlOg,53585
8
8
  AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
9
9
  AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
10
10
  AIEmailAutomationUtility/__init__.py,sha256=Jad3IdPRsVMeLqEEh-FbCrc1lE2tzJO2DTG5Hgmxh5g,391
11
- AIEmailAutomationUtility-0.0.33.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
12
- AIEmailAutomationUtility-0.0.33.dist-info/METADATA,sha256=9kdhPKSz1FqZiWMkeQq6GFK3TefmevbwWikMIem7BHw,699
13
- AIEmailAutomationUtility-0.0.33.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
14
- AIEmailAutomationUtility-0.0.33.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
15
- AIEmailAutomationUtility-0.0.33.dist-info/RECORD,,
11
+ AIEmailAutomationUtility-0.0.35.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
12
+ AIEmailAutomationUtility-0.0.35.dist-info/METADATA,sha256=H3FAgTui1ayJB9Q-opXbEKkgkqv1ORrrD6PXVVDnQEI,743
13
+ AIEmailAutomationUtility-0.0.35.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
14
+ AIEmailAutomationUtility-0.0.35.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
15
+ AIEmailAutomationUtility-0.0.35.dist-info/RECORD,,