feat: MassHealth member detail demographics, wait for name/plan before PDF

- Extract gender, address (street/city/state/zip) and home phone from the
  MassHealth Member Detail page and save to the patient; phone is only
  filled when the patient has none
- Shared extract_eligibility_row polls up to 30s until the matching results
  row has both name and plan before the PDF is printed (AngularJS fills cell
  text after the rows appear); used by all three MH eligibility workers
- Fall back to the Member Detail heading for the name when the results table
  never filled it in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 15:53:34 -04:00
co-authored by Claude Opus 5.5
parent 13be6e97d5
commit ebc5fba2c9
8 changed files with 219 additions and 232 deletions
@@ -9,6 +9,8 @@ import time
import os
import base64
from mh_eligibility_results import extract_eligibility_row
class AutomationMassHealthEligibilityCheck:
def __init__(self, data):
self.headless = False
@@ -245,124 +247,9 @@ class AutomationMassHealthEligibilityCheck:
print(f"[step1] Could not save screenshot: {ss_err}")
return f"ERROR:STEP1:{substep}"
def _cell_text(self, cell):
"""Get text from a cell, falling back to JS innerText if .text is empty."""
text = cell.text.strip()
if not text:
try:
text = (self.driver.execute_script("return arguments[0].innerText;", cell) or "").strip()
except Exception:
pass
return text
def _normalize_id(self, s):
"""Strip all non-alphanumeric characters and lowercase for robust ID matching."""
return ''.join(c for c in str(s) if c.isalnum()).lower()
def _extract_data_from_page(self):
wait = WebDriverWait(self.driver, 15)
extracted = {}
try:
# Wait until at least one table row appears under Eligible or Ineligible —
# the h4 header renders before the rows are populated, so we must wait for rows.
wait.until(
EC.presence_of_element_located(
(By.XPATH, "//h4[text()='Eligible' or text()='Ineligible']/following::table[1]/tbody/tr")
)
)
eligible_rows = self.driver.find_elements(
By.XPATH,
"//h4[text()='Eligible']/following::table[1]/tbody/tr"
)
if eligible_rows:
for row in eligible_rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) < 3:
continue
member_number = self._cell_text(cells[2])
norm_cell = self._normalize_id(member_number)
norm_self = self._normalize_id(self.memberId)
print(f"[eligible] cells count={len(cells)}, memberId check: '{norm_self}' vs '{norm_cell}' (raw: '{member_number}')")
if len(cells) >= 5:
print(f" cells[3]='{self._cell_text(cells[3])}' cells[4]='{self._cell_text(cells[4])}'", end="")
if len(cells) > 5:
print(f" cells[5]='{self._cell_text(cells[5])}'", end="")
if len(cells) > 6:
print(f" cells[6]='{self._cell_text(cells[6])}'", end="")
print()
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
# name is in cells[4], insurance in cells[6] (fallback to last cell)
full_name = self._cell_text(cells[4]) if len(cells) > 4 else ""
plan_name = self._cell_text(cells[6]) if len(cells) > 6 else (self._cell_text(cells[-1]) if len(cells) > 4 else "")
name_parts = full_name.split()
first_name = name_parts[0] if name_parts else ""
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
print(f"[eligible] MATCHED → name='{full_name}' plan='{plan_name}'")
extracted = {
"eligibility": "Y",
"firstName": first_name,
"lastName": last_name,
"insurance": plan_name
}
return extracted
ineligible_rows = self.driver.find_elements(
By.XPATH,
"//h4[text()='Ineligible']/following::table[1]/tbody/tr"
)
if ineligible_rows:
for row in ineligible_rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) < 3:
continue
member_number = self._cell_text(cells[2])
norm_cell = self._normalize_id(member_number)
norm_self = self._normalize_id(self.memberId)
print(f"[ineligible] cells count={len(cells)}, memberId check: '{norm_self}' vs '{norm_cell}' (raw: '{member_number}')")
if len(cells) >= 5:
print(f" cells[3]='{self._cell_text(cells[3])}' cells[4]='{self._cell_text(cells[4])}'", end="")
if len(cells) > 5:
print(f" cells[5]='{self._cell_text(cells[5])}'", end="")
print()
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
full_name = self._cell_text(cells[4]) if len(cells) > 4 else ""
plan_name = self._cell_text(cells[5]) if len(cells) > 5 else (self._cell_text(cells[-1]) if len(cells) > 4 else "")
name_parts = full_name.split()
first_name = name_parts[0] if name_parts else ""
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
print(f"[ineligible] MATCHED → name='{full_name}' plan='{plan_name}'")
extracted = {
"eligibility": "N",
"firstName": first_name,
"lastName": last_name,
"insurance": plan_name
}
return extracted
print(f"[extraction] No matching row found for memberId='{self.memberId}'")
return {"eligibility": None}
except Exception as e:
print("Extraction error:", e)
return {"eligibility": None}
# Waits until the matching row's name and plan are filled in
return extract_eligibility_row(self.driver, self.memberId)
def _print_current_page_as_pdf(self):
"""Generate PDF from the currently active tab using CDP."""