selenium being fixed

This commit is contained in:
2025-06-27 23:10:02 +05:30
parent aefcf950e8
commit a0876b61da
18 changed files with 12706 additions and 174 deletions

View File

@@ -4,10 +4,7 @@ import { storage } from "../storage";
import { z } from "zod";
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
import multer from "multer";
import {
forwardToSeleniumAgent,
forwardToSeleniumAgent2,
} from "../services/seleniumClient";
import { forwardToSeleniumAgent } from "../services/seleniumClient";
import path from "path";
import axios from "axios";
import fs from "fs";
@@ -110,9 +107,11 @@ router.post(
]);
res.json({ success: true, data: result });
} catch (err) {
} catch (err: any) {
console.error(err);
res.status(500).json({ error: "Failed to forward to selenium agent" });
return res.status(500).json({
error: err.message || "Failed to forward to selenium agent",
});
}
}
);
@@ -120,30 +119,34 @@ router.post(
router.post(
"/selenium/fetchpdf",
async (req: Request, res: Response): Promise<any> => {
if (!req.user || !req.user.id) {
return res.status(401).json({ error: "Unauthorized: user info missing" });
function sendError(res: Response, message: string, status = 400) {
console.error("Error:", message);
return res.status(status).json({ error: message });
}
try {
const { patientId, claimId } = req.body;
if (!patientId || !claimId) {
return res.status(400).json({ error: "Missing patientId or claimId" });
if (!req.user || !req.user.id) {
return sendError(res, "Unauthorized: user info missing", 401);
}
const { patientId, claimId, pdf_url } = req.body;
if (!pdf_url) {
return sendError(res, "Missing pdf_url");
}
if (!patientId) {
return sendError(res, "Missing Patient Id");
}
if (!claimId) {
return sendError(res, "Missing Claim Id");
}
const parsedPatientId = parseInt(patientId);
const parsedClaimId = parseInt(claimId);
const result = await forwardToSeleniumAgent2();
if (result.status !== "success") {
return res
.status(400)
.json({ error: result.message || "Failed to fetch PDF" });
}
const pdfUrl = result.pdf_url;
const filename = path.basename(new URL(pdfUrl).pathname);
const pdfResponse = await axios.get(pdfUrl, {
const filename = path.basename(new URL(pdf_url).pathname);
const pdfResponse = await axios.get(pdf_url, {
responseType: "arraybuffer",
});
@@ -166,14 +169,12 @@ router.post(
return res.json({
success: true,
pdfPath: `/temp/${filename}`,
pdfUrl,
pdf_url,
fileName: filename,
});
} catch (err) {
console.error("Error in /selenium/fetchpdf:", err);
return res
.status(500)
.json({ error: "Failed to forward to selenium agent 2" });
return sendError(res, "Failed to Fetch and Download the pdf", 500);
}
}
);

View File

@@ -36,14 +36,17 @@ export async function forwardToSeleniumAgent(
images,
};
const response = await axios.post(
const result = await axios.post(
"http://localhost:5002/start-workflow",
payload
);
return response.data;
}
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);
}
export async function forwardToSeleniumAgent2(): Promise<any> {
const response = await axios.post("http://localhost:5002/fetch-pdf");
return response.data;
}
return result.data;
}

View File

@@ -1,12 +1,14 @@
import { Switch, Route } from "wouter";
import React, { Suspense, lazy } from "react";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "./components/ui/toaster";
import { TooltipProvider } from "./components/ui/tooltip";
import { ProtectedRoute } from "./lib/protected-route";
import { AuthProvider } from "./hooks/use-auth";
import Dashboard from "./pages/dashboard";
import Dashboard from "./pages/dashboard";
import LoadingScreen from "./components/ui/LoadingScreen";
const AuthPage = lazy(() => import("./pages/auth-page"));
@@ -14,7 +16,9 @@ const AppointmentsPage = lazy(() => import("./pages/appointments-page"));
const PatientsPage = lazy(() => import("./pages/patients-page"));
const SettingsPage = lazy(() => import("./pages/settings-page"));
const ClaimsPage = lazy(() => import("./pages/claims-page"));
const PreAuthorizationsPage = lazy(() => import("./pages/preauthorizations-page"));
const PreAuthorizationsPage = lazy(
() => import("./pages/preauthorizations-page")
);
const PaymentsPage = lazy(() => import("./pages/payments-page"));
const DocumentPage = lazy(() => import("./pages/documents-page"));
const NotFound = lazy(() => import("./pages/not-found"));
@@ -23,32 +27,39 @@ function Router() {
return (
<Switch>
<ProtectedRoute path="/" component={() => <Dashboard />} />
<ProtectedRoute path="/appointments" component={() => <AppointmentsPage />} />
<ProtectedRoute
path="/appointments"
component={() => <AppointmentsPage />}
/>
<ProtectedRoute path="/patients" component={() => <PatientsPage />} />
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
<ProtectedRoute path="/preauthorizations" component={() => <PreAuthorizationsPage />} />
<ProtectedRoute
path="/preauthorizations"
component={() => <PreAuthorizationsPage />}
/>
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
<ProtectedRoute path="/documents" component={() => <DocumentPage/>}/>
<ProtectedRoute path="/documents" component={() => <DocumentPage />} />
<Route path="/auth" component={() => <AuthPage />} />
<Route component={() => <NotFound />} />
</Switch>
);
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<TooltipProvider>
<Toaster />
<Suspense fallback={<LoadingScreen />}>
<Router />
</Suspense>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<TooltipProvider>
<Toaster />
<Suspense fallback={<LoadingScreen />}>
<Router />
</Suspense>
</TooltipProvider>
</AuthProvider>
</QueryClientProvider>
</Provider>
);
}

View File

@@ -0,0 +1,50 @@
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "@/redux/store";
import { clearTaskStatus } from "@/redux/slices/seleniumTaskSlice";
import { Loader2, CheckCircle, XCircle } from "lucide-react";
export const SeleniumTaskBanner = () => {
const { status, message, show } = useSelector(
(state: RootState) => state.seleniumTask
);
const dispatch = useDispatch();
if (!show) return null;
const getIcon = () => {
switch (status) {
case "pending":
return <Loader2 className="w-5 h-5 animate-spin text-blue-500" />;
case "success":
return <CheckCircle className="w-5 h-5 text-green-500" />;
case "error":
return <XCircle className="w-5 h-5 text-red-500" />;
default:
return null;
}
};
return (
<div className="bg-white border border-gray-200 shadow-md rounded-lg p-3 mb-4 flex items-start justify-between">
<div className="flex items-start gap-3">
{getIcon()}
<div>
<div className="font-medium text-gray-800">
{status === "pending"
? "Selenium Task In Progress"
: status === "success"
? "Selenium Task Completed"
: "Selenium Task Error"}
</div>
<p className="text-gray-600 text-sm">{message}</p>
</div>
</div>
<button
onClick={() => dispatch(clearTaskStatus())}
className="text-sm text-gray-500 hover:text-gray-800"
>
</button>
</div>
);
};

View File

@@ -18,6 +18,12 @@ import { apiRequest, queryClient } from "@/lib/queryClient";
import { useLocation } from "wouter";
import RecentClaims from "@/components/claims/recent-claims";
import { Button } from "@/components/ui/button";
import { useDispatch } from "react-redux";
import {
setTaskStatus,
clearTaskStatus,
} from "@/redux/slices/seleniumTaskSlice";
import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner";
//creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
@@ -71,8 +77,8 @@ type UpdatePatient = z.infer<typeof updatePatientSchema>;
export default function ClaimsPage() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
const [isMhPopupOpen, setIsMhPopupOpen] = useState(false);
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
const dispatch = useDispatch();
const { toast } = useToast();
const { user } = useAuth();
const [claimFormData, setClaimFormData] = useState<any>({
@@ -215,38 +221,6 @@ export default function ClaimsPage() {
},
});
// selenium pdf download handler
const handleSeleniumPopup = async (actionType: string) => {
try {
if (!claimRes?.id) {
throw new Error("Missing claimId");
}
if (!selectedPatient) {
throw new Error("Missing patientId");
}
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
action: actionType,
patientId: selectedPatient,
claimId: claimRes.id,
});
const result = await res.json();
toast({
title: "Success",
description: "MH action completed.",
});
setIsMhPopupOpen(false);
setSelectedPatient(null);
} catch (error: any) {
toast({
title: "Error",
description: error.message,
variant: "destructive",
});
}
};
// Converts local date to exact UTC date with no offset issues
function parseLocalDate(dateInput: Date | string): Date {
if (dateInput instanceof Date) return dateInput;
@@ -332,6 +306,7 @@ export default function ClaimsPage() {
return res.json();
},
onSuccess: (data) => {
console.log(data)
setClaimRes(data);
toast({
title: "Claim submitted successfully",
@@ -519,7 +494,6 @@ export default function ClaimsPage() {
const handleSelenium = async (data: any) => {
const formData = new FormData();
formData.append("data", JSON.stringify(data));
const uploadedFiles: File[] = data.uploadedFiles ?? [];
uploadedFiles.forEach((file: File) => {
@@ -531,22 +505,43 @@ export default function ClaimsPage() {
});
try {
dispatch(
setTaskStatus({
status: "pending",
message: "Submitting claim to Selenium...",
})
);
const response = await apiRequest(
"POST",
"/api/claims/selenium",
formData
);
const result = await response.json();
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, Wait for its response.",
"Your claim data was successfully sent to Selenium, Waitinig for its response.",
variant: "default",
});
setIsMhPopupOpen(true);
return result;
// const result2 = await handleSeleniumPdfDownload(result1);
// return result2;
} catch (error: any) {
dispatch(
setTaskStatus({
status: "error",
message: error.message || "Selenium submission failed",
})
);
toast({
title: "Selenium service error",
description: error.message || "An error occurred.",
@@ -555,6 +550,61 @@ export default function ClaimsPage() {
}
};
// selenium pdf download handler
const handleSeleniumPdfDownload = async (data: any) => {
try {
if (!claimRes?.id) {
throw new Error("Missing claimId2s");
}
if (!selectedPatient) {
throw new Error("Missing patientId");
}
dispatch(
setTaskStatus({
status: "pending",
message: "Downloading PDF from Selenium...",
})
);
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
patientId: selectedPatient,
claimId: claimRes.id,
pdf_url: data["pdf_url"],
});
const result = await res.json();
if (result.error) throw new Error(result.error);
dispatch(
setTaskStatus({
status: "success",
message: "Claim submitted & PDF downloaded successfully.",
})
);
toast({
title: "Success",
description: "Claim Submitted and Pdf Downloaded completed.",
});
setSelectedPatient(null);
return result;
} catch (error: any) {
dispatch(
setTaskStatus({
status: "error",
message: error.message || "Failed to download PDF",
})
);
toast({
title: "Error",
description: error.message || "Failed to fetch PDF",
variant: "destructive",
});
}
};
return (
<div className="flex h-screen overflow-hidden bg-gray-100">
<Sidebar
@@ -565,6 +615,8 @@ export default function ClaimsPage() {
<div className="flex-1 flex flex-col overflow-hidden">
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
<SeleniumTaskBanner />
<main className="flex-1 overflow-y-auto p-4">
{/* Header */}
<div className="mb-6">
@@ -680,31 +732,6 @@ export default function ClaimsPage() {
onHandleForSelenium={handleSelenium}
/>
)}
{isMhPopupOpen && (
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black bg-opacity-50">
<div className="bg-white rounded-lg shadow-lg p-6 max-w-md w-full">
<h2 className="text-xl font-semibold mb-4">Selenium Handler</h2>
<div className="flex justify-between space-x-4">
<Button
className="flex-1"
onClick={() => handleSeleniumPopup("submit")}
>
Download the PDF.
</Button>
<Button
className="flex-1"
variant="secondary"
onClick={() => setIsMhPopupOpen(false)}
>
Cancel
</Button>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,5 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@@ -0,0 +1,32 @@
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 seleniumTaskSlice = createSlice({
name: "seleniumTask",
initialState,
reducers: {
setTaskStatus: (
state: SeleniumTaskState,
action: PayloadAction<Partial<SeleniumTaskState>>
) => {
return { ...state, ...action.payload, show: true };
},
clearTaskStatus: () => initialState,
},
});
export const { setTaskStatus, clearTaskStatus } = seleniumTaskSlice.actions;
export default seleniumTaskSlice.reducer;

View File

@@ -0,0 +1,11 @@
import { configureStore } from "@reduxjs/toolkit";
import seleniumTaskReducer from "./slices/seleniumTaskSlice";
export const store = configureStore({
reducer: {
seleniumTask: seleniumTaskReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

View File

@@ -19,31 +19,14 @@ async def start_workflow(request: Request):
data = await request.json()
try:
bot = AutomationMassHealth(data)
result = bot.main_workflow_upto_step2("https://providers.massdhp.com/providers_login.asp")
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)}
# Endpoint: Step 2 — Extract the PDF content after manual submission
@app.post("/fetch-pdf")
async def fetch_pdf():
try:
bot = AutomationMassHealth.get_last_instance()
if not bot:
return {"status": "error", "message": "No running automation session"}
result = bot.reach_to_pdf()
if result.get("status") != "success":
return {"status": "error", "message": result.get("message")}
return {
"status": "success",
"pdf_url": result["pdf_url"]
}
except Exception as e:
return {"status": "error", "message": str(e)}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=5002)

View File

@@ -2,6 +2,7 @@
"name": "seleniumservice",
"private": true,
"scripts": {
"postinstall": "pip install -r requirements.txt"
"postinstall": "pip install -r requirements.txt",
"dev": "python agent.py"
}
}

View File

@@ -11,16 +11,11 @@ from datetime import datetime
import tempfile
import base64
import os
import requests
import json
class AutomationMassHealth:
last_instance = None
def __init__(self, data):
self.headless = False
self.driver = None
AutomationMassHealth.last_instance = self
self.data = data
self.claim = data.get("claim", {})
@@ -36,11 +31,9 @@ class AutomationMassHealth:
self.missingTeethStatus = self.claim.get("missingTeethStatus", "")
self.missingTeeth = self.claim.get("missingTeeth", {})
@staticmethod
def get_last_instance():
return AutomationMassHealth.last_instance
def config_driver(self):
print("caled config")
options = webdriver.ChromeOptions()
if self.headless:
options.add_argument("--headless")
@@ -289,13 +282,23 @@ class AutomationMassHealth:
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(3)
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, 30)
wait = WebDriverWait(self.driver, 90)
try:
print("Waiting for PDF link to appear on success page...")
pdf_link_element = wait.until(
@@ -313,10 +316,7 @@ class AutomationMassHealth:
full_pdf_url = pdf_relative_url
print("FULL PDF LINK: ",full_pdf_url)
return {
"status": "success",
"pdf_url": full_pdf_url
}
return full_pdf_url
except Exception as e:
print(f"ERROR: {str(e)}")
@@ -328,27 +328,37 @@ class AutomationMassHealth:
finally:
if self.driver:
self.driver.quit()
AutomationMassHealth.last_instance = None
def main_workflow_upto_step2(self, url):
self.config_driver()
print("Reaching Site :", url)
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
def main_workflow(self, url):
try:
self.config_driver()
print("Reaching Site :", url)
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
if self.login().startswith("ERROR"):
return {"status": "error", "message": "Login failed"}
login_result = self.login()
if login_result.startswith("ERROR"):
return {"status": "error", "message": login_result}
if self.step1().startswith("ERROR"):
return {"status": "error", "message": "Step1 failed"}
step1_result = self.step1()
if step1_result.startswith("ERROR"):
return {"status": "error", "message": step1_result}
if self.step2().startswith("ERROR"):
return {"status": "error", "message": "Step2 failed"}
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}
print("Waiting for user to manually submit form in browser...")
return {
"status": "waiting_for_user",
"message": "Automation paused. Please submit the form manually in browser."
}
return {
"status": "success",
"pdf_url": reachToPdf_result
}
except Exception as e:
return {
"status": "error",
"message": e
}

View File

@@ -0,0 +1,6 @@
{
"win64_chromedriver_137.0.7151.119_for_137.0.7151": {
"timestamp": "27/06/2025",
"binary_path": "~\\.wdm\\drivers\\chromedriver\\win64\\137.0.7151.119\\chromedriver-win32/chromedriver.exe"
}
}

View File

@@ -0,0 +1,27 @@
// Copyright 2015 The Chromium Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.