Files
DentalManagementMH06/apps/Backend/src/routes/insuranceStatusDDMA.ts
Gitead 6cfca0d015 feat: select DDMA provider by NPI settings instead of always first
Pass the user's primary NPI provider name through the eligibility and
claim routes so the Selenium workers click the matching option in the
DDMA member-search provider dropdown (data-testid=member-search_provider_select-btn)
rather than always falling back to the first entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:32:01 -04:00

148 lines
4.6 KiB
TypeScript
Executable File

import { Router, Request, Response } from "express";
import { storage } from "../storage";
import { forwardOtpToSeleniumDdmaAgent } from "../services/seleniumDdmaInsuranceEligibilityClient";
import { io } from "../socket";
import { enqueueSeleniumJob } from "../queue/jobRunner";
const router = Router();
function log(tag: string, msg: string, ctx?: any) {
console.log(`${new Date().toISOString()} [${tag}] ${msg}`, ctx ?? "");
}
function emitSafe(socketId: string | undefined, event: string, payload: any) {
if (!socketId || !io) return;
try {
const socket = io.sockets.sockets.get(socketId);
if (socket) socket.emit(event, payload);
} catch (err: any) {
log("socket", "emit failed", { socketId, event, err: err?.message });
}
}
/**
* POST /ddma-eligibility
*
* Enqueues a DDMA eligibility check in the shared InProcessQueue
* (concurrency=1, mirrors the Python semaphore).
*
* Body:
* data — patient + search fields (memberId, dateOfBirth, …)
* socketId — socket.io client id for real-time updates
*
* Response: { status: "queued", jobId: "…" }
*
* Real-time events emitted to socketId during job execution:
* job:update { jobId, jobType, status: "active"|"completed"|"failed", … }
* selenium:ddma_session_started { session_id, jobId }
* selenium:otp_required { session_id, jobId, message }
*/
router.post(
"/ddma-eligibility",
async (req: Request, res: Response): Promise<any> => {
if (!req.body.data) {
return res
.status(400)
.json({ error: "Missing Insurance Eligibility data for selenium" });
}
if (!req.user?.id) {
return res.status(401).json({ error: "Unauthorized: user info missing" });
}
try {
const rawData =
typeof req.body.data === "string"
? JSON.parse(req.body.data)
: req.body.data;
// Fetch credentials from DB
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
req.user.id,
rawData.insuranceSiteKey
);
if (!credentials) {
return res.status(404).json({
error:
"No insurance credentials found for this provider. Please update them at the Settings page.",
});
}
// Fetch NPI providers to pick the target provider on the DDMA portal
const npiProviders = await storage.getNpiProvidersByUser(req.user.id);
const primaryProvider = npiProviders[0]; // sorted by sortOrder asc, then id asc
const enrichedData = {
...rawData,
massddmaUsername: credentials.username,
massddmaPassword: credentials.password,
providerName: primaryProvider?.providerName ?? "",
};
const socketId: string | undefined = req.body.socketId;
// Enqueue — this enforces the same concurrency=1 as all other selenium jobs
const jobId = enqueueSeleniumJob({
jobType: "ddma-eligibility-check",
userId: req.user.id,
socketId,
enrichedPayload: enrichedData,
insuranceId: String(rawData.memberId ?? "").trim(),
formFirstName: rawData.firstName,
formLastName: rawData.lastName,
formDob: rawData.dateOfBirth,
});
log("ddma-route", "job enqueued", { jobId, insuranceId: rawData.memberId });
return res.json({ status: "queued", jobId });
} catch (err: any) {
console.error("[ddma-route] enqueue failed:", err);
return res.status(500).json({
error: err.message || "Failed to enqueue DDMA selenium job",
});
}
}
);
/**
* POST /selenium/submit-otp
*
* Forwards the OTP entered by the user directly to the Python agent.
* This is a side-channel — it does NOT go through the queue.
* The polling loop inside ddmaEligibilityProcessor picks up the completed
* state after OTP is submitted.
*
* Body: { session_id, otp, socketId? }
*/
router.post(
"/selenium/submit-otp",
async (req: Request, res: Response): Promise<any> => {
const { session_id: sessionId, otp, socketId } = req.body;
if (!sessionId || !otp) {
return res.status(400).json({ error: "session_id and otp are required" });
}
try {
const r = await forwardOtpToSeleniumDdmaAgent(sessionId, otp);
emitSafe(socketId, "selenium:otp_submitted", {
session_id: sessionId,
result: r,
});
return res.json(r);
} catch (err: any) {
console.error(
"[ddma-route] submit-otp failed:",
err?.response?.data || err?.message || err
);
return res.status(500).json({
error: "Failed to forward OTP to selenium agent",
detail: err?.message || err,
});
}
}
);
export default router;