Hanan-Alnakhal/Lab-test-decoder
0
1"""2Lab Report Decoder - Flask Application3Professional web interface for lab report analysis4Fixed for Hugging Face Spaces stateless environment5"""6 7from flask import Flask, render_template, request, jsonify8from werkzeug.utils import secure_filename9import os10import tempfile11import secrets12import json13import uuid14from pdf_extractor import LabReportExtractor, LabResult15from rag_engine import LabReportRAG16from dotenv import load_dotenv17 18load_dotenv()19 20app = Flask(__name__)21app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16))22app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size23app.config['UPLOAD_FOLDER'] = "/tmp"24 25# Initialize RAG system (singleton)26rag_system = None27 28# In-memory storage for sessions (better than Flask sessions in HF Spaces)29session_storage = {}30 31def get_rag_system():32 """Lazy load RAG system"""33 global rag_system34 if rag_system is None:35 print("๐ Initializing RAG system...")36 rag_system = LabReportRAG()37 print("โ
RAG system ready")38 return rag_system39 40@app.route('/')41def index():42 """Main page"""43 return render_template('index.html')44 45@app.route('/api/upload', methods=['POST'])46def upload_file():47 """Handle PDF upload and extraction"""48 try:49 if 'file' not in request.files:50 return jsonify({'error': 'No file provided'}), 40051 52 file = request.files['file']53 54 if file.filename == '':55 return jsonify({'error': 'No file selected'}), 40056 57 if not file.filename.lower().endswith('.pdf'):58 return jsonify({'error': 'Only PDF files are allowed'}), 40059 60 # Save file temporarily61 filename = secure_filename(file.filename)62 filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)63 file.save(filepath)64 65 try:66 # Extract lab results67 print("๐ Extracting lab results from PDF...")68 extractor = LabReportExtractor()69 results = extractor.extract_from_pdf(filepath)70 71 if not results:72 return jsonify({'error': 'No lab results found in PDF. Please make sure your PDF contains a valid lab report with test names, values, and reference ranges.'}), 40073 74 print(f"โ
Extracted {len(results)} results")75 76 # Convert to JSON-serializable format77 results_data = [78 {79 'test_name': r.test_name,80 'value': r.value,81 'unit': r.unit,82 'reference_range': r.reference_range,83 'status': r.status84 }85 for r in results86 ]87 88 # Generate a unique session ID89 session_id = str(uuid.uuid4())90 91 # Store results in memory with session ID92 session_storage[session_id] = {93 'results': results_data,94 'results_objects': results # Store LabResult objects for later use95 }96 97 print(f"๐พ Stored results in session: {session_id}")98 99 return jsonify({100 'success': True,101 'session_id': session_id,102 'results': results_data,103 'count': len(results_data)104 })105 106 finally:107 # Clean up temp file108 if os.path.exists(filepath):109 os.remove(filepath)110 111 except Exception as e:112 print(f"โ Upload error: {str(e)}")113 return jsonify({'error': str(e)}), 500114 115@app.route('/api/explain', methods=['POST'])116def explain_results():117 """Generate explanations for lab results"""118 try:119 data = request.get_json()120 session_id = data.get('session_id')121 122 if not session_id or session_id not in session_storage:123 return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400124 125 # Get results from storage126 session_data = session_storage[session_id]127 results = session_data['results_objects']128 129 print(f"๐ง Generating explanations for {len(results)} results...")130 131 # Initialize RAG system132 rag = get_rag_system()133 134 # Generate explanations with timeout protection135 from concurrent.futures import TimeoutError136 import signal137 138 try:139 explanations = rag.explain_all_results(results)140 except Exception as e:141 print(f"โ ๏ธ Explanation generation error: {str(e)}")142 # Provide basic explanations as fallback143 explanations = {}144 for result in results:145 status_msg = {146 'normal': f"โ
Your {result.test_name} is within normal range.",147 'high': f"โ ๏ธ Your {result.test_name} is above normal range. Consult your doctor.",148 'low': f"โ ๏ธ Your {result.test_name} is below normal range. Consult your doctor.",149 'unknown': f"Your {result.test_name} result is {result.value} {result.unit}."150 }151 explanations[result.test_name] = status_msg.get(result.status, "Result recorded.")152 153 # Store explanations in session154 session_storage[session_id]['explanations'] = explanations155 156 print(f"โ
Generated {len(explanations)} explanations")157 158 return jsonify({159 'success': True,160 'explanations': explanations161 })162 163 except Exception as e:164 print(f"โ Explanation error: {str(e)}")165 import traceback166 traceback.print_exc()167 return jsonify({'error': 'Error generating explanations. Please try uploading again.'}), 500168 169@app.route('/api/ask', methods=['POST'])170def ask_question():171 """Answer follow-up questions"""172 try:173 data = request.get_json()174 question = data.get('question', '').strip()175 session_id = data.get('session_id')176 177 if not question:178 return jsonify({'error': 'No question provided'}), 400179 180 if not session_id or session_id not in session_storage:181 return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400182 183 # Get results from storage184 session_data = session_storage[session_id]185 results = session_data['results_objects']186 187 print(f"๐ฌ Answering question: {question}")188 189 # Get answer190 rag = get_rag_system()191 answer = rag.answer_followup_question(question, results)192 193 print("โ
Answer generated")194 195 return jsonify({196 'success': True,197 'question': question,198 'answer': answer199 })200 201 except Exception as e:202 print(f"โ Question error: {str(e)}")203 return jsonify({'error': str(e)}), 500204 205@app.route('/api/summary', methods=['POST'])206def get_summary():207 """Generate overall summary"""208 try:209 data = request.get_json()210 session_id = data.get('session_id')211 212 if not session_id or session_id not in session_storage:213 return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400214 215 # Get results from storage216 session_data = session_storage[session_id]217 results = session_data['results_objects']218 219 print("๐ Generating summary...")220 221 # Generate summary222 rag = get_rag_system()223 summary = rag.generate_summary(results)224 225 # Calculate statistics226 stats = {227 'total': len(results),228 'normal': sum(1 for r in results if r.status == 'normal'),229 'high': sum(1 for r in results if r.status == 'high'),230 'low': sum(1 for r in results if r.status == 'low'),231 'unknown': sum(1 for r in results if r.status == 'unknown')232 }233 234 print(f"โ
Summary generated - Stats: {stats}")235 236 return jsonify({237 'success': True,238 'summary': summary,239 'stats': stats240 })241 242 except Exception as e:243 print(f"โ Summary error: {str(e)}")244 return jsonify({'error': str(e)}), 500245 246@app.route('/api/health', methods=['GET'])247def health_check():248 """Health check endpoint"""249 return jsonify({250 'status': 'healthy',251 'active_sessions': len(session_storage)252 })253 254@app.errorhandler(413)255def request_entity_too_large(error):256 return jsonify({'error': 'File too large. Maximum size is 16MB.'}), 413257 258@app.errorhandler(500)259def internal_error(error):260 return jsonify({'error': 'Internal server error'}), 500261 262if __name__ == '__main__':263 print("๐ Starting Lab Report Decoder...")264 print("๐ Server will be available at http://0.0.0.0:7860")265 app.run(debug=True, host='0.0.0.0', port=7860)