feat: add Remaining/Third Party columns, toggle PDF viewer, retire CMSP as separate insurance type

- Add remainingBenefit and thirdPartyInsurance columns to Patient; extract both
  from the MassHealth accumulator/member-detail pages during the eligibility
  check and surface them in the eligibility table
- Add an updated-date display and a toggle to suppress the post-check PDF
  viewer popup (default off) on the insurance eligibility page
- Rename CMSP-prefixed PDF filenames and UI copy to MH, since CMSP and MH now
  share the same worker
- Fold CMSP into MassHealth/MH across patient/claim insurance-type dropdowns
  and the chatbot's eligibility routing, since they're the same insurance
- Remove auto-download of saved eligibility PDFs; downloading is now manual

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 22:32:43 -04:00
co-authored by Claude Sonnet 5
parent f8235787db
commit 52ddfd9f57
96 changed files with 561 additions and 160 deletions
@@ -234,7 +234,7 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
print("No new tab for eligibility; printing current page directly")
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_eligibility_{safe_member}.pdf"
pdf_filename = f"mh_eligibility_{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)
@@ -279,14 +279,57 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
# ── step 3b — print member details page as PDF via CDP ───────────────────────
def extract_third_party_insurance(self):
"""Reads the "Third Party Liability" table (Policy Number | Coverage Type |
Effective Date | Termination Date | Insurer Name | Insurer Payment Order)
and returns the Insurer Name(s), or None if the table has no rows
(i.e. the member has no third-party/2nd insurance).
"""
try:
tables = self.driver.find_elements(By.TAG_NAME, "table")
for table in tables:
rows = table.find_elements(By.TAG_NAME, "tr")
if not rows:
continue
header_cells = [
c.text.strip().lower()
for c in (rows[0].find_elements(By.TAG_NAME, "th") or rows[0].find_elements(By.TAG_NAME, "td"))
]
if "insurer name" not in header_cells:
continue
insurer_idx = header_cells.index("insurer name")
insurers = []
for row in rows[1:]:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) <= insurer_idx:
continue
name = cells[insurer_idx].text.strip()
if name:
insurers.append(name)
third_party = ", ".join(insurers) if insurers else None
self.extracted_data["third_party_insurance"] = third_party
print(f"[extract_third_party_insurance] {third_party!r}")
return third_party
print("[extract_third_party_insurance] No 'Third Party Liability' table found")
return None
except Exception as e:
print(f"[extract_third_party_insurance] failed: {e}")
return None
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)
self.extract_third_party_insurance()
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_filename = f"mh_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)
@@ -341,7 +384,7 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
time.sleep(3)
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_history_{safe_member}.pdf"
pdf_filename = f"mh_history_{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)
@@ -353,7 +396,68 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
print(f"[step5_service_history_pdf] failed: {e}")
return None
# ── step 6 — Tab B: accumulator → wait for data → CDP print ─────────────────
# ── step 6 — Tab B: accumulator → extract remaining → CDP print ─────────────
def extract_remaining_amount(self):
"""Reads the accumulator benefits table and returns the Remaining amount.
Layout (see sample PDF "Accumulator Details"):
Type | Level | Network | Benefit Period | Period | Limit | Applied | Remaining
Prefers the Type="Maximum", Level="Individual" row; falls back to the
first data row with a non-empty Remaining cell if that combination
isn't found (payer may only return one row, or reorder columns).
"""
try:
wait = WebDriverWait(self.driver, 15)
wait.until(EC.presence_of_element_located((By.TAG_NAME, "table")))
tables = self.driver.find_elements(By.TAG_NAME, "table")
for table in tables:
rows = table.find_elements(By.TAG_NAME, "tr")
if not rows:
continue
header_cells = [
c.text.strip().lower()
for c in (rows[0].find_elements(By.TAG_NAME, "th") or rows[0].find_elements(By.TAG_NAME, "td"))
]
if "remaining" not in header_cells:
continue
remaining_idx = header_cells.index("remaining")
type_idx = header_cells.index("type") if "type" in header_cells else None
level_idx = header_cells.index("level") if "level" in header_cells else None
fallback_value = None
best_value = None
for row in rows[1:]:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) <= remaining_idx:
continue
cell_texts = [c.text.strip() for c in cells]
value = cell_texts[remaining_idx]
if not value:
continue
if fallback_value is None:
fallback_value = value
type_ok = type_idx is None or "maximum" in cell_texts[type_idx].lower()
level_ok = level_idx is None or "individual" in cell_texts[level_idx].lower()
if type_ok and level_ok:
best_value = value
break
remaining_value = best_value or fallback_value
if remaining_value:
self.extracted_data["remaining_amount"] = remaining_value
print(f"[extract_remaining_amount] Remaining: {remaining_value}")
return remaining_value
print("[extract_remaining_amount] No accumulator table with a 'Remaining' column found")
return None
except Exception as e:
print(f"[extract_remaining_amount] failed: {e}")
return None
def step6_accumulator_pdf(self):
wait = WebDriverWait(self.driver, 30)
@@ -379,8 +483,10 @@ class AutomationCMSPEligibilityHistoryRemainingCheck:
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
time.sleep(4)
self.extract_remaining_amount()
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
pdf_filename = f"cmsp_accumulator_{safe_member}.pdf"
pdf_filename = f"mh_accumulator_{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)