fix: MH eligibility selenium integration and patient data extraction
This commit is contained in:
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 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,
|
||||
payload: any
|
||||
): Promise<string> {
|
||||
payload: any,
|
||||
timeoutMs = 8 * 60 * 1000
|
||||
): Promise<any> {
|
||||
const resp = await axios.post(
|
||||
`${SELENIUM_BASE_URL}${endpoint}`,
|
||||
payload,
|
||||
{ timeout: 10_000 }
|
||||
{ timeout: timeoutMs }
|
||||
);
|
||||
const sid: string = resp.data?.session_id;
|
||||
if (!sid) throw new Error(`Python service did not return a session_id from ${endpoint}`);
|
||||
return sid;
|
||||
}
|
||||
|
||||
/** Poll /job/<sid>/status until completed/failed or timeout. */
|
||||
export async function pollPythonJob(
|
||||
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);
|
||||
const data = resp.data;
|
||||
if (data?.status === "error") {
|
||||
const msg =
|
||||
typeof data.message === "string"
|
||||
? data.message
|
||||
: data.message?.msg ?? "Selenium returned error status";
|
||||
throw new Error(msg);
|
||||
}
|
||||
throw new Error("Selenium job timed out after polling");
|
||||
return data;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -8,8 +8,7 @@ import path from "path";
|
||||
import { storage } from "../../storage";
|
||||
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
||||
import {
|
||||
startPythonJob,
|
||||
pollPythonJob,
|
||||
callPythonSync,
|
||||
imageToPdfBuffer,
|
||||
} from "./_shared";
|
||||
|
||||
@@ -28,14 +27,11 @@ export async function runClaimStatusProcessor(
|
||||
): Promise<ClaimStatusProcessorResult> {
|
||||
const { enrichedPayload, insuranceId } = input;
|
||||
|
||||
// 1) Start async Python job
|
||||
const sid = await startPythonJob("/claim-status-check/async", {
|
||||
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||
const result = await callPythonSync("/claim-status-check", {
|
||||
data: enrichedPayload,
|
||||
});
|
||||
|
||||
// 2) Poll for completion
|
||||
const result = await pollPythonJob(sid);
|
||||
|
||||
const outputResult: ClaimStatusProcessorResult = {};
|
||||
|
||||
// 3) Look up patient
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Mirrors routes/claims.ts /selenium-claim and /selenium-claim-pre-auth
|
||||
*/
|
||||
import { storage } from "../../storage";
|
||||
import { startPythonJob, pollPythonJob } from "./_shared";
|
||||
import { callPythonSync } from "./_shared";
|
||||
|
||||
export interface ClaimSubmitProcessorInput {
|
||||
enrichedPayload: any;
|
||||
@@ -37,13 +37,10 @@ export async function runClaimSubmitProcessor(
|
||||
const payload = { claim: enrichedPayload, pdfs, images };
|
||||
|
||||
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
|
||||
const sid = await startPythonJob(endpoint, payload);
|
||||
|
||||
// 2) Poll for result
|
||||
const result = await pollPythonJob(sid, 10 * 60 * 1000); // claim submit can take up to 10 min
|
||||
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||
const result = await callPythonSync(endpoint, payload, 10 * 60 * 1000);
|
||||
|
||||
// 3) Persist claimNumber if returned
|
||||
if (result?.claimNumber && claimId) {
|
||||
|
||||
@@ -11,8 +11,7 @@ import { storage } from "../../storage";
|
||||
import { emptyFolderContainingFile } from "../../utils/emptyTempFolder";
|
||||
import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService";
|
||||
import {
|
||||
startPythonJob,
|
||||
pollPythonJob,
|
||||
callPythonSync,
|
||||
splitName,
|
||||
createOrUpdatePatientByInsuranceId,
|
||||
} from "./_shared";
|
||||
@@ -45,26 +44,29 @@ export async function runEligibilityProcessor(
|
||||
formDob,
|
||||
} = input;
|
||||
|
||||
// 1) Fire the async Python job
|
||||
const sid = await startPythonJob("/eligibility-check/async", {
|
||||
// 1) Call the Python service synchronously (BullMQ worker handles async)
|
||||
const seleniumResult = await callPythonSync("/eligibility-check", {
|
||||
data: enrichedPayload,
|
||||
});
|
||||
|
||||
// 2) Wait for completion
|
||||
const seleniumResult = await pollPythonJob(sid);
|
||||
|
||||
const outputResult: EligibilityProcessorResult = {};
|
||||
|
||||
// 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 } = {};
|
||||
|
||||
if (seleniumResult.firstName || seleniumResult.lastName) {
|
||||
extracted.firstName = seleniumResult.firstName ?? null;
|
||||
extracted.lastName = seleniumResult.lastName ?? null;
|
||||
} else if (seleniumResult.name) {
|
||||
const parts = splitName(seleniumResult.name);
|
||||
extracted.firstName = parts.firstName;
|
||||
extracted.lastName = parts.lastName;
|
||||
if (seleniumFirst || seleniumLast) {
|
||||
extracted.firstName = seleniumFirst;
|
||||
extracted.lastName = seleniumLast;
|
||||
} else if (seleniumName) {
|
||||
const parts = splitName(seleniumName);
|
||||
extracted.firstName = parts.firstName || null;
|
||||
extracted.lastName = parts.lastName || null;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -81,14 +83,15 @@ export async function runEligibilityProcessor(
|
||||
} as any);
|
||||
if (extraction.name) {
|
||||
const parts = splitName(extraction.name);
|
||||
extracted.firstName = parts.firstName;
|
||||
extracted.lastName = parts.lastName;
|
||||
extracted.firstName = parts.firstName || null;
|
||||
extracted.lastName = parts.lastName || null;
|
||||
}
|
||||
} catch (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 preferLast = extracted.lastName || formLastName || null;
|
||||
|
||||
@@ -113,7 +116,7 @@ export async function runEligibilityProcessor(
|
||||
else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE";
|
||||
|
||||
const updates: any = { status: newStatus };
|
||||
if (seleniumResult.insurance) updates.insuranceProvider = seleniumResult.insurance;
|
||||
if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance;
|
||||
|
||||
await storage.updatePatient(patient.id, updates);
|
||||
outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`;
|
||||
|
||||
Reference in New Issue
Block a user