feat: add BCBS MA eligibility check with OTP flow

- New Selenium worker (fresh Chrome per run, no persistent session)
  login → OTP modal → eTools → ConnectCenter → Verification →
  New Eligibility Request → fill form (NPI, member ID, DOB) →
  Expand All → CDP PDF back to app
- Backend route fetches BCBS_MA credentials + provider NPI from settings
- Frontend OTP modal with 6-digit code entry
- BCBS MA added to insurance credentials dropdown in settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-01 00:36:11 -04:00
parent 535619c286
commit e644d21cee
12 changed files with 1468 additions and 9 deletions

View File

@@ -23,6 +23,7 @@ import helpers_cca_preauth as hcca_preauth
import helpers_ddma_claim as hddma_claim
import helpers_uniteddh_claim as huniteddh_claim
import helpers_tuftssco_claim as htuftssco_claim
import helpers_bcbs_ma_eligibility as hbcbs_ma
# Import startup session-clear functions
from ddma_browser_manager import clear_ddma_session_on_startup
@@ -547,6 +548,48 @@ async def cca_eligibility(request: Request):
return {"status": "started", "session_id": sid}
async def _bcbs_ma_worker_wrapper(sid: str, data: dict, url: str):
"""Background worker for BCBS MA eligibility — fresh browser, always OTP."""
global active_jobs, waiting_jobs
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try:
await hbcbs_ma.start_bcbs_ma_run(sid, data, url)
finally:
async with lock:
active_jobs -= 1
@app.post("/bcbs-ma-eligibility")
async def bcbs_ma_eligibility(request: Request):
"""
Starts a BCBS MA eligibility session in the background.
Fresh Chrome each time — no persistent session (OTP always required).
Body: { "data": { memberId, dateOfBirth, firstName, lastName, bcbsMaUsername, bcbsMaPassword } }
Returns: { status: "started", session_id: "<uuid>" }
"""
global waiting_jobs
body = await request.json()
data = body.get("data", {})
sid = hbcbs_ma.make_session_entry()
hbcbs_ma.sessions[sid]["type"] = "bcbs_ma_eligibility"
hbcbs_ma.sessions[sid]["last_activity"] = time.time()
async with lock:
waiting_jobs += 1
asyncio.create_task(_bcbs_ma_worker_wrapper(
sid, data,
url="https://provider.bluecrossma.com/ProviderHome/portal/"
))
return {"status": "started", "session_id": sid}
async def _cca_claim_worker_wrapper(sid: str, data: dict, url: str):
"""Background worker for CCA claim submission."""
global active_jobs, waiting_jobs
@@ -782,6 +825,8 @@ async def submit_otp(request: Request):
res = huniteddh_claim.submit_otp(sid, otp)
elif sid in htuftssco_claim.sessions:
res = htuftssco_claim.submit_otp(sid, otp)
elif sid in hbcbs_ma.sessions:
res = hbcbs_ma.submit_otp(sid, otp)
else:
raise HTTPException(status_code=404, detail="session not found")
@@ -813,6 +858,8 @@ async def session_status(sid: str):
s = huniteddh_claim.get_session_status(sid)
elif sid in htuftssco_claim.sessions:
s = htuftssco_claim.get_session_status(sid)
elif sid in hbcbs_ma.sessions:
s = hbcbs_ma.get_session_status(sid)
else:
s = {"status": "not_found"}
if s.get("status") == "not_found":

View File

@@ -0,0 +1,199 @@
import os
import time
import asyncio
import uuid
from typing import Dict, Any
from selenium.common.exceptions import WebDriverException
from selenium_BCBS_MA_eligibilityCheckWorker import AutomationBCBSMAEligibilityCheck
# In-memory session store
sessions: Dict[str, Dict[str, Any]] = {}
SESSION_OTP_TIMEOUT = int(os.getenv("SESSION_OTP_TIMEOUT", "120")) # seconds
def make_session_entry() -> str:
sid = str(uuid.uuid4())
sessions[sid] = {
"status": "created", # created → running → waiting_for_otp → completed / error
"created_at": time.time(),
"last_activity": time.time(),
"bot": None,
"driver": None,
"otp_event": asyncio.Event(),
"otp_value": None,
"result": None,
"message": None,
"type": None,
}
return sid
async def _remove_session_later(sid: str, delay: int = 30):
await asyncio.sleep(delay)
sessions.pop(sid, None)
print(f"[helpers_bcbs_ma] cleaned session {sid}")
async def start_bcbs_ma_run(sid: str, data: dict, url: str):
"""
Full BCBS MA eligibility workflow for one session.
Always fresh Chrome — no persistent session because BCBS MA always
requires OTP (the prefix changes each login).
OTP handling:
a) Accept OTP submitted from the app via /submit-otp (sets otp_value)
b) Poll the browser directly to detect user entry in the open window
"""
s = sessions.get(sid)
if not s:
return {"status": "error", "message": "session not found"}
s["status"] = "running"
s["last_activity"] = time.time()
bot = None
try:
bot = AutomationBCBSMAEligibilityCheck({"data": data})
bot.config_driver()
s["bot"] = bot
s["driver"] = bot.driver
# ── Login ──────────────────────────────────────────────────────────────
try:
login_result = bot.login(url)
except WebDriverException as e:
s["status"] = "error"
s["message"] = f"WebDriver error during login: {e}"
return {"status": "error", "message": s["message"]}
except Exception as e:
s["status"] = "error"
s["message"] = f"Login failed: {e}"
return {"status": "error", "message": s["message"]}
if isinstance(login_result, str) and login_result.startswith("ERROR"):
s["status"] = "error"
s["message"] = login_result
return {"status": "error", "message": login_result}
# ── OTP required (always for BCBS MA) ─────────────────────────────────
if isinstance(login_result, str) and login_result == "OTP_REQUIRED":
s["status"] = "waiting_for_otp"
s["message"] = "OTP required — enter the 6-digit code from the BCBS MA email"
s["last_activity"] = time.time()
driver = bot.driver
login_success = False
print(f"[BCBS MA] Waiting for OTP (up to {SESSION_OTP_TIMEOUT}s)...")
for poll in range(SESSION_OTP_TIMEOUT):
await asyncio.sleep(1)
s["last_activity"] = time.time()
try:
# a) App submitted OTP
otp_value = s.get("otp_value")
if otp_value:
print(f"[BCBS MA OTP poll {poll+1}] Submitting OTP from app...")
otp_result = bot.submit_otp_step(otp_value)
s["otp_value"] = None
if isinstance(otp_result, str) and otp_result == "SUCCESS":
login_success = True
break
elif isinstance(otp_result, str) and otp_result.startswith("ERROR"):
print(f"[BCBS MA OTP] submit_otp_step returned: {otp_result}")
# Don't abort yet — let the poll loop check the browser state
# b) Detect success by browser URL
current_url = driver.current_url.lower()
print(f"[BCBS MA OTP poll {poll+1}/{SESSION_OTP_TIMEOUT}] URL: {current_url[:70]}")
if "authsvc" not in current_url and "/mga/sps/" not in current_url:
# Left the OTP page — login completed (user entered OTP in browser)
print("[BCBS MA OTP] Browser left OTP page — login successful")
login_success = True
break
except Exception as poll_err:
print(f"[BCBS MA OTP poll {poll+1}] Error: {poll_err}")
if not login_success:
s["status"] = "error"
s["message"] = "OTP timeout — login not completed in time"
return {"status": "error", "message": s["message"]}
s["status"] = "running"
s["message"] = "Login successful after OTP"
print("[BCBS MA] OTP accepted, proceeding to eligibility search...")
await asyncio.sleep(2)
# ── Already logged in (no OTP path) ──────────────────────────────────
elif isinstance(login_result, str) and login_result == "SUCCESS":
print("[BCBS MA] Login succeeded without OTP")
s["status"] = "running"
# ── Step 1: search member ──────────────────────────────────────────────
step1_result = bot.step1()
if isinstance(step1_result, str) and step1_result.startswith("ERROR"):
s["status"] = "error"
s["message"] = step1_result
return {"status": "error", "message": step1_result}
# ── Step 2: extract + PDF ──────────────────────────────────────────────
step2_result = bot.step2()
if isinstance(step2_result, dict) and step2_result.get("status") == "success":
s["status"] = "completed"
s["result"] = step2_result
s["message"] = "completed"
asyncio.create_task(_remove_session_later(sid, 30))
return step2_result
else:
s["status"] = "error"
s["message"] = step2_result.get("message", "unknown error") if isinstance(step2_result, dict) else str(step2_result)
return {"status": "error", "message": s["message"]}
except Exception as e:
s["status"] = "error"
s["message"] = f"worker exception: {e}"
print(f"[helpers_bcbs_ma] Unexpected error: {e}")
return {"status": "error", "message": s["message"]}
finally:
# Always close the disposable browser
try:
if bot:
bot.close_driver()
except Exception:
pass
s["driver"] = None
def submit_otp(sid: str, otp: str) -> Dict[str, Any]:
"""Called by /submit-otp to hand OTP to the polling loop."""
s = sessions.get(sid)
if not s:
return {"status": "error", "message": "session not found"}
if s.get("status") != "waiting_for_otp":
return {"status": "error", "message": f"session not waiting for OTP (state={s.get('status')})"}
s["otp_value"] = otp
s["last_activity"] = time.time()
try:
s["otp_event"].set()
except Exception:
pass
return {"status": "ok", "message": "otp accepted"}
def get_session_status(sid: str) -> Dict[str, Any]:
s = sessions.get(sid)
if not s:
return {"status": "not_found"}
return {
"session_id": sid,
"status": s.get("status"),
"message": s.get("message"),
"created_at": s.get("created_at"),
"last_activity": s.get("last_activity"),
"result": s.get("result") if s.get("status") == "completed" else None,
}

View File

@@ -0,0 +1,462 @@
import os
import time
import base64
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
DOWNLOAD_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "downloads", "bcbs_ma"))
def _fresh_driver() -> webdriver.Chrome:
"""Create a disposable Chrome instance for a single BCBS MA session."""
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
options = Options()
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
options.add_experimental_option("prefs", {
"download.default_directory": DOWNLOAD_DIR,
"download.prompt_for_download": False,
"plugins.always_open_pdf_externally": True,
})
headless = os.getenv("SELENIUM_HEADLESS", "false").lower() == "true"
if headless:
options.add_argument("--headless=new")
try:
from webdriver_manager.chrome import ChromeDriverManager
service = Service(ChromeDriverManager().install())
except Exception:
service = Service()
driver = webdriver.Chrome(service=service, options=options)
driver.maximize_window()
return driver
class AutomationBCBSMAEligibilityCheck:
"""
BCBS MA Provider Central eligibility check.
No persistent session — fresh Chrome every run because BCBS MA
always requires OTP on new login (the OTP prefix changes each time).
Flow: login(url) → OTP_REQUIRED → [caller polls for OTP] → submit_otp_step(otp)
→ step1() [search member] → step2() [extract + PDF]
"""
LOGIN_URL = "https://provider.bluecrossma.com/ProviderHome/portal/"
def __init__(self, data: dict):
raw = data.get("data", data)
self.member_id = raw.get("memberId", "")
self.dob = raw.get("dateOfBirth", "") # YYYY-MM-DD from frontend
self.first_name = raw.get("firstName", "")
self.last_name = raw.get("lastName", "")
self.username = raw.get("bcbsMaUsername", "")
self.password = raw.get("bcbsMaPassword", "")
self.provider_npi = raw.get("providerNpi", "")
self.driver: webdriver.Chrome | None = None
# ── Driver lifecycle ──────────────────────────────────────────────────────
def config_driver(self):
self.driver = _fresh_driver()
def close_driver(self):
try:
if self.driver:
self.driver.quit()
except Exception:
pass
self.driver = None
# ── Helpers ───────────────────────────────────────────────────────────────
def _wait(self, timeout=15):
return WebDriverWait(self.driver, timeout)
def _dob_mmddyyyy(self) -> str:
"""Convert YYYY-MM-DD → MM/DD/YYYY."""
try:
parts = self.dob.split("-")
return f"{parts[1]}/{parts[2]}/{parts[0]}"
except Exception:
return self.dob
# ── Step: Login ───────────────────────────────────────────────────────────
def login(self, url: str = "") -> str:
"""
Navigate to BCBS MA Provider Central, fill credentials, click Log in.
Returns:
"OTP_REQUIRED" redirected to MFA challenge page (/mga/sps/authsvc)
"SUCCESS" landed on dashboard without OTP
"ERROR:..." something went wrong
"""
target = url or self.LOGIN_URL
try:
print(f"[BCBS MA login] Navigating to {target}")
self.driver.get(target)
# Wait for username field: id="txtUsername0"
username_input = self._wait(20).until(
EC.presence_of_element_located((By.ID, "txtUsername0"))
)
username_input.clear()
username_input.send_keys(self.username)
print("[BCBS MA login] Username filled")
# Password field: id="txtPassword"
password_input = self._wait(10).until(
EC.presence_of_element_located((By.ID, "txtPassword"))
)
password_input.clear()
password_input.send_keys(self.password)
print("[BCBS MA login] Password filled")
# Log in button: id="ns_Z7_09ME1282N8N3B0QGV9ND6N20G2_loginSubmit"
login_btn = self._wait(10).until(
EC.element_to_be_clickable((By.ID, "ns_Z7_09ME1282N8N3B0QGV9ND6N20G2_loginSubmit"))
)
login_btn.click()
print("[BCBS MA login] Log in clicked, waiting for response...")
time.sleep(3)
return self._detect_post_login_state()
except Exception as e:
print(f"[BCBS MA login] Error: {e}")
return f"ERROR: login failed: {e}"
def _detect_post_login_state(self) -> str:
"""Check current URL/DOM to decide what happened after login."""
for _ in range(6):
time.sleep(1)
url = self.driver.current_url.lower()
print(f"[BCBS MA] post-login URL: {url[:80]}")
if "authsvc" in url or "/mga/sps/" in url:
print("[BCBS MA] OTP page detected")
return "OTP_REQUIRED"
if "providerhome" in url or "portal" in url:
# Check if we're on a real portal page (not login form)
try:
self.driver.find_element(By.XPATH,
"//*[contains(@href,'eTools') or contains(text(),'eTools') or "
"contains(@href,'eligibility') or contains(text(),'Eligibility')]"
)
print("[BCBS MA] Dashboard detected — logged in without OTP")
return "SUCCESS"
except Exception:
pass
print("[BCBS MA] Could not determine post-login state")
return "OTP_REQUIRED" # default assumption for BCBS MA
# ── Step: Submit OTP ──────────────────────────────────────────────────────
def submit_otp_step(self, otp: str) -> str:
"""
Enter the 6-digit OTP into the BCBS MA verification page and click Submit.
OTP input: id="otppswd", name="otp.user.otp"
Submit btn: id="submitButton" (starts disabled, enables after OTP typed)
Returns "SUCCESS" or "ERROR:..."
"""
try:
print(f"[BCBS MA OTP] Submitting OTP: {otp}")
# OTP input field: id="otppswd"
otp_input = self._wait(15).until(
EC.presence_of_element_located((By.ID, "otppswd"))
)
otp_input.clear()
otp_input.send_keys(otp)
print("[BCBS MA OTP] OTP entered")
# Submit button starts disabled — wait for it to become clickable
submit_btn = self._wait(10).until(
EC.element_to_be_clickable((By.ID, "submitButton"))
)
submit_btn.click()
print("[BCBS MA OTP] Submit clicked, waiting for dashboard...")
time.sleep(4)
# Wait for dashboard
for _ in range(15):
time.sleep(1)
url = self.driver.current_url.lower()
print(f"[BCBS MA OTP] URL: {url[:80]}")
if "authsvc" not in url and "/mga/sps/" not in url:
print("[BCBS MA OTP] Left OTP page — login successful")
return "SUCCESS"
return "ERROR: OTP page still visible after submission"
except Exception as e:
print(f"[BCBS MA OTP] Error: {e}")
return f"ERROR: OTP submission failed: {e}"
# ── Step 1: Navigate to ConnectCenter → New Eligibility Request ──────────
def step1(self) -> str:
"""
After OTP login:
1. Click eTools menu
2. Click ConnectCenter in the dropdown
3. Click Go Now on the ConnectCenter launch page
4. Click Continue in the popup (opens ConnectCenter in new tab)
5. Switch to new tab, click Verification
6. Click New Eligibility Request in dropdown
Returns "SUCCESS" (on Eligibility Identifier page) or "ERROR:..."
"""
try:
from selenium.webdriver.common.keys import Keys
# 1. Click eTools
print("[BCBS MA step1] Clicking eTools...")
etools = self._wait(15).until(
EC.element_to_be_clickable((By.XPATH,
"//span[text()='eTools'] | //a[.//span[text()='eTools']]"
))
)
etools.click()
print("[BCBS MA step1] eTools clicked, waiting for dropdown...")
time.sleep(2)
# 2. Click ConnectCenter link in the dropdown
print("[BCBS MA step1] Clicking ConnectCenter...")
connect_center = self._wait(15).until(
EC.element_to_be_clickable((By.XPATH,
"//a[contains(@href,'connectcenter')]"
))
)
connect_center.click()
print("[BCBS MA step1] ConnectCenter clicked, waiting for page to load...")
time.sleep(3)
# 3. Click Go Now on the ConnectCenter page
print("[BCBS MA step1] Clicking Go Now...")
go_now = self._wait(20).until(
EC.element_to_be_clickable((By.ID, "GoNow-2c338de9-6a2f-4d71-b5fb-a6e4ae14be80"))
)
go_now.click()
time.sleep(2)
print("[BCBS MA step1] Go Now clicked — popup opened, pressing Enter to continue...")
# 4. Press Enter to auto-confirm the popup
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(self.driver).send_keys(Keys.ENTER).perform()
time.sleep(4)
print("[BCBS MA step1] Enter pressed — continuing to ConnectCenter")
# 5. Switch to the new tab that ConnectCenter opened
self._wait(10).until(lambda d: len(d.window_handles) > 1)
self.driver.switch_to.window(self.driver.window_handles[-1])
print(f"[BCBS MA step1] Switched to new tab: {self.driver.current_url[:60]}")
time.sleep(3)
# 6. Click Verification menu
print("[BCBS MA step1] Clicking Verification...")
verification = self._wait(15).until(
EC.element_to_be_clickable((By.XPATH,
"//a[text()='Verification' or normalize-space(text())='Verification']"
))
)
verification.click()
time.sleep(2)
print("[BCBS MA step1] Verification clicked")
# 7. Click New Eligibility Request in dropdown
print("[BCBS MA step1] Clicking New Eligibility Request...")
new_elig = self._wait(10).until(
EC.element_to_be_clickable((By.XPATH,
"//a[@ng-click='newEligibility();']"
))
)
new_elig.click()
time.sleep(3)
print("[BCBS MA step1] New Eligibility Request clicked — on Eligibility Identifier page")
return "SUCCESS"
except Exception as e:
print(f"[BCBS MA step1] Error: {e}")
return f"ERROR: step1 failed: {e}"
# ── Step 2: Fill Eligibility Identifier form and get results ─────────────
def step2(self) -> dict:
"""
On the Eligibility Identifier page:
1. Enter provider NPI → click Find Provider
2. Select payer search option: Member ID, Subscriber Date Of Birth (value=2)
3. Select service type: Dental Care [35] (value=33)
4. Select place of service: OFFICE [11] (value=30)
5. Enter member ID
6. Enter date of birth (MM/DD/YYYY)
7. Click Submit → wait for results page → PDF
"""
from selenium.webdriver.support.ui import Select
try:
print("[BCBS MA step2] Filling Eligibility Identifier form...")
# 1. Enter provider NPI
provider_id_input = self._wait(15).until(
EC.presence_of_element_located((By.ID, "providerID"))
)
provider_id_input.clear()
provider_id_input.send_keys(self.provider_npi)
print(f"[BCBS MA step2] Provider NPI entered: {self.provider_npi}")
# Click Find Provider
find_provider = self._wait(10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@ng-click='searchProviders()']"
))
)
find_provider.click()
print("[BCBS MA step2] Find Provider clicked, waiting...")
time.sleep(3)
# 2. Payer search options → value="2": Member ID, Subscriber Date Of Birth
payer_select = self._wait(10).until(
EC.presence_of_element_located((By.ID, "payerSearchOptions"))
)
Select(payer_select).select_by_value("2")
print("[BCBS MA step2] Payer search option selected: Member ID + DOB")
time.sleep(1)
# 3. Service type → value="33": Dental Care [35]
service_select = self._wait(10).until(
EC.presence_of_element_located((By.ID, "serviceType"))
)
Select(service_select).select_by_value("33")
print("[BCBS MA step2] Service type selected: Dental Care [35]")
# 4. Place of service → value="30": OFFICE [11]
pos_select = self._wait(10).until(
EC.presence_of_element_located((By.ID, "placeOfService"))
)
Select(pos_select).select_by_value("30")
print("[BCBS MA step2] Place of service selected: OFFICE [11]")
# 5. Member ID — field name is subscriberMedicaidID
member_input = self._wait(10).until(
EC.presence_of_element_located((By.XPATH,
"//input[@name='subscriberMedicaidID' or @id='subscriberMedicaidID']"
))
)
member_input.clear()
member_input.send_keys(self.member_id)
print(f"[BCBS MA step2] Member ID entered: {self.member_id}")
# 6. Date of birth — id="subscriberDateOfBirth", placeholder="mm/dd/yyyy"
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
dob_formatted = self._dob_mmddyyyy()
dob_input = self._wait(10).until(
EC.presence_of_element_located((By.ID, "subscriberDateOfBirth"))
)
# Double-click to focus, then type directly via ActionChains
ActionChains(self.driver).double_click(dob_input).perform()
time.sleep(0.3)
ActionChains(self.driver).send_keys(dob_formatted).perform()
print(f"[BCBS MA step2] DOB typed: {dob_formatted}")
time.sleep(1)
# 7. Click Submit
submit_btn = self._wait(10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@ng-click='submit()' and @type='submit']"
))
)
submit_btn.click()
print("[BCBS MA step2] Submit clicked, waiting for results...")
time.sleep(5)
# Wait for results page to load
self._wait(20).until(
EC.presence_of_element_located((By.XPATH,
"//*[contains(text(),'Eligible') or contains(text(),'Not Eligible') or "
"contains(text(),'Active') or contains(text(),'Inactive') or "
"contains(text(),'Coverage') or contains(text(),'Benefit')]"
))
)
print("[BCBS MA step2] Results page loaded")
# Click Expand All to expand all eligibility sections before PDFing
try:
expand_all = self._wait(10).until(
EC.element_to_be_clickable((By.XPATH,
"//h6[normalize-space(text())='Expand All']"
))
)
expand_all.click()
print("[BCBS MA step2] Expand All clicked, waiting for sections to expand...")
time.sleep(3)
except Exception as e:
print(f"[BCBS MA step2] Expand All not found or failed: {e}")
# Extract eligibility status
page_text = self.driver.find_element(By.TAG_NAME, "body").text
text_lower = page_text.lower()
if "not eligible" in text_lower or "inactive" in text_lower or "terminated" in text_lower:
eligibility = "Not Eligible"
elif "eligible" in text_lower or "active" in text_lower or "covered" in text_lower:
eligibility = "Eligible"
else:
eligibility = "Unknown"
patient_name = f"{self.first_name} {self.last_name}".strip()
print(f"[BCBS MA step2] Eligibility: {eligibility}")
# PDF the results page via CDP
pdf_base64 = ""
pdf_path = None
try:
result = self.driver.execute_cdp_cmd("Page.printToPDF", {
"printBackground": True,
"paperWidth": 8.5,
"paperHeight": 11,
"marginTop": 0.5,
"marginBottom": 0.5,
"marginLeft": 0.5,
"marginRight": 0.5,
})
pdf_data = result.get("data", "")
if pdf_data:
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
filename = f"bcbs_ma_eligibility_{self.member_id}_{int(time.time())}.pdf"
pdf_path = os.path.join(DOWNLOAD_DIR, filename)
with open(pdf_path, "wb") as f:
f.write(base64.b64decode(pdf_data))
pdf_base64 = pdf_data
print(f"[BCBS MA step2] PDF saved: {pdf_path}")
except Exception as e:
print(f"[BCBS MA step2] PDF generation failed: {e}")
return {
"status": "success",
"eligibility": eligibility,
"patientName": patient_name,
"memberId": self.member_id,
"insurerName": "BCBS MA",
"pdfBase64": pdf_base64,
"pdf_path": pdf_path,
}
except Exception as e:
print(f"[BCBS MA step2] Error: {e}")
return {"status": "error", "message": f"step2 failed: {e}"}