fix: MH eligibility selenium integration and patient data extraction
This commit is contained in:
0
.turbo/daemon/53b0054db79f7114-turbo.log.2026-04-13
Normal file
0
.turbo/daemon/53b0054db79f7114-turbo.log.2026-04-13
Normal file
0
.turbo/daemon/f6a8bbc9aba2c062-turbo.log.2026-04-14
Normal file
0
.turbo/daemon/f6a8bbc9aba2c062-turbo.log.2026-04-14
Normal file
2327
apps/Backend/backups/dental_backup_1776124800085.sql
Normal file
2327
apps/Backend/backups/dental_backup_1776124800085.sql
Normal file
File diff suppressed because one or more lines are too long
4152
apps/Backend/backups/dental_backup_1776211200056.sql
Normal file
4152
apps/Backend/backups/dental_backup_1776211200056.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -18,50 +18,30 @@ const SELENIUM_BASE_URL =
|
|||||||
// Python service helpers
|
// Python service helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Start an async job on the Python service and return the session ID. */
|
/**
|
||||||
export async function startPythonJob(
|
* Call a synchronous Python service endpoint and return its result.
|
||||||
|
* The Python service runs the selenium job inline and returns the result directly.
|
||||||
|
* Timeout is long (8 min) to accommodate slow selenium flows.
|
||||||
|
*/
|
||||||
|
export async function callPythonSync(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
payload: any
|
payload: any,
|
||||||
): Promise<string> {
|
timeoutMs = 8 * 60 * 1000
|
||||||
|
): Promise<any> {
|
||||||
const resp = await axios.post(
|
const resp = await axios.post(
|
||||||
`${SELENIUM_BASE_URL}${endpoint}`,
|
`${SELENIUM_BASE_URL}${endpoint}`,
|
||||||
payload,
|
payload,
|
||||||
{ timeout: 10_000 }
|
{ timeout: timeoutMs }
|
||||||
);
|
);
|
||||||
const sid: string = resp.data?.session_id;
|
const data = resp.data;
|
||||||
if (!sid) throw new Error(`Python service did not return a session_id from ${endpoint}`);
|
if (data?.status === "error") {
|
||||||
return sid;
|
const msg =
|
||||||
}
|
typeof data.message === "string"
|
||||||
|
? data.message
|
||||||
/** Poll /job/<sid>/status until completed/failed or timeout. */
|
: data.message?.msg ?? "Selenium returned error status";
|
||||||
export async function pollPythonJob(
|
throw new Error(msg);
|
||||||
sid: string,
|
|
||||||
timeoutMs = 5 * 60 * 1000,
|
|
||||||
intervalMs = 2_000
|
|
||||||
): Promise<any> {
|
|
||||||
const deadline = Date.now() + timeoutMs;
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
const resp = await axios.get(
|
|
||||||
`${SELENIUM_BASE_URL}/job/${sid}/status`,
|
|
||||||
{ timeout: 5_000 }
|
|
||||||
);
|
|
||||||
const s = resp.data;
|
|
||||||
if (s.status === "completed") {
|
|
||||||
if (s.result?.status === "error") {
|
|
||||||
const msg =
|
|
||||||
typeof s.result.message === "string"
|
|
||||||
? s.result.message
|
|
||||||
: s.result.message?.msg ?? "Selenium returned error status";
|
|
||||||
throw new Error(msg);
|
|
||||||
}
|
|
||||||
return s.result;
|
|
||||||
}
|
|
||||||
if (s.status === "failed") {
|
|
||||||
throw new Error(s.error || "Python job failed");
|
|
||||||
}
|
|
||||||
await sleep(intervalMs);
|
|
||||||
}
|
}
|
||||||
throw new Error("Selenium job timed out after polling");
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import path from "path";
|
|||||||
import { storage } from "../../storage";
|
import { storage } from "../../storage";
|
||||||
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
||||||
import {
|
import {
|
||||||
startPythonJob,
|
callPythonSync,
|
||||||
pollPythonJob,
|
|
||||||
imageToPdfBuffer,
|
imageToPdfBuffer,
|
||||||
} from "./_shared";
|
} from "./_shared";
|
||||||
|
|
||||||
@@ -28,14 +27,11 @@ export async function runClaimStatusProcessor(
|
|||||||
): Promise<ClaimStatusProcessorResult> {
|
): Promise<ClaimStatusProcessorResult> {
|
||||||
const { enrichedPayload, insuranceId } = input;
|
const { enrichedPayload, insuranceId } = input;
|
||||||
|
|
||||||
// 1) Start async Python job
|
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||||
const sid = await startPythonJob("/claim-status-check/async", {
|
const result = await callPythonSync("/claim-status-check", {
|
||||||
data: enrichedPayload,
|
data: enrichedPayload,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Poll for completion
|
|
||||||
const result = await pollPythonJob(sid);
|
|
||||||
|
|
||||||
const outputResult: ClaimStatusProcessorResult = {};
|
const outputResult: ClaimStatusProcessorResult = {};
|
||||||
|
|
||||||
// 3) Look up patient
|
// 3) Look up patient
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Mirrors routes/claims.ts /selenium-claim and /selenium-claim-pre-auth
|
* Mirrors routes/claims.ts /selenium-claim and /selenium-claim-pre-auth
|
||||||
*/
|
*/
|
||||||
import { storage } from "../../storage";
|
import { storage } from "../../storage";
|
||||||
import { startPythonJob, pollPythonJob } from "./_shared";
|
import { callPythonSync } from "./_shared";
|
||||||
|
|
||||||
export interface ClaimSubmitProcessorInput {
|
export interface ClaimSubmitProcessorInput {
|
||||||
enrichedPayload: any;
|
enrichedPayload: any;
|
||||||
@@ -37,13 +37,10 @@ export async function runClaimSubmitProcessor(
|
|||||||
const payload = { claim: enrichedPayload, pdfs, images };
|
const payload = { claim: enrichedPayload, pdfs, images };
|
||||||
|
|
||||||
const endpoint =
|
const endpoint =
|
||||||
input.variant === "claim-pre-auth" ? "/claim-pre-auth/async" : "/claimsubmit/async";
|
input.variant === "claim-pre-auth" ? "/claim-pre-auth" : "/claimsubmit";
|
||||||
|
|
||||||
// 1) Start async Python job
|
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||||
const sid = await startPythonJob(endpoint, payload);
|
const result = await callPythonSync(endpoint, payload, 10 * 60 * 1000);
|
||||||
|
|
||||||
// 2) Poll for result
|
|
||||||
const result = await pollPythonJob(sid, 10 * 60 * 1000); // claim submit can take up to 10 min
|
|
||||||
|
|
||||||
// 3) Persist claimNumber if returned
|
// 3) Persist claimNumber if returned
|
||||||
if (result?.claimNumber && claimId) {
|
if (result?.claimNumber && claimId) {
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import { storage } from "../../storage";
|
|||||||
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
||||||
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
|
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
|
||||||
import {
|
import {
|
||||||
startPythonJob,
|
callPythonSync,
|
||||||
pollPythonJob,
|
|
||||||
splitName,
|
splitName,
|
||||||
createOrUpdatePatientByInsuranceId,
|
createOrUpdatePatientByInsuranceId,
|
||||||
} from "./_shared";
|
} from "./_shared";
|
||||||
@@ -45,26 +44,29 @@ export async function runEligibilityProcessor(
|
|||||||
formDob,
|
formDob,
|
||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
// 1) Fire the async Python job
|
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||||
const sid = await startPythonJob("/eligibility-check/async", {
|
const seleniumResult = await callPythonSync("/eligibility-check", {
|
||||||
data: enrichedPayload,
|
data: enrichedPayload,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Wait for completion
|
|
||||||
const seleniumResult = await pollPythonJob(sid);
|
|
||||||
|
|
||||||
const outputResult: EligibilityProcessorResult = {};
|
const outputResult: EligibilityProcessorResult = {};
|
||||||
|
|
||||||
// 3) Extract name: prefer selenium extraction → PDF extractor → form input
|
// 3) Extract name: prefer selenium extraction → PDF extractor → form input
|
||||||
|
// Normalize: treat empty strings from Python the same as null
|
||||||
|
const seleniumFirst = seleniumResult.firstName?.trim() || null;
|
||||||
|
const seleniumLast = seleniumResult.lastName?.trim() || null;
|
||||||
|
const seleniumName = seleniumResult.name?.trim() || null;
|
||||||
|
const seleniumInsurance = seleniumResult.insurance?.trim() || null;
|
||||||
|
|
||||||
const extracted: { firstName?: string | null; lastName?: string | null } = {};
|
const extracted: { firstName?: string | null; lastName?: string | null } = {};
|
||||||
|
|
||||||
if (seleniumResult.firstName || seleniumResult.lastName) {
|
if (seleniumFirst || seleniumLast) {
|
||||||
extracted.firstName = seleniumResult.firstName ?? null;
|
extracted.firstName = seleniumFirst;
|
||||||
extracted.lastName = seleniumResult.lastName ?? null;
|
extracted.lastName = seleniumLast;
|
||||||
} else if (seleniumResult.name) {
|
} else if (seleniumName) {
|
||||||
const parts = splitName(seleniumResult.name);
|
const parts = splitName(seleniumName);
|
||||||
extracted.firstName = parts.firstName;
|
extracted.firstName = parts.firstName || null;
|
||||||
extracted.lastName = parts.lastName;
|
extracted.lastName = parts.lastName || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -81,14 +83,15 @@ export async function runEligibilityProcessor(
|
|||||||
} as any);
|
} as any);
|
||||||
if (extraction.name) {
|
if (extraction.name) {
|
||||||
const parts = splitName(extraction.name);
|
const parts = splitName(extraction.name);
|
||||||
extracted.firstName = parts.firstName;
|
extracted.firstName = parts.firstName || null;
|
||||||
extracted.lastName = parts.lastName;
|
extracted.lastName = parts.lastName || null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[eligibilityProcessor] PDF name extraction failed:", e);
|
console.error("[eligibilityProcessor] PDF name extraction failed:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final fallback: use form data sent by the frontend
|
||||||
const preferFirst = extracted.firstName || formFirstName || null;
|
const preferFirst = extracted.firstName || formFirstName || null;
|
||||||
const preferLast = extracted.lastName || formLastName || null;
|
const preferLast = extracted.lastName || formLastName || null;
|
||||||
|
|
||||||
@@ -113,7 +116,7 @@ export async function runEligibilityProcessor(
|
|||||||
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
|
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
|
||||||
|
|
||||||
const updates: any = { status: newStatus };
|
const updates: any = { status: newStatus };
|
||||||
if (seleniumResult.insurance) updates.insuranceProvider = seleniumResult.insurance;
|
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
|
||||||
|
|
||||||
await storage.updatePatient(patient.id, updates);
|
await storage.updatePatient(patient.id, updates);
|
||||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from "@/redux/slices/seleniumTaskSlice";
|
} from "@/redux/slices/seleniumTaskSlice";
|
||||||
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
import { SeleniumTaskBanner } from "@/components/ui/selenium-task-banner";
|
||||||
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
import { formatLocalDate, parseLocalDate } from "@/utils/dateUtils";
|
||||||
|
import { socket } from "@/lib/socket";
|
||||||
import { InsertPatient, Patient } from "@repo/db/types";
|
import { InsertPatient, Patient } from "@repo/db/types";
|
||||||
import { DateInput } from "@/components/ui/dateInput";
|
import { DateInput } from "@/components/ui/dateInput";
|
||||||
import { QK_PATIENTS_BASE } from "@/components/patients/patient-table";
|
import { QK_PATIENTS_BASE } from "@/components/patients/patient-table";
|
||||||
@@ -117,6 +118,8 @@ export default function InsuranceStatusPage() {
|
|||||||
memberId,
|
memberId,
|
||||||
dateOfBirth: formattedDob,
|
dateOfBirth: formattedDob,
|
||||||
insuranceSiteKey: "MH",
|
insuranceSiteKey: "MH",
|
||||||
|
firstName: firstName || undefined,
|
||||||
|
lastName: lastName || undefined,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -129,11 +132,44 @@ export default function InsuranceStatusPage() {
|
|||||||
const response = await apiRequest(
|
const response = await apiRequest(
|
||||||
"POST",
|
"POST",
|
||||||
"/api/insurance-status/eligibility-check",
|
"/api/insurance-status/eligibility-check",
|
||||||
{ data: data },
|
{ data: data, socketId: socket.id },
|
||||||
);
|
);
|
||||||
// { data: JSON.stringify(data) },
|
const enqueueResult = await response.json();
|
||||||
const result = await response.json();
|
if (enqueueResult.error) throw new Error(enqueueResult.error);
|
||||||
if (result.error) throw new Error(result.error);
|
|
||||||
|
const jobId = enqueueResult.jobId;
|
||||||
|
if (!jobId) throw new Error("No jobId returned from server");
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
key: "eligibilityCheck",
|
||||||
|
status: "pending",
|
||||||
|
message: "Selenium browser starting...",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the BullMQ worker to emit job:update on this socket
|
||||||
|
const jobResult = await new Promise<any>((resolve, reject) => {
|
||||||
|
const handler = (payload: any) => {
|
||||||
|
if (String(payload.jobId) !== String(jobId)) return;
|
||||||
|
if (payload.status === "active") {
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
key: "eligibilityCheck",
|
||||||
|
status: "pending",
|
||||||
|
message: payload.message ?? "Selenium running...",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (payload.status === "completed") {
|
||||||
|
socket.off("job:update", handler);
|
||||||
|
resolve(payload.result ?? {});
|
||||||
|
} else if (payload.status === "failed") {
|
||||||
|
socket.off("job:update", handler);
|
||||||
|
reject(new Error(payload.error ?? "Selenium job failed"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.on("job:update", handler);
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setTaskStatus({
|
setTaskStatus({
|
||||||
@@ -153,12 +189,11 @@ export default function InsuranceStatusPage() {
|
|||||||
|
|
||||||
setSelectedPatient(null);
|
setSelectedPatient(null);
|
||||||
|
|
||||||
// If server returned pdfFileId: open preview modal
|
// If worker returned pdfFileId: open preview modal
|
||||||
if (result.pdfFileId) {
|
if (jobResult.pdfFileId) {
|
||||||
setPreviewPdfId(Number(result.pdfFileId));
|
setPreviewPdfId(Number(jobResult.pdfFileId));
|
||||||
// optional fallback name while header is parsed
|
|
||||||
setPreviewFallbackFilename(
|
setPreviewFallbackFilename(
|
||||||
result.pdfFilename ?? `eligibility_${memberId}.pdf`,
|
jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`,
|
||||||
);
|
);
|
||||||
setPreviewOpen(true);
|
setPreviewOpen(true);
|
||||||
}
|
}
|
||||||
@@ -198,10 +233,44 @@ export default function InsuranceStatusPage() {
|
|||||||
const response = await apiRequest(
|
const response = await apiRequest(
|
||||||
"POST",
|
"POST",
|
||||||
"/api/insurance-status/claim-status-check",
|
"/api/insurance-status/claim-status-check",
|
||||||
{ data: JSON.stringify(data) },
|
{ data: JSON.stringify(data), socketId: socket.id },
|
||||||
);
|
);
|
||||||
const result = await response.json();
|
const enqueueResult = await response.json();
|
||||||
if (result.error) throw new Error(result.error);
|
if (enqueueResult.error) throw new Error(enqueueResult.error);
|
||||||
|
|
||||||
|
const jobId = enqueueResult.jobId;
|
||||||
|
if (!jobId) throw new Error("No jobId returned from server");
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
key: "eligibilityCheck",
|
||||||
|
status: "pending",
|
||||||
|
message: "Selenium browser starting...",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the BullMQ worker to emit job:update on this socket
|
||||||
|
const jobResult = await new Promise<any>((resolve, reject) => {
|
||||||
|
const handler = (payload: any) => {
|
||||||
|
if (String(payload.jobId) !== String(jobId)) return;
|
||||||
|
if (payload.status === "active") {
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
key: "eligibilityCheck",
|
||||||
|
status: "pending",
|
||||||
|
message: payload.message ?? "Selenium running...",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (payload.status === "completed") {
|
||||||
|
socket.off("job:update", handler);
|
||||||
|
resolve(payload.result ?? {});
|
||||||
|
} else if (payload.status === "failed") {
|
||||||
|
socket.off("job:update", handler);
|
||||||
|
reject(new Error(payload.error ?? "Selenium job failed"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.on("job:update", handler);
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setTaskStatus({
|
setTaskStatus({
|
||||||
@@ -221,12 +290,11 @@ export default function InsuranceStatusPage() {
|
|||||||
|
|
||||||
setSelectedPatient(null);
|
setSelectedPatient(null);
|
||||||
|
|
||||||
// If server returned pdfFileId: open preview modal
|
// If worker returned pdfFileId: open preview modal
|
||||||
if (result.pdfFileId) {
|
if (jobResult.pdfFileId) {
|
||||||
setPreviewPdfId(Number(result.pdfFileId));
|
setPreviewPdfId(Number(jobResult.pdfFileId));
|
||||||
// optional fallback name while header is parsed
|
|
||||||
setPreviewFallbackFilename(
|
setPreviewFallbackFilename(
|
||||||
result.pdfFilename ?? `eligibility_${memberId}.pdf`,
|
jobResult.pdfFilename ?? `eligibility_${memberId}.pdf`,
|
||||||
);
|
);
|
||||||
setPreviewOpen(true);
|
setPreviewOpen(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,23 @@ export const PROCEDURE_COMBOS: Record<
|
|||||||
codes: ["D2394"],
|
codes: ["D2394"],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Pedo
|
||||||
|
pedoSealants: {
|
||||||
|
id: "pedoSealants",
|
||||||
|
label: "Sealants",
|
||||||
|
codes: ["D1351"],
|
||||||
|
},
|
||||||
|
pedoPulpotomy: {
|
||||||
|
id: "pedoPulpotomy",
|
||||||
|
label: "Pulpotomy",
|
||||||
|
codes: ["D3220"],
|
||||||
|
},
|
||||||
|
pedoSSCrown: {
|
||||||
|
id: "pedoSSCrown",
|
||||||
|
label: "Stainless Steel Crown",
|
||||||
|
codes: ["D2930"],
|
||||||
|
},
|
||||||
|
|
||||||
// Dentures / Partials
|
// Dentures / Partials
|
||||||
fu: {
|
fu: {
|
||||||
id: "fu",
|
id: "fu",
|
||||||
@@ -303,6 +320,7 @@ export const COMBO_CATEGORIES: Record<
|
|||||||
"threeSurfCompBack",
|
"threeSurfCompBack",
|
||||||
"fourSurfCompBack",
|
"fourSurfCompBack",
|
||||||
],
|
],
|
||||||
|
Pedo: ["pedoSealants", "pedoPulpotomy", "pedoSSCrown"],
|
||||||
"Dentures / Partials (>21 price)": [
|
"Dentures / Partials (>21 price)": [
|
||||||
"fu",
|
"fu",
|
||||||
"fl",
|
"fl",
|
||||||
|
|||||||
Binary file not shown.
@@ -162,6 +162,20 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
print(f"Error while step1: {e}")
|
print(f"Error while step1: {e}")
|
||||||
return "ERROR:STEP1"
|
return "ERROR:STEP1"
|
||||||
|
|
||||||
|
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):
|
def _extract_data_from_page(self):
|
||||||
wait = WebDriverWait(self.driver, 5)
|
wait = WebDriverWait(self.driver, 5)
|
||||||
extracted = {}
|
extracted = {}
|
||||||
@@ -183,19 +197,32 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
for row in eligible_rows:
|
for row in eligible_rows:
|
||||||
cells = row.find_elements(By.TAG_NAME, "td")
|
cells = row.find_elements(By.TAG_NAME, "td")
|
||||||
|
|
||||||
if len(cells) < 6:
|
if len(cells) < 3:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
member_number = cells[2].text.strip()
|
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 str(self.memberId) in member_number:
|
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
|
||||||
full_name = cells[4].text.strip()
|
# name is in cells[4], insurance in cells[6] (fallback to last cell)
|
||||||
plan_name = cells[6].text.strip() if len(cells) > 6 else cells[-1].text.strip()
|
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()
|
name_parts = full_name.split()
|
||||||
first_name = name_parts[0] if name_parts else ""
|
first_name = name_parts[0] if name_parts else ""
|
||||||
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
|
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
|
||||||
|
|
||||||
|
print(f"[eligible] MATCHED → name='{full_name}' plan='{plan_name}'")
|
||||||
|
|
||||||
extracted = {
|
extracted = {
|
||||||
"eligibility": "Y",
|
"eligibility": "Y",
|
||||||
"firstName": first_name,
|
"firstName": first_name,
|
||||||
@@ -204,7 +231,7 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extracted
|
return extracted
|
||||||
|
|
||||||
ineligible_rows = self.driver.find_elements(
|
ineligible_rows = self.driver.find_elements(
|
||||||
By.XPATH,
|
By.XPATH,
|
||||||
"//h4[text()='Ineligible']/following::table[1]/tbody/tr"
|
"//h4[text()='Ineligible']/following::table[1]/tbody/tr"
|
||||||
@@ -214,19 +241,29 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
for row in ineligible_rows:
|
for row in ineligible_rows:
|
||||||
cells = row.find_elements(By.TAG_NAME, "td")
|
cells = row.find_elements(By.TAG_NAME, "td")
|
||||||
|
|
||||||
if len(cells) < 5:
|
if len(cells) < 3:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
member_number = cells[2].text.strip()
|
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 str(self.memberId) in member_number:
|
if norm_self and norm_cell and (norm_self in norm_cell or norm_cell in norm_self):
|
||||||
full_name = cells[4].text.strip()
|
full_name = self._cell_text(cells[4]) if len(cells) > 4 else ""
|
||||||
plan_name = cells[5].text.strip()
|
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()
|
name_parts = full_name.split()
|
||||||
first_name = name_parts[0] if name_parts else ""
|
first_name = name_parts[0] if name_parts else ""
|
||||||
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
|
last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
|
||||||
|
|
||||||
|
print(f"[ineligible] MATCHED → name='{full_name}' plan='{plan_name}'")
|
||||||
|
|
||||||
extracted = {
|
extracted = {
|
||||||
"eligibility": "N",
|
"eligibility": "N",
|
||||||
"firstName": first_name,
|
"firstName": first_name,
|
||||||
@@ -235,7 +272,8 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return extracted
|
return extracted
|
||||||
|
|
||||||
|
print(f"[extraction] No matching row found for memberId='{self.memberId}'")
|
||||||
return {"eligibility": None}
|
return {"eligibility": None}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user