| 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 |
|
|
| |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
|
|
| from src.config_manager import get_config |
| from datetime import datetime |
|
|
| |
| config = get_config() |
|
|
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| 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() |
|
|
| |
| 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'), |
| ) |
|
|
| |
| 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"<style>{f.read()}</style>", unsafe_allow_html=True) |
|
|
| |
| with st.sidebar: |
| st.markdown('### 🌍 Global Settings') |
|
|
| |
| 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') |
|
|
| |
| if data_summary and data_summary.get('year_range'): |
| min_year, max_year = data_summary['year_range'] |
| else: |
| min_year, max_year = 2000, 2025 |
|
|
| |
| if min_year == max_year: |
| |
| year_range = (min_year, max_year) |
| st.info(f'📅 Data available for year: {min_year} (single year dataset)') |
| else: |
| |
| 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)') |
|
|
| |
| 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) |
|
|
| |
| 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)') |
|
|
| |
| latest_year = year_range[1] |
| metrics = get_country_metrics(df, selected_country, latest_year) |
| filtered_df = filter_data(df, selected_country, year_range) |
|
|
| |
| 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: |
| |
| 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] |
|
|
| |
| if selected_length == 1500: |
| selected_type = 'professional' |
| elif selected_length == 800: |
| selected_type = 'policy' |
| else: |
| selected_type = '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, |
| '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): |
| |
| 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}") |
| |
| |
| with st.container(border=True): |
| st.markdown(st.session_state['current_report']) |
| |
| |
| 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" |
| ) |
|
|
| |
| st.divider() |
| st.markdown( |
| """ |
| <div class="footer"> |
| <p><b>Global SDG Tracker AI v2025.1</b></p> |
| <p>Developed by Senior AI & Environmental Systems Engineer</p> |
| <p>Data Source: <b>Sustainable Development Report 2025 (SDSN & Dublin University Press)</b> | Released: June 2025</p> |
| <p><i>Note: The 2025 data follows the latest UN SDG indicator framework and official SDSN computations.</i></p> |
| </div> |
| """, |
| unsafe_allow_html=True, |
| ) |
|
|