Song commited on
Commit ·
d237759
1
Parent(s): 1e507d5
hi
Browse files- DEPLOYMENT.md +174 -0
- Dockerfile +24 -0
- README.md +173 -6
- app.py +273 -0
- data/sdg_index_2000-2023.csv +0 -0
- requirements.txt +18 -0
- scripts/merge_data.py +92 -0
- src/__pycache__/ai_engine.cpython-313.pyc +0 -0
- src/__pycache__/data_loader.cpython-313.pyc +0 -0
- src/__pycache__/export_engine.cpython-313.pyc +0 -0
- src/__pycache__/viz_engine.cpython-313.pyc +0 -0
- src/ai_engine.py +72 -0
- src/data_loader.py +59 -0
- src/export_engine.py +69 -0
- src/viz_engine.py +263 -0
DEPLOYMENT.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🌍 Global SDG Tracker AI - HuggingFace Spaces 部署指南
|
| 2 |
+
|
| 3 |
+
## 📋 概述
|
| 4 |
+
|
| 5 |
+
Global SDG Tracker AI 是一個專業的永續發展目標分析儀表板,現已完全適配 HuggingFace Spaces 部署。本指南將幫助您快速將應用程式部署到 HuggingFace Spaces。
|
| 6 |
+
|
| 7 |
+
## ✅ 部署準備狀態
|
| 8 |
+
|
| 9 |
+
- ✅ Dockerfile 配置完成
|
| 10 |
+
- ✅ 動態端口支援 (使用 $PORT 環境變數)
|
| 11 |
+
- ✅ Streamlit 應用程式優化
|
| 12 |
+
- ✅ 依賴套件完整性檢查
|
| 13 |
+
- ✅ 部署測試驗證通過 (100%)
|
| 14 |
+
|
| 15 |
+
## 🚀 部署步驟
|
| 16 |
+
|
| 17 |
+
### 步驟 1: 準備 GitHub 倉庫
|
| 18 |
+
|
| 19 |
+
1. **創建 GitHub 倉庫**
|
| 20 |
+
```bash
|
| 21 |
+
# 初始化 Git 倉庫
|
| 22 |
+
git init
|
| 23 |
+
git add .
|
| 24 |
+
git commit -m "Initial commit: Global SDG Tracker AI ready for HuggingFace Spaces"
|
| 25 |
+
|
| 26 |
+
# 連接到 GitHub 倉庫
|
| 27 |
+
git remote add origin https://github.com/your-username/global-sdg-tracker-ai.git
|
| 28 |
+
git branch -M main
|
| 29 |
+
git push -u origin main
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### 步驟 2: 在 HuggingFace Spaces 創建新空間
|
| 33 |
+
|
| 34 |
+
1. **登入 HuggingFace**
|
| 35 |
+
- 訪問 [https://huggingface.co/spaces](https://huggingface.co/spaces)
|
| 36 |
+
- 使用您的 GitHub 帳號登入
|
| 37 |
+
|
| 38 |
+
2. **創建新空間**
|
| 39 |
+
- 點擊 "Create new Space"
|
| 40 |
+
- 填寫空間資訊:
|
| 41 |
+
- **Space name**: `global-sdg-tracker-ai`
|
| 42 |
+
- **License**: `mit`
|
| 43 |
+
- **SDK**: `Docker`
|
| 44 |
+
- **Hardware**: `CPU basic` (免費方案)
|
| 45 |
+
- **Visibility**: `Public`
|
| 46 |
+
|
| 47 |
+
3. **連接 GitHub 倉庫**
|
| 48 |
+
- 選擇 "Connect a GitHub repository"
|
| 49 |
+
- 選擇您剛創建的 `global-sdg-tracker-ai` 倉庫
|
| 50 |
+
- 點擊 "Create a Space"
|
| 51 |
+
|
| 52 |
+
### 步驟 3: 自動建構和部署
|
| 53 |
+
|
| 54 |
+
1. **自動觸發建構**
|
| 55 |
+
- HuggingFace 會自動檢測到 Dockerfile
|
| 56 |
+
- 開始建構 Docker 映像
|
| 57 |
+
- 整個過程約需要 3-5 分鐘
|
| 58 |
+
|
| 59 |
+
2. **監控建構進度**
|
| 60 |
+
- 在 Spaces 頁面查看建構日誌
|
| 61 |
+
- 確保所有依賴套件成功安裝
|
| 62 |
+
- 檢查應用程式啟動是否正常
|
| 63 |
+
|
| 64 |
+
### 步驟 4: 環境變數配置 (可選)
|
| 65 |
+
|
| 66 |
+
如果您需要配置 API 金鑰或其他環境變數:
|
| 67 |
+
|
| 68 |
+
1. **在 HuggingFace Spaces 設置中**
|
| 69 |
+
- 前往您的 Space 頁面
|
| 70 |
+
- 點擊 "Settings" 標籤
|
| 71 |
+
- 在 "Repository secrets" 部分添加環境變數:
|
| 72 |
+
- `LITELLM_API_KEY`: 您的 API 金鑰
|
| 73 |
+
- `LITELLM_BASE_URL`: API 基礎 URL
|
| 74 |
+
- `OPENAI_API_KEY`: OpenAI API 金鑰 (如果使用)
|
| 75 |
+
|
| 76 |
+
2. **重新部署**
|
| 77 |
+
- 添加環境變數後,點擊 "Restart" 按鈕
|
| 78 |
+
- 應用程式將使用新的環境變數重新啟動
|
| 79 |
+
|
| 80 |
+
## 🔧 技術規格
|
| 81 |
+
|
| 82 |
+
### Dockerfile 規格
|
| 83 |
+
- **基礎映像**: `python:3.9-slim`
|
| 84 |
+
- **端口**: 動態 (使用 $PORT 環境變數)
|
| 85 |
+
- **啟動命令**: `streamlit run app.py --server.port=$PORT --server.address=0.0.0.0 --server.headless=true`
|
| 86 |
+
|
| 87 |
+
### 依賴套件
|
| 88 |
+
- Streamlit >= 1.35.0
|
| 89 |
+
- Pandas >= 2.0.0
|
| 90 |
+
- Plotly >= 5.18.0
|
| 91 |
+
- Python-pptx >= 0.6.21
|
| 92 |
+
- Reportlab >= 3.6.0
|
| 93 |
+
- Openpyxl >= 3.1.0
|
| 94 |
+
- 其他核心依賴套件
|
| 95 |
+
|
| 96 |
+
### 系統要求
|
| 97 |
+
- **記憶體**: 最少 2GB RAM
|
| 98 |
+
- **存儲**: 約 500MB (包含所有依賴)
|
| 99 |
+
- **網路**: 需要網路連接以載入外部資源
|
| 100 |
+
|
| 101 |
+
## 🧪 本地測試
|
| 102 |
+
|
| 103 |
+
在部署到 HuggingFace Spaces 之前,您可以先在本地測試:
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
# 進入專案目錄
|
| 107 |
+
cd global-sdg-tracker-ai
|
| 108 |
+
|
| 109 |
+
# 運行部署測試
|
| 110 |
+
python deploy_test.py
|
| 111 |
+
|
| 112 |
+
# 本地運行應用程式
|
| 113 |
+
streamlit run app.py --server.port=8501
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
## 🔍 故障排除
|
| 117 |
+
|
| 118 |
+
### 常見問題
|
| 119 |
+
|
| 120 |
+
1. **建構失敗**
|
| 121 |
+
- 檢查 `requirements.txt` 檔案格式
|
| 122 |
+
- 確認所有套件版本相容性
|
| 123 |
+
- 查看 HuggingFace 建構日誌
|
| 124 |
+
|
| 125 |
+
2. **應用程式啟動失敗**
|
| 126 |
+
- 確認 `app.py` 沒有語法錯誤
|
| 127 |
+
- 檢查模組導入路徑
|
| 128 |
+
- 驗證數據檔案是否存在
|
| 129 |
+
|
| 130 |
+
3. **端口問題**
|
| 131 |
+
- 確保使用 `$PORT` 環境變數
|
| 132 |
+
- 不要在 Dockerfile 中硬編碼端口號
|
| 133 |
+
|
| 134 |
+
4. **依賴套件錯誤**
|
| 135 |
+
- 執行 `python deploy_test.py` 檢查完整性
|
| 136 |
+
- 確認所有必需的套件都在 `requirements.txt` 中
|
| 137 |
+
|
| 138 |
+
### 獲取幫助
|
| 139 |
+
|
| 140 |
+
- **GitHub Issues**: [https://github.com/your-repo/global-sdg-tracker-ai/issues](https://github.com/your-repo/global-sdg-tracker-ai/issues)
|
| 141 |
+
- **HuggingFace 社群**: [https://discuss.huggingface.co/](https://discuss.huggingface.co/)
|
| 142 |
+
|
| 143 |
+
## 📈 效能優化
|
| 144 |
+
|
| 145 |
+
### 建構時間優化
|
| 146 |
+
- Dockerfile 使用多階段建構
|
| 147 |
+
- 依賴套件分層快取
|
| 148 |
+
- 使用 slim 基礎映像
|
| 149 |
+
|
| 150 |
+
### 運行時優化
|
| 151 |
+
- 啟用 Streamlit 快取機制
|
| 152 |
+
- 使用 `st.cache_data` 減少數據載入時間
|
| 153 |
+
- 適當的記憶體管理
|
| 154 |
+
|
| 155 |
+
## 🔐 安全考慮
|
| 156 |
+
|
| 157 |
+
- **API 金鑰**: 使用 HuggingFace 環境變數而非硬編碼
|
| 158 |
+
- **資料隱私**: 確保敏感數據不會提交到版本控制
|
| 159 |
+
- **依賴套件**: 定期更新依賴套件以修補安全漏洞
|
| 160 |
+
|
| 161 |
+
## 📞 支援
|
| 162 |
+
|
| 163 |
+
如需技術支援或報告問題,請:
|
| 164 |
+
|
| 165 |
+
1. 執行 `python deploy_test.py` 並分享結果
|
| 166 |
+
2. 提供 HuggingFace Spaces 建構日誌
|
| 167 |
+
3. 描述具體的錯誤訊息和重現步驟
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
**版本**: 1.0.0
|
| 172 |
+
**更新日期**: 2025-12-27
|
| 173 |
+
**相容性**: HuggingFace Spaces Docker SDK
|
| 174 |
+
**測試狀態**: ✅ 所有測試通過 (100%)
|
Dockerfile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用官方 Python 基礎映像
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# 設置工作目錄
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 升級 pip
|
| 8 |
+
RUN pip install --no-cache-dir --upgrade pip
|
| 9 |
+
|
| 10 |
+
# 複製需求檔案
|
| 11 |
+
COPY requirements.txt .
|
| 12 |
+
|
| 13 |
+
# 安裝 Python 依賴套件
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
# 複製應用程式碼
|
| 17 |
+
COPY . .
|
| 18 |
+
|
| 19 |
+
# 暴露 HuggingFace Spaces 動態端口
|
| 20 |
+
EXPOSE $PORT
|
| 21 |
+
|
| 22 |
+
# 設定啟動命令
|
| 23 |
+
# 動態使用 HuggingFace Spaces 分配的端口
|
| 24 |
+
CMD ["streamlit", "run", "app.py", "--server.port=$PORT", "--server.address=0.0.0.0", "--server.headless=true"]
|
README.md
CHANGED
|
@@ -1,10 +1,177 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
emoji: 🌍
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Global SDG Tracker AI
|
| 3 |
emoji: 🌍
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
app_port: 7860
|
| 8 |
---
|
| 9 |
|
| 10 |
+
# Global SDG Tracker AI
|
| 11 |
+
|
| 12 |
+
<div align="center">
|
| 13 |
+
|
| 14 |
+

|
| 15 |
+
|
| 16 |
+
**專業的聯合國永續發展目標分析儀表板,由AI驅動**
|
| 17 |
+
|
| 18 |
+
[演示](https://huggingface.co/spaces) • [文檔](#使用說明) • [報告問題](#貢獻)
|
| 19 |
+
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
## 📖 專案簡介
|
| 23 |
+
|
| 24 |
+
Global SDG Tracker AI 是一個專業的永續發展目標 (SDG) 分析儀表板,整合了先進的AI技術,為用戶提供深入的國家和全球SDG績效分析。
|
| 25 |
+
|
| 26 |
+
### ✨ 主要功能
|
| 27 |
+
|
| 28 |
+
- **📊 全球視覺化儀表板**: 互動式世界地圖和圖表展示全球SDG績效
|
| 29 |
+
- **🌏 國家深度分析**: 選擇特定國家進行詳細的SDG目標分析
|
| 30 |
+
- **🤖 AI戰略報告**: 使用先進的語言模型生成專業SDG分析報告
|
| 31 |
+
- **📈 趨勢分析**: 多年份SDG數據趨勢和預測
|
| 32 |
+
- **📥 多格式匯出**: 支持PowerPoint和PDF格式報告匯出
|
| 33 |
+
|
| 34 |
+
### 🎯 支援的SDG目標
|
| 35 |
+
|
| 36 |
+
- SDG 1: 消除貧窮
|
| 37 |
+
- SDG 2: 零飢餓
|
| 38 |
+
- SDG 3: 健康與福祉
|
| 39 |
+
- SDG 4: 優質教育
|
| 40 |
+
- SDG 5: 性別平等
|
| 41 |
+
- SDG 6: 清潔飲水和衛生設施
|
| 42 |
+
- SDG 7: 清潔能源
|
| 43 |
+
- SDG 8: 體面工作和經濟增長
|
| 44 |
+
- SDG 9: 產業、創新和基礎設施
|
| 45 |
+
- SDG 10: 減少不平等
|
| 46 |
+
- SDG 11: 可持續城市和社區
|
| 47 |
+
- SDG 12: 負責任消費和生產
|
| 48 |
+
- SDG 13: 氣候行動
|
| 49 |
+
- SDG 14: 水下生物
|
| 50 |
+
- SDG 15: 陸地生物
|
| 51 |
+
- SDG 16: 和平、正義與強有力機構
|
| 52 |
+
- SDG 17: 促進目標實現的夥伴關係
|
| 53 |
+
|
| 54 |
+
## 🚀 部署說明
|
| 55 |
+
|
| 56 |
+
### HuggingFace Spaces 部署
|
| 57 |
+
|
| 58 |
+
本應用程式已優化用於 HuggingFace Spaces 部署:
|
| 59 |
+
|
| 60 |
+
1. ** Fork 本專案**: 點擊右上角的 "Fork" 按鈕
|
| 61 |
+
2. **自動部署**: HuggingFace 會自動檢測 Dockerfile 並開始建構
|
| 62 |
+
3. **等待建構**: 首次部署可能需要 5-10 分鐘
|
| 63 |
+
4. **訪問應用**: 部署完成後即可通過生成的URL訪問
|
| 64 |
+
|
| 65 |
+
### 本地開發
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
# 克隆專案
|
| 69 |
+
git clone <your-repo-url>
|
| 70 |
+
cd global-sdg-tracker-ai
|
| 71 |
+
|
| 72 |
+
# 安裝依賴
|
| 73 |
+
pip install -r requirements.txt
|
| 74 |
+
|
| 75 |
+
# 運行應用
|
| 76 |
+
streamlit run app.py
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## 📋 系統需求
|
| 80 |
+
|
| 81 |
+
- Python 3.9+
|
| 82 |
+
- 至少 2GB RAM
|
| 83 |
+
- 穩定的網路連接(用於AI模型訪問)
|
| 84 |
+
|
| 85 |
+
## 🛠️ 技術架構
|
| 86 |
+
|
| 87 |
+
### 核心技術棧
|
| 88 |
+
|
| 89 |
+
- **前端框架**: Streamlit
|
| 90 |
+
- **數據處理**: Pandas, NumPy
|
| 91 |
+
- **視覺化**: Plotly
|
| 92 |
+
- **AI模型**: OpenAI API兼容模型
|
| 93 |
+
- **報告生成**: python-pptx, fpdf2
|
| 94 |
+
|
| 95 |
+
### 項目結構
|
| 96 |
+
|
| 97 |
+
```
|
| 98 |
+
global-sdg-tracker-ai/
|
| 99 |
+
├── app.py # 主應用程式
|
| 100 |
+
├── requirements.txt # Python依賴
|
| 101 |
+
├── Dockerfile # 容器化配置
|
| 102 |
+
├── data/ # SDG數據文件
|
| 103 |
+
│ └── sdg_index_2000-2023.csv
|
| 104 |
+
├── src/ # 核心模組
|
| 105 |
+
│ ├── data_loader.py # 數據加載處理
|
| 106 |
+
│ ├── viz_engine.py # 視覺化引擎
|
| 107 |
+
│ ├── ai_engine.py # AI報告引擎
|
| 108 |
+
│ └── export_engine.py # 報告匯出引擎
|
| 109 |
+
└── assets/ # 靜態資源
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## 📖 使用說明
|
| 113 |
+
|
| 114 |
+
### 1. 選擇國家
|
| 115 |
+
在左側邊欄中選擇要分析的國家。
|
| 116 |
+
|
| 117 |
+
### 2. 設定年份範圍
|
| 118 |
+
調整滑桿選擇分析的年份範圍(2000-2023)。
|
| 119 |
+
|
| 120 |
+
### 3. AI配置
|
| 121 |
+
- 選擇偏好的語言模型
|
| 122 |
+
- 選擇報告生成語言(中文、英文、日文、越南文)
|
| 123 |
+
|
| 124 |
+
### 4. 查看分析結果
|
| 125 |
+
- **全球概覽**: 查看世界地圖和頂級表現者
|
| 126 |
+
- **國家深入分析**: 雷達圖、趨勢圖和詳細指標
|
| 127 |
+
- **AI戰略報告**: 點擊生成專業分析報告
|
| 128 |
+
|
| 129 |
+
### 5. 匯出報告
|
| 130 |
+
生成的報告可以匯出為PowerPoint或PDF格式。
|
| 131 |
+
|
| 132 |
+
## 🔧 環境變數
|
| 133 |
+
|
| 134 |
+
本應用程式支持以下環境變數:
|
| 135 |
+
|
| 136 |
+
- `LITELLM_BASE_URL`: AI模型API端點
|
| 137 |
+
- `LITELLM_API_KEY`: AI模型API密鑰
|
| 138 |
+
- `PORT`: HuggingFace Spaces動態端口(自動設定)
|
| 139 |
+
|
| 140 |
+
## 🤝 貢獻
|
| 141 |
+
|
| 142 |
+
歡迎提交Issue和Pull Request來改進此專案!
|
| 143 |
+
|
| 144 |
+
### 開發指南
|
| 145 |
+
|
| 146 |
+
1. Fork 專案
|
| 147 |
+
2. 創建特性分支 (`git checkout -b feature/AmazingFeature`)
|
| 148 |
+
3. 提交變更 (`git commit -m 'Add some AmazingFeature'`)
|
| 149 |
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
| 150 |
+
5. 開啟Pull Request
|
| 151 |
+
|
| 152 |
+
## 📄 授權
|
| 153 |
+
|
| 154 |
+
本專案採用 MIT 授權 - 詳見 [LICENSE](LICENSE) 文件。
|
| 155 |
+
|
| 156 |
+
## 🙏 致謝
|
| 157 |
+
|
| 158 |
+
- 聯合國永續發展目標數據
|
| 159 |
+
- HuggingFace 社群
|
| 160 |
+
- Streamlit 開發團隊
|
| 161 |
+
- 所有貢獻者
|
| 162 |
+
|
| 163 |
+
## 📞 聯絡方式
|
| 164 |
+
|
| 165 |
+
- **開發者**: 高級AI與環境系統工程師
|
| 166 |
+
- **GitHub**: [your-repo/global-sdg-tracker-ai](https://github.com/your-repo/global-sdg-tracker-ai)
|
| 167 |
+
- **問題回報**: [GitHub Issues](https://github.com/your-repo/global-sdg-tracker-ai/issues)
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
<div align="center">
|
| 172 |
+
|
| 173 |
+
**🌟 如果這個專案對您有幫助,請給我們一個星標!**
|
| 174 |
+
|
| 175 |
+
[⬆ 回到頂部](#global-sdg-tracker-ai)
|
| 176 |
+
|
| 177 |
+
</div>
|
app.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from src.data_loader import load_sdg_data, get_country_list, filter_data, get_latest_data, get_country_metrics
|
| 4 |
+
from src.viz_engine import create_world_map, create_radar_chart, create_trend_chart, create_detailed_trend_chart
|
| 5 |
+
from src.ai_engine import SDG_AI_Report_Engine
|
| 6 |
+
from src.export_engine import generate_pptx, generate_pdf
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# Get port from environment (HuggingFace Spaces requirement)
|
| 10 |
+
PORT = int(os.environ.get('PORT', 8501))
|
| 11 |
+
|
| 12 |
+
# Page Config
|
| 13 |
+
st.set_page_config(
|
| 14 |
+
page_title="Global SDG Tracker AI",
|
| 15 |
+
page_icon="🌍",
|
| 16 |
+
layout="wide",
|
| 17 |
+
initial_sidebar_state="expanded",
|
| 18 |
+
menu_items={
|
| 19 |
+
'Get Help': 'https://github.com/your-repo/global-sdg-tracker-ai',
|
| 20 |
+
'Report a bug': 'https://github.com/your-repo/global-sdg-tracker-ai/issues',
|
| 21 |
+
'About': '# Global SDG Tracker AI\n\nA professional SDG analysis dashboard powered by AI.'
|
| 22 |
+
}
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# Configure server settings for HuggingFace Spaces
|
| 26 |
+
st.server.port = PORT
|
| 27 |
+
st.server.address = "0.0.0.0"
|
| 28 |
+
|
| 29 |
+
# Custom Styling - Enhanced Premium Design
|
| 30 |
+
st.markdown("""
|
| 31 |
+
<style>
|
| 32 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
| 33 |
+
|
| 34 |
+
* {
|
| 35 |
+
font-family: 'Inter', sans-serif;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.main {
|
| 39 |
+
background: linear-gradient(135deg, #f8f9fa 0%, #e8f4f8 100%);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.stMetric {
|
| 43 |
+
background: linear-gradient(145deg, #ffffff 0%, #f0f9ff 100%);
|
| 44 |
+
padding: 20px;
|
| 45 |
+
border-radius: 16px;
|
| 46 |
+
box-shadow: 0 4px 20px rgba(0, 100, 150, 0.1);
|
| 47 |
+
border: 1px solid rgba(0, 150, 200, 0.1);
|
| 48 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.stMetric:hover {
|
| 52 |
+
transform: translateY(-2px);
|
| 53 |
+
box-shadow: 0 8px 30px rgba(0, 100, 150, 0.15);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
h1 {
|
| 57 |
+
color: #0d4f6c;
|
| 58 |
+
font-weight: 700;
|
| 59 |
+
background: linear-gradient(90deg, #0d4f6c, #2980b9);
|
| 60 |
+
-webkit-background-clip: text;
|
| 61 |
+
-webkit-text-fill-color: transparent;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
h2, h3 {
|
| 65 |
+
color: #1f3b4d;
|
| 66 |
+
font-weight: 600;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.stTabs [data-baseweb="tab-list"] {
|
| 70 |
+
gap: 8px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.stTabs [data-baseweb="tab"] {
|
| 74 |
+
background-color: #f0f7fa;
|
| 75 |
+
border-radius: 10px;
|
| 76 |
+
padding: 10px 20px;
|
| 77 |
+
font-weight: 500;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.stTabs [aria-selected="true"] {
|
| 81 |
+
background: linear-gradient(135deg, #0096c7 0%, #023e8a 100%) !important;
|
| 82 |
+
color: white !important;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.stButton > button {
|
| 86 |
+
background: linear-gradient(135deg, #0096c7 0%, #023e8a 100%);
|
| 87 |
+
color: white;
|
| 88 |
+
border: none;
|
| 89 |
+
border-radius: 10px;
|
| 90 |
+
padding: 10px 24px;
|
| 91 |
+
font-weight: 600;
|
| 92 |
+
transition: all 0.3s ease;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.stButton > button:hover {
|
| 96 |
+
transform: translateY(-2px);
|
| 97 |
+
box-shadow: 0 4px 15px rgba(0, 150, 200, 0.4);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.stDownloadButton > button {
|
| 101 |
+
background: linear-gradient(135deg, #27ae60 0%, #1e8449 100%);
|
| 102 |
+
border-radius: 10px;
|
| 103 |
+
font-weight: 600;
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
div[data-testid="stSidebar"] {
|
| 107 |
+
background: linear-gradient(180deg, #0d4f6c 0%, #154360 100%);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
div[data-testid="stSidebar"] .stSelectbox label,
|
| 111 |
+
div[data-testid="stSidebar"] .stSlider label,
|
| 112 |
+
div[data-testid="stSidebar"] h1,
|
| 113 |
+
div[data-testid="stSidebar"] h2,
|
| 114 |
+
div[data-testid="stSidebar"] h3 {
|
| 115 |
+
color: white !important;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.footer {
|
| 119 |
+
text-align: center;
|
| 120 |
+
padding: 20px;
|
| 121 |
+
color: #6c757d;
|
| 122 |
+
font-size: 0.85em;
|
| 123 |
+
}
|
| 124 |
+
</style>
|
| 125 |
+
""", unsafe_allow_html=True)
|
| 126 |
+
|
| 127 |
+
# Initialization - Use environment variables for security
|
| 128 |
+
BASE_URL = os.environ.get("LITELLM_BASE_URL", "https://litellm-ekkks8gsocw.dgx-coolify.apmic.ai/")
|
| 129 |
+
API_KEY = os.environ.get("LITELLM_API_KEY", "sk-eT_04m428oAPUD5kUmIhVA")
|
| 130 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 131 |
+
DATA_PATH = os.path.join(SCRIPT_DIR, "data", "sdg_index_2000-2023.csv")
|
| 132 |
+
|
| 133 |
+
# Load Data
|
| 134 |
+
@st.cache_data
|
| 135 |
+
def get_data():
|
| 136 |
+
if os.path.exists(DATA_PATH):
|
| 137 |
+
return load_sdg_data(DATA_PATH)
|
| 138 |
+
|
| 139 |
+
# Fallback for demo: create sample data if file doesn't exist
|
| 140 |
+
countries = ["United States", "China", "Japan", "Germany", "United Kingdom", "France", "Taiwan", "Vietnam"]
|
| 141 |
+
data = []
|
| 142 |
+
for c in countries:
|
| 143 |
+
for y in range(2000, 2024):
|
| 144 |
+
base_score = 60 + (y - 2000) * 0.5
|
| 145 |
+
row = {
|
| 146 |
+
"country": c,
|
| 147 |
+
"year": y,
|
| 148 |
+
"sdg_index_score": base_score + (0.5 if c == "Taiwan" else 0)
|
| 149 |
+
}
|
| 150 |
+
for i in range(1, 18):
|
| 151 |
+
row[f"goal_{i}_score"] = min(100, 50 + (y - 2000) * 1.2 + (i * 1.1))
|
| 152 |
+
data.append(row)
|
| 153 |
+
return pd.DataFrame(data)
|
| 154 |
+
|
| 155 |
+
df = get_data()
|
| 156 |
+
ai_engine = SDG_AI_Report_Engine(BASE_URL, API_KEY)
|
| 157 |
+
|
| 158 |
+
# Sidebar
|
| 159 |
+
with st.sidebar:
|
| 160 |
+
st.image("https://upload.wikimedia.org/wikipedia/commons/d/df/UN_Sustainable_Development_Goals_logo.png", width=100)
|
| 161 |
+
st.title("Settings")
|
| 162 |
+
|
| 163 |
+
country_list = get_country_list(df)
|
| 164 |
+
# Default to China if available, otherwise first country
|
| 165 |
+
default_index = country_list.index("China") if "China" in country_list else 0
|
| 166 |
+
selected_country = st.selectbox("🌏 Select Country", country_list, index=default_index)
|
| 167 |
+
|
| 168 |
+
year_range = st.slider("📅 Select Year Range", 2000, 2023, (2015, 2023))
|
| 169 |
+
|
| 170 |
+
st.divider()
|
| 171 |
+
st.subheader("AI Configuration")
|
| 172 |
+
model_options = ai_engine.get_available_models()
|
| 173 |
+
selected_model_key = st.selectbox("LLM Model", options=list(model_options.keys()), format_func=lambda x: model_options[x])
|
| 174 |
+
|
| 175 |
+
report_lang = st.selectbox("Report Language", ["Chinese", "English", "Japanese", "Vietnamese"])
|
| 176 |
+
|
| 177 |
+
# Main Content
|
| 178 |
+
st.title("🌍 Global SDG Tracker AI")
|
| 179 |
+
st.markdown("### Professional Sustainable Development Goal Analysis Dashboard")
|
| 180 |
+
|
| 181 |
+
tab1, tab2, tab3 = st.tabs(["Overview (Global)", "Country Deep Dive", "AI Strategic Report"])
|
| 182 |
+
|
| 183 |
+
with tab1:
|
| 184 |
+
st.header("Global SDG Progress")
|
| 185 |
+
latest_df = get_latest_data(df)
|
| 186 |
+
fig_map = create_world_map(latest_df)
|
| 187 |
+
st.plotly_chart(fig_map, use_container_width=True)
|
| 188 |
+
|
| 189 |
+
st.subheader("Top Performers (Latest Year)")
|
| 190 |
+
top_10 = latest_df.sort_values("sdg_index_score", ascending=False).head(10)[["country", "sdg_index_score"]]
|
| 191 |
+
st.table(top_10)
|
| 192 |
+
|
| 193 |
+
with tab2:
|
| 194 |
+
st.header(f"Projected Performance: {selected_country}")
|
| 195 |
+
|
| 196 |
+
# KPI Cards
|
| 197 |
+
latest_year = year_range[1]
|
| 198 |
+
metrics = get_country_metrics(df, selected_country, latest_year)
|
| 199 |
+
|
| 200 |
+
if metrics:
|
| 201 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 202 |
+
col1.metric("Latest SDG Score", f"{metrics['score']:.1f}")
|
| 203 |
+
col2.metric("Global Rank", f"{metrics['rank']} / {metrics['country_count']}")
|
| 204 |
+
col3.metric("Global Average", f"{metrics['global_avg']:.1f}")
|
| 205 |
+
diff = metrics['score'] - metrics['global_avg']
|
| 206 |
+
col4.metric("Performance vs Avg", f"{diff:+.1f}", delta_color="normal")
|
| 207 |
+
|
| 208 |
+
# Visualizations
|
| 209 |
+
col_left, col_right = st.columns([1, 1])
|
| 210 |
+
|
| 211 |
+
filtered_df = filter_data(df, selected_country, year_range)
|
| 212 |
+
|
| 213 |
+
with col_left:
|
| 214 |
+
st.subheader("SDG Goal Multi-dimensional Analysis")
|
| 215 |
+
fig_radar = create_radar_chart(df, selected_country, latest_year)
|
| 216 |
+
if fig_radar:
|
| 217 |
+
st.plotly_chart(fig_radar, use_container_width=True)
|
| 218 |
+
|
| 219 |
+
with col_right:
|
| 220 |
+
st.subheader("Historical Trend")
|
| 221 |
+
fig_trend = create_trend_chart(filtered_df)
|
| 222 |
+
st.plotly_chart(fig_trend, use_container_width=True)
|
| 223 |
+
|
| 224 |
+
st.subheader("Detailed Goals Trends")
|
| 225 |
+
fig_det_trend = create_detailed_trend_chart(filtered_df)
|
| 226 |
+
st.plotly_chart(fig_det_trend, use_container_width=True)
|
| 227 |
+
|
| 228 |
+
with tab3:
|
| 229 |
+
st.header("🤖 AI-Powered Strategic Analysis")
|
| 230 |
+
|
| 231 |
+
if st.button("Generate Professional Report"):
|
| 232 |
+
with st.spinner("AI is analyzing SDG data and generating strategic insights..."):
|
| 233 |
+
meta = {
|
| 234 |
+
'country': selected_country,
|
| 235 |
+
'start_year': year_range[0],
|
| 236 |
+
'end_year': year_range[1],
|
| 237 |
+
'latest_score': f"{metrics['score']:.1f}" if metrics else "N/A",
|
| 238 |
+
'rank': metrics['rank'] if metrics else "N/A",
|
| 239 |
+
'total_countries': metrics['country_count'] if metrics else "N/A",
|
| 240 |
+
'global_avg': f"{metrics['global_avg']:.1f}" if metrics else "N/A"
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
report = ai_engine.generate_report(filtered_df, meta, language=report_lang, model=selected_model_key)
|
| 244 |
+
st.session_state['current_report'] = report
|
| 245 |
+
|
| 246 |
+
if 'current_report' in st.session_state:
|
| 247 |
+
st.markdown(st.session_state['current_report'])
|
| 248 |
+
|
| 249 |
+
st.divider()
|
| 250 |
+
st.subheader("Export Results")
|
| 251 |
+
col_ex1, col_ex2 = st.columns(2)
|
| 252 |
+
|
| 253 |
+
with col_ex1:
|
| 254 |
+
pptx_data = generate_pptx(st.session_state['current_report'], selected_country)
|
| 255 |
+
st.download_button(
|
| 256 |
+
label="📥 Download PowerPoint Presentation",
|
| 257 |
+
data=pptx_data,
|
| 258 |
+
file_name=f"SDG_Report_{selected_country}.pptx",
|
| 259 |
+
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
with col_ex2:
|
| 263 |
+
pdf_data = generate_pdf(st.session_state['current_report'], selected_country)
|
| 264 |
+
st.download_button(
|
| 265 |
+
label="📥 Download PDF Report",
|
| 266 |
+
data=pdf_data,
|
| 267 |
+
file_name=f"SDG_Report_{selected_country}.pdf",
|
| 268 |
+
mime="application/pdf"
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
# Footer
|
| 272 |
+
st.divider()
|
| 273 |
+
st.caption("Developed by Senior AI & Environmental Systems Engineer | Data Source: Sustainable Development Report (2000-2023)")
|
data/sdg_index_2000-2023.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --------- Core ---------
|
| 2 |
+
streamlit>=1.35.0
|
| 3 |
+
pandas>=2.0.0
|
| 4 |
+
plotly>=5.18.0
|
| 5 |
+
numpy>=1.24.0
|
| 6 |
+
|
| 7 |
+
# --------- AI ---------
|
| 8 |
+
openai>=1.3.0
|
| 9 |
+
|
| 10 |
+
# --------- Export ---------
|
| 11 |
+
python-pptx>=0.6.21
|
| 12 |
+
fpdf2>=2.7.4
|
| 13 |
+
reportlab>=3.6.0
|
| 14 |
+
openpyxl>=3.1.0
|
| 15 |
+
|
| 16 |
+
# --------- Utils ---------
|
| 17 |
+
requests>=2.31.0
|
| 18 |
+
scipy>=1.10.0
|
scripts/merge_data.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
SDG Data Merger Script
|
| 4 |
+
|
| 5 |
+
This script merges historical SDG data (2000-2022) with the latest 2023 report data.
|
| 6 |
+
The output is a combined CSV file ready for the SDG Tracker dashboard.
|
| 7 |
+
|
| 8 |
+
Data Sources:
|
| 9 |
+
- sdg_index_2000-2022.csv: Historical SDG Index data
|
| 10 |
+
- sustainable_development_report_2023.csv: Latest SDG Report 2023
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import pandas as pd
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
def merge_sdg_data(historical_path, latest_path, output_path):
|
| 17 |
+
"""
|
| 18 |
+
Merge historical SDG data with latest 2023 data.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
historical_path: Path to the 2000-2022 historical data
|
| 22 |
+
latest_path: Path to the 2023 report data
|
| 23 |
+
output_path: Output path for merged data
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
print("Loading historical data (2000-2022)...")
|
| 27 |
+
df_historical = pd.read_csv(historical_path, encoding='utf-8-sig')
|
| 28 |
+
|
| 29 |
+
print("Loading 2023 data...")
|
| 30 |
+
df_2023 = pd.read_csv(latest_path, encoding='utf-8-sig')
|
| 31 |
+
|
| 32 |
+
# Rename 2023 data columns to match historical format
|
| 33 |
+
# 2023 uses 'overall_score' instead of 'sdg_index_score'
|
| 34 |
+
df_2023 = df_2023.rename(columns={'overall_score': 'sdg_index_score'})
|
| 35 |
+
|
| 36 |
+
# Add year column to 2023 data
|
| 37 |
+
df_2023['year'] = 2023
|
| 38 |
+
|
| 39 |
+
# Select only matching columns
|
| 40 |
+
common_columns = [
|
| 41 |
+
'country_code', 'country', 'year', 'sdg_index_score',
|
| 42 |
+
'goal_1_score', 'goal_2_score', 'goal_3_score', 'goal_4_score',
|
| 43 |
+
'goal_5_score', 'goal_6_score', 'goal_7_score', 'goal_8_score',
|
| 44 |
+
'goal_9_score', 'goal_10_score', 'goal_11_score', 'goal_12_score',
|
| 45 |
+
'goal_13_score', 'goal_14_score', 'goal_15_score', 'goal_16_score',
|
| 46 |
+
'goal_17_score'
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
# Filter to common columns
|
| 50 |
+
df_historical_clean = df_historical[common_columns].copy()
|
| 51 |
+
|
| 52 |
+
# Handle 2023 data - keep only matching columns
|
| 53 |
+
df_2023_columns = [col for col in common_columns if col in df_2023.columns]
|
| 54 |
+
df_2023_clean = df_2023[df_2023_columns].copy()
|
| 55 |
+
|
| 56 |
+
# Merge datasets
|
| 57 |
+
df_merged = pd.concat([df_historical_clean, df_2023_clean], ignore_index=True)
|
| 58 |
+
|
| 59 |
+
# Sort by country and year
|
| 60 |
+
df_merged = df_merged.sort_values(['country', 'year'])
|
| 61 |
+
|
| 62 |
+
# Save merged data
|
| 63 |
+
df_merged.to_csv(output_path, index=False, encoding='utf-8-sig')
|
| 64 |
+
|
| 65 |
+
print(f"✅ Merged data saved to: {output_path}")
|
| 66 |
+
print(f"📊 Total records: {len(df_merged)}")
|
| 67 |
+
print(f"📅 Year range: {df_merged['year'].min()} - {df_merged['year'].max()}")
|
| 68 |
+
print(f"🌍 Countries: {df_merged['country'].nunique()}")
|
| 69 |
+
|
| 70 |
+
return df_merged
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 74 |
+
project_root = os.path.dirname(script_dir)
|
| 75 |
+
data_dir = os.path.join(project_root, 'data')
|
| 76 |
+
parent_dir = os.path.dirname(project_root)
|
| 77 |
+
|
| 78 |
+
# Create data directory if not exists
|
| 79 |
+
os.makedirs(data_dir, exist_ok=True)
|
| 80 |
+
|
| 81 |
+
# Define paths
|
| 82 |
+
historical_path = os.path.join(parent_dir, 'sdg_index_2000-2022.csv')
|
| 83 |
+
latest_path = os.path.join(parent_dir, 'sustainable_development_report_2023.csv')
|
| 84 |
+
output_path = os.path.join(data_dir, 'sdg_index_2000-2023.csv')
|
| 85 |
+
|
| 86 |
+
# Run merge
|
| 87 |
+
if os.path.exists(historical_path) and os.path.exists(latest_path):
|
| 88 |
+
merge_sdg_data(historical_path, latest_path, output_path)
|
| 89 |
+
else:
|
| 90 |
+
print("❌ Error: Source data files not found!")
|
| 91 |
+
print(f"Looking for: {historical_path}")
|
| 92 |
+
print(f"Looking for: {latest_path}")
|
src/__pycache__/ai_engine.cpython-313.pyc
ADDED
|
Binary file (3.89 kB). View file
|
|
|
src/__pycache__/data_loader.cpython-313.pyc
ADDED
|
Binary file (2.74 kB). View file
|
|
|
src/__pycache__/export_engine.cpython-313.pyc
ADDED
|
Binary file (3.72 kB). View file
|
|
|
src/__pycache__/viz_engine.cpython-313.pyc
ADDED
|
Binary file (8.93 kB). View file
|
|
|
src/ai_engine.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from openai import OpenAI
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
class SDG_AI_Report_Engine:
|
| 5 |
+
def __init__(self, base_url, api_key):
|
| 6 |
+
self.client = OpenAI(
|
| 7 |
+
base_url=base_url,
|
| 8 |
+
api_key=api_key
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
def generate_report(self, country_data, meta_info, language="Chinese", model="Llama-3.3-70B-Instruct-FP8"):
|
| 12 |
+
"""
|
| 13 |
+
Generate a professional SDG assessment report.
|
| 14 |
+
"""
|
| 15 |
+
# Prepare context data
|
| 16 |
+
data_summary = country_data.to_dict(orient='records')
|
| 17 |
+
|
| 18 |
+
system_prompt = f"""
|
| 19 |
+
You are a senior Environmental Consultant and AI Applications Expert specializing in UN SDG tracking.
|
| 20 |
+
Your task is to write a professional, positive, and insightful SDG progress report for {meta_info['country']}.
|
| 21 |
+
The report should be written in {language}.
|
| 22 |
+
Tone: Professional, expert, strategic, and constructive.
|
| 23 |
+
Format: Markdown.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
user_prompt = f"""
|
| 27 |
+
Generate an SDG assessment report based on the following data for {meta_info['country']} ({meta_info['start_year']} - {meta_info['end_year']}).
|
| 28 |
+
|
| 29 |
+
Recent SDG Metrics:
|
| 30 |
+
{json.dumps(data_summary[-2:], indent=2)}
|
| 31 |
+
|
| 32 |
+
Global Comparison (Latest Year):
|
| 33 |
+
- Score: {meta_info['latest_score']}
|
| 34 |
+
- Global Rank: {meta_info['rank']} / {meta_info['total_countries']}
|
| 35 |
+
- Global Average: {meta_info['global_avg']}
|
| 36 |
+
|
| 37 |
+
Structure:
|
| 38 |
+
1. Executive Summary: High-level overview of the country's sustainable development.
|
| 39 |
+
2. Overall Progress Overview: Analysis of the trend from {meta_info['start_year']} to {meta_info['end_year']}.
|
| 40 |
+
3. Top 3 Highlights: Goals where the country is performing best or showing significant improvement.
|
| 41 |
+
4. Top 3 Challenges: Goals requiring urgent attention (specifically mention environment-related ones like SDG 13 if applicable).
|
| 42 |
+
5. Global & Peer Comparison: How the country stands compared to global averages.
|
| 43 |
+
6. Strategic Recommendations (3-5 items): Concrete, actionable steps for improvement.
|
| 44 |
+
7. Future Scenarios (5-year forecast):
|
| 45 |
+
- Business-as-usual scenario.
|
| 46 |
+
- Accelerated action scenario.
|
| 47 |
+
|
| 48 |
+
Use Markdown headers, bullet points, and bold text for readability.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
response = self.client.chat.completions.create(
|
| 53 |
+
model=model,
|
| 54 |
+
messages=[
|
| 55 |
+
{"role": "system", "content": system_prompt},
|
| 56 |
+
{"role": "user", "content": user_prompt}
|
| 57 |
+
],
|
| 58 |
+
temperature=0.7,
|
| 59 |
+
max_tokens=2000
|
| 60 |
+
)
|
| 61 |
+
return response.choices[0].message.content
|
| 62 |
+
except Exception as e:
|
| 63 |
+
return f"Error generating report: {str(e)}"
|
| 64 |
+
|
| 65 |
+
def get_available_models(self):
|
| 66 |
+
return {
|
| 67 |
+
"Llama-3.3-70B-Instruct-FP8": "Best Quality (Default)",
|
| 68 |
+
"gemma-3-27b-it": "Balanced Quality",
|
| 69 |
+
"Llama-4-Scout-17B-16E-Instruct": "Fast Generation",
|
| 70 |
+
"Mistral-Small-24B-Instruct-2501": "Structured Output",
|
| 71 |
+
"azure-grok-3": "Grok Style"
|
| 72 |
+
}
|
src/data_loader.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
def load_sdg_data(file_path):
|
| 5 |
+
"""
|
| 6 |
+
Load and clean the SDG Kaggle CSV data.
|
| 7 |
+
"""
|
| 8 |
+
try:
|
| 9 |
+
df = pd.read_csv(file_path)
|
| 10 |
+
|
| 11 |
+
# Basic cleaning: remove whitespace from column names if any
|
| 12 |
+
df.columns = [c.strip() for c in df.columns]
|
| 13 |
+
|
| 14 |
+
# Ensure year is integer
|
| 15 |
+
df['year'] = df['year'].astype(int)
|
| 16 |
+
|
| 17 |
+
# Handle missing values if any (fill with NaN or 0 depending on context)
|
| 18 |
+
# For scores, 0 might not be accurate, but for visualization NaN is fine
|
| 19 |
+
|
| 20 |
+
return df
|
| 21 |
+
except Exception as e:
|
| 22 |
+
print(f"Error loading data: {e}")
|
| 23 |
+
return None
|
| 24 |
+
|
| 25 |
+
def get_country_list(df):
|
| 26 |
+
return sorted(df['country'].unique().tolist())
|
| 27 |
+
|
| 28 |
+
def filter_data(df, country, year_range):
|
| 29 |
+
mask = (df['country'] == country) & (df['year'] >= year_range[0]) & (df['year'] <= year_range[1])
|
| 30 |
+
return df[mask].sort_values('year')
|
| 31 |
+
|
| 32 |
+
def get_latest_data(df):
|
| 33 |
+
"""Returns the latest year's data for all countries."""
|
| 34 |
+
latest_year = df['year'].max()
|
| 35 |
+
return df[df['year'] == latest_year]
|
| 36 |
+
|
| 37 |
+
def get_country_metrics(df, country, year):
|
| 38 |
+
"""
|
| 39 |
+
Get metrics for a specific country in a specific year.
|
| 40 |
+
"""
|
| 41 |
+
country_data = df[(df['country'] == country) & (df['year'] == year)]
|
| 42 |
+
if country_data.empty:
|
| 43 |
+
return None
|
| 44 |
+
|
| 45 |
+
# Calculate global average for that year
|
| 46 |
+
global_avg = df[df['year'] == year]['sdg_index_score'].mean()
|
| 47 |
+
|
| 48 |
+
# Get rank
|
| 49 |
+
year_data = df[df['year'] == year].copy()
|
| 50 |
+
year_data['rank'] = year_data['sdg_index_score'].rank(ascending=False, method='min')
|
| 51 |
+
country_rank = year_data[year_data['country'] == country]['rank'].values[0]
|
| 52 |
+
|
| 53 |
+
metrics = {
|
| 54 |
+
'score': country_data['sdg_index_score'].values[0],
|
| 55 |
+
'rank': int(country_rank),
|
| 56 |
+
'global_avg': global_avg,
|
| 57 |
+
'country_count': len(year_data)
|
| 58 |
+
}
|
| 59 |
+
return metrics
|
src/export_engine.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pptx import Presentation
|
| 2 |
+
from pptx.util import Inches, Pt
|
| 3 |
+
from fpdf import FPDF
|
| 4 |
+
import io
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
def generate_pptx(report_content, country_name):
|
| 8 |
+
"""
|
| 9 |
+
Generate a PowerPoint presentation from the report content.
|
| 10 |
+
"""
|
| 11 |
+
prs = Presentation()
|
| 12 |
+
|
| 13 |
+
# Title Slide
|
| 14 |
+
slide_layout = prs.slide_layouts[0]
|
| 15 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 16 |
+
title = slide.shapes.title
|
| 17 |
+
subtitle = slide.placeholders[1]
|
| 18 |
+
title.text = f"SDG Progress Report: {country_name}"
|
| 19 |
+
subtitle.text = "Generated by Global SDG Tracker AI\nStrategic Analysis and Forecast"
|
| 20 |
+
|
| 21 |
+
# Split report into sections based on headers
|
| 22 |
+
sections = re.split(r'#{1,3}\s+', report_content)
|
| 23 |
+
|
| 24 |
+
for section in sections[1:]: # Skip the first empty split if any
|
| 25 |
+
lines = section.strip().split('\n')
|
| 26 |
+
header = lines[0]
|
| 27 |
+
body = '\n'.join(lines[1:])
|
| 28 |
+
|
| 29 |
+
slide_layout = prs.slide_layouts[1] # Title and Content
|
| 30 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 31 |
+
slide.shapes.title.text = header
|
| 32 |
+
|
| 33 |
+
tf = slide.placeholders[1].text_frame
|
| 34 |
+
tf.text = body[:500] # Limit content per slide for simplicity
|
| 35 |
+
tf.word_wrap = True
|
| 36 |
+
|
| 37 |
+
binary_output = io.BytesIO()
|
| 38 |
+
prs.save(binary_output)
|
| 39 |
+
return binary_output.getvalue()
|
| 40 |
+
|
| 41 |
+
class PDFReport(FPDF):
|
| 42 |
+
def header(self):
|
| 43 |
+
self.set_font('helvetica', 'B', 12)
|
| 44 |
+
self.cell(0, 10, 'Global SDG Tracker AI - Professional Assessment', 0, 1, 'C')
|
| 45 |
+
self.ln(5)
|
| 46 |
+
|
| 47 |
+
def footer(self):
|
| 48 |
+
self.set_y(-15)
|
| 49 |
+
self.set_font('helvetica', 'I', 8)
|
| 50 |
+
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
|
| 51 |
+
|
| 52 |
+
def generate_pdf(report_content, country_name):
|
| 53 |
+
"""
|
| 54 |
+
Generate a PDF from the report content.
|
| 55 |
+
Note: For CJK characters, a Unicode font would be needed.
|
| 56 |
+
Simplifying here for English/Western, but mentioning font support.
|
| 57 |
+
"""
|
| 58 |
+
pdf = PDFReport()
|
| 59 |
+
pdf.add_page()
|
| 60 |
+
pdf.set_font("helvetica", size=11)
|
| 61 |
+
|
| 62 |
+
# We strip some markdown characters for basic PDF output
|
| 63 |
+
clean_text = report_content.replace('**', '').replace('* ', '- ').replace('### ', '')
|
| 64 |
+
|
| 65 |
+
# PDF generation logic for multi-language usually requires .ttf fonts
|
| 66 |
+
# For this demo, we use default fonts. In production, we'd use pdf.add_font()
|
| 67 |
+
pdf.multi_cell(0, 10, clean_text)
|
| 68 |
+
|
| 69 |
+
return pdf.output(dest='S')
|
src/viz_engine.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import plotly.express as px
|
| 2 |
+
import plotly.graph_objects as go
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
# SDG Official Colors (UN Colors for SDG 1-17)
|
| 6 |
+
SDG_COLORS = {
|
| 7 |
+
1: '#E5243B', # No Poverty
|
| 8 |
+
2: '#DDA63A', # Zero Hunger
|
| 9 |
+
3: '#4C9F38', # Good Health and Well-being
|
| 10 |
+
4: '#C5192D', # Quality Education
|
| 11 |
+
5: '#FF3A21', # Gender Equality
|
| 12 |
+
6: '#26BDE2', # Clean Water and Sanitation
|
| 13 |
+
7: '#FCC30B', # Affordable and Clean Energy
|
| 14 |
+
8: '#A21942', # Decent Work and Economic Growth
|
| 15 |
+
9: '#FD6925', # Industry, Innovation and Infrastructure
|
| 16 |
+
10: '#DD1367', # Reduced Inequalities
|
| 17 |
+
11: '#FD9D24', # Sustainable Cities and Communities
|
| 18 |
+
12: '#BF8B2E', # Responsible Consumption and Production
|
| 19 |
+
13: '#3F7E44', # Climate Action
|
| 20 |
+
14: '#0A97D9', # Life Below Water
|
| 21 |
+
15: '#56C02B', # Life on Land
|
| 22 |
+
16: '#00689D', # Peace, Justice and Strong Institutions
|
| 23 |
+
17: '#19486A' # Partnerships for the Goals
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
SDG_NAMES = {
|
| 27 |
+
1: 'No Poverty',
|
| 28 |
+
2: 'Zero Hunger',
|
| 29 |
+
3: 'Good Health',
|
| 30 |
+
4: 'Quality Education',
|
| 31 |
+
5: 'Gender Equality',
|
| 32 |
+
6: 'Clean Water',
|
| 33 |
+
7: 'Clean Energy',
|
| 34 |
+
8: 'Decent Work',
|
| 35 |
+
9: 'Industry & Innovation',
|
| 36 |
+
10: 'Reduced Inequalities',
|
| 37 |
+
11: 'Sustainable Cities',
|
| 38 |
+
12: 'Responsible Consumption',
|
| 39 |
+
13: 'Climate Action',
|
| 40 |
+
14: 'Life Below Water',
|
| 41 |
+
15: 'Life on Land',
|
| 42 |
+
16: 'Peace & Justice',
|
| 43 |
+
17: 'Partnerships'
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
def create_world_map(df):
|
| 47 |
+
"""
|
| 48 |
+
Create a Choropleth map for the overall SDG index score.
|
| 49 |
+
"""
|
| 50 |
+
fig = px.choropleth(
|
| 51 |
+
df,
|
| 52 |
+
locations="country",
|
| 53 |
+
locationmode="country names",
|
| 54 |
+
color="sdg_index_score",
|
| 55 |
+
hover_name="country",
|
| 56 |
+
hover_data={'sdg_index_score': ':.1f'},
|
| 57 |
+
color_continuous_scale=[
|
| 58 |
+
[0, '#FF6B6B'],
|
| 59 |
+
[0.25, '#FFE66D'],
|
| 60 |
+
[0.5, '#4ECDC4'],
|
| 61 |
+
[0.75, '#45B7D1'],
|
| 62 |
+
[1.0, '#2ECC71']
|
| 63 |
+
],
|
| 64 |
+
title="🌐 Global SDG Index Progress (Latest Year)",
|
| 65 |
+
labels={'sdg_index_score': 'SDG Index Score'}
|
| 66 |
+
)
|
| 67 |
+
fig.update_layout(
|
| 68 |
+
geo=dict(
|
| 69 |
+
showframe=False,
|
| 70 |
+
showcoastlines=True,
|
| 71 |
+
coastlinecolor='#ddd',
|
| 72 |
+
projection_type='equirectangular',
|
| 73 |
+
bgcolor='rgba(0,0,0,0)',
|
| 74 |
+
landcolor='#f5f5f5',
|
| 75 |
+
countrycolor='#fff'
|
| 76 |
+
),
|
| 77 |
+
margin=dict(l=0, r=0, b=0, t=50),
|
| 78 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 79 |
+
plot_bgcolor='rgba(0,0,0,0)',
|
| 80 |
+
coloraxis_colorbar=dict(
|
| 81 |
+
title=dict(text="Score", font=dict(size=14)),
|
| 82 |
+
tickfont=dict(size=12),
|
| 83 |
+
len=0.6,
|
| 84 |
+
thickness=15
|
| 85 |
+
),
|
| 86 |
+
title=dict(font=dict(size=18, color='#0d4f6c'))
|
| 87 |
+
)
|
| 88 |
+
return fig
|
| 89 |
+
|
| 90 |
+
def create_radar_chart(df, country, year):
|
| 91 |
+
"""
|
| 92 |
+
Create a radar chart for the 17 SDG goals with official colors.
|
| 93 |
+
"""
|
| 94 |
+
target_row = df[(df['country'] == country) & (df['year'] == year)]
|
| 95 |
+
if target_row.empty:
|
| 96 |
+
return None
|
| 97 |
+
|
| 98 |
+
categories = [f"SDG {i}" for i in range(1, 18)]
|
| 99 |
+
values = [target_row[f"goal_{i}_score"].values[0] for i in range(1, 18)]
|
| 100 |
+
colors = [SDG_COLORS[i] for i in range(1, 18)]
|
| 101 |
+
|
| 102 |
+
# Add first value at end to close the radar
|
| 103 |
+
values_closed = values + [values[0]]
|
| 104 |
+
categories_closed = categories + [categories[0]]
|
| 105 |
+
|
| 106 |
+
fig = go.Figure()
|
| 107 |
+
|
| 108 |
+
# Fill area
|
| 109 |
+
fig.add_trace(go.Scatterpolar(
|
| 110 |
+
r=values_closed,
|
| 111 |
+
theta=categories_closed,
|
| 112 |
+
fill='toself',
|
| 113 |
+
fillcolor='rgba(0, 150, 199, 0.25)',
|
| 114 |
+
line=dict(color='#0096c7', width=2),
|
| 115 |
+
name=f'{country} ({year})',
|
| 116 |
+
hovertemplate='%{theta}<br>Score: %{r:.1f}<extra></extra>'
|
| 117 |
+
))
|
| 118 |
+
|
| 119 |
+
# Individual goal markers with SDG colors
|
| 120 |
+
for i, (r, theta, color) in enumerate(zip(values, categories, colors)):
|
| 121 |
+
fig.add_trace(go.Scatterpolar(
|
| 122 |
+
r=[r],
|
| 123 |
+
theta=[theta],
|
| 124 |
+
mode='markers',
|
| 125 |
+
marker=dict(color=color, size=12, symbol='circle'),
|
| 126 |
+
name=SDG_NAMES[i+1],
|
| 127 |
+
showlegend=False,
|
| 128 |
+
hovertemplate=f'{SDG_NAMES[i+1]}<br>Score: {r:.1f}<extra></extra>'
|
| 129 |
+
))
|
| 130 |
+
|
| 131 |
+
fig.update_layout(
|
| 132 |
+
polar=dict(
|
| 133 |
+
radialaxis=dict(
|
| 134 |
+
visible=True,
|
| 135 |
+
range=[0, 100],
|
| 136 |
+
tickfont=dict(size=10),
|
| 137 |
+
gridcolor='#e0e0e0',
|
| 138 |
+
linecolor='#ccc'
|
| 139 |
+
),
|
| 140 |
+
angularaxis=dict(
|
| 141 |
+
tickfont=dict(size=11, color='#1f3b4d'),
|
| 142 |
+
linecolor='#ccc',
|
| 143 |
+
gridcolor='#e0e0e0'
|
| 144 |
+
),
|
| 145 |
+
bgcolor='rgba(248, 250, 252, 0.5)'
|
| 146 |
+
),
|
| 147 |
+
showlegend=False,
|
| 148 |
+
title=dict(
|
| 149 |
+
text=f"🎯 SDG Performance Profile - {country} ({year})",
|
| 150 |
+
font=dict(size=16, color='#0d4f6c'),
|
| 151 |
+
x=0.5
|
| 152 |
+
),
|
| 153 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 154 |
+
margin=dict(t=80, b=40, l=60, r=60),
|
| 155 |
+
height=450
|
| 156 |
+
)
|
| 157 |
+
return fig
|
| 158 |
+
|
| 159 |
+
def create_trend_chart(df_filtered):
|
| 160 |
+
"""
|
| 161 |
+
Create a multi-line chart for SDG trends.
|
| 162 |
+
"""
|
| 163 |
+
fig = px.line(
|
| 164 |
+
df_filtered,
|
| 165 |
+
x="year",
|
| 166 |
+
y="sdg_index_score",
|
| 167 |
+
title="📈 Overall SDG Index Score Trend",
|
| 168 |
+
markers=True,
|
| 169 |
+
line_shape="spline"
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
fig.update_traces(
|
| 173 |
+
line=dict(color='#0096c7', width=3),
|
| 174 |
+
marker=dict(size=8, symbol='circle', line=dict(width=2, color='white')),
|
| 175 |
+
hovertemplate='Year: %{x}<br>Score: %{y:.1f}<extra></extra>'
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
fig.update_layout(
|
| 179 |
+
xaxis=dict(
|
| 180 |
+
title=dict(text="Year", font=dict(size=14, color='#1f3b4d')),
|
| 181 |
+
tickfont=dict(size=12),
|
| 182 |
+
gridcolor='#eee',
|
| 183 |
+
showgrid=True
|
| 184 |
+
),
|
| 185 |
+
yaxis=dict(
|
| 186 |
+
title=dict(text="SDG Index Score", font=dict(size=14, color='#1f3b4d')),
|
| 187 |
+
tickfont=dict(size=12),
|
| 188 |
+
gridcolor='#eee',
|
| 189 |
+
showgrid=True,
|
| 190 |
+
range=[0, 100]
|
| 191 |
+
),
|
| 192 |
+
title=dict(font=dict(size=16, color='#0d4f6c')),
|
| 193 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 194 |
+
plot_bgcolor='rgba(248, 250, 252, 0.5)',
|
| 195 |
+
hovermode='x unified',
|
| 196 |
+
height=400
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
return fig
|
| 200 |
+
|
| 201 |
+
def create_detailed_trend_chart(df_filtered):
|
| 202 |
+
"""
|
| 203 |
+
Create a detailed multi-line chart for individual goals.
|
| 204 |
+
"""
|
| 205 |
+
cols = [f"goal_{i}_score" for i in range(1, 18)]
|
| 206 |
+
|
| 207 |
+
# Melt the dataframe for plotting
|
| 208 |
+
df_melted = df_filtered.melt(
|
| 209 |
+
id_vars=['year'],
|
| 210 |
+
value_vars=cols,
|
| 211 |
+
var_name='Goal',
|
| 212 |
+
value_name='Score'
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
# Map goal numbers to SDG names and colors
|
| 216 |
+
df_melted['Goal_Num'] = df_melted['Goal'].str.extract(r'goal_(\d+)_score').astype(int)
|
| 217 |
+
df_melted['Goal_Name'] = df_melted['Goal_Num'].map(lambda x: f"SDG {x}: {SDG_NAMES[x]}")
|
| 218 |
+
df_melted['Color'] = df_melted['Goal_Num'].map(SDG_COLORS)
|
| 219 |
+
|
| 220 |
+
fig = go.Figure()
|
| 221 |
+
|
| 222 |
+
for goal_num in range(1, 18):
|
| 223 |
+
goal_data = df_melted[df_melted['Goal_Num'] == goal_num]
|
| 224 |
+
fig.add_trace(go.Scatter(
|
| 225 |
+
x=goal_data['year'],
|
| 226 |
+
y=goal_data['Score'],
|
| 227 |
+
mode='lines+markers',
|
| 228 |
+
name=f"SDG {goal_num}",
|
| 229 |
+
line=dict(color=SDG_COLORS[goal_num], width=2),
|
| 230 |
+
marker=dict(size=6),
|
| 231 |
+
hovertemplate=f'{SDG_NAMES[goal_num]}<br>Year: %{{x}}<br>Score: %{{y:.1f}}<extra></extra>'
|
| 232 |
+
))
|
| 233 |
+
|
| 234 |
+
fig.update_layout(
|
| 235 |
+
title=dict(
|
| 236 |
+
text="📊 Individual SDG Goals Trends",
|
| 237 |
+
font=dict(size=16, color='#0d4f6c')
|
| 238 |
+
),
|
| 239 |
+
xaxis=dict(
|
| 240 |
+
title=dict(text="Year", font=dict(size=13)),
|
| 241 |
+
gridcolor='#eee'
|
| 242 |
+
),
|
| 243 |
+
yaxis=dict(
|
| 244 |
+
title=dict(text="Score", font=dict(size=13)),
|
| 245 |
+
range=[0, 100],
|
| 246 |
+
gridcolor='#eee'
|
| 247 |
+
),
|
| 248 |
+
legend=dict(
|
| 249 |
+
orientation='h',
|
| 250 |
+
yanchor='bottom',
|
| 251 |
+
y=-0.4,
|
| 252 |
+
xanchor='center',
|
| 253 |
+
x=0.5,
|
| 254 |
+
font=dict(size=10)
|
| 255 |
+
),
|
| 256 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
| 257 |
+
plot_bgcolor='rgba(248, 250, 252, 0.5)',
|
| 258 |
+
height=500,
|
| 259 |
+
margin=dict(b=120)
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
return fig
|
| 263 |
+
|