Axcel1/icd_10_coding_assistant
1
1import gradio as gr2import requests3import time4import re5import threading6import uvicorn7import logging8import os9import signal10import sys11from typing import Dict, List, Optional, Tuple12from collections import defaultdict13 14# Import your backend modules15from service_v2 import app as fastapi_app16from chapter_retrieval_system_v2 import MultiCollectionChapterRetrieval17 18# Configure logging for Spaces19logging.basicConfig(20 level=logging.INFO,21 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'22)23logger = logging.getLogger(__name__)24 25class ICD10SearchInterface:26 def __init__(self, api_base_url: str = "http://127.0.0.1:8000"):27 """Initialize the interface with API base URL"""28 self.api_base_url = api_base_url.rstrip('/')29 self.server_ready = False30 self.max_retries = 30 # Increased for Spaces startup time31 32 # ICD-10 code to chapter mapping33 self.code_to_chapter = self._build_code_to_chapter_mapping()34 35 def _build_code_to_chapter_mapping(self) -> Dict[str, Dict[str, str]]:36 """Build mapping from ICD-10 code ranges to chapters"""37 return {38 # Chapter I: Certain infectious and parasitic diseases (A00-B99)39 "chapter_1_I": {40 "title": "Certain infectious and parasitic diseases",41 "code_ranges": ["A", "B"],42 "description": "Infectious diseases, parasitic diseases, and related conditions"43 },44 45 # Chapter II: Neoplasms (C00-D49)46 "chapter_2_II": {47 "title": "Neoplasms", 48 "code_ranges": ["C", "D"],49 "description": "Malignant neoplasms, benign neoplasms, and neoplasms of uncertain behavior"50 },51 52 # Chapter III: Diseases of blood and blood-forming organs (D50-D89)53 "chapter_3_III": {54 "title": "Diseases of the blood and blood-forming organs",55 "code_ranges": ["D5", "D6", "D7", "D8"],56 "description": "Anemias, coagulation defects, and other blood disorders"57 },58 59 # Chapter IV: Endocrine, nutritional and metabolic diseases (E00-E89)60 "chapter_4_IV": {61 "title": "Endocrine, nutritional and metabolic diseases",62 "code_ranges": ["E"],63 "description": "Diabetes, thyroid disorders, nutritional deficiencies, and metabolic disorders"64 },65 66 # Chapter V: Mental and behavioural disorders (F01-F99)67 "chapter_5_V": {68 "title": "Mental and behavioural disorders",69 "code_ranges": ["F"],70 "description": "Mental disorders, substance abuse, and behavioral conditions"71 },72 73 # Chapter VI: Diseases of the nervous system (G00-G99)74 "chapter_6_VI": {75 "title": "Diseases of the nervous system",76 "code_ranges": ["G"],77 "description": "Neurological disorders, epilepsy, migraines, and nervous system diseases"78 },79 80 # Chapter VII: Diseases of the eye and adnexa (H00-H59)81 "chapter_7_VII": {82 "title": "Diseases of the eye and adnexa",83 "code_ranges": ["H0", "H1", "H2", "H3", "H4", "H5"],84 "description": "Eye diseases, visual disorders, and related conditions"85 },86 87 # Chapter VIII: Diseases of the ear and mastoid process (H60-H95)88 "chapter_8_VIII": {89 "title": "Diseases of the ear and mastoid process",90 "code_ranges": ["H6", "H7", "H8", "H9"],91 "description": "Hearing disorders, ear infections, and mastoid conditions"92 },93 94 # Chapter IX: Diseases of the circulatory system (I00-I99)95 "chapter_9_IX": {96 "title": "Diseases of the circulatory system",97 "code_ranges": ["I"],98 "description": "Heart disease, hypertension, stroke, and vascular disorders"99 },100 101 # Chapter X: Diseases of the respiratory system (J00-J99)102 "chapter_10_X": {103 "title": "Diseases of the respiratory system",104 "code_ranges": ["J"],105 "description": "Pneumonia, asthma, COPD, and other respiratory conditions"106 },107 108 # Chapter XI: Diseases of the digestive system (K00-K95)109 "chapter_11_XI": {110 "title": "Diseases of the digestive system",111 "code_ranges": ["K"],112 "description": "Gastrointestinal disorders, liver disease, and digestive conditions"113 },114 115 # Chapter XII: Diseases of the skin and subcutaneous tissue (L00-L99)116 "chapter_12_XII": {117 "title": "Diseases of the skin and subcutaneous tissue",118 "code_ranges": ["L"],119 "description": "Skin infections, dermatitis, and subcutaneous tissue disorders"120 },121 122 # Chapter XIII: Diseases of the musculoskeletal system (M00-M99)123 "chapter_13_XIII": {124 "title": "Diseases of the musculoskeletal system and connective tissue",125 "code_ranges": ["M"],126 "description": "Arthritis, bone disorders, muscle diseases, and connective tissue conditions"127 },128 129 # Chapter XIV: Diseases of the genitourinary system (N00-N99)130 "chapter_14_XIV": {131 "title": "Diseases of the genitourinary system",132 "code_ranges": ["N"],133 "description": "Kidney disease, urinary disorders, and reproductive system conditions"134 },135 136 # Chapter XV: Pregnancy, childbirth and the puerperium (O00-O9A)137 "chapter_15_XV": {138 "title": "Pregnancy, childbirth and the puerperium",139 "code_ranges": ["O"],140 "description": "Pregnancy complications, delivery issues, and postpartum conditions"141 },142 143 # Chapter XVI: Certain conditions originating in the perinatal period (P00-P96)144 "chapter_16_XVI": {145 "title": "Certain conditions originating in the perinatal period",146 "code_ranges": ["P"],147 "description": "Newborn conditions and perinatal complications"148 },149 150 # Chapter XVII: Congenital malformations (Q00-Q99)151 "chapter_17_XVII": {152 "title": "Congenital malformations, deformations and chromosomal abnormalities",153 "code_ranges": ["Q"],154 "description": "Birth defects and chromosomal disorders"155 },156 157 # Chapter XVIII: Symptoms, signs and abnormal findings (R00-R99)158 "chapter_18_XVIII": {159 "title": "Symptoms, signs and abnormal clinical and laboratory findings",160 "code_ranges": ["R"],161 "description": "Symptoms and signs not elsewhere classified"162 },163 164 # Chapter XIX: Injury, poisoning and external causes (S00-T88)165 "chapter_19_XIX": {166 "title": "Injury, poisoning and certain other consequences of external causes",167 "code_ranges": ["S", "T"],168 "description": "Injuries, poisoning, and external cause consequences"169 },170 171 # Chapter XX: External causes of morbidity (V01-Y99)172 "chapter_20_XX": {173 "title": "External causes of morbidity",174 "code_ranges": ["V", "W", "X", "Y"],175 "description": "External causes of injury and poisoning"176 },177 178 # Chapter XXI: Factors influencing health status (Z00-Z99)179 "chapter_21_XXI": {180 "title": "Factors influencing health status and contact with health services",181 "code_ranges": ["Z"],182 "description": "Health maintenance, screening, and healthcare encounters"183 }184 }185 186 def wait_for_server(self, max_wait_time=60):187 """Wait for FastAPI server to be ready with enhanced logging"""188 logger.info(f"Waiting for FastAPI server at {self.api_base_url}")189 start_time = time.time()190 attempt = 0191 192 while time.time() - start_time < max_wait_time:193 attempt += 1194 try:195 response = requests.get(f"{self.api_base_url}/health", timeout=10)196 if response.status_code == 200:197 self.server_ready = True198 logger.info(f"FastAPI server ready after {attempt} attempts ({time.time() - start_time:.1f}s)")199 return True200 else:201 logger.warning(f"Server returned status {response.status_code}, attempt {attempt}")202 except requests.exceptions.RequestException as e:203 if attempt % 10 == 0: # Log every 10 attempts204 logger.info(f"Waiting for server... attempt {attempt} ({time.time() - start_time:.1f}s)")205 time.sleep(2)206 continue207 208 logger.error(f"FastAPI server failed to start within {max_wait_time} seconds")209 return False210 211 def get_server_status(self) -> Tuple[bool, str]:212 """Get current server status for UI display"""213 if not self.server_ready:214 return False, "Server starting up..."215 216 try:217 response = requests.get(f"{self.api_base_url}/health", timeout=5)218 if response.status_code == 200:219 return True, "Server Ready"220 else:221 return False, f"Server Error (Status: {response.status_code})"222 except requests.exceptions.RequestException as e:223 return False, f"Connection Error: {str(e)}"224 225 def test_connection(self) -> Tuple[bool, str]:226 """Test if the API is accessible"""227 return self.get_server_status()228 229 # Keep all your existing methods (copy from original code)230 def extract_category_code(self, icd_code: str) -> str:231 """Extract the main category code from ICD-10 code (e.g., I21.0 -> I21)"""232 if not icd_code:233 return ""234 235 code = icd_code.strip().upper()236 match = re.match(r'^([A-Z]\d{2,3})', code)237 if match:238 return match.group(1)239 240 return code241 242 def group_codes_by_category(self, results: List[Dict]) -> Dict[str, List[Dict]]:243 """Group ICD-10 codes by their main category"""244 categories = defaultdict(list)245 246 for result in results:247 code = result.get('code', '')248 category = self.extract_category_code(code)249 if category:250 categories[category].append(result)251 252 return dict(categories)253 254 def get_category_info(self, category_code: str, codes_in_category: List[Dict]) -> Dict:255 """Get information about a category from its codes"""256 category_result = None257 max_score = 0258 259 for code_info in codes_in_category:260 if code_info['code'] == category_code:261 category_result = code_info262 break263 if code_info['score'] > max_score:264 max_score = code_info['score']265 category_result = code_info266 267 return category_result or codes_in_category[0]268 269 def get_chapter_info_for_code(self, icd_code: str) -> Optional[Dict[str, str]]:270 """Get chapter information for a given ICD-10 code"""271 if not icd_code:272 return None273 274 code = icd_code.strip().upper()275 276 # Check each chapter's code ranges277 for chapter_id, chapter_data in self.code_to_chapter.items():278 for code_prefix in chapter_data["code_ranges"]:279 if code.startswith(code_prefix):280 return {281 "chapter_id": chapter_id,282 "title": chapter_data["title"],283 "description": chapter_data["description"]284 }285 286 return None287 288 def search_icd10(289 self, 290 query: str,291 limit: int = 10,292 score_threshold: float = 0.3,293 search_mode: str = "smart",294 target_chapters: str = "",295 detailed_analysis: bool = False,296 chapters_per_sentence: int = 2297 ) -> str:298 """Search ICD-10 codes using the API with enhanced error handling for Spaces"""299 if not query or not query.strip():300 return "Please enter a diagnostic query."301 302 if not self.server_ready:303 return """304 <div style='text-align: center; padding: 20px; background: #ffeaa7; border-radius: 8px; margin: 20px 0;'>305 <h3>Server Starting Up</h3>306 <p>The FastAPI server is still initializing. Please wait a moment and try again.</p>307 <p><em>This usually takes 10-30 seconds on first load.</em></p>308 </div>309 """310 311 is_connected, connection_msg = self.test_connection()312 if not is_connected:313 return f"""314 <div style='text-align: center; padding: 20px; background: #fab1a0; border-radius: 8px; margin: 20px 0;'>315 <h3>Connection Error</h3>316 <p>{connection_msg}</p>317 <p><em>Please refresh the page and try again.</em></p>318 </div>319 """320 321 try:322 params = {323 "q": query.strip(),324 "limit": limit * 2,325 "score_threshold": score_threshold,326 "search_mode": search_mode or "smart",327 "detailed_analysis": detailed_analysis,328 "chapters_per_sentence": chapters_per_sentence329 }330 331 if target_chapters and target_chapters.strip():332 params["target_chapters"] = target_chapters.strip()333 334 start_time = time.time()335 response = requests.get(f"{self.api_base_url}/api/search", params=params, timeout=120)336 request_time = time.time() - start_time337 338 if response.status_code != 200:339 error_data = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"detail": response.text}340 return f"""341 <div style='text-align: center; padding: 20px; background: #fab1a0; border-radius: 8px; margin: 20px 0;'>342 <h3>API Error ({response.status_code})</h3>343 <p>{error_data.get('detail', 'Unknown error')}</p>344 </div>345 """346 347 data = response.json()348 return self._format_sentence_results_with_enhanced_categories(data)349 350 except requests.exceptions.Timeout:351 return """352 <div style='text-align: center; padding: 20px; background: #fab1a0; border-radius: 8px; margin: 20px 0;'>353 <h3>Request Timeout</h3>354 <p>The search is taking too long. Try reducing the limit or increasing the score threshold.</p>355 </div>356 """357 except requests.exceptions.RequestException as e:358 logger.error(f"Request error: {e}")359 return f"""360 <div style='text-align: center; padding: 20px; background: #fab1a0; border-radius: 8px; margin: 20px 0;'>361 <h3>Request Error</h3>362 <p>{str(e)}</p>363 </div>364 """365 except Exception as e:366 logger.error(f"Unexpected error: {e}")367 return f"""368 <div style='text-align: center; padding: 20px; background: #fab1a0; border-radius: 8px; margin: 20px 0;'>369 <h3>Unexpected Error</h3>370 <p>{str(e)}</p>371 </div>372 """373 374 def _format_sentence_results_with_enhanced_categories(self, data: Dict) -> str:375 """Format sentence-based results with enhanced category and chapter information"""376 sentence_results = data.get('sentence_results', [])377 378 if not sentence_results:379 return "<div style='text-align: center; color: #666; padding: 20px;'>No sentence-based results available.</div>"380 381 html = """382 <div style='margin-bottom: 20px;'>383 <h3 style='color: #2c3e50; margin-bottom: 15px;'>Results by Sentence with Enhanced Category Information</h3>384 <p style='color: #666; margin-bottom: 20px;'>385 Results are organized by sentence and grouped by ICD-10 categories with chapter context. High-scoring codes are highlighted.386 </p>387 </div>388 """389 390 for i, sent_result in enumerate(sentence_results, 1):391 # Group results by category392 categories = self.group_codes_by_category(sent_result['results'])393 394 html += f"""395 <div style='margin-bottom: 30px; border: 2px solid #3498db; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1);'>396 <div style='background: linear-gradient(135deg, #3498db, #2980b9); color: white; padding: 15px;'>397 <h4 style='margin: 0; font-size: 1.2em;'>398 Sentence {i}: "{sent_result['sentence_text']}"399 </h4>400 <div style='margin-top: 8px; font-size: 0.9em; opacity: 0.9;'>401 <span style='background-color: rgba(255,255,255,0.2); padding: 3px 8px; border-radius: 12px; margin-right: 10px;'>402 {sent_result['total_results']} total results403 </span>404 <span style='background-color: rgba(255,255,255,0.2); padding: 3px 8px; border-radius: 12px;'>405 Top 3 of {len(categories)} categories406 </span>407 </div>408 </div>409 <div style='padding: 20px;'>410 """411 412 # Sort categories by highest score and limit to top 3413 sorted_categories = sorted(414 categories.items(), 415 key=lambda x: max(code['score'] for code in x[1]), 416 reverse=True417 )[:3]418 419 for category_code, codes_in_category in sorted_categories:420 # Get category information421 category_info = self.get_category_info(category_code, codes_in_category)422 highest_score = max(code['score'] for code in codes_in_category)423 category_color = self._get_category_color(highest_score)424 425 # Get chapter information for this category426 sample_code = codes_in_category[0].get('code', category_code)427 chapter_info = self.get_chapter_info_for_code(sample_code)428 429 # Build enhanced category header430 category_title = category_info.get('title', 'Unknown Category')431 chapter_display = ""432 chapter_tooltip = ""433 434 if chapter_info:435 chapter_display = f" • Chapter {chapter_info['chapter_id'].split('_')[1]} ({chapter_info['chapter_id'].split('_')[2]})"436 chapter_tooltip = f"title='{chapter_info['description']}'"437 438 html += f"""439 <div style='margin-bottom: 20px; border: 1px solid {category_color}; border-radius: 8px; overflow: hidden;'>440 <div style='background-color: {category_color}; color: white; padding: 12px 15px;'>441 <div style='display: flex; justify-content: space-between; align-items: flex-start;'>442 <div style='flex-grow: 1;'>443 <h5 style='margin: 0; font-size: 1em; line-height: 1.3;'>444 <span style='display: block;'>445 Category {category_code}: {category_title}446 </span>447 {f'<span style="font-size: 0.85em; opacity: 0.9; display: block; margin-top: 4px;" {chapter_tooltip}>{chapter_display}</span>' if chapter_info else ''}448 </h5>449 {f'<div style="font-size: 0.8em; opacity: 0.8; margin-top: 6px; line-height: 1.2;">{chapter_info["description"]}</div>' if chapter_info else ''}450 </div>451 <div style='text-align: right; margin-left: 15px;'>452 <span style='font-size: 0.8em; background-color: rgba(255,255,255,0.2); padding: 2px 6px; border-radius: 10px; display: block;'>453 Max: {highest_score:.3f}454 </span>455 <span style='font-size: 0.75em; opacity: 0.8; margin-top: 2px; display: block;'>456 {len(codes_in_category)} codes457 </span>458 </div>459 </div>460 </div>461 <div style='padding: 12px;'>462 """463 464 # Sort codes within category by score465 sorted_codes = sorted(codes_in_category, key=lambda x: x['score'], reverse=True)466 467 # Filter out codes that are the same as the category code468 filtered_codes = [code for code in sorted_codes if code.get('code', '') != category_code]469 470 # If we filtered out all codes or have no codes, show a message471 if not filtered_codes:472 html += f"""473 <div style='margin-bottom: 8px; padding: 12px; background-color: #f8f9fa; border-radius: 6px; border-left: 4px solid #95a5a6;'>474 <div style='color: #666; text-align: center; font-style: italic;'>475 Category {category_code} represents the main code group. Specific subcodes available in detailed search.476 </div>477 </div>478 """479 else:480 for j, result in enumerate(filtered_codes, 1):481 score_color = self._get_score_color(result['score'])482 is_high_score = result['score'] >= 0.6483 484 # Add highlighting for high-scoring codes485 highlight_style = ""486 if is_high_score:487 highlight_style = "box-shadow: 0 0 0 2px #f39c12; background: linear-gradient(135deg, #fff9e6, #ffffff);"488 489 html += f"""490 <div style='margin-bottom: 8px; padding: 12px; background-color: #f8f9fa; border-radius: 6px; border-left: 4px solid {score_color}; {highlight_style}'>491 <div style='display: flex; justify-content: space-between; align-items: center;'>492 <div style='flex-grow: 1;'>493 <strong style='color: #2c3e50; font-size: 1em;'>494 {result['code']} - {result['title']}495 {' ⭐' if is_high_score else ''}496 </strong>497 </div>498 <span style='background-color: {score_color}; color: white; padding: 3px 8px; border-radius: 4px; font-size: 0.85em; font-weight: bold;'>499 {result['score']:.3f}500 </span>501 </div>502 {f"<div style='font-size: 0.9em; color: #666; margin-top: 8px; line-height: 1.4;'>{result['description'][:250]}{'...' if len(result.get('description', '')) > 250 else ''}</div>" if result.get('description') else ""}503 </div>504 """505 506 html += "</div></div>"507 508 html += "</div></div>"509 510 # Enhanced legend with chapter info511 html += """512 <div style='background: var(--background-fill-secondary, #f8f9fa); border: 1px solid var(--border-color-primary, #e9ecef); border-radius: 8px; padding: 15px; margin-top: 20px;'>513 <h4 style='color: var(--body-text-color, #2c3e50); margin-bottom: 15px;'>Enhanced Legend</h4>514 515 <div style='margin-bottom: 15px;'>516 <h5 style='color: var(--body-text-color, #2c3e50); margin-bottom: 8px;'>Score Quality:</h5>517 <div style='display: flex; flex-wrap: wrap; gap: 15px; align-items: center;'>518 <div style='display: flex; align-items: center;'>519 <div style='width: 20px; height: 20px; background-color: #27ae60; border-radius: 3px; margin-right: 8px;'></div>520 <span style='font-size: 0.9em; color: var(--body-text-color, #333);'>Excellent Match (≥0.8)</span>521 </div>522 <div style='display: flex; align-items: center;'>523 <div style='width: 20px; height: 20px; background-color: #f39c12; border-radius: 3px; margin-right: 8px;'></div>524 <span style='font-size: 0.9em; color: var(--body-text-color, #333);'>Good Match (≥0.6)</span>525 </div>526 <div style='display: flex; align-items: center;'>527 <div style='width: 20px; height: 20px; background-color: #e67e22; border-radius: 3px; margin-right: 8px;'></div>528 <span style='font-size: 0.9em; color: var(--body-text-color, #333);'>Fair Match (≥0.4)</span>529 </div>530 <div style='display: flex; align-items: center;'>531 <div style='width: 20px; height: 20px; background-color: #e74c3c; border-radius: 3px; margin-right: 8px;'></div>532 <span style='font-size: 0.9em; color: var(--body-text-color, #333);'>Low Match (<0.4)</span>533 </div>534 </div>535 </div>536 537 <div>538 <h5 style='color: var(--body-text-color, #2c3e50); margin-bottom: 8px;'>Features:</h5>539 <div style='display: flex; flex-wrap: wrap; gap: 20px; align-items: center; font-size: 0.9em; color: var(--body-text-color, #666);'>540 <span>⭐ High-scoring codes (≥0.6)</span>541 <span>📂 Category grouping by ICD-10 structure</span>542 <span>📚 Chapter context and descriptions</span>543 <span>📊 Score-based category prioritization</span>544 <span>🔄 Duplicate category codes filtered</span>545 </div>546 </div>547 </div>548 """549 550 return html551 552 def _get_score_color(self, score: float) -> str:553 """Get color based on similarity score"""554 if score >= 0.8:555 return "#27ae60" # Green556 elif score >= 0.6:557 return "#f39c12" # Orange558 elif score >= 0.4:559 return "#e67e22" # Dark orange560 else:561 return "#e74c3c" # Red562 563 def _get_category_color(self, max_score: float) -> str:564 """Get category header color based on highest score in category"""565 if max_score >= 0.8:566 return "#2ecc71" # Bright green567 elif max_score >= 0.6:568 return "#3498db" # Blue569 elif max_score >= 0.4:570 return "#9b59b6" # Purple571 else:572 return "#95a5a6" # Gray573 574def start_fastapi_server():575 """Start FastAPI server with enhanced error handling for Spaces"""576 try:577 logger.info("Starting FastAPI server...")578 # Use environment variable for port if available579 port = int(os.environ.get("FASTAPI_PORT", "8000"))580 581 # Enhanced server configuration for Spaces582 uvicorn.run(583 fastapi_app, 584 host="127.0.0.1", 585 port=port, 586 log_level="info",587 access_log=False, # Reduce log noise588 workers=1, # Single worker for Spaces589 timeout_keep_alive=30590 )591 except Exception as e:592 logger.error(f"FastAPI server failed to start: {e}")593 # Don't raise - let Gradio continue with error messages594 595def create_gradio_interface():596 """Create the Gradio interface with server status monitoring"""597 search_interface = ICD10SearchInterface()598 599 search_interface.wait_for_server(max_wait_time=60)600 601 css = """602 .gradio-container {603 max-width: 1400px !important;604 margin: auto !important;605 }606 607 .server-status {608 transition: all 0.3s ease;609 }610 """611 612 with gr.Blocks(css=css, title="ICD-10 Smart Search", theme=gr.themes.Soft()) as demo:613 gr.HTML("""614 <div style='text-align: center; margin-bottom: 30px; padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);'>615 <h1 style='color: white; margin: 0; font-size: 2.5em;'>ICD-10 Smart Search</h1>616 <p style='color: #f1f2f6; margin: 15px 0 0 0; font-size: 1.2em;'>Advanced diagnostic code search with AI-powered sentence analysis</p>617 </div>618 """)619 620 # Server status indicator621 def get_server_status():622 is_ready, msg = search_interface.get_server_status()623 if is_ready:624 return "<div class='server-status' style='text-align: center; padding: 10px; background: #00b894; color: white; border-radius: 5px; margin-bottom: 20px;'>🟢 Server Ready</div>"625 else:626 return f"<div class='server-status' style='text-align: center; padding: 10px; background: #e17055; color: white; border-radius: 5px; margin-bottom: 20px;'>🔴 {msg}</div>"627 628 server_status = gr.HTML(value=get_server_status())629 630 with gr.Row():631 with gr.Column(scale=1):632 gr.HTML("<h3>Search Parameters</h3>")633 634 query_input = gr.Textbox(635 label="Diagnostic Query",636 placeholder="Enter diagnostic description (e.g., 'chest pain with shortness of breath')",637 lines=3,638 value=""639 )640 641 with gr.Accordion("Advanced Options", open=False):642 with gr.Row():643 limit_input = gr.Slider(644 label="Maximum Results per Sentence",645 minimum=5,646 maximum=50,647 value=15,648 step=5,649 info="Higher values show more codes per category"650 )651 652 score_threshold_input = gr.Slider(653 label="Score Threshold",654 minimum=0.1,655 maximum=0.9,656 value=0.2,657 step=0.05,658 info="Lower values include more potential matches"659 )660 661 search_mode_input = gr.Dropdown(662 label="Search Mode",663 choices=["smart", "all_chapters", "specific_chapters"],664 value="smart"665 )666 667 target_chapters_input = gr.Textbox(668 label="Target Chapters (comma-separated)",669 placeholder="e.g., chapter_9_IX, chapter_10_X",670 visible=False671 )672 673 with gr.Row():674 detailed_analysis_input = gr.Checkbox(675 label="Include Detailed Analysis",676 value=True677 )678 679 chapters_per_sentence_input = gr.Slider(680 label="Chapters per Sentence",681 minimum=1,682 maximum=5,683 value=3,684 step=1685 )686 687 search_button = gr.Button("Search ICD-10 Codes", variant="primary", size="lg")688 689 def update_target_chapters_visibility(search_mode):690 return gr.update(visible=(search_mode == "specific_chapters"))691 692 search_mode_input.change(693 update_target_chapters_visibility,694 inputs=search_mode_input,695 outputs=target_chapters_input696 )697 698 with gr.Column(scale=2):699 gr.HTML("<h3>Enhanced Category-Grouped Results</h3>")700 sentence_results_output = gr.HTML(701 value="<div style='text-align: center; color: #666; padding: 40px;'>Enter a diagnostic query and click search to see categorized results with chapter context.</div>"702 )703 704 # Example queries705 gr.HTML("<h3>Example Queries</h3>")706 707 example_queries = [708 "acute myocardial infarction with chest pain",709 "type 2 diabetes with diabetic nephropathy",710 "major depressive disorder with anxiety", 711 "fracture of distal radius from fall",712 "acute appendicitis with peritonitis",713 "gestational diabetes in pregnancy",714 "chronic kidney disease stage 3",715 "essential hypertension with heart disease"716 ]717 718 with gr.Row():719 for i in range(0, len(example_queries), 2):720 with gr.Column():721 for j in range(2):722 if i + j < len(example_queries):723 example_btn = gr.Button(724 example_queries[i + j],725 variant="secondary",726 size="sm"727 )728 example_btn.click(729 lambda x=example_queries[i + j]: x,730 outputs=query_input731 )732 733 # Search functionality734 search_button.click(735 fn=search_interface.search_icd10,736 inputs=[737 query_input,738 limit_input,739 score_threshold_input,740 search_mode_input,741 target_chapters_input,742 detailed_analysis_input,743 chapters_per_sentence_input744 ],745 outputs=sentence_results_output746 )747 748 # Enhanced footer749 gr.HTML("""750 <div style='text-align: center; margin-top: 30px; padding: 20px; background-color: #f8f9fa; border-radius: 12px; border: 1px solid #e9ecef;'>751 <p style='margin: 0; color: #666; line-height: 1.6;'>752 Powered by advanced semantic search and AI-driven sentence analysis<br>753 <strong>Features:</strong> Chapter context • Category descriptions • Score-based prioritization<br>754 <strong>Note:</strong> This tool is for research purposes only and should not replace professional medical diagnosis755 </p>756 </div>757 """)758 759 # Auto-refresh server status every 10 seconds760 demo.load(get_server_status, outputs=server_status)761 762 return demo763 764# Global variable to track server thread765server_thread = None766 767def graceful_shutdown():768 """Handle graceful shutdown"""769 logger.info("Shutting down application...")770 # Add any cleanup code here if needed771 772# Signal handlers for graceful shutdown773signal.signal(signal.SIGTERM, lambda signum, frame: graceful_shutdown())774signal.signal(signal.SIGINT, lambda signum, frame: graceful_shutdown())775 776# Main application entry point for Hugging Face Spaces777if __name__ == "__main__":778 logger.info("Starting ICD-10 Search Application for Hugging Face Spaces...")779 780 try:781 # Start FastAPI server in background thread782 logger.info("Initializing FastAPI server thread...")783 server_thread = threading.Thread(target=start_fastapi_server, daemon=True)784 server_thread.start()785 logger.info("FastAPI server thread started")786 787 # Give server time to start (increased for Spaces)788 logger.info("Waiting for FastAPI server initialization...")789 time.sleep(8) # Increased wait time for Spaces790 791 # Create and launch Gradio interface792 logger.info("Creating Gradio interface...")793 demo = create_gradio_interface()794 795 # Launch for Spaces environment796 logger.info("Launching Gradio interface for Hugging Face Spaces...")797 demo.launch(798 share=False, # Don't create public link799 show_error=True, # Show errors for debugging800 # show_tips=False, # Don't show Gradio tips801 quiet=False, # Show startup info802 server_name="0.0.0.0", # Listen on all interfaces for Spaces803 server_port=7860, # Default Gradio port for Spaces804 prevent_thread_lock=False,805 root_path=os.environ.get("GRADIO_ROOT_PATH", "") # Support for Spaces routing806 )807 808 except Exception as e:809 logger.error(f"Application failed to start: {e}")810 sys.exit(1)