Team Ai
Apppublic

Axcel1/icd_10_coding_assistant

sourceHugging Facemitupdated 1y agoView on Hugging Face
1likes
app.py810 linesDownload Raw Back to root
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)