From 0628f9f7fc8f8513ebf2cfd0e18780d743d888a3 Mon Sep 17 00:00:00 2001 From: Gitead Date: Thu, 14 May 2026 11:33:02 -0400 Subject: [PATCH] feat: add member details PDF step to MH history and CMSP flows After clicking the member ID link, print the member details page via CDP before navigating to service history. Adds member details as a panel in the side-by-side PDF viewer: MH History shows 3 panels (eligibility, member details, service history); CMSP shows 4 panels (eligibility, member details, service history, accumulator). Co-Authored-By: Claude Sonnet 4.6 --- ...mspEligibilityHistoryRemainingProcessor.ts | 8 +++++ .../processors/eligibilityHistoryProcessor.ts | 10 +++++- .../src/pages/insurance-status-page.tsx | 36 +++++++++++++++---- ..._eligibilityHistoryRemainingCheckWorker.py | 31 ++++++++++++++-- ...lenium_MH_eligibilityHistoryCheckWorker.py | 31 +++++++++++++--- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts b/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts index ca8905c7..c1c82336 100644 --- a/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts +++ b/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts @@ -28,6 +28,8 @@ export interface CmspEligibilityHistoryRemainingProcessorResult { pdfUploadStatus?: string; pdfFileId?: number | null; pdfFilename?: string | null; + memberDetailsPdfFileId?: number | null; + memberDetailsPdfFilename?: string | null; historyPdfFileId?: number | null; historyPdfFilename?: string | null; accumulatorPdfFileId?: number | null; @@ -135,6 +137,12 @@ export async function runCmspEligibilityHistoryRemainingProcessor( outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium"; } + // Save member details PDF + if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) { + outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path); + outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path); + } + // Save history PDF if (seleniumResult.history_pdf_path?.endsWith(".pdf")) { outputResult.historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path); diff --git a/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts b/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts index 36ad9f3a..2e4ede8b 100644 --- a/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts +++ b/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts @@ -27,8 +27,10 @@ export interface EligibilityHistoryProcessorResult { patientUpdateStatus?: string; pdfUploadStatus?: string; pdfFileId?: number | null; - historyPdfFileId?: number | null; pdfFilename?: string | null; + memberDetailsPdfFileId?: number | null; + memberDetailsPdfFilename?: string | null; + historyPdfFileId?: number | null; historyPdfFilename?: string | null; } @@ -137,6 +139,12 @@ export async function runEligibilityHistoryProcessor( } outputResult.pdfFileId = eligibilityPdfFileId; + // Save member details PDF + if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) { + outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path); + outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path); + } + // Save history PDF let historyPdfFileId: number | null = null; if (seleniumResult.history_pdf_path?.endsWith(".pdf")) { diff --git a/apps/Frontend/src/pages/insurance-status-page.tsx b/apps/Frontend/src/pages/insurance-status-page.tsx index 3c0ff8c0..77f77183 100755 --- a/apps/Frontend/src/pages/insurance-status-page.tsx +++ b/apps/Frontend/src/pages/insurance-status-page.tsx @@ -98,13 +98,17 @@ export default function InsuranceStatusPage() { const [dualPreviewOpen, setDualPreviewOpen] = useState(false); const [dualEligibilityPdfId, setDualEligibilityPdfId] = useState(null); const [dualEligibilityFilename, setDualEligibilityFilename] = useState(null); + const [dualMemberDetailsPdfId, setDualMemberDetailsPdfId] = useState(null); + const [dualMemberDetailsFilename, setDualMemberDetailsFilename] = useState(null); const [dualHistoryPdfId, setDualHistoryPdfId] = useState(null); const [dualHistoryFilename, setDualHistoryFilename] = useState(null); - // Triple PDF modal state (used by CMSP Eligibility & History & Remaining) + // CMSP PDF modal state (used by CMSP Eligibility & History & Remaining) const [cmspPreviewOpen, setCmspPreviewOpen] = useState(false); const [cmspEligibilityPdfId, setCmspEligibilityPdfId] = useState(null); const [cmspEligibilityFilename, setCmspEligibilityFilename] = useState(null); + const [cmspMemberDetailsPdfId, setCmspMemberDetailsPdfId] = useState(null); + const [cmspMemberDetailsFilename, setCmspMemberDetailsFilename] = useState(null); const [cmspHistoryPdfId, setCmspHistoryPdfId] = useState(null); const [cmspHistoryFilename, setCmspHistoryFilename] = useState(null); const [cmspAccumulatorPdfId, setCmspAccumulatorPdfId] = useState(null); @@ -469,10 +473,12 @@ export default function InsuranceStatusPage() { setSelectedPatient(null); await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE }); - // Open both PDFs side by side in the dual modal - if (jobResult.pdfFileId || jobResult.historyPdfFileId) { + // Open all PDFs side by side in the modal + if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId) { setDualEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null); setDualEligibilityFilename(jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`); + setDualMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null); + setDualMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `eligibility_member_details_${memberId}.pdf`); setDualHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null); setDualHistoryFilename(jobResult.historyPdfFilename ?? `eligibility_history_${memberId}.pdf`); setDualPreviewOpen(true); @@ -548,10 +554,12 @@ export default function InsuranceStatusPage() { setSelectedPatient(null); await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE }); - // Open 3-panel modal - if (jobResult.pdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) { + // Open 4-panel modal + if (jobResult.pdfFileId || jobResult.memberDetailsPdfFileId || jobResult.historyPdfFileId || jobResult.accumulatorPdfFileId) { setCmspEligibilityPdfId(jobResult.pdfFileId ? Number(jobResult.pdfFileId) : null); setCmspEligibilityFilename(jobResult.pdfFilename ?? `cmsp_eligibility_${memberId}.pdf`); + setCmspMemberDetailsPdfId(jobResult.memberDetailsPdfFileId ? Number(jobResult.memberDetailsPdfFileId) : null); + setCmspMemberDetailsFilename(jobResult.memberDetailsPdfFilename ?? `cmsp_member_details_${memberId}.pdf`); setCmspHistoryPdfId(jobResult.historyPdfFileId ? Number(jobResult.historyPdfFileId) : null); setCmspHistoryFilename(jobResult.historyPdfFilename ?? `cmsp_history_${memberId}.pdf`); setCmspAccumulatorPdfId(jobResult.accumulatorPdfFileId ? Number(jobResult.accumulatorPdfFileId) : null); @@ -1159,13 +1167,15 @@ export default function InsuranceStatusPage() { autoDownload /> - {/* Triple PDF modal for CMSP — eligibility, history, accumulator side by side */} + {/* 4-panel modal for CMSP — eligibility, member details, history, accumulator */} { setCmspPreviewOpen(false); setCmspEligibilityPdfId(null); setCmspEligibilityFilename(null); + setCmspMemberDetailsPdfId(null); + setCmspMemberDetailsFilename(null); setCmspHistoryPdfId(null); setCmspHistoryFilename(null); setCmspAccumulatorPdfId(null); @@ -1179,6 +1189,11 @@ export default function InsuranceStatusPage() { label: "Eligibility", autoDownload: true, }, + { + pdfId: cmspMemberDetailsPdfId, + fallbackFilename: cmspMemberDetailsFilename, + label: "Member Details", + }, { pdfId: cmspHistoryPdfId, fallbackFilename: cmspHistoryFilename, @@ -1192,13 +1207,15 @@ export default function InsuranceStatusPage() { ]} /> - {/* Dual PDF modal for MH Eligibility & History — both PDFs side by side */} + {/* 3-panel modal for MH Eligibility & History */} { setDualPreviewOpen(false); setDualEligibilityPdfId(null); setDualEligibilityFilename(null); + setDualMemberDetailsPdfId(null); + setDualMemberDetailsFilename(null); setDualHistoryPdfId(null); setDualHistoryFilename(null); }} @@ -1210,6 +1227,11 @@ export default function InsuranceStatusPage() { label: "Eligibility", autoDownload: true, }, + { + pdfId: dualMemberDetailsPdfId, + fallbackFilename: dualMemberDetailsFilename, + label: "Member Details", + }, { pdfId: dualHistoryPdfId, fallbackFilename: dualHistoryFilename, diff --git a/apps/SeleniumService/selenium_CMSP_eligibilityHistoryRemainingCheckWorker.py b/apps/SeleniumService/selenium_CMSP_eligibilityHistoryRemainingCheckWorker.py index a31efabb..ce83f514 100644 --- a/apps/SeleniumService/selenium_CMSP_eligibilityHistoryRemainingCheckWorker.py +++ b/apps/SeleniumService/selenium_CMSP_eligibilityHistoryRemainingCheckWorker.py @@ -275,6 +275,27 @@ class AutomationCMSPEligibilityHistoryRemainingCheck: print(f"[step3] FAILED at substep='{substep}': {e}") return f"ERROR:STEP3:{substep}" + # ── step 3b — print member details page as PDF via CDP ─────────────────────── + + def step3b_member_details_pdf(self): + wait = WebDriverWait(self.driver, 30) + try: + wait.until(lambda d: d.execute_script("return document.readyState") == "complete") + time.sleep(2) + + safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.") + pdf_filename = f"cmsp_member_details_{safe_member}.pdf" + pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True}) + pdf_bytes = base64.b64decode(pdf_data["data"]) + pdf_path = os.path.join(self.download_dir, pdf_filename) + with open(pdf_path, "wb") as f: + f.write(pdf_bytes) + print("Member details PDF saved at:", pdf_path) + return pdf_path + except Exception as e: + print(f"[step3b_member_details_pdf] failed: {e}") + return None + # ── step 4 — click "VIEW SERVICE HISTORY" → service history page ───────────── def step4_view_service_history(self): @@ -409,10 +430,14 @@ class AutomationCMSPEligibilityHistoryRemainingCheck: return {"status": "partial", "message": step3_result, "pdf_path": eligibility_pdf_path, "file_type": "pdf"} + member_details_pdf_path = self.step3b_member_details_pdf() + step4_result = self.step4_view_service_history() if step4_result.startswith("ERROR"): return {"status": "partial", "message": step4_result, - "pdf_path": eligibility_pdf_path, "file_type": "pdf"} + "pdf_path": eligibility_pdf_path, + "member_details_pdf_path": member_details_pdf_path, + "file_type": "pdf"} history_pdf_path = self.step5_history_pdf() @@ -420,6 +445,7 @@ class AutomationCMSPEligibilityHistoryRemainingCheck: if step6_result.startswith("ERROR"): return {"status": "partial", "message": step6_result, "pdf_path": eligibility_pdf_path, + "member_details_pdf_path": member_details_pdf_path, "history_pdf_path": history_pdf_path, "file_type": "pdf"} accumulator_pdf_path = self.step7_accumulator_pdf() @@ -427,10 +453,11 @@ class AutomationCMSPEligibilityHistoryRemainingCheck: result = { "status": "success", "pdf_path": eligibility_pdf_path, + "member_details_pdf_path": member_details_pdf_path, "history_pdf_path": history_pdf_path, "accumulator_pdf_path": accumulator_pdf_path, "file_type": "pdf", - "message": "Eligibility, service history, and accumulator PDFs captured", + "message": "Eligibility, member details, service history, and accumulator PDFs captured", } if self.extracted_data: result.update(self.extracted_data) diff --git a/apps/SeleniumService/selenium_MH_eligibilityHistoryCheckWorker.py b/apps/SeleniumService/selenium_MH_eligibilityHistoryCheckWorker.py index a13c0411..c304f25a 100644 --- a/apps/SeleniumService/selenium_MH_eligibilityHistoryCheckWorker.py +++ b/apps/SeleniumService/selenium_MH_eligibilityHistoryCheckWorker.py @@ -281,7 +281,6 @@ class AutomationMassHealthEligibilityHistoryCheck: wait = WebDriverWait(self.driver, 30) substep = "init" try: - # Primary: ng-bind="member.memberNumber" is the stable Angular binding on the link substep = "ng_bind_link" member_link = wait.until( EC.element_to_be_clickable( @@ -296,6 +295,27 @@ class AutomationMassHealthEligibilityHistoryCheck: print(f"[step3] FAILED at substep='{substep}': {e}") return f"ERROR:STEP3:{substep}" + # ── step 3b — print member details page as PDF via CDP ─────────────────────── + + def step3b_member_details_pdf(self): + wait = WebDriverWait(self.driver, 30) + try: + wait.until(lambda d: d.execute_script("return document.readyState") == "complete") + time.sleep(2) + + safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.") + pdf_filename = f"eligibility_member_details_{safe_member}.pdf" + pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {"printBackground": True}) + pdf_bytes = base64.b64decode(pdf_data["data"]) + pdf_path = os.path.join(self.download_dir, pdf_filename) + with open(pdf_path, "wb") as f: + f.write(pdf_bytes) + print("Member details PDF saved at:", pdf_path) + return pdf_path + except Exception as e: + print(f"[step3b_member_details_pdf] failed: {e}") + return None + # ── step 4 — click "View Service History" on member details page ───────────── def step4_view_service_history(self): @@ -382,10 +402,12 @@ class AutomationMassHealthEligibilityHistoryCheck: "status": "partial", "message": step3_result, "pdf_path": eligibility_pdf_path, - "history_pdf_path": None, "file_type": "pdf", } + # Print member details page + member_details_pdf_path = self.step3b_member_details_pdf() + # Click "View Service History" step4_result = self.step4_view_service_history() if step4_result.startswith("ERROR"): @@ -393,7 +415,7 @@ class AutomationMassHealthEligibilityHistoryCheck: "status": "partial", "message": step4_result, "pdf_path": eligibility_pdf_path, - "history_pdf_path": None, + "member_details_pdf_path": member_details_pdf_path, "file_type": "pdf", } @@ -403,9 +425,10 @@ class AutomationMassHealthEligibilityHistoryCheck: result = { "status": "success", "pdf_path": eligibility_pdf_path, + "member_details_pdf_path": member_details_pdf_path, "history_pdf_path": history_pdf_path, "file_type": "pdf", - "message": "Eligibility and service history PDFs captured successfully", + "message": "Eligibility, member details, and service history PDFs captured", } if self.extracted_data: result.update(self.extracted_data)