Spaces:
Sleeping
Sleeping
Commit ·
ed9a133
1
Parent(s): 96e57ad
Fixing this
Browse files
server.py
CHANGED
|
@@ -54,7 +54,6 @@ def _read_tabular_from_b64(filename: str, b64_content: str) -> pd.DataFrame:
|
|
| 54 |
bio = io.BytesIO(raw)
|
| 55 |
if ext.endswith('.xlsx'):
|
| 56 |
return pd.read_excel(bio)
|
| 57 |
-
# default csv
|
| 58 |
return pd.read_csv(io.StringIO(raw.decode('utf-8', errors='ignore')))
|
| 59 |
|
| 60 |
# =========================
|
|
@@ -70,7 +69,7 @@ class GmailApiService:
|
|
| 70 |
from googleapiclient.discovery import build
|
| 71 |
base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
|
| 72 |
if not base64_creds:
|
| 73 |
-
print("[Server Log] WARNING:
|
| 74 |
return
|
| 75 |
creds_json = base64.b64decode(base64_creds).decode('utf-8')
|
| 76 |
creds_dict = json.loads(creds_json)
|
|
@@ -349,7 +348,6 @@ def handle_initialize_and_process_files(payload):
|
|
| 349 |
if k not in payload:
|
| 350 |
raise ValueError(f"Missing required field: {k}")
|
| 351 |
|
| 352 |
-
# Read files
|
| 353 |
df_app = _read_tabular_from_b64(payload['app_data_filename'], payload['app_data_content'])
|
| 354 |
df_quantum = _read_tabular_from_b64(payload['quantum_data_filename'], payload['quantum_data_content'])
|
| 355 |
|
|
@@ -358,7 +356,6 @@ def handle_initialize_and_process_files(payload):
|
|
| 358 |
if 'Name' not in df_quantum.columns:
|
| 359 |
raise ValueError("Quantum Data must contain a 'Name' column.")
|
| 360 |
|
| 361 |
-
# Build PRN lookup from App Data
|
| 362 |
df_app_filtered = df_app.dropna(subset=['PRN'])
|
| 363 |
df_app_filtered = df_app_filtered[df_app_filtered['PRN'].astype(str).str.strip() != '']
|
| 364 |
prn_lookup = {
|
|
@@ -366,16 +363,13 @@ def handle_initialize_and_process_files(payload):
|
|
| 366 |
for _, row in df_app_filtered.iterrows()
|
| 367 |
}
|
| 368 |
|
| 369 |
-
# Assign PRN to Quantum by exact Name match (expected to be cleaned already)
|
| 370 |
df_quantum['PRN'] = df_quantum['Name'].apply(lambda name: prn_lookup.get(name, ""))
|
| 371 |
|
| 372 |
-
# Prepare master with Status
|
| 373 |
master_df = df_quantum.copy()
|
| 374 |
if 'PRN' not in master_df.columns:
|
| 375 |
master_df['PRN'] = ''
|
| 376 |
master_df['Status'] = ''
|
| 377 |
|
| 378 |
-
# Save to session
|
| 379 |
session_data[session_id] = {
|
| 380 |
'emails': payload.get('emails', []),
|
| 381 |
'filename': (payload.get('filename') or '').strip(),
|
|
@@ -386,7 +380,6 @@ def handle_initialize_and_process_files(payload):
|
|
| 386 |
'patient_data': master_df.to_dict('records')
|
| 387 |
}
|
| 388 |
|
| 389 |
-
# Signal UI to open login modal
|
| 390 |
emit('data_processed')
|
| 391 |
print(f"[Server Log] Data prepared. Total records: {len(master_df)}")
|
| 392 |
except Exception as e:
|
|
|
|
| 54 |
bio = io.BytesIO(raw)
|
| 55 |
if ext.endswith('.xlsx'):
|
| 56 |
return pd.read_excel(bio)
|
|
|
|
| 57 |
return pd.read_csv(io.StringIO(raw.decode('utf-8', errors='ignore')))
|
| 58 |
|
| 59 |
# =========================
|
|
|
|
| 69 |
from googleapiclient.discovery import build
|
| 70 |
base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
|
| 71 |
if not base64_creds:
|
| 72 |
+
print("[Server Log] WARNING: GDRIVE_SA_KEY_BASE64 not found for Gmail.")
|
| 73 |
return
|
| 74 |
creds_json = base64.b64decode(base64_creds).decode('utf-8')
|
| 75 |
creds_dict = json.loads(creds_json)
|
|
|
|
| 348 |
if k not in payload:
|
| 349 |
raise ValueError(f"Missing required field: {k}")
|
| 350 |
|
|
|
|
| 351 |
df_app = _read_tabular_from_b64(payload['app_data_filename'], payload['app_data_content'])
|
| 352 |
df_quantum = _read_tabular_from_b64(payload['quantum_data_filename'], payload['quantum_data_content'])
|
| 353 |
|
|
|
|
| 356 |
if 'Name' not in df_quantum.columns:
|
| 357 |
raise ValueError("Quantum Data must contain a 'Name' column.")
|
| 358 |
|
|
|
|
| 359 |
df_app_filtered = df_app.dropna(subset=['PRN'])
|
| 360 |
df_app_filtered = df_app_filtered[df_app_filtered['PRN'].astype(str).str.strip() != '']
|
| 361 |
prn_lookup = {
|
|
|
|
| 363 |
for _, row in df_app_filtered.iterrows()
|
| 364 |
}
|
| 365 |
|
|
|
|
| 366 |
df_quantum['PRN'] = df_quantum['Name'].apply(lambda name: prn_lookup.get(name, ""))
|
| 367 |
|
|
|
|
| 368 |
master_df = df_quantum.copy()
|
| 369 |
if 'PRN' not in master_df.columns:
|
| 370 |
master_df['PRN'] = ''
|
| 371 |
master_df['Status'] = ''
|
| 372 |
|
|
|
|
| 373 |
session_data[session_id] = {
|
| 374 |
'emails': payload.get('emails', []),
|
| 375 |
'filename': (payload.get('filename') or '').strip(),
|
|
|
|
| 380 |
'patient_data': master_df.to_dict('records')
|
| 381 |
}
|
| 382 |
|
|
|
|
| 383 |
emit('data_processed')
|
| 384 |
print(f"[Server Log] Data prepared. Total records: {len(master_df)}")
|
| 385 |
except Exception as e:
|
worker.py
CHANGED
|
@@ -12,6 +12,7 @@ from selenium.webdriver.support.ui import WebDriverWait
|
|
| 12 |
from selenium.webdriver.support import expected_conditions as EC
|
| 13 |
from selenium.common.exceptions import TimeoutException
|
| 14 |
|
|
|
|
| 15 |
class QuantumBot:
|
| 16 |
def __init__(self, socketio, app):
|
| 17 |
self.socketio = socketio
|
|
@@ -31,8 +32,8 @@ class QuantumBot:
|
|
| 31 |
|
| 32 |
def initialize_driver(self):
|
| 33 |
"""
|
| 34 |
-
Headless on Linux/
|
| 35 |
-
|
| 36 |
"""
|
| 37 |
try:
|
| 38 |
self.micro_status("Initializing browser...")
|
|
@@ -40,7 +41,6 @@ class QuantumBot:
|
|
| 40 |
headless = os.getenv("HEADLESS", "1") == "1"
|
| 41 |
|
| 42 |
if headless:
|
| 43 |
-
# Headless / CI / HF runner
|
| 44 |
chrome_binary = os.getenv("CHROME_BINARY", "/usr/bin/chromium")
|
| 45 |
chromedriver_path = os.getenv("CHROMEDRIVER", "/usr/bin/chromedriver")
|
| 46 |
options.binary_location = chrome_binary
|
|
@@ -53,12 +53,10 @@ class QuantumBot:
|
|
| 53 |
options.add_argument(f"--user-agent={ua}")
|
| 54 |
service = ChromeService(executable_path=chromedriver_path)
|
| 55 |
else:
|
| 56 |
-
# Local desktop with installed Chrome
|
| 57 |
from webdriver_manager.chrome import ChromeDriverManager
|
| 58 |
options.add_argument("--start-maximized")
|
| 59 |
service = ChromeService(ChromeDriverManager().install())
|
| 60 |
|
| 61 |
-
# Anti-automation flags
|
| 62 |
options.add_argument('--disable-blink-features=AutomationControlled')
|
| 63 |
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
| 64 |
options.add_experimental_option('useAutomationExtension', False)
|
|
@@ -148,7 +146,9 @@ class QuantumBot:
|
|
| 148 |
|
| 149 |
def _set_date_range_on_page(self, start_date_str, end_date_str):
|
| 150 |
self.micro_status("Opening calendar...")
|
| 151 |
-
date_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 152 |
time.sleep(2)
|
| 153 |
date_button.click()
|
| 154 |
self._wait_for_page_load(timeout=10)
|
|
@@ -234,7 +234,6 @@ class QuantumBot:
|
|
| 234 |
self.micro_status(f"Navigating to Void page for '{patient_name}'")
|
| 235 |
self.driver.get("https://gateway.quantumepay.com/credit-card/void")
|
| 236 |
|
| 237 |
-
# Search with retries for stability
|
| 238 |
for attempt in range(15):
|
| 239 |
try:
|
| 240 |
self.micro_status(f"Searching... (Attempt {attempt + 1})")
|
|
@@ -252,11 +251,17 @@ class QuantumBot:
|
|
| 252 |
EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))
|
| 253 |
).click()
|
| 254 |
|
| 255 |
-
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 256 |
|
| 257 |
self.micro_status("Adding to Vault...")
|
| 258 |
-
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
|
| 261 |
try:
|
| 262 |
self.micro_status("Verifying success and saving...")
|
|
@@ -264,18 +269,23 @@ class QuantumBot:
|
|
| 264 |
company_input.clear(); company_input.send_keys(patient_name)
|
| 265 |
|
| 266 |
contact_input = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.NAME, "company_contact")))
|
| 267 |
-
# Clear reliably then fill PRN
|
| 268 |
contact_input.click(); contact_input.send_keys(Keys.CONTROL + "a"); contact_input.send_keys(Keys.BACK_SPACE)
|
| 269 |
contact_input.send_keys(str(patient_prn))
|
| 270 |
|
| 271 |
-
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
time.sleep(2)
|
| 274 |
return 'Done'
|
| 275 |
except TimeoutException:
|
| 276 |
self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
|
| 277 |
try:
|
| 278 |
-
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 279 |
except Exception:
|
| 280 |
pass
|
| 281 |
return 'Bad'
|
|
@@ -309,16 +319,22 @@ class QuantumBot:
|
|
| 309 |
time.sleep(0.6); details_button.click()
|
| 310 |
self._wait_for_page_load(timeout=5)
|
| 311 |
|
| 312 |
-
transaction_link = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 313 |
time.sleep(0.6); transaction_link.click()
|
| 314 |
self._wait_for_page_load()
|
| 315 |
|
| 316 |
self.micro_status("Adding to Vault...")
|
| 317 |
-
vault_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 318 |
time.sleep(0.6); vault_button.click()
|
| 319 |
self._wait_for_page_load()
|
| 320 |
|
| 321 |
-
confirm_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 322 |
time.sleep(0.6); confirm_button.click()
|
| 323 |
self._wait_for_page_load()
|
| 324 |
|
|
@@ -335,11 +351,15 @@ class QuantumBot:
|
|
| 335 |
contact_input.click(); contact_input.send_keys(Keys.CONTROL + "a"); contact_input.send_keys(Keys.BACK_SPACE)
|
| 336 |
contact_input.send_keys(str(patient_prn))
|
| 337 |
|
| 338 |
-
save_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 339 |
time.sleep(0.6); save_button.click()
|
| 340 |
self._wait_for_page_load()
|
| 341 |
|
| 342 |
-
final_confirm = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 343 |
time.sleep(0.6); final_confirm.click()
|
| 344 |
time.sleep(1.2)
|
| 345 |
self._wait_for_page_load()
|
|
@@ -347,7 +367,9 @@ class QuantumBot:
|
|
| 347 |
except TimeoutException:
|
| 348 |
self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
|
| 349 |
try:
|
| 350 |
-
cancel_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
|
|
|
|
|
|
| 351 |
time.sleep(0.5); cancel_button.click()
|
| 352 |
except Exception:
|
| 353 |
pass
|
|
|
|
| 12 |
from selenium.webdriver.support import expected_conditions as EC
|
| 13 |
from selenium.common.exceptions import TimeoutException
|
| 14 |
|
| 15 |
+
|
| 16 |
class QuantumBot:
|
| 17 |
def __init__(self, socketio, app):
|
| 18 |
self.socketio = socketio
|
|
|
|
| 32 |
|
| 33 |
def initialize_driver(self):
|
| 34 |
"""
|
| 35 |
+
Headless on Linux/CI (HEADLESS=1 default), desktop Chrome when HEADLESS=0.
|
| 36 |
+
Optional env: CHROME_BINARY, CHROMEDRIVER, USER_AGENT
|
| 37 |
"""
|
| 38 |
try:
|
| 39 |
self.micro_status("Initializing browser...")
|
|
|
|
| 41 |
headless = os.getenv("HEADLESS", "1") == "1"
|
| 42 |
|
| 43 |
if headless:
|
|
|
|
| 44 |
chrome_binary = os.getenv("CHROME_BINARY", "/usr/bin/chromium")
|
| 45 |
chromedriver_path = os.getenv("CHROMEDRIVER", "/usr/bin/chromedriver")
|
| 46 |
options.binary_location = chrome_binary
|
|
|
|
| 53 |
options.add_argument(f"--user-agent={ua}")
|
| 54 |
service = ChromeService(executable_path=chromedriver_path)
|
| 55 |
else:
|
|
|
|
| 56 |
from webdriver_manager.chrome import ChromeDriverManager
|
| 57 |
options.add_argument("--start-maximized")
|
| 58 |
service = ChromeService(ChromeDriverManager().install())
|
| 59 |
|
|
|
|
| 60 |
options.add_argument('--disable-blink-features=AutomationControlled')
|
| 61 |
options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
| 62 |
options.add_experimental_option('useAutomationExtension', False)
|
|
|
|
| 146 |
|
| 147 |
def _set_date_range_on_page(self, start_date_str, end_date_str):
|
| 148 |
self.micro_status("Opening calendar...")
|
| 149 |
+
date_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 150 |
+
EC.element_to_be_clickable((By.XPATH, "//button[.//span[contains(text(), '-')]]"))
|
| 151 |
+
)
|
| 152 |
time.sleep(2)
|
| 153 |
date_button.click()
|
| 154 |
self._wait_for_page_load(timeout=10)
|
|
|
|
| 234 |
self.micro_status(f"Navigating to Void page for '{patient_name}'")
|
| 235 |
self.driver.get("https://gateway.quantumepay.com/credit-card/void")
|
| 236 |
|
|
|
|
| 237 |
for attempt in range(15):
|
| 238 |
try:
|
| 239 |
self.micro_status(f"Searching... (Attempt {attempt + 1})")
|
|
|
|
| 251 |
EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))
|
| 252 |
).click()
|
| 253 |
|
| 254 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 255 |
+
EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))
|
| 256 |
+
).click()
|
| 257 |
|
| 258 |
self.micro_status("Adding to Vault...")
|
| 259 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 260 |
+
EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))
|
| 261 |
+
).click()
|
| 262 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 263 |
+
EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))
|
| 264 |
+
).click()
|
| 265 |
|
| 266 |
try:
|
| 267 |
self.micro_status("Verifying success and saving...")
|
|
|
|
| 269 |
company_input.clear(); company_input.send_keys(patient_name)
|
| 270 |
|
| 271 |
contact_input = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable((By.NAME, "company_contact")))
|
|
|
|
| 272 |
contact_input.click(); contact_input.send_keys(Keys.CONTROL + "a"); contact_input.send_keys(Keys.BACK_SPACE)
|
| 273 |
contact_input.send_keys(str(patient_prn))
|
| 274 |
|
| 275 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 276 |
+
EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))
|
| 277 |
+
).click()
|
| 278 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 279 |
+
EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))
|
| 280 |
+
).click()
|
| 281 |
time.sleep(2)
|
| 282 |
return 'Done'
|
| 283 |
except TimeoutException:
|
| 284 |
self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
|
| 285 |
try:
|
| 286 |
+
WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 287 |
+
EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))
|
| 288 |
+
).click()
|
| 289 |
except Exception:
|
| 290 |
pass
|
| 291 |
return 'Bad'
|
|
|
|
| 319 |
time.sleep(0.6); details_button.click()
|
| 320 |
self._wait_for_page_load(timeout=5)
|
| 321 |
|
| 322 |
+
transaction_link = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 323 |
+
EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))
|
| 324 |
+
)
|
| 325 |
time.sleep(0.6); transaction_link.click()
|
| 326 |
self._wait_for_page_load()
|
| 327 |
|
| 328 |
self.micro_status("Adding to Vault...")
|
| 329 |
+
vault_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 330 |
+
EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))
|
| 331 |
+
)
|
| 332 |
time.sleep(0.6); vault_button.click()
|
| 333 |
self._wait_for_page_load()
|
| 334 |
|
| 335 |
+
confirm_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 336 |
+
EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))
|
| 337 |
+
)
|
| 338 |
time.sleep(0.6); confirm_button.click()
|
| 339 |
self._wait_for_page_load()
|
| 340 |
|
|
|
|
| 351 |
contact_input.click(); contact_input.send_keys(Keys.CONTROL + "a"); contact_input.send_keys(Keys.BACK_SPACE)
|
| 352 |
contact_input.send_keys(str(patient_prn))
|
| 353 |
|
| 354 |
+
save_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 355 |
+
EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))
|
| 356 |
+
)
|
| 357 |
time.sleep(0.6); save_button.click()
|
| 358 |
self._wait_for_page_load()
|
| 359 |
|
| 360 |
+
final_confirm = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 361 |
+
EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))
|
| 362 |
+
)
|
| 363 |
time.sleep(0.6); final_confirm.click()
|
| 364 |
time.sleep(1.2)
|
| 365 |
self._wait_for_page_load()
|
|
|
|
| 367 |
except TimeoutException:
|
| 368 |
self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
|
| 369 |
try:
|
| 370 |
+
cancel_button = WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
|
| 371 |
+
EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))
|
| 372 |
+
)
|
| 373 |
time.sleep(0.5); cancel_button.click()
|
| 374 |
except Exception:
|
| 375 |
pass
|