sonuprasad23 commited on
Commit
cf2c247
·
1 Parent(s): 14961a3

Updated logic for file processing

Browse files
Files changed (2) hide show
  1. server.py +22 -27
  2. worker.py +35 -57
server.py CHANGED
@@ -32,15 +32,12 @@ session_data = {}
32
 
33
  class GmailApiService:
34
  def __init__(self):
35
- self.sender_email = os.getenv('EMAIL_SENDER')
36
- self.service = None
37
  try:
38
- from google.oauth2 import service_account
39
- from googleapiclient.discovery import build
40
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
41
  if not base64_creds: print("[Gmail API] WARNING: GDRIVE_SA_KEY_BASE64 not found."); return
42
- creds_json = base64.b64decode(base64_creds).decode('utf-8')
43
- creds_dict = json.loads(creds_json)
44
  credentials = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/gmail.send'])
45
  if self.sender_email: credentials = credentials.with_subject(self.sender_email)
46
  self.service = build('gmail', 'v1', credentials=credentials)
@@ -51,20 +48,16 @@ class GmailApiService:
51
  if not self.service or not recipients: return False
52
  try:
53
  from googleapiclient.errors import HttpError
54
- message = MIMEMultipart()
55
- message['From'] = self.sender_email; message['To'] = ', '.join(recipients); message['Subject'] = subject
56
  message.attach(MIMEText(body, 'html'))
57
  if attachments:
58
  for filename, content in attachments.items():
59
- part = MIMEBase('application', 'octet-stream')
60
- part.set_payload(content.encode('utf-8'))
61
- encoders.encode_base64(part)
62
- part.add_header('Content-Disposition', f'attachment; filename="{filename}"')
63
- message.attach(part)
64
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')
65
  sent_message = self.service.users().messages().send(userId='me', body={'raw': raw_message}).execute()
66
- print(f"[Gmail API] Email sent successfully! Message ID: {sent_message['id']}")
67
- return True
68
  except Exception as e: print(f"[Gmail API] Error: {e}"); return False
69
 
70
  class GoogleDriveService:
@@ -121,19 +114,20 @@ def generate_and_send_reports(session_id, results, is_crash_report=False, is_ter
121
  socketio.emit('process_complete', {'message': 'No patients were processed.'}); return
122
  data = session_data.get(session_id, {})
123
 
124
- full_df = pd.DataFrame(data.get('patient_data_for_report'))
125
- # --- Definitive Data Type Fix ---
126
- full_df['Status'] = full_df['Status'].astype('object')
127
-
128
  result_df = pd.DataFrame(results)
129
 
130
  if not result_df.empty:
131
- # Use a multi-index merge for robustness
132
- full_df = full_df.set_index(['Name', 'PRN'])
133
- result_df = result_df.set_index(['Name', 'PRN'])
134
- full_df.update(result_df)
135
- full_df.reset_index(inplace=True)
136
-
 
 
137
  bad_df = full_df[full_df['Status'] == 'Bad'][['Name', 'PRN', 'Status']]
138
  timestamp = datetime.now().strftime("%d_%b_%Y"); custom_name = data.get('filename') or timestamp
139
  full_report_name = f"{custom_name}_Full.csv"; bad_report_name = f"{custom_name}_Bad.csv"
@@ -178,9 +172,10 @@ def handle_file_processing():
178
  filtered_df = final_df[final_df['PRN'] != ''].copy()
179
 
180
  patient_data_for_automation = filtered_df.to_dict('records')
181
-
182
- # Base DataFrame for the final report
183
  report_base_df = df_quantum.copy()
 
184
  report_base_df['Status'] = ''
185
  report_base_df['Status'] = report_base_df['Status'].astype('object')
186
 
 
32
 
33
  class GmailApiService:
34
  def __init__(self):
35
+ self.sender_email = os.getenv('EMAIL_SENDER'); self.service = None
 
36
  try:
37
+ from google.oauth2 import service_account; from googleapiclient.discovery import build
 
38
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
39
  if not base64_creds: print("[Gmail API] WARNING: GDRIVE_SA_KEY_BASE64 not found."); return
40
+ creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
 
41
  credentials = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/gmail.send'])
42
  if self.sender_email: credentials = credentials.with_subject(self.sender_email)
43
  self.service = build('gmail', 'v1', credentials=credentials)
 
48
  if not self.service or not recipients: return False
49
  try:
50
  from googleapiclient.errors import HttpError
51
+ message = MIMEMultipart(); message['From'] = self.sender_email; message['To'] = ', '.join(recipients); message['Subject'] = subject
 
52
  message.attach(MIMEText(body, 'html'))
53
  if attachments:
54
  for filename, content in attachments.items():
55
+ part = MIMEBase('application', 'octet-stream'); part.set_payload(content.encode('utf-8'))
56
+ encoders.encode_base64(part); part.add_header('Content-Disposition', f'attachment; filename="{filename}"')
57
+ msg.attach(part)
 
 
58
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')
59
  sent_message = self.service.users().messages().send(userId='me', body={'raw': raw_message}).execute()
60
+ print(f"[Gmail API] Email sent successfully! Message ID: {sent_message['id']}"); return True
 
61
  except Exception as e: print(f"[Gmail API] Error: {e}"); return False
62
 
63
  class GoogleDriveService:
 
114
  socketio.emit('process_complete', {'message': 'No patients were processed.'}); return
115
  data = session_data.get(session_id, {})
116
 
117
+ # --- Definitive Reporting Fix ---
118
+ # Create the base of the report from the processed data which includes Name and PRN
119
+ report_base_df = pd.DataFrame(data.get('patient_data_for_report'))
 
120
  result_df = pd.DataFrame(results)
121
 
122
  if not result_df.empty:
123
+ # Use a reliable merge to update the status
124
+ report_base_df = pd.merge(report_base_df, result_df[['Name', 'Status']], on='Name', how='left')
125
+ # Fill any rows that weren't processed with their original status (or blank)
126
+ report_base_df['Status_y'].fillna(report_base_df['Status_x'], inplace=True)
127
+ report_base_df.rename(columns={'Status_y': 'Status'}, inplace=True)
128
+ del report_base_df['Status_x']
129
+
130
+ full_df = report_base_df
131
  bad_df = full_df[full_df['Status'] == 'Bad'][['Name', 'PRN', 'Status']]
132
  timestamp = datetime.now().strftime("%d_%b_%Y"); custom_name = data.get('filename') or timestamp
133
  full_report_name = f"{custom_name}_Full.csv"; bad_report_name = f"{custom_name}_Bad.csv"
 
172
  filtered_df = final_df[final_df['PRN'] != ''].copy()
173
 
174
  patient_data_for_automation = filtered_df.to_dict('records')
175
+
176
+ # Base DataFrame for the final report now correctly includes PRN
177
  report_base_df = df_quantum.copy()
178
+ report_base_df = pd.merge(report_base_df, filtered_df[['Name', 'PRN']], on='Name', how='left')
179
  report_base_df['Status'] = ''
180
  report_base_df['Status'] = report_base_df['Status'].astype('object')
181
 
worker.py CHANGED
@@ -6,17 +6,15 @@ from selenium import webdriver
6
  from selenium.webdriver.chrome.service import Service as ChromeService
7
  from selenium.webdriver.chrome.options import Options as ChromeOptions
8
  from selenium.webdriver.common.by import By
 
9
  from selenium.webdriver.support.ui import WebDriverWait
10
  from selenium.webdriver.support import expected_conditions as EC
11
  from selenium.common.exceptions import TimeoutException
12
 
13
  class QuantumBot:
14
  def __init__(self, socketio, app):
15
- self.socketio = socketio
16
- self.app = app
17
- self.driver = None
18
- self.DEFAULT_TIMEOUT = 30
19
- self.termination_event = threading.Event()
20
 
21
  def _kill_chrome_processes(self):
22
  try:
@@ -29,12 +27,9 @@ class QuantumBot:
29
  try:
30
  self.micro_status("Initializing headless browser...")
31
  self._kill_chrome_processes()
32
- options = ChromeOptions()
33
- options.binary_location = "/usr/bin/chromium"
34
- options.add_argument("--headless=new")
35
- options.add_argument("--no-sandbox")
36
- options.add_argument("--disable-dev-shm-usage")
37
- options.add_argument("--disable-gpu")
38
  options.add_argument("--window-size=1920,1080")
39
  user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
40
  options.add_argument(f"--user-agent={user_agent}")
@@ -45,8 +40,7 @@ class QuantumBot:
45
  })
46
  return True, None
47
  except Exception as e:
48
- error_message = f"Message: {str(e)}"
49
- print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
50
  return False, error_message
51
 
52
  def micro_status(self, message):
@@ -55,15 +49,12 @@ class QuantumBot:
55
  self.socketio.emit('micro_status_update', {'message': message})
56
 
57
  def stop(self):
58
- self.micro_status("Termination signal received...")
59
- self.termination_event.set()
60
 
61
  def login(self, username, password):
62
  try:
63
- self.micro_status("Navigating to login page...")
64
- self.driver.get("https://gateway.quantumepay.com/")
65
- time.sleep(2)
66
- self.micro_status("Entering credentials...")
67
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Username"))).send_keys(username)
68
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Password"))).send_keys(password)
69
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
@@ -71,39 +62,34 @@ class QuantumBot:
71
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
72
  return True, None
73
  except Exception as e:
74
- error_message = f"Error during login: {str(e)}"
75
- print(f"[Bot] ERROR during login: {error_message}")
76
  return False, error_message
77
 
78
  def submit_otp(self, otp):
79
  try:
80
- self.micro_status(f"Submitting OTP...")
81
- otp_digits = list(otp)
82
- for i in range(6):
83
- self.driver.find_element(By.ID, f"code{i+1}").send_keys(otp_digits[i])
84
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
85
  self.micro_status("Verifying login success...")
86
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
87
  return True, None
88
  except Exception as e:
89
- error_message = f"Error during OTP submission: {str(e)}"
90
- print(f"[Bot] ERROR during OTP submission: {error_message}")
91
  return False, error_message
92
 
93
  def process_patient_list(self, patient_data):
94
  results = []
95
  for index, record in enumerate(patient_data):
96
- if self.termination_event.is_set():
97
- print("[Bot] Termination detected.")
98
- break
99
- patient_name = record['Name']
100
- patient_prn = record.get('PRN', '')
101
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_data)})...")
102
  status = self._process_single_patient(patient_name, patient_prn)
103
  results.append({'Name': patient_name, 'PRN': patient_prn, 'Status': status})
104
  with self.app.app_context():
105
  self.socketio.emit('log_update', {'name': patient_name, 'prn': patient_prn, 'status': status})
106
- self.socketio.emit('stats_update', {'processed': len(results), 'remaining': len(patient_data) - len(results), 'status': status})
 
 
107
  return results
108
 
109
  def _process_single_patient(self, patient_name, patient_prn):
@@ -116,15 +102,10 @@ class QuantumBot:
116
  self.micro_status(f"Searching... (Attempt {attempt + 1})")
117
  WebDriverWait(self.driver, 2).until(EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'table-wrapper')]")))
118
  search_box = WebDriverWait(self.driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='Search']")))
119
- search_box.click(); time.sleep(0.5)
120
- search_box.clear(); time.sleep(0.5)
121
- search_box.send_keys(patient_name)
122
- search_successful = True
123
- break
124
- except Exception:
125
- time.sleep(1)
126
- if not search_successful:
127
- raise Exception("Failed to search for patient.")
128
  time.sleep(3)
129
  self.micro_status("Opening transaction details...")
130
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
@@ -135,39 +116,36 @@ class QuantumBot:
135
  try:
136
  self.micro_status("Verifying success and saving...")
137
  company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
138
- company_input.clear()
139
- company_input.send_keys(patient_name)
140
 
141
  # --- Definitive PRN Handling Logic ---
142
  try:
143
- contact_input = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.NAME, "last_name")))
144
- self.micro_status("Clearing Company Contact field...")
145
- contact_input.clear()
 
 
146
  if patient_prn and pd.notna(patient_prn) and str(patient_prn).strip():
147
  self.micro_status(f"Entering PRN: {patient_prn}...")
148
  contact_input.send_keys(str(patient_prn))
149
  else:
150
- self.micro_status("No PRN provided, leaving Company Contact blank.")
151
  except TimeoutException:
152
- self.micro_status("Company Contact field not found, skipping PRN step.")
153
 
154
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))).click()
155
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))).click()
156
- time.sleep(5)
157
- return 'Done'
158
  except TimeoutException:
159
  self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
160
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))).click()
161
  return 'Bad'
162
  except Exception as e:
163
- print(f"An error occurred while processing {patient_name}: {e}")
164
- return 'Error'
165
 
166
  def shutdown(self):
167
  try:
168
- if self.driver:
169
- self.driver.quit()
170
  self._kill_chrome_processes()
171
  print("[Bot] Chrome session closed and cleaned up.")
172
- except Exception as e:
173
- print(f"[Bot] Error during shutdown: {e}")
 
6
  from selenium.webdriver.chrome.service import Service as ChromeService
7
  from selenium.webdriver.chrome.options import Options as ChromeOptions
8
  from selenium.webdriver.common.by import By
9
+ from selenium.webdriver.common.keys import Keys
10
  from selenium.webdriver.support.ui import WebDriverWait
11
  from selenium.webdriver.support import expected_conditions as EC
12
  from selenium.common.exceptions import TimeoutException
13
 
14
  class QuantumBot:
15
  def __init__(self, socketio, app):
16
+ self.socketio = socketio; self.app = app; self.driver = None
17
+ self.DEFAULT_TIMEOUT = 30; self.termination_event = threading.Event()
 
 
 
18
 
19
  def _kill_chrome_processes(self):
20
  try:
 
27
  try:
28
  self.micro_status("Initializing headless browser...")
29
  self._kill_chrome_processes()
30
+ options = ChromeOptions(); options.binary_location = "/usr/bin/chromium"
31
+ options.add_argument("--headless=new"); options.add_argument("--no-sandbox")
32
+ options.add_argument("--disable-dev-shm-usage"); options.add_argument("--disable-gpu")
 
 
 
33
  options.add_argument("--window-size=1920,1080")
34
  user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
35
  options.add_argument(f"--user-agent={user_agent}")
 
40
  })
41
  return True, None
42
  except Exception as e:
43
+ error_message = f"Message: {str(e)}"; print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
 
44
  return False, error_message
45
 
46
  def micro_status(self, message):
 
49
  self.socketio.emit('micro_status_update', {'message': message})
50
 
51
  def stop(self):
52
+ self.micro_status("Termination signal received..."); self.termination_event.set()
 
53
 
54
  def login(self, username, password):
55
  try:
56
+ self.micro_status("Navigating to login page..."); self.driver.get("https://gateway.quantumepay.com/")
57
+ time.sleep(2); self.micro_status("Entering credentials...")
 
 
58
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Username"))).send_keys(username)
59
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Password"))).send_keys(password)
60
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
 
62
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
63
  return True, None
64
  except Exception as e:
65
+ error_message = f"Error during login: {str(e)}"; print(f"[Bot] ERROR during login: {error_message}")
 
66
  return False, error_message
67
 
68
  def submit_otp(self, otp):
69
  try:
70
+ self.micro_status(f"Submitting OTP..."); otp_digits = list(otp)
71
+ for i in range(6): self.driver.find_element(By.ID, f"code{i+1}").send_keys(otp_digits[i])
 
 
72
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
73
  self.micro_status("Verifying login success...")
74
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
75
  return True, None
76
  except Exception as e:
77
+ error_message = f"Error during OTP submission: {str(e)}"; print(f"[Bot] ERROR during OTP submission: {error_message}")
 
78
  return False, error_message
79
 
80
  def process_patient_list(self, patient_data):
81
  results = []
82
  for index, record in enumerate(patient_data):
83
+ if self.termination_event.is_set(): print("[Bot] Termination detected."); break
84
+ patient_name = record['Name']; patient_prn = record.get('PRN', '')
 
 
 
85
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_data)})...")
86
  status = self._process_single_patient(patient_name, patient_prn)
87
  results.append({'Name': patient_name, 'PRN': patient_prn, 'Status': status})
88
  with self.app.app_context():
89
  self.socketio.emit('log_update', {'name': patient_name, 'prn': patient_prn, 'status': status})
90
+ self.socketio.emit('stats_update', {
91
+ 'processed': len(results), 'remaining': len(patient_data) - len(results), 'status': status
92
+ })
93
  return results
94
 
95
  def _process_single_patient(self, patient_name, patient_prn):
 
102
  self.micro_status(f"Searching... (Attempt {attempt + 1})")
103
  WebDriverWait(self.driver, 2).until(EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'table-wrapper')]")))
104
  search_box = WebDriverWait(self.driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='Search']")))
105
+ search_box.click(); time.sleep(0.5); search_box.clear(); time.sleep(0.5)
106
+ search_box.send_keys(patient_name); search_successful = True; break
107
+ except Exception: time.sleep(1)
108
+ if not search_successful: raise Exception("Failed to search for patient.")
 
 
 
 
 
109
  time.sleep(3)
110
  self.micro_status("Opening transaction details...")
111
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
 
116
  try:
117
  self.micro_status("Verifying success and saving...")
118
  company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
119
+ company_input.clear(); company_input.send_keys(patient_name)
 
120
 
121
  # --- Definitive PRN Handling Logic ---
122
  try:
123
+ contact_input = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.NAME, "company_contact")))
124
+ self.micro_status("Clearing Contact Name field...")
125
+ contact_input.click()
126
+ contact_input.send_keys(Keys.CONTROL + "a")
127
+ contact_input.send_keys(Keys.BACK_SPACE)
128
  if patient_prn and pd.notna(patient_prn) and str(patient_prn).strip():
129
  self.micro_status(f"Entering PRN: {patient_prn}...")
130
  contact_input.send_keys(str(patient_prn))
131
  else:
132
+ self.micro_status("No PRN provided, leaving Contact Name blank.")
133
  except TimeoutException:
134
+ self.micro_status("Contact Name field not found, skipping PRN step.")
135
 
136
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))).click()
137
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))).click()
138
+ time.sleep(5); return 'Done'
 
139
  except TimeoutException:
140
  self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
141
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))).click()
142
  return 'Bad'
143
  except Exception as e:
144
+ print(f"An error occurred while processing {patient_name}: {e}"); return 'Error'
 
145
 
146
  def shutdown(self):
147
  try:
148
+ if self.driver: self.driver.quit()
 
149
  self._kill_chrome_processes()
150
  print("[Bot] Chrome session closed and cleaned up.")
151
+ except Exception as e: print(f"[Bot] Error during shutdown: {e}")