Commit
·
56edde7
0
Parent(s):
Initial backend deployment
Browse files- .gitattributes +72 -0
- .gitignore +30 -0
- Dockerfile +18 -0
- how_to_run.txt +117 -0
- readme.md +30 -0
- requirements.txt +6 -0
- src/api/__init__.py +3 -0
- src/api/routes.py +34 -0
- src/config/__init__.py +3 -0
- src/config/settings.py +23 -0
- src/main.py +41 -0
- src/models/__init__.py +3 -0
- src/models/request_models.py +25 -0
- src/models/response_models.py +38 -0
- src/services/__init__.py +3 -0
- src/services/groq_service.py +61 -0
- src/utils/__init__.py +1 -0
- src/utils/prompts.py +46 -0
- tests/__init__.py +1 -0
- tests/test_api.py +21 -0
.gitattributes
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<<<<<<< HEAD
|
| 2 |
+
# Auto detect text files and perform LF normalization
|
| 3 |
+
* text=auto
|
| 4 |
+
|
| 5 |
+
# Python files
|
| 6 |
+
*.py text eol=lf
|
| 7 |
+
*.pyi text eol=lf
|
| 8 |
+
|
| 9 |
+
# Shell scripts
|
| 10 |
+
*.sh text eol=lf
|
| 11 |
+
|
| 12 |
+
# Docker files
|
| 13 |
+
Dockerfile text eol=lf
|
| 14 |
+
*.dockerfile text eol=lf
|
| 15 |
+
|
| 16 |
+
# Config files
|
| 17 |
+
*.yml text eol=lf
|
| 18 |
+
*.yaml text eol=lf
|
| 19 |
+
*.json text eol=lf
|
| 20 |
+
*.toml text eol=lf
|
| 21 |
+
*.ini text eol=lf
|
| 22 |
+
*.cfg text eol=lf
|
| 23 |
+
|
| 24 |
+
# Documentation
|
| 25 |
+
*.md text eol=lf
|
| 26 |
+
*.txt text eol=lf
|
| 27 |
+
|
| 28 |
+
# Git
|
| 29 |
+
.gitignore text eol=lf
|
| 30 |
+
.gitattributes text eol=lf
|
| 31 |
+
|
| 32 |
+
# Windows specific
|
| 33 |
+
*.bat text eol=crlf
|
| 34 |
+
*.cmd text eol=crlf
|
| 35 |
+
*.ps1 text eol=crlf
|
| 36 |
+
=======
|
| 37 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 62 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 63 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 64 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 65 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 66 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 67 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 68 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 69 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 70 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 71 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 72 |
+
>>>>>>> cc72a56032827788eed3105a0ef90e037552e5a7
|
.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment variables
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
*$py.class
|
| 8 |
+
*.so
|
| 9 |
+
.Python
|
| 10 |
+
env/
|
| 11 |
+
venv/
|
| 12 |
+
ENV/
|
| 13 |
+
build/
|
| 14 |
+
dist/
|
| 15 |
+
*.egg-info/
|
| 16 |
+
|
| 17 |
+
# IDE
|
| 18 |
+
.vscode/
|
| 19 |
+
.idea/
|
| 20 |
+
*.swp
|
| 21 |
+
*.swo
|
| 22 |
+
|
| 23 |
+
# OS
|
| 24 |
+
.DS_Store
|
| 25 |
+
Thumbs.db
|
| 26 |
+
|
| 27 |
+
# Test
|
| 28 |
+
.pytest_cache/
|
| 29 |
+
.coverage
|
| 30 |
+
htmlcov/
|
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy requirements first for better caching
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
|
| 8 |
+
# Install dependencies
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# Copy the entire backend
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Expose port 7860 (Hugging Face Spaces default)
|
| 15 |
+
EXPOSE 7860
|
| 16 |
+
|
| 17 |
+
# Run the application
|
| 18 |
+
CMD ["python", "src/main.py"]
|
how_to_run.txt
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<<<<<<< HEAD
|
| 2 |
+
# ICD-CPT Model
|
| 3 |
+
|
| 4 |
+
This project implements a FastAPI application that utilizes the Groq API and a language model (Llama 3.3 70b Versatile) to provide CPT and ICD coding based on provider notes. The application is designed to return structured JSON responses that include the predicted codes along with explanations for each code.
|
| 5 |
+
|
| 6 |
+
## Project Structure
|
| 7 |
+
|
| 8 |
+
- **src/**: Contains the main application code.
|
| 9 |
+
- **main.py**: Entry point for the FastAPI application.
|
| 10 |
+
- **api/**: Contains API route definitions.
|
| 11 |
+
- **routes.py**: Defines the endpoints for the application.
|
| 12 |
+
- **services/**: Contains service logic for interacting with external APIs.
|
| 13 |
+
- **groq_service.py**: Handles requests to the Groq API and processes responses.
|
| 14 |
+
- **models/**: Contains data models for requests and responses.
|
| 15 |
+
- **request_models.py**: Defines request models for incoming data.
|
| 16 |
+
- **response_models.py**: Defines response models for outgoing data.
|
| 17 |
+
- **config/**: Contains configuration settings for the application.
|
| 18 |
+
- **settings.py**: Configuration for API keys and model IDs.
|
| 19 |
+
- **utils/**: Contains utility functions and prompt templates.
|
| 20 |
+
- **prompts.py**: Defines prompt templates for querying the model.
|
| 21 |
+
|
| 22 |
+
- **tests/**: Contains unit tests for the application.
|
| 23 |
+
- **test_api.py**: Tests for API endpoints.
|
| 24 |
+
|
| 25 |
+
- **requirements.txt**: Lists the dependencies required for the project.
|
| 26 |
+
|
| 27 |
+
- **.env.example**: Template for environment variables.
|
| 28 |
+
|
| 29 |
+
- **.gitignore**: Specifies files to be ignored by Git.
|
| 30 |
+
|
| 31 |
+
- **Dockerfile**: Instructions for building a Docker image for the application.
|
| 32 |
+
|
| 33 |
+
## Setup Instructions
|
| 34 |
+
|
| 35 |
+
1. **Clone the Repository**:
|
| 36 |
+
Clone the repository to your local machine.
|
| 37 |
+
|
| 38 |
+
2. **Create a Virtual Environment**:
|
| 39 |
+
Create a virtual environment to manage dependencies.
|
| 40 |
+
```
|
| 41 |
+
python -m venv venv
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
3. **Activate the Virtual Environment**:
|
| 45 |
+
Activate the virtual environment.
|
| 46 |
+
- On Windows:
|
| 47 |
+
```
|
| 48 |
+
venv\Scripts\activate
|
| 49 |
+
```
|
| 50 |
+
- On macOS/Linux:
|
| 51 |
+
```
|
| 52 |
+
source venv/bin/activate
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
4. **Install Dependencies**:
|
| 56 |
+
Install the required dependencies using pip.
|
| 57 |
+
```
|
| 58 |
+
pip install -r requirements.txt
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
5. **Set Up Environment Variables**:
|
| 62 |
+
Copy `.env.example` to `.env` and fill in the necessary values, including the Groq API key and model ID.
|
| 63 |
+
|
| 64 |
+
6. **Run the Application**:
|
| 65 |
+
Start the FastAPI application.
|
| 66 |
+
```
|
| 67 |
+
uvicorn src.main:app --reload
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
## Usage
|
| 71 |
+
|
| 72 |
+
- **Endpoint**: `/api/coding`
|
| 73 |
+
- **Method**: `POST`
|
| 74 |
+
- **Request Body**:
|
| 75 |
+
```json
|
| 76 |
+
{
|
| 77 |
+
"provider_notes": "Your provider notes here."
|
| 78 |
+
}
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
- **Response**:
|
| 82 |
+
```json
|
| 83 |
+
{
|
| 84 |
+
"icd_codes": [
|
| 85 |
+
{
|
| 86 |
+
"code": "ICD_CODE_1",
|
| 87 |
+
"explanation": "Explanation for ICD_CODE_1"
|
| 88 |
+
}
|
| 89 |
+
],
|
| 90 |
+
"cpt_codes": [
|
| 91 |
+
{
|
| 92 |
+
"code": "CPT_CODE_1",
|
| 93 |
+
"explanation": "Explanation for CPT_CODE_1"
|
| 94 |
+
}
|
| 95 |
+
]
|
| 96 |
+
}
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
## Deployment
|
| 100 |
+
|
| 101 |
+
For deployment, you can use Docker to containerize the application. Follow the instructions in the Dockerfile to build and run the application in a containerized environment.
|
| 102 |
+
|
| 103 |
+
## License
|
| 104 |
+
|
| 105 |
+
This project is licensed under the MIT License. See the LICENSE file for more details.
|
| 106 |
+
=======
|
| 107 |
+
---
|
| 108 |
+
title: Icd Cpt Coding Api
|
| 109 |
+
emoji: 📈
|
| 110 |
+
colorFrom: indigo
|
| 111 |
+
colorTo: indigo
|
| 112 |
+
sdk: docker
|
| 113 |
+
pinned: false
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 117 |
+
>>>>>>> cc72a56032827788eed3105a0ef90e037552e5a7
|
readme.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ICD CPT Coding API Backend
|
| 3 |
+
emoji: 🔧
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# ICD-10 and CPT Coding API - Backend
|
| 12 |
+
|
| 13 |
+
FastAPI backend service for medical coding analysis.
|
| 14 |
+
|
| 15 |
+
## API Endpoints
|
| 16 |
+
|
| 17 |
+
- **POST /api/v1/analyze** - Analyze provider notes
|
| 18 |
+
- **GET /api/v1/health** - Health check
|
| 19 |
+
|
| 20 |
+
## Documentation
|
| 21 |
+
|
| 22 |
+
- Interactive Docs: `/docs`
|
| 23 |
+
- ReDoc: `/redoc`
|
| 24 |
+
|
| 25 |
+
## Environment Variables
|
| 26 |
+
|
| 27 |
+
Set in Hugging Face Space Settings → Repository secrets:
|
| 28 |
+
|
| 29 |
+
- `GROQ_API_KEY`
|
| 30 |
+
- `MODEL_ID`
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
groq==0.11.0
|
| 4 |
+
pydantic==2.5.0
|
| 5 |
+
python-dotenv==1.0.0
|
| 6 |
+
httpx==0.27.0
|
src/api/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The contents of the file: /icd-cpt-model/icd-cpt-model/src/api/__init__.py
|
| 2 |
+
|
| 3 |
+
# This file is intentionally left blank.
|
src/api/routes.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException
|
| 2 |
+
import sys
|
| 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 |
+
@router.post("/analyze", response_model=CodingResponse)
|
| 15 |
+
async def analyze_provider_notes(request: ProviderNotesRequest):
|
| 16 |
+
"""
|
| 17 |
+
Analyze provider notes and return ICD-10 and CPT codes with explanations.
|
| 18 |
+
|
| 19 |
+
- **provider_notes**: The medical provider notes to analyze
|
| 20 |
+
|
| 21 |
+
Returns ICD-10 codes, CPT codes, and explanations for each.
|
| 22 |
+
"""
|
| 23 |
+
try:
|
| 24 |
+
result = await groq_service.analyze_provider_notes(request.provider_notes)
|
| 25 |
+
return CodingResponse(**result)
|
| 26 |
+
except ValueError as e:
|
| 27 |
+
raise HTTPException(status_code=422, detail=str(e))
|
| 28 |
+
except Exception as e:
|
| 29 |
+
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
| 30 |
+
|
| 31 |
+
@router.get("/health")
|
| 32 |
+
async def health_check():
|
| 33 |
+
"""Health check endpoint"""
|
| 34 |
+
return {"status": "healthy", "service": "ICD-CPT Coding API"}
|
src/config/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The contents of the file: /icd-cpt-model/icd-cpt-model/src/config/__init__.py
|
| 2 |
+
|
| 3 |
+
# This file is intentionally left blank.
|
src/config/settings.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
|
| 4 |
+
# Load environment variables from .env file
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
class Settings:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 10 |
+
self.MODEL_ID = os.getenv("MODEL_ID", "llama-3.3-70b-versatile")
|
| 11 |
+
|
| 12 |
+
# Validate required environment variables
|
| 13 |
+
if not self.GROQ_API_KEY:
|
| 14 |
+
raise ValueError(
|
| 15 |
+
"GROQ_API_KEY not found in environment variables. "
|
| 16 |
+
"Please set it in Hugging Face Space Settings -> Repository secrets"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
print(f"✅ Settings loaded successfully")
|
| 20 |
+
print(f"✅ Model ID: {self.MODEL_ID}")
|
| 21 |
+
print(f"✅ API Key configured: {self.GROQ_API_KEY[:10]}...")
|
| 22 |
+
|
| 23 |
+
settings = Settings()
|
src/main.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 and CPT Coding API",
|
| 13 |
+
description="Analyzes medical provider notes and returns appropriate ICD-10 and CPT codes with explanations",
|
| 14 |
+
version="1.0.0",
|
| 15 |
+
docs_url="/docs",
|
| 16 |
+
redoc_url="/redoc"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
# CORS middleware
|
| 20 |
+
app.add_middleware(
|
| 21 |
+
CORSMiddleware,
|
| 22 |
+
allow_origins=["*"],
|
| 23 |
+
allow_credentials=True,
|
| 24 |
+
allow_methods=["*"],
|
| 25 |
+
allow_headers=["*"],
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# Include routers
|
| 29 |
+
app.include_router(router)
|
| 30 |
+
|
| 31 |
+
@app.get("/")
|
| 32 |
+
async def root():
|
| 33 |
+
return {
|
| 34 |
+
"message": "ICD-10 and CPT Coding API",
|
| 35 |
+
"docs": "/docs",
|
| 36 |
+
"health": "/api/v1/health"
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
import uvicorn
|
| 41 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
src/models/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The contents of the file: /icd-cpt-model/icd-cpt-model/src/models/__init__.py
|
| 2 |
+
|
| 3 |
+
# This file is intentionally left blank.
|
src/models/request_models.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
|
| 3 |
+
class ProviderNotesRequest(BaseModel):
|
| 4 |
+
provider_notes: str = Field(
|
| 5 |
+
...,
|
| 6 |
+
description="The medical provider notes to analyze",
|
| 7 |
+
min_length=10,
|
| 8 |
+
example="Patient presents with acute bronchitis. Performed comprehensive examination and prescribed antibiotics."
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
class Config:
|
| 12 |
+
json_schema_extra = {
|
| 13 |
+
"example": {
|
| 14 |
+
"provider_notes": "Patient presents with acute bronchitis. Cough for 5 days, productive with yellow sputum. Lung exam reveals diffuse wheezing. Prescribed azithromycin 500mg."
|
| 15 |
+
}
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
class ProviderNote(BaseModel):
|
| 19 |
+
note: str
|
| 20 |
+
|
| 21 |
+
class CodingResponse(BaseModel):
|
| 22 |
+
cpt_codes: list
|
| 23 |
+
cpt_explanation: str
|
| 24 |
+
icd_codes: list
|
| 25 |
+
icd_explanation: str
|
src/models/response_models.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import List
|
| 3 |
+
|
| 4 |
+
class ICDCode(BaseModel):
|
| 5 |
+
code: str = Field(..., description="ICD-10 diagnosis code")
|
| 6 |
+
description: str = Field(..., description="Description of the diagnosis")
|
| 7 |
+
explanation: str = Field(..., description="Explanation for why this code was selected")
|
| 8 |
+
|
| 9 |
+
class CPTCode(BaseModel):
|
| 10 |
+
code: str = Field(..., description="CPT procedure code")
|
| 11 |
+
description: str = Field(..., description="Description of the procedure/service")
|
| 12 |
+
explanation: str = Field(..., description="Explanation for why this code was selected")
|
| 13 |
+
|
| 14 |
+
class CodingResponse(BaseModel):
|
| 15 |
+
icd_codes: List[ICDCode] = Field(default_factory=list, description="List of ICD-10 codes")
|
| 16 |
+
cpt_codes: List[CPTCode] = Field(default_factory=list, description="List of CPT codes")
|
| 17 |
+
overall_summary: str = Field(..., description="Overall summary of coding decisions")
|
| 18 |
+
|
| 19 |
+
class Config:
|
| 20 |
+
json_schema_extra = {
|
| 21 |
+
"example": {
|
| 22 |
+
"icd_codes": [
|
| 23 |
+
{
|
| 24 |
+
"code": "J20.9",
|
| 25 |
+
"description": "Acute bronchitis, unspecified",
|
| 26 |
+
"explanation": "Patient presents with acute bronchitis as documented in provider notes"
|
| 27 |
+
}
|
| 28 |
+
],
|
| 29 |
+
"cpt_codes": [
|
| 30 |
+
{
|
| 31 |
+
"code": "99213",
|
| 32 |
+
"description": "Office visit, established patient",
|
| 33 |
+
"explanation": "Comprehensive examination performed as documented"
|
| 34 |
+
}
|
| 35 |
+
],
|
| 36 |
+
"overall_summary": "Patient encounter for acute bronchitis with examination and treatment"
|
| 37 |
+
}
|
| 38 |
+
}
|
src/services/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The contents of the file: /icd-cpt-model/icd-cpt-model/src/services/__init__.py
|
| 2 |
+
|
| 3 |
+
# This file is intentionally left blank.
|
src/services/groq_service.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from groq import Groq
|
| 2 |
+
import json
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
# Add parent directory to path for imports
|
| 7 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 8 |
+
|
| 9 |
+
from config.settings import settings
|
| 10 |
+
from utils.prompts import SYSTEM_PROMPT, create_user_prompt
|
| 11 |
+
|
| 12 |
+
class GroqService:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
try:
|
| 15 |
+
print(f"🔧 Initializing Groq client...")
|
| 16 |
+
self.client = Groq(api_key=settings.GROQ_API_KEY)
|
| 17 |
+
self.model_id = settings.MODEL_ID
|
| 18 |
+
print(f"✅ Groq client initialized successfully with model: {self.model_id}")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"❌ Failed to initialize Groq client: {str(e)}")
|
| 21 |
+
raise
|
| 22 |
+
|
| 23 |
+
async def analyze_provider_notes(self, provider_notes: str) -> dict:
|
| 24 |
+
"""
|
| 25 |
+
Analyze provider notes and return ICD and CPT codes with explanations
|
| 26 |
+
"""
|
| 27 |
+
try:
|
| 28 |
+
print(f"📝 Analyzing provider notes...")
|
| 29 |
+
# Create the chat completion with system and user prompts
|
| 30 |
+
chat_completion = self.client.chat.completions.create(
|
| 31 |
+
messages=[
|
| 32 |
+
{
|
| 33 |
+
"role": "system",
|
| 34 |
+
"content": SYSTEM_PROMPT
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"role": "user",
|
| 38 |
+
"content": create_user_prompt(provider_notes)
|
| 39 |
+
}
|
| 40 |
+
],
|
| 41 |
+
model=self.model_id,
|
| 42 |
+
temperature=0.1, # Low temperature for consistent, factual outputs
|
| 43 |
+
response_format={"type": "json_object"} # Force JSON response
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# Extract and parse the response
|
| 47 |
+
response_content = chat_completion.choices[0].message.content
|
| 48 |
+
print(f"✅ Received response from Groq API")
|
| 49 |
+
parsed_response = json.loads(response_content)
|
| 50 |
+
|
| 51 |
+
return parsed_response
|
| 52 |
+
|
| 53 |
+
except json.JSONDecodeError as e:
|
| 54 |
+
print(f"❌ JSON parsing error: {str(e)}")
|
| 55 |
+
raise ValueError(f"Failed to parse JSON response from model: {str(e)}")
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"❌ Groq API error: {str(e)}")
|
| 58 |
+
raise Exception(f"Error calling Groq API: {str(e)}")
|
| 59 |
+
|
| 60 |
+
# Don't initialize here - let it be initialized when imported
|
| 61 |
+
groq_service = GroqService()
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# This file is intentionally left blank.
|
src/utils/prompts.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SYSTEM_PROMPT = """You are a specialized medical coding assistant AI that analyzes clinical provider notes and assigns appropriate ICD-10 and CPT codes.
|
| 2 |
+
|
| 3 |
+
CRITICAL INSTRUCTIONS:
|
| 4 |
+
1. You MUST respond ONLY in valid JSON format as specified below
|
| 5 |
+
2. Do NOT hallucinate or make up codes - only use codes you are confident about
|
| 6 |
+
3. If you are uncertain about any code, do NOT include it in the response
|
| 7 |
+
4. If you cannot find any relevant ICD or CPT codes, return empty arrays for those sections
|
| 8 |
+
5. Always provide clear, evidence-based explanations for each code you assign
|
| 9 |
+
6. Your response must be parseable JSON - do not add any text before or after the JSON object
|
| 10 |
+
|
| 11 |
+
REQUIRED JSON FORMAT:
|
| 12 |
+
{
|
| 13 |
+
"icd_codes": [
|
| 14 |
+
{
|
| 15 |
+
"code": "ICD-10 code",
|
| 16 |
+
"description": "Description of the diagnosis",
|
| 17 |
+
"explanation": "Detailed explanation of why this code was selected based on the provider notes"
|
| 18 |
+
}
|
| 19 |
+
],
|
| 20 |
+
"cpt_codes": [
|
| 21 |
+
{
|
| 22 |
+
"code": "CPT code",
|
| 23 |
+
"description": "Description of the procedure/service",
|
| 24 |
+
"explanation": "Detailed explanation of why this code was selected based on the provider notes"
|
| 25 |
+
}
|
| 26 |
+
],
|
| 27 |
+
"overall_summary": "Brief summary of the coding decisions"
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
CODING PRINCIPLES:
|
| 31 |
+
- Only assign codes that are clearly supported by documentation in the provider notes
|
| 32 |
+
- Be conservative - if unsure, omit the code rather than guess
|
| 33 |
+
- Prioritize accuracy over quantity
|
| 34 |
+
- Each explanation must reference specific details from the provider notes
|
| 35 |
+
- If no relevant codes can be determined, respond with empty arrays
|
| 36 |
+
|
| 37 |
+
Remember: Return ONLY the JSON object, nothing else."""
|
| 38 |
+
|
| 39 |
+
def create_user_prompt(provider_notes: str) -> str:
|
| 40 |
+
"""Create the user prompt with provider notes"""
|
| 41 |
+
return f"""Analyze the following provider notes and extract appropriate ICD-10 and CPT codes.
|
| 42 |
+
|
| 43 |
+
PROVIDER NOTES:
|
| 44 |
+
{provider_notes}
|
| 45 |
+
|
| 46 |
+
Respond ONLY with the JSON object following the exact format specified in the system prompt."""
|
tests/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# This file is intentionally left blank.
|
tests/test_api.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"notes": "Patient has a fever and cough."
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
response = client.post("/api/coding", json=provider_notes)
|
| 12 |
+
|
| 13 |
+
assert response.status_code == 200
|
| 14 |
+
data = response.json()
|
| 15 |
+
|
| 16 |
+
assert "CPT" in data
|
| 17 |
+
assert "ICD" in data
|
| 18 |
+
assert isinstance(data["CPT"], list)
|
| 19 |
+
assert isinstance(data["ICD"], list)
|
| 20 |
+
assert "explanation" in data["CPT"][0]
|
| 21 |
+
assert "explanation" in data["ICD"][0]
|