import gradio as gr import requests import json # ==================== API Functions ==================== def check_api_health(api_url): """Check if the backend API is healthy""" try: response = requests.get(f"{api_url}/api/v1/health", timeout=10) response.raise_for_status() health_data = response.json() return f"✅ API is healthy and running!\n\nService: {health_data.get('service', 'ICD-CPT Coding API')}" except requests.exceptions.Timeout: return "❌ API Health Check Failed: Request timed out" except requests.exceptions.RequestException as e: return f"❌ API Health Check Failed: {str(e)}" except Exception as e: return f"❌ Unexpected Error: {str(e)}" def analyze_provider_notes(provider_notes, api_url, progress=gr.Progress()): """Send provider notes to backend API for analysis""" if not provider_notes or not provider_notes.strip(): return generate_empty_response() progress(0, desc="Starting analysis...") try: payload = {"provider_notes": provider_notes} progress(0.3, desc="Sending request to API...") response = requests.post( f"{api_url}/api/v1/analyze", json=payload, headers={"Content-Type": "application/json"}, timeout=60 ) progress(0.6, desc="Processing response...") response.raise_for_status() result = response.json() progress(0.9, desc="Formatting results...") # Format complete response formatted_response = format_complete_response(result) progress(1.0, desc="Complete!") return formatted_response except requests.exceptions.Timeout: return "❌ **Request Timeout**\n\nThe API is taking too long to respond. Please try again." except requests.exceptions.HTTPError as e: return f"❌ **HTTP Error {e.response.status_code}**\n\n{e.response.text}" except requests.exceptions.RequestException as e: return f"❌ **Request Error**\n\n{str(e)}" except Exception as e: return f"❌ **Unexpected Error**\n\n{str(e)}" def generate_empty_response(): """Generate empty response message""" return """ ### 💬 Welcome to ICD-10 & CPT Coding Assistant Please enter provider notes in the chat box and click **Send** to analyze. **Tips:** - Provide detailed clinical documentation - Include symptoms, diagnoses, and procedures - Be specific about treatments and prescriptions """ def format_complete_response(result): """Format complete analysis response""" output = "---\n\n" # Overall Summary if result.get("overall_summary"): output += f"### 📋 Overall Summary\n\n{result['overall_summary']}\n\n---\n\n" # ICD-10 Codes output += "### 🏥 ICD-10 Diagnostic Codes\n\n" icd_codes = result.get("icd_codes", []) if icd_codes: for idx, icd in enumerate(icd_codes, 1): output += f"**{idx}. {icd.get('code', 'N/A')}** - {icd.get('description', 'N/A')}\n\n" output += f"*Explanation:* {icd.get('explanation', 'N/A')}\n\n" else: output += "*No ICD-10 codes identified*\n\n" output += "---\n\n" # CPT Codes output += "### 💼 CPT Procedure Codes\n\n" cpt_codes = result.get("cpt_codes", []) if cpt_codes: for idx, cpt in enumerate(cpt_codes, 1): output += f"**{idx}. {cpt.get('code', 'N/A')}** - {cpt.get('description', 'N/A')}\n\n" output += f"*Explanation:* {cpt.get('explanation', 'N/A')}\n\n" else: output += "*No CPT codes identified*\n\n" return output # ==================== Example Notes ==================== EXAMPLES = { "Acute Bronchitis": """Patient presents with acute bronchitis. Cough for 5 days, productive with yellow sputum. Lung exam reveals diffuse wheezing. Prescribed azithromycin 500mg.""", "Type 2 Diabetes": """Patient with type 2 diabetes mellitus, uncontrolled. HbA1c 9.2%. Discussed diet and medication compliance. Adjusted insulin dosing. Referred to diabetes educator.""", "Hypertension Follow-up": """Follow-up visit for hypertension. Blood pressure 145/92. Patient reports good medication compliance. Continue current antihypertensive regimen. Return in 3 months.""", "Annual Physical": """Annual physical examination for 45-year-old patient. Comprehensive metabolic panel ordered. Discussed preventive health measures. No acute concerns. Patient in good health.""", "Acute Pharyngitis": """Patient with sore throat for 3 days, fever 101.5°F. Physical exam shows erythematous pharynx with exudate. Rapid strep test positive. Prescribed amoxicillin 500mg TID for 10 days.""", } def load_example(example_name): """Load example provider notes""" return EXAMPLES.get(example_name, "") # ==================== Custom CSS ==================== custom_css = """ /* Global Styles */ .gradio-container { max-width: 100% !important; padding: 0 !important; background: linear-gradient(135deg, #0d1117 0%, #1a1f2e 100%) !important; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; } /* Header Styling */ .header-container { background: linear-gradient(90deg, #00d4aa 0%, #00a896 100%); padding: 25px; text-align: center; border-radius: 0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); margin-bottom: 0; } .header-container h1 { color: white; font-size: 2rem; font-weight: 700; margin: 0; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); } .header-container p { color: #e0f7f4; font-size: 1rem; margin: 8px 0 0 0; } /* Sidebar Styling */ .sidebar { background: #1a1f2e !important; border-right: 1px solid #00d4aa !important; padding: 20px !important; height: 100vh !important; overflow-y: auto !important; } .sidebar-title { color: #00d4aa !important; font-size: 1.3rem !important; font-weight: 600 !important; margin-bottom: 20px !important; padding-bottom: 10px !important; border-bottom: 2px solid #00d4aa !important; } /* Example Buttons */ .example-btn { background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; padding: 12px 16px !important; margin: 8px 0 !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.3s ease !important; width: 100% !important; text-align: left !important; } .example-btn:hover { background: linear-gradient(135deg, #00ffcc 0%, #00d4aa 100%) !important; transform: translateX(5px) !important; box-shadow: 0 4px 12px rgba(0, 212, 170, 0.4) !important; } /* Chat Container */ .chat-container { background: #0d1117 !important; border-radius: 12px !important; padding: 20px !important; height: calc(100vh - 200px) !important; display: flex !important; flex-direction: column !important; } /* Input Area */ .input-row { background: #1a1f2e !important; border: 2px solid #00d4aa !important; border-radius: 12px !important; padding: 15px !important; margin-top: 10px !important; } .input-row textarea { background: #0d1117 !important; color: #e6e6e6 !important; border: 1px solid #2d3748 !important; border-radius: 8px !important; font-size: 1rem !important; padding: 12px !important; } .input-row textarea:focus { border-color: #00d4aa !important; outline: none !important; box-shadow: 0 0 0 3px rgba(0, 212, 170, 0.1) !important; } /* Send Button */ .send-button { background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important; color: white !important; border: none !important; border-radius: 8px !important; padding: 12px 32px !important; font-size: 1rem !important; font-weight: 600 !important; cursor: pointer !important; transition: all 0.3s ease !important; margin-top: 10px !important; } .send-button:hover { background: linear-gradient(135deg, #00ffcc 0%, #00d4aa 100%) !important; box-shadow: 0 6px 20px rgba(0, 212, 170, 0.4) !important; transform: translateY(-2px) !important; } /* Clear Button */ .clear-button { background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%) !important; color: white !important; border: 1px solid #4a5568 !important; border-radius: 8px !important; padding: 12px 32px !important; font-size: 1rem !important; font-weight: 600 !important; cursor: pointer !important; transition: all 0.3s ease !important; margin-top: 10px !important; } .clear-button:hover { background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%) !important; border-color: #718096 !important; transform: translateY(-2px) !important; } /* Output Area */ .output-container { background: #1a1f2e !important; border: 1px solid #2d3748 !important; border-radius: 12px !important; padding: 20px !important; color: #e6e6e6 !important; overflow-y: auto !important; flex-grow: 1 !important; margin-bottom: 15px !important; } .output-container h3 { color: #00d4aa !important; border-bottom: 2px solid #00d4aa !important; padding-bottom: 8px !important; margin-top: 20px !important; } .output-container strong { color: #00ffcc !important; } .output-container hr { border: none !important; border-top: 1px solid #2d3748 !important; margin: 20px 0 !important; } /* API Config Section */ .api-config { background: #1a1f2e !important; border: 1px solid #2d3748 !important; border-radius: 8px !important; padding: 15px !important; margin-bottom: 15px !important; } .api-config input { background: #0d1117 !important; color: #e6e6e6 !important; border: 1px solid #2d3748 !important; border-radius: 6px !important; padding: 10px !important; } .api-config input:focus { border-color: #00d4aa !important; outline: none !important; } /* Check API Button */ .check-api-button { background: linear-gradient(135deg, #00a896 0%, #008577 100%) !important; color: white !important; border: none !important; border-radius: 6px !important; padding: 10px 20px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.3s ease !important; } .check-api-button:hover { background: linear-gradient(135deg, #00d4aa 0%, #00a896 100%) !important; box-shadow: 0 4px 12px rgba(0, 168, 150, 0.4) !important; } /* Footer */ .footer { text-align: center; padding: 20px; color: #718096; font-size: 0.9rem; background: #0d1117; border-top: 1px solid #2d3748; margin-top: 20px; } .footer strong { color: #00d4aa; } /* Scrollbar Styling */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #0d1117; } ::-webkit-scrollbar-thumb { background: #00d4aa; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #00ffcc; } /* Accordion Styling */ .accordion { background: #1a1f2e !important; border: 1px solid #2d3748 !important; border-radius: 8px !important; } .accordion summary { color: #00d4aa !important; font-weight: 600 !important; padding: 12px !important; cursor: pointer !important; } /* Responsive */ @media (max-width: 768px) { .header-container h1 { font-size: 1.5rem; } .sidebar { height: auto !important; border-right: none !important; border-bottom: 1px solid #00d4aa !important; } } """ # ==================== Gradio Interface ==================== with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="ICD-10 & CPT Coding Assistant") as demo: # Hidden state for API URL api_url_state = gr.State(value="https://Distopia22-icd-cpt-coding-api.hf.space") # Header gr.HTML("""

🏥 ICD-10 & CPT Coding Assistant

AI-Powered Medical Coding Analysis using Groq LLaMA 3.3 70B

""") # Main Layout with gr.Row(): # Left Sidebar with gr.Column(scale=1, elem_classes="sidebar"): gr.HTML('') # Example buttons for example_name in EXAMPLES.keys(): gr.Button( f"📄 {example_name}", elem_classes="example-btn" ).click( fn=lambda name=example_name: load_example(name), outputs=gr.Textbox(elem_id="provider_notes_input", visible=False) ) gr.Markdown("---") # API Configuration with gr.Accordion("⚙️ API Configuration", open=False, elem_classes="api-config"): api_url_input = gr.Textbox( label="Backend API URL", value="https://Distopia22-icd-cpt-coding-api.hf.space", placeholder="https://your-backend-api.hf.space", interactive=True ) check_api_btn = gr.Button("🔍 Check API Status", elem_classes="check-api-button", size="sm") api_status_output = gr.Textbox(label="Status", lines=3, interactive=False) check_api_btn.click( fn=check_api_health, inputs=[api_url_input], outputs=[api_status_output] ) # Update state when URL changes api_url_input.change( fn=lambda x: x, inputs=[api_url_input], outputs=[api_url_state] ) # Right Chat Area with gr.Column(scale=3, elem_classes="chat-container"): # Output/Chat History output_area = gr.Markdown( value=generate_empty_response(), elem_classes="output-container", label="Analysis Results" ) # Input Area with gr.Row(elem_classes="input-row"): with gr.Column(scale=5): provider_notes_input = gr.Textbox( label="", placeholder="Enter clinical provider notes here...", lines=4, max_lines=8, elem_id="provider_notes_input" ) with gr.Column(scale=1): send_btn = gr.Button("🚀 Send", elem_classes="send-button", size="lg") clear_btn = gr.Button("🗑️ Clear", elem_classes="clear-button", size="lg") # Footer gr.HTML(""" """) # ==================== Event Handlers ==================== # Send button - Analyze notes send_btn.click( fn=analyze_provider_notes, inputs=[provider_notes_input, api_url_state], outputs=[output_area] ) # Clear button clear_btn.click( fn=lambda: ("", generate_empty_response()), outputs=[provider_notes_input, output_area] ) # Example buttons - Load example into input for example_name in EXAMPLES.keys(): example_btn = gr.Button(f"📄 {example_name}", visible=False) example_btn.click( fn=lambda name=example_name: load_example(name), outputs=[provider_notes_input] ) # Launch the app if __name__ == "__main__": demo.launch( server_name="0.0.0.0", server_port=7860, share=False )