Commit
·
a8f12f6
1
Parent(s):
c9bda9d
Add file upload functionality for provider notes
Browse files- requirements.txt +4 -4
- src/api/routes.py +86 -23
- src/main.py +22 -17
- src/models/request_models.py +26 -1
- src/services/file_service.py +109 -0
- tests/test_api.py +56 -7
requirements.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
fastapi==0.104.1
|
| 2 |
-
uvicorn
|
| 3 |
-
groq==0.11.0
|
| 4 |
-
pydantic==2.5.0
|
| 5 |
python-dotenv==1.0.0
|
| 6 |
-
|
|
|
|
|
|
|
|
|
| 1 |
fastapi==0.104.1
|
| 2 |
+
uvicorn==0.24.0
|
|
|
|
|
|
|
| 3 |
python-dotenv==1.0.0
|
| 4 |
+
groq==0.4.1
|
| 5 |
+
pydantic==2.5.0
|
| 6 |
+
python-multipart==0.0.6
|
src/api/routes.py
CHANGED
|
@@ -1,34 +1,97 @@
|
|
| 1 |
-
from fastapi import APIRouter, HTTPException
|
| 2 |
-
import
|
| 3 |
-
import os
|
| 4 |
-
|
| 5 |
-
# Add parent directory to path for imports
|
| 6 |
-
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 7 |
-
|
| 8 |
-
from models.request_models import ProviderNotesRequest
|
| 9 |
from models.response_models import CodingResponse
|
| 10 |
from services.groq_service import groq_service
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
router = APIRouter(prefix="/api/v1", tags=["Medical Coding"])
|
| 13 |
|
| 14 |
-
|
|
|
|
| 15 |
async def analyze_provider_notes(request: ProviderNotesRequest):
|
| 16 |
"""
|
| 17 |
-
Analyze provider notes and
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
Returns ICD-10 codes, CPT codes, and explanations for each.
|
| 22 |
"""
|
| 23 |
try:
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
except Exception as e:
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, UploadFile, File
|
| 2 |
+
from models.request_models import ProviderNotesRequest, FileUploadResponse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from models.response_models import CodingResponse
|
| 4 |
from services.groq_service import groq_service
|
| 5 |
+
from services.file_service import file_service
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
router = APIRouter()
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
|
|
|
|
| 11 |
|
| 12 |
+
# EXISTING ENDPOINT - DO NOT CHANGE
|
| 13 |
+
@router.post("/coding", response_model=CodingResponse)
|
| 14 |
async def analyze_provider_notes(request: ProviderNotesRequest):
|
| 15 |
"""
|
| 16 |
+
Analyze provider notes and extract ICD-10 and CPT codes
|
| 17 |
|
| 18 |
+
This endpoint accepts provider notes as text input.
|
|
|
|
|
|
|
| 19 |
"""
|
| 20 |
try:
|
| 21 |
+
logger.info("Received coding request")
|
| 22 |
+
|
| 23 |
+
# Get provider notes from request
|
| 24 |
+
provider_notes = request.provider_notes
|
| 25 |
+
|
| 26 |
+
if not provider_notes or len(provider_notes.strip()) < 10:
|
| 27 |
+
raise HTTPException(
|
| 28 |
+
status_code=400,
|
| 29 |
+
detail="Provider notes must be at least 10 characters long"
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
# Process through Groq service
|
| 33 |
+
result = await groq_service.analyze_notes(provider_notes)
|
| 34 |
+
|
| 35 |
+
logger.info("Successfully processed coding request")
|
| 36 |
+
return result
|
| 37 |
+
|
| 38 |
+
except HTTPException:
|
| 39 |
+
raise
|
| 40 |
except Exception as e:
|
| 41 |
+
logger.error(f"Error in analyze_provider_notes: {str(e)}")
|
| 42 |
+
raise HTTPException(
|
| 43 |
+
status_code=500,
|
| 44 |
+
detail=f"Error processing request: {str(e)}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
|
| 48 |
+
# NEW ENDPOINT - File Upload
|
| 49 |
+
@router.post("/upload-file", response_model=FileUploadResponse)
|
| 50 |
+
async def upload_provider_notes_file(file: UploadFile = File(...)):
|
| 51 |
+
"""
|
| 52 |
+
Upload a TXT file containing provider notes and extract ICD-10 and CPT codes
|
| 53 |
+
|
| 54 |
+
This endpoint accepts a TXT file, extracts the text, and processes it through the LLM.
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
file: TXT file containing provider notes
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
FileUploadResponse with extracted codes and explanations
|
| 61 |
+
"""
|
| 62 |
+
try:
|
| 63 |
+
logger.info(f"Received file upload request: {file.filename}")
|
| 64 |
+
|
| 65 |
+
# Step 1: Extract text from uploaded file
|
| 66 |
+
extraction_result = await file_service.extract_text_from_file(file)
|
| 67 |
+
|
| 68 |
+
extracted_text = extraction_result["text"]
|
| 69 |
+
filename = extraction_result["filename"]
|
| 70 |
+
text_length = extraction_result["text_length"]
|
| 71 |
+
|
| 72 |
+
logger.info(f"Extracted {text_length} characters from {filename}")
|
| 73 |
+
|
| 74 |
+
# Step 2: Process extracted text through Groq LLM
|
| 75 |
+
coding_result = await groq_service.analyze_notes(extracted_text)
|
| 76 |
+
|
| 77 |
+
logger.info(f"Successfully processed file: {filename}")
|
| 78 |
+
|
| 79 |
+
# Step 3: Return combined response
|
| 80 |
+
return FileUploadResponse(
|
| 81 |
+
success=True,
|
| 82 |
+
filename=filename,
|
| 83 |
+
extracted_text_length=text_length,
|
| 84 |
+
cpt_codes=coding_result.cpt_codes,
|
| 85 |
+
cpt_explanation=coding_result.cpt_explanation,
|
| 86 |
+
icd_codes=coding_result.icd_codes,
|
| 87 |
+
icd_explanation=coding_result.icd_explanation
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
except HTTPException:
|
| 91 |
+
raise
|
| 92 |
+
except Exception as e:
|
| 93 |
+
logger.error(f"Error in upload_provider_notes_file: {str(e)}")
|
| 94 |
+
raise HTTPException(
|
| 95 |
+
status_code=500,
|
| 96 |
+
detail=f"Error processing uploaded file: {str(e)}"
|
| 97 |
+
)
|
src/main.py
CHANGED
|
@@ -1,19 +1,17 @@
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
-
import sys
|
| 4 |
-
import os
|
| 5 |
-
|
| 6 |
-
# Add current directory to path
|
| 7 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
-
|
| 9 |
from api.routes import router
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
app = FastAPI(
|
| 12 |
-
title="ICD-10
|
| 13 |
-
description="
|
| 14 |
-
version="1.
|
| 15 |
-
docs_url="/docs",
|
| 16 |
-
redoc_url="/redoc"
|
| 17 |
)
|
| 18 |
|
| 19 |
# CORS middleware
|
|
@@ -25,17 +23,24 @@ app.add_middleware(
|
|
| 25 |
allow_headers=["*"],
|
| 26 |
)
|
| 27 |
|
| 28 |
-
# Include
|
| 29 |
-
app.include_router(router)
|
| 30 |
|
| 31 |
@app.get("/")
|
| 32 |
async def root():
|
| 33 |
return {
|
| 34 |
-
"message": "ICD-10
|
| 35 |
-
"
|
| 36 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
if __name__ == "__main__":
|
| 40 |
-
import uvicorn
|
| 41 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from api.routes import router
|
| 4 |
+
import uvicorn
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
# Configure logging
|
| 8 |
+
logging.basicConfig(level=logging.INFO)
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
app = FastAPI(
|
| 12 |
+
title="ICD-10 & CPT Code Analyzer",
|
| 13 |
+
description="AI-powered medical coding assistant",
|
| 14 |
+
version="1.1.0"
|
|
|
|
|
|
|
| 15 |
)
|
| 16 |
|
| 17 |
# CORS middleware
|
|
|
|
| 23 |
allow_headers=["*"],
|
| 24 |
)
|
| 25 |
|
| 26 |
+
# Include API routes
|
| 27 |
+
app.include_router(router, prefix="/api")
|
| 28 |
|
| 29 |
@app.get("/")
|
| 30 |
async def root():
|
| 31 |
return {
|
| 32 |
+
"message": "ICD-10 & CPT Code Analyzer API",
|
| 33 |
+
"version": "1.1.0",
|
| 34 |
+
"endpoints": {
|
| 35 |
+
"text_input": "/api/coding",
|
| 36 |
+
"file_upload": "/api/upload-file",
|
| 37 |
+
"docs": "/docs"
|
| 38 |
+
}
|
| 39 |
}
|
| 40 |
|
| 41 |
+
@app.get("/health")
|
| 42 |
+
async def health_check():
|
| 43 |
+
return {"status": "healthy"}
|
| 44 |
+
|
| 45 |
if __name__ == "__main__":
|
|
|
|
| 46 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
src/models/request_models.py
CHANGED
|
@@ -22,4 +22,29 @@ class CodingResponse(BaseModel):
|
|
| 22 |
cpt_codes: list
|
| 23 |
cpt_explanation: str
|
| 24 |
icd_codes: list
|
| 25 |
-
icd_explanation: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
cpt_codes: list
|
| 23 |
cpt_explanation: str
|
| 24 |
icd_codes: list
|
| 25 |
+
icd_explanation: str
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# NEW: File upload response model
|
| 29 |
+
class FileUploadResponse(BaseModel):
|
| 30 |
+
"""Response model for file upload endpoint"""
|
| 31 |
+
success: bool = Field(description="Whether file processing was successful")
|
| 32 |
+
filename: str = Field(description="Name of uploaded file")
|
| 33 |
+
extracted_text_length: int = Field(description="Length of extracted text")
|
| 34 |
+
cpt_codes: list = Field(description="List of CPT codes")
|
| 35 |
+
cpt_explanation: str = Field(description="Explanation of CPT codes")
|
| 36 |
+
icd_codes: list = Field(description="List of ICD codes")
|
| 37 |
+
icd_explanation: str = Field(description="Explanation of ICD codes")
|
| 38 |
+
|
| 39 |
+
class Config:
|
| 40 |
+
json_schema_extra = {
|
| 41 |
+
"example": {
|
| 42 |
+
"success": True,
|
| 43 |
+
"filename": "provider_notes.txt",
|
| 44 |
+
"extracted_text_length": 450,
|
| 45 |
+
"cpt_codes": ["99213", "93000"],
|
| 46 |
+
"cpt_explanation": "Office visit and EKG",
|
| 47 |
+
"icd_codes": ["I20.0", "R07.9"],
|
| 48 |
+
"icd_explanation": "Unstable angina and chest pain"
|
| 49 |
+
}
|
| 50 |
+
}
|
src/services/file_service.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import UploadFile, HTTPException
|
| 2 |
+
import os
|
| 3 |
+
from typing import Dict
|
| 4 |
+
import logging
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class FileService:
|
| 10 |
+
"""Service to handle file uploads and text extraction"""
|
| 11 |
+
|
| 12 |
+
ALLOWED_EXTENSIONS = {'.txt'}
|
| 13 |
+
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
|
| 14 |
+
|
| 15 |
+
@staticmethod
|
| 16 |
+
def validate_file(file: UploadFile) -> None:
|
| 17 |
+
"""
|
| 18 |
+
Validate uploaded file
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
file: Uploaded file object
|
| 22 |
+
|
| 23 |
+
Raises:
|
| 24 |
+
HTTPException: If file is invalid
|
| 25 |
+
"""
|
| 26 |
+
# Check if file exists
|
| 27 |
+
if not file:
|
| 28 |
+
raise HTTPException(status_code=400, detail="No file provided")
|
| 29 |
+
|
| 30 |
+
# Check file extension
|
| 31 |
+
file_ext = os.path.splitext(file.filename)[1].lower()
|
| 32 |
+
if file_ext not in FileService.ALLOWED_EXTENSIONS:
|
| 33 |
+
raise HTTPException(
|
| 34 |
+
status_code=400,
|
| 35 |
+
detail=f"Invalid file type. Only {', '.join(FileService.ALLOWED_EXTENSIONS)} files are allowed"
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
@staticmethod
|
| 39 |
+
async def extract_text_from_file(file: UploadFile) -> Dict[str, any]:
|
| 40 |
+
"""
|
| 41 |
+
Extract text content from uploaded file
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
file: Uploaded file object
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
Dictionary containing extracted text and metadata
|
| 48 |
+
"""
|
| 49 |
+
try:
|
| 50 |
+
# Validate file
|
| 51 |
+
FileService.validate_file(file)
|
| 52 |
+
|
| 53 |
+
# Read file content
|
| 54 |
+
content = await file.read()
|
| 55 |
+
|
| 56 |
+
# Check file size
|
| 57 |
+
file_size = len(content)
|
| 58 |
+
if file_size > FileService.MAX_FILE_SIZE:
|
| 59 |
+
raise HTTPException(
|
| 60 |
+
status_code=400,
|
| 61 |
+
detail=f"File too large. Maximum size is {FileService.MAX_FILE_SIZE / (1024*1024)} MB"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Decode text
|
| 65 |
+
try:
|
| 66 |
+
text = content.decode('utf-8')
|
| 67 |
+
except UnicodeDecodeError:
|
| 68 |
+
try:
|
| 69 |
+
text = content.decode('latin-1')
|
| 70 |
+
except Exception as e:
|
| 71 |
+
raise HTTPException(
|
| 72 |
+
status_code=400,
|
| 73 |
+
detail="Unable to decode file. Please ensure it's a valid text file"
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# Validate extracted text
|
| 77 |
+
if not text.strip():
|
| 78 |
+
raise HTTPException(
|
| 79 |
+
status_code=400,
|
| 80 |
+
detail="File is empty or contains no readable text"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
if len(text.strip()) < 10:
|
| 84 |
+
raise HTTPException(
|
| 85 |
+
status_code=400,
|
| 86 |
+
detail="Extracted text is too short. Please provide more detailed provider notes"
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
logger.info(f"Successfully extracted {len(text)} characters from {file.filename}")
|
| 90 |
+
|
| 91 |
+
return {
|
| 92 |
+
"text": text,
|
| 93 |
+
"filename": file.filename,
|
| 94 |
+
"file_size": file_size,
|
| 95 |
+
"text_length": len(text)
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
except HTTPException:
|
| 99 |
+
raise
|
| 100 |
+
except Exception as e:
|
| 101 |
+
logger.error(f"Error extracting text from file: {str(e)}")
|
| 102 |
+
raise HTTPException(
|
| 103 |
+
status_code=500,
|
| 104 |
+
detail=f"Error processing file: {str(e)}"
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
# Singleton instance
|
| 109 |
+
file_service = FileService()
|
tests/test_api.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
| 1 |
from fastapi.testclient import TestClient
|
| 2 |
from src.main import app
|
|
|
|
| 3 |
|
| 4 |
client = TestClient(app)
|
| 5 |
|
| 6 |
def test_coding_endpoint():
|
|
|
|
| 7 |
provider_notes = {
|
| 8 |
-
"
|
| 9 |
}
|
| 10 |
|
| 11 |
response = client.post("/api/coding", json=provider_notes)
|
|
@@ -13,9 +15,56 @@ def test_coding_endpoint():
|
|
| 13 |
assert response.status_code == 200
|
| 14 |
data = response.json()
|
| 15 |
|
| 16 |
-
assert "
|
| 17 |
-
assert "
|
| 18 |
-
assert isinstance(data["
|
| 19 |
-
assert isinstance(data["
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from fastapi.testclient import TestClient
|
| 2 |
from src.main import app
|
| 3 |
+
from io import BytesIO
|
| 4 |
|
| 5 |
client = TestClient(app)
|
| 6 |
|
| 7 |
def test_coding_endpoint():
|
| 8 |
+
"""Test existing text input endpoint"""
|
| 9 |
provider_notes = {
|
| 10 |
+
"provider_notes": "Patient has a fever and cough."
|
| 11 |
}
|
| 12 |
|
| 13 |
response = client.post("/api/coding", json=provider_notes)
|
|
|
|
| 15 |
assert response.status_code == 200
|
| 16 |
data = response.json()
|
| 17 |
|
| 18 |
+
assert "cpt_codes" in data
|
| 19 |
+
assert "icd_codes" in data
|
| 20 |
+
assert isinstance(data["cpt_codes"], list)
|
| 21 |
+
assert isinstance(data["icd_codes"], list)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_file_upload_endpoint():
|
| 25 |
+
"""Test new file upload endpoint"""
|
| 26 |
+
# Create a sample TXT file
|
| 27 |
+
file_content = b"Patient John Doe presents with acute bronchitis. Cough for 5 days, productive with yellow sputum. Lung exam reveals diffuse wheezing."
|
| 28 |
+
|
| 29 |
+
files = {
|
| 30 |
+
"file": ("provider_notes.txt", BytesIO(file_content), "text/plain")
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
response = client.post("/api/upload-file", files=files)
|
| 34 |
+
|
| 35 |
+
assert response.status_code == 200
|
| 36 |
+
data = response.json()
|
| 37 |
+
|
| 38 |
+
assert data["success"] is True
|
| 39 |
+
assert data["filename"] == "provider_notes.txt"
|
| 40 |
+
assert data["extracted_text_length"] > 0
|
| 41 |
+
assert "cpt_codes" in data
|
| 42 |
+
assert "icd_codes" in data
|
| 43 |
+
assert isinstance(data["cpt_codes"], list)
|
| 44 |
+
assert isinstance(data["icd_codes"], list)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_file_upload_invalid_extension():
|
| 48 |
+
"""Test file upload with invalid file type"""
|
| 49 |
+
file_content = b"Some content"
|
| 50 |
+
|
| 51 |
+
files = {
|
| 52 |
+
"file": ("document.pdf", BytesIO(file_content), "application/pdf")
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
response = client.post("/api/upload-file", files=files)
|
| 56 |
+
|
| 57 |
+
assert response.status_code == 400
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def test_file_upload_empty_file():
|
| 61 |
+
"""Test file upload with empty file"""
|
| 62 |
+
file_content = b""
|
| 63 |
+
|
| 64 |
+
files = {
|
| 65 |
+
"file": ("empty.txt", BytesIO(file_content), "text/plain")
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
response = client.post("/api/upload-file", files=files)
|
| 69 |
+
|
| 70 |
+
assert response.status_code == 400
|