From 9945a29a8eac1c5050fad03eec0337a4cd843ec1 Mon Sep 17 00:00:00 2001 From: pjmalandrino Date: Fri, 30 May 2025 10:18:08 +0200 Subject: [PATCH] Update project structure to isolate backend files in order to preprare containerisation properly --- backend/__init__.py | 0 app.py => backend/app.py | 169 ++++++++++++++---- backend/batch_treatment/__init__.py | 0 .../batch_treatment/batch_processor.py | 14 +- backend/page_treatment/__init__.py | 0 .../page_treatment/analyzer.py | 11 +- .../page_treatment/picture_extractor.py | 8 +- .../page_treatment/visualizer.py | 20 ++- frontend/index.html | 18 +- frontend/static/app.js | 24 ++- frontend/static/styles.css | 123 +++++++++++++ run_app.sh | 20 +++ 12 files changed, 360 insertions(+), 47 deletions(-) create mode 100644 backend/__init__.py rename app.py => backend/app.py (79%) create mode 100644 backend/batch_treatment/__init__.py rename batch_processor.py => backend/batch_treatment/batch_processor.py (97%) create mode 100644 backend/page_treatment/__init__.py rename analyzer.py => backend/page_treatment/analyzer.py (97%) rename picture_extractor.py => backend/page_treatment/picture_extractor.py (97%) rename visualizer.py => backend/page_treatment/visualizer.py (95%) mode change 100644 => 100755 run_app.sh diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app.py b/backend/app.py similarity index 79% rename from app.py rename to backend/app.py index b809d7a..d00b054 100644 --- a/app.py +++ b/backend/app.py @@ -1,22 +1,27 @@ from flask import Flask, request, send_file, jsonify, Response import subprocess import os +import sys import time import threading import logging from pathlib import Path -import sys import uuid import pdf2image +# Add the parent directory to Python path to allow imports +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Initialize Flask app with frontend folder structure +# Frontend is in the parent directory +frontend_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'frontend') app = Flask(__name__, - static_folder='frontend/static', + static_folder=os.path.join(frontend_path, 'static'), static_url_path='/static') # Dictionary to store background task results @@ -24,7 +29,7 @@ task_results = {} # Import batch processor if available try: - from batch_processor import start_batch_processing, get_batch_processor, cleanup_old_batches + from backend.batch_treatment.batch_processor import start_batch_processing, get_batch_processor, cleanup_old_batches batch_processing_available = True except ImportError: logger.warning("batch_processor.py not found. Batch processing features will be disabled.") @@ -32,14 +37,18 @@ except ImportError: # Ensure results folder exists def ensure_results_folder(): - results_dir = Path("results") + # Always create results folder relative to where app.py is run from + results_dir = Path.cwd() / "results" if not results_dir.exists(): results_dir.mkdir() + logger.info(f"Created results directory at: {results_dir.absolute()}") + else: + logger.info(f"Results directory exists at: {results_dir.absolute()}") return results_dir # Ensure frontend folders exist def ensure_frontend_folders(): - frontend_dir = Path("frontend") + frontend_dir = Path(frontend_path) if not frontend_dir.exists(): frontend_dir.mkdir() logger.info(f"Created frontend directory: {frontend_dir}") @@ -53,16 +62,16 @@ def ensure_frontend_folders(): @app.route('/') def index(): - return send_file('frontend/index.html') + return send_file(os.path.join(frontend_path, 'index.html')) @app.route('/batch') def batch_interface(): """Serve the batch processing interface""" - return send_file('frontend/batch.html') + return send_file(os.path.join(frontend_path, 'batch.html')) @app.route('/static/') def serve_static(filename): - return send_file(os.path.join('frontend/static', filename)) + return send_file(os.path.join(frontend_path, 'static', filename)) @app.route('/pdf-files') def pdf_files(): @@ -123,19 +132,22 @@ def run_command(task_id, command): current_dir = os.getcwd() logger.info(f"Current working directory: {current_dir}") - # Check if the script exists - script_name = command.split()[1] - if not os.path.exists(script_name): - logger.error(f"Script not found: {script_name} in {current_dir}") + # The command already contains the full path, so we just need to verify it exists + script_parts = command.split() + script_path = script_parts[1] + + if not os.path.exists(script_path): + logger.error(f"Script not found: {script_path} in {current_dir}") task_results[task_id] = { 'success': False, - 'error': f"Script not found: {script_name}. Make sure all scripts are in the same directory as app.py.", + 'error': f"Script not found: {script_path}. Make sure all scripts are in the correct directory.", 'done': True } return # Run the command with more detailed error capture # Important: Use pipe input to automatically answer "n" to the prompt + # Make sure we run from the project root directory process = subprocess.Popen( command, shell=True, @@ -143,7 +155,8 @@ def run_command(task_id, command): stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, - universal_newlines=True + universal_newlines=True, + cwd=os.getcwd() # Explicitly set working directory to current directory ) # Send "n" to the process to bypass the "process all pages" prompt @@ -198,13 +211,14 @@ def run_analyzer(): logger.error(f"PDF file not found: {pdf_file}") return jsonify({'success': False, 'error': f'PDF file not found: {pdf_file}'}), 400 - # Check if analyzer.py exists - if not os.path.exists('analyzer.py'): - logger.error("analyzer.py not found in current directory") - return jsonify({'success': False, 'error': 'analyzer.py not found in current directory'}), 500 + # Check if analyzer.py exists in the new location + analyzer_path = os.path.join('backend', 'page_treatment', 'analyzer.py') + if not os.path.exists(analyzer_path): + logger.error(f"analyzer.py not found at: {analyzer_path}") + return jsonify({'success': False, 'error': 'analyzer.py not found in backend/page_treatment directory'}), 500 # Add the --all-pages=false flag or explicitly specify the page to avoid the prompt - command = f"python analyzer.py --image {pdf_file} --page {page_num} --start-page {page_num} --end-page {page_num}" + command = f"python backend/page_treatment/analyzer.py --image {pdf_file} --page {page_num} --start-page {page_num} --end-page {page_num}" # Generate a task ID task_id = f"analyzer_{int(time.time())}" @@ -238,15 +252,40 @@ def task_status(task_id): if task_id not in task_results: return jsonify({'success': False, 'error': 'Task not found'}), 404 - result = task_results[task_id] + result = task_results[task_id].copy() # Make a copy to avoid modifying the original - # If task is completed, add file paths + # If task is completed, add file paths and verify they exist if result['done'] and result['success']: if task_id.startswith('analyzer_'): - result['doctags_file'] = f"results/output.doctags.txt" + doctags_path = Path("results") / "output.doctags.txt" + if doctags_path.exists(): + result['doctags_file'] = "results/output.doctags.txt" + else: + logger.warning(f"DocTags file not found: {doctags_path}") + elif task_id.startswith('visualizer_'): page_num = task_id.split('_')[-1] if len(task_id.split('_')) > 2 else "1" - result['image_file'] = f"results/visualization_page_{page_num}.png" + viz_filename = f"visualization_page_{page_num}.png" + + # Check multiple possible locations + possible_paths = [ + Path("results") / viz_filename, + Path.cwd() / "results" / viz_filename, + Path(__file__).parent.parent / "results" / viz_filename + ] + + for path in possible_paths: + if path.exists(): + result['image_file'] = f"results/{viz_filename}" + logger.info(f"Found visualization at: {path}") + break + else: + logger.error(f"Visualization file not found in any location for page {page_num}") + # Log what files exist in results + results_dir = Path("results") + if results_dir.exists(): + files = list(results_dir.glob("*.png")) + logger.info(f"PNG files in results: {[f.name for f in files[:5]]}") return jsonify(result) @@ -260,7 +299,7 @@ def run_visualizer(): if not pdf_file: return jsonify({'success': False, 'error': 'PDF file not specified'}), 400 - command = f"python visualizer.py --doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}" + command = f"python backend/page_treatment/visualizer.py --doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}" if adjust: command += " --adjust" @@ -298,7 +337,7 @@ def run_extractor(): if not pdf_file: return jsonify({'success': False, 'error': 'PDF file not specified'}), 400 - command = f"python picture_extractor.py --doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}" + command = f"python backend/page_treatment/picture_extractor.py --doctags results/output.doctags.txt --pdf {pdf_file} --page {page_num}" if adjust: command += " --adjust" @@ -329,14 +368,28 @@ def run_extractor(): @app.route('/results/') def results(filename): try: - file_path = os.path.join('results', filename) - if os.path.exists(file_path): - return send_file(file_path) + # Always use the results directory relative to current working directory + results_dir = Path.cwd() / "results" + file_path = results_dir / filename + + logger.info(f"Requested file: {filename}") + logger.info(f"Looking for file at: {file_path.absolute()}") + logger.info(f"File exists: {file_path.exists()}") + + if file_path.exists() and file_path.is_file(): + logger.info(f"Serving file: {file_path}") + return send_file(str(file_path.absolute())) else: logger.error(f"File not found: {file_path}") + # List what files ARE in the results directory + if results_dir.exists(): + files = list(results_dir.glob("*")) + logger.info(f"Files in results directory: {[f.name for f in files[:10]]}") return jsonify({'error': f"File not found: {filename}"}), 404 except Exception as e: logger.error(f"Error serving file {filename}: {str(e)}") + import traceback + logger.error(traceback.format_exc()) return jsonify({'error': f"Error serving file: {filename}"}), 500 @app.route('/pdf-preview//') @@ -405,9 +458,15 @@ def check_environment(): # List all files in directory files = os.listdir('.') - # Check for required scripts + # Check for required scripts in new location + backend_page_dir = os.path.join('backend', 'page_treatment') required_scripts = ['analyzer.py', 'visualizer.py', 'picture_extractor.py'] - missing_scripts = [script for script in required_scripts if script not in files] + missing_scripts = [] + + for script in required_scripts: + script_path = os.path.join(backend_page_dir, script) + if not os.path.exists(script_path): + missing_scripts.append(script) # Check for PDFs pdf_files = [f for f in files if f.endswith('.pdf')] @@ -415,6 +474,7 @@ def check_environment(): # Check for results directory results_dir = Path("results") results_dir_exists = results_dir.exists() + results_files = [] # Try to check Python version and installed packages python_info = subprocess.run(['python', '--version'], capture_output=True, text=True) @@ -429,6 +489,9 @@ def check_environment(): f.write("test") os.remove(test_file) results_writable = True + + # List files in results directory + results_files = [f.name for f in results_dir.iterdir() if f.is_file()][:20] # Limit to 20 files except: pass @@ -439,6 +502,7 @@ def check_environment(): 'pdf_files': pdf_files, 'results_dir_exists': results_dir_exists, 'results_dir_writable': results_writable, + 'results_files': results_files, 'python_version': python_version, 'batch_processing_available': batch_processing_available }) @@ -460,6 +524,12 @@ def run_manual_command(): logger.info(f"Running manual command: {command}") + # Update paths in manual commands to use backend directory only if not already present + if 'backend/page_treatment/' not in command: + command = command.replace('analyzer.py', 'backend/page_treatment/analyzer.py') + command = command.replace('visualizer.py', 'backend/page_treatment/visualizer.py') + command = command.replace('picture_extractor.py', 'backend/page_treatment/picture_extractor.py') + try: # Run the command synchronously for immediate feedback process = subprocess.Popen( @@ -766,6 +836,45 @@ def open_results_folder(): logger.error(f"Error opening results folder: {str(e)}") return jsonify({'success': False, 'error': str(e)}), 500 +@app.route('/debug-results') +def debug_results(): + """Debug endpoint to check results directory""" + try: + results_info = { + 'cwd': os.getcwd(), + 'results_paths_checked': [] + } + + # Check multiple possible results locations + possible_results_dirs = [ + Path("results"), + Path.cwd() / "results", + Path(__file__).parent.parent / "results" + ] + + for results_dir in possible_results_dirs: + dir_info = { + 'path': str(results_dir), + 'absolute_path': str(results_dir.absolute()), + 'exists': results_dir.exists(), + 'files': [] + } + + if results_dir.exists(): + try: + # List all files in the directory + files = list(results_dir.glob("*")) + dir_info['files'] = [f.name for f in files if f.is_file()][:20] # Limit to 20 files + except Exception as e: + dir_info['error'] = str(e) + + results_info['results_paths_checked'].append(dir_info) + + return jsonify(results_info) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + # Cleanup task for batch processors def cleanup_task(): """Periodic cleanup of old batch processors""" diff --git a/backend/batch_treatment/__init__.py b/backend/batch_treatment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/batch_processor.py b/backend/batch_treatment/batch_processor.py similarity index 97% rename from batch_processor.py rename to backend/batch_treatment/batch_processor.py index 032e84e..a7ee170 100644 --- a/batch_processor.py +++ b/backend/batch_treatment/batch_processor.py @@ -5,6 +5,7 @@ Handles batch processing of PDF documents with parallel processing support """ import os +import sys import json import time import threading @@ -17,6 +18,9 @@ import subprocess import shutil import zipfile +# Add the parent directory to Python path to allow imports +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + # Configure logging logging.basicConfig( level=logging.INFO, @@ -172,8 +176,9 @@ class BatchProcessor: # Use standard results directory for analyzer output output_base = Path("results") / f"output" + # Update path to use backend directory command = [ - "python", "analyzer.py", + "python", "backend/page_treatment/analyzer.py", "--image", self.pdf_file, "--page", str(page_num), "--output", str(output_base), @@ -224,8 +229,9 @@ class BatchProcessor: if page_doctags.exists(): shutil.copy2(page_doctags, doctags_path) + # Update path to use backend directory command = [ - "python", "visualizer.py", + "python", "backend/page_treatment/visualizer.py", "--doctags", str(doctags_path), "--pdf", self.pdf_file, "--page", str(page_num) @@ -272,9 +278,9 @@ class BatchProcessor: if page_doctags.exists(): shutil.copy2(page_doctags, doctags_path) - # Let extractor create files in its default location first + # Update path to use backend directory command = [ - "python", "picture_extractor.py", + "python", "backend/page_treatment/picture_extractor.py", "--doctags", str(doctags_path), "--pdf", self.pdf_file, "--page", str(page_num) diff --git a/backend/page_treatment/__init__.py b/backend/page_treatment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/analyzer.py b/backend/page_treatment/analyzer.py similarity index 97% rename from analyzer.py rename to backend/page_treatment/analyzer.py index 196e9e7..573e9e5 100644 --- a/analyzer.py +++ b/backend/page_treatment/analyzer.py @@ -28,10 +28,17 @@ from docling_core.types.doc.document import DocTagsDocument, DoclingDocument def ensure_results_folder(): """Create the results folder if it doesn't exist.""" - results_dir = Path("results") + # Get the project root directory (where the script is called from) + # Since we're in backend/page_treatment/, we need to go up to the root + script_dir = Path(__file__).parent + project_root = script_dir.parent.parent # Go up two levels from backend/page_treatment/ + results_dir = project_root / "results" + if not results_dir.exists(): - results_dir.mkdir() + results_dir.mkdir(parents=True) print(f"Created results directory: {results_dir}") + + print(f"Using results directory: {results_dir.absolute()}") return results_dir diff --git a/picture_extractor.py b/backend/page_treatment/picture_extractor.py similarity index 97% rename from picture_extractor.py rename to backend/page_treatment/picture_extractor.py index eca88ff..5cd839d 100644 --- a/picture_extractor.py +++ b/backend/page_treatment/picture_extractor.py @@ -23,11 +23,17 @@ def ensure_results_folder(custom_path=None): if custom_path: results_dir = Path(custom_path) else: - results_dir = Path("results") + # Get the project root directory (where the script is called from) + # Since we're in backend/page_treatment/, we need to go up to the root + script_dir = Path(__file__).parent + project_root = script_dir.parent.parent # Go up two levels from backend/page_treatment/ + results_dir = project_root / "results" if not results_dir.exists(): results_dir.mkdir(parents=True) print(f"Created directory: {results_dir}") + + print(f"Using results directory: {results_dir.absolute()}") return results_dir def parse_arguments(): diff --git a/visualizer.py b/backend/page_treatment/visualizer.py similarity index 95% rename from visualizer.py rename to backend/page_treatment/visualizer.py index 4826e91..ee683c2 100644 --- a/visualizer.py +++ b/backend/page_treatment/visualizer.py @@ -4,7 +4,7 @@ DocTags Zone Visualizer - Simple script to visualize zones identified in DocTags PNG-only version: Creates debug images with rectangles around zones. Usage: - python visualizer_png_only.py --doctags output.doctags.txt --pdf document.pdf --page 8 + python visualizer.py --doctags output.doctags.txt --pdf document.pdf --page 8 """ import argparse @@ -20,10 +20,17 @@ LOC_PATTERN = r'' def ensure_results_folder(): """Create the results folder if it doesn't exist.""" - results_dir = Path("results") + # Get the project root directory (where the script is called from) + # Since we're in backend/page_treatment/, we need to go up to the root + script_dir = Path(__file__).parent + project_root = script_dir.parent.parent # Go up two levels from backend/page_treatment/ + results_dir = project_root / "results" + if not results_dir.exists(): - results_dir.mkdir() + results_dir.mkdir(parents=True) print(f"Created results directory: {results_dir}") + + print(f"Using results directory: {results_dir.absolute()}") return results_dir def parse_arguments(): @@ -227,6 +234,7 @@ def create_debug_image(image, zones, page_num, output_path): # Save the debug image debug_img.save(output_path) print(f"Debug image saved to: {output_path}") + print(f"Absolute path: {output_path.absolute()}") return debug_img @@ -270,6 +278,9 @@ def process_page(pdf_path, page_num, doctags_path, output_path, dpi=200, scale=1 output_name = f"visualization_page_{page_num}.png" output_path = results_dir / output_name + # Make sure output_path is a Path object + output_path = Path(output_path) + # Load the page image try: image = load_image_from_pdf(pdf_path, page_num, dpi) @@ -372,9 +383,6 @@ def process_all_pages(pdf_path, doctags_path, output_base, dpi=200, scale=1.0, s return True def main(): - # Ensure results folder exists - ensure_results_folder() - # Parse arguments args = parse_arguments() diff --git a/frontend/index.html b/frontend/index.html index 5119b6b..c573a31 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,12 +3,26 @@ - DocTags Analyzer & Visualizer + SCUB-Doc Intelligence Suite - Professional Document Analysis
-

DocTags Analyzer & Visualizer

+ +
+
+ + + +
+
+

+ DocTags Intelligence Suite + AI powered +

+

Enterprise-grade document processing and analysis

+
+
diff --git a/frontend/static/app.js b/frontend/static/app.js index e8f7d43..fee90aa 100644 --- a/frontend/static/app.js +++ b/frontend/static/app.js @@ -327,6 +327,11 @@ function checkEnvironment() { } else { html += '
  • ✗ Results directory is not writable
  • '; } + + // Show files in results directory + if (data.results_files && data.results_files.length > 0) { + html += '
  • Files in results directory: ' + data.results_files.join(', ') + '
  • '; + } } else { html += '
  • ✗ Results directory does not exist
  • '; } @@ -338,6 +343,18 @@ function checkEnvironment() { data.files.join('\n') + ''; envDetails.innerHTML = html; + + // Also check debug-results endpoint + fetch('/debug-results') + .then(response => response.json()) + .then(debugData => { + html += '
    Results Directory Debug Info
    ' +
    +                        JSON.stringify(debugData, null, 2) + '
    '; + envDetails.innerHTML = html; + }) + .catch(error => { + console.error('Error getting debug info:', error); + }); }) .catch(error => { envDetails.innerHTML = '
    Error checking environment: ' + error.message + '
    '; @@ -346,7 +363,7 @@ function checkEnvironment() { // Manual command execution for debugging function manuallyRunScript() { - const command = prompt("Enter command to run (e.g., 'python analyzer.py --image document.pdf --page 1')"); + const command = prompt("Enter command to run (e.g., 'python backend/page_treatment/analyzer.py --image document.pdf --page 1')"); if (!command) return; const outputDiv = document.getElementById('output'); @@ -423,8 +440,11 @@ function pollTasks() { // Show image if available for visualizer if (taskInfo.type === 'visualizer' && data.image_file) { + // Fix: Ensure we're using the correct path format generatedOutputs.visualizer = '/' + data.image_file; - document.getElementById('result-image').src = generatedOutputs.visualizer + '?t=' + new Date().getTime(); + const imagePath = generatedOutputs.visualizer + '?t=' + new Date().getTime(); + console.log('Loading visualization from:', imagePath); + document.getElementById('result-image').src = imagePath; document.getElementById('image-container').classList.remove('hidden'); } diff --git a/frontend/static/styles.css b/frontend/static/styles.css index a849533..4995e0a 100644 --- a/frontend/static/styles.css +++ b/frontend/static/styles.css @@ -31,6 +31,128 @@ --spacing-lg: 24px; --spacing-xl: 32px; } +/* Professional Enterprise Title Styles - Add this after the :root variables in styles.css */ + +/* Enterprise Style Title */ +.title-enterprise { + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + margin-bottom: 48px; + padding-top: 16px; +} + +.title-enterprise .brand-icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + background: var(--surface-white); + border-radius: 16px; + box-shadow: var(--shadow-lg); + position: relative; + overflow: hidden; + flex-shrink: 0; +} + +.title-enterprise .brand-icon::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, var(--accent-mint) 0%, transparent 70%); + transform: rotate(45deg); +} + +.title-enterprise .brand-icon svg { + width: 36px; + height: 36px; + fill: var(--primary-dark); + position: relative; + z-index: 1; +} + +.title-enterprise .brand-text { + text-align: left; +} + +.title-enterprise h1 { + font-size: 2.25rem; + font-weight: 600; + color: var(--primary-dark); + margin: 0 0 4px 0; + letter-spacing: -0.025em; + display: flex; + align-items: baseline; + gap: 12px; + line-height: 1.2; +} + +.title-enterprise .pro-badge { + display: inline-flex; + align-items: center; + padding: 4px 12px; + background: var(--accent-amber); + color: var(--primary-dark); + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.05em; + border-radius: 6px; + text-transform: uppercase; + line-height: 1; +} + +.title-enterprise .tagline { + font-size: 1rem; + color: var(--text-medium); + font-weight: 400; + margin: 0; + letter-spacing: 0.01em; +} + +/* Update the old h1 styles to remove conflicts */ +body > .container > h1 { + display: none; /* Hide if there's still an old h1 */ +} + +/* Responsive adjustments for the title */ +@media (max-width: 768px) { + .title-enterprise { + flex-direction: column; + text-align: center; + gap: 16px; + margin-bottom: 32px; + } + + .title-enterprise .brand-text { + text-align: center; + } + + .title-enterprise h1 { + font-size: 1.75rem; + flex-direction: column; + gap: 8px; + align-items: center; + } + + .title-enterprise .brand-icon { + width: 56px; + height: 56px; + } + + .title-enterprise .brand-icon svg { + width: 32px; + height: 32px; + } + + .title-enterprise .tagline { + font-size: 0.9rem; + } +} * { box-sizing: border-box; @@ -844,4 +966,5 @@ button:disabled { font-size: 0.8rem; padding: var(--spacing-xs) var(--spacing-sm); } + } \ No newline at end of file diff --git a/run_app.sh b/run_app.sh old mode 100644 new mode 100755 index e69de29..ffa3abc --- a/run_app.sh +++ b/run_app.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +echo "Starting DocTags Application..." + +# Check if backend directory exists +if [ ! -d "backend" ]; then + echo "Error: backend directory not found!" + echo "Please run the reorganization script first." + exit 1 +fi + +# Check if app.py exists in backend +if [ ! -f "backend/app.py" ]; then + echo "Error: backend/app.py not found!" + exit 1 +fi + +# Run the Flask application +echo "Running Flask application..." +python backend/app.py \ No newline at end of file