#!/usr/bin/env python3 """ Smart Plant Doctor - Streamlit Web Application Combines sensor monitoring and plant disease detection """ import streamlit as st import pandas as pd import plotly.express as px import plotly.graph_objects as go from datetime import datetime, timedelta import time import random import os import sys from PIL import Image import io import sqlite3 import numpy as np import math try: from streamlit_autorefresh import st_autorefresh AUTOREFRESH_COMPONENT_AVAILABLE = True except ImportError: AUTOREFRESH_COMPONENT_AVAILABLE = False # Add the project root and ai directory to the path project_root = os.path.dirname(os.path.abspath(__file__)) sys.path.append(project_root) sys.path.append(os.path.join(project_root, 'ai')) try: from inference import SmartPlantDoctor except ImportError: st.error("โŒ Could not import SmartPlantDoctor. Make sure inference.py is in the ai/ directory.") st.stop() # Import alert system try: from utils.alert_system import alert_system, Alert from alert_config import ALERT_LEVELS, ALERT_SETTINGS # WhatsApp is intentionally disabled for now. alert_system.disable_whatsapp() except ImportError as e: st.error(f"โŒ Could not import alert system: {str(e)}") st.error("Make sure alert_system.py is in utils/ directory and alert_config.py is in root directory.") st.stop() # Live sensors DB (populated by ingest.py) - use absolute path anchored to project root DB_PATH = os.path.join(project_root, "data", "sensors.db") def _db_exists() -> bool: return os.path.exists(DB_PATH) def get_plants_from_db(): if not _db_exists(): return [] con = sqlite3.connect(DB_PATH) try: rows = con.execute("SELECT DISTINCT plant FROM readings ORDER BY plant").fetchall() return [r[0] for r in rows] except Exception: return [] finally: con.close() def get_latest_from_db(plant: str): if not _db_exists(): return None con = sqlite3.connect(DB_PATH) try: cur = con.execute( "SELECT ts, plant, temperature, humidity, light, soil_moisture, ph FROM readings WHERE plant=? ORDER BY rowid DESC LIMIT 1", (plant,), ) row = cur.fetchone() if not row: return None keys = ["ts","plant","temperature","humidity","light","soil_moisture","ph"] return dict(zip(keys, row)) except Exception: return None finally: con.close() def get_previous_from_db(plant: str): if not _db_exists(): return None con = sqlite3.connect(DB_PATH) try: cur = con.execute( "SELECT ts, plant, temperature, humidity, light, soil_moisture, ph FROM readings WHERE plant=? ORDER BY rowid DESC LIMIT 1 OFFSET 1", (plant,), ) row = cur.fetchone() if not row: return None keys = ["ts", "plant", "temperature", "humidity", "light", "soil_moisture", "ph"] return dict(zip(keys, row)) except Exception: return None finally: con.close() def _parse_ts_to_datetime(ts_value, fallback_index=0): """Convert device timestamp to datetime; fallback to recent synthetic timestamp.""" try: ts_num = float(ts_value) # If millis since epoch, scale down to seconds. if ts_num > 1e12: ts_num = ts_num / 1000.0 # Epoch sanity check (year 2000+). if ts_num > 946684800: return datetime.fromtimestamp(ts_num) except Exception: pass return datetime.now() - timedelta(minutes=max(0, fallback_index)) def calculate_health_score(sensor_data: dict) -> int: """Estimate health score from current sensor values (0-100).""" ranges = { 'temperature': (18, 28), 'humidity': (40, 80), 'light': (200, 1000), 'soil_moisture': (30, 70), } penalties = 0.0 for key, (low, high) in ranges.items(): value = float(sensor_data.get(key, (low + high) / 2)) if value < low: penalties += min(25.0, ((low - value) / max(1.0, low)) * 25.0) elif value > high: penalties += min(25.0, ((value - high) / max(1.0, high)) * 25.0) return int(max(0, min(100, round(100.0 - penalties)))) def get_soil_condition(moisture_percent: float): """Map soil moisture percentage to the same wet/moist/dry states used by firmware.""" if moisture_percent >= 85: return "Wet", "#2E8B57", "๐ŸŒŠ" if moisture_percent >= 35: return "Moist", "#FFC107", "๐Ÿ’ง" return "Dry", "#E53935", "โš ๏ธ" # Page configuration st.set_page_config( page_title="๐ŸŒฑ Smart Plant Doctor", page_icon="๐ŸŒฑ", layout="wide", initial_sidebar_state="expanded" ) # Custom CSS st.markdown(""" """, unsafe_allow_html=True) # Initialize session state if 'doctor' not in st.session_state: st.session_state.doctor = None if 'sensor_data' not in st.session_state: st.session_state.sensor_data = [] def load_model(): """Load the Smart Plant Doctor model""" if st.session_state.doctor is None: try: with st.spinner("๐ŸŒฑ Loading Smart Plant Doctor model..."): model_path = os.path.join(project_root, "ai", "exports", "smart_plant_doctor_model.pth") st.session_state.doctor = SmartPlantDoctor(model_path=model_path) st.success("โœ… Model loaded successfully!") except Exception as e: st.error(f"โŒ Error loading model: {str(e)}") return False return True def generate_mock_sensor_data(): """Generate mock sensor data for demonstration""" now = datetime.now() data = { 'timestamp': now, 'temperature': round(random.uniform(20, 30), 1), 'humidity': round(random.uniform(40, 80), 1), 'light': round(random.uniform(200, 1000), 0), 'soil_moisture': round(random.uniform(30, 70), 1), 'ph': round(random.uniform(6.0, 7.5), 1), 'nutrients': round(random.uniform(60, 90), 1) } return data def check_alerts_for_plant(plant_name: str, sensor_data: dict): """Check for alerts based on sensor data for a specific plant""" # Convert sensor data to the format expected by alert system alert_sensor_data = { 'soil_moisture': sensor_data['soil_moisture'], 'temperature': sensor_data['temperature'], 'humidity': sensor_data['humidity'], 'sunlight': sensor_data['light'] / 100 # Convert to hours (simplified) } # Check for alerts new_alerts = alert_system.check_sensor_data(plant_name, alert_sensor_data) return new_alerts def get_sensor_history(plant: str, limit: int = 500): """Get historical sensor data for a plant from DB; fallback to mock if DB missing. Note: The ingest stores `ts` from the device; we use row order for recency and synthesize timestamps spaced uniformly for charting. """ # Try DB-backed history first for true realtime charts. if _db_exists() and plant: con = sqlite3.connect(DB_PATH) try: rows = con.execute( "SELECT ts, temperature, humidity, light, soil_moisture FROM readings WHERE plant=? ORDER BY rowid DESC LIMIT ?", (plant, limit), ).fetchall() if rows: rows = list(reversed(rows)) data_points = [] for idx, r in enumerate(rows): data_points.append({ 'timestamp': _parse_ts_to_datetime(r[0], fallback_index=len(rows) - idx), 'temperature': float(r[1]), 'humidity': float(r[2]), 'light': float(r[3]), 'soil_moisture': float(r[4]), }) return pd.DataFrame(data_points) except Exception: pass finally: con.close() # Fallback mock data when DB is unavailable/empty. base_time = datetime.now() - timedelta(hours=24) hours = 24 data_points = [] # Base values for realistic plant environment base_temp = 22 base_humidity = 60 base_light = 500 base_soil = 45 for i in range(hours): # Create realistic daily patterns hour_of_day = (base_time + timedelta(hours=i)).hour # Temperature: cooler at night, warmer during day if 6 <= hour_of_day <= 18: temp_variation = 3 * np.sin((hour_of_day - 6) * np.pi / 12) else: temp_variation = -2 temperature = base_temp + temp_variation + random.uniform(-1, 1) # Humidity: higher at night, lower during day if 6 <= hour_of_day <= 18: humidity_variation = 10 * np.cos((hour_of_day - 6) * np.pi / 12) else: humidity_variation = 5 humidity = base_humidity + humidity_variation + random.uniform(-3, 3) # Light: zero at night, peak during midday if 6 <= hour_of_day <= 18: light_variation = 300 * np.sin((hour_of_day - 6) * np.pi / 12) light = light_variation + random.uniform(-50, 50) else: light = random.uniform(0, 20) # Very low at night # Soil moisture: gradual decrease, occasional spikes (watering) soil_trend = -0.5 * i # Gradual decrease over time soil_spike = 15 if random.random() < 0.1 else 0 # 10% chance of watering spike soil_moisture = base_soil + soil_trend + soil_spike + random.uniform(-2, 2) soil_moisture = max(20, min(80, soil_moisture)) # Keep within realistic bounds data_points.append({ 'timestamp': base_time + timedelta(hours=i), 'temperature': round(temperature, 1), 'humidity': round(humidity, 1), 'light': round(max(0, light), 0), 'soil_moisture': round(soil_moisture, 1) }) return pd.DataFrame(data_points) def home_dashboard(): """Home page with sensor data dashboard""" st.markdown('

๐ŸŒฑ Smart Plant Doctor Dashboard

', unsafe_allow_html=True) st.subheader("๐Ÿ“Š Real-time Plant Monitoring") # If live DB exists, offer plant/device dropdown and use live reading live_mode = _db_exists() selected_plant = None current_data = None previous_data = None if live_mode: plants = get_plants_from_db() if plants: selected_plant = st.selectbox("Select plant/device", plants) live = get_latest_from_db(selected_plant) previous_data = get_previous_from_db(selected_plant) if live: current_data = { 'temperature': round(float(live['temperature']), 1), 'humidity': round(float(live['humidity']), 1), 'light': round(float(live['light']), 0), 'soil_moisture': round(float(live['soil_moisture']), 1), 'timestamp': _parse_ts_to_datetime(live['ts']) } # Fallback to mock data if no live reading if current_data is None: current_data = generate_mock_sensor_data() # Tag showing source if live_mode and selected_plant: st.caption(f"Live data from sensors DB โ€” plant: {selected_plant}") else: st.caption("Demo data (no live sensors DB found)") # Display metrics col1, col2, col3, col4 = st.columns(4) with col1: temp_change = random.uniform(-2, 2) if previous_data is not None: temp_change = current_data['temperature'] - float(previous_data['temperature']) temp_color = "normal" if current_data['temperature'] < 18 or current_data['temperature'] > 28: temp_color = "inverse" st.metric( "๐ŸŒก๏ธ Temperature", f"{current_data['temperature']}ยฐC", f"{temp_change:+.1f}ยฐC", delta_color=temp_color ) with col2: humidity_change = random.uniform(-5, 5) if previous_data is not None: humidity_change = current_data['humidity'] - float(previous_data['humidity']) humidity_color = "normal" if current_data['humidity'] < 40 or current_data['humidity'] > 80: humidity_color = "inverse" st.metric( "๐Ÿ’ง Humidity", f"{current_data['humidity']}%", f"{humidity_change:+.1f}%", delta_color=humidity_color ) with col3: light_change = random.uniform(-50, 50) if previous_data is not None: light_change = current_data['light'] - float(previous_data['light']) light_color = "normal" if current_data['light'] < 200 or current_data['light'] > 1000: light_color = "inverse" st.metric( "๐ŸŒž Light Intensity", f"{current_data['light']} lux", f"{light_change:+.0f} lux", delta_color=light_color ) with col4: moisture_change = random.uniform(-3, 3) if previous_data is not None: moisture_change = current_data['soil_moisture'] - float(previous_data['soil_moisture']) moisture_color = "normal" if current_data['soil_moisture'] < 30 or current_data['soil_moisture'] > 70: moisture_color = "inverse" st.metric( "๐ŸŒฑ Soil Moisture", f"{current_data['soil_moisture']}%", f"{moisture_change:+.1f}%", delta_color=moisture_color ) soil_label, soil_color, soil_icon = get_soil_condition(float(current_data['soil_moisture'])) st.markdown( f"

{soil_icon} Soil Status: {soil_label}

", unsafe_allow_html=True, ) # Additional metrics (health only) col7, = st.columns(1) with col7: health_score = calculate_health_score(current_data) health_color = "normal" if health_score < 80: health_color = "inverse" st.metric("๐Ÿ’š Plant Health", f"{health_score}%", "5%", delta_color=health_color) # Charts section st.subheader("๐Ÿ“ˆ Historical Data (Last 24 Hours)") # Get historical data from DB for selected plant when available df = get_sensor_history(selected_plant) if (live_mode and selected_plant) else get_sensor_history("Demo") # Temperature and Humidity chart col1, col2 = st.columns(2) with col1: fig_temp = px.line( df, x='timestamp', y='temperature', title='๐ŸŒก๏ธ Temperature Over Time', labels={'temperature': 'Temperature (ยฐC)', 'timestamp': 'Time'} ) fig_temp.add_hline(y=22, line_dash="dash", line_color="green", annotation_text="Ideal: 22ยฐC", annotation_position="top right") fig_temp.update_layout(height=300) st.plotly_chart(fig_temp, use_container_width=True) with col2: fig_humidity = px.line( df, x='timestamp', y='humidity', title='๐Ÿ’ง Humidity Over Time', labels={'humidity': 'Humidity (%)', 'timestamp': 'Time'} ) fig_humidity.add_hline(y=60, line_dash="dash", line_color="green", annotation_text="Ideal: 60%", annotation_position="top right") fig_humidity.update_layout(height=300) st.plotly_chart(fig_humidity, use_container_width=True) # Light and Soil Moisture chart col3, col4 = st.columns(2) with col3: fig_light = px.line( df, x='timestamp', y='light', title='๐ŸŒž Light Intensity Over Time', labels={'light': 'Light (lux)', 'timestamp': 'Time'} ) fig_light.add_hline(y=500, line_dash="dash", line_color="green", annotation_text="Ideal: 500 lux", annotation_position="top right") fig_light.update_layout(height=300) st.plotly_chart(fig_light, use_container_width=True) with col4: fig_moisture = px.line( df, x='timestamp', y='soil_moisture', title='๐ŸŒฑ Soil Moisture Over Time', labels={'soil_moisture': 'Moisture (%)', 'timestamp': 'Time'} ) fig_moisture.add_hline(y=45, line_dash="dash", line_color="green", annotation_text="Ideal: 45%", annotation_position="top right") fig_moisture.update_layout(height=300) st.plotly_chart(fig_moisture, use_container_width=True) # Plant Health Status st.subheader("๐ŸŒฟ Plant Health Status") health_col1, health_col2, health_col3 = st.columns(3) with health_col1: if health_score >= 85: status_color = "#d4edda" text_color = "#155724" status_text = "โœ… Excellent" elif health_score >= 70: status_color = "#fff3cd" text_color = "#856404" status_text = "โš ๏ธ Good" else: status_color = "#f8d7da" text_color = "#721c24" status_text = "โŒ Needs Attention" st.markdown(f"""

{status_text}

Overall Health Score: {health_score}%

""", unsafe_allow_html=True) with health_col2: recommendations = [] if current_data['soil_moisture'] < 30: recommendations.append("๐Ÿ’ง Water your plant") if current_data['light'] < 300: recommendations.append("๐ŸŒž Increase light exposure") if current_data['temperature'] > 28: recommendations.append("โ„๏ธ Provide shade") if current_data['humidity'] < 40: recommendations.append("๐Ÿ’จ Increase humidity") if not recommendations: recommendations.append("โœ… All conditions optimal") rec_text = "
".join(recommendations) st.markdown(f"""

๐Ÿ’ก Recommendations

{rec_text}

""", unsafe_allow_html=True) with health_col3: next_check = datetime.now() + timedelta(hours=6) st.markdown(f"""

๐Ÿ“… Next Check

{next_check.strftime('%H:%M')} today

""", unsafe_allow_html=True) # Alerts section st.markdown("---") st.subheader("๐Ÿšจ Plant Alerts") # Check for alerts with current sensor data plant_name = "Rose" # Default plant for demo new_alerts = check_alerts_for_plant(plant_name, current_data) # Get all active alerts active_alerts = alert_system.get_active_alerts() if active_alerts: # Display alerts by severity for severity in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]: severity_alerts = [a for a in active_alerts if a.severity == severity] if severity_alerts: alert_config = ALERT_LEVELS[severity] st.markdown(f"""

{alert_config['icon']} {severity} Alerts ({len(severity_alerts)})

""", unsafe_allow_html=True) for alert in severity_alerts: col1, col2, col3 = st.columns([3, 1, 1]) with col1: st.markdown(f"""

{alert.message}

{alert.timestamp.strftime('%H:%M:%S')}
""", unsafe_allow_html=True) with col2: if st.button("โœ“", key=f"ack_{alert.id}", help="Acknowledge"): alert_system.acknowledge_alert(alert.id) st.rerun() with col3: if st.button("โœ•", key=f"dismiss_{alert.id}", help="Dismiss"): alert_system.dismiss_alert(alert.id) st.rerun() # Alert management buttons col1, col2, col3 = st.columns([1, 1, 1]) with col1: if st.button("๐Ÿ”• Dismiss All", use_container_width=True): dismissed_count = alert_system.dismiss_all_alerts() st.success(f"Dismissed {dismissed_count} alerts") st.rerun() with col2: if st.button("๐Ÿ“Š Alert Summary", use_container_width=True): summary = alert_system.get_alert_summary() st.json(summary) with col3: if st.button("๐Ÿงน Cleanup Old", use_container_width=True): alert_system.cleanup_old_alerts(hours=1) st.success("Cleaned up old alerts") st.rerun() else: st.success("โœ… No active alerts - All plants are healthy!") # Auto-refresh button st.markdown("---") col1, col2, col3 = st.columns([1, 1, 1]) with col2: if st.button("๐Ÿ”„ Refresh Data", use_container_width=True): st.rerun() # Optional auto-refresh for near realtime dashboard updates. auto_refresh = st.sidebar.checkbox("Auto-refresh dashboard", value=True) refresh_seconds = st.sidebar.slider("Refresh interval (seconds)", min_value=2, max_value=30, value=5) if auto_refresh: if AUTOREFRESH_COMPONENT_AVAILABLE: st_autorefresh(interval=refresh_seconds * 1000, key="dashboard_autorefresh") else: st.caption("Tip: install streamlit-autorefresh for smoother non-blocking updates.") time.sleep(refresh_seconds) st.rerun() def disease_detection(): """Disease detection page""" st.markdown('

๐Ÿ”ฌ Plant Disease Detection

', unsafe_allow_html=True) # Load model if not load_model(): return st.subheader("๐Ÿ“ธ Upload Plant Image") # File uploader uploaded_file = st.file_uploader( "Choose an image of your plant", type=['jpg', 'jpeg', 'png'], help="Upload a clear image of the plant leaves or affected area" ) if uploaded_file is not None: # Display uploaded image col1, col2 = st.columns([1, 1]) with col1: st.markdown("### ๐Ÿ“ท Uploaded Image") image = Image.open(uploaded_file) st.image(image, caption="Your plant image", use_container_width=True) with col2: st.markdown("### ๐Ÿ” Analysis Results") # Make prediction with st.spinner("๐Ÿ”ฌ Analyzing plant image..."): try: # Save uploaded file temporarily temp_path = f"temp_{uploaded_file.name}" with open(temp_path, "wb") as f: f.write(uploaded_file.getbuffer()) # Get prediction result = st.session_state.doctor.predict(temp_path) # Clean up temp file os.remove(temp_path) if 'error' in result: st.markdown(f"""

โŒ Error

{result['error']}

""", unsafe_allow_html=True) else: # Display results confidence_color = "#d4edda" if result['confidence'] > 80 else "#fff3cd" if result['confidence'] > 60 else "#f8d7da" text_color = "#155724" if result['confidence'] > 80 else "#856404" if result['confidence'] > 60 else "#721c24" st.markdown(f"""

๐ŸŽฏ {result['output_format']}

Confidence: {result['confidence']:.2f}%

Class: {result['class_name']}

""", unsafe_allow_html=True) # Treatment recommendations if result['treatment']: treatment = result['treatment'] st.markdown("### ๐Ÿฉบ Treatment Recommendations") st.markdown(f"""

๐Ÿ“‹ Disease: {treatment['name']}

๐Ÿ” Symptoms: {treatment['symptoms']}

""", unsafe_allow_html=True) st.markdown("#### ๐Ÿ’Š Home Remedies:") for i, remedy in enumerate(treatment['home_remedies'], 1): st.markdown(f"**{i}.** {remedy}") st.markdown(f"""

๐Ÿ›ก๏ธ Prevention

{treatment['prevention']}

""", unsafe_allow_html=True) except Exception as e: st.markdown(f"""

โŒ Error

Error processing image: {str(e)}

""", unsafe_allow_html=True) else: # Show example images and instructions st.info("๐Ÿ‘† Please upload an image to get started with disease detection") st.subheader("๐Ÿ“‹ How to Use:") st.markdown(""" 1. **Take a clear photo** of your plant's leaves or affected area 2. **Upload the image** using the file uploader above 3. **Wait for analysis** - our AI will identify the disease 4. **Follow treatment recommendations** for plant recovery **๐Ÿ’ก Tips for better results:** - Use good lighting - Focus on the affected leaves - Avoid blurry or dark images - Include multiple leaves if possible """) # Show supported plants st.subheader("๐ŸŒฑ Supported Plants:") col1, col2, col3 = st.columns(3) with col1: st.markdown(""" **๐ŸŒฟ Aloe Vera** - Anthracnose - Leaf Spot - Rust - Sunburn - Healthy """) with col2: st.markdown(""" **๐ŸŒน Rose** - Black Spot - Powdery Mildew - Rust - Mosaic Virus - Downy Mildew - Insect Damage - Healthy """) with col3: st.markdown(""" **๐ŸŒบ Hibiscus** - Blight - Necrosis - Scorch - Healthy """) col4, col5, col6 = st.columns(3) with col4: st.markdown(""" **๐ŸŒผ Chrysanthemum** - Bacterial Leaf Spot - Septoria Leaf Spot - Healthy """) with col5: st.markdown(""" **๐ŸŒฟ Money Plant** - Bacterial Wilt - Chlorosis - Manganese Toxicity - Healthy """) with col6: st.markdown(""" **๐ŸŒพ Turmeric** - Aphid Infestation - Leaf Blotch - Leaf Necrosis - Leaf Spot - Healthy """) def about_page(): """About page""" st.markdown('

โ„น๏ธ About Smart Plant Doctor

', unsafe_allow_html=True) st.markdown(""" ## ๐ŸŒฑ What is Smart Plant Doctor? Smart Plant Doctor is an AI-powered plant care system that combines: - **Real-time sensor monitoring** of plant health parameters - **Computer vision** for disease detection and diagnosis - **Expert treatment recommendations** for plant recovery ## ๐Ÿ”ฌ How It Works ### Sensor Monitoring - Continuously tracks temperature, humidity, light, and soil moisture - Provides real-time alerts and recommendations - Historical data analysis for trend identification ### Disease Detection - Uses advanced MobileNetV2 deep learning model - Trained on 29 different plant disease classes - 92.37% accuracy in disease identification - Provides detailed treatment recommendations ## ๐ŸŒฟ Supported Plants Our AI model can identify diseases in: - **Aloe Vera** (5 disease types) - **Chrysanthemum** (3 disease types) - **Hibiscus** (4 disease types) - **Money Plant** (4 disease types) - **Rose** (8 disease types) - **Turmeric** (5 disease types) ## ๐Ÿ› ๏ธ Technology Stack - **AI Model**: PyTorch + MobileNetV2 - **Web Interface**: Streamlit - **Data Visualization**: Plotly - **Image Processing**: PIL/Pillow ## ๐Ÿ“Š Model Performance - **Accuracy**: 92.37% - **Classes**: 29 plant disease classes - **Input Size**: 224x224 pixels - **Training Data**: 45,895+ plant images ## ๐Ÿš€ Features ### Real-time Monitoring - Live sensor data visualization - Historical trend analysis - Health score calculation - Automated recommendations ### Disease Detection - Instant image analysis - High-accuracy predictions - Detailed treatment plans - Prevention strategies ## ๐Ÿ“ž Support For technical support or questions, please contact our team. """) def alerts_page(): """Dedicated alerts management page""" st.markdown('

๐Ÿšจ Plant Alerts Management

', unsafe_allow_html=True) # Alert summary summary = alert_system.get_alert_summary() col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Total Active Alerts", summary["total_active"]) with col2: st.metric("Critical Alerts", summary["by_severity"]["CRITICAL"]) with col3: st.metric("High Priority", summary["by_severity"]["HIGH"]) with col4: st.metric("Medium Priority", summary["by_severity"]["MEDIUM"]) st.markdown("---") # Alert management controls col1, col2, col3, col4 = st.columns(4) with col1: if st.button("๐Ÿ”„ Refresh Alerts", use_container_width=True): st.rerun() with col2: if st.button("๐Ÿ”• Dismiss All", use_container_width=True): dismissed_count = alert_system.dismiss_all_alerts() st.success(f"Dismissed {dismissed_count} alerts") st.rerun() with col3: if st.button("๐Ÿงน Cleanup Old", use_container_width=True): alert_system.cleanup_old_alerts(hours=1) st.success("Cleaned up old alerts") st.rerun() with col4: if st.button("๐Ÿ“Š Export Alerts", use_container_width=True): # Export alerts as JSON alerts_data = [] for alert in alert_system.alert_history: alerts_data.append({ "id": alert.id, "plant_name": alert.plant_name, "sensor_type": alert.sensor_type, "severity": alert.severity, "message": alert.message, "timestamp": alert.timestamp.isoformat(), "is_active": alert.is_active, "is_acknowledged": alert.is_acknowledged }) st.download_button( label="๐Ÿ“ฅ Download Alerts JSON", data=pd.DataFrame(alerts_data).to_csv(index=False), file_name=f"plant_alerts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", mime="text/csv" ) st.markdown("---") # Display all alerts active_alerts = alert_system.get_active_alerts() if active_alerts: st.subheader("๐Ÿ“‹ Active Alerts") # Filter options col1, col2, col3 = st.columns(3) with col1: severity_filter = st.selectbox( "Filter by Severity", ["All"] + list(ALERT_LEVELS.keys()), key="severity_filter" ) with col2: plant_filter = st.selectbox( "Filter by Plant", ["All"] + list(set(alert.plant_name for alert in active_alerts)), key="plant_filter" ) with col3: sensor_filter = st.selectbox( "Filter by Sensor", ["All"] + list(set(alert.sensor_type for alert in active_alerts)), key="sensor_filter" ) # Apply filters filtered_alerts = active_alerts if severity_filter != "All": filtered_alerts = [a for a in filtered_alerts if a.severity == severity_filter] if plant_filter != "All": filtered_alerts = [a for a in filtered_alerts if a.plant_name == plant_filter] if sensor_filter != "All": filtered_alerts = [a for a in filtered_alerts if a.sensor_type == sensor_filter] # Display filtered alerts if filtered_alerts: for alert in filtered_alerts: alert_config = ALERT_LEVELS[alert.severity] with st.container(): col1, col2, col3, col4 = st.columns([4, 1, 1, 1]) with col1: st.markdown(f"""

{alert_config['icon']} {alert.severity} - {alert.plant_name}

{alert.message}

Sensor: {alert.sensor_type} | Value: {alert.current_value} | Threshold: {alert.threshold_value} | Time: {alert.timestamp.strftime('%Y-%m-%d %H:%M:%S')}
""", unsafe_allow_html=True) with col2: if st.button("โœ“", key=f"ack_{alert.id}", help="Acknowledge"): alert_system.acknowledge_alert(alert.id) st.rerun() with col3: if st.button("โœ•", key=f"dismiss_{alert.id}", help="Dismiss"): alert_system.dismiss_alert(alert.id) st.rerun() with col4: if st.button("โ„น๏ธ", key=f"info_{alert.id}", help="More Info"): st.info(f""" **Alert Details:** - ID: {alert.id} - Plant: {alert.plant_name} - Sensor: {alert.sensor_type} - Current Value: {alert.current_value} - Threshold: {alert.threshold_value} - Severity: {alert.severity} - Created: {alert.timestamp.strftime('%Y-%m-%d %H:%M:%S')} - Acknowledged: {'Yes' if alert.is_acknowledged else 'No'} """) else: st.info("No alerts match the selected filters.") else: st.success("โœ… No active alerts - All plants are healthy!") # Alert history st.markdown("---") st.subheader("๐Ÿ“ˆ Alert History") if alert_system.alert_history: # Create a DataFrame for better display history_data = [] for alert in alert_system.alert_history[-50:]: # Show last 50 alerts history_data.append({ "Timestamp": alert.timestamp.strftime('%Y-%m-%d %H:%M:%S'), "Plant": alert.plant_name, "Sensor": alert.sensor_type, "Severity": alert.severity, "Message": alert.message, "Status": "Active" if alert.is_active else "Dismissed", "Acknowledged": "Yes" if alert.is_acknowledged else "No" }) df = pd.DataFrame(history_data) st.dataframe(df, use_container_width=True) else: st.info("No alert history available.") def main(): """Main application""" # Sidebar navigation st.sidebar.title("๐ŸŒฑ Smart Plant Doctor") st.sidebar.markdown("---") page = st.sidebar.selectbox( "Navigate", ["๐Ÿ  Home Dashboard", "๐Ÿ”ฌ Disease Detection", "๐Ÿšจ Alerts", "โ„น๏ธ About"] ) st.sidebar.markdown("---") # Model status if st.session_state.doctor: st.sidebar.success("โœ… AI Model Loaded") else: st.sidebar.info("๐Ÿ”„ AI Model Not Loaded") # Quick stats st.sidebar.markdown("### ๐Ÿ“Š Quick Stats") st.sidebar.metric("Model Accuracy", "92.37%") st.sidebar.metric("Supported Plants", "6") st.sidebar.metric("Disease Classes", "29") # Page routing if page == "๐Ÿ  Home Dashboard": home_dashboard() elif page == "๐Ÿ”ฌ Disease Detection": disease_detection() elif page == "๐Ÿšจ Alerts": alerts_page() elif page == "โ„น๏ธ About": about_page() if __name__ == "__main__": main()