# MySDGS API 規格書
## 共通錯誤處理(HTTPS)
| HTTP 狀態碼 | 說明 | 回應格式 |
|--------------|-------------------|---------------------------------------------|
| 400 | 無效的請求參數 | `{ "error": "Invalid request" }` |
| 401 | 未授權存取 | `{ "error": "Unauthorized" }` |
| 403 | 沒有操作權限 | `{ "error": "Forbidden" }` |
| 404 | 找不到資源 | `{ "error": "Not found" }` |
| 500 | 伺服器內部錯誤 | `{ "error": "Internal server error" }` |
## Module list
1. User Service(使用者帳號管理)
2. Interaction Service(互動交流)
3. Activity Form Service(活動資料表單)
4. SROI Service(SROI 計算與分析)
## 使用者帳號管理模組
### RESTful API (HTTPS)
#### POST /auth/register
**描述**:註冊帳號(一般使用者、企業後台管理者、企業使用者)
帳號角色說明:
- **平台管理者(platform_admin)**:預設一組帳號,負責審核企業後台管理者。
- **一般使用者(general_user)**:可註冊,驗證 Email 後啟用。
- **企業後台管理者(organization_admin)**:註冊時需填寫企業名稱,需由平台管理者審核;每間企業僅可有一名後台管理者。
- **企業使用者(organization_user)**:註冊時需填寫企業名稱,需由該企業的後台管理者審核加入。
註冊流程邏輯:
- 一般使用者:帳號建立後需進行 Email 驗證。
- 企業使用者:帳號建立後需企業後台管理者審核。
- 企業後台管理者:帳號建立後需平台管理者審核。
```json
Request Body:
{
"account": "使用者名稱",
"email": "user@example.com",
"password": "string",
"account_type": "general | company_user | company_admin",
"company_name": "(企業相關帳號需填)"
}
Response 200:
{
"uid": "uuid",
"email": "user@example.com",
"account_type": "company_user | company_admin | general",
"status": "pending_approval | pending_email_verification | active"
}
```
**描述**:註冊帳號(一般/企業後台管理者/企業使用者)
註冊流程邏輯:
- 一般使用者:帳號建立後需進行 Email 驗證
- 企業使用者:需填企業名稱,提交後由企業後台管理者審核
- 企業後台管理者:填企業名稱,由平台管理者審核,每個企業僅允許一名後台管理者
```json
Request Body:
{
"account": "名稱 (僅一般使用者)",
"email": "user@example.com",
"password": "string",
"account_type": "platform_admin | general_user | organization_user | organization_admin",
"company_name": "(僅企業類型需填)"
}
Response 200:
{
"uid": "uuid",
"email": "user@example.com",
"account_type": "platform_admin | general_user | organization_user | organization_admin",
"status": "pending_approval | pending_email_verification | active"
}
```
**描述**:註冊帳號(一般/企業/管理者)
```json
Request Body:
{
"email": "user@example.com",
"password": "string",
"account_type": "general | company_user | company_admin"
}
Response 200:
{
"uid": "uuid",
"email": "user@example.com",
"account_type": "company_user"
}
```
#### POST /auth/login
```json
Request Body:
{
"email": "user@example.com",
"password": "string"
}
Response 200:
{
"access_token": "jwt_access_token",
"refresh_token": "jwt_refresh_token",
"expires_in": 3600
}
```
#### POST /auth/refresh-token
```json
Request Body:
{
"refresh_token": "jwt_refresh_token"
}
Response 200:
{
"access_token": "new_jwt_access_token",
"expires_in": 3600
}
```
#### GET /auth/verify-email
```json
Query:
?token=xxxxxx
Response 200:
{ "message": "Email verified" }
```
#### PUT /auth/profile
```json
Request Body:
{
"origin_account": "origin_name",
"new_account": "new_name",
"new_email": "new_email@example.com",
"new_company_name": "new_company_name"
}
Response 200:
{ "message": "Profile updated" }
```
### gRPC(UserService)
```proto
message SignUpRequest {
string email = 1;
string password = 2;
string account_type = 3;
}
message MemberResponse {
string uid = 1;
string email = 2;
string account_type = 3;
string status = 4;
}
message LoginRequest {
string email = 1;
string password = 2;
}
message TokenPair {
string access_token = 1;
string refresh_token = 2;
int32 expires_in = 3;
}
message RefreshTokenRequest {
string refresh_token = 1;
}
service MemberService {
rpc SignUp(SignUpRequest) returns (MemberResponse);
rpc Login(LoginRequest) returns (TokenPair);
rpc RefreshToken(RefreshTokenRequest) returns (TokenPair);
rpc VerifyEmail(VerifyEmailRequest) returns (BoolResponse);
rpc UpdateProfile(UpdateProfileRequest) returns (UserResponse);
rpc ChangePassword(ChangePasswordRequest) returns (BoolResponse);
rpc GetUser(UserIdRequest) returns (MemberResponse);
rpc GetUserRole(UserIdRequest) returns (UserRoleResponse);
}
```
## 互動交流模組
### 模組功能說明
- 首頁包含兩個跳轉按鈕:
- 經驗分享文章區
- 發問區
- 經驗分享文章:
- 文章為貼文形式,主要為文字,可附多張圖片與多個影片
- 僅限企業使用者可發布
- 發布前系統需驗證文章是否與 SDGs、ESG 活動相關
- 文章可附分類與 hashtag 標籤
- 所有使用者皆可瀏覽
- 所有使用者皆可留言(純文字,100 字內)
- 發問區:
- 問題與回覆皆為純文字(討論串形式)
- 所有使用者皆可提問與回覆
- 提問需經過系統審核是否為永續相關問題
- 搜尋功能:
- 可搜尋已發布的文章與討論串
- 可依 hashtag 搜尋對應的文章
### RESTful API (HTTPS)
#### POST /articles
**描述**:建立經驗分享文章(僅限企業使用者,需經 ESG/SDGs 文字自動審核)
```json
Request Body:
{
"title": "文章標題",
"content": "文章內文",
"tags": ["education", "environment"],
"image_urls": ["https://..."],
"video_urls": ["https://..."]
}
Response 201:
{
"article_id": "uuid",
"status": "pending_review | approved | rejected"
}
```
#### PUT /articles/{id}
描述:編輯文章(貼文者)
```json!
Request Body:
{
"title":"更新後的title",
"content": "更新後的留言內容",
"tags": ["education", "environment"],
"image_urls": ["https://..."],
"video_urls": ["https://..."]
}
Response 200:
{
"article_id": "uuid",
}
```
#### DELETE /articles/{id}
描述:刪除文章(發文者或平台後台管理員)
```json!
Response 200:
{
"status": "success"
}
```
#### POST /articles/{id}/comments
**描述**:發表留言(所有使用者)
```json
Request Body:
{
"parent_comment_id": null,
"text": "留言內容 (<= 100 字)"
}
Response 200:
{
"comment_id": "uuid",
"parent article": null,
"parent_comment_id": null,
"created_time": "2025-06-20T16:50:00Z"
}
```
#### PUT /articles/{id}/comments/{comment_id}
描述:編輯留言(留言者)
```json!
Request Body:
{
"content": "更新後的留言內容"
}
Response 200:
{
"comment_id": "uuid",
"parent_comment_id": null,
"created_time": "2025-06-20T16:50:00Z"
}
```
#### DELETE /articles/{id}/comments/{comment_id}
描述:刪除留言(留言者或平台後台管理員)
```json!
Response 200:
{
"comment_id": "uuid",
"parent_comment_id": null,
"created_time": "2025-06-20T16:50:00Z"
}
```
#### POST /questions
**描述**:提問(所有使用者,需經 ESG/SDGs 文字審核)
```json
Request Body:
{
"title": "請問社區永續的做法有哪些?",
"text": "問題問題問題"
}
Response 201:
{
"question_id": "uuid",
"status": "pending_review | approved | rejected"
}
```
#### POST /questions/{id}/answers
**描述**:回覆問題(所有使用者)
```json
Request Body:
{
"text": "可從環境教育、在地採購開始推動"
}
Response 200:
{
"question_id": "uuid",
"answer_id": "uuid",
"created_time": "2025-06-20T16:50:00Z"
}
```
#### GET /list_articles
**描述**:列出所有資料庫的文章
```https
GET /list_article
```
```json
Response 200:
{
"articles": [
{
"article_id": "uuid",
"title": "..." ,
"content": "...",
"tags": ["education", "environment"],
"image_urls": ["https://..."],
"video_urls": ["https://..."],
"created_time": "2025-06-20T16:50:00Z"
} ],
}
```
#### GET /search
**描述**:搜尋貼文與問題
```https
GET /search?q=永續&tag=education
```
```json
Response 200:
{
"articles": [ { "article_id": "uuid", "title": "..." } ],
"questions": [ { "question_id": "uuid", "text": "..." } ]
}
```
#### POST /moderation/classify-text(暫時不做)
**描述**:文章或問題內容自動審查是否與 ESG/SDGs 有關
```json
Request Body:
{
"text": "文章或問題全文"
}
Response 200:
{
"is_sustainable_related": true,
"confidence": ["true | false"],
"tags_detected": ["climate", "community"]
}
```
```json
Request Body:
{
"title": "string",
"content": "string",
"tags": ["education", "environment"]
}
Response 201:
{
"article_id": "uuid",
"question_id": "uuid",
"status": "pending_review | approved | rejected"
}
```
#### POST /articles/{id}/comments
```json
Request Body:
{
"text": "留言內容 (<= 100 字)"
}
Response 200:
{
"comment_id": "uuid"
}
```
### gRPC(InteractionService)
```proto
syntax = "proto3";
message CreateArticleRequest {
string title = 1;
string content = 2;
repeated string tags = 3;
repeated string image_urls = 4;
repeated string video_urls = 5;
}
message Article {
string article_id = 1;
string title = 2;
string status = 3;
string author_id = 4;
repeated string tags = 5;
repeated string image_urls = 6;
repeated string video_urls = 7;
}
message CommentRequest {
string article_id = 1;
string text = 2;
string parent_comment_id = 3;
}
message EditCommentRequest {
string comment_id = 1;
string article_id = 2;
string new_text = 3;
}
message DeleteCommentRequest{
string comment_id = 1;
string article_id = 2;
}
message Comment {
string comment_id = 1;
string article_id = 2;
string user_id = 3;
string text = 4;
string created_time = 5;
}
message Question {
string question_id = 1;
string user_id = 2;
string title = 3;
string text = 4;
string status = 5; // pending_review, approved
}
message CreateQuestionRequest {
string title = 1;
string text = 2;
string user_id = 3;
}
message AnswerRequest {
string question_id = 1;
string text = 2;
}
message SearchRequest {
string query = 1;
string tag = 2;
}
message SearchResults {
repeated Article articles = 1;
repeated Question questions = 2;
}
message BoolResponse {
bool success = 1;
}
message Empty {}
message ArticleList {
repeated Article articles = 1;
}
message ArticleId {
string article_id = 1;
}
message CommentList {
repeated Comment comments = 1;
}
service InteractionService {
rpc ListArticles(Empty) returns (ArticleList);
rpc GetArticle(ArticleId) returns (Article);
rpc CreateArticle(CreateArticleRequest) returns (Article);
rpc CommentOnArticle(CommentRequest) returns (Comment);
rpc EditCommentOnArticle(EditCommentRequest) returns (BoolResponse);
rpc DeleteComment(DeleteCommentRequest) returns (BoolResponse);
rpc AskQuestion(CreateQuestionRequest) returns (Question);
rpc AnswerQuestion(AnswerRequest) returns (BoolResponse);
rpc SearchContent(SearchRequest) returns (SearchResults);
rpc EditArticle(EditArticleRequest) returns (Article);
rpc DeleteArticle(ArticleId) returns (BoolResponse);
rpc GetComment(ArticleId) returns (CommentList);
}
```
```proto
message CreateArticleRequest {
string title = 1;
string content = 2;
repeated string tags = 3;
}
message Article {
string article_id = 1;
string title = 2;
string status = 3;
}
```
## 活動資料收集表單設計與資料處理模組
### 模組功能說明
本模組負責活動資料收集表單的設計與填寫流程,支援後續 SROI 計算所需的結構化資料。表單內容包含:
#### 一、定義範圍
- 活動名稱:文字輸入
- 情境(要解決的問題):文字輸入
- 投入:預計投入的資源、資金(數字)
- 受益對象:文字輸入
- 利害關係人:
- 類別(支持者、執行者、受益者)
- 名稱、人數、是否接受訪談(勾選)
- 納入/排除(勾選)、排除原因
- 地點:文字輸入
- 活動時間:起始與結束日期
- 預期產出:百分比輸入
#### 二、描繪成果
- 利害關係人類別
- 成果種類、項目、事件鏈
- 成果指標佐證:文字或照片
- 納入或排除(勾選)
#### 三、衡量成果的貨幣價值
- 資金/資源投入:
- 利害關係人類別、投入項目與其貨幣價值
- 人員投入時間:
- 類別、總人數、人時數、時薪(系統換算金額)
- 財務代理變數:
- 成果名稱、變數、價值(單價×數量)、資料來源
#### 四、確定影響範圍
- 無謂因子/歸因因子/衰減因子/移轉因子:
- 每筆包含:利害關係人類別、成果項目、因子數值、說明
#### 五、成果統計
- 針對每種利害關係人統計各類成果比例
#### 六、後續管理
- 發現的現象(例如未預期成果)
- 利害關係人、說明、後續管理方案
### RESTful API (HTTPS)
#### PUT /activities/{id}/form
**描述**:填寫或更新完整的活動表單內容
```json
Request Body:
{
"activity_name": "活動A",
"context": "解決某問題",
"location": "台中",
"start_date": "2025-06-01",
"end_date": "2025-06-30",
"beneficiaries": "在地居民",
"expected_output": 0.85,
"inputs": 120000,
"stakeholders": [
{
"type": "受益者",
"name": "青年",
"count": 100,
"interview": true,
"included": true,
"exclude_reason": ""
}
],
"outcomes": [
{
"stakeholder_type": "受益者",
"type": "教育",
"item": "技能提升",
"chain": "訓練 → 就業",
"evidence": "證照取得",
"included": true
}
],
"monetary_values": {
"resource_inputs": [
{
"stakeholder_type": "支持者",
"item": "設備捐贈",
"value": 30000
}
],
"labor_inputs": [
{
"stakeholder_type": "執行者",
"count": 10,
"hours": 200,
"hourly_wage": 300
}
],
"proxies": [
{
"outcome_item": "就業",
"proxy": "平均月薪",
"unit_value": 35000,
"units": 2,
"source": "行政院統計處"
}
]
},
"impact_scope": {
"deadweight": [...],
"attribution": [...],
"drop_off": [...],
"displacement": [...]
},
"follow_up": [
{
"issue": "低出席率",
"stakeholder_type": "受益者",
"description": "部分地區參與意願低",
"solution": "補助交通費"
}
]
}
Response 200:
{ "message": "Form submitted" }
```
### RESTful API (HTTPS)
#### POST /activities
```json
Request Body:
{
"name": "活動名稱",
"start_date": "2025-06-01",
"end_date": "2025-06-30"
}
Response 201:
{
"activity_id": "uuid"
}
```
#### PUT /activities/{id}/form
```json
Request Body:
{
"inputs": { "資金": 50000 },
"stakeholders": [
{
"type": "受益者",
"name": "在地居民",
"count": 100,
"interview": true
}
]
}
Response 200:
{ "message": "Form updated" }
```
### gRPC(ActivityFormService)
```proto
message Stakeholder {
string type = 1;
string name = 2;
int32 count = 3;
bool interview = 4;
bool included = 5;
string exclude_reason = 6;
}
message Outcome {
string stakeholder_type = 1;
string type = 2;
string item = 3;
string chain = 4;
string evidence = 5;
bool included = 6;
}
message ResourceInput {
string stakeholder_type = 1;
string item = 2;
double value = 3;
}
message LaborInput {
string stakeholder_type = 1;
int32 count = 2;
int32 hours = 3;
double hourly_wage = 4;
}
message FinancialProxy {
string outcome_item = 1;
string proxy = 2;
double unit_value = 3;
double units = 4;
string source = 5;
}
message ImpactFactor {
string stakeholder_type = 1;
string outcome_item = 2;
double value = 3;
string description = 4;
}
message FollowUpAction {
string issue = 1;
string stakeholder_type = 2;
string description = 3;
string solution = 4;
}
message FormSection {
string activity_id = 1;
oneof section {
Stakeholder stakeholder = 2;
Outcome outcome = 3;
ResourceInput resource_input = 4;
LaborInput labor_input = 5;
FinancialProxy proxy = 6;
ImpactFactor factor = 7;
FollowUpAction follow_up = 8;
}
}
message GetFormSectionRequest {
string activity_id = 1;
string section_type = 2; // stakeholders, outcomes, inputs, impact, follow_up
}
message FormSectionList {
repeated FormSection sections = 1;
}
message SubmitFormRequest {
string activity_id = 1;
repeated Stakeholder stakeholders = 2;
repeated Outcome outcomes = 3;
repeated ResourceInput resource_inputs = 4;
repeated LaborInput labor_inputs = 5;
repeated FinancialProxy proxies = 6;
repeated ImpactFactor deadweight = 7;
repeated ImpactFactor attribution = 8;
repeated ImpactFactor drop_off = 9;
repeated ImpactFactor displacement = 10;
repeated FollowUpAction follow_ups = 11;
}
message ActivityId {
string id = 1;
}
service ActivityFormService {
rpc SubmitForm(SubmitFormRequest) returns (BoolResponse);
rpc GetFormSection(GetFormSectionRequest) returns (FormSectionList);
rpc CreateActivity(ActivityRequest) returns (ActivityId);
rpc GetActivity(ActivityId) returns (ActivityDetails);
rpc UpdateActivity(ActivityUpdateRequest) returns (BoolResponse);
rpc GetForm(ActivityId) returns (ActivityForm);
rpc UpdateForm(ActivityForm) returns (BoolResponse);
}
```
```proto
message ActivityRequest {
string name = 1;
string start_date = 2;
string end_date = 3;
}
message ActivityId {
string id = 1;
}
```
## SROI模型建立與敏感度分析模組
### 模組功能說明
本模組負責根據活動資料表單進行 SROI 計算與敏感度分析。
#### SROI 計算公式
```
SROI = 總現值 / 總投入
總現值 = 每年影響力折現後加總
每年影響力值 = 總影響力 - (無謂因子 + 歸因因子 + 衰減因子 + 移轉因子)
```
#### 功能描述
- 使用者可從下拉選單中選取所管理的活動進行 SROI 計算
- 系統根據活動資料自動抓取所需欄位並套用公式進行 SROI 計算
- 計算結果會顯示所有參數與最終的 SROI 值
- 權限設計:
- 企業使用者可檢視參數值(不可查看詳細數據)
- 企業使用者可針對自身活動進行參數微調以進行敏感度模擬分析
### RESTful API (HTTPS)
#### GET /sroi/{activityId}
**描述**:取得某活動的 SROI 計算結果與使用參數(資料庫資料)
```json
Response 200:
{
"sroi_value": 2.73,
"total_present_value": 273000,
"total_investment": 100000,
"parameters": {
"deadweight": 0.1,
"attribution": 0.15,
"drop_off": 0.1,
"displacement": 0.05
}
}
```
#### POST /sroi/{activityId}/simulate
**描述**:模擬調整參數後的 SROI 敏感度分析(不儲存)
```json
Request Body:
{
"deadweight": 0.2,
"attribution": 0.1,
"drop_off": 0.05,
"displacement": 0.0
}
Response 200:
{
"sroi_value": 1.93,
"total_present_value": 193000,
"total_investment": 100000,
"parameters": {
"deadweight": 0.2,
"attribution": 0.1,
"drop_off": 0.05,
"displacement": 0.0
}
}
```
### gRPC(SroiService)
```proto
message SroiCalculationRequest {
string activity_id = 1;
}
message SroiSimulationRequest {
string activity_id = 1;
double deadweight = 2;
double attribution = 3;
double drop_off = 4;
double displacement = 5;
}
message SroiResult {
double sroi_value = 1;
double total_present_value = 2;
double total_investment = 3;
map<string, double> parameters = 4;
}
service SroiService {
rpc CalculateSroi(SroiCalculationRequest) returns (SroiResult);
rpc SimulateSroi(SroiSimulationRequest) returns (SroiResult);
}
```
### RESTful API (HTTPS)
#### GET /sroi/{activityId}
```json
Response 200:
{
"sroi_value": 2.73,
"parameters": {
"總投入": 100000,
"總現值": 273000
}
}
```
#### POST /sroi/{activityId}/simulate
```json
Request Body:
{
"無謂因子": 0.1,
"歸因因子": 0.15
}
Response 200:
{
"sroi_value": 2.15
}
```
### gRPC(SroiService)
```proto
message ActivityId {
string id = 1;
}
message SroiResult {
double sroi_value = 1;
map<string, double> parameters = 2;
}
```