AIEmailAutomationUtility 0.0.31__py3-none-any.whl → 0.0.33__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.
@@ -6,7 +6,6 @@ import loggerutility as logger
6
6
  from flask import Flask,request
7
7
  from datetime import datetime
8
8
 
9
-
10
9
  class Email_DocumentUploader:
11
10
  def upload_document(self, upload_config, file_data):
12
11
  document_type = upload_config['document_type']
@@ -73,6 +73,7 @@ class Email_Draft:
73
73
  "<html><body>"
74
74
  f"<p>{body_html}</p>"
75
75
  f"{table_html}"
76
+ f"<b>GST Extra as per applicable.</b>"
76
77
  f"<p>{signature_html}</p>"
77
78
  "<hr>"
78
79
  f"<p>{mail_details}</p>"
@@ -7,7 +7,7 @@ from datetime import datetime
7
7
  import threading
8
8
  import traceback
9
9
  import json
10
- import os
10
+ import os, shutil
11
11
  import csv
12
12
 
13
13
  import io
@@ -112,7 +112,7 @@ class Email_Read:
112
112
  login_status = "Success"
113
113
  mail.select('inbox')
114
114
 
115
- file_JsonArray, categories = self.read_JSON_File(templateName)
115
+ file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
116
116
 
117
117
  except Exception as e:
118
118
  login_status = "Failed"
@@ -251,7 +251,7 @@ class Email_Read:
251
251
  login_status = "Success"
252
252
  mail.select('inbox')
253
253
 
254
- file_JsonArray, categories = self.read_JSON_File(templateName)
254
+ file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
255
255
 
256
256
  except Exception as e:
257
257
  logger.log(f"Login failed: {e}")
@@ -484,7 +484,7 @@ class Email_Read:
484
484
  templateName = "ai_email_automation.json"
485
485
  fileName = ""
486
486
 
487
- file_JsonArray, categories = self.read_JSON_File(templateName)
487
+ file_JsonArray, categories = self.read_JSON_File(templateName, user_id)
488
488
  # Call the `extract_all_email_info` method to extract details from the eml content
489
489
  extracted_info,msg = self.extract_all_email_info(eml_content)
490
490
 
@@ -552,24 +552,37 @@ class Email_Read:
552
552
 
553
553
  return "success"
554
554
 
555
- def read_JSON_File(self, json_fileName):
555
+ def read_JSON_File(self, json_fileName, user_id):
556
556
  category_list = []
557
557
  categories = ""
558
558
  try:
559
- if os.path.exists(json_fileName):
560
- with open(json_fileName, "r") as fileObj:
561
- file_JsonArray = json.load(fileObj)
559
+ logger.log(f"\nEmail_Read() read_JSON_File user_id ::: {user_id}")
560
+ user_file = json_fileName
561
+ if user_id:
562
+ user_dir = os.path.join('user_data', user_id)
563
+ logger.log(f"\nEmail_Read() read_JSON_File user_dir ::: {user_dir}")
564
+ if not os.path.exists(user_dir):
565
+ os.makedirs(user_dir, exist_ok=True)
566
+ user_file = os.path.join(user_dir, json_fileName)
567
+ if not os.path.exists(user_file) and os.path.exists(json_fileName):
568
+ shutil.copy(json_fileName, user_file)
569
+
570
+ logger.log(f"\nEmail_Read() read_JSON_File user_file ::: {user_file}")
571
+
572
+ if os.path.exists(user_file):
573
+ with open(user_file, "r") as fileObj:
574
+ file_JsonArray = json.load(fileObj)
562
575
 
563
576
  for eachJson in file_JsonArray :
564
577
  for key, value in eachJson.items():
565
- if key == "Category" and value:
578
+ if key == "Category":
566
579
  category_list.append(value)
567
580
  # categories = ", ".join(category_list)
568
581
 
569
582
  return file_JsonArray, category_list
570
583
 
571
584
  else:
572
- message = f"{json_fileName} file not found."
585
+ message = f"{user_file} file not found."
573
586
  raise Exception(message)
574
587
  except Exception as e:
575
588
  msg = f"'{json_fileName}' file is empty. Please provide JSON parameters in the filename."
@@ -579,7 +592,7 @@ class Email_Read:
579
592
 
580
593
  def log_email_login(self, user_id, email, model_name, login_status):
581
594
  base_dir="EMail_log"
582
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
595
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
583
596
  log_dir = os.path.join(base_dir, user_id)
584
597
  os.makedirs(log_dir, exist_ok=True)
585
598
  log_file_path = os.path.join(log_dir, f"{user_id}.csv")
@@ -14,6 +14,8 @@ import openai
14
14
  import json
15
15
  import csv
16
16
  import os
17
+ from email import message_from_string
18
+ import re
17
19
 
18
20
  class Process_Category:
19
21
 
@@ -106,12 +108,13 @@ class Process_Category:
106
108
  logger.log(f"document_type 108::: {document_type}")
107
109
 
108
110
  if responseMethod == "Upload_Document":
109
- # Get today's date folder path
110
- today_date = datetime.today().strftime('%Y-%m-%d')
111
- order_folder = os.path.join(document_type, today_date)
112
111
 
113
112
  email_upload_document = Email_DocumentUploader()
114
113
  if len(fileName) != 0 and document_type != '':
114
+ # Get today's date folder path
115
+ today_date = datetime.today().strftime('%Y-%m-%d')
116
+ order_folder = os.path.join(document_type, today_date)
117
+
115
118
  file_path = os.path.join(order_folder, fileName) # Correct file path
116
119
 
117
120
  with open(file_path, "rb") as file:
@@ -128,7 +131,22 @@ class Process_Category:
128
131
 
129
132
  parameters["DOCUMENT_TYPE"] = document_type
130
133
  logger.log(f"Updated Parameters ::: {parameters}")
131
- new_fileName = self.create_file_from_emailBody(email_body, sender_email_addr, parameters)
134
+
135
+ email_parts = []
136
+ if sender_email_addr:
137
+ email_parts.append(f"From: {sender_email_addr}")
138
+ if to_email_addr:
139
+ email_parts.append(f"To: {to_email_addr}")
140
+ if cc_email_addr:
141
+ email_parts.append(f"CC: {cc_email_addr}")
142
+ if subject:
143
+ email_parts.append(f"Subject: {subject}")
144
+
145
+ email_parts.append(email_body)
146
+ email_body_with_details = "\n".join(email_parts)
147
+
148
+ logger.log(f"email_body ::: {email_body}")
149
+ new_fileName = self.create_file_from_emailBody(email_body_with_details, sender_email_addr, parameters)
132
150
  new_file_path = os.path.join(order_folder, new_fileName)
133
151
 
134
152
  with open(new_file_path, "rb") as file:
@@ -161,12 +179,11 @@ class Process_Category:
161
179
  # Extract customer code once
162
180
  customer_code = customer_data.get("customer_code", "")
163
181
 
164
-
165
- # Step 5: Identify product from email using AI
166
-
182
+ # Step 5: Identify product from email using AI
167
183
  #If there is attachment then append the content in the body.
168
184
  extracted_content = dataValues.get('extracted_content')
169
- if extracted_content != "NA":
185
+ logger.log(f"extracted_content is ::: {extracted_content}")
186
+ if extracted_content != "NA" and extracted_content != None:
170
187
  attachment_email_body =email_body + " \n\n" + extracted_content
171
188
  products = self.identify_products(attachment_email_body, subject, Model_Name, openai_api_key, geminiAI_APIKey, localAIURL, parameters["Product_Assistant_Id"])
172
189
  else:
@@ -366,7 +383,7 @@ class Process_Category:
366
383
  mail.expunge()
367
384
  logger.log(f"Mail removed from inbox and added to '{LABEL}' label.")
368
385
 
369
- current_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
386
+ current_timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
370
387
  filename = f'{uid}{current_timestamp}'
371
388
  logger.log(f"The file name for csv and eml is ::: {filename}")
372
389
  self.store_email_details_to_csv(
@@ -426,7 +443,7 @@ class Process_Category:
426
443
  filename = part.get_filename()
427
444
  mime_type = part.get_content_type().lower()
428
445
 
429
- if "pdf" in mime_type:
446
+ if "pdf" in mime_type or filename.lower().endswith(".pdf"):
430
447
  document_type = "Orders"
431
448
  elif "msword" in mime_type or "wordprocessingml" in mime_type or filename.lower().endswith(".docx"):
432
449
  document_type = "Order Excel"
@@ -438,12 +455,12 @@ class Process_Category:
438
455
  document_type = "Order Email"
439
456
  elif "rtf" in mime_type or filename.lower().endswith(".rtf"):
440
457
  document_type = "Order Excel"
441
-
442
- today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
443
- date_folder = os.path.join(document_type, today_date) # Path: ORDERS/YYYY-MM-DD
444
- os.makedirs(date_folder, exist_ok=True)
445
458
 
446
459
  if filename:
460
+ today_date = datetime.today().strftime('%Y-%m-%d') # Format: YYYY-MM-DD
461
+ date_folder = os.path.join(document_type, today_date) # Path: ORDERS/YYYY-MM-DD
462
+ os.makedirs(date_folder, exist_ok=True)
463
+
447
464
  filepath = os.path.join(date_folder, filename) # Save inside date-wise folder
448
465
 
449
466
  with open(filepath, 'wb') as f:
@@ -479,7 +496,7 @@ class Process_Category:
479
496
  fileName = fileName + ".txt"
480
497
  filePath = os.path.join(order_folder, fileName)
481
498
 
482
- with open(filePath, "w") as file:
499
+ with open(filePath, "w", encoding="utf-8") as file:
483
500
  file.write(text)
484
501
  logger.log(f"New TXT file created from email body and stored in '{filePath}'")
485
502
  else:
@@ -525,7 +542,7 @@ class Process_Category:
525
542
  logger.log("Inside identify_products")
526
543
 
527
544
  if model_type == "OpenAI":
528
- 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, 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}. """
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}. """
529
546
  emailreplyassistant = EmailReplyAssistant()
530
547
  ai_result = emailreplyassistant.identify_customer_product_reply_assitant(openai_api_key, assistant_id, email_body, subject, prompt)
531
548
 
@@ -554,13 +571,31 @@ class Process_Category:
554
571
  return product_data
555
572
 
556
573
  def generate_quotation_draft(self, customer_data, products, model_type, openai_api_key, gemini_api_key, local_ai_url, assistant_id, email_body, subject, signature):
557
- logger.log("Inside generate_quotation_draft")
558
574
 
559
575
  customer = customer_data
560
576
 
561
577
  product_table = "Products:\n"
562
578
  for product in products:
563
- product_table += f'- {product.get("requested_description")} (Code: {product.get("item_no")}) = ${product.get("rate")}\n'
579
+ rate = product.get("rate", "NA")
580
+ quantity = product.get("Quantity", "NA")
581
+ try:
582
+ total = float(rate) * float(quantity)
583
+ except:
584
+ total = rate if rate != "NA" and rate not in ("", None) else "-"
585
+ product_table += (
586
+ f'- Requested Description: {product.get("requested_description", "-")}, '
587
+ f'Item Code: {product.get("item_no", "-")}, '
588
+ f'Item Description: {product.get("description", "-")}, '
589
+ f'Manufacturer: {product.get("make", "-")}, '
590
+ f'Price: {product.get("price", "-")}, '
591
+ f'Inventory Unit: {product.get("inventory unit", "-")}, '
592
+ f'Discount: {product.get("Discount", "-")}, '
593
+ f'Rate: {rate}, '
594
+ f'Quantity: {quantity}, '
595
+ f'Total: {total}, '
596
+ f'Price Pickup Source: {product.get("Price Pickup Source", "-")}, '
597
+ f'Availability: ""\n'
598
+ )
564
599
 
565
600
  if model_type == "OpenAI":
566
601
  prompt = f"""
@@ -570,7 +605,12 @@ class Process_Category:
570
605
  Customer Code: {customer.get('customer_code', '')}
571
606
 
572
607
  {product_table}
573
- product_table must contain price column, if it is none(set it as -).
608
+ 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
610
+
611
+ - If any value is missing, use a dash ("-") instead.
612
+ - "Total" is calculated as Rate × Quantity.
613
+ - "Availability" should be a blank column.
574
614
  Original Email Subject: {subject}
575
615
 
576
616
  Return only the following JSON String format:
@@ -787,6 +827,17 @@ class Process_Category:
787
827
  with the name user_id.csv.
788
828
  """
789
829
 
830
+ logger.log(f"The response am getting in csv is : {response}")
831
+ match = re.search(
832
+ r'Content-Transfer-Encoding:\s*base64\s+([\s\S]+?)\n--',
833
+ response,
834
+ re.IGNORECASE
835
+ )
836
+ if match:
837
+ response = self.extract_html_from_mime(response)
838
+
839
+ logger.log(f"The response after am getting in csv is : {response}")
840
+
790
841
  # Ensure the Mail_log folder exists
791
842
  log_folder = "Mail_log"
792
843
  os.makedirs(log_folder, exist_ok=True)
@@ -910,4 +961,18 @@ class Process_Category:
910
961
  if user_id:
911
962
  return user_id.value
912
963
  return None
964
+
965
+ def extract_html_from_mime(self, raw_data):
966
+ msg = message_from_string(raw_data)
967
+
968
+ if msg.is_multipart():
969
+ for part in msg.walk():
970
+ content_type = part.get_content_type()
971
+ content_encoding = part.get("Content-Transfer-Encoding", "").lower()
972
+
973
+ # Look for text/html part with base64 encoding
974
+ if content_type == "text/html" and content_encoding == "base64":
975
+ payload = part.get_payload(decode=True)
976
+ return payload.decode(part.get_content_charset() or 'utf-8')
977
+ return "No HTML content found."
913
978
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: AIEmailAutomationUtility
3
- Version: 0.0.31
4
- Summary: If attachement in email download it and pass attachement file data to product identification service
3
+ Version: 0.0.33
4
+ Summary: Added new columns and Modified the draft prompt to return draft in consistent format for all mails
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
- If attachement in email download it and pass attachement file data to product identification service
16
+ Added new columns and Modified the draft prompt to return draft in consistent format for all mails
@@ -0,0 +1,15 @@
1
+ AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=R_wJna3-ITsVxQEccryhM93T_Nf_Oxo8DXnS-sDN8VE,6679
2
+ AIEmailAutomationUtility/Email_Classification.py,sha256=Ar0g4Ff8HOT7xICktd3nP_C_vCyeY-xCpUjVCVRWAyc,9417
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
6
+ AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
7
+ AIEmailAutomationUtility/Process_Category.py,sha256=9X3b2BEw31T6RmJ66g0zMP8WYVlVqi8CDGMIsR6ATbU,47741
8
+ AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
9
+ AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
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,,
@@ -1,15 +0,0 @@
1
- AIEmailAutomationUtility/EmailReplyAssistant.py,sha256=R_wJna3-ITsVxQEccryhM93T_Nf_Oxo8DXnS-sDN8VE,6679
2
- AIEmailAutomationUtility/Email_Classification.py,sha256=Ar0g4Ff8HOT7xICktd3nP_C_vCyeY-xCpUjVCVRWAyc,9417
3
- AIEmailAutomationUtility/Email_DocumentUploader.py,sha256=YJu4tuTHr0K-5vuds9gZfj-Hwsgm4MuAOP39Lmu_t98,3219
4
- AIEmailAutomationUtility/Email_Draft.py,sha256=DcyBeDaE8CReKHnHxLiz-o2tDxuUgwy91c4k0qhQbVw,7749
5
- AIEmailAutomationUtility/Email_Read.py,sha256=6obif-ZLhxuatNcGhO8en_R7-Q56EOhzeyILb8vZBjM,30743
6
- AIEmailAutomationUtility/Email_Upload_Document.py,sha256=3bdkxfDlwoeRp-46KPw2Gs1dqBhEIoA1yE5GCudpdV8,1320
7
- AIEmailAutomationUtility/Process_Category.py,sha256=Rhs_flyv-_LvDzu6aO_i-ZDjnMKWDIP_mAcqf8mkl_Y,44607
8
- AIEmailAutomationUtility/Save_Draft.py,sha256=yzLgFN14I_lXE6qL0I3tKNduvcnWdbsY9i2mKdTtio4,5348
9
- AIEmailAutomationUtility/Save_Transaction.py,sha256=Gg1w6hhzHmEFjsuzYvkq-3-EsWReetjLHsYSv5YIGgM,3816
10
- AIEmailAutomationUtility/__init__.py,sha256=Jad3IdPRsVMeLqEEh-FbCrc1lE2tzJO2DTG5Hgmxh5g,391
11
- AIEmailAutomationUtility-0.0.31.dist-info/LICENCE.txt,sha256=2qX9IkEUBx0VJp1Vh9O2dsRwE-IpYId0lXDyn7OVsJ8,1073
12
- AIEmailAutomationUtility-0.0.31.dist-info/METADATA,sha256=E_KUzOjVXHaejQTsqUsfCZqLQYTWJQIFY4xUrZufw_o,701
13
- AIEmailAutomationUtility-0.0.31.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
14
- AIEmailAutomationUtility-0.0.31.dist-info/top_level.txt,sha256=3jTWrTUblVkaP7mpwY2UBSnrlfot5Ykpfsehyke-Uzw,25
15
- AIEmailAutomationUtility-0.0.31.dist-info/RECORD,,