feat: MetLife Dental eligibility check with persistent session

Mirrors the DDMA flow: persistent Chrome profile + credential-change logout,
PingOne login with email MFA selection and OTP from app or browser,
Eligibility & Plan Details search (masked Subscriber ID/SSN), family member
picked by DOB and re-verified on the plan details page, PDF saved to the
patient's Eligibility folder and status set ACTIVE/INACTIVE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 00:29:28 -04:00
co-authored by Claude Opus 5.5
parent 143319909f
commit db7ea8ef2f
17 changed files with 2475 additions and 12 deletions
+57
View File
@@ -31,6 +31,7 @@ import helpers_tuftssco_claim as htuftssco_claim
import helpers_tuftssco_preauth as htuftssco_preauth
import helpers_uniteducard_claim as huniteducard_claim
import helpers_bcbs_ma_eligibility as hbcbs_ma
import helpers_metlife_eligibility as hmetlife
# Import startup session-clear functions
from ddma_browser_manager import clear_ddma_session_on_startup
@@ -40,6 +41,7 @@ from unitedsco_browser_manager import clear_unitedsco_session_on_startup
from uniteducard_browser_manager import clear_uniteducard_session_on_startup
from dentaquest_browser_manager import clear_dentaquest_session_on_startup
from cca_browser_manager import clear_cca_session_on_startup
from metlife_browser_manager import clear_metlife_session_on_startup
from dotenv import load_dotenv
load_dotenv()
@@ -55,6 +57,7 @@ clear_unitedsco_session_on_startup()
clear_uniteducard_session_on_startup()
clear_dentaquest_session_on_startup()
clear_cca_session_on_startup()
clear_metlife_session_on_startup()
print("=" * 50)
print("SESSION CLEAR COMPLETE")
print("=" * 50)
@@ -423,6 +426,46 @@ async def ddma_eligibility(request: Request):
return {"status": "started", "session_id": sid}
# Endpoint: MetLife Dental eligibility (background, OTP, persistent session)
async def _metlife_worker_wrapper(sid: str, data: dict, url: str):
"""Background worker for MetLife eligibility — acquires semaphore, updates counters."""
global active_jobs, waiting_jobs
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try:
await hmetlife.start_metlife_run(sid, data, url)
finally:
async with lock:
active_jobs -= 1
@app.post("/metlife-eligibility")
async def metlife_eligibility(request: Request):
"""
Starts a MetLife eligibility session in the background.
Body: { "data": { ... } }
Returns: { status: "started", session_id: "<uuid>" }
"""
global waiting_jobs
body = await request.json()
data = body.get("data", {})
sid = hmetlife.make_session_entry()
hmetlife.sessions[sid]["type"] = "metlife_eligibility"
hmetlife.sessions[sid]["last_activity"] = time.time()
async with lock:
waiting_jobs += 1
asyncio.create_task(_metlife_worker_wrapper(sid, data, url="https://dentalprovider.metlife.com/home"))
return {"status": "started", "session_id": sid}
async def _deltaindiv_worker_wrapper(sid: str, data: dict, url: str):
"""Background worker for Delta Indiv eligibility — acquires semaphore, updates counters."""
global active_jobs, waiting_jobs
@@ -1126,6 +1169,8 @@ async def submit_otp(request: Request):
res = huniteducard_claim.submit_otp(sid, otp)
elif sid in hbcbs_ma.sessions:
res = hbcbs_ma.submit_otp(sid, otp)
elif sid in hmetlife.sessions:
res = hmetlife.submit_otp(sid, otp)
else:
raise HTTPException(status_code=404, detail="session not found")
@@ -1171,6 +1216,8 @@ async def session_status(sid: str):
s = huniteducard_claim.get_session_status(sid)
elif sid in hbcbs_ma.sessions:
s = hbcbs_ma.get_session_status(sid)
elif sid in hmetlife.sessions:
s = hmetlife.get_session_status(sid)
else:
s = {"status": "not_found"}
if s.get("status") == "not_found":
@@ -1240,6 +1287,16 @@ async def clear_cca_session_endpoint():
return {"status": "error", "message": str(e)}
@app.post("/clear-metlife-session")
async def clear_metlife_session_endpoint():
"""Clears the MetLife browser session. Call when credentials are deleted or changed."""
try:
clear_metlife_session_on_startup()
return {"status": "success", "message": "MetLife session cleared"}
except Exception as e:
return {"status": "error", "message": str(e)}
# ✅ Health Check Endpoint
@app.get("/")
async def health_check():