Spaces:
Sleeping
Sleeping
| """ | |
| Lab Report Decoder - Flask Application | |
| Professional web interface for lab report analysis | |
| Fixed for Hugging Face Spaces stateless environment | |
| """ | |
| from flask import Flask, render_template, request, jsonify | |
| from werkzeug.utils import secure_filename | |
| import os | |
| import tempfile | |
| import secrets | |
| import json | |
| import uuid | |
| from pdf_extractor import LabReportExtractor, LabResult | |
| from rag_engine import LabReportRAG | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| app = Flask(__name__) | |
| app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16)) | |
| app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size | |
| app.config['UPLOAD_FOLDER'] = tempfile.gettempdir() | |
| # Initialize RAG system (singleton) | |
| rag_system = None | |
| # In-memory storage for sessions (better than Flask sessions in HF Spaces) | |
| session_storage = {} | |
| def get_rag_system(): | |
| """Lazy load RAG system""" | |
| global rag_system | |
| if rag_system is None: | |
| print("π Initializing RAG system...") | |
| rag_system = LabReportRAG() | |
| print("β RAG system ready") | |
| return rag_system | |
| def index(): | |
| """Main page""" | |
| return render_template('index.html') | |
| def upload_file(): | |
| """Handle PDF upload and extraction""" | |
| try: | |
| if 'file' not in request.files: | |
| return jsonify({'error': 'No file provided'}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({'error': 'No file selected'}), 400 | |
| if not file.filename.lower().endswith('.pdf'): | |
| return jsonify({'error': 'Only PDF files are allowed'}), 400 | |
| # Save file temporarily | |
| filename = secure_filename(file.filename) | |
| filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) | |
| file.save(filepath) | |
| try: | |
| # Extract lab results | |
| print("π Extracting lab results from PDF...") | |
| extractor = LabReportExtractor() | |
| results = extractor.extract_from_pdf(filepath) | |
| if not results: | |
| 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.'}), 400 | |
| print(f"β Extracted {len(results)} results") | |
| # Convert to JSON-serializable format | |
| results_data = [ | |
| { | |
| 'test_name': r.test_name, | |
| 'value': r.value, | |
| 'unit': r.unit, | |
| 'reference_range': r.reference_range, | |
| 'status': r.status | |
| } | |
| for r in results | |
| ] | |
| # Generate a unique session ID | |
| session_id = str(uuid.uuid4()) | |
| # Store results in memory with session ID | |
| session_storage[session_id] = { | |
| 'results': results_data, | |
| 'results_objects': results # Store LabResult objects for later use | |
| } | |
| print(f"πΎ Stored results in session: {session_id}") | |
| return jsonify({ | |
| 'success': True, | |
| 'session_id': session_id, | |
| 'results': results_data, | |
| 'count': len(results_data) | |
| }) | |
| finally: | |
| # Clean up temp file | |
| if os.path.exists(filepath): | |
| os.remove(filepath) | |
| except Exception as e: | |
| print(f"β Upload error: {str(e)}") | |
| return jsonify({'error': str(e)}), 500 | |
| def explain_results(): | |
| """Generate explanations for lab results""" | |
| try: | |
| data = request.get_json() | |
| session_id = data.get('session_id') | |
| if not session_id or session_id not in session_storage: | |
| return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400 | |
| # Get results from storage | |
| session_data = session_storage[session_id] | |
| results = session_data['results_objects'] | |
| print(f"π§ Generating explanations for {len(results)} results...") | |
| # Generate explanations | |
| rag = get_rag_system() | |
| explanations = {} | |
| for i, result in enumerate(results): | |
| print(f" Explaining {i+1}/{len(results)}: {result.test_name}...") | |
| try: | |
| explanation = rag.explain_result(result) | |
| explanations[result.test_name] = explanation | |
| except Exception as e: | |
| print(f" Error: {str(e)}") | |
| explanations[result.test_name] = f"Unable to generate explanation: {str(e)}" | |
| # Store explanations in session | |
| session_storage[session_id]['explanations'] = explanations | |
| print("β All explanations generated") | |
| return jsonify({ | |
| 'success': True, | |
| 'explanations': explanations | |
| }) | |
| except Exception as e: | |
| print(f"β Explanation error: {str(e)}") | |
| return jsonify({'error': str(e)}), 500 | |
| def ask_question(): | |
| """Answer follow-up questions""" | |
| try: | |
| data = request.get_json() | |
| question = data.get('question', '').strip() | |
| session_id = data.get('session_id') | |
| if not question: | |
| return jsonify({'error': 'No question provided'}), 400 | |
| if not session_id or session_id not in session_storage: | |
| return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400 | |
| # Get results from storage | |
| session_data = session_storage[session_id] | |
| results = session_data['results_objects'] | |
| print(f"π¬ Answering question: {question}") | |
| # Get answer | |
| rag = get_rag_system() | |
| answer = rag.answer_followup_question(question, results) | |
| print("β Answer generated") | |
| return jsonify({ | |
| 'success': True, | |
| 'question': question, | |
| 'answer': answer | |
| }) | |
| except Exception as e: | |
| print(f"β Question error: {str(e)}") | |
| return jsonify({'error': str(e)}), 500 | |
| def get_summary(): | |
| """Generate overall summary""" | |
| try: | |
| data = request.get_json() | |
| session_id = data.get('session_id') | |
| if not session_id or session_id not in session_storage: | |
| return jsonify({'error': 'Session expired. Please upload your PDF again.'}), 400 | |
| # Get results from storage | |
| session_data = session_storage[session_id] | |
| results = session_data['results_objects'] | |
| print("π Generating summary...") | |
| # Generate summary | |
| rag = get_rag_system() | |
| summary = rag.generate_summary(results) | |
| # Calculate statistics | |
| stats = { | |
| 'total': len(results), | |
| 'normal': sum(1 for r in results if r.status == 'normal'), | |
| 'high': sum(1 for r in results if r.status == 'high'), | |
| 'low': sum(1 for r in results if r.status == 'low'), | |
| 'unknown': sum(1 for r in results if r.status == 'unknown') | |
| } | |
| print(f"β Summary generated - Stats: {stats}") | |
| return jsonify({ | |
| 'success': True, | |
| 'summary': summary, | |
| 'stats': stats | |
| }) | |
| except Exception as e: | |
| print(f"β Summary error: {str(e)}") | |
| return jsonify({'error': str(e)}), 500 | |
| def health_check(): | |
| """Health check endpoint""" | |
| return jsonify({ | |
| 'status': 'healthy', | |
| 'active_sessions': len(session_storage) | |
| }) | |
| def request_entity_too_large(error): | |
| return jsonify({'error': 'File too large. Maximum size is 16MB.'}), 413 | |
| def internal_error(error): | |
| return jsonify({'error': 'Internal server error'}), 500 | |
| if __name__ == '__main__': | |
| print("π Starting Lab Report Decoder...") | |
| print("π Server will be available at http://0.0.0.0:7860") | |
| app.run(debug=True, host='0.0.0.0', port=7860) |