Delta Dental MA moved their provider login from providers.deltadentalma.com/onboarding to www.deltadentalma.com/sign-in with new form field selectors, and moved eligibility search behind a "Group/Health Connector" search page keyed by subscriber ID + birthdate instead of the old member-search form. Updates login, OTP/MFA handling, and member search/selection across the eligibility and claim DDMA workers and their session helpers.
1059 lines
34 KiB
Python
Executable File
1059 lines
34 KiB
Python
Executable File
from fastapi import FastAPI, Request, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import RedirectResponse, JSONResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
import uvicorn
|
|
import asyncio
|
|
from selenium_claimSubmitWorker import AutomationMassHealthClaimsLogin
|
|
from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck
|
|
from selenium_MH_eligibilityHistoryCheckWorker import AutomationMassHealthEligibilityHistoryCheck
|
|
from selenium_CMSP_eligibilityHistoryRemainingCheckWorker import AutomationCMSPEligibilityHistoryRemainingCheck
|
|
from selenium_claimStatusCheckWorker import AutomationMassHealthClaimStatusCheck
|
|
from selenium_preAuthWorker import AutomationMassHealthPreAuth
|
|
from selenium_MHSinglePaymentCheckWorker import AutomationMassHealthSinglePaymentCheck
|
|
from selenium_MHBatchPaymentCheckWorker import AutomationMassHealthBatchPaymentCheck
|
|
import os
|
|
import time
|
|
import helpers_ddma_eligibility as hddma
|
|
import helpers_deltains_eligibility as hdeltains
|
|
import helpers_unitedsco_eligibility as hunitedsco
|
|
import helpers_dentaquest_eligibility as hdentaquest
|
|
import helpers_cca_eligibility as hcca
|
|
import helpers_cca_claim as hcca_claim
|
|
import helpers_cca_preauth as hcca_preauth
|
|
import helpers_ddma_claim as hddma_claim
|
|
import helpers_uniteddh_claim as huniteddh_claim
|
|
import helpers_uniteddh_preauth as huniteddh_preauth
|
|
import helpers_tuftssco_claim as htuftssco_claim
|
|
import helpers_tuftssco_preauth as htuftssco_preauth
|
|
import helpers_bcbs_ma_eligibility as hbcbs_ma
|
|
|
|
# Import startup session-clear functions
|
|
from ddma_browser_manager import clear_ddma_session_on_startup
|
|
from deltains_browser_manager import clear_deltains_session_on_startup
|
|
from unitedsco_browser_manager import clear_unitedsco_session_on_startup
|
|
from dentaquest_browser_manager import clear_dentaquest_session_on_startup
|
|
from cca_browser_manager import clear_cca_session_on_startup
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
# Clear sessions on startup so fresh login is required after PC restart.
|
|
print("=" * 50)
|
|
print("SELENIUM AGENT STARTING - CLEARING SESSIONS")
|
|
print("=" * 50)
|
|
clear_ddma_session_on_startup()
|
|
clear_deltains_session_on_startup()
|
|
clear_unitedsco_session_on_startup()
|
|
clear_dentaquest_session_on_startup()
|
|
clear_cca_session_on_startup()
|
|
print("=" * 50)
|
|
print("SESSION CLEAR COMPLETE")
|
|
print("=" * 50)
|
|
|
|
app = FastAPI()
|
|
|
|
# Allow 1 selenium session at a time
|
|
semaphore = asyncio.Semaphore(1)
|
|
|
|
# Manual counters to track active & queued jobs
|
|
active_jobs = 0
|
|
waiting_jobs = 0
|
|
lock = asyncio.Lock() # To safely update counters
|
|
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"], # Replace with your frontend domain for security
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Mount static files for serving PDFs (must be after middleware)
|
|
DOWNLOAD_DIR = os.path.join(os.path.dirname(__file__), "downloads")
|
|
os.makedirs(DOWNLOAD_DIR, exist_ok=True)
|
|
app.mount("/downloads", StaticFiles(directory=DOWNLOAD_DIR), name="downloads")
|
|
|
|
# Endpoint: 1 — Start the automation of submitting Claim.
|
|
@app.post("/claimsubmit")
|
|
async def start_workflow(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
|
|
try:
|
|
bot = AutomationMassHealthClaimsLogin(data)
|
|
# result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
result = bot.run()
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
# Convert pdf_path to pdf_url
|
|
if result.get("pdf_path"):
|
|
filename = os.path.basename(result["pdf_path"])
|
|
port = os.getenv("PORT", "5002")
|
|
url_host = os.getenv("HOST", "localhost")
|
|
result["pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
print(f"DEBUG: Generated pdf_url = {result['pdf_url']}")
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 2 — Start the automation of cheking eligibility
|
|
@app.post("/eligibility-check")
|
|
async def start_workflow(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthEligibilityCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
# Convert pdf_path to pdf_url
|
|
if result.get("pdf_path"):
|
|
filename = os.path.basename(result["pdf_path"])
|
|
port = os.getenv("PORT", "5002")
|
|
url_host = os.getenv("HOST", "localhost")
|
|
result["pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
print(f"DEBUG: Generated pdf_url = {result['pdf_url']}")
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 2a — MH Eligibility + Service History
|
|
@app.post("/mh-eligibility-history-check")
|
|
async def mh_eligibility_history_check(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthEligibilityHistoryCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") == "error":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
port = os.getenv("PORT", "5002")
|
|
url_host = os.getenv("HOST", "localhost")
|
|
|
|
if result.get("pdf_path"):
|
|
filename = os.path.basename(result["pdf_path"])
|
|
result["pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
|
|
if result.get("history_pdf_path"):
|
|
filename = os.path.basename(result["history_pdf_path"])
|
|
result["history_pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 2b — CMSP Eligibility + Service History + Accumulator (Remaining)
|
|
@app.post("/cmsp-eligibility-history-remaining-check")
|
|
async def cmsp_eligibility_history_remaining_check(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationCMSPEligibilityHistoryRemainingCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") == "error":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
port = os.getenv("PORT", "5002")
|
|
url_host = os.getenv("HOST", "localhost")
|
|
|
|
for key, url_key in [
|
|
("pdf_path", "pdf_url"),
|
|
("history_pdf_path", "history_pdf_url"),
|
|
("accumulator_pdf_path", "accumulator_pdf_url"),
|
|
]:
|
|
if result.get(key):
|
|
filename = os.path.basename(result[key])
|
|
result[url_key] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 2.1 — Start the automation for Claims login (open browser and log in)
|
|
@app.post("/claims-login")
|
|
async def start_claims_login(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthClaimsLogin(data)
|
|
result = bot.run()
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 3 — Start the automation of cheking claim status
|
|
@app.post("/claim-status-check")
|
|
async def start_workflow(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthClaimStatusCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint: 5 — Check MassHealth payment for a given claim number
|
|
@app.post("/mh-payment-check")
|
|
async def mh_payment_check(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthSinglePaymentCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
# Endpoint: 6 — Check MassHealth payments in batch by date range
|
|
@app.post("/mh-batch-payment-check")
|
|
async def mh_batch_payment_check(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthBatchPaymentCheck(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
# Endpoint: 4 — Start the automation of cheking claim pre auth
|
|
@app.post("/claim-pre-auth")
|
|
async def start_workflow(request: Request):
|
|
global active_jobs, waiting_jobs
|
|
data = await request.json()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
bot = AutomationMassHealthPreAuth(data)
|
|
result = bot.main_workflow("https://provider.masshealth-dental.org/mh_provider_login")
|
|
|
|
if result.get("status") != "success":
|
|
return {"status": "error", "message": result.get("message")}
|
|
|
|
# Convert pdf_path to pdf_url so the frontend can fetch it
|
|
if result.get("pdf_path"):
|
|
filename = os.path.basename(result["pdf_path"])
|
|
port = os.getenv("PORT", "5002")
|
|
url_host = os.getenv("HOST", "localhost")
|
|
result["pdf_url"] = f"http://{url_host}:{port}/downloads/{filename}"
|
|
print(f"DEBUG: Generated pdf_url = {result['pdf_url']}")
|
|
|
|
return result
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
# Endpoint:5 - DDMA eligibility (background, OTP)
|
|
|
|
async def _ddma_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""
|
|
Background worker that:
|
|
- acquires semaphore (to keep 1 selenium at a time),
|
|
- updates active/queued counters,
|
|
- runs the DDMA flow via helpers.start_ddma_run.
|
|
"""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hddma.start_ddma_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/ddma-eligibility")
|
|
async def ddma_eligibility(request: Request):
|
|
"""
|
|
Starts a DDMA eligibility session in the background.
|
|
Body: { "data": { ... }, "url"?: string }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
data = body.get("data", {})
|
|
|
|
# create session
|
|
sid = hddma.make_session_entry()
|
|
hddma.sessions[sid]["type"] = "ddma_eligibility"
|
|
hddma.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
# run in background (queued under semaphore)
|
|
asyncio.create_task(_ddma_worker_wrapper(sid, data, url="https://www.deltadentalma.com/sign-in"))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _deltains_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for DeltaIns — acquires semaphore, updates counters."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hdeltains.start_deltains_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/deltains-eligibility")
|
|
async def deltains_eligibility(request: Request):
|
|
"""
|
|
Starts a DeltaIns 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 = hdeltains.make_session_entry()
|
|
hdeltains.sessions[sid]["type"] = "deltains_eligibility"
|
|
hdeltains.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_deltains_worker_wrapper(
|
|
sid, data,
|
|
url="https://www.deltadentalins.com/ciam/login?TARGET=%2Fprovider-tools%2Fv2"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _unitedsco_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for UnitedSCO — acquires semaphore, updates counters."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hunitedsco.start_unitedsco_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/unitedsco-eligibility")
|
|
async def unitedsco_eligibility(request: Request):
|
|
"""
|
|
Starts a UnitedSCO 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 = hunitedsco.make_session_entry()
|
|
hunitedsco.sessions[sid]["type"] = "unitedsco_eligibility"
|
|
hunitedsco.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_unitedsco_worker_wrapper(
|
|
sid, data,
|
|
url="https://app.dentalhub.com/app/login"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _dentaquest_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for DentaQuest (Tufts SCO) — acquires semaphore, updates counters."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hdentaquest.start_dentaquest_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/dentaquest-eligibility")
|
|
async def dentaquest_eligibility(request: Request):
|
|
"""
|
|
Starts a DentaQuest (Tufts SCO) 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 = hdentaquest.make_session_entry()
|
|
hdentaquest.sessions[sid]["type"] = "dentaquest_eligibility"
|
|
hdentaquest.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_dentaquest_worker_wrapper(
|
|
sid, data,
|
|
url="https://providers.dentaquest.com/"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _cca_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for CCA — acquires semaphore, updates counters. No OTP."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hcca.start_cca_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/cca-eligibility")
|
|
async def cca_eligibility(request: Request):
|
|
"""
|
|
Starts a CCA eligibility session in the background (no OTP).
|
|
Body: { "data": { ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
data = body.get("data", {})
|
|
|
|
sid = hcca.make_session_entry()
|
|
hcca.sessions[sid]["type"] = "cca_eligibility"
|
|
hcca.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_cca_worker_wrapper(
|
|
sid, data,
|
|
url="https://pwp.sciondental.com/PWP/Landing"
|
|
))
|
|
|
|
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
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hcca_claim.start_cca_claim_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/cca-claim")
|
|
async def cca_claim(request: Request):
|
|
"""
|
|
Starts a CCA claim submission session in the background.
|
|
Logs in, navigates Claims > Submit Claims, opens claim entry page.
|
|
Body: { "claim": { "cca_username": "...", "cca_password": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = hcca_claim.make_session_entry()
|
|
hcca_claim.sessions[sid]["type"] = "cca_claim"
|
|
hcca_claim.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_cca_claim_worker_wrapper(
|
|
sid, body,
|
|
url="https://pwp.sciondental.com/PWP/Landing"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _ddma_claim_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for DDMA claim submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hddma_claim.start_ddma_claim_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/ddma-claim")
|
|
async def ddma_claim(request: Request):
|
|
"""
|
|
Starts a DDMA claim submission session in the background.
|
|
Logs in, searches patient, opens Member Information page, clicks Create claim,
|
|
fills service date and procedure code.
|
|
Body: { "claim": { "massddmaUsername": "...", "massddmaPassword": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = hddma_claim.make_session_entry()
|
|
hddma_claim.sessions[sid]["type"] = "ddma_claim"
|
|
hddma_claim.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_ddma_claim_worker_wrapper(
|
|
sid, body,
|
|
url="https://www.deltadentalma.com/sign-in"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _uniteddh_claim_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for United/DentalHub claim submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await huniteddh_claim.start_uniteddh_claim_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/uniteddh-claim")
|
|
async def uniteddh_claim(request: Request):
|
|
"""
|
|
Starts a United/DentalHub claim submission session in the background.
|
|
Logs in, searches patient, opens Member Information page, clicks Create claim,
|
|
fills service date and procedure codes.
|
|
Body: { "claim": { "uniteddhUsername": "...", "uniteddhPassword": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = huniteddh_claim.make_session_entry()
|
|
huniteddh_claim.sessions[sid]["type"] = "uniteddh_claim"
|
|
huniteddh_claim.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_uniteddh_claim_worker_wrapper(
|
|
sid, body,
|
|
url="https://app.dentalhub.com/app/login"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _tuftssco_claim_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for Tufts SCO (DentaQuest) claim submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await htuftssco_claim.start_tuftssco_claim_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/tuftssco-claim")
|
|
async def tuftssco_claim(request: Request):
|
|
"""
|
|
Starts a Tufts SCO (DentaQuest) claim submission session in the background.
|
|
Logs in, searches patient, opens Member Information page, clicks Create claim,
|
|
fills service date and procedure codes.
|
|
Body: { "claim": { "dentaquestUsername": "...", "dentaquestPassword": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = htuftssco_claim.make_session_entry()
|
|
htuftssco_claim.sessions[sid]["type"] = "tuftssco_claim"
|
|
htuftssco_claim.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_tuftssco_claim_worker_wrapper(
|
|
sid, body,
|
|
url="https://providers.dentaquest.com/"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _uniteddh_preauth_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for United/DentalHub pre-authorization submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await huniteddh_preauth.start_uniteddh_preauth_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/uniteddh-preauth")
|
|
async def uniteddh_preauth(request: Request):
|
|
"""
|
|
Starts a United/DentalHub pre-authorization session in the background.
|
|
Logs in (persistent session, OTP on first use), searches patient, clicks Submit
|
|
Pre-Authorization, fills the form and submits.
|
|
Body: { "claim": { "uniteddhUsername": "...", "uniteddhPassword": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = huniteddh_preauth.make_session_entry()
|
|
huniteddh_preauth.sessions[sid]["type"] = "uniteddh_preauth"
|
|
huniteddh_preauth.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_uniteddh_preauth_worker_wrapper(
|
|
sid, body,
|
|
url="https://app.dentalhub.com/app/login"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _tuftssco_preauth_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for Tufts SCO (DentaQuest) pre-authorization submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await htuftssco_preauth.start_tuftssco_preauth_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/tuftssco-preauth")
|
|
async def tuftssco_preauth(request: Request):
|
|
"""
|
|
Starts a Tufts SCO (DentaQuest) pre-authorization session in the background.
|
|
Logs in (persistent session, OTP on first use), searches patient, clicks
|
|
Create Prior Authorization, fills the form and submits.
|
|
Body: { "claim": { "dentaquestUsername": "...", "dentaquestPassword": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = htuftssco_preauth.make_session_entry()
|
|
htuftssco_preauth.sessions[sid]["type"] = "tuftssco_preauth"
|
|
htuftssco_preauth.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_tuftssco_preauth_worker_wrapper(
|
|
sid, body,
|
|
url="https://providers.dentaquest.com/"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
async def _cca_preauth_worker_wrapper(sid: str, data: dict, url: str):
|
|
"""Background worker for CCA pre-authorization submission."""
|
|
global active_jobs, waiting_jobs
|
|
async with semaphore:
|
|
async with lock:
|
|
waiting_jobs -= 1
|
|
active_jobs += 1
|
|
try:
|
|
await hcca_preauth.start_cca_preauth_run(sid, data, url)
|
|
finally:
|
|
async with lock:
|
|
active_jobs -= 1
|
|
|
|
|
|
@app.post("/cca-preauth")
|
|
async def cca_preauth(request: Request):
|
|
"""
|
|
Starts a CCA pre-authorization session in the background.
|
|
Logs in, navigates to Authorization Entry, fills the form and submits.
|
|
Body: { "claim": { "cca_username": "...", "cca_password": "...", ... } }
|
|
Returns: { status: "started", session_id: "<uuid>" }
|
|
"""
|
|
global waiting_jobs
|
|
|
|
body = await request.json()
|
|
|
|
sid = hcca_preauth.make_session_entry()
|
|
hcca_preauth.sessions[sid]["type"] = "cca_preauth"
|
|
hcca_preauth.sessions[sid]["last_activity"] = time.time()
|
|
|
|
async with lock:
|
|
waiting_jobs += 1
|
|
|
|
asyncio.create_task(_cca_preauth_worker_wrapper(
|
|
sid, body,
|
|
url="https://pwp.sciondental.com/PWP/Landing"
|
|
))
|
|
|
|
return {"status": "started", "session_id": sid}
|
|
|
|
|
|
@app.post("/submit-otp")
|
|
async def submit_otp(request: Request):
|
|
"""
|
|
Body: { "session_id": "<sid>", "otp": "123456" }
|
|
Tries each session store in order (CCA has no OTP but included for completeness).
|
|
"""
|
|
body = await request.json()
|
|
sid = body.get("session_id")
|
|
otp = body.get("otp")
|
|
if not sid or not otp:
|
|
raise HTTPException(status_code=400, detail="session_id and otp required")
|
|
|
|
# Try each session store in order
|
|
if sid in hddma.sessions:
|
|
res = hddma.submit_otp(sid, otp)
|
|
elif sid in hdeltains.sessions:
|
|
res = hdeltains.submit_otp(sid, otp)
|
|
elif sid in hunitedsco.sessions:
|
|
res = hunitedsco.submit_otp(sid, otp)
|
|
elif sid in hdentaquest.sessions:
|
|
res = hdentaquest.submit_otp(sid, otp)
|
|
elif sid in hddma_claim.sessions:
|
|
res = hddma_claim.submit_otp(sid, otp)
|
|
elif sid in huniteddh_claim.sessions:
|
|
res = huniteddh_claim.submit_otp(sid, otp)
|
|
elif sid in huniteddh_preauth.sessions:
|
|
res = huniteddh_preauth.submit_otp(sid, otp)
|
|
elif sid in htuftssco_claim.sessions:
|
|
res = htuftssco_claim.submit_otp(sid, otp)
|
|
elif sid in htuftssco_preauth.sessions:
|
|
res = htuftssco_preauth.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")
|
|
|
|
if res.get("status") == "error":
|
|
raise HTTPException(status_code=400, detail=res.get("message"))
|
|
return res
|
|
|
|
|
|
@app.get("/session/{sid}/status")
|
|
async def session_status(sid: str):
|
|
# Try each session store in order
|
|
if sid in hddma.sessions:
|
|
s = hddma.get_session_status(sid)
|
|
elif sid in hdeltains.sessions:
|
|
s = hdeltains.get_session_status(sid)
|
|
elif sid in hunitedsco.sessions:
|
|
s = hunitedsco.get_session_status(sid)
|
|
elif sid in hdentaquest.sessions:
|
|
s = hdentaquest.get_session_status(sid)
|
|
elif sid in hcca.sessions:
|
|
s = hcca.get_session_status(sid)
|
|
elif sid in hcca_claim.sessions:
|
|
s = hcca_claim.get_session_status(sid)
|
|
elif sid in hcca_preauth.sessions:
|
|
s = hcca_preauth.get_session_status(sid)
|
|
elif sid in hddma_claim.sessions:
|
|
s = hddma_claim.get_session_status(sid)
|
|
elif sid in huniteddh_claim.sessions:
|
|
s = huniteddh_claim.get_session_status(sid)
|
|
elif sid in huniteddh_preauth.sessions:
|
|
s = huniteddh_preauth.get_session_status(sid)
|
|
elif sid in htuftssco_claim.sessions:
|
|
s = htuftssco_claim.get_session_status(sid)
|
|
elif sid in htuftssco_preauth.sessions:
|
|
s = htuftssco_preauth.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":
|
|
raise HTTPException(status_code=404, detail="session not found")
|
|
return s
|
|
|
|
|
|
# ── Session management endpoints ─────────────────────────────────────────────
|
|
|
|
@app.post("/clear-ddma-session")
|
|
async def clear_ddma_session_endpoint():
|
|
"""Clears the DDMA browser session. Call when credentials are deleted or changed."""
|
|
try:
|
|
clear_ddma_session_on_startup()
|
|
return {"status": "success", "message": "DDMA session cleared"}
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
|
|
@app.post("/clear-deltains-session")
|
|
async def clear_deltains_session_endpoint():
|
|
"""Clears the DeltaIns browser session. Call when credentials are deleted or changed."""
|
|
try:
|
|
clear_deltains_session_on_startup()
|
|
return {"status": "success", "message": "DeltaIns session cleared"}
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
|
|
@app.post("/clear-unitedsco-session")
|
|
async def clear_unitedsco_session_endpoint():
|
|
"""Clears the UnitedSCO browser session. Call when credentials are deleted or changed."""
|
|
try:
|
|
clear_unitedsco_session_on_startup()
|
|
return {"status": "success", "message": "UnitedSCO session cleared"}
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
|
|
@app.post("/clear-cca-session")
|
|
async def clear_cca_session_endpoint():
|
|
"""Clears the CCA browser session. Call when credentials are deleted or changed."""
|
|
try:
|
|
clear_cca_session_on_startup()
|
|
return {"status": "success", "message": "CCA session cleared"}
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|
|
|
|
|
|
# ✅ Health Check Endpoint
|
|
@app.get("/")
|
|
async def health_check():
|
|
return {
|
|
"status": "ok",
|
|
"service": "Selenium Service",
|
|
"message": "Service is running"
|
|
}
|
|
|
|
# ✅ Status Endpoint
|
|
@app.get("/status")
|
|
async def get_status():
|
|
async with lock:
|
|
return {
|
|
"active_jobs": active_jobs,
|
|
"queued_jobs": waiting_jobs,
|
|
"status": "busy" if active_jobs > 0 or waiting_jobs > 0 else "idle"
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
host = os.getenv("HOST", "0.0.0.0") # Default to 0.0.0.0 to accept connections from all interfaces
|
|
port = int(os.getenv("PORT", "5002")) # Default to 5002
|
|
print(f"Starting Selenium service on {host}:{port}")
|
|
uvicorn.run(app, host=host, port=port)
|