feat: DDMA claim submission with OTP, PDF, claim number extraction

- 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>
This commit is contained in:
Gitead
2026-05-24 13:35:04 -04:00
parent 5ceecbeb7f
commit cd1381e9c6
13 changed files with 2139 additions and 22 deletions

View File

@@ -19,6 +19,8 @@ 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
@@ -584,6 +586,89 @@ async def cca_claim(request: Request):
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):
"""
@@ -605,6 +690,8 @@ async def submit_otp(request: Request):
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")
@@ -628,6 +715,10 @@ async def session_status(sid: str):
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":