fix: Tufts SCO + UnitedDH pre-auth file upload, tooth fill, PDF and pre-auth number

- Frontend: upload attachments to disk before sending pre-auth payload (same pattern as claims)
- cloud-storage: finalizeFileUpload returns diskPath so Python workers get real file paths
- upload-to-cloud route: return diskPath instead of API URL
- TuftsSCO preAuth worker: skip 'Add a file' button click; send_keys directly to hidden react-aria-Input
- TuftsSCO preAuth worker: JS focus() on tooth field to bypass warning-banner overlay
- TuftsSCO preAuth worker: 1.5s wait after procedure code for layout shift to settle
- TuftsSCO preAuth worker: step8 waits for 'thank' in page_source then extracts via 'submitted pre-authorization' regex
- helpers_tuftssco_preauth: convert pdf_path → pdf_url (http://localhost:5002/downloads/...)
- tuftsSCOPreAuthProcessor: use pdf_url (not pdf_path), save preAuthNumber to preAuthNumber field
- unitedDHPreAuthProcessor: save preAuthNumber to preAuthNumber field (not claimNumber)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-15 23:54:05 -04:00
parent beb6a6a8e8
commit dc039741ca
9 changed files with 107 additions and 105 deletions

View File

@@ -88,12 +88,10 @@ async function pollUntilDone(
throw new Error(`Tufts SCO preauth polling exhausted all attempts for session ${sessionId}`);
}
async function savePdfFromSelenium(pdf_path: string, patientId: number) {
async function savePdfFromSelenium(pdf_url: string, patientId: number) {
try {
const filename = path.basename(pdf_path);
const seleniumPort = process.env.SELENIUM_PORT || "5002";
const localUrl = `http://localhost:${seleniumPort}/downloads/${filename}`;
const resp = await axios.get(localUrl, { responseType: "arraybuffer", timeout: 30000 });
const filename = path.basename(new URL(pdf_url).pathname);
const resp = await axios.get(pdf_url, { responseType: "arraybuffer", timeout: 30000 });
let group = await storage.findPdfGroupByPatientTitleKey(patientId, "INSURANCE_CLAIM_PREAUTH");
if (!group) {
@@ -116,7 +114,7 @@ export interface TuftsSCOPreAuthProcessorInput {
export async function runTuftsSCOPreAuthProcessor(
input: TuftsSCOPreAuthProcessorInput,
jobId: string
): Promise<{ status: string; pdf_path?: string; preAuthNumber?: string }> {
): Promise<{ status: string; pdf_url?: string; preAuthNumber?: string }> {
const { enrichedPayload, userId, claimId, socketId } = input;
log("tuftssco-preauth-processor", "starting Python agent session", { claimId });
@@ -138,12 +136,12 @@ export async function runTuftsSCOPreAuthProcessor(
}
const preAuthNumber: string | undefined = seleniumResult.preAuthNumber ?? undefined;
const pdf_path: string | undefined = seleniumResult.pdf_path ?? undefined;
const pdf_url: string | undefined = seleniumResult.pdf_url ?? undefined;
if (claimId) {
try {
const updates: Record<string, any> = { status: "PREAUTH" };
if (preAuthNumber) updates.claimNumber = preAuthNumber;
if (preAuthNumber) updates.preAuthNumber = preAuthNumber;
await storage.updateClaim(claimId, updates);
log("tuftssco-preauth-processor", "claim record updated", { claimId, preAuthNumber });
@@ -157,22 +155,22 @@ export async function runTuftsSCOPreAuthProcessor(
}
}
if (pdf_path && !socketId) {
if (pdf_url && !socketId) {
const claim = claimId ? await storage.getClaim(claimId).catch(() => null) : null;
const patientId = claim?.patientId ?? enrichedPayload?.claim?.patientId ?? enrichedPayload?.patientId;
if (patientId) await savePdfFromSelenium(pdf_path, Number(patientId));
if (patientId) await savePdfFromSelenium(pdf_url, Number(patientId));
}
emitToSocket(socketId, "selenium:tuftssco_preauth_completed", {
jobId,
claimId,
preAuthNumber,
pdf_path,
pdf_url,
message: preAuthNumber
? `Tufts SCO pre-authorization submitted — PreAuth #: ${preAuthNumber}`
: (seleniumResult?.message ?? "Tufts SCO pre-authorization submitted successfully"),
});
log("tuftssco-preauth-processor", "done", { claimId, preAuthNumber });
return { status: "success", pdf_path, preAuthNumber };
return { status: "success", pdf_url, preAuthNumber };
}

View File

@@ -153,7 +153,7 @@ export async function runUnitedDHPreAuthProcessor(
if (claimId) {
try {
const updates: Record<string, any> = { status: "PREAUTH" };
if (preAuthNumber) updates.claimNumber = preAuthNumber;
if (preAuthNumber) updates.preAuthNumber = preAuthNumber;
await storage.updateClaim(claimId, updates);
log("uniteddh-preauth-processor", "claim record updated", { claimId, preAuthNumber });

View File

@@ -136,12 +136,12 @@ router.post(
(folder as any).id
);
await storage.appendFileChunk((cloudFile as any).id, 0, file.buffer);
await storage.finalizeFileUpload((cloudFile as any).id);
const finalized = await storage.finalizeFileUpload((cloudFile as any).id);
result.push({
filename: file.originalname,
mimeType: file.mimetype,
filePath: `/api/cloud-storage/files/${(cloudFile as any).id}/content`,
filePath: finalized.diskPath,
});
}

View File

@@ -10,13 +10,12 @@ const router = Router();
* POST /tuftssco-preauth
*
* Enqueues a Tufts SCO (DentaQuest) pre-authorization submission job.
* Uses persistent session + OTP handling, same pattern as UnitedDH preauth.
*
* Body fields (JSON):
* data — preauth payload (memberId, dateOfBirth, serviceDate, serviceLines, patientName, etc.)
* socketId — socket.io client id
* claimId — existing claim DB id (optional)
*
* Response: { status: "queued", jobId: "…" }
*/
router.post("/tuftssco-preauth", async (req: Request, res: Response): Promise<any> => {
if (!req.user?.id) {

View File

@@ -1,6 +1,6 @@
import axios from "axios";
const SELENIUM_BASE = process.env.SELENIUM_SERVICE_URL || "http://localhost:8000";
const SELENIUM_BASE = process.env.SELENIUM_SERVICE_URL ?? "http://localhost:5002";
export async function forwardToSeleniumTuftsSCOPreAuthAgent(data: any) {
const response = await axios.post(`${SELENIUM_BASE}/tuftssco-preauth`, data, {

View File

@@ -106,7 +106,7 @@ export interface IStorage {
folderId?: number | null
): Promise<CloudFile>;
appendFileChunk(fileId: number, seq: number, data: Buffer): Promise<void>;
finalizeFileUpload(fileId: number): Promise<{ ok: true; size: string }>;
finalizeFileUpload(fileId: number): Promise<{ ok: true; size: string; diskPath: string }>;
deleteFile(fileId: number): Promise<boolean>;
updateFile(
id: number,
@@ -354,7 +354,7 @@ export const cloudStorageStorage: IStorage = {
});
await updateFolderTimestampsRecursively(file.folderId);
return { ok: true, size: BigInt(total).toString() };
return { ok: true, size: BigInt(total).toString(), diskPath };
},
async deleteFile(fileId: number) {