eligibility check - checkpoint1

This commit is contained in:
2025-07-14 18:12:21 +05:30
parent de94e4cbb6
commit bbd7a0089f
15 changed files with 637 additions and 160 deletions

View File

@@ -4,7 +4,7 @@ import { storage } from "../storage";
import { z } from "zod"; import { z } from "zod";
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas"; import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import multer from "multer"; import multer from "multer";
import { forwardToSeleniumAgent } from "../services/seleniumClaimClient"; import { forwardToSeleniumClaimAgent } from "../services/seleniumClaimClient";
import path from "path"; import path from "path";
import axios from "axios"; import axios from "axios";
import fs from "fs"; import fs from "fs";
@@ -101,7 +101,7 @@ router.post(
massdhpPassword: credentials.password, massdhpPassword: credentials.password,
}; };
const result = await forwardToSeleniumAgent(enrichedData, [ const result = await forwardToSeleniumClaimAgent(enrichedData, [
...pdfs, ...pdfs,
...images, ...images,
]); ]);

View File

@@ -7,6 +7,7 @@ import pdfExtractionRoutes from './pdfExtraction';
import claimsRoutes from './claims'; import claimsRoutes from './claims';
import insuranceCredsRoutes from './insuranceCreds'; import insuranceCredsRoutes from './insuranceCreds';
import documentRoutes from './documents'; import documentRoutes from './documents';
import insuranceEligibilityRoutes from './insuranceEligibility'
const router = Router(); const router = Router();
@@ -18,5 +19,7 @@ router.use('/pdfExtraction', pdfExtractionRoutes);
router.use('/claims', claimsRoutes); router.use('/claims', claimsRoutes);
router.use('/insuranceCreds', insuranceCredsRoutes); router.use('/insuranceCreds', insuranceCredsRoutes);
router.use('/documents', documentRoutes); router.use('/documents', documentRoutes);
router.use('/insuranceEligibility', insuranceEligibilityRoutes);
export default router; export default router;

View File

@@ -1,46 +1,17 @@
import { Router } from "express"; import { Router } from "express";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { storage } from "../storage"; import { storage } from "../storage";
import { z } from "zod"; import { forwardToSeleniumInsuranceEligibilityAgent } from "../services/seleniumInsuranceEligibilityClient";
import multer from "multer";
import { forwardToSeleniumAgent } from "../services/seleniumClaimClient";
const router = Router(); const router = Router();
// Routes
const multerStorage = multer.memoryStorage(); // NO DISK
const upload = multer({
storage: multerStorage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limit per file
fileFilter: (req, file, cb) => {
const allowed = [
"application/pdf",
"image/jpeg",
"image/png",
"image/webp",
];
if (allowed.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("Unsupported file type"));
}
},
});
router.post( router.post(
"/selenium", "/check",
upload.fields([
{ name: "pdfs", maxCount: 10 },
{ name: "images", maxCount: 10 },
]),
async (req: Request, res: Response): Promise<any> => { async (req: Request, res: Response): Promise<any> => {
if (!req.files || !req.body.data) { if (!req.body.data) {
return res return res
.status(400) .status(400)
.json({ error: "Missing files or claim data for selenium" }); .json({ error: "Missing Insurance Eligibility data for selenium" });
} }
if (!req.user || !req.user.id) { if (!req.user || !req.user.id) {
@@ -48,37 +19,29 @@ router.post(
} }
try { try {
const claimData = JSON.parse(req.body.data); const insuranceEligibilityData = JSON.parse(req.body.data);
const pdfs =
(req.files as Record<string, Express.Multer.File[]>).pdfs ?? [];
const images =
(req.files as Record<string, Express.Multer.File[]>).images ?? [];
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey( const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
req.user.id, req.user.id,
claimData.insuranceSiteKey insuranceEligibilityData.insuranceSiteKey
); );
if (!credentials) { if (!credentials) {
return res return res.status(404).json({
.status(404) error:
.json({ error: "No insurance credentials found for this provider." }); "No insurance credentials found for this provider, Kindly Update this at Settings Page.",
});
} }
const enrichedData = { const enrichedData = {
...claimData, ...insuranceEligibilityData,
massdhpUsername: credentials.username, massdhpUsername: credentials.username,
massdhpPassword: credentials.password, massdhpPassword: credentials.password,
}; };
const result = await forwardToSeleniumAgent(enrichedData, [ const result =
...pdfs, await forwardToSeleniumInsuranceEligibilityAgent(enrichedData);
...images,
]);
res.json({ res.json(result);
...result,
claimId: claimData.claimId,
});
} catch (err: any) { } catch (err: any) {
console.error(err); console.error(err);
return res.status(500).json({ return res.status(500).json({
@@ -88,5 +51,4 @@ router.post(
} }
); );
export default router; export default router;

View File

@@ -12,7 +12,7 @@ export interface SeleniumPayload {
}[]; }[];
} }
export async function forwardToSeleniumAgent( export async function forwardToSeleniumClaimAgent(
claimData: any, claimData: any,
files: Express.Multer.File[] files: Express.Multer.File[]
): Promise<any> { ): Promise<any> {
@@ -37,7 +37,7 @@ export async function forwardToSeleniumAgent(
}; };
const result = await axios.post( const result = await axios.post(
"http://localhost:5002/start-workflow", "http://localhost:5002/claimsubmit",
payload payload
); );
if (result.data.status === "error") { if (result.data.status === "error") {

View File

@@ -1,52 +0,0 @@
import axios from "axios";
export interface SeleniumPayload {
claim: any;
pdfs: {
originalname: string;
bufferBase64: string;
}[];
images: {
originalname: string;
bufferBase64: string;
}[];
}
export async function forwardToSeleniumAgent(
claimData: any,
files: Express.Multer.File[]
): Promise<any> {
const pdfs = files
.filter((file) => file.mimetype === "application/pdf")
.map((file) => ({
originalname: file.originalname,
bufferBase64: file.buffer.toString("base64"),
}));
const images = files
.filter((file) => file.mimetype.startsWith("image/"))
.map((file) => ({
originalname: file.originalname,
bufferBase64: file.buffer.toString("base64"),
}));
const payload: SeleniumPayload = {
claim: claimData,
pdfs,
images,
};
const result = await axios.post(
"http://localhost:5002/eligibiliy-check",
payload
);
if (result.data.status === "error") {
const errorMsg =
typeof result.data.message === "string"
? result.data.message
: result.data.message?.msg || "Selenium agent error";
throw new Error(errorMsg);
}
return result.data;
}

View File

@@ -0,0 +1,27 @@
import axios from "axios";
export interface SeleniumPayload {
data: any;
}
export async function forwardToSeleniumInsuranceEligibilityAgent(
insuranceEligibilityData: any
): Promise<any> {
const payload: SeleniumPayload = {
data: insuranceEligibilityData,
};
const result = await axios.post(
"http://localhost:5002/eligibility-check",
payload
);
if (result.data.status === "error") {
const errorMsg =
typeof result.data.message === "string"
? result.data.message
: result.data.message?.msg || "Selenium agent error";
throw new Error(errorMsg);
}
return result.data;
}

View File

@@ -1,14 +1,15 @@
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "@/redux/store";
import { clearTaskStatus } from "@/redux/slices/seleniumTaskSlice";
import { Loader2, CheckCircle, XCircle } from "lucide-react"; import { Loader2, CheckCircle, XCircle } from "lucide-react";
export const SeleniumTaskBanner = () => { export type TaskStatus = "idle" | "pending" | "success" | "error";
const { status, message, show } = useSelector(
(state: RootState) => state.seleniumTask
);
const dispatch = useDispatch();
interface Props {
status: TaskStatus;
message: string;
show: boolean;
onClear: () => void;
}
export const SeleniumTaskBanner = ({ status, message, show, onClear }: Props) => {
if (!show) return null; if (!show) return null;
const getIcon = () => { const getIcon = () => {
@@ -40,7 +41,7 @@ export const SeleniumTaskBanner = () => {
</div> </div>
</div> </div>
<button <button
onClick={() => dispatch(clearTaskStatus())} onClick={onClear}
className="text-sm text-gray-500 hover:text-gray-800" className="text-sm text-gray-500 hover:text-gray-800"
> >

View File

@@ -11,18 +11,18 @@ import {
AppointmentUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema,
ClaimUncheckedCreateInputObjectSchema, ClaimUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas"; } from "@repo/db/usedSchemas";
import { AlertCircle, CheckCircle, Clock, FileCheck } from "lucide-react"; import { FileCheck } from "lucide-react";
import { parse, format } from "date-fns"; import { parse, format } from "date-fns";
import { z } from "zod"; import { z } from "zod";
import { apiRequest, queryClient } from "@/lib/queryClient"; import { apiRequest, queryClient } from "@/lib/queryClient";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import RecentClaims from "@/components/claims/recent-claims"; import RecentClaims from "@/components/claims/recent-claims";
import { Button } from "@/components/ui/button";
import { useDispatch } from "react-redux"; import { useAppDispatch, useAppSelector } from "@/redux/hooks";
import { import {
setTaskStatus, setTaskStatus,
clearTaskStatus, clearTaskStatus,
} from "@/redux/slices/seleniumTaskSlice"; } from "@/redux/slices/seleniumClaimSubmitTaskSlice";
import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner"; import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner";
//creating types out of schema auto generated. //creating types out of schema auto generated.
@@ -78,7 +78,11 @@ export default function ClaimsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false); const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
const [selectedPatient, setSelectedPatient] = useState<number | null>(null); const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
const dispatch = useDispatch(); const dispatch = useAppDispatch();
const { status, message, show } = useAppSelector(
(state) => state.seleniumClaimSubmitTask
);
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
const [claimFormData, setClaimFormData] = useState<any>({ const [claimFormData, setClaimFormData] = useState<any>({
@@ -428,7 +432,6 @@ export default function ClaimsPage() {
} }
}, [memberId, dob]); }, [memberId, dob]);
const getDisplayProvider = (provider: string) => { const getDisplayProvider = (provider: string) => {
const insuranceMap: Record<string, string> = { const insuranceMap: Record<string, string> = {
delta: "Delta Dental", delta: "Delta Dental",
@@ -614,8 +617,12 @@ export default function ClaimsPage() {
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<TopAppBar toggleMobileMenu={toggleMobileMenu} /> <TopAppBar toggleMobileMenu={toggleMobileMenu} />
<SeleniumTaskBanner
<SeleniumTaskBanner /> status={status}
message={message}
show={show}
onClear={() => dispatch(clearTaskStatus())}
/>
<main className="flex-1 overflow-y-auto p-4"> <main className="flex-1 overflow-y-auto p-4">
{/* Header */} {/* Header */}

View File

@@ -27,6 +27,12 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { apiRequest, queryClient } from "@/lib/queryClient"; import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAppDispatch, useAppSelector } from "@/redux/hooks";
import {
setTaskStatus,
clearTaskStatus,
} from "@/redux/slices/seleniumEligibilityCheckTaskSlice";
import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner";
const PatientSchema = ( const PatientSchema = (
PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any> PatientUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
@@ -47,6 +53,11 @@ type InsertPatient = z.infer<typeof insertPatientSchema>;
export default function InsuranceEligibilityPage() { export default function InsuranceEligibilityPage() {
const { user } = useAuth(); const { user } = useAuth();
const { toast } = useToast(); const { toast } = useToast();
const dispatch = useAppDispatch();
const { status, message, show } = useAppSelector(
(state) => state.seleniumEligibilityCheckTask
);
const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null); const [selectedPatient, setSelectedPatient] = useState<Patient | null>(null);
@@ -113,7 +124,7 @@ export default function InsuranceEligibilityPage() {
}, },
}); });
// Insurance eligibility check mutation // Insurance eligibility check mutation --- not using right now
const checkInsuranceMutation = useMutation({ const checkInsuranceMutation = useMutation({
mutationFn: async ({ mutationFn: async ({
provider, provider,
@@ -155,6 +166,67 @@ export default function InsuranceEligibilityPage() {
}, },
}); });
// handle selenium
const handleSelenium = async () => {
const data = {memberId, dateOfBirth, insuranceSiteKey: "MH", };
try {
dispatch(
setTaskStatus({
status: "pending",
message: "Sending Data to Selenium...",
})
);
const response = await apiRequest(
"POST",
"/api/insuranceEligibility/check",
{ data: JSON.stringify(data) }
);
const result1 = await response.json();
if (result1.error) throw new Error(result1.error);
dispatch(
setTaskStatus({
status: "pending",
message: "Submitted to Selenium. Awaiting PDF...",
})
);
toast({
title: "Selenium service notified",
description:
"Your claim data was successfully sent to Selenium, Waitinig for its response.",
variant: "default",
});
} catch (error: any) {
dispatch(
setTaskStatus({
status: "error",
message: error.message || "Selenium submission failed",
})
);
toast({
title: "Selenium service error",
description: error.message || "An error occurred.",
variant: "destructive",
});
}
};
const handleAddPatient = () => {
const newPatient: InsertPatient = {
firstName,
lastName,
dateOfBirth: dateOfBirth,
gender: "",
phone: "",
userId: user?.id ?? 1,
status: "active",
insuranceId: memberId,
};
addPatientMutation.mutate(newPatient);
}
// Handle insurance provider button clicks // Handle insurance provider button clicks
const handleMHButton = () => { const handleMHButton = () => {
// Form Fields check // Form Fields check
@@ -169,19 +241,9 @@ export default function InsuranceEligibilityPage() {
} }
// Adding patient if same patient exists then it will skip. // Adding patient if same patient exists then it will skip.
const newPatient: InsertPatient = { handleAddPatient();
firstName,
lastName,
dateOfBirth: dateOfBirth,
gender: "",
phone: "",
userId: user?.id ?? 1,
status: "active",
insuranceId: memberId,
};
addPatientMutation.mutate(newPatient);
handleSelenium();
}; };
@@ -196,6 +258,13 @@ export default function InsuranceEligibilityPage() {
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<TopAppBar toggleMobileMenu={toggleMobileMenu} /> <TopAppBar toggleMobileMenu={toggleMobileMenu} />
<SeleniumTaskBanner
status={status}
message={message}
show={show}
onClear={() => dispatch(clearTaskStatus())}
/>
<main className="flex-1 overflow-auto p-6"> <main className="flex-1 overflow-auto p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}

View File

@@ -0,0 +1,33 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export type TaskStatus = "idle" | "pending" | "success" | "error";
export interface SeleniumTaskState {
status: TaskStatus;
message: string;
show: boolean;
}
const initialState: SeleniumTaskState = {
status: "idle",
message: "",
show: false,
};
const seleniumClaimSubmitTaskSlice = createSlice({
name: "seleniumClaimSubmitTask",
initialState,
reducers: {
setTaskStatus: (
state: SeleniumTaskState,
action: PayloadAction<Partial<SeleniumTaskState>>
) => {
return { ...state, ...action.payload, show: true };
},
clearTaskStatus: () => initialState,
},
});
// ✅ Make sure you're exporting from the renamed slice
export const { setTaskStatus, clearTaskStatus } = seleniumClaimSubmitTaskSlice.actions;
export default seleniumClaimSubmitTaskSlice.reducer;

View File

@@ -14,8 +14,8 @@ const initialState: SeleniumTaskState = {
show: false, show: false,
}; };
const seleniumTaskSlice = createSlice({ const seleniumEligibilityCheckTaskSlice = createSlice({
name: "seleniumTask", name: "seleniumEligibilityCheckTask",
initialState, initialState,
reducers: { reducers: {
setTaskStatus: ( setTaskStatus: (
@@ -28,5 +28,5 @@ const seleniumTaskSlice = createSlice({
}, },
}); });
export const { setTaskStatus, clearTaskStatus } = seleniumTaskSlice.actions; export const { setTaskStatus, clearTaskStatus } = seleniumEligibilityCheckTaskSlice.actions;
export default seleniumTaskSlice.reducer; export default seleniumEligibilityCheckTaskSlice.reducer;

View File

@@ -1,9 +1,11 @@
import { configureStore } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit";
import seleniumTaskReducer from "./slices/seleniumTaskSlice"; import seleniumClaimSubmitTaskReducer from "./slices/seleniumClaimSubmitTaskSlice";
import seleniumEligibilityCheckTaskReducer from "./slices/seleniumEligibilityCheckTaskSlice";
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
seleniumTask: seleniumTaskReducer, seleniumClaimSubmitTask: seleniumClaimSubmitTaskReducer,
seleniumEligibilityCheckTask: seleniumEligibilityCheckTaskReducer,
}, },
}); });

View File

@@ -1,9 +1,20 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import uvicorn import uvicorn
from selenium_worker import AutomationMassHealth import asyncio
from selenium_claimSubmitWorker import AutomationMassHealth
from selenium_eligibilityCheckWorker import AutomationMassHealthEligibilityCheck
app = FastAPI() app = FastAPI()
# Allow 1 selenium session at a time
semaphore = asyncio.Semaphore(1)
# Manual counters to track active & queued jobs
active_jobs = 0
waiting_jobs = 0
lock = asyncio.Lock() # To safely update counters
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@@ -12,11 +23,20 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# Endpoint: Step 1 — Start the automation of submitting Claim.
# Endpoint: Step 1 — Start the automation @app.post("/claimsubmit")
@app.post("/start-workflow")
async def start_workflow(request: Request): async def start_workflow(request: Request):
global active_jobs, waiting_jobs
data = await request.json() data = await request.json()
async with lock:
waiting_jobs += 1
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try: try:
bot = AutomationMassHealth(data) bot = AutomationMassHealth(data)
result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp") result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp")
@@ -27,6 +47,47 @@ async def start_workflow(request: Request):
return result return result
except Exception as e: except Exception as e:
return {"status": "error", "message": str(e)} return {"status": "error", "message": str(e)}
finally:
async with lock:
active_jobs -= 1
# Endpoint: Step 2 — Start the automation of cheking eligibility
@app.post("/eligibility-check")
async def start_workflow(request: Request):
global active_jobs, waiting_jobs
data = await request.json()
async with lock:
waiting_jobs += 1
async with semaphore:
async with lock:
waiting_jobs -= 1
active_jobs += 1
try:
print(data)
bot = AutomationMassHealthEligibilityCheck(data)
result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp")
if result.get("status") != "success":
return {"status": "error", "message": result.get("message")}
return result
except Exception as e:
return {"status": "error", "message": str(e)}
finally:
async with lock:
active_jobs -= 1
# ✅ Status Endpoint
@app.get("/status")
async def get_status():
async with lock:
return {
"active_jobs": active_jobs,
"queued_jobs": waiting_jobs,
"status": "busy" if active_jobs > 0 or waiting_jobs > 0 else "idle"
}
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5002) uvicorn.run(app, host="0.0.0.0", port=5002)

View File

@@ -0,0 +1,364 @@
from selenium import webdriver
from selenium.common import TimeoutException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import Select
import time
from datetime import datetime
import tempfile
import base64
import os
class AutomationMassHealthEligibilityCheck:
def __init__(self, data):
self.headless = False
self.driver = None
self.data = data
self.claim = data.get("claim", {})
self.upload_files = data.get("pdfs", []) + data.get("images", [])
# Flatten values for convenience
self.memberId = self.claim.get("memberId", "")
self.dateOfBirth = self.claim.get("dateOfBirth", "")
self.remarks = self.claim.get("remarks", "")
self.massdhp_username = self.claim.get("massdhpUsername", "")
self.massdhp_password = self.claim.get("massdhpPassword", "")
self.serviceLines = self.claim.get("serviceLines", [])
self.missingTeethStatus = self.claim.get("missingTeethStatus", "")
self.missingTeeth = self.claim.get("missingTeeth", {})
def config_driver(self):
options = webdriver.ChromeOptions()
if self.headless:
options.add_argument("--headless")
s = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=s, options=options)
self.driver = driver
def login(self):
wait = WebDriverWait(self.driver, 30)
try:
# Enter email
email_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='Email' and @type='text']")))
email_field.clear()
email_field.send_keys(self.massdhp_username)
# Enter password
password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='Pass' and @type='password']")))
password_field.clear()
password_field.send_keys(self.massdhp_password)
# Click login
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Login']")))
login_button.click()
return "Success"
except Exception as e:
print(f"Error while logging in: {e}")
return "ERROR:LOGIN FAILED"
def step1(self):
wait = WebDriverWait(self.driver, 30)
try:
claim_upload_link = wait.until(
EC.element_to_be_clickable((By.XPATH, "//a[text()='Claim Upload']"))
)
claim_upload_link.click()
time.sleep(3)
# Fill Member ID
member_id_input = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Text1"]')))
member_id_input.clear()
member_id_input.send_keys(self.memberId)
# Fill DOB parts
try:
dob_parts = self.dateOfBirth.split("/")
month= dob_parts[0].zfill(2) # "12"
day= dob_parts[1].zfill(2) # "13"
year = dob_parts[2] # "1965"
except Exception as e:
print(f"Error parsing DOB: {e}")
return "ERROR: PARSING DOB"
wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Text2"]'))).send_keys(month)
wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Text3"]'))).send_keys(day)
wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Text4"]'))).send_keys(year)
# Rendering Provider NPI dropdown
npi_dropdown = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Select1"]')))
select_npi = Select(npi_dropdown)
select_npi.select_by_index(1)
# Office Location dropdown
location_dropdown = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="Select2"]')))
select_location = Select(location_dropdown)
select_location.select_by_index(1)
# Click Continue button
continue_btn = wait.until(EC.element_to_be_clickable((By.XPATH, '//input[@type="submit" and @value="Continue"]')))
continue_btn.click()
# Check for error message
try:
error_msg = WebDriverWait(self.driver, 5).until(EC.presence_of_element_located(
(By.XPATH, "//td[@class='text_err_msg' and contains(text(), 'Invalid Member ID or Date of Birth')]")
))
if error_msg:
print("Error: Invalid Member ID or Date of Birth.")
return "ERROR: INVALID MEMBERID OR DOB"
except TimeoutException:
pass
return "Success"
except Exception as e:
print(f"Error while step1 i.e Cheking the MemberId and DOB in: {e}")
return "ERROR:STEP1"
def step2(self):
wait = WebDriverWait(self.driver, 30)
# already waiting in step1 last part, so no time sleep.
# 1 - Procedure Codes part
try:
for proc in self.serviceLines:
# Wait for Procedure Code dropdown and select code
select_element = wait.until(EC.presence_of_element_located((By.XPATH, "//select[@id='Select3']")))
Select(select_element).select_by_value(proc['procedureCode'])
# Fill Procedure Date if present
if proc.get("procedureDate"):
try:
# Try to normalize date to MM/DD/YYYY format
parsed_date = datetime.strptime(proc["procedureDate"], "%Y-%m-%d")
formatted_date = parsed_date.strftime("%m/%d/%Y")
date_xpath = "//input[@name='ProcedureDate']"
wait.until(EC.presence_of_element_located((By.XPATH, date_xpath))).clear()
self.driver.find_element(By.XPATH, date_xpath).send_keys(formatted_date)
except ValueError:
# Invalid date format - skip filling ProcedureDate field
pass
# Fill Oral Cavity Area if present
if proc.get("oralCavityArea"):
oral_xpath = "//input[@name='OralCavityArea']"
wait.until(EC.presence_of_element_located((By.XPATH, oral_xpath))).clear()
self.driver.find_element(By.XPATH, oral_xpath).send_keys(proc["oralCavityArea"])
# Fill Tooth Number if present
if proc.get("toothNumber"):
tooth_num_dropdown = wait.until(EC.presence_of_element_located((By.XPATH, "//select[@name='ToothNumber']")))
select_tooth = Select(tooth_num_dropdown)
select_tooth.select_by_value(proc["toothNumber"])
# Fill Tooth Surface if present
if proc.get("toothSurface"):
surfaces = proc["toothSurface"].split(",")
for surface in surfaces:
surface = surface.strip()
checkbox_xpath = f"//input[@type='checkbox' and @name='TS_{surface}']"
checkbox = wait.until(EC.element_to_be_clickable((By.XPATH, checkbox_xpath)))
if not checkbox.is_selected():
checkbox.click()
# Fill Fees if present
if proc.get("billedAmount"):
fees_xpath = "//input[@name='ProcedureFee']"
wait.until(EC.presence_of_element_located((By.XPATH, fees_xpath))).clear()
self.driver.find_element(By.XPATH, fees_xpath).send_keys(proc["billedAmount"])
# Click "Add Procedure" button
add_proc_xpath = "//input[@type='submit' and @value='Add Procedure']"
wait.until(EC.element_to_be_clickable((By.XPATH, add_proc_xpath))).click()
time.sleep(1)
except Exception as e:
print(f"Error while filling Procedure Codes: {e}")
return "ERROR:PROCEDURE CODES"
# 2 - Upload PDFs:
try:
with tempfile.TemporaryDirectory() as tmp_dir:
for file_obj in self.upload_files:
base64_data = file_obj["bufferBase64"]
file_name = file_obj.get("originalname", "tempfile.bin")
# Ensure valid extension fallback if missing
if not any(file_name.lower().endswith(ext) for ext in [".pdf", ".jpg", ".jpeg", ".png", ".webp"]):
file_name += ".bin"
# Full path with original filename inside temp dir
tmp_file_path = os.path.join(tmp_dir, file_name)
# Decode and save
with open(tmp_file_path, "wb") as tmp_file:
tmp_file.write(base64.b64decode(base64_data))
# Upload using Selenium
file_input = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='FileName' and @type='file']")))
file_input.send_keys(tmp_file_path)
upload_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Upload File']")))
upload_button.click()
time.sleep(3)
except Exception as e:
print(f"Error while uploading PDFs: {e}")
return "ERROR:PDF FAILED"
# 3 - Indicate Missing Teeth Part
try:
# Handle the missing teeth section based on the status
missing_status = self.missingTeethStatus.strip() if self.missingTeethStatus else "No_missing"
if missing_status == "No_missing":
missing_teeth_no = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@type='checkbox' and @name='PAU_Step3_Checkbox1']")))
missing_teeth_no.click()
elif missing_status == "endentulous":
missing_teeth_edentulous = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@type='checkbox' and @name='PAU_Step3_Checkbox2']")))
missing_teeth_edentulous.click()
elif missing_status == "Yes_missing":
missing_teeth_dict = self.missingTeeth
# For each tooth in the missing teeth dict, select the dropdown option
for tooth_name, value in missing_teeth_dict.items():
if value: # only if there's a value to select
select_element = wait.until(EC.presence_of_element_located((By.XPATH, f"//select[@name='{tooth_name}']")))
select_obj = Select(select_element)
select_obj.select_by_value(value)
# Wait for upload button and click it
update_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Update Missing Teeth']")))
update_button.click()
time.sleep(3)
except Exception as e:
print(f"Error while filling missing teeth: {e}")
return "ERROR:MISSING TEETH FAILED"
# 4 - Update Remarks
try:
if self.remarks.strip():
textarea = wait.until(EC.presence_of_element_located((By.XPATH, "//textarea[@name='Remarks']")))
textarea.clear()
textarea.send_keys(self.remarks)
# Wait for update button and click it
update_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Update Remarks']")))
update_button.click()
time.sleep(3)
except Exception as e:
print(f"Error while filling remarks: {e}")
return "ERROR:REMARKS FAILED"
# 5 - close buton
try:
close_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Submit Request']")))
close_button.click()
time.sleep(1)
# Switch to alert and accept it
try:
wait.until(EC.alert_is_present())
alert = self.driver.switch_to.alert
alert.accept()
except TimeoutException:
print("No alert appeared after clicking the button.")
time.sleep(1)
except Exception as e:
print(f"Error while Closing: {e}")
return "ERROR:CLOSE FAILED"
return "Success"
def reach_to_pdf(self):
wait = WebDriverWait(self.driver, 90)
try:
pdf_link_element = wait.until(
EC.element_to_be_clickable((By.XPATH, "//a[contains(@href, '.pdf')]"))
)
time.sleep(5)
pdf_relative_url = pdf_link_element.get_attribute("href")
if not pdf_relative_url.startswith("http"):
full_pdf_url = f"https://providers.massdhp.com{pdf_relative_url}"
else:
full_pdf_url = pdf_relative_url
print("FULL PDF LINK: ",full_pdf_url)
return full_pdf_url
except Exception as e:
print(f"ERROR: {str(e)}")
return {
"status": "error",
"message": str(e),
}
finally:
if self.driver:
self.driver.quit()
def main_workflow(self, url):
try:
self.config_driver()
print("Reaching Site :", url)
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
login_result = self.login()
if login_result.startswith("ERROR"):
return {"status": "error", "message": login_result}
step1_result = self.step1()
if step1_result.startswith("ERROR"):
return {"status": "error", "message": step1_result}
step2_result = self.step2()
if step2_result.startswith("ERROR"):
return {"status": "error", "message": step2_result}
reachToPdf_result = self.reach_to_pdf()
if reachToPdf_result.startswith("ERROR"):
return {"status": "error", "message": reachToPdf_result}
return {
"status": "success",
"pdf_url": reachToPdf_result
}
except Exception as e:
return {
"status": "error",
"message": e
}