---
# System prepended metadata

title: MySDGS API 規格書

---

# 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;
}
```