Distopia22 commited on
Commit
480f467
·
1 Parent(s): 0b51e25

Fix: Update response format and add better error handling

Browse files
requirements.txt CHANGED
@@ -1,7 +1,6 @@
1
  fastapi==0.104.1
2
- uvicorn==0.24.0
3
- python-dotenv==1.0.0
4
- groq>=0.9.0,<0.12.0
5
- httpx==0.24.1
6
  pydantic==2.5.0
 
 
7
  python-multipart==0.0.6
 
1
  fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
 
 
 
3
  pydantic==2.5.0
4
+ pydantic-settings==2.1.0
5
+ groq==0.4.0
6
  python-multipart==0.0.6
src/api/routes.py CHANGED
@@ -1,114 +1,104 @@
 
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
- provider_notes = request.provider_notes
24
 
25
- if not provider_notes or len(provider_notes.strip()) < 10:
 
26
  raise HTTPException(
27
  status_code=400,
28
  detail="Provider notes must be at least 10 characters long"
29
  )
30
 
31
- result = await groq_service.analyze_provider_notes(provider_notes)
 
32
 
33
- logger.info("Successfully processed coding request")
34
 
35
- return CodingResponse(
36
- cpt_codes=result.get("CPT", []),
37
- cpt_explanation=result.get("CPT_explanation", ""),
38
- icd_codes=result.get("ICD", []),
39
- icd_explanation=result.get("ICD_explanation", "")
40
- )
41
 
42
  except HTTPException:
43
  raise
 
 
 
44
  except Exception as e:
45
- logger.error(f"Error in analyze_provider_notes: {str(e)}")
46
- raise HTTPException(
47
- status_code=500,
48
- detail=f"Error processing request: {str(e)}"
49
- )
50
 
51
 
52
- # FILE UPLOAD ENDPOINT WITH REGEX-BASED PII REMOVAL
53
  @router.post("/upload-file", response_model=FileUploadResponse)
54
  async def upload_provider_notes_file(file: UploadFile = File(...)):
55
  """
56
- Upload a TXT file containing provider notes and extract ICD-10 and CPT codes
57
 
58
- This endpoint:
59
- 1. Extracts text from uploaded TXT file
60
- 2. Automatically detects and removes patient personal information using regex patterns
61
- 3. Processes sanitized text through LLM
62
- 4. Returns ICD-10 and CPT codes
63
-
64
- Args:
65
- file: TXT file containing provider notes
66
-
67
  Returns:
68
- FileUploadResponse with codes, explanations, and PII removal info
 
 
 
69
  """
70
  try:
71
- logger.info(f"Received file upload request: {file.filename}")
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- # Step 1: Extract text from file with automatic regex-based PII removal
74
- extraction_result = await file_service.extract_text_from_file(
75
- file=file,
76
- remove_pii=True # Always remove PII using regex patterns
77
- )
78
 
79
- extracted_text = extraction_result["text"]
80
- filename = extraction_result["filename"]
81
- text_length = extraction_result["text_length"]
82
- pii_info = extraction_result["pii_info"]
83
 
84
- logger.info(f"Extracted {text_length} characters from {filename}")
85
 
86
- if pii_info["pii_removed"]:
87
- logger.info(f"Removed {pii_info['pii_count']} PII entities using regex before processing")
88
 
89
- # Step 2: Process sanitized text through Groq LLM
90
- coding_result = await groq_service.analyze_provider_notes(extracted_text)
 
 
 
 
 
 
 
 
 
91
 
92
- logger.info(f"Successfully processed file: {filename}")
93
 
94
- # Step 3: Return combined response with PII info
95
- return FileUploadResponse(
96
- success=True,
97
- filename=filename,
98
- extracted_text_length=text_length,
99
- pii_removed=pii_info["pii_removed"],
100
- pii_count=pii_info["pii_count"],
101
- cpt_codes=coding_result.get("CPT", []),
102
- cpt_explanation=coding_result.get("CPT_explanation", ""),
103
- icd_codes=coding_result.get("ICD", []),
104
- icd_explanation=coding_result.get("ICD_explanation", "")
105
- )
106
 
107
  except HTTPException:
108
  raise
109
  except Exception as e:
110
- logger.error(f"Error in upload_provider_notes_file: {str(e)}")
111
- raise HTTPException(
112
- status_code=500,
113
- detail=f"Error processing uploaded file: {str(e)}"
114
- )
 
1
+ import logging
2
  from fastapi import APIRouter, HTTPException, UploadFile, File
3
  from models.request_models import ProviderNotesRequest, FileUploadResponse
4
  from models.response_models import CodingResponse
5
  from services.groq_service import groq_service
6
  from services.file_service import file_service
 
7
 
8
  router = APIRouter()
9
  logger = logging.getLogger(__name__)
10
 
11
 
 
12
  @router.post("/coding", response_model=CodingResponse)
13
  async def analyze_provider_notes(request: ProviderNotesRequest):
14
  """
15
  Analyze provider notes and extract ICD-10 and CPT codes
16
 
17
+ This endpoint accepts provider notes as text input and returns:
18
+ - ICD-10 diagnostic codes with explanations
19
+ - CPT procedure codes with explanations
20
+ - Overall encounter summary
21
  """
22
  try:
23
+ logger.info(f"📥 Received coding request (notes length: {len(request.provider_notes)})")
 
 
24
 
25
+ # Validate input
26
+ if not request.provider_notes or len(request.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
+ # Analyze with Groq
33
+ result = groq_service.analyze_provider_notes(request.provider_notes)
34
 
35
+ logger.info(f" Analysis complete: {len(result.get('icd_codes', []))} ICD codes, {len(result.get('cpt_codes', []))} CPT codes")
36
 
37
+ return result
 
 
 
 
 
38
 
39
  except HTTPException:
40
  raise
41
+ except ValueError as e:
42
+ logger.error(f"❌ Validation error: {str(e)}")
43
+ raise HTTPException(status_code=400, detail=str(e))
44
  except Exception as e:
45
+ logger.error(f"Error processing request: {str(e)}", exc_info=True)
46
+ raise HTTPException(status_code=500, detail=f"Error processing request: {str(e)}")
 
 
 
47
 
48
 
 
49
  @router.post("/upload-file", response_model=FileUploadResponse)
50
  async def upload_provider_notes_file(file: UploadFile = File(...)):
51
  """
52
+ Upload a provider notes file (.txt), remove PII, and analyze
53
 
 
 
 
 
 
 
 
 
 
54
  Returns:
55
+ - File processing info (PII removal stats)
56
+ - ICD-10 codes with explanations
57
+ - CPT codes with explanations
58
+ - Overall summary
59
  """
60
  try:
61
+ logger.info(f"📤 Received file upload: {file.filename}")
62
+
63
+ # Validate file type
64
+ if not file.filename.endswith('.txt'):
65
+ raise HTTPException(
66
+ status_code=400,
67
+ detail="Only .txt files are allowed"
68
+ )
69
+
70
+ # Read file content
71
+ content = await file.read()
72
+ text = content.decode('utf-8')
73
 
74
+ logger.info(f"📄 File read successfully (length: {len(text)})")
 
 
 
 
75
 
76
+ # Remove PII
77
+ cleaned_text, pii_count = file_service.remove_pii(text)
 
 
78
 
79
+ logger.info(f"🔒 PII removal complete: {pii_count} entities removed")
80
 
81
+ # Analyze with Groq
82
+ result = groq_service.analyze_provider_notes(cleaned_text)
83
 
84
+ # Combine results
85
+ response = {
86
+ "success": True,
87
+ "filename": file.filename,
88
+ "extracted_text_length": len(text),
89
+ "pii_removed": pii_count > 0,
90
+ "pii_count": pii_count,
91
+ "icd_codes": result.get("icd_codes", []),
92
+ "cpt_codes": result.get("cpt_codes", []),
93
+ "overall_summary": result.get("overall_summary", "")
94
+ }
95
 
96
+ logger.info(f" File processing complete")
97
 
98
+ return response
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  except HTTPException:
101
  raise
102
  except Exception as e:
103
+ logger.error(f"Error processing uploaded file: {str(e)}", exc_info=True)
104
+ raise HTTPException(status_code=500, detail=f"Error processing uploaded file: {str(e)}")
 
 
 
src/config/settings.py CHANGED
@@ -1,23 +1,26 @@
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()
 
 
 
 
 
 
 
1
  import os
2
+ from pydantic_settings import BaseSettings
3
 
4
+ class Settings(BaseSettings):
5
+ # Groq API Configuration
6
+ groq_api_key: str = os.getenv("GROQ_API_KEY", "")
7
+ groq_model: str = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
8
+
9
+ # API Configuration
10
+ api_title: str = "ICD-CPT Coding API"
11
+ api_version: str = "1.0.0"
12
+ api_description: str = "Medical coding assistant powered by Groq LLaMA 3.3 70B"
13
+
14
+ class Config:
15
+ env_file = ".env"
16
+ case_sensitive = False
17
 
18
+ settings = Settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ # Validate API key on startup
21
+ if not settings.groq_api_key:
22
+ print("⚠️ WARNING: GROQ_API_KEY is not set! API will not function properly.")
23
+ print("Please set GROQ_API_KEY in Hugging Face Space secrets.")
24
+ else:
25
+ print(f"✅ Groq API Key loaded (length: {len(settings.groq_api_key)})")
26
+ print(f"✅ Using model: {settings.groq_model}")
src/main.py CHANGED
@@ -1,20 +1,25 @@
 
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
18
  app.add_middleware(
19
  CORSMiddleware,
20
  allow_origins=["*"],
@@ -23,24 +28,49 @@ app.add_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)
 
 
 
 
 
 
 
1
+ import logging
2
  from fastapi import FastAPI
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from api.routes import router
5
+ from config.settings import settings
 
6
 
7
  # Configure logging
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
+ )
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ # Create FastAPI app
16
  app = FastAPI(
17
+ title=settings.api_title,
18
+ version=settings.api_version,
19
+ description=settings.api_description
20
  )
21
 
22
+ # CORS configuration
23
  app.add_middleware(
24
  CORSMiddleware,
25
  allow_origins=["*"],
 
28
  allow_headers=["*"],
29
  )
30
 
31
+ # Include routes
32
  app.include_router(router, prefix="/api")
33
 
34
+ @app.on_event("startup")
35
+ async def startup_event():
36
+ """Log startup information"""
37
+ logger.info("=" * 50)
38
+ logger.info(f"🚀 {settings.api_title} v{settings.api_version}")
39
+ logger.info("=" * 50)
40
+
41
+ if settings.groq_api_key:
42
+ logger.info(f"✅ Groq API Key: Configured (length: {len(settings.groq_api_key)})")
43
+ logger.info(f"✅ Groq Model: {settings.groq_model}")
44
+ else:
45
+ logger.error("❌ GROQ_API_KEY is NOT set!")
46
+ logger.error("⚠️ API will NOT function without valid API key")
47
+
48
+ logger.info("=" * 50)
49
+
50
  @app.get("/")
51
  async def root():
52
+ """Root endpoint"""
53
  return {
54
+ "service": settings.api_title,
55
+ "version": settings.api_version,
56
+ "status": "running",
57
  "endpoints": {
58
+ "docs": "/docs",
59
+ "health": "/api/v1/health",
60
+ "coding": "/api/coding",
61
+ "upload": "/api/upload-file"
62
  }
63
  }
64
 
65
+ @app.get("/api/v1/health")
66
  async def health_check():
67
+ """Health check endpoint"""
68
+ api_key_status = "configured" if settings.groq_api_key else "missing"
69
+
70
+ return {
71
+ "status": "healthy",
72
+ "service": settings.api_title,
73
+ "version": settings.api_version,
74
+ "groq_api_key": api_key_status,
75
+ "groq_model": settings.groq_model
76
+ }
src/models/response_models.py CHANGED
@@ -1,6 +1,16 @@
1
- from pydantic import BaseModel, Field
2
  from typing import List, Optional
3
 
 
 
 
 
 
 
 
 
 
 
4
  class ProviderNotesRequest(BaseModel):
5
  provider_notes: str = Field(
6
  ...,
@@ -20,33 +30,40 @@ class ProviderNote(BaseModel):
20
  note: str
21
 
22
  class CodingResponse(BaseModel):
23
- cpt_codes: list
24
- cpt_explanation: str
25
- icd_codes: list
26
- icd_explanation: str
27
-
28
-
29
- # PII Detection Detail Model
30
- class PIIDetail(BaseModel):
31
- """Details of detected PII entity"""
32
- entity_type: str = Field(description="Type of PII detected (e.g., PERSON, PHONE_NUMBER)")
33
- start: int = Field(description="Start position in original text")
34
- end: int = Field(description="End position in original text")
35
- score: float = Field(description="Confidence score")
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- # Updated File Upload Response with PII info
39
  class FileUploadResponse(BaseModel):
40
- """Response model for file upload endpoint with PII removal info"""
41
- success: bool = Field(description="Whether file processing was successful")
42
- filename: str = Field(description="Name of uploaded file")
43
- extracted_text_length: int = Field(description="Length of extracted text (after PII removal)")
44
- pii_removed: bool = Field(description="Whether PII was detected and removed")
45
- pii_count: int = Field(description="Number of PII entities removed")
46
- cpt_codes: list = Field(description="List of CPT codes")
47
- cpt_explanation: str = Field(description="Explanation of CPT codes")
48
- icd_codes: list = Field(description="List of ICD codes")
49
- icd_explanation: str = Field(description="Explanation of ICD codes")
50
 
51
  class Config:
52
  json_schema_extra = {
 
1
+ from pydantic import BaseModel
2
  from typing import List, Optional
3
 
4
+ class ICDCode(BaseModel):
5
+ code: str
6
+ description: str
7
+ explanation: str
8
+
9
+ class CPTCode(BaseModel):
10
+ code: str
11
+ description: str
12
+ explanation: str
13
+
14
  class ProviderNotesRequest(BaseModel):
15
  provider_notes: str = Field(
16
  ...,
 
30
  note: str
31
 
32
  class CodingResponse(BaseModel):
33
+ icd_codes: List[ICDCode]
34
+ cpt_codes: List[CPTCode]
35
+ overall_summary: str
 
 
 
 
 
 
 
 
 
 
36
 
37
+ class Config:
38
+ json_schema_extra = {
39
+ "example": {
40
+ "icd_codes": [
41
+ {
42
+ "code": "J20.9",
43
+ "description": "Acute bronchitis, unspecified",
44
+ "explanation": "Patient presents with acute bronchitis as documented in provider notes"
45
+ }
46
+ ],
47
+ "cpt_codes": [
48
+ {
49
+ "code": "99213",
50
+ "description": "Office visit, established patient",
51
+ "explanation": "Comprehensive examination performed as documented"
52
+ }
53
+ ],
54
+ "overall_summary": "Patient encounter for acute bronchitis with examination and treatment"
55
+ }
56
+ }
57
 
 
58
  class FileUploadResponse(BaseModel):
59
+ success: bool
60
+ filename: str
61
+ extracted_text_length: int
62
+ pii_removed: bool
63
+ pii_count: int
64
+ icd_codes: List[ICDCode]
65
+ cpt_codes: List[CPTCode]
66
+ overall_summary: str
 
 
67
 
68
  class Config:
69
  json_schema_extra = {
src/services/groq_service.py CHANGED
@@ -1,61 +1,127 @@
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()
 
 
1
  import json
2
+ import logging
3
+ from groq import Groq
 
 
 
 
4
  from config.settings import settings
5
+ from utils.prompts import get_coding_prompt
6
+
7
+ logger = logging.getLogger(__name__)
8
 
9
  class GroqService:
10
  def __init__(self):
11
+ """Initialize Groq client with API key from settings"""
12
+ if not settings.groq_api_key:
13
+ logger.error("❌ GROQ_API_KEY is not set!")
14
+ raise ValueError("GROQ_API_KEY environment variable is required")
15
+
16
  try:
17
+ self.client = Groq(api_key=settings.groq_api_key)
18
+ self.model = settings.groq_model
19
+ logger.info(f"✅ Groq client initialized with model: {self.model}")
 
20
  except Exception as e:
21
+ logger.error(f"Failed to initialize Groq client: {str(e)}")
22
  raise
23
 
24
+ def analyze_provider_notes(self, provider_notes: str) -> dict:
25
  """
26
+ Analyze provider notes and extract ICD-10 and CPT codes
27
+
28
+ Args:
29
+ provider_notes: Clinical provider notes text
30
+
31
+ Returns:
32
+ dict: Parsed coding response with ICD-10, CPT codes and summary
33
  """
34
  try:
35
+ logger.info(f"📝 Analyzing provider notes (length: {len(provider_notes)})")
36
+
37
+ # Get the prompt
38
+ prompt = get_coding_prompt(provider_notes)
39
+
40
+ # Call Groq API
41
+ logger.info(f"🚀 Calling Groq API with model: {self.model}")
42
+ response = self.client.chat.completions.create(
43
+ model=self.model,
44
  messages=[
45
  {
46
  "role": "system",
47
+ "content": "You are a medical coding expert specializing in ICD-10 and CPT coding. Always respond with valid JSON."
48
  },
49
  {
50
  "role": "user",
51
+ "content": prompt
52
  }
53
  ],
54
+ temperature=0.3,
55
+ max_tokens=2000,
56
+ response_format={"type": "json_object"}
57
  )
58
 
59
+ # Extract response
60
+ raw_response = response.choices[0].message.content
61
+ logger.info(f"📥 Received response from Groq (length: {len(raw_response)})")
62
+ logger.debug(f"Raw response: {raw_response[:500]}...")
63
 
64
+ # Parse JSON
65
+ parsed_response = json.loads(raw_response)
66
+
67
+ # Validate and structure response
68
+ result = self._structure_response(parsed_response)
69
+
70
+ logger.info(f"✅ Successfully analyzed notes: {len(result.get('icd_codes', []))} ICD codes, {len(result.get('cpt_codes', []))} CPT codes")
71
+
72
+ return result
73
 
74
  except json.JSONDecodeError as e:
75
+ logger.error(f"JSON parsing error: {str(e)}")
76
+ logger.error(f"Raw response: {raw_response}")
77
+ raise ValueError(f"Failed to parse Groq response as JSON: {str(e)}")
78
+
79
  except Exception as e:
80
+ logger.error(f" Error analyzing provider notes: {str(e)}")
81
+ raise
82
+
83
+ def _structure_response(self, parsed_response: dict) -> dict:
84
+ """
85
+ Structure and validate the response from Groq
86
+
87
+ Args:
88
+ parsed_response: Raw parsed JSON from Groq
89
+
90
+ Returns:
91
+ dict: Properly structured response
92
+ """
93
+ # Handle different possible response formats
94
+ icd_codes = []
95
+ cpt_codes = []
96
+ overall_summary = parsed_response.get("overall_summary", "")
97
+
98
+ # Extract ICD codes
99
+ raw_icd = parsed_response.get("icd_codes", [])
100
+ if isinstance(raw_icd, list):
101
+ for icd in raw_icd:
102
+ if isinstance(icd, dict):
103
+ icd_codes.append({
104
+ "code": icd.get("code", ""),
105
+ "description": icd.get("description", ""),
106
+ "explanation": icd.get("explanation", "")
107
+ })
108
+
109
+ # Extract CPT codes
110
+ raw_cpt = parsed_response.get("cpt_codes", [])
111
+ if isinstance(raw_cpt, list):
112
+ for cpt in raw_cpt:
113
+ if isinstance(cpt, dict):
114
+ cpt_codes.append({
115
+ "code": cpt.get("code", ""),
116
+ "description": cpt.get("description", ""),
117
+ "explanation": cpt.get("explanation", "")
118
+ })
119
+
120
+ return {
121
+ "icd_codes": icd_codes,
122
+ "cpt_codes": cpt_codes,
123
+ "overall_summary": overall_summary
124
+ }
125
 
126
+ # Global instance
127
  groq_service = GroqService()
src/utils/prompts.py CHANGED
@@ -43,4 +43,52 @@ def create_user_prompt(provider_notes: str) -> str:
43
  PROVIDER NOTES:
44
  {provider_notes}
45
 
46
- Respond ONLY with the JSON object following the exact format specified in the system prompt."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  PROVIDER NOTES:
44
  {provider_notes}
45
 
46
+ Respond ONLY with the JSON object following the exact format specified in the system prompt."""
47
+
48
+ def get_coding_prompt(provider_notes: str) -> str:
49
+ """
50
+ Generate prompt for medical coding analysis
51
+
52
+ Args:
53
+ provider_notes: Clinical provider notes
54
+
55
+ Returns:
56
+ str: Formatted prompt for Groq API
57
+ """
58
+ return f"""You are an expert medical coder. Analyze the following provider notes and extract:
59
+
60
+ 1. **ICD-10 Diagnostic Codes**: Identify all relevant diagnoses
61
+ 2. **CPT Procedure Codes**: Identify all billable procedures/services
62
+ 3. **Overall Summary**: Brief summary of the encounter
63
+
64
+ **Provider Notes:**
65
+ {provider_notes}
66
+
67
+ **Instructions:**
68
+ - Provide accurate ICD-10 and CPT codes based on current coding guidelines
69
+ - Include detailed explanations for each code
70
+ - Provide an overall summary of the patient encounter
71
+ - Respond ONLY with valid JSON in this exact format:
72
+
73
+ {{
74
+ "icd_codes": [
75
+ {{
76
+ "code": "J20.9",
77
+ "description": "Acute bronchitis, unspecified",
78
+ "explanation": "Patient presents with acute bronchitis as documented with cough and sputum production"
79
+ }}
80
+ ],
81
+ "cpt_codes": [
82
+ {{
83
+ "code": "99213",
84
+ "description": "Office or other outpatient visit for established patient, moderate complexity",
85
+ "explanation": "Established patient office visit with moderate medical decision making"
86
+ }}
87
+ ],
88
+ "overall_summary": "Patient encounter for acute respiratory infection with examination and prescription provided"
89
+ }}
90
+
91
+ **Important:**
92
+ - Return ONLY valid JSON, no markdown, no code blocks
93
+ - Include at least one ICD-10 code and one CPT code if applicable
94
+ - Be specific and accurate with coding"""