HebaElshimy's picture
Upload 2 files
1621c4e verified
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()