sdgToPic / app.py
Song
right
4d7f47c
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,
)