import streamlit as st import pandas as pd from src.data_loader import ( load_sdg_data, get_country_list, filter_data, get_latest_data, get_country_metrics, get_data_summary, ) from src.viz_engine import ( create_world_map, create_radar_chart, create_trend_chart, create_detailed_trend_chart, ) from src.ai_engine import SDG_AI_Report_Engine import os import re # Define SCRIPT_DIR early SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) from src.config_manager import get_config from datetime import datetime # Initialize configuration config = get_config() # Force data path to prefer SDR2025 if available default_sdr2025 = os.path.join(SCRIPT_DIR, 'data', 'SDR2025-data.xlsx') DATA_PATH = config.get('data_sources.primary_data_path') or default_sdr2025 if not DATA_PATH or not os.path.exists(DATA_PATH): if os.path.exists(default_sdr2025): DATA_PATH = default_sdr2025 # Load AI configuration and engine config_valid = config.get('ai_engine.enabled', True) and (config.get('ai_engine.api_key') or config.get('ai_engine.base_url')) ai_engine = SDG_AI_Report_Engine(config.get('ai_engine.base_url'), config.get('ai_engine.api_key')) if config_valid else None # Use a fixed model as requested SELECTED_MODEL_KEY = 'azure-gpt-4.1' @st.cache_data(ttl=config.get('data_sources.cache_ttl_seconds', 3600)) def get_data(): df = load_sdg_data(DATA_PATH) summary = get_data_summary(df) return df, summary df, data_summary = get_data() # Page Config st.set_page_config( page_title=config.get('ui.page_title', 'Global SDG Tracker AI'), page_icon=config.get('ui.page_icon', '🌍'), layout=config.get('ui.layout', 'wide'), ) # Load CSS css_path = os.path.join(SCRIPT_DIR, 'assets', 'style.css') if os.path.exists(css_path): with open(css_path) as f: st.markdown(f"", unsafe_allow_html=True) # Sidebar with st.sidebar: st.markdown('### 🌍 Global Settings') # Country list now returns only countries in the latest year (SDR 2025) country_list = get_country_list(df) if not country_list: st.warning('No countries available in the data source. Please check your data configuration.') selected_country = st.selectbox('Select Country', [], key='country_select') else: selected_country = st.selectbox('Select Country', country_list, index=0, key='country_select') # Year range dynamic from data summary if data_summary and data_summary.get('year_range'): min_year, max_year = data_summary['year_range'] else: min_year, max_year = 2000, 2025 # Handle case where min_year == max_year (single year data) if min_year == max_year: # For single year data, allow selection of that year only year_range = (min_year, max_year) st.info(f'📅 Data available for year: {min_year} (single year dataset)') else: # For multi-year data, use the full range with 10-year window default_start = max(min_year, max_year - 10) year_range = st.slider('Year Range', min_year, max_year, (default_start, max_year), key='year_slider') st.divider() st.markdown('### 📊 Project Info') st.info('Global SDG Tracker AI v2025.1\nData: SDR 2025 (SDSN)') # Top-level navigation tab_names = ['Global Overview', 'Country Analysis', 'AI Report'] if 'active_tab' not in st.session_state: st.session_state['active_tab'] = tab_names[0] st.session_state['active_tab'] = st.radio('Navigation', tab_names, index=tab_names.index(st.session_state['active_tab']), horizontal=True, label_visibility='collapsed') active_tab = st.session_state['active_tab'] if active_tab == 'Global Overview': st.header('Global SDG Progress (SDR 2025)') latest_df = get_latest_data(df) fig_map = create_world_map(latest_df) st.plotly_chart(fig_map, use_container_width=True) st.subheader('Top Performers (Latest Year, SDR 2025)') top_10 = latest_df.sort_values('sdg_index_score', ascending=False).head(10)[['country', 'sdg_index_score']] st.table(top_10) elif active_tab == 'Country Analysis': st.header(f'Projected Performance: {selected_country} (SDR 2025)') latest_year = year_range[1] metrics = get_country_metrics(df, selected_country, latest_year) if not metrics: st.warning(f"⚠️ Data for '{selected_country}' in {latest_year} is not available or incomplete.") else: col1, col2, col3, col4 = st.columns(4) prev_year = latest_year - 1 prev_metrics = get_country_metrics(df, selected_country, prev_year) score_delta = (metrics.get('score', 0) - prev_metrics.get('score', 0)) if prev_metrics else 0 col1.metric('Latest SDG Score', f"{metrics.get('score', 0):.1f}", delta=f"{score_delta:+.1f}") col2.metric('Global Rank', f"{metrics.get('rank', 'N/A')} / {metrics.get('country_count', 'N/A')}") col3.metric('Global Average', f"{metrics.get('global_avg', 0):.1f}") diff = metrics.get('score', 0) - metrics.get('global_avg', 0) col4.metric('Performance vs Avg', f"{diff:+.1f}", delta_color='normal') filtered_df = filter_data(df, selected_country, year_range) col_left, col_right = st.columns([1, 1]) with col_left: st.subheader('SDG Goal Multi-dimensional Analysis') fig_radar = create_radar_chart(df, selected_country, latest_year) if fig_radar: st.plotly_chart(fig_radar, use_container_width=True) with col_right: st.subheader('Historical Trend') fig_trend = create_trend_chart(filtered_df) st.plotly_chart(fig_trend, use_container_width=True) st.subheader('Detailed Goals Trends') fig_det_trend = create_detailed_trend_chart(filtered_df) st.plotly_chart(fig_det_trend, use_container_width=True) # Export CSV st.divider() csv = filtered_df.to_csv(index=False).encode('utf-8') if not filtered_df.empty else b'' st.download_button(label='📥 Export Country Data to CSV', data=csv, file_name=f'SDG_Data_{selected_country}_{latest_year}.csv', mime='text/csv') elif active_tab == 'AI Report': st.header('🤖 AI-Powered Strategic Analysis (SDR 2025)') # Recompute metrics and filtered_df to avoid stale variables latest_year = year_range[1] metrics = get_country_metrics(df, selected_country, latest_year) filtered_df = filter_data(df, selected_country, year_range) # Defensive checks: if no metrics or no filtered data, stop and inform user if metrics is None or filtered_df.empty: st.error(f"⚠️ 國家 '{selected_country}' 在 {latest_year} 年 SDG 數據中不存在或無完整資料,請選擇其他國家(例如 Finland、Sweden、China 等)。") st.stop() if not config_valid: st.warning('⚠️ AI report functionality is not configured. Set environment variables to enable it.') else: # AI Settings Layout col_lang, col_depth = st.columns(2) with col_lang: report_lang = st.selectbox('Language / 報告語言', ['Chinese', 'English', 'Japanese'], key='lang_select') with col_depth: report_depth = st.selectbox('Report Depth / 報告深度', ['Short', 'Standard', 'Detailed'], index=0, key='depth_select') depth_map = {'Short': 300, 'Standard': 800, 'Detailed': 1500} selected_length = depth_map[report_depth] # Automatically determine Report Type based on Depth if selected_length == 1500: selected_type = 'professional' # Detailed -> Professional Report elif selected_length == 800: selected_type = 'policy' # Standard -> Policy Brief else: selected_type = 'summary' # Short -> Executive Summary if st.button('🤖 Generate Professional Report / 生成專業報告', help='點擊生成基於AI的專業SDG分析報告'): try: with st.spinner('AI is analyzing SDG data and generating strategic insights...'): meta = { 'country': selected_country, 'start_year': year_range[0], 'end_year': year_range[1], 'report_type': selected_type, # Auto-selected based on depth 'latest_score': f"{metrics.get('score', 'N/A'):.1f}" if isinstance(metrics.get('score'), (int, float)) else metrics.get('score', 'N/A'), 'rank': metrics.get('rank', 'N/A'), 'total_countries': metrics.get('country_count', 'N/A'), 'global_avg': f"{metrics.get('global_avg', 'N/A'):.1f}" if isinstance(metrics.get('global_avg'), (int, float)) else metrics.get('global_avg', 'N/A'), 'length': selected_length if 'selected_length' in locals() else 800 } report = ai_engine.generate_report(filtered_df, meta, language=report_lang) if isinstance(report, str): # Remove all HTML comments (like ) instead of splitting report = re.sub(r'', '', report, flags=re.DOTALL).strip() st.session_state['current_report'] = report except Exception as e: st.error(f"❌ AI報告生成失敗:{str(e)}") if 'current_report' in st.session_state: st.divider() st.subheader(f"📝 策略分析報告: {selected_country}") # Display the report in a container with st.container(border=True): st.markdown(st.session_state['current_report']) # Add download button report_text = st.session_state['current_report'] st.download_button( label="📥 下載完整報告 (Markdown)", data=report_text, file_name=f"SDG_Report_{selected_country}_{datetime.now().strftime('%Y%V%d')}.md", mime="text/markdown" ) # Footer st.divider() st.markdown( """ """, unsafe_allow_html=True, )