import streamlit as st import pandas as pd import plotly.graph_objects as go import json import os import importlib.util from env.environment import EcoGridEnv from models.schemas import GridAction from baseline import heuristic_agent, local_llm_agent, load_trained_model, LORA_DIR, is_lora_valid # Use wide mode with a custom icon st.set_page_config(page_title="EcoGrid Dashboard", layout="wide", page_icon="🌍") @st.cache_data def init_llm_model(): """Fast availability check (no heavyweight model load).""" required_files = ( "adapter_config.json", "adapter_model.safetensors", "tokenizer.json", "tokenizer_config.json", ) files_ok = is_lora_valid() deps_ok = ( importlib.util.find_spec("transformers") is not None and importlib.util.find_spec("peft") is not None and importlib.util.find_spec("torch") is not None ) return files_ok and deps_ok TRAINED_AVAILABLE = init_llm_model() def load_reward_curve(): try: if os.path.exists("./logs/reward_curve.json"): with open("./logs/reward_curve.json", "r") as f: return json.load(f) except: pass return [] def init_session(): if "env" not in st.session_state: st.session_state.env = EcoGridEnv() if "current_task" not in st.session_state: st.session_state.current_task = "medium" if "state" not in st.session_state: st.session_state.state = st.session_state.env.reset(task=st.session_state.current_task, seed=42) if "history" not in st.session_state: st.session_state.history = [] if "cumulative_reward" not in st.session_state: st.session_state.cumulative_reward = 0.0 if "trained_runtime_checked" not in st.session_state: st.session_state.trained_runtime_checked = False if "trained_runtime_ready" not in st.session_state: st.session_state.trained_runtime_ready = False if "trained_fallback_used" not in st.session_state: st.session_state.trained_fallback_used = False def step_env(agent_type): env = st.session_state.env state = st.session_state.state task = st.session_state.current_task if agent_type == "Random Agent": action = env.action_space.sample() action = GridAction(renewable_ratio=action[0], fossil_ratio=action[1], battery_action=action[2]) elif agent_type == "Heuristic Rule-Based": action = heuristic_agent(state, task) elif agent_type == "AI Agent (Trained LoRA)": if not st.session_state.trained_runtime_checked: # First time load attempt model, error = load_trained_model() st.session_state.trained_runtime_ready = (model is not None) st.session_state.trained_runtime_error = error if not st.session_state.trained_runtime_ready else None st.session_state.trained_runtime_checked = True if st.session_state.trained_runtime_ready: action = local_llm_agent(state, task) else: st.session_state.trained_fallback_used = True action = heuristic_agent(state, task) # Execute step try: result = env.step(action) st.session_state.state = result.observation st.session_state.cumulative_reward += result.reward # Save history for plotting log_entry = { "step": env.current_step, "demand": state.demand, "reward": result.reward, "cost_score": result.info["reward_breakdown"]["cost_score"], "carbon_score": result.info["reward_breakdown"]["carbon_score"], "stability_score": result.info["reward_breakdown"]["stability_score"], "emissions": result.info["carbon_emitted_step"] } st.session_state.history.append(log_entry) except Exception as e: st.error(f"Environment Error: {e}") init_session() # ─── THEME TOKENS ─── COLOR_TEXT = "#f8fafc" COLOR_MUTED = "#94a3b8" COLOR_GRID = "rgba(255, 255, 255, 0.05)" COLOR_PRIMARY = "#00f2fe" # Vibrant teal COLOR_SECONDARY = "#4facfe" # Soft blue COLOR_WARN = "#facc15" # Yellow COLOR_DANGER = "#ff4b4b" # Red/Pink COLOR_SUCCESS = "#00f260" # Green COLOR_PURPLE = "#c084fc" # Accent purple # ─── SIDEBAR CONTROL PANEL ─── with st.sidebar: st.markdown("""

⚡ CONTROL ROOM

EcoGrid Intelligence Unit

""", unsafe_allow_html=True) task_labels = {"easy": "Easy (No Battery, Flat Demand)", "medium": "Medium (Small Battery, Spikes)", "hard": "Hard (Carbon Cap, High Volatility)"} task = st.selectbox( "SIMULATION DIFFICULTY", ["easy", "medium", "hard"], index=1, format_func=lambda x: task_labels[x], help="Changes the weather volatility, demand curves, and carbon constraints." ) if task != st.session_state.current_task: st.session_state.current_task = task st.session_state.env = EcoGridEnv() st.session_state.state = st.session_state.env.reset(task=task, seed=42) st.session_state.history = [] st.session_state.cumulative_reward = 0.0 st.session_state.trained_runtime_checked = False st.session_state.trained_runtime_ready = False st.session_state.trained_fallback_used = False st.markdown("
", unsafe_allow_html=True) agent_options = ["Random Agent", "Heuristic Rule-Based"] if TRAINED_AVAILABLE: agent_options.append("AI Agent (Trained LoRA)") agent = st.radio( "ACTIVE INTELLIGENCE", agent_options, index=1, help="Select which intelligence is controlling the grid." ) if not TRAINED_AVAILABLE: st.markdown("""

⚠️ AI weights missing. LFS pull required for LoRA inference.

""", unsafe_allow_html=True) elif st.session_state.trained_fallback_used and not st.session_state.trained_runtime_ready: err_detail = st.session_state.get('trained_runtime_error', 'Unknown Error') st.markdown(f"""

🚨 Fallback Active.
{err_detail}

""", unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) col_btn1, col_btn2 = st.columns(2) with col_btn1: if st.button("▶ Step Once", use_container_width=True): step_env(agent) with col_btn2: if st.button("⏩ Run Full", use_container_width=True): while not st.session_state.env.is_done: step_env(agent) if st.button("🔄 Reset Simulation", use_container_width=True): st.session_state.env = EcoGridEnv() st.session_state.state = st.session_state.env.reset(task=task, seed=42) st.session_state.history = [] st.session_state.cumulative_reward = 0.0 st.session_state.trained_runtime_checked = False st.session_state.trained_runtime_ready = False st.session_state.trained_fallback_used = False # ─── MAIN UI HEADER ─── st.markdown("""
STABLE RELEASE v1.1.0

🌍 EcoGrid Intelligence

AI-Powered Sustainable Energy Grid Management

""", unsafe_allow_html=True) if TRAINED_AVAILABLE and st.session_state.trained_runtime_ready: st.markdown("""
TRAINED LORA ACTIVE
""", unsafe_allow_html=True) col_live, col_reward, col_emissions = st.columns(3) # ─── PANEL 1: LIVE GRID STATE ─── with col_live: with st.container(border=True): st.markdown('
📡 Live Grid State
', unsafe_allow_html=True) st.markdown('

Real-time supply and demand metrics.

', unsafe_allow_html=True) state = st.session_state.state # Timestep Metric ep_len = st.session_state.env.get_task_config(st.session_state.current_task)['episode_length'] progress_pct = (state.time_step / ep_len) * 100 st.markdown(f"""
Timestep Progress
{state.time_step} / {ep_len}
""", unsafe_allow_html=True) # Battery Gauge fig = go.Figure(go.Indicator( mode = "gauge+number", value = state.battery_level * 100, number = {'suffix': "%", 'font': {'color': COLOR_TEXT, 'size': 28, 'family': 'Outfit'}}, title = {'text': "Battery Charge State", 'font': {'size': 14, 'color': COLOR_MUTED}}, gauge = { 'axis': {'range': [0, 100], 'tickwidth': 1, 'tickcolor': COLOR_GRID}, 'bar': {'color': COLOR_PRIMARY, 'thickness': 0.25}, 'bgcolor': "rgba(0,0,0,0)", 'borderwidth': 0, 'steps': [ {'range': [0, 20], 'color': "rgba(255, 75, 75, 0.15)"}, {'range': [80, 100], 'color': "rgba(0, 242, 96, 0.15)"} ] } )) fig.update_layout(height=180, margin=dict(l=25, r=25, t=40, b=10), paper_bgcolor="rgba(0,0,0,0)", font={'family': 'Inter'}) st.plotly_chart(fig, use_container_width=True, config={'displayModeBar': False}) # Capacity Bars fig2 = go.Figure() fig2.add_trace(go.Bar(name='Demand', x=['DEMAND'], y=[state.demand], marker_color=COLOR_DANGER, opacity=0.9, marker_line_width=0, hoverinfo="y+name")) fig2.add_trace(go.Bar(name='Solar', x=['SOLAR'], y=[state.solar_capacity * 100], marker_color=COLOR_WARN, opacity=0.9, marker_line_width=0, hoverinfo="y+name")) fig2.add_trace(go.Bar(name='Wind', x=['WIND'], y=[state.wind_capacity * 100], marker_color=COLOR_SECONDARY, opacity=0.9, marker_line_width=0, hoverinfo="y+name")) fig2.update_layout( height=200, margin=dict(l=10, r=10, t=10, b=20), barmode='group', showlegend=False, paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", yaxis=dict(gridcolor=COLOR_GRID, showticklabels=False, zeroline=False), xaxis=dict(tickfont=dict(color=COLOR_MUTED, size=11, family='Outfit'), zeroline=False), font=dict(family='Inter') ) st.plotly_chart(fig2, use_container_width=True, config={'displayModeBar': False}) # ─── PANEL 2: AGENT PERFORMANCE ─── with col_reward: with st.container(border=True): st.markdown('
📈 Performance Analytics
', unsafe_allow_html=True) st.markdown('

Multi-objective optimization scoring.

', unsafe_allow_html=True) if st.session_state.history: df = pd.DataFrame(st.session_state.history) # Area Chart for Overall Reward fig3 = go.Figure() fig3.add_trace(go.Scatter( x=df['step'], y=df['reward'], mode='lines', fill='tozeroy', name='Step Reward', line=dict(color=COLOR_PRIMARY, width=3), fillcolor='rgba(0, 242, 254, 0.15)', hovertemplate="Step %{x}
Reward: %{y:.2f}" )) fig3.update_layout( title=dict(text="CUMULATIVE STEP REWARD", font=dict(color=COLOR_MUTED, size=11, family='Outfit')), height=190, margin=dict(l=10, r=10, t=35, b=10), paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, zeroline=False), yaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, range=[0, 1.05], zeroline=False), font=dict(family='Inter') ) st.plotly_chart(fig3, use_container_width=True, config={'displayModeBar': False}) # Breakdown Lines fig4 = go.Figure() fig4.add_trace(go.Scatter(x=df['step'], y=df['cost_score'], name='Cost', line=dict(color=COLOR_WARN, width=2, dash='dot'), hovertemplate="%{y:.2f}")) fig4.add_trace(go.Scatter(x=df['step'], y=df['carbon_score'], name='Eco', line=dict(color=COLOR_SUCCESS, width=2), hovertemplate="%{y:.2f}")) fig4.add_trace(go.Scatter(x=df['step'], y=df['stability_score'], name='Grid', line=dict(color=COLOR_PURPLE, width=2), hovertemplate="%{y:.2f}")) fig4.update_layout( title=dict(text="OBJECTIVE BREAKDOWN", font=dict(color=COLOR_MUTED, size=11, family='Outfit')), height=210, margin=dict(l=10, r=10, t=35, b=10), legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1, font=dict(color=COLOR_MUTED, size=10)), paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, zeroline=False), yaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, range=[0, 1.05], zeroline=False), font=dict(family='Inter'), hovermode="x unified" ) st.plotly_chart(fig4, use_container_width=True, config={'displayModeBar': False}) else: st.info("Initiate simulation to view live performance data.") st.markdown("
", unsafe_allow_html=True) # ─── PANEL 3: CARBON & TRAINING ─── with col_emissions: with st.container(border=True): st.markdown('
🌱 Eco Constraints
', unsafe_allow_html=True) st.markdown('

Carbon limits and model convergence.

', unsafe_allow_html=True) # Carbon Budget Gauge max_budget = st.session_state.env.get_task_config(st.session_state.current_task)['carbon_budget'] current_budget = state.carbon_budget_remaining is_strict = st.session_state.env.get_task_config(st.session_state.current_task)['carbon_strict'] budget_color = COLOR_SUCCESS if current_budget > max_budget * 0.2 else COLOR_DANGER if current_budget < 0: budget_color = "#8b0000" fig5 = go.Figure(go.Indicator( mode = "gauge+number", value = max(0, current_budget), number = {'valueformat': ".0f", 'font': {'color': COLOR_TEXT, 'size': 28, 'family': 'Outfit'}}, title = {'text': f"Carbon Budget (kgCO2) {'STRICT' if is_strict else ''}", 'font': {'size': 14, 'color': COLOR_MUTED}}, gauge = { 'axis': {'range': [0, max_budget], 'tickwidth': 1, 'tickcolor': COLOR_GRID}, 'bar': {'color': budget_color, 'thickness': 0.25}, 'bgcolor': "rgba(0,0,0,0)", 'borderwidth': 0, 'steps': [ {'range': [0, max_budget * 0.2], 'color': "rgba(255, 75, 75, 0.15)"} ] } )) fig5.update_layout(height=180, margin=dict(l=25, r=25, t=40, b=10), paper_bgcolor="rgba(0,0,0,0)", font=dict(family='Inter')) st.plotly_chart(fig5, use_container_width=True, config={'displayModeBar': False}) # Training Convergence st.markdown("
🧠 GRPO Training Convergence
", unsafe_allow_html=True) curve_data = load_reward_curve() if not curve_data and os.path.exists("training_metrics.json"): try: with open("training_metrics.json", "r") as f: metrics = json.load(f) # Extract history and filter for valid reward entries raw_history = metrics.get("log_history", []) curve_data = [ {"step": e["step"], "reward": e["reward"]} for e in raw_history if "step" in e and "reward" in e ] except Exception: curve_data = [] if curve_data: df_curve = pd.DataFrame(curve_data) fig6 = go.Figure() fig6.add_trace(go.Scatter( x=df_curve['step'], y=df_curve['reward'], mode='lines', line=dict(color=COLOR_PRIMARY, width=2), fill='tozeroy', fillcolor='rgba(0, 242, 254, 0.08)' )) fig6.update_layout( height=190, margin=dict(l=10, r=10, t=10, b=20), paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, title=dict(text="TRAINING STEPS", font=dict(size=10)), zeroline=False), yaxis=dict(gridcolor=COLOR_GRID, color=COLOR_MUTED, title=dict(text="REWARD", font=dict(size=10)), zeroline=False), font=dict(family='Inter') ) st.plotly_chart(fig6, use_container_width=True, config={'displayModeBar': False}) else: if os.path.exists("docs/reward_curve.png"): st.image("docs/reward_curve.png", caption="Historical Training Performance") else: st.info("Convergence telemetry unavailable.") st.markdown(""" """, unsafe_allow_html=True) # ─── GLOBAL STYLING (Rich Aesthetics & Glassmorphism) ─── st.markdown(""" """, unsafe_allow_html=True)