{ "cells": [ { "cell_type": "code", "execution_count": 2, "id": "3ba22d8d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Current directory: C:\\Users\\HP\\NitroSense-AI\n", "Target file: C:\\Users\\HP\\NitroSense-AI\\app.py\n", "โœ… app.py created successfully with session check and flash messages!\n", "File location: C:\\Users\\HP\\NitroSense-AI\\app.py\n", "โœ… Verified: app.py exists (20.96 KB)\n" ] } ], "source": [ "# %% [markdown]\n", "# # Create app.py file\n", "# ## Run this to generate the Flask application file\n", "\n", "# %%\n", "import os\n", "\n", "# Get current directory\n", "current_dir = os.getcwd()\n", "print(f\"Current directory: {current_dir}\")\n", "print(f\"Target file: {os.path.join(current_dir, 'app.py')}\")\n", "\n", "# %%\n", "# Complete Flask app code with session check and flash messages\n", "app_code = '''import os\n", "import numpy as np\n", "import tensorflow as tf\n", "from tensorflow.keras import backend as K\n", "from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash\n", "from werkzeug.utils import secure_filename\n", "from sklearn.preprocessing import StandardScaler, MinMaxScaler\n", "import cv2\n", "from datetime import datetime\n", "import pickle\n", "import uuid\n", "import time\n", "import shutil\n", "from PIL import Image\n", "import traceback\n", "import sys\n", "\n", "# Import utilities\n", "from utils.background_removal import BackgroundRemover\n", "from utils.edge_detection import EdgeDetector\n", "from utils.preprocessing import ImagePreprocessor\n", "\n", "# ============= DEFINE CUSTOM ACTIVATION FUNCTIONS =============\n", "def mish_activation(x):\n", " \"\"\"\n", " Mish activation function: x * tanh(softplus(x))\n", " \"\"\"\n", " return x * tf.math.tanh(tf.math.softplus(x))\n", "\n", "def swish_activation(x):\n", " \"\"\"\n", " Swish activation function: x * sigmoid(x)\n", " \"\"\"\n", " return x * tf.nn.sigmoid(x)\n", "\n", "# Register custom activations\n", "tf.keras.utils.get_custom_objects()['mish_activation'] = mish_activation\n", "tf.keras.utils.get_custom_objects()['swish_activation'] = swish_activation\n", "# ==============================================================\n", "\n", "# Define custom metrics functions (same as during training)\n", "def rmse(y_true, y_pred):\n", " \"\"\"Root Mean Square Error\"\"\"\n", " return K.sqrt(K.mean(K.square(y_pred - y_true)))\n", "\n", "def r2(y_true, y_pred):\n", " \"\"\"R-squared (Coefficient of determination)\"\"\"\n", " SS_res = K.sum(K.square(y_true - y_pred))\n", " SS_tot = K.sum(K.square(y_true - K.mean(y_true)))\n", " return 1 - SS_res/(SS_tot + K.epsilon())\n", "\n", "def mae(y_true, y_pred):\n", " \"\"\"Mean Absolute Error\"\"\"\n", " return K.mean(K.abs(y_pred - y_true))\n", "\n", "# Create Flask app\n", "app = Flask(__name__)\n", "app.secret_key = 'nitrosense-secret-key-2024'\n", "app.config['SESSION_TYPE'] = 'filesystem'\n", "app.config['SESSION_PERMANENT'] = False\n", "app.config['SESSION_USE_SIGNER'] = True\n", "app.config['SESSION_COOKIE_NAME'] = 'nitrosense_session'\n", "app.config['SESSION_COOKIE_SECURE'] = False\n", "app.config['SESSION_COOKIE_HTTPONLY'] = True\n", "app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'\n", "\n", "# Configuration\n", "app.config['UPLOAD_FOLDER'] = 'static/uploads'\n", "app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024\n", "app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}\n", "\n", "# Initialize models and utilities\n", "print(\"=\"*50)\n", "print(\"Loading NitroSense AI Application\")\n", "print(\"=\"*50)\n", "\n", "# ============= LOAD SOIL TEMPERATURE MEAN =============\n", "print(\"\\\\n๐Ÿ“Š Loading soil temperature mean from training data...\")\n", "try:\n", " with open('utils/temperature_means.pkl', 'rb') as f:\n", " temp_means = pickle.load(f)\n", " SOIL_TEMP_MEAN = temp_means.get('soil_temp_mean', 29.8)\n", " print(f\"โœ… Using Soil Temperature mean: {SOIL_TEMP_MEAN}ยฐC\")\n", " print(f\"โ„น๏ธ Air Temperature will be provided by user input\")\n", "except Exception as e:\n", " print(f\"โš ๏ธ Could not load temperature means: {e}\")\n", " print(\" Using default soil temperature:\")\n", " SOIL_TEMP_MEAN = 29.8\n", " print(f\" Soil Temp: {SOIL_TEMP_MEAN}ยฐC\")\n", "# =====================================================\n", "\n", "# Load U2Net model (portable version)\n", "print(\"\\\\n๐Ÿ“ฆ Initializing Background Remover...\")\n", "try:\n", " background_remover = BackgroundRemover()\n", " print(\"โœ… BackgroundRemover initialized (portable version)\")\n", " print(\" Weights should be at: u2net/weights/u2net.pth\")\n", "except Exception as e:\n", " print(f\"โš ๏ธ Error initializing BackgroundRemover: {e}\")\n", " print(\" Will use fallback mode (no background removal)\")\n", " background_remover = None\n", "\n", "# Load edge detector\n", "print(\"\\\\n๐Ÿ“ฆ Initializing Edge Detector...\")\n", "try:\n", " edge_detector = EdgeDetector()\n", " print(\"โœ… EdgeDetector initialized\")\n", "except Exception as e:\n", " print(f\"โš ๏ธ Error initializing EdgeDetector: {e}\")\n", " edge_detector = None\n", "\n", "# Load preprocessor - Use 224x224 to match DenseNet121 model\n", "print(\"\\\\n๐Ÿ“ฆ Initializing Image Preprocessor...\")\n", "try:\n", " preprocessor = ImagePreprocessor(target_size=(224, 224))\n", " print(\"โœ… ImagePreprocessor initialized with target_size=(224, 224)\")\n", "except Exception as e:\n", " print(f\"โš ๏ธ Error initializing ImagePreprocessor: {e}\")\n", " preprocessor = None\n", "\n", "# Load your trained model with custom metrics\n", "print(\"\\\\n๐Ÿ“ฆ Loading Model...\")\n", "model_path = os.path.join('models', 'Pyramid_fusion_densenet121_model.h5')\n", "\n", "if os.path.exists(model_path):\n", " try:\n", " custom_objects = {\n", " 'mse': tf.keras.losses.MeanSquaredError(),\n", " 'MSE': tf.keras.losses.MeanSquaredError(),\n", " 'mean_squared_error': tf.keras.losses.MeanSquaredError(),\n", " 'mae': mae,\n", " 'MAE': mae,\n", " 'mean_absolute_error': mae,\n", " 'rmse': rmse,\n", " 'RMSE': rmse,\n", " 'root_mean_squared_error': rmse,\n", " 'r2': r2,\n", " 'R2': r2,\n", " 'r_squared': r2,\n", " 'R_squared': r2,\n", " 'mish_activation': mish_activation,\n", " 'swish_activation': swish_activation,\n", " }\n", " \n", " model = tf.keras.models.load_model(\n", " model_path, \n", " custom_objects=custom_objects,\n", " compile=False\n", " )\n", " print(f\"โœ… Model loaded successfully from: {model_path}\")\n", " \n", " print(\"\\\\n๐Ÿ“Š Model input structure:\")\n", " for i, input_layer in enumerate(model.inputs):\n", " print(f\" Input {i+1}: {input_layer.name} - Shape: {input_layer.shape}\")\n", " \n", " model.compile(\n", " optimizer='adam',\n", " loss='mse',\n", " metrics=[mae, rmse, r2]\n", " )\n", " print(\"โœ… Model recompiled with custom metrics\")\n", " \n", " except Exception as e:\n", " print(f\"โš ๏ธ Error loading model: {e}\")\n", " traceback.print_exc()\n", " model = None\n", "else:\n", " print(f\"โŒ Model file not found at: {model_path}\")\n", "\n", "# Load all scalers\n", "print(\"\\\\n๐Ÿ“ฆ Loading All Scalers...\")\n", "try:\n", " continuous_scaler_path = 'utils/scalers/continuous_scaler.pkl'\n", " if os.path.exists(continuous_scaler_path):\n", " continuous_scaler = pickle.load(open(continuous_scaler_path, 'rb'))\n", " print(\"โœ… continuous_scaler loaded successfully\")\n", " else:\n", " continuous_scaler = None\n", " print(\"โš ๏ธ continuous_scaler not found\")\n", " \n", " days_scaler_path = 'utils/scalers/days_scaler.pkl'\n", " if os.path.exists(days_scaler_path):\n", " days_scaler = pickle.load(open(days_scaler_path, 'rb'))\n", " print(\"โœ… days_scaler loaded successfully\")\n", " else:\n", " days_scaler = None\n", " print(\"โš ๏ธ days_scaler not found\")\n", " \n", " nitrogen_map_path = 'utils/scalers/nitrogen_map.pkl'\n", " if os.path.exists(nitrogen_map_path):\n", " nitrogen_map = pickle.load(open(nitrogen_map_path, 'rb'))\n", " print(\"โœ… nitrogen_map loaded successfully\")\n", " else:\n", " nitrogen_map = {0:0, 30:1, 60:2, 90:3, 120:4, 150:5, 180:6, 210:7}\n", " print(\"โš ๏ธ nitrogen_map not found, using default mapping\")\n", " \n", " scaler_y_path = 'utils/scalers/scaler_y.pkl'\n", " if os.path.exists(scaler_y_path):\n", " scaler_y = pickle.load(open(scaler_y_path, 'rb'))\n", " print(f\"โœ… scaler_y loaded successfully\")\n", " print(f\" Target scaler mean: {scaler_y.mean_[0]:.4f}\")\n", " print(f\" Target scaler scale: {scaler_y.scale_[0]:.4f}\")\n", " else:\n", " scaler_y = None\n", " print(\"โš ๏ธ scaler_y not found\")\n", " \n", "except Exception as e:\n", " print(f\"โš ๏ธ Error loading scalers: {e}\")\n", " continuous_scaler = None\n", " days_scaler = None\n", " nitrogen_map = {0:0, 30:1, 60:2, 90:3, 120:4, 150:5, 180:6, 210:7}\n", " scaler_y = None\n", "\n", "print(\"\\\\n\" + \"=\"*50)\n", "print(\"โœ… Application initialization complete!\")\n", "print(\"=\"*50)\n", "\n", "def cleanup_old_images(max_age_hours=24):\n", " \"\"\"Delete images older than max_age_hours\"\"\"\n", " try:\n", " upload_folder = app.config['UPLOAD_FOLDER']\n", " if not os.path.exists(upload_folder):\n", " return\n", " current_time = time.time()\n", " for filename in os.listdir(upload_folder):\n", " filepath = os.path.join(upload_folder, filename)\n", " if os.path.isfile(filepath):\n", " file_age = current_time - os.path.getctime(filepath)\n", " if file_age > max_age_hours * 3600:\n", " os.remove(filepath)\n", " print(f\"๐Ÿงน Deleted old image: {filename}\")\n", " except Exception as e:\n", " print(f\"โš ๏ธ Error cleaning up images: {e}\")\n", "\n", "def allowed_file(filename):\n", " return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']\n", "\n", "def calculate_days(sowing_date, capture_date):\n", " \"\"\"Calculate number of days between sowing and capture\"\"\"\n", " date_format = \"%Y-%m-%d\"\n", " sowing = datetime.strptime(sowing_date, date_format)\n", " capture = datetime.strptime(capture_date, date_format)\n", " days = (capture - sowing).days\n", " return max(days, 0)\n", "\n", "def map_fertilizer_to_category(fertilizer_value):\n", " \"\"\"Map continuous fertilizer value (0-210) to categorical bin (0-7)\"\"\"\n", " nitrogen_bins = [0, 30, 60, 90, 120, 150, 180, 210]\n", " nitrogen_map = {0:0, 30:1, 60:2, 90:3, 120:4, 150:5, 180:6, 210:7}\n", " closest_bin = min(nitrogen_bins, key=lambda x: abs(x - fertilizer_value))\n", " return nitrogen_map[closest_bin]\n", "\n", "def classify_nitrogen_level(nitrogen_value):\n", " \"\"\"Classify nitrogen content into categories\"\"\"\n", " if nitrogen_value < 3.0:\n", " return {\n", " 'category': 'Deficient',\n", " 'message': 'โš ๏ธ Nitrogen Deficient - Fertilizer Recommended',\n", " 'color': 'warning',\n", " 'action': 'Apply nitrogen fertilizer'\n", " }\n", " elif nitrogen_value <= 4.0:\n", " return {\n", " 'category': 'Sufficient',\n", " 'message': 'โœ… Nitrogen Sufficient - No Fertilizer Needed',\n", " 'color': 'success',\n", " 'action': 'Maintain current practices'\n", " }\n", " else:\n", " return {\n", " 'category': 'Excess',\n", " 'message': 'โš ๏ธ Nitrogen Excess - Reduce Fertilizer Application',\n", " 'color': 'danger',\n", " 'action': 'Reduce or skip nitrogen application'\n", " }\n", "\n", "@app.route('/')\n", "def index():\n", " return render_template('index.html')\n", " \n", "@app.route('/estimation')\n", "def estimation():\n", " \"\"\"Estimation page route - redirects to home if accessed directly without proper session\"\"\"\n", " # Check URL parameters for image filename (from capture page)\n", " image_param = request.args.get('image')\n", " \n", " if image_param:\n", " # If coming from capture page with image parameter\n", " session['current_image'] = image_param\n", " session.modified = True\n", " return render_template('estimation.html')\n", " \n", " # Check if there's an image in session (uploaded from file or capture)\n", " if 'current_image' not in session:\n", " # This appears to be a direct access - redirect to home page\n", " flash('Please upload or capture a wheat leaf image first before estimation.', 'info')\n", " return redirect(url_for('index'))\n", " \n", " return render_template('estimation.html')\n", " \n", "@app.route('/capture')\n", "def capture():\n", " return render_template('capture.html')\n", "\n", "@app.route('/upload', methods=['POST'])\n", "def upload_file():\n", " try:\n", " if 'image' not in request.files:\n", " return jsonify({'error': 'No image uploaded'}), 400\n", " \n", " file = request.files['image']\n", " print(f\"๐Ÿ“ค Upload - File received: {file.filename}\")\n", " \n", " if file.filename == '':\n", " return jsonify({'error': 'No image selected'}), 400\n", " \n", " if file and allowed_file(file.filename):\n", " os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)\n", " \n", " filename = str(uuid.uuid4()) + '_' + secure_filename(file.filename)\n", " filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)\n", " file.save(filepath)\n", " print(f\"๐Ÿ’พ File saved to: {filepath}\")\n", " \n", " if os.path.exists(filepath) and os.path.getsize(filepath) > 0:\n", " session['current_image'] = filename\n", " session.modified = True\n", " \n", " return jsonify({\n", " 'success': True,\n", " 'filename': filename,\n", " 'image_url': f'/static/uploads/{filename}'\n", " })\n", " else:\n", " return jsonify({'error': 'Failed to save file'}), 400\n", " \n", " return jsonify({'error': 'Invalid file type'}), 400\n", " \n", " except Exception as e:\n", " print(f\"โŒ Upload error: {e}\")\n", " traceback.print_exc()\n", " return jsonify({'error': str(e)}), 500\n", "\n", "@app.route('/predict', methods=['POST'])\n", "def predict():\n", " cleanup_old_images(24)\n", " \n", " try:\n", " if request.is_json:\n", " data = request.get_json()\n", " else:\n", " data = request.form\n", " \n", " print(f\"๐Ÿ“จ Form data received: {data}\")\n", " \n", " # Get continuous fertilizer value\n", " try:\n", " fertilizer_amount = float(data.get('fertilizer'))\n", " if fertilizer_amount < 0 or fertilizer_amount > 210:\n", " return jsonify({'error': 'Fertilizer amount must be between 0 and 210 kg/ha'}), 400\n", " except (TypeError, ValueError):\n", " return jsonify({'error': 'Please provide a valid fertilizer amount between 0-210 kg/ha'}), 400\n", " \n", " # Get dates and calculate days\n", " sowing_date = data.get('sowingDate')\n", " capture_date = data.get('captureDate')\n", " days = calculate_days(sowing_date, capture_date)\n", " \n", " if days < 60 or days > 120:\n", " return jsonify({'error': f'Days must be between 60-120. Current: {days}'}), 400\n", " \n", " # Get air temperature\n", " try:\n", " air_temp = float(data.get('airTemp'))\n", " except (TypeError, ValueError):\n", " return jsonify({'error': 'Please provide valid air temperature value'}), 400\n", " \n", " soil_temp = SOIL_TEMP_MEAN\n", " \n", " print(f\"\\\\n๐Ÿ“ Input parameters:\")\n", " print(f\" Fertilizer: {fertilizer_amount} kg/ha\")\n", " print(f\" Days after sowing: {days}\")\n", " print(f\" Air Temperature: {air_temp}ยฐC\")\n", " print(f\" Soil Temperature: {soil_temp}ยฐC (mean from training)\")\n", " \n", " nitrogen_category = map_fertilizer_to_category(fertilizer_amount)\n", " print(f\" Fertilizer {fertilizer_amount} kg/ha โ†’ Category {nitrogen_category}\")\n", " \n", " if any(v is None for v in [continuous_scaler, days_scaler, scaler_y]):\n", " missing = []\n", " if continuous_scaler is None: missing.append(\"continuous_scaler\")\n", " if days_scaler is None: missing.append(\"days_scaler\")\n", " if scaler_y is None: missing.append(\"scaler_y\")\n", " return jsonify({'error': f'Scalers not loaded: {\", \".join(missing)}'}), 500\n", " \n", " # Get image from session\n", " filename = session.get('current_image')\n", " if not filename:\n", " return jsonify({'error': 'No image found. Please upload an image first.'}), 400\n", " \n", " image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)\n", " \n", " if not os.path.exists(image_path):\n", " return jsonify({'error': f'Image file not found'}), 400\n", " \n", " # Read image\n", " image = cv2.imread(image_path)\n", " if image is None:\n", " try:\n", " pil_img = Image.open(image_path)\n", " if pil_img.mode != 'RGB':\n", " pil_img = pil_img.convert('RGB')\n", " image = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)\n", " print(f\"โœ… PIL fallback succeeded\")\n", " except Exception as e:\n", " print(f\"โŒ All image reading methods failed: {e}\")\n", " return jsonify({'error': 'Could not read image'}), 400\n", " \n", " image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n", " \n", " # Background removal\n", " if background_remover is not None:\n", " try:\n", " bg_removed, mask = background_remover.remove_background(\n", " image_rgb, target_size=(224, 224), max_size=800\n", " )\n", " print(f\"โœ… Background removal completed\")\n", " except Exception as e:\n", " print(f\"โš ๏ธ Background removal error: {e}\")\n", " bg_removed = cv2.resize(image_rgb, (224, 224))\n", " else:\n", " bg_removed = cv2.resize(image_rgb, (224, 224))\n", " \n", " # Edge detection\n", " if edge_detector is not None:\n", " try:\n", " edge_image, edges = edge_detector.detect_edges(bg_removed)\n", " except Exception as e:\n", " print(f\"โš ๏ธ Edge detection error: {e}\")\n", " edge_image = bg_removed\n", " else:\n", " edge_image = bg_removed\n", " \n", " # Final preprocessing\n", " if preprocessor is not None:\n", " processed_image = preprocessor.preprocess_for_model(edge_image)\n", " else:\n", " processed_image = cv2.resize(edge_image, (224, 224)).astype(np.float32) / 255.0\n", " \n", " # Process tabular features\n", " temperature_features = np.array([[air_temp, soil_temp]], dtype=np.float32)\n", " days_array = np.array([[days]], dtype=np.float32)\n", " \n", " temperature_scaled = continuous_scaler.transform(temperature_features)\n", " nitrogen_categorical = np.array([[nitrogen_category]], dtype=np.float32)\n", " days_scaled = days_scaler.transform(days_array)\n", " \n", " tabular_processed = np.concatenate([\n", " temperature_scaled, nitrogen_categorical, days_scaled\n", " ], axis=1).astype('float32')\n", " \n", " print(f\"๐Ÿ“Š Processed features shape: {tabular_processed.shape}\")\n", " \n", " # Make prediction\n", " nitrogen_content = 3.62\n", " prediction_successful = False\n", "\n", " if model is not None:\n", " try:\n", " image_input = np.expand_dims(processed_image, axis=0)\n", " feature_input = np.expand_dims(tabular_processed[0], axis=0)\n", " \n", " prediction = model.predict([image_input, feature_input], verbose=0)\n", " raw_prediction = prediction[0][0]\n", " print(f\"๐Ÿ” Raw model output: {raw_prediction:.6f}\")\n", " \n", " if scaler_y is not None:\n", " nitrogen_content = scaler_y.inverse_transform(prediction)[0][0]\n", " print(f\"๐Ÿ” Denormalized: {nitrogen_content:.4f}%\")\n", " prediction_successful = True\n", " \n", " if nitrogen_content < 1.0 or nitrogen_content > 6.0:\n", " print(f\"โš ๏ธ Warning: Predicted nitrogen {nitrogen_content:.2f}% is outside expected range\")\n", " nitrogen_content = 3.62\n", " prediction_successful = False\n", " else:\n", " nitrogen_content = raw_prediction\n", " prediction_successful = True\n", " \n", " except Exception as e:\n", " print(f\"โŒ Prediction error: {e}\")\n", " traceback.print_exc()\n", " nitrogen_content = 3.62\n", " else:\n", " print(\"โš ๏ธ Model not loaded, using default value\")\n", " nitrogen_content = 3.62\n", "\n", " print(f\"โœ… Final nitrogen content: {nitrogen_content:.2f}%\")\n", " \n", " classification = classify_nitrogen_level(nitrogen_content)\n", " \n", " session['prediction_result'] = {\n", " 'nitrogen_content': round(float(nitrogen_content), 2),\n", " 'fertilizer_applied': fertilizer_amount,\n", " 'days': days,\n", " 'classification': classification,\n", " 'prediction_successful': prediction_successful\n", " }\n", " session.modified = True\n", " \n", " return redirect(url_for('result'))\n", " \n", " except Exception as e:\n", " print(f\"โŒ Prediction route error: {e}\")\n", " traceback.print_exc()\n", " return jsonify({'error': str(e)}), 500\n", "\n", "@app.route('/result')\n", "def result():\n", " prediction_result = session.get('prediction_result', None)\n", " if prediction_result is None:\n", " return redirect(url_for('estimation'))\n", " return render_template('result.html', result=prediction_result)\n", " \n", "@app.route('/about')\n", "def about():\n", " return render_template('about.html')\n", "\n", "@app.route('/how-it-works')\n", "def how_it_works():\n", " return render_template('how-it-works.html')\n", "\n", "@app.route('/contact')\n", "def contact():\n", " return render_template('contact.html')\n", "\n", "@app.route('/team')\n", "def team():\n", " return render_template('team.html')\n", "\n", "if __name__ == '__main__':\n", " os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)\n", " print(f\"๐Ÿ“ Upload folder ready: {app.config['UPLOAD_FOLDER']}\")\n", " print(f\"๐Ÿ“ U2Net weights should be at: u2net/weights/u2net.pth\")\n", " print(f\"\\\\n๐Ÿš€ Starting NitroSense AI application...\")\n", " print(f\" Access at: http://localhost:5000\")\n", " print(f\" Press CTRL+C to quit\\\\n\")\n", " \n", " try:\n", " app.run(\n", " debug=True, \n", " host='0.0.0.0', \n", " port=5000,\n", " use_reloader=False\n", " )\n", " except SystemExit:\n", " print(\"\\\\n๐Ÿ‘‹ Application stopped normally\")\n", " except Exception as e:\n", " print(f\"\\\\nโŒ Error running application: {e}\")\n", " traceback.print_exc()\n", "'''\n", "\n", "# Write the file\n", "with open('app.py', 'w', encoding='utf-8') as f:\n", " f.write(app_code)\n", "\n", "print(\"โœ… app.py created successfully with session check and flash messages!\")\n", "print(f\"File location: {os.path.join(os.getcwd(), 'app.py')}\")\n", "\n", "# Verify file was created\n", "if os.path.exists('app.py'):\n", " file_size = os.path.getsize('app.py') / 1024\n", " print(f\"โœ… Verified: app.py exists ({file_size:.2f} KB)\")\n", "else:\n", " print(\"โŒ Failed to create app.py\")" ] }, { "cell_type": "code", "execution_count": null, "id": "4e37f289", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" } }, "nbformat": 4, "nbformat_minor": 5 }