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)