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,
)