import gradio as gr import pandas as pd import requests import json from transformers import pipeline, AutoTokenizer, AutoModel import torch from sentence_transformers import SentenceTransformer, CrossEncoder import time from typing import List, Dict, Tuple import re import numpy as np # ============================================================================ # ADVANCED NLP MODELS INITIALIZATION # ============================================================================ print("Loading advanced models...") # Initialize advanced models try: # Cross-encoder for accurate semantic similarity cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-12-v2', max_length=512) # Zero-shot classifier for criteria matching classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") # Medical sentence transformer sentence_model = SentenceTransformer('pritamdeka/BioBERT-mnli-snli-scinli-scitail-mednli-stsb') # PubMedBERT for medical text understanding pubmed_tokenizer = AutoTokenizer.from_pretrained("microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract") pubmed_model = AutoModel.from_pretrained("microsoft/BiomedNLP-PubMedBERT-base-uncased-abstract") print("Advanced models loaded successfully!") USE_ADVANCED_MODELS = True except Exception as e: print(f"Warning: Could not load advanced models, falling back to basic models. Error: {e}") # Fallback to basic models classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli") similarity_model = pipeline("feature-extraction", model="sentence-transformers/all-MiniLM-L6-v2") USE_ADVANCED_MODELS = False print("Basic models loaded successfully!") # Medical terminology expansions MEDICAL_SYNONYMS = { 'rct': ['randomized controlled trial', 'randomised controlled trial', 'randomized clinical trial'], 'pain': ['pain', 'nociception', 'analgesia', 'hyperalgesia', 'allodynia', 'neuropathic pain', 'chronic pain', 'acute pain', 'postoperative pain', 'pain management'], 'surgery': ['surgery', 'surgical', 'operation', 'operative', 'postoperative', 'perioperative', 'preoperative', 'surgical procedure', 'surgical intervention'], 'study design': ['study design', 'trial design', 'research design', 'methodology', 'randomized', 'controlled', 'cohort', 'case-control', 'cross-sectional'], } # ============================================================================ # ADVANCED NLP FUNCTIONS # ============================================================================ def expand_medical_terms(term: str) -> List[str]: """Expand medical terms with synonyms""" term_lower = term.lower() expanded = [term] for key, synonyms in MEDICAL_SYNONYMS.items(): if key in term_lower or any(syn in term_lower for syn in synonyms): expanded.extend(synonyms[:3]) # Limit expansion return list(set(expanded)) def cross_encoder_score(text: str, criteria: str) -> float: """Calculate cross-encoder similarity score""" if not USE_ADVANCED_MODELS: return 0.5 # Default score if not available try: score = cross_encoder.predict([[text, criteria]]) return float(1 / (1 + np.exp(-score[0]))) except: return 0.5 def get_pubmed_embedding(text: str) -> np.ndarray: """Get PubMedBERT embedding for medical text""" if not USE_ADVANCED_MODELS: return np.zeros(768) try: inputs = pubmed_tokenizer(text, return_tensors="pt", truncation=True, max_length=512, padding=True) with torch.no_grad(): outputs = pubmed_model(**inputs) embedding = outputs.last_hidden_state[:, 0, :].numpy() return embedding.squeeze() except: return np.zeros(768) def zero_shot_classify(text: str, labels: List[str], hypothesis_template: str = "This study is about {}") -> Dict: """Perform zero-shot classification""" if not labels: return {} try: result = classifier(text, candidate_labels=labels[:10], hypothesis_template=hypothesis_template, multi_label=True) scores = {} for label, score in zip(result['labels'], result['scores']): scores[label] = score return scores except: return {} # ============================================================================ # ENHANCED CRITERIA PARSING # ============================================================================ def parse_criteria(criteria_text: str, stage: str = "stage1") -> Dict: """Parse criteria with medical term expansion""" criteria = { 'population': [], 'intervention': [], 'comparator': [], 'outcomes': [], 'study_design': [], 'include_general': [], 'exclude_general': [] } lines = criteria_text.lower().split('\n') current_section = None for line in lines: line = line.strip() if not line: continue # Detect section headers if any(keyword in line for keyword in ['population:', 'participants:', 'subjects:']): current_section = 'population' elif any(keyword in line for keyword in ['intervention:', 'exposure:', 'treatment:']): current_section = 'intervention' elif any(keyword in line for keyword in ['comparator:', 'control:', 'comparison:']): current_section = 'comparator' elif any(keyword in line for keyword in ['outcomes:', 'endpoint:', 'results:']): current_section = 'outcomes' elif any(keyword in line for keyword in ['study design:', 'design:', 'study type:']): current_section = 'study_design' elif 'include' in line and ':' in line: current_section = 'include_general' elif 'exclude' in line and ':' in line: current_section = 'exclude_general' elif line.startswith('-') and current_section: term = line[1:].strip() if term and len(term) > 2: # Expand medical terms if advanced models are available if USE_ADVANCED_MODELS: expanded = expand_medical_terms(term) criteria[current_section].extend(expanded) else: criteria[current_section].append(term) elif current_section and not any(keyword in line for keyword in ['include', 'exclude', 'population', 'intervention', 'comparator', 'outcomes', 'study']): terms = [t.strip() for t in line.split(',') if t.strip() and len(t.strip()) > 2] if USE_ADVANCED_MODELS: for term in terms: expanded = expand_medical_terms(term) criteria[current_section].extend(expanded) else: criteria[current_section].extend(terms) # Remove duplicates for key in criteria: criteria[key] = list(set(criteria[key])) return criteria # ============================================================================ # ENHANCED STAGE 1 CLASSIFICATION # ============================================================================ def semantic_similarity_score(study_text: str, criteria_terms: List[str]) -> Tuple[float, str]: """Calculate semantic similarity with advanced models if available""" if not criteria_terms: return 0.0, "" best_score, best_match = 0.0, "" if USE_ADVANCED_MODELS: # Use cross-encoder for more accurate matching for term in criteria_terms[:5]: # Limit to avoid slowdown score = cross_encoder_score(study_text, term) if score > best_score: best_score, best_match = score, term else: # Fallback to basic embedding similarity study_embedding = get_text_embedding(study_text) for term in criteria_terms: term_embedding = get_text_embedding(term) similarity = cosine_similarity(study_embedding, term_embedding) if similarity > best_score: best_score, best_match = similarity, term return best_score, best_match def cosine_similarity(a, b): """Simple cosine similarity calculation""" dot_product = np.dot(a, b) norm_a = np.linalg.norm(a) norm_b = np.linalg.norm(b) return dot_product / (norm_a * norm_b) if norm_a > 0 and norm_b > 0 else 0 def get_text_embedding(text): """Get text embedding using the similarity model""" if USE_ADVANCED_MODELS: try: embedding = sentence_model.encode(text) return embedding except: return np.zeros(384) else: try: if 'similarity_model' in globals(): embeddings = similarity_model(text) return np.mean(embeddings[0], axis=0) else: return np.zeros(384) except: return np.zeros(384) def stage1_classification(title: str, abstract: str, criteria_text: str) -> Dict: """Enhanced Stage 1 classification with advanced NLP when available""" study_text = f"{title} {abstract}".lower() if len(study_text.strip()) < 20: return {'decision': 'UNCLEAR', 'confidence': 20, 'reasoning': 'Insufficient text', 'stage': 1} criteria = parse_criteria(criteria_text, "stage1") # Use zero-shot classification if available with advanced models if USE_ADVANCED_MODELS and criteria['include_general']: zs_scores = zero_shot_classify( study_text, criteria['include_general'][:5], "This study is relevant to {}" ) if zs_scores: max_zs_score = max(zs_scores.values()) if max_zs_score > 0.7: return { 'decision': 'INCLUDE', 'confidence': min(int(max_zs_score * 100), 85), 'reasoning': f"Stage 1 INCLUDE: High relevance to inclusion criteria ({max_zs_score:.2f})", 'stage': 1 } # Calculate PICOS scores with appropriate thresholds pop_score, pop_match = semantic_similarity_score(study_text, criteria['population']) int_score, int_match = semantic_similarity_score(study_text, criteria['intervention']) out_score, out_match = semantic_similarity_score(study_text, criteria['outcomes']) design_score, design_match = semantic_similarity_score(study_text, criteria['study_design']) inc_score, inc_match = semantic_similarity_score(study_text, criteria['include_general']) exc_score, exc_match = semantic_similarity_score(study_text, criteria['exclude_general']) # Adjust thresholds based on model availability threshold = 0.4 if USE_ADVANCED_MODELS else 0.25 reasoning_parts = [] if pop_score > threshold: reasoning_parts.append(f"Population: '{pop_match}' ({pop_score:.2f})") if int_score > threshold: reasoning_parts.append(f"Intervention: '{int_match}' ({int_score:.2f})") if out_score > threshold: reasoning_parts.append(f"Outcome: '{out_match}' ({out_score:.2f})") if design_score > threshold: reasoning_parts.append(f"Design: '{design_match}' ({design_score:.2f})") if inc_score > threshold: reasoning_parts.append(f"Include: '{inc_match}' ({inc_score:.2f})") if exc_score > threshold: reasoning_parts.append(f"Exclude: '{exc_match}' ({exc_score:.2f})") # Decision Logic exc_threshold = 0.5 if USE_ADVANCED_MODELS else 0.35 if exc_score > exc_threshold: decision, confidence = 'EXCLUDE', min(int(exc_score * 100), 90) reasoning = f"Stage 1 EXCLUDE: {'; '.join(reasoning_parts)}" elif sum([pop_score > threshold, int_score > threshold, out_score > threshold]) >= 2 and USE_ADVANCED_MODELS: avg_score = np.mean([s for s in [pop_score, int_score, out_score, design_score, inc_score] if s > threshold]) decision, confidence = 'INCLUDE', min(int(avg_score * 85), 85) reasoning = f"Stage 1 INCLUDE (Advanced): {'; '.join(reasoning_parts)}" elif sum([pop_score > 0.25, int_score > 0.25, out_score > 0.25]) >= 1: avg_score = np.mean([s for s in [pop_score, int_score, out_score, design_score, inc_score] if s > 0.25]) decision, confidence = 'INCLUDE', min(int(avg_score * 75), 80) reasoning = f"Stage 1 INCLUDE: {'; '.join(reasoning_parts)}" else: decision, confidence = 'UNCLEAR', 40 reasoning = f"Stage 1 UNCLEAR: {'; '.join(reasoning_parts) if reasoning_parts else 'No clear matches'}" return {'decision': decision, 'confidence': confidence, 'reasoning': reasoning, 'stage': 1} # ============================================================================ # STAGE 2 CLASSIFICATION (keeping original) # ============================================================================ def stage2_classification(title: str, abstract: str, full_text: str, criteria_text: str, data_extraction_fields: Dict = None) -> Dict: """Stage 2: Detailed full-text screening with data extraction""" # Combine all available text study_text = f"{title} {abstract} {full_text}".lower() if len(study_text.strip()) < 50: return {'decision': 'UNCLEAR', 'confidence': 25, 'reasoning': 'Insufficient full text', 'stage': 2} criteria = parse_criteria(criteria_text, "stage2") # More stringent scoring for Stage 2 pop_score, pop_match = semantic_similarity_score(study_text, criteria['population']) int_score, int_match = semantic_similarity_score(study_text, criteria['intervention']) comp_score, comp_match = semantic_similarity_score(study_text, criteria['comparator']) out_score, out_match = semantic_similarity_score(study_text, criteria['outcomes']) design_score, design_match = semantic_similarity_score(study_text, criteria['study_design']) exc_score, exc_match = semantic_similarity_score(study_text, criteria['exclude_general']) # Data extraction scoring extraction_scores = {} if data_extraction_fields: for field, terms in data_extraction_fields.items(): if terms: field_score, field_match = semantic_similarity_score(study_text, terms) extraction_scores[field] = {'score': field_score, 'match': field_match} reasoning_parts = [] if pop_score > 0.3: reasoning_parts.append(f"Population: '{pop_match}' ({pop_score:.2f})") if int_score > 0.3: reasoning_parts.append(f"Intervention: '{int_match}' ({int_score:.2f})") if comp_score > 0.3: reasoning_parts.append(f"Comparator: '{comp_match}' ({comp_score:.2f})") if out_score > 0.3: reasoning_parts.append(f"Outcome: '{out_match}' ({out_score:.2f})") if design_score > 0.3: reasoning_parts.append(f"Design: '{design_match}' ({design_score:.2f})") if exc_score > 0.3: reasoning_parts.append(f"Exclusion: '{exc_match}' ({exc_score:.2f})") # Stage 2 Decision Logic (High Specificity) if exc_score > 0.4: decision, confidence = 'EXCLUDE', min(int(exc_score * 100), 95) reasoning = f"Stage 2 EXCLUDE: {'; '.join(reasoning_parts)}" elif sum([pop_score > 0.4, int_score > 0.4, out_score > 0.4, design_score > 0.4]) >= 3: avg_score = np.mean([pop_score, int_score, comp_score, out_score, design_score]) decision, confidence = 'INCLUDE', min(int(avg_score * 85), 92) reasoning = f"Stage 2 INCLUDE: {'; '.join(reasoning_parts)}" elif max(pop_score, int_score, out_score) > 0.5: decision, confidence = 'INCLUDE', min(int(max(pop_score, int_score, out_score) * 80), 88) reasoning = f"Stage 2 INCLUDE: {'; '.join(reasoning_parts)}" else: decision, confidence = 'EXCLUDE', 60 reasoning = f"Stage 2 EXCLUDE: Insufficient criteria match. {'; '.join(reasoning_parts)}" result = { 'decision': decision, 'confidence': confidence, 'reasoning': reasoning, 'stage': 2, 'extraction_data': extraction_scores } return result # ============================================================================ # PROCESSING FUNCTIONS (keeping original structure) # ============================================================================ def process_stage1(file, title_col, abstract_col, criteria, sample_size): """Process Stage 1 screening with enhanced NLP""" try: df = pd.read_csv(file.name) if sample_size < len(df): df = df.head(sample_size) results = [] for idx, row in df.iterrows(): title = str(row[title_col]) if pd.notna(row[title_col]) else "" abstract = str(row[abstract_col]) if pd.notna(row[abstract_col]) else "" if not title and not abstract: continue classification = stage1_classification(title, abstract, criteria) result = { 'Study_ID': idx + 1, 'Title': title[:100] + "..." if len(title) > 100 else title, 'Stage1_Decision': classification['decision'], 'Stage1_Confidence': f"{classification['confidence']}%", 'Stage1_Reasoning': classification['reasoning'], 'Ready_for_Stage2': 'Yes' if classification['decision'] == 'INCLUDE' else 'No', 'Full_Title': title, 'Full_Abstract': abstract } results.append(result) results_df = pd.DataFrame(results) # Summary for Stage 1 total = len(results_df) included = len(results_df[results_df['Stage1_Decision'] == 'INCLUDE']) excluded = len(results_df[results_df['Stage1_Decision'] == 'EXCLUDE']) unclear = len(results_df[results_df['Stage1_Decision'] == 'UNCLEAR']) model_info = "**Using Advanced Medical NLP Models**" if USE_ADVANCED_MODELS else "**Using Basic NLP Models**" summary = f""" ## 📊 Stage 1 (Title/Abstract) Results {model_info} **Screening Complete:** - **Total Studies:** {total} - **Include for Stage 2:** {included} ({included/total*100:.1f}%) - **Exclude:** {excluded} ({excluded/total*100:.1f}%) - **Needs Manual Review:** {unclear} ({unclear/total*100:.1f}%) **Next Steps:** 1. Review {unclear} studies marked as UNCLEAR 2. Proceed to Stage 2 with {included} included studies 3. Obtain full texts for Stage 2 screening """ return summary, results_df, results_df.to_csv(index=False) except Exception as e: return f"Error: {str(e)}", None, "" def process_stage2(file, title_col, abstract_col, fulltext_col, criteria, extraction_fields, sample_size): """Process Stage 2 screening with data extraction""" try: df = pd.read_csv(file.name) # Filter to only Stage 1 included studies if column exists if 'Stage1_Decision' in df.columns: df = df[df['Stage1_Decision'] == 'INCLUDE'] if sample_size < len(df): df = df.head(sample_size) # Parse extraction fields extraction_dict = {} if extraction_fields: for line in extraction_fields.split('\n'): if ':' in line: field, terms = line.split(':', 1) extraction_dict[field.strip()] = [t.strip() for t in terms.split(',') if t.strip()] results = [] for idx, row in df.iterrows(): title = str(row[title_col]) if pd.notna(row[title_col]) else "" abstract = str(row[abstract_col]) if pd.notna(row[abstract_col]) else "" full_text = str(row[fulltext_col]) if fulltext_col and fulltext_col in df.columns and pd.notna(row[fulltext_col]) else "" if not title and not abstract: continue classification = stage2_classification(title, abstract, full_text, criteria, extraction_dict) result = { 'Study_ID': idx + 1, 'Title': title[:100] + "..." if len(title) > 100 else title, 'Stage2_Decision': classification['decision'], 'Stage2_Confidence': f"{classification['confidence']}%", 'Stage2_Reasoning': classification['reasoning'], 'Final_Include': 'Yes' if classification['decision'] == 'INCLUDE' else 'No', 'Extraction_Data': str(classification.get('extraction_data', {})), 'Full_Title': title, 'Full_Abstract': abstract, 'Full_Text': full_text } results.append(result) results_df = pd.DataFrame(results) # Summary for Stage 2 total = len(results_df) final_included = len(results_df[results_df['Stage2_Decision'] == 'INCLUDE']) final_excluded = len(results_df[results_df['Stage2_Decision'] == 'EXCLUDE']) summary = f""" ## 📊 Stage 2 (Full-Text) Results **Detailed Screening Complete:** - **Studies Reviewed:** {total} - **Final INCLUDE:** {final_included} ({final_included/total*100:.1f}%) - **Final EXCLUDE:** {final_excluded} ({final_excluded/total*100:.1f}%) **Ready for Next Steps:** - **Data Extraction:** {final_included} studies - **Quality Assessment:** {final_included} studies - **Evidence Synthesis:** Ready to proceed **Recommended Actions:** 1. Export {final_included} included studies for detailed data extraction 2. Conduct quality assessment (ROB2, ROBINS-I, etc.) 3. Begin evidence synthesis and meta-analysis planning """ return summary, results_df, results_df.to_csv(index=False) except Exception as e: return f"Error: {str(e)}", None, "" # ============================================================================ # ORIGINAL INTERFACE (PRESERVED) # ============================================================================ def create_interface(): with gr.Blocks(title="🔬 2-Stage Systematic Review AI Assistant", theme=gr.themes.Soft()) as interface: gr.Markdown(""" # 🔬 2-Stage Systematic Review AI Assistant **Complete workflow for evidence-based systematic reviews** This tool supports the full 2-stage systematic review process: - **Stage 1:** Title/Abstract screening (high sensitivity) - **Stage 2:** Full-text screening with data extraction (high specificity) """) with gr.Tabs(): # STAGE 1 TAB with gr.TabItem("📋 Stage 1: Title/Abstract Screening"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📁 Upload Study Data") stage1_file = gr.File( label="Upload Studies (CSV) - Search results from databases", file_types=[".csv"], type="filepath" ) with gr.Row(): stage1_title_col = gr.Dropdown(label="Title Column", choices=[], interactive=True) stage1_abstract_col = gr.Dropdown(label="Abstract Column", choices=[], interactive=True) stage1_sample = gr.Slider(label="Studies to Process", minimum=5, maximum=500, value=100, step=5) with gr.Column(scale=1): gr.Markdown("### 🎯 Stage 1 Criteria (Broad/Sensitive)") stage1_criteria = gr.Textbox( label="Inclusion/Exclusion Criteria for Stage 1", value="""POPULATION: - Adult participants - Human studies INTERVENTION: - [Your intervention/exposure of interest] OUTCOMES: - [Primary outcomes of interest] STUDY DESIGN: - Randomized controlled trials - Cohort studies - Case-control studies EXCLUDE: - Animal studies - Case reports - Reviews (unless relevant)""", lines=15 ) stage1_process_btn = gr.Button("🚀 Start Stage 1 Screening", variant="primary") stage1_results = gr.Markdown() stage1_table = gr.Dataframe(label="Stage 1 Results") stage1_download_data = gr.Textbox(visible=False) stage1_download_btn = gr.DownloadButton(label="💾 Download Stage 1 Results", visible=False) # STAGE 2 TAB with gr.TabItem("📄 Stage 2: Full-Text Screening"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📁 Upload Stage 1 Results or Full-Text Data") stage2_file = gr.File( label="Upload Stage 1 Results or Studies with Full Text", file_types=[".csv"], type="filepath" ) with gr.Row(): stage2_title_col = gr.Dropdown(label="Title Column", choices=[], interactive=True) stage2_abstract_col = gr.Dropdown(label="Abstract Column", choices=[], interactive=True) stage2_fulltext_col = gr.Dropdown(label="Full Text Column", choices=[], interactive=True) stage2_sample = gr.Slider(label="Studies to Process", minimum=5, maximum=200, value=50, step=5) with gr.Column(scale=1): gr.Markdown("### 🎯 Stage 2 Criteria (Strict/Specific)") stage2_criteria = gr.Textbox( label="Detailed Inclusion/Exclusion Criteria for Stage 2", value="""POPULATION: - [Specific population criteria] - [Age ranges, conditions, etc.] INTERVENTION: - [Detailed intervention specifications] - [Dosage, duration, delivery method] COMPARATOR: - [Control group specifications] - [Placebo, standard care, etc.] OUTCOMES: - [Primary endpoint definitions] - [Secondary outcomes] - [Measurement methods] STUDY DESIGN: - [Minimum study quality requirements] - [Follow-up duration requirements] EXCLUDE: - [Specific exclusion criteria] - [Study quality thresholds]""", lines=15 ) extraction_fields = gr.Textbox( label="Data Extraction Fields (Optional)", value="""Sample Size: participants, subjects, patients, n= Intervention Duration: weeks, months, days, duration Primary Outcome: endpoint, primary outcome, main outcome Statistical Method: analysis, statistical, regression, model Risk of Bias: randomization, blinding, allocation""", lines=8 ) stage2_process_btn = gr.Button("🔍 Start Stage 2 Screening", variant="primary") stage2_results = gr.Markdown() stage2_table = gr.Dataframe(label="Stage 2 Results with Data Extraction") stage2_download_data = gr.Textbox(visible=False) stage2_download_btn = gr.DownloadButton(label="💾 Download Final Results", visible=False) # WORKFLOW GUIDANCE TAB with gr.TabItem("📚 Systematic Review Workflow"): gr.Markdown(""" ## 🔄 Complete 2-Stage Systematic Review Process ### **Stage 1: Title/Abstract Screening** **Objective:** High sensitivity screening to identify potentially relevant studies **Process:** 1. Upload search results from multiple databases (PubMed, Embase, etc.) 2. Define broad inclusion/exclusion criteria 3. AI screens titles/abstracts with high sensitivity 4. Manually review "UNCLEAR" classifications 5. Export studies marked for inclusion to Stage 2 **Criteria Guidelines:** - Use broad terms to capture all potentially relevant studies - Focus on key PICOS elements (Population, Intervention, Outcomes) - Err on the side of inclusion when uncertain ### **Stage 2: Full-Text Screening** **Objective:** High specificity screening with detailed data extraction **Process:** 1. Upload Stage 1 results or add full-text content 2. Define strict, specific inclusion/exclusion criteria 3. AI performs detailed full-text analysis 4. Extract key data points for synthesis 5. Export final included studies for meta-analysis **Criteria Guidelines:** - Use specific, measurable criteria - Include detailed PICOS specifications - Define minimum quality thresholds - Specify exact outcome measurements needed ### **Quality Assurance Recommendations:** **For Stage 1:** - Manual review of 10-20% of AI decisions - Inter-rater reliability testing with subset - Calibration exercises among reviewers **For Stage 2:** - Manual validation of all AI INCLUDE decisions - Detailed reason documentation for exclusions - Data extraction verification by second reviewer ### **After 2-Stage Screening:** 1. **Data Extraction:** Extract detailed study characteristics 2. **Quality Assessment:** Apply ROB2, ROBINS-I, or other tools 3. **Evidence Synthesis:** Qualitative synthesis and meta-analysis 4. **GRADE Assessment:** Evaluate certainty of evidence 5. **Reporting:** Follow PRISMA guidelines ### **Best Practices:** - **Document everything:** Keep detailed logs of decisions and criteria - **Validate AI decisions:** Use AI as assistance, not replacement - **Follow guidelines:** Adhere to Cochrane and PRISMA standards - **Test criteria:** Pilot with known studies before full screening - **Multiple reviewers:** Have disagreements resolved by third reviewer ### **When to Use Each Stage:** **Use Stage 1 when:** - Starting with large search results (>1000 studies) - Need to quickly filter irrelevant studies - Working with title/abstract data only **Use Stage 2 when:** - Have full-text access to studies - Need detailed inclusion/exclusion assessment - Ready for data extraction - Preparing for meta-analysis ### **Advanced NLP Features:** This tool now includes advanced medical NLP models when available: - **PubMedBERT** for medical text understanding - **Cross-encoders** for accurate semantic matching - **Zero-shot classification** for flexible criteria - **Medical term expansion** for comprehensive matching The system automatically detects and uses advanced models when available, falling back to basic models if needed. """) # Event handlers for file uploads and column detection def update_stage1_columns(file): if file is None: return gr.Dropdown(choices=[]), gr.Dropdown(choices=[]) try: df = pd.read_csv(file.name) columns = df.columns.tolist() title_col = next((col for col in columns if 'title' in col.lower()), columns[0] if columns else None) abstract_col = next((col for col in columns if 'abstract' in col.lower()), columns[1] if len(columns) > 1 else None) return gr.Dropdown(choices=columns, value=title_col), gr.Dropdown(choices=columns, value=abstract_col) except: return gr.Dropdown(choices=[]), gr.Dropdown(choices=[]) def update_stage2_columns(file): if file is None: return gr.Dropdown(choices=[]), gr.Dropdown(choices=[]), gr.Dropdown(choices=[]) try: df = pd.read_csv(file.name) columns = df.columns.tolist() title_col = next((col for col in columns if 'title' in col.lower()), columns[0] if columns else None) abstract_col = next((col for col in columns if 'abstract' in col.lower()), columns[1] if len(columns) > 1 else None) fulltext_col = next((col for col in columns if any(term in col.lower() for term in ['full_text', 'fulltext', 'text', 'content'])), None) return (gr.Dropdown(choices=columns, value=title_col), gr.Dropdown(choices=columns, value=abstract_col), gr.Dropdown(choices=columns, value=fulltext_col)) except: return gr.Dropdown(choices=[]), gr.Dropdown(choices=[]), gr.Dropdown(choices=[]) # Event bindings stage1_file.change(fn=update_stage1_columns, inputs=[stage1_file], outputs=[stage1_title_col, stage1_abstract_col]) stage2_file.change(fn=update_stage2_columns, inputs=[stage2_file], outputs=[stage2_title_col, stage2_abstract_col, stage2_fulltext_col]) def process_stage1_with_download(*args): summary, table, csv_data = process_stage1(*args) return summary, table, csv_data, gr.DownloadButton(visible=bool(csv_data)) def process_stage2_with_download(*args): summary, table, csv_data = process_stage2(*args) return summary, table, csv_data, gr.DownloadButton(visible=bool(csv_data)) stage1_process_btn.click( fn=process_stage1_with_download, inputs=[stage1_file, stage1_title_col, stage1_abstract_col, stage1_criteria, stage1_sample], outputs=[stage1_results, stage1_table, stage1_download_data, stage1_download_btn] ) stage2_process_btn.click( fn=process_stage2_with_download, inputs=[stage2_file, stage2_title_col, stage2_abstract_col, stage2_fulltext_col, stage2_criteria, extraction_fields, stage2_sample], outputs=[stage2_results, stage2_table, stage2_download_data, stage2_download_btn] ) stage1_download_btn.click(lambda data: data, inputs=[stage1_download_data], outputs=[gr.File()]) stage2_download_btn.click(lambda data: data, inputs=[stage2_download_data], outputs=[gr.File()]) return interface if __name__ == "__main__": interface = create_interface() interface.launch()