File size: 10,515 Bytes
d237759 b88006b d237759 b88006b d237759 b88006b 2a44b6c b88006b d237759 b88006b d237759 b88006b d237759 b88006b 2a44b6c b88006b 2a44b6c b88006b 2a44b6c b88006b 2a44b6c b88006b 2a44b6c b88006b 2a44b6c b88006b d237759 b88006b d237759 b88006b d237759 b88006b d237759 b88006b 2a44b6c b88006b d237759 b88006b d237759 b88006b d237759 b88006b d237759 b88006b d237759 b88006b d237759 b88006b 2a44b6c b88006b 2a44b6c b88006b 2a44b6c 84034c4 b88006b be724e1 4d7f47c b88006b 84034c4 b88006b 2a44b6c b88006b 2a44b6c 84034c4 b88006b 2a44b6c b88006b 2a44b6c b88006b d237759 b88006b d237759 b88006b d237759 b88006b 2a44b6c b88006b 2a44b6c b88006b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | 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"<style>{f.read()}</style>", 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 <!-- thought blocks -->) 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(
"""
<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,
)
|