uploading pdfs, including images now at claim
This commit is contained in:
@@ -4,7 +4,10 @@ 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, forwardToSeleniumAgent2 } from "../services/seleniumClient";
|
import {
|
||||||
|
forwardToSeleniumAgent,
|
||||||
|
forwardToSeleniumAgent2,
|
||||||
|
} from "../services/seleniumClient";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -43,11 +46,30 @@ const ExtendedClaimSchema = (
|
|||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
const multerStorage = multer.memoryStorage(); // NO DISK
|
const multerStorage = multer.memoryStorage(); // NO DISK
|
||||||
const upload = multer({ storage: multerStorage });
|
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",
|
"/selenium",
|
||||||
upload.array("pdfs"),
|
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.files || !req.body.data) {
|
||||||
return res
|
return res
|
||||||
@@ -61,7 +83,10 @@ router.post(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const claimData = JSON.parse(req.body.data);
|
const claimData = JSON.parse(req.body.data);
|
||||||
const files = req.files as Express.Multer.File[];
|
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,
|
||||||
@@ -78,7 +103,11 @@ router.post(
|
|||||||
massdhpUsername: credentials.username,
|
massdhpUsername: credentials.username,
|
||||||
massdhpPassword: credentials.password,
|
massdhpPassword: credentials.password,
|
||||||
};
|
};
|
||||||
const result = await forwardToSeleniumAgent(enrichedData, files);
|
|
||||||
|
const result = await forwardToSeleniumAgent(enrichedData, [
|
||||||
|
...pdfs,
|
||||||
|
...images,
|
||||||
|
]);
|
||||||
|
|
||||||
res.json({ success: true, data: result });
|
res.json({ success: true, data: result });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -95,10 +124,10 @@ router.post(
|
|||||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try {
|
||||||
const { patientId, claimId } = req.body;
|
const { patientId, claimId } = req.body;
|
||||||
|
|
||||||
if (!patientId || !claimId) {
|
if (!patientId || !claimId) {
|
||||||
return res.status(400).json({ error: "Missing patientId or claimId" });
|
return res.status(400).json({ error: "Missing patientId or claimId" });
|
||||||
}
|
}
|
||||||
const parsedPatientId = parseInt(patientId);
|
const parsedPatientId = parseInt(patientId);
|
||||||
@@ -107,12 +136,16 @@ router.post(
|
|||||||
const result = await forwardToSeleniumAgent2();
|
const result = await forwardToSeleniumAgent2();
|
||||||
|
|
||||||
if (result.status !== "success") {
|
if (result.status !== "success") {
|
||||||
return res.status(400).json({ error: result.message || "Failed to fetch PDF" });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ error: result.message || "Failed to fetch PDF" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfUrl = result.pdf_url;
|
const pdfUrl = result.pdf_url;
|
||||||
const filename = path.basename(new URL(pdfUrl).pathname);
|
const filename = path.basename(new URL(pdfUrl).pathname);
|
||||||
const pdfResponse = await axios.get(pdfUrl, { responseType: "arraybuffer" });
|
const pdfResponse = await axios.get(pdfUrl, {
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
});
|
||||||
|
|
||||||
// Temp savving the pdf incase, creatClaimPdf failed:
|
// Temp savving the pdf incase, creatClaimPdf failed:
|
||||||
const tempDir = path.join(__dirname, "..", "..", "temp");
|
const tempDir = path.join(__dirname, "..", "..", "temp");
|
||||||
@@ -138,9 +171,11 @@ router.post(
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error in /selenium/fetchpdf:", err);
|
console.error("Error in /selenium/fetchpdf:", err);
|
||||||
return res.status(500).json({ error: "Failed to forward to selenium agent 2" });
|
return 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
|
||||||
|
|||||||
@@ -6,27 +6,44 @@ export interface SeleniumPayload {
|
|||||||
originalname: string;
|
originalname: string;
|
||||||
bufferBase64: string;
|
bufferBase64: string;
|
||||||
}[];
|
}[];
|
||||||
|
images: {
|
||||||
|
originalname: string;
|
||||||
|
bufferBase64: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function forwardToSeleniumAgent(
|
export async function forwardToSeleniumAgent(
|
||||||
claimData: any,
|
claimData: any,
|
||||||
files: Express.Multer.File[]
|
files: Express.Multer.File[]
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const payload: SeleniumPayload = {
|
const pdfs = files
|
||||||
claim: claimData,
|
.filter((file) => file.mimetype === "application/pdf")
|
||||||
pdfs: files.map(file => ({
|
.map((file) => ({
|
||||||
originalname: file.originalname,
|
originalname: file.originalname,
|
||||||
bufferBase64: file.buffer.toString("base64"),
|
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 response = await axios.post("http://localhost:5002/start-workflow", payload);
|
const response = await axios.post(
|
||||||
|
"http://localhost:5002/start-workflow",
|
||||||
|
payload
|
||||||
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function forwardToSeleniumAgent2(
|
export async function forwardToSeleniumAgent2(): Promise<any> {
|
||||||
): Promise<any> {
|
|
||||||
|
|
||||||
const response = await axios.post("http://localhost:5002/fetch-pdf");
|
const response = await axios.post("http://localhost:5002/fetch-pdf");
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,12 +172,14 @@ export function ClaimForm({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (staffMembersRaw.length > 0 && !staff) {
|
if (staffMembersRaw.length > 0 && !staff) {
|
||||||
const kaiGao = staffMembersRaw.find((member) => member.name === "Kai Gao");
|
const kaiGao = staffMembersRaw.find(
|
||||||
const defaultStaff = kaiGao || staffMembersRaw[0];
|
(member) => member.name === "Kai Gao"
|
||||||
if (defaultStaff) setStaff(defaultStaff);
|
);
|
||||||
}
|
const defaultStaff = kaiGao || staffMembersRaw[0];
|
||||||
}, [staffMembersRaw, staff]);
|
if (defaultStaff) setStaff(defaultStaff);
|
||||||
|
}
|
||||||
|
}, [staffMembersRaw, staff]);
|
||||||
|
|
||||||
// Service date state
|
// Service date state
|
||||||
function parseLocalDate(dateInput: Date | string): Date {
|
function parseLocalDate(dateInput: Date | string): Date {
|
||||||
@@ -333,11 +335,30 @@ export function ClaimForm({
|
|||||||
const handleFileUpload = (files: File[]) => {
|
const handleFileUpload = (files: File[]) => {
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
|
|
||||||
const validFiles = files.filter((file) => file.type === "application/pdf");
|
const allowedTypes = [
|
||||||
if (validFiles.length > 5) {
|
"application/pdf",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
];
|
||||||
|
|
||||||
|
const validFiles = files.filter((file) => allowedTypes.includes(file.type));
|
||||||
|
|
||||||
|
if (validFiles.length > 10) {
|
||||||
toast({
|
toast({
|
||||||
title: "Too Many Files",
|
title: "Too Many Files",
|
||||||
description: "You can only upload up to 5 PDFs.",
|
description: "You can only upload up to 10 files (PDFs or images).",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
setIsUploading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
toast({
|
||||||
|
title: "Invalid File Type",
|
||||||
|
description: "Only PDF and image files are allowed.",
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
@@ -351,7 +372,7 @@ export function ClaimForm({
|
|||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Files Selected",
|
title: "Files Selected",
|
||||||
description: `${validFiles.length} PDF file(s) ready for processing.`,
|
description: `${validFiles.length} file(s) ready for processing.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
@@ -711,34 +732,15 @@ 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 5 files. File types
|
You can upload up to 10 files. Allowed types: PDF, JPG, PNG,
|
||||||
with 4+ character extensions like .DOCX, .PPTX, or .XLSX are not
|
WEBP.
|
||||||
allowed.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Label>Select Field:</Label>
|
|
||||||
<Select defaultValue="supportData">
|
|
||||||
<SelectTrigger className="w-60">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="supportData">
|
|
||||||
Support Data for Claim
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="xrays">X-Ray Images</SelectItem>
|
|
||||||
<SelectItem value="photos">Clinical Photos</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MultipleFileUploadZone
|
<MultipleFileUploadZone
|
||||||
onFileUpload={handleFileUpload}
|
onFileUpload={handleFileUpload}
|
||||||
isUploading={isUploading}
|
isUploading={isUploading}
|
||||||
acceptedFileTypes="application/pdf"
|
acceptedFileTypes="application/pdf,image/jpeg,image/jpg,image/png,image/webp"
|
||||||
maxFiles={5}
|
maxFiles={10}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{form.uploadedFiles.length > 0 && (
|
{form.uploadedFiles.length > 0 && (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useRef, useCallback } from 'react';
|
import React, { useState, useRef, useCallback } from "react";
|
||||||
import { Upload, File, X, FilePlus } from 'lucide-react';
|
import { Upload, File, X, FilePlus } from "lucide-react";
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from "@/components/ui/button";
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface FileUploadZoneProps {
|
interface FileUploadZoneProps {
|
||||||
onFileUpload: (files: File[]) => void;
|
onFileUpload: (files: File[]) => void;
|
||||||
@@ -14,40 +14,22 @@ interface FileUploadZoneProps {
|
|||||||
export function MultipleFileUploadZone({
|
export function MultipleFileUploadZone({
|
||||||
onFileUpload,
|
onFileUpload,
|
||||||
isUploading,
|
isUploading,
|
||||||
acceptedFileTypes = "application/pdf",
|
acceptedFileTypes = "application/pdf,image/jpeg,image/jpg,image/png,image/webp",
|
||||||
maxFiles = 5,
|
maxFiles = 10,
|
||||||
}: FileUploadZoneProps) {
|
}: FileUploadZoneProps) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const allowedTypes = acceptedFileTypes.split(",").map((type) => type.trim());
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsDragging(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
if (!isDragging) {
|
|
||||||
setIsDragging(true);
|
|
||||||
}
|
|
||||||
}, [isDragging]);
|
|
||||||
|
|
||||||
const validateFile = (file: File) => {
|
const validateFile = (file: File) => {
|
||||||
if (!file.type.match(acceptedFileTypes)) {
|
if (!allowedTypes.includes(file.type)) {
|
||||||
toast({
|
toast({
|
||||||
title: "Invalid file type",
|
title: "Invalid file type",
|
||||||
description: "Please upload a PDF file.",
|
description: "Only PDF and image files are allowed.",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -55,8 +37,8 @@ export function MultipleFileUploadZone({
|
|||||||
if (file.size > 5 * 1024 * 1024) {
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
toast({
|
toast({
|
||||||
title: "File too large",
|
title: "File too large",
|
||||||
description: "File size should be less than 5MB.",
|
description: "File size must be less than 5MB.",
|
||||||
variant: "destructive"
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -84,16 +66,45 @@ export function MultipleFileUploadZone({
|
|||||||
onFileUpload(updatedFiles);
|
onFileUpload(updatedFiles);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
const handleDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
handleFiles(e.dataTransfer.files);
|
}, []);
|
||||||
}, [uploadedFiles]);
|
|
||||||
|
|
||||||
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleDragOver = useCallback(
|
||||||
handleFiles(e.target.files);
|
(e: React.DragEvent<HTMLDivElement>) => {
|
||||||
}, [uploadedFiles]);
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isDragging) {
|
||||||
|
setIsDragging(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isDragging]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDrop = useCallback(
|
||||||
|
(e: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsDragging(false);
|
||||||
|
handleFiles(e.dataTransfer.files);
|
||||||
|
},
|
||||||
|
[uploadedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFileSelect = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
handleFiles(e.target.files);
|
||||||
|
},
|
||||||
|
[uploadedFiles]
|
||||||
|
);
|
||||||
|
|
||||||
const handleBrowseClick = () => {
|
const handleBrowseClick = () => {
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@@ -122,7 +133,9 @@ export function MultipleFileUploadZone({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-2 border-dashed rounded-lg p-8 flex flex-col items-center justify-center text-center transition-colors",
|
"border-2 border-dashed rounded-lg p-8 flex flex-col items-center justify-center text-center transition-colors",
|
||||||
isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25",
|
isDragging
|
||||||
|
? "border-primary bg-primary/5"
|
||||||
|
: "border-muted-foreground/25",
|
||||||
isUploading && "opacity-50 cursor-not-allowed"
|
isUploading && "opacity-50 cursor-not-allowed"
|
||||||
)}
|
)}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
@@ -141,10 +154,15 @@ export function MultipleFileUploadZone({
|
|||||||
</div>
|
</div>
|
||||||
) : uploadedFiles.length > 0 ? (
|
) : uploadedFiles.length > 0 ? (
|
||||||
<div className="flex flex-col items-center gap-4 w-full">
|
<div className="flex flex-col items-center gap-4 w-full">
|
||||||
<p className="font-medium text-primary">{uploadedFiles.length} file(s) uploaded</p>
|
<p className="font-medium text-primary">
|
||||||
|
{uploadedFiles.length} file(s) uploaded
|
||||||
|
</p>
|
||||||
<ul className="w-full text-left space-y-2">
|
<ul className="w-full text-left space-y-2">
|
||||||
{uploadedFiles.map((file, index) => (
|
{uploadedFiles.map((file, index) => (
|
||||||
<li key={index} className="flex justify-between items-center border-b pb-1">
|
<li
|
||||||
|
key={index}
|
||||||
|
className="flex justify-between items-center border-b pb-1"
|
||||||
|
>
|
||||||
<span className="text-sm">{file.name}</span>
|
<span className="text-sm">{file.name}</span>
|
||||||
<button
|
<button
|
||||||
className="ml-2 p-1 text-muted-foreground hover:text-red-500"
|
className="ml-2 p-1 text-muted-foreground hover:text-red-500"
|
||||||
@@ -163,16 +181,26 @@ export function MultipleFileUploadZone({
|
|||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<FilePlus className="h-12 w-12 text-primary/70" />
|
<FilePlus className="h-12 w-12 text-primary/70" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-primary">Drag and drop PDF files here</p>
|
<p className="font-medium text-primary">
|
||||||
<p className="text-sm text-muted-foreground mt-1">Or click to browse files</p>
|
Drag and drop PDF or Image files here
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
|
Or click to browse files
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button type="button" variant="secondary" onClick={(e) => {
|
<Button
|
||||||
e.stopPropagation();
|
type="button"
|
||||||
handleBrowseClick();
|
variant="secondary"
|
||||||
}}>
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleBrowseClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
Browse files
|
Browse files
|
||||||
</Button>
|
</Button>
|
||||||
<p className="text-xs text-muted-foreground">Up to {maxFiles} PDF files, 5MB each</p>
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Allowed types: PDF, JPG, PNG — Max {maxFiles} files, 5MB each
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -518,13 +518,16 @@ export default function ClaimsPage() {
|
|||||||
// handle selenium
|
// handle selenium
|
||||||
const handleSelenium = async (data: any) => {
|
const handleSelenium = async (data: any) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
formData.append("data", JSON.stringify(data));
|
formData.append("data", JSON.stringify(data));
|
||||||
|
|
||||||
const uploadedFiles: File[] = data.uploadedFiles ?? [];
|
const uploadedFiles: File[] = data.uploadedFiles ?? [];
|
||||||
|
|
||||||
uploadedFiles.forEach((file: File) => {
|
uploadedFiles.forEach((file: File) => {
|
||||||
formData.append("pdfs", file);
|
if (file.type === "application/pdf") {
|
||||||
|
formData.append("pdfs", file);
|
||||||
|
} else if (file.type.startsWith("image/")) {
|
||||||
|
formData.append("images", file);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -537,7 +540,8 @@ export default function ClaimsPage() {
|
|||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Selenium service notified",
|
title: "Selenium service notified",
|
||||||
description: "Your claim data was successfully sent to Selenium.",
|
description:
|
||||||
|
"Your claim data was successfully sent to Selenium, Wait for its response.",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
setIsMhPopupOpen(true);
|
setIsMhPopupOpen(true);
|
||||||
|
|||||||
@@ -1,327 +0,0 @@
|
|||||||
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
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import os
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
# this file is just for testing selenium solely.
|
|
||||||
procedure_codes = [
|
|
||||||
{
|
|
||||||
"procedure_code": "D0210",
|
|
||||||
"procedure_date": "06/30/2025",
|
|
||||||
"oralCavityArea": "",
|
|
||||||
"toothNumber": "",
|
|
||||||
"toothSurface": "",
|
|
||||||
"fees":"19"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"procedure_code": "D0250",
|
|
||||||
"procedure_date": "06/30/2025",
|
|
||||||
"oralCavityArea": "here",
|
|
||||||
"toothNumber": "15",
|
|
||||||
"toothSurface": "B",
|
|
||||||
"fees":"190"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
pdfs = ["PDF_To_Test/sample1.pdf", "PDF_To_Test/sample2.pdf"]
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"massdhp_username": os.getenv("MASSDHP_USERNAME"),
|
|
||||||
"massdhp_password": os.getenv("MASSDHP_PASSWORD"),
|
|
||||||
"memberId": os.getenv("memberId"),
|
|
||||||
"dateOfBirth":os.getenv("dob"),
|
|
||||||
"procedure_codes": procedure_codes,
|
|
||||||
"pdfs" : pdfs,
|
|
||||||
"missingTeethStatus": "Yes_missing", # can be Yes_missing , No_missing, or endentulous
|
|
||||||
"missingTeeth": {
|
|
||||||
# Tooth name: selection ("X" for missing, "O" for "To be Pulled")
|
|
||||||
|
|
||||||
# for permanent type : T_n : X or O ( n here is teeth number like 1, 2, 3)
|
|
||||||
"T_1": "X",
|
|
||||||
"T_32": "O",
|
|
||||||
|
|
||||||
# for primay type: T_x : X or 0 (x here is alphabet)
|
|
||||||
"T_A": "X",
|
|
||||||
"T_B": "O",
|
|
||||||
},
|
|
||||||
"remarks": "Hello remarks"
|
|
||||||
}
|
|
||||||
|
|
||||||
class AutomationMassDHP:
|
|
||||||
def __init__(self):
|
|
||||||
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(data["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(data["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.")
|
|
||||||
|
|
||||||
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(data["memberId"])
|
|
||||||
|
|
||||||
# Fill DOB parts
|
|
||||||
try:
|
|
||||||
dob_parts = data["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
|
|
||||||
|
|
||||||
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 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 = [os.path.abspath(pdf) for pdf in data["pdfs"]]
|
|
||||||
|
|
||||||
for pdf in pdfs_abs:
|
|
||||||
# Wait for file input and upload file
|
|
||||||
file_input = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='FileName' and @type='file']")))
|
|
||||||
file_input.send_keys(pdf)
|
|
||||||
|
|
||||||
# Wait for upload button and click it
|
|
||||||
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 = 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 = 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(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"
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
obj1 = AutomationMassDHP()
|
|
||||||
obj1.main_workflow(url= "https://providers.massdhp.com/providers_login.asp")
|
|
||||||
@@ -24,7 +24,7 @@ class AutomationMassHealth:
|
|||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
self.claim = data.get("claim", {})
|
self.claim = data.get("claim", {})
|
||||||
self.pdfs = data.get("pdfs", [])
|
self.upload_files = data.get("pdfs", []) + data.get("images", [])
|
||||||
|
|
||||||
# Flatten values for convenience
|
# Flatten values for convenience
|
||||||
self.memberId = self.claim.get("memberId", "")
|
self.memberId = self.claim.get("memberId", "")
|
||||||
@@ -211,12 +211,14 @@ class AutomationMassHealth:
|
|||||||
|
|
||||||
# 2 - Upload PDFs:
|
# 2 - Upload PDFs:
|
||||||
try:
|
try:
|
||||||
pdfs_abs = [proc for proc in self.pdfs]
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
for pdf_obj in pdfs_abs:
|
for file_obj in self.upload_files:
|
||||||
base64_data = pdf_obj["bufferBase64"]
|
base64_data = file_obj["bufferBase64"]
|
||||||
file_name = pdf_obj.get("originalname", "tempfile.pdf")
|
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
|
# Full path with original filename inside temp dir
|
||||||
tmp_file_path = os.path.join(tmp_dir, file_name)
|
tmp_file_path = os.path.join(tmp_dir, file_name)
|
||||||
|
|||||||
Reference in New Issue
Block a user