checkpoint date working

This commit is contained in:
2025-06-01 18:35:51 +05:30
parent c91d5efb05
commit 0844c1296e
15 changed files with 511 additions and 77 deletions

View File

@@ -3,6 +3,9 @@ import { Request, Response } from "express";
import { storage } from "../storage"; 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 forwardToSeleniumAgent from "../services/seleniumClient";
const router = Router(); const router = Router();
@@ -37,6 +40,26 @@ const ExtendedClaimSchema = (
}); });
// Routes // Routes
const multerStorage = multer.memoryStorage(); // NO DISK
const upload = multer({ storage: multerStorage });
router.post("/selenium", upload.array("pdfs"), async (req: Request, res: Response): Promise<any> => {
if (!req.files || !req.body.data) {
return res.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 result = await forwardToSeleniumAgent(claimData, files);
res.json({ success: true, data: result });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Failed to forward to selenium agent" });
}
});
// Get all claims for the logged-in user // Get all claims for the logged-in user
router.get("/", async (req: Request, res: Response) => { router.get("/", async (req: Request, res: Response) => {

View File

@@ -200,18 +200,10 @@ router.delete(
.json({ message: "Forbidden: Patient belongs to a different user" }); .json({ message: "Forbidden: Patient belongs to a different user" });
} }
const appointments = await storage.getAppointmentsByPatientId(patientId);
console.log(appointments)
if (appointments.length > 0) {
throw new Error(`Cannot delete patient with ID ${patientId} because they have appointments`);
}
// Delete patient // Delete patient
await storage.deletePatient(patientId); await storage.deletePatient(patientId);
res.status(204).send(); res.status(204).send();
} catch (error:any) { } catch (error:any) {
if (error.message.includes("have appointments")) {
return res.status(400).json({ message: error.message });
}
console.error("Delete patient error:", error); console.error("Delete patient error:", error);
res.status(500).json({ message: "Failed to delete patient" }); res.status(500).json({ message: "Failed to delete patient" });
} }

View File

@@ -2,7 +2,7 @@ import { Router } from "express";
import type { Request, Response } from "express"; import type { Request, Response } from "express";
const router = Router(); const router = Router();
import multer from "multer"; import multer from "multer";
import forwardToPythonService from "../services/pythonClient"; import forwardToPdfService from "../services/PdfClient";
const upload = multer({ storage: multer.memoryStorage() }); const upload = multer({ storage: multer.memoryStorage() });
@@ -12,7 +12,7 @@ router.post("/extract", upload.single("pdf"), async (req: Request, res: Response
} }
try { try {
const result = await forwardToPythonService(req.file); const result = await forwardToPdfService(req.file);
res.json(result); res.json(result);
} catch (err) { } catch (err) {
console.error(err); console.error(err);

View File

@@ -9,7 +9,7 @@ export interface ExtractedData {
[key: string]: any; [key: string]: any;
} }
export default async function forwardToPythonService( export default async function forwardToPdfService(
file: Express.Multer.File file: Express.Multer.File
): Promise<ExtractedData> { ): Promise<ExtractedData> {
const form = new FormData(); const form = new FormData();

View File

@@ -0,0 +1,25 @@
import axios from "axios";
export interface SeleniumPayload {
claim: any;
pdfs: {
originalname: string;
bufferBase64: string;
}[];
}
export default async function forwardToSeleniumAgent(
claimData: any,
files: Express.Multer.File[]
): Promise<any> {
const payload: SeleniumPayload = {
claim: claimData,
pdfs: files.map(file => ({
originalname: file.originalname,
bufferBase64: file.buffer.toString("base64"),
})),
};
const response = await axios.post("http://localhost:5002/run", payload);
return response.data;
}

View File

@@ -227,7 +227,8 @@ export const storage: IStorage = {
try { try {
await db.patient.delete({ where: { id } }); await db.patient.delete({ where: { id } });
} catch (err) { } catch (err) {
throw new Error(`Patient with ID ${id} not found`); console.error("Error deleting patient:", err);
throw new Error(`Failed to delete patient: ${err}`);
} }
}, },

View File

@@ -91,6 +91,8 @@ interface ClaimFormData {
serviceDate: string; // YYYY-MM-DD serviceDate: string; // YYYY-MM-DD
insuranceProvider: string; insuranceProvider: string;
status: string; // default "pending" status: string; // default "pending"
massdhp_username?:string,
massdhp_password?:string,
serviceLines: ServiceLine[]; serviceLines: ServiceLine[];
} }
@@ -102,6 +104,7 @@ interface ClaimFormProps {
appointmentData: InsertAppointment | UpdateAppointment appointmentData: InsertAppointment | UpdateAppointment
) => void; ) => void;
onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void; onHandleUpdatePatient: (patient: UpdatePatient & { id: number }) => void;
onHandleForSelenium: (data: ClaimFormData) => void;
onClose: () => void; onClose: () => void;
} }
@@ -117,6 +120,7 @@ export function ClaimForm({
extractedData, extractedData,
onHandleAppointmentSubmit, onHandleAppointmentSubmit,
onHandleUpdatePatient, onHandleUpdatePatient,
onHandleForSelenium,
onSubmit, onSubmit,
onClose, onClose,
}: ClaimFormProps) { }: ClaimFormProps) {
@@ -170,31 +174,23 @@ export function ClaimForm({
// Service date state // Service date state
const [serviceDateValue, setServiceDateValue] = useState<Date>(new Date()); const [serviceDateValue, setServiceDateValue] = useState<Date>(new Date());
const [serviceDate, setServiceDate] = useState<string>( const [serviceDate, setServiceDate] = useState<string>(
format(new Date(), "MM/dd/yyyy") new Date().toLocaleDateString("en-CA") // "YYYY-MM-DD"
); );
const formatServiceDate = (date: Date | undefined): string => {
return date ? format(date, "MM/dd/yyyy") : "";
};
useEffect(() => { useEffect(() => {
if (extractedData?.serviceDate) { if (extractedData?.serviceDate) {
const parsed = new Date(extractedData.serviceDate); const parsed = new Date(extractedData.serviceDate);
const isoFormatted = parsed.toLocaleDateString("en-CA");
setServiceDateValue(parsed); setServiceDateValue(parsed);
setServiceDate(formatServiceDate(parsed)); setServiceDate(isoFormatted);
} }
}, [extractedData]); }, [extractedData]);
// used in submit button to send correct date.
function convertToISODate(mmddyyyy: string): string {
const [month, day, year] = mmddyyyy.split("/");
return `${year}-${month?.padStart(2, "0")}-${day?.padStart(2, "0")}`;
}
// Update service date when calendar date changes // Update service date when calendar date changes
const onServiceDateChange = (date: Date | undefined) => { const onServiceDateChange = (date: Date | undefined) => {
if (date) { if (date) {
const formattedDate = format(date, "MM/dd/yyyy"); const formattedDate = date.toLocaleDateString("en-CA"); // "YYYY-MM-DD"
setServiceDateValue(date); setServiceDateValue(date);
setServiceDate(formattedDate); setServiceDate(formattedDate);
setForm((prev) => ({ ...prev, serviceDate: formattedDate })); setForm((prev) => ({ ...prev, serviceDate: formattedDate }));
@@ -215,7 +211,7 @@ export function ClaimForm({
}; };
// MAIN FORM INITIAL STATE // MAIN FORM INITIAL STATE
const [form, setForm] = useState<ClaimFormData>({ const [form, setForm] = useState<ClaimFormData & { uploadedFiles: File[] }>({
patientId: patientId || 0, patientId: patientId || 0,
appointmentId: 0, //need to update appointmentId: 0, //need to update
userId: Number(user?.id), userId: Number(user?.id),
@@ -227,6 +223,8 @@ export function ClaimForm({
serviceDate: serviceDate, serviceDate: serviceDate,
insuranceProvider: "", insuranceProvider: "",
status: "pending", status: "pending",
massdhp_username: "",
massdhp_password: "",
serviceLines: [ serviceLines: [
{ {
procedureCode: "", procedureCode: "",
@@ -237,6 +235,8 @@ export function ClaimForm({
billedAmount: 0, billedAmount: 0,
}, },
], ],
uploadedFiles: [],
}); });
// Sync patient data to form when patient updates // Sync patient data to form when patient updates
@@ -294,7 +294,7 @@ export function ClaimForm({
const updateProcedureDate = (index: number, date: Date | undefined) => { const updateProcedureDate = (index: number, date: Date | undefined) => {
if (!date) return; if (!date) return;
const formattedDate = format(date, "MM/dd/yyyy"); const formattedDate = date.toLocaleDateString("en-CA");
const updatedLines = [...form.serviceLines]; const updatedLines = [...form.serviceLines];
if (updatedLines[index]) { if (updatedLines[index]) {
@@ -305,24 +305,26 @@ export function ClaimForm({
}; };
// FILE UPLOAD ZONE // FILE UPLOAD ZONE
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const handleFileUpload = (files: File[]) => { const handleFileUpload = (files: File[]) => {
setIsUploading(true); setIsUploading(true);
const validFiles = files.filter((file) => file.type === "application/pdf"); const validFiles = files.filter((file) => file.type === "application/pdf");
if (validFiles.length > 10) { if (validFiles.length > 5) {
toast({ toast({
title: "Too Many Files", title: "Too Many Files",
description: "You can only upload up to 10 PDFs.", description: "You can only upload up to 5 PDFs.",
variant: "destructive", variant: "destructive",
}); });
setIsUploading(false); setIsUploading(false);
return; return;
} }
setUploadedFiles(validFiles); setForm((prev) => ({
...prev,
uploadedFiles: validFiles,
}));
toast({ toast({
title: "Files Selected", title: "Files Selected",
@@ -334,9 +336,15 @@ export function ClaimForm({
// Delta MA Button Handler // Delta MA Button Handler
const handleDeltaMASubmit = async () => { const handleDeltaMASubmit = async () => {
function convertToISODate(mmddyyyy: string): string {
const [month, day, year] = mmddyyyy.split("/");
return `${year}-${month?.padStart(2, "0")}-${day?.padStart(2, "0")}`;
}
const appointmentData = { const appointmentData = {
patientId: patientId, patientId: patientId,
date: convertToISODate(serviceDate), date: serviceDate,
staffId: staff?.id, staffId: staff?.id,
}; };
@@ -361,14 +369,25 @@ export function ClaimForm({
} }
// 3. Create Claim(if not) // 3. Create Claim(if not)
const { uploadedFiles, massdhp_username, massdhp_password, ...formToCreateClaim } = form;
onSubmit({ onSubmit({
...form, ...formToCreateClaim,
staffId: Number(staff?.id), staffId: Number(staff?.id),
patientId: patient?.id, patientId: patientId,
insuranceProvider: "Delta MA", insuranceProvider: "Delta MA",
appointmentId: appointmentId!, appointmentId: appointmentId!,
}); });
// 4. sending form data to selenium service
onHandleForSelenium({
...form,
staffId: Number(staff?.id),
patientId: patientId,
insuranceProvider: "Delta MA",
appointmentId: appointmentId!,
massdhp_username: "kqkgaox@yahoo.com",
massdhp_password: "Lex123456", //fetch this from db, by call
});
// 4. Close form // 4. Close form
onClose(); onClose();
}; };
@@ -630,9 +649,9 @@ export function ClaimForm({
{/* File Upload Section */} {/* File Upload Section */}
<div className="mt-4 bg-gray-100 p-4 rounded-md space-y-4"> <div className="mt-4 bg-gray-100 p-4 rounded-md space-y-4">
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
Only PDF files allowed. You can upload up to 10 files. File Only PDF files allowed. You can upload up to 5 files. File types
types with 4+ character extensions like .DOCX, .PPTX, or .XLSX with 4+ character extensions like .DOCX, .PPTX, or .XLSX are not
are not allowed. allowed.
</p> </p>
<div className="flex flex-wrap gap-4 items-center justify-between"> <div className="flex flex-wrap gap-4 items-center justify-between">
@@ -657,12 +676,12 @@ export function ClaimForm({
onFileUpload={handleFileUpload} onFileUpload={handleFileUpload}
isUploading={isUploading} isUploading={isUploading}
acceptedFileTypes="application/pdf" acceptedFileTypes="application/pdf"
maxFiles={10} maxFiles={5}
/> />
{uploadedFiles.length > 0 && ( {form.uploadedFiles.length > 0 && (
<ul className="text-sm text-gray-700 list-disc ml-6"> <ul className="text-sm text-gray-700 list-disc ml-6">
{uploadedFiles.map((file, index) => ( {form.uploadedFiles.map((file, index) => (
<li key={index}>{file.name}</li> <li key={index}>{file.name}</li>
))} ))}
</ul> </ul>

View File

@@ -15,7 +15,7 @@ export function MultipleFileUploadZone({
onFileUpload, onFileUpload,
isUploading, isUploading,
acceptedFileTypes = "application/pdf", acceptedFileTypes = "application/pdf",
maxFiles = 10, maxFiles = 5,
}: FileUploadZoneProps) { }: FileUploadZoneProps) {
const { toast } = useToast(); const { toast } = useToast();
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);

View File

@@ -10,8 +10,8 @@ import {
PatientUncheckedCreateInputObjectSchema, PatientUncheckedCreateInputObjectSchema,
AppointmentUncheckedCreateInputObjectSchema, AppointmentUncheckedCreateInputObjectSchema,
} from "@repo/db/usedSchemas"; } from "@repo/db/usedSchemas";
import { FileCheck, CheckCircle, Clock, AlertCircle } from "lucide-react"; import { FileCheck } from "lucide-react";
import { 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";
@@ -209,16 +209,15 @@ export default function ClaimsPage() {
}, },
}); });
const handleAppointmentSubmit = async (
appointmentData: InsertAppointment | UpdateAppointment
): Promise<number> => {
// 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;
const parts = dateInput.split("-"); const dateString = dateInput.split("T")[0] || dateInput;
const parts = dateString.split("-");
if (parts.length !== 3) { if (parts.length !== 3) {
throw new Error(`Invalid date format: ${dateInput}`); throw new Error(`Invalid date format: ${dateString}`);
} }
const year = Number(parts[0]); const year = Number(parts[0]);
@@ -226,13 +225,17 @@ export default function ClaimsPage() {
const day = Number(parts[2]); const day = Number(parts[2]);
if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) { if (Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day)) {
throw new Error(`Invalid date parts in date string: ${dateInput}`); throw new Error(`Invalid date parts in date string: ${dateString}`);
} }
return new Date(year, month - 1, day); // month is 0-indexed return new Date(year, month - 1, day); // month is 0-indexed
} }
const handleAppointmentSubmit = async (
appointmentData: InsertAppointment | UpdateAppointment
): Promise<number> => {
const rawDate = parseLocalDate(appointmentData.date); const rawDate = parseLocalDate(appointmentData.date);
const formattedDate = rawDate.toLocaleDateString("en-CA"); // YYYY-MM-DD format const formattedDate = rawDate.toLocaleDateString("en-CA"); // YYYY-MM-DD format
// Prepare minimal data to update/create // Prepare minimal data to update/create
@@ -354,12 +357,14 @@ export default function ClaimsPage() {
(a) => a.patientId === patient.id (a) => a.patientId === patient.id
); );
const dateToUse = lastAppointment
? parseLocalDate(lastAppointment.date)
: new Date();
setClaimFormData((prev: any) => ({ setClaimFormData((prev: any) => ({
...prev, ...prev,
patientId: patient.id, patientId: patient.id,
serviceDate: lastAppointment serviceDate: dateToUse.toLocaleDateString("en-CA"), // consistent "YYYY-MM-DD"
? new Date(lastAppointment.date).toISOString().slice(0, 10)
: new Date().toISOString().slice(0, 10),
})); }));
}; };
@@ -378,13 +383,15 @@ export default function ClaimsPage() {
const [firstName, ...rest] = name.trim().split(" "); const [firstName, ...rest] = name.trim().split(" ");
const lastName = rest.join(" ") || ""; const lastName = rest.join(" ") || "";
const parsedDob = new Date(dob); const parsedDob = parse(dob, "M/d/yyyy", new Date()); // robust for "4/17/1964", "12/1/1975", etc.
const isValidDob = !isNaN(parsedDob.getTime()); const isValidDob = !isNaN(parsedDob.getTime());
const newPatient: InsertPatient = { const newPatient: InsertPatient = {
firstName, firstName,
lastName, lastName,
dateOfBirth: isValidDob ? parsedDob : new Date(), dateOfBirth: isValidDob
? format(parsedDob, "yyyy-MM-dd")
: format(new Date(), "yyyy-MM-dd"),
gender: "", gender: "",
phone: "", phone: "",
userId: user?.id ?? 1, userId: user?.id ?? 1,
@@ -463,6 +470,42 @@ export default function ClaimsPage() {
} }
}; };
// handle selenium
const handleSelenium = async (data: any) => {
const formData = new FormData();
formData.append("data", JSON.stringify(data));
const uploadedFiles: File[] = data.uploadedFiles ?? [];
uploadedFiles.forEach((file: File) => {
formData.append("pdfs", file);
});
try {
const response = await apiRequest(
"POST",
"/api/claims/selenium",
formData
);
const result = await response.json();
toast({
title: "Selenium service notified",
description: "Your claim data was successfully sent to Selenium.",
variant: "default",
});
return result;
} catch (error: any) {
toast({
title: "Selenium service error",
description: error.message || "An error occurred.",
variant: "destructive",
});
}
};
return ( return (
<div className="flex h-screen overflow-hidden bg-gray-100"> <div className="flex h-screen overflow-hidden bg-gray-100">
<Sidebar <Sidebar
@@ -580,6 +623,7 @@ export default function ClaimsPage() {
onSubmit={handleClaimSubmit} onSubmit={handleClaimSubmit}
onHandleAppointmentSubmit={handleAppointmentSubmit} onHandleAppointmentSubmit={handleAppointmentSubmit}
onHandleUpdatePatient={handleUpdatePatient} onHandleUpdatePatient={handleUpdatePatient}
onHandleForSelenium={handleSelenium}
/> />
)} )}
</div> </div>

View File

@@ -1 +1,2 @@
.env .env
/__pycache__

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

@@ -1,6 +1,5 @@
from selenium import webdriver from selenium import webdriver
from selenium.common import TimeoutException from selenium.common import TimeoutException
from selenium.common.exceptions import ElementNotInteractableException
from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
@@ -13,6 +12,7 @@ from dotenv import load_dotenv
import os import os
load_dotenv() load_dotenv()
# this file is just for testing selenium solely.
procedure_codes = [ procedure_codes = [
{ {
"procedure_code": "D0210", "procedure_code": "D0210",
@@ -38,7 +38,7 @@ data = {
"massdhp_username": os.getenv("MASSDHP_USERNAME"), "massdhp_username": os.getenv("MASSDHP_USERNAME"),
"massdhp_password": os.getenv("MASSDHP_PASSWORD"), "massdhp_password": os.getenv("MASSDHP_PASSWORD"),
"memberId": os.getenv("memberId"), "memberId": os.getenv("memberId"),
"dob":os.getenv("dob"), "dateOfBirth":os.getenv("dob"),
"procedure_codes": procedure_codes, "procedure_codes": procedure_codes,
"pdfs" : pdfs, "pdfs" : pdfs,
"missingTeethStatus": "Yes_missing", # can be Yes_missing , No_missing, or endentulous "missingTeethStatus": "Yes_missing", # can be Yes_missing , No_missing, or endentulous
@@ -111,7 +111,7 @@ class AutomationMassDHP:
# Fill DOB parts # Fill DOB parts
try: try:
dob_parts = data["dob"].split("/") dob_parts = data["dateOfBirth"].split("/")
month= dob_parts[0].zfill(2) # "12" month= dob_parts[0].zfill(2) # "12"
day= dob_parts[1].zfill(2) # "13" day= dob_parts[1].zfill(2) # "13"
year = dob_parts[2] # "1965" year = dob_parts[2] # "1965"
@@ -320,7 +320,6 @@ class AutomationMassDHP:
input("should Close?") # here it sholud get confirmation from the frontend, input("should Close?") # here it sholud get confirmation from the frontend,
self.driver.close() self.driver.close()

View File

@@ -0,0 +1,302 @@
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 AutomationMassDHP:
def __init__(self, data):
self.data = data
self.headless = False
self.driver = None
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.data["claim"]["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.data["claim"]["massdhp_password"])
# Click login
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Login']")))
login_button.click()
print("Login submitted successfully.")
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.data["claim"]["memberId"])
# Fill DOB parts
try:
dob_parts = self.data["claim"]["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.")
input("HERE?")
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.data["procedure_codes"]:
# 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['procedure_code'])
# Fill Procedure Date if present
if proc.get("procedure_date"):
try:
# Try to normalize date to MM/DD/YYYY format
parsed_date = datetime.strptime(proc["procedure_date"], "%m/%d/%Y")
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"):
surface = proc["toothSurface"]
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("fees"):
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["fees"])
# 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)
print("Procedure codes submitted successfully.")
except Exception as e:
print(f"Error while filling Procedure Codes: {e}")
return "ERROR:PROCEDURE CODES"
# 2 - Upload PDFs:
try:
pdfs_abs = [proc for proc in self.data.get("pdfs", [])]
with tempfile.TemporaryDirectory() as tmp_dir:
for pdf_obj in pdfs_abs:
base64_data = pdf_obj["buffer"]
file_name = pdf_obj.get("originalname", "tempfile.pdf")
# 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.data.get("missingTeethStatus")
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.data.get("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:
textarea = wait.until(EC.presence_of_element_located((By.XPATH, "//textarea[@name='Remarks']")))
textarea.clear()
textarea.send_keys(self.data["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"
return "Success"
def main_workflow(self, url):
self.config_driver()
print("Reaching Site :", url)
self.driver.maximize_window()
self.driver.get(url)
time.sleep(3)
value = self.login()
if value.startswith("ERROR"):
self.driver.close()
return value
time.sleep(5)
value2 = self.step1()
if value2.startswith("ERROR"):
self.driver.close()
return value2
time.sleep(5)
value3 = self.step2()
if value3.startswith("ERROR"):
self.driver.close()
return value3
input("should Close?") # here it sholud get confirmation from the frontend,
self.driver.close()
return {"status": "success", "message": "Successfully submitted the form."}

View File

@@ -67,10 +67,11 @@ model Appointment {
status String @default("scheduled") // "scheduled", "completed", "cancelled", "no-show" status String @default("scheduled") // "scheduled", "completed", "cancelled", "no-show"
createdAt DateTime @default(now()) createdAt DateTime @default(now())
patient Patient @relation(fields: [patientId], references: [id]) patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id]) user User @relation(fields: [userId], references: [id])
staff Staff? @relation(fields: [staffId], references: [id]) staff Staff? @relation(fields: [staffId], references: [id])
claims Claim[] claims Claim[]
} }
model Staff { model Staff {
@@ -100,10 +101,11 @@ model Claim {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
status String @default("pending") // "pending", "completed", "cancelled", "no-show" status String @default("pending") // "pending", "completed", "cancelled", "no-show"
patient Patient @relation(fields: [patientId], references: [id]) patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
appointment Appointment @relation(fields: [appointmentId], references: [id]) appointment Appointment @relation(fields: [appointmentId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id]) user User? @relation(fields: [userId], references: [id])
staff Staff? @relation("ClaimStaff", fields: [staffId], references: [id]) staff Staff? @relation("ClaimStaff", fields: [staffId], references: [id])
serviceLines ServiceLine[] serviceLines ServiceLine[]
} }
@@ -116,5 +118,5 @@ model ServiceLine {
toothNumber String? toothNumber String?
toothSurface String? toothSurface String?
billedAmount Float billedAmount Float
claim Claim @relation(fields: [claimId], references: [id]) claim Claim @relation(fields: [claimId], references: [id], onDelete: Cascade)
} }