Spaces:
Runtime error
Runtime error
| 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(""" | |
| <div class="header-container"> | |
| <h1>π₯ ICD-10 & CPT Coding Assistant</h1> | |
| <p>AI-Powered Medical Coding Analysis using Groq LLaMA 3.3 70B</p> | |
| </div> | |
| """) | |
| # Main Layout | |
| with gr.Row(): | |
| # Left Sidebar | |
| with gr.Column(scale=1, elem_classes="sidebar"): | |
| gr.HTML('<div class="sidebar-title">π Example Cases</div>') | |
| # 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(""" | |
| <div class="footer"> | |
| <p>Powered by <strong>Groq LLaMA 3.3 70B</strong> | <strong>FastAPI</strong> | <strong>Gradio</strong></p> | |
| <p>Β© 2025 ICD-CPT Coding Assistant - Secure & HIPAA Compliant</p> | |
| </div> | |
| """) | |
| # ==================== 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 | |
| ) |