selenium fetchpdf checkpoint

This commit is contained in:
2025-06-09 19:59:58 +05:30
parent d3cf01b65b
commit f585111b23
8 changed files with 309 additions and 66 deletions

View File

@@ -4,8 +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/seleniumClient"; import { forwardToSeleniumAgent, forwardToSeleniumAgent2 } from "../services/seleniumClient";
const router = Router(); const router = Router();
@@ -43,38 +42,65 @@ const ExtendedClaimSchema = (
const multerStorage = multer.memoryStorage(); // NO DISK const multerStorage = multer.memoryStorage(); // NO DISK
const upload = multer({ storage: multerStorage }); const upload = multer({ storage: multerStorage });
router.post("/selenium", upload.array("pdfs"), async (req: Request, res: Response): Promise<any> => { router.post(
if (!req.files || !req.body.data) { "/selenium",
return res.status(400).json({ error: "Missing files or claim data for selenium" }); upload.array("pdfs"),
} async (req: Request, res: Response): Promise<any> => {
if (!req.files || !req.body.data) {
if (!req.user || !req.user.id) { return res
return res.status(401).json({ error: "Unauthorized: user info missing" }); .status(400)
} .json({ error: "Missing files or claim data for selenium" });
try {
const claimData = JSON.parse(req.body.data);
const files = req.files as Express.Multer.File[];
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, claimData.insuranceSiteKey);
if (!credentials) {
return res.status(404).json({ error: "No insurance credentials found for this provider." });
} }
const enrichedData = { if (!req.user || !req.user.id) {
...claimData, return res.status(401).json({ error: "Unauthorized: user info missing" });
massdhpUsername: credentials.username, }
massdhpPassword: credentials.password,
};
const result = await forwardToSeleniumAgent(enrichedData, files);
res.json({ success: true, data: result }); try {
} catch (err) { const claimData = JSON.parse(req.body.data);
console.error(err); const files = req.files as Express.Multer.File[];
res.status(500).json({ error: "Failed to forward to selenium agent" });
const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(
req.user.id,
claimData.insuranceSiteKey
);
if (!credentials) {
return res
.status(404)
.json({ error: "No insurance credentials found for this provider." });
}
const enrichedData = {
...claimData,
massdhpUsername: credentials.username,
massdhpPassword: credentials.password,
};
const result = await forwardToSeleniumAgent(enrichedData, files);
res.json({ success: true, data: result });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Failed to forward to selenium agent" });
}
} }
}); );
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" });
}
try {
const result = await forwardToSeleniumAgent2();
res.json({ success: true, data: result });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Failed to forward to selenium agent 2" });
}
}
);
// GET /api/claims?page=1&limit=5 // GET /api/claims?page=1&limit=5
router.get("/", async (req: Request, res: Response) => { router.get("/", async (req: Request, res: Response) => {
@@ -110,7 +136,6 @@ router.get("/recent", async (req: Request, res: Response) => {
} }
}); });
// Get all claims for the logged-in user // Get all claims for the logged-in user
router.get("/all", async (req: Request, res: Response) => { router.get("/all", async (req: Request, res: Response) => {
try { try {
@@ -121,7 +146,6 @@ router.get("/all", async (req: Request, res: Response) => {
} }
}); });
// Get a single claim by ID // Get a single claim by ID
router.get("/:id", async (req: Request, res: Response): Promise<any> => { router.get("/:id", async (req: Request, res: Response): Promise<any> => {
try { try {

View File

@@ -8,7 +8,7 @@ export interface SeleniumPayload {
}[]; }[];
} }
export default async function forwardToSeleniumAgent( export async function forwardToSeleniumAgent(
claimData: any, claimData: any,
files: Express.Multer.File[] files: Express.Multer.File[]
): Promise<any> { ): Promise<any> {
@@ -20,6 +20,13 @@ export default async function forwardToSeleniumAgent(
})), })),
}; };
const response = await axios.post("http://localhost:5002/run", payload); const response = await axios.post("http://localhost:5002/start-workflow", payload);
return response.data;
}
export async function forwardToSeleniumAgent2(
): Promise<any> {
const response = await axios.post("http://localhost:5002/fetch-pdf");
return response.data; return response.data;
} }

View File

@@ -360,9 +360,9 @@ export function ClaimForm({
// 3. Create Claim(if not) // 3. Create Claim(if not)
// Filter out empty service lines (empty procedureCode) // Filter out empty service lines (empty procedureCode)
const filteredServiceLines = form.serviceLines.filter( const filteredServiceLines = form.serviceLines.filter(
(line) => line.procedureCode.trim() !== "" (line) => line.procedureCode.trim() !== ""
); );
const { uploadedFiles, insuranceSiteKey, ...formToCreateClaim } = form; const { uploadedFiles, insuranceSiteKey, ...formToCreateClaim } = form;
onSubmit({ onSubmit({
@@ -377,7 +377,7 @@ export function ClaimForm({
// 4. sending form data to selenium service // 4. sending form data to selenium service
onHandleForSelenium({ onHandleForSelenium({
...form, ...form,
serviceLines: filteredServiceLines, serviceLines: filteredServiceLines,
staffId: Number(staff?.id), staffId: Number(staff?.id),
patientId: patientId, patientId: patientId,
insuranceProvider: "Mass Health", insuranceProvider: "Mass Health",

View File

@@ -16,6 +16,7 @@ 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";
//creating types out of schema auto generated. //creating types out of schema auto generated.
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>; type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
@@ -68,6 +69,7 @@ type UpdatePatient = z.infer<typeof updatePatientSchema>;
export default function ClaimsPage() { 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 [isMhPopupOpen, setIsMhPopupOpen] = useState(false);
const [selectedPatient, setSelectedPatient] = useState<number | null>(null); const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
const { toast } = useToast(); const { toast } = useToast();
const { user } = useAuth(); const { user } = useAuth();
@@ -210,6 +212,29 @@ export default function ClaimsPage() {
}, },
}); });
// selenium pdf download handler
const handleSeleniumPopup = async (actionType: string) => {
try {
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
action: actionType,
});
const result = await res.json();
toast({
title: "Success",
description: "MH action completed.",
});
setIsMhPopupOpen(false);
} catch (error: any) {
toast({
title: "Error",
description: error.message,
variant: "destructive",
});
}
};
// Converts local date to exact UTC date with no offset issues // Converts local date to exact UTC date with no offset issues
function parseLocalDate(dateInput: Date | string): Date { function parseLocalDate(dateInput: Date | string): Date {
if (dateInput instanceof Date) return dateInput; if (dateInput instanceof Date) return dateInput;
@@ -496,7 +521,7 @@ export default function ClaimsPage() {
description: "Your claim data was successfully sent to Selenium.", description: "Your claim data was successfully sent to Selenium.",
variant: "default", variant: "default",
}); });
setIsMhPopupOpen(true);
return result; return result;
} catch (error: any) { } catch (error: any) {
toast({ toast({
@@ -630,6 +655,31 @@ export default function ClaimsPage() {
onHandleForSelenium={handleSelenium} 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> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
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 AutomationMassDHP from selenium_worker import AutomationMassHealth
app = FastAPI() app = FastAPI()
@@ -12,15 +12,38 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
@app.post("/run")
async def run_bot(request: Request): # Endpoint: Step 1 — Start the automation
@app.post("/start-workflow")
async def start_workflow(request: Request):
data = await request.json() data = await request.json()
try: try:
bot = AutomationMassDHP(data) bot = AutomationMassHealth(data)
result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp") result = bot.main_workflow_upto_step2("https://providers.massdhp.com/providers_login.asp")
return result return result
except Exception as e: except Exception as e:
return {"status": "error", "message": str(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"}
pdf_data = bot.reach_to_pdf()
if pdf_data.get("status") != "success":
return {"status": "error", "message": pdf_data.get("message")}
return {
"status": "success",
"pdf_url": pdf_data["pdf_url"],
"pdf_base64": pdf_data["pdf_bytes"]
}
except Exception as e:
return {"status": "error", "message": str(e)}
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,26 @@
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from selenium_worker import AutomationMassDHP
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Replace with your frontend domain for security
allow_methods=["*"],
allow_headers=["*"],
)
@app.post("/run")
async def run_bot(request: Request):
data = await request.json()
try:
bot = AutomationMassDHP(data)
result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp")
return result
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

@@ -0,0 +1,42 @@
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from selenium_worker import AutomationMassHealth
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Replace with your frontend domain for security
allow_methods=["*"],
allow_headers=["*"],
)
# Endpoint: Step 1 — Start the automation
@app.post("/start-workflow")
async def start_workflow(request: Request):
data = await request.json()
try:
bot = AutomationMassHealth(data)
result = bot.main_workflow_upto_step2("https://abc.com/providers_login.asp")
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()
pdf_data = bot.reach_to_pdf()
if not pdf_data:
return {"status": "error", "message": "Failed to fetch PDF"}
return {"status": "success", "pdf_data": pdf_data}
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

@@ -11,12 +11,16 @@ from datetime import datetime
import tempfile import tempfile
import base64 import base64
import os import os
import requests
import json
class AutomationMassHealth:
last_instance = None
class AutomationMassDHP:
def __init__(self, data): def __init__(self, data):
self.headless = False self.headless = False
self.driver = None self.driver = None
AutomationMassHealth.last_instance = self
self.data = data self.data = data
self.claim = data.get("claim", {}) self.claim = data.get("claim", {})
@@ -31,6 +35,10 @@ class AutomationMassDHP:
self.serviceLines = self.claim.get("serviceLines", []) self.serviceLines = self.claim.get("serviceLines", [])
self.missingTeethStatus = self.claim.get("missingTeethStatus", "") self.missingTeethStatus = self.claim.get("missingTeethStatus", "")
self.missingTeeth = self.claim.get("missingTeeth", {}) self.missingTeeth = self.claim.get("missingTeeth", {})
@staticmethod
def get_last_instance():
return AutomationMassHealth.last_instance
def config_driver(self): def config_driver(self):
options = webdriver.ChromeOptions() options = webdriver.ChromeOptions()
@@ -279,35 +287,98 @@ class AutomationMassDHP:
return "ERROR:REMARKS FAILED" return "ERROR:REMARKS FAILED"
return "Success" return "Success"
def reach_to_pdf(self):
wait = WebDriverWait(self.driver, 30)
try:
print("Waiting for PDF link to appear on success page...")
pdf_link_element = wait.until(
EC.element_to_be_clickable((By.XPATH, "//a[contains(@href, '.pdf')]"))
)
print("PDF link found. Clicking it...")
# Click the PDF link
pdf_link_element.click()
time.sleep(5)
existing_windows = self.driver.window_handles
# Wait for the new tab
WebDriverWait(self.driver, 90).until(
lambda d: len(d.window_handles) > len(existing_windows)
)
print("Switching to PDF tab...")
self.driver.switch_to.window(self.driver.window_handles[1])
def main_workflow(self, url): time.sleep(2)
current_url = self.driver.current_url
print(f"Switched to PDF tab. Current URL: {current_url}")
# Get full PDF URL in case it's a relative path
pdf_url = pdf_link_element.get_attribute("href")
if not pdf_url.startswith("http"):
base_url = self.driver.current_url.split("/providers")[0]
pdf_url = f"{base_url}/{pdf_url}"
# Get cookies from Selenium session, saving just for my referece while testing. in prod just use below one line
# cookies = {c['name']: c['value'] for c in self.driver.get_cookies()}
# 1. Get raw Selenium cookies (list of dicts)
raw_cookies = self.driver.get_cookies()
with open("raw_cookies.txt", "w") as f:
json.dump(raw_cookies, f, indent=2)
formatted_cookies = {c['name']: c['value'] for c in raw_cookies}
with open("formatted_cookies.txt", "w") as f:
for k, v in formatted_cookies.items():
f.write(f"{k}={v}\n")
# Use requests to download the file using session cookies
print("Downloading PDF content via requests...")
pdf_response = requests.get(pdf_url, cookies=formatted_cookies)
if pdf_response.status_code == 200:
print("PDF successfully fetched (bytes length):")
return {
"status": "success",
"pdf_bytes": base64.b64encode(pdf_response.content).decode(),
}
else:
print("Failed to fetch PDF. Status:", pdf_response.status_code, pdf_response)
return {
"status": "error",
"message": pdf_response,
}
except Exception as e:
print(f"ERROR: {str(e)}")
return {
"status": "error",
"message": str(e),
}
def main_workflow_upto_step2(self, url):
self.config_driver() self.config_driver()
print("Reaching Site :", url) print("Reaching Site :", url)
self.driver.maximize_window() self.driver.maximize_window()
self.driver.get(url) self.driver.get(url)
time.sleep(3) time.sleep(3)
value = self.login()
if value.startswith("ERROR"):
# self.driver.close()
return value
time.sleep(5) if self.login().startswith("ERROR"):
value2 = self.step1() return {"status": "error", "message": "Login failed"}
if value2.startswith("ERROR"):
# self.driver.close()
return value2
time.sleep(5) if self.step1().startswith("ERROR"):
value3 = self.step2() return {"status": "error", "message": "Step1 failed"}
if value3.startswith("ERROR"):
# self.driver.close()
return value3
if self.step2().startswith("ERROR"):
return {"status": "error", "message": "Step2 failed"}
input("should Close?") # here it sholud get confirmation from the frontend, print("Waiting for user to manually submit form in browser...")
return {
self.driver.close() "status": "waiting_for_user",
"message": "Automation paused. Please submit the form manually in browser."
return {"status": "success", "message": "Successfully submitted the form."} }