- Add full DDMA claim Selenium flow (steps 1-8): search patient, open member page, create claim, fill form, attach files, next, submit, extract claim number and save confirmation PDF - Add fee schedule price-mismatch dialog for all claim buttons (MH, CCA, DDMA, United, Tufts, Save) with optional price update to JSON - Add OTP modal for DDMA claim when session expires, mirroring eligibility OTP flow - Close Chrome after claim submission via quit_driver() (session preserved in profile) - Move Map Price button between Direct Submission and procedure table, right-aligned above Billed Amount column - Add fee-schedule update-price backend route - Add DDMA claim processor with claimNumber/pdf_url result handling Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
795 lines
26 KiB
Python
Executable File
795 lines
26 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_MHPaymentCheckWorker import AutomationMassHealthPaymentCheck
|
|
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 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 = AutomationMassHealthPaymentCheck(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://providers.deltadentalma.com/onboarding/start/"))
|
|
|
|
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 _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://providers.deltadentalma.com/onboarding/start/"
|
|
))
|
|
|
|
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)
|
|
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)
|
|
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)
|