Distopia22 commited on
Commit
1441b40
·
0 Parent(s):

Initial commit: ICD-CPT Coding API

Browse files
.gitattributes ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
3
+
4
+ # Python files
5
+ *.py text eol=lf
6
+ *.pyi text eol=lf
7
+
8
+ # Shell scripts
9
+ *.sh text eol=lf
10
+
11
+ # Docker files
12
+ Dockerfile text eol=lf
13
+ *.dockerfile text eol=lf
14
+
15
+ # Config files
16
+ *.yml text eol=lf
17
+ *.yaml text eol=lf
18
+ *.json text eol=lf
19
+ *.toml text eol=lf
20
+ *.ini text eol=lf
21
+ *.cfg text eol=lf
22
+
23
+ # Documentation
24
+ *.md text eol=lf
25
+ *.txt text eol=lf
26
+
27
+ # Git
28
+ .gitignore text eol=lf
29
+ .gitattributes text eol=lf
30
+
31
+ # Windows specific
32
+ *.bat text eol=crlf
33
+ *.cmd text eol=crlf
34
+ *.ps1 text eol=crlf
.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 project
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"]
README.md ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ICD-CPT Model
2
+
3
+ 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.
4
+
5
+ ## Project Structure
6
+
7
+ - **src/**: Contains the main application code.
8
+ - **main.py**: Entry point for the FastAPI application.
9
+ - **api/**: Contains API route definitions.
10
+ - **routes.py**: Defines the endpoints for the application.
11
+ - **services/**: Contains service logic for interacting with external APIs.
12
+ - **groq_service.py**: Handles requests to the Groq API and processes responses.
13
+ - **models/**: Contains data models for requests and responses.
14
+ - **request_models.py**: Defines request models for incoming data.
15
+ - **response_models.py**: Defines response models for outgoing data.
16
+ - **config/**: Contains configuration settings for the application.
17
+ - **settings.py**: Configuration for API keys and model IDs.
18
+ - **utils/**: Contains utility functions and prompt templates.
19
+ - **prompts.py**: Defines prompt templates for querying the model.
20
+
21
+ - **tests/**: Contains unit tests for the application.
22
+ - **test_api.py**: Tests for API endpoints.
23
+
24
+ - **requirements.txt**: Lists the dependencies required for the project.
25
+
26
+ - **.env.example**: Template for environment variables.
27
+
28
+ - **.gitignore**: Specifies files to be ignored by Git.
29
+
30
+ - **Dockerfile**: Instructions for building a Docker image for the application.
31
+
32
+ ## Setup Instructions
33
+
34
+ 1. **Clone the Repository**:
35
+ Clone the repository to your local machine.
36
+
37
+ 2. **Create a Virtual Environment**:
38
+ Create a virtual environment to manage dependencies.
39
+ ```
40
+ python -m venv venv
41
+ ```
42
+
43
+ 3. **Activate the Virtual Environment**:
44
+ Activate the virtual environment.
45
+ - On Windows:
46
+ ```
47
+ venv\Scripts\activate
48
+ ```
49
+ - On macOS/Linux:
50
+ ```
51
+ source venv/bin/activate
52
+ ```
53
+
54
+ 4. **Install Dependencies**:
55
+ Install the required dependencies using pip.
56
+ ```
57
+ pip install -r requirements.txt
58
+ ```
59
+
60
+ 5. **Set Up Environment Variables**:
61
+ Copy `.env.example` to `.env` and fill in the necessary values, including the Groq API key and model ID.
62
+
63
+ 6. **Run the Application**:
64
+ Start the FastAPI application.
65
+ ```
66
+ uvicorn src.main:app --reload
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ - **Endpoint**: `/api/coding`
72
+ - **Method**: `POST`
73
+ - **Request Body**:
74
+ ```json
75
+ {
76
+ "provider_notes": "Your provider notes here."
77
+ }
78
+ ```
79
+
80
+ - **Response**:
81
+ ```json
82
+ {
83
+ "icd_codes": [
84
+ {
85
+ "code": "ICD_CODE_1",
86
+ "explanation": "Explanation for ICD_CODE_1"
87
+ }
88
+ ],
89
+ "cpt_codes": [
90
+ {
91
+ "code": "CPT_CODE_1",
92
+ "explanation": "Explanation for CPT_CODE_1"
93
+ }
94
+ ]
95
+ }
96
+ ```
97
+
98
+ ## Deployment
99
+
100
+ 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.
101
+
102
+ ## License
103
+
104
+ This project is licensed under the MIT License. See the LICENSE file for more details.
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ groq==0.4.1
4
+ pydantic==2.5.0
5
+ python-dotenv==1.0.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,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ GROQ_API_KEY: str = os.getenv("GROQ_API_KEY")
9
+ MODEL_ID: str = os.getenv("MODEL_ID")
10
+
11
+ def __init__(self):
12
+ if not self.GROQ_API_KEY:
13
+ raise ValueError("GROQ_API_KEY not found in environment variables")
14
+ if not self.MODEL_ID:
15
+ raise ValueError("MODEL_ID not found in environment variables")
16
+
17
+ 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,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ self.client = Groq(api_key=settings.GROQ_API_KEY)
15
+ self.model_id = settings.MODEL_ID
16
+
17
+ async def analyze_provider_notes(self, provider_notes: str) -> dict:
18
+ """
19
+ Analyze provider notes and return ICD and CPT codes with explanations
20
+ """
21
+ try:
22
+ # Create the chat completion with system and user prompts
23
+ chat_completion = self.client.chat.completions.create(
24
+ messages=[
25
+ {
26
+ "role": "system",
27
+ "content": SYSTEM_PROMPT
28
+ },
29
+ {
30
+ "role": "user",
31
+ "content": create_user_prompt(provider_notes)
32
+ }
33
+ ],
34
+ model=self.model_id,
35
+ temperature=0.1, # Low temperature for consistent, factual outputs
36
+ response_format={"type": "json_object"} # Force JSON response
37
+ )
38
+
39
+ # Extract and parse the response
40
+ response_content = chat_completion.choices[0].message.content
41
+ parsed_response = json.loads(response_content)
42
+
43
+ return parsed_response
44
+
45
+ except json.JSONDecodeError as e:
46
+ raise ValueError(f"Failed to parse JSON response from model: {str(e)}")
47
+ except Exception as e:
48
+ raise Exception(f"Error calling Groq API: {str(e)}")
49
+
50
+ 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]