| | """ |
| | 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 |
| | app.config['UPLOAD_FOLDER'] = "/tmp" |
| |
|
| | |
| | rag_system = None |
| |
|
| | |
| | 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 |
| |
|
| | @app.route('/') |
| | def index(): |
| | """Main page""" |
| | return render_template('index.html') |
| |
|
| | @app.route('/api/upload', methods=['POST']) |
| | 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 |
| | |
| | |
| | filename = secure_filename(file.filename) |
| | filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) |
| | file.save(filepath) |
| | |
| | try: |
| | |
| | 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") |
| | |
| | |
| | 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 |
| | ] |
| | |
| | |
| | session_id = str(uuid.uuid4()) |
| | |
| | |
| | session_storage[session_id] = { |
| | 'results': results_data, |
| | 'results_objects': results |
| | } |
| | |
| | print(f"πΎ Stored results in session: {session_id}") |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'session_id': session_id, |
| | 'results': results_data, |
| | 'count': len(results_data) |
| | }) |
| | |
| | finally: |
| | |
| | if os.path.exists(filepath): |
| | os.remove(filepath) |
| | |
| | except Exception as e: |
| | print(f"β Upload error: {str(e)}") |
| | return jsonify({'error': str(e)}), 500 |
| |
|
| | @app.route('/api/explain', methods=['POST']) |
| | 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 |
| | |
| | |
| | session_data = session_storage[session_id] |
| | results = session_data['results_objects'] |
| | |
| | print(f"π§ Generating explanations for {len(results)} results...") |
| | |
| | |
| | rag = get_rag_system() |
| | |
| | |
| | from concurrent.futures import TimeoutError |
| | import signal |
| | |
| | try: |
| | explanations = rag.explain_all_results(results) |
| | except Exception as e: |
| | print(f"β οΈ Explanation generation error: {str(e)}") |
| | |
| | explanations = {} |
| | for result in results: |
| | status_msg = { |
| | 'normal': f"β
Your {result.test_name} is within normal range.", |
| | 'high': f"β οΈ Your {result.test_name} is above normal range. Consult your doctor.", |
| | 'low': f"β οΈ Your {result.test_name} is below normal range. Consult your doctor.", |
| | 'unknown': f"Your {result.test_name} result is {result.value} {result.unit}." |
| | } |
| | explanations[result.test_name] = status_msg.get(result.status, "Result recorded.") |
| | |
| | |
| | session_storage[session_id]['explanations'] = explanations |
| | |
| | print(f"β
Generated {len(explanations)} explanations") |
| | |
| | return jsonify({ |
| | 'success': True, |
| | 'explanations': explanations |
| | }) |
| | |
| | except Exception as e: |
| | print(f"β Explanation error: {str(e)}") |
| | import traceback |
| | traceback.print_exc() |
| | return jsonify({'error': 'Error generating explanations. Please try uploading again.'}), 500 |
| |
|
| | @app.route('/api/ask', methods=['POST']) |
| | 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 |
| | |
| | |
| | session_data = session_storage[session_id] |
| | results = session_data['results_objects'] |
| | |
| | print(f"π¬ Answering question: {question}") |
| | |
| | |
| | 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 |
| |
|
| | @app.route('/api/summary', methods=['POST']) |
| | 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 |
| | |
| | |
| | session_data = session_storage[session_id] |
| | results = session_data['results_objects'] |
| | |
| | print("π Generating summary...") |
| | |
| | |
| | rag = get_rag_system() |
| | summary = rag.generate_summary(results) |
| | |
| | |
| | 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 |
| |
|
| | @app.route('/api/health', methods=['GET']) |
| | def health_check(): |
| | """Health check endpoint""" |
| | return jsonify({ |
| | 'status': 'healthy', |
| | 'active_sessions': len(session_storage) |
| | }) |
| |
|
| | @app.errorhandler(413) |
| | def request_entity_too_large(error): |
| | return jsonify({'error': 'File too large. Maximum size is 16MB.'}), 413 |
| |
|
| | @app.errorhandler(500) |
| | 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) |