# 在 OpenDashboard 建好流程，接到你的網站

新報名接入由 OpenDashboard 管理問題、正式答案、版本與申請進度。先在管理介面建立及發佈流程，再在該流程的「網站接入」選擇使用方式。

| 使用方式 | 做法 |
| --- | --- |
| 分享報名連結 | 開放入口，複製連結給參加者 |
| 嵌入你的網站 | 複製 iframe 代碼；使用平台填表頁，不需要金鑰 |
| 使用自己的 UI | 在流程內啟用專屬 API，網站後端取得問題、建立申請及提交答案 |

**從[流程接入與自訂 UI](workflow-ui.md)開始**，包含完整 API 用法、私人憑證、附件及重試規則。三種方式共用同一條流程；原有獨立表單、電郵、QR 及簽到繼續使用原有入口。

[接入方式說明](integration-modes.md)解釋填表和資料查看如何組合。新流程不提供「原網站主導」的選擇；原站回寫僅供既有整合維護，不會自動搬遷歷史資料。

## 工程師接入路線與憑證

| 需求 | 先讀 | 使用的憑證 |
| --- | --- | --- |
| 新流程、自訂填表 UI | [流程接入](workflow-ui.md) | 流程金鑰 `odb_flow_live_`／`odb_flow_test_`，加每宗申請的私人憑證 |
| 外站需要申請和附件副本 | [流程資料同步](workflow-sync.md) | 同一應用的管理金鑰 `odb_live_`／`odb_test_`；通知另用簽章密鑰 |
| 單份連接應用表單／既有報表 | [單份表單](custom-ui.md)、[API 參考](reference.md) | 應用管理金鑰；不能拿流程金鑰代替 |
| 維護既有原站主導接駁 | [舊式原站回寫](intakes.md) | 既有應用金鑰及回寫設定；新流程不用此路徑 |

一般管理員先在現有 UI 建流程、設定問題和發佈權限；工程師取得該入口的金鑰後接入，毋須另外用 API 重建同一套表單。單份 API 的 `form_id` 不能直接代替流程的 `node`，表單回覆的 `expected_version` 也不能代替流程的 `revision`。

新工程師建議順序：先讀流程教學第 1–5 節，完成「讀定義 → 須知確認 → 開始 → 保存／提交 → 繼續 → 讀回」；需要副本才加通知。下載[流程後端範例](workflow-client.mjs)，按教程提供自己的持久化身份對應。此檔不自動建立申請或發佈流程。

## 既有應用 API 與報表

以下保留既有 `/api/apps/v1` 表單、資料及報表合約的接入說明。應用管理金鑰的範圍比流程填表金鑰廣，不應交給只需填寫一個流程的網站前端。普通 forms API 也不會自動產生多步報名入口。

| 名稱 | 用途 |
| --- | --- |
| 表單 form | 宣告欄位、型別和哪些資料可以進報表。 |
| 回覆 response | 一位回覆者在一份表單的最新答案，可更新。 |
| 回覆者 respondent | 你選的匿名穩定編號，例如 demo-contact-a。不要用姓名或電郵。 |
| 合集 collection | 把同一應用的多份表單按次序組合成報表資料。 |
| 記錄 record | 保留多次發生的歷史，例如練習或交易；不能覆寫。 |
| 個人報告 report link | 只讀取某個回覆者的資料，持有連結即可查看。 |

新網站可從第一份表單開始直接保存到 OpenDashboard。已有資料的網站可匯入舊資料，之後持續同步或切換為直接保存；一次匯入不代表日後修改會自動同步。

## 1．取得測試金鑰

請 OpenDashboard 超級管理員在「連接應用」新增你的應用，選擇報表工作區。保存只顯示一次的測試金鑰和正式金鑰。

測試金鑰以 `odb_test_` 開頭；正式金鑰以 `odb_live_` 開頭。兩邊資料分開，每個應用只可存取自己的資料。把金鑰放在伺服器環境變數，不能放在前端 JavaScript、網址或公開儲存庫。

以下指令在你自己的伺服器／終端執行。將主機改為你獲准使用的 OpenDashboard 部署，先用測試金鑰。

```bash
export OPENDASHBOARD_BASE_URL='https://registration.konnec.ai'
# 從秘密管理服務或伺服器環境載入 OPENDASHBOARD_TEST_KEY；不要把金鑰寫入檔案。
curl --fail-with-body "$OPENDASHBOARD_BASE_URL/api/apps/v1/me?capabilities=true" \
  -H "Authorization: Bearer $OPENDASHBOARD_TEST_KEY"
```

成功會傳回應用資料、`test: true`，以及選填能力資訊。只有管理員開啟 `identified_mode` 的應用，才能使用電郵、電話及私人附件。不要把此模式當作公開個人資料的許可。

## 2．建立表單欄位

這個虛構工作坊讓每位聯絡人登記多位參加者。課程、場次和地點可做統計；匿名參加者編號只用作來源內部區分。

```json
{
  "title": "Synthetic workshop registrations",
  "questions": [
    { "key": "program", "title": "課程", "type": "short_text", "personal": false },
    { "key": "session", "title": "場次", "type": "short_text", "personal": false },
    { "key": "location", "title": "地點", "type": "short_text", "personal": false },
    {
      "key": "participants", "title": "參加者", "type": "repeating_group",
      "min": 0, "max": 100, "personal": false,
      "fields": [
        { "key": "participant_ref", "label": "匿名參加者編號", "type": "text", "personal": false },
        { "key": "category", "label": "類別", "type": "text", "personal": false }
      ]
    }
  ]
}
```

將 JSON 保存為 `form.json`，送至 `POST /api/apps/v1/forms`。

```bash
curl --fail-with-body "$OPENDASHBOARD_BASE_URL/api/apps/v1/forms" \
  -H "Authorization: Bearer $OPENDASHBOARD_TEST_KEY" -H 'Content-Type: application/json' \
  --data-binary @form.json
```

保存回傳的 `id` 作為 `FORM_ID`。建立表單不是冪等操作：如果請求逾時而結果未知，先由管理員查核是否已建立，避免直接重送產生重複表單。

文字欄位預設屬個人資料；只對確認適合報表的非識別資料設定 `personal: false`。姓名、電郵、電話、附件等留在私人欄位。群組只要有一個個人欄位，整個群組就不會進入合集報表；如需人數統計，另建一個只含安全統計欄位的群組。

## 3．提交及更新回覆

由網站後端根據已核對的使用者身份，選擇穩定匿名編號作 `respondent`；沒有登入的表單也需安全的回覆識別及存取方法。不要讓瀏覽器任意指定要讀寫另一人的 respondent，也不要每次提交換一個編號，否則 OpenDashboard 會視為另一位回覆者。

```json
{
  "respondent": "demo-contact-a",
  "answers": {
    "program": "合成課程", "session": "Demo A", "location": "Demo Room",
    "participants": [
      { "participant_ref": "demo-a-1", "category": "Demo" },
      { "participant_ref": "demo-a-2", "category": "Demo" },
      { "participant_ref": "demo-a-3", "category": "Demo" }
    ]
  },
  "submitted_at": "2026-01-01T00:00:00Z",
  "replace": true,
  "expected_version": 0
}
```

將它保存為 `response.json`，送至 `POST /api/apps/v1/forms/$FORM_ID/responses`。首次成功為 `201`，回傳回覆 `id` 和 `version: 1`。

```bash
curl --fail-with-body "$OPENDASHBOARD_BASE_URL/api/apps/v1/forms/$FORM_ID/responses" \
  -H "Authorization: Bearer $OPENDASHBOARD_TEST_KEY" -H 'Content-Type: application/json' \
  --data-binary @response.json
```

更新時由網站後端讀 `GET /forms/$FORM_ID/responses?respondent=$RESPONDENT`，在後端核對目前使用者有權管理這位回覆者，再送完整新答案、`replace: true` 及 OpenDashboard 目前的 `expected_version`。省略 `respondent` 會傳回整份表單的回覆，不可把全體答案直接傳到訪客瀏覽器。更新是整份取代，不是局部合併。

同一次寫入重試時，保留完全相同的答案、版本及 `submitted_at`。精確重送回傳 `200` 和原來的 id／版本。收到 `409 response_changed` 時，重新讀取並處理衝突；不要把舊資料強行覆蓋較新的版本。

## 4．建立合集及核對人數

用剛才回傳的表單 id 建立合集：

```json
{ "title": "Synthetic workshops", "form_ids": ["<FORM_ID>"] }
```

送至 `POST /collections`，保存回傳的 `COLLECTION_ID`，再讀資料表。

```bash
curl --fail-with-body "$OPENDASHBOARD_BASE_URL/api/apps/v1/collections/$COLLECTION_ID/tables" \
  -H "Authorization: Bearer $OPENDASHBOARD_TEST_KEY"
```

`submissions` 每位回覆者每份表單一行。安全重複群組另外產生 `unit: "項目"` 的資料表；多選欄位另外產生 `unit: "選項"` 的資料表。表鍵以 `groups:` 開頭，應視為不透明識別，依回傳的 `label`、`unit` 和欄位字典選擇，不要解析或自行拼接。

| 合成情況 | 正確計算 |
| --- | --- |
| 兩份回覆，各有 3 和 4 位參加者 | 提交數 2；參加者項目數 7。 |
| 第一份改成 2 位參加者 | 提交數仍為 2；參加者項目數變成 6。 |
| 一個項目勾選兩個選項 | 項目數 1；選項次數 2。 |
| 合集包含同一回覆者的多份表單 | count 算提交份數；countDistinct(respondent) 才是不重複回覆者數。 |

空群組仍有欄位字典及 0 行資料。父層安全文字可出現在 `parent:<key>` 維度，方便分類；父層數值不會複製到每個子項目，避免重複加總。

## 5．在 OpenDashboard 建立儀表板

用管理員帳號打開應用所屬的工作區，在「數據來源」加入該應用的合集。合集會帶入回覆、題目、標籤，以及已宣告安全的群組資料表。

初始報告會分開顯示已提交表單、群組項目數、選項次數及有選項的項目數。設定區塊時先選對資料表，再選計數、平均或分類。所有新指標都未經核實，請由負責人核對後再標記；應用 API 不會自動發布或核實管理報告。

資料會在工作區重新開啟時刷新，保留已有指標和排版。安全分類改動會移除被撤回群組的舊快取。快照及已凍結的分享內容維持原值，需要另外更新分享。

## 6．提供個人報告連結

在工作區建立只使用適當合集資料的儀表板，設定穩定的「個人報告範本」名稱，例如 `workshop_report`。這與可重用的版面範本不同。

```json
{
  "respondent": "demo-contact-a",
  "template": "workshop_report",
  "expires_in_days": 30
}
```

送至 `POST /collections/$COLLECTION_ID/report-links`，保存回傳的 `url`，放在你網站的「查看報告」按鈕。沒有對應範本會回傳 `template_not_found`，應先在 OpenDashboard 完成設定。

連結持有者不需要登入。OpenDashboard 每次開啟只讀取該回覆者的合集／應用記錄，不能把包含全體資料的手動資料表放進個人報告。不要公開轉貼個人連結。需要停用時送 `DELETE /report-links/$REPORT_LINK_ID`。

## 7．執行完整虛構範例

[下載伺服器端範例](example.mjs)。需要 Node.js 22.13 或更新版本，只接受測試金鑰。它建立一份表單和合集，提交 3＋4 個參加者，驗證重試不重複，再更新為 2＋4。它不寄信、不建立真人資料、不自動發布儀表板。

```bash
export OPENDASHBOARD_BASE_URL='https://registration.konnec.ai'
export OPENDASHBOARD_EXAMPLE_STATE='./opendashboard-example-state.json'
# 先在伺服器環境提供 OPENDASHBOARD_TEST_KEY。
node example.mjs
```

預期結果：`submissions: 2`、`participantsBefore: 7`、`participantsAfter: 6`，附表單和合集 id。範例將合成答案直接送到 OpenDashboard；本地狀態檔只保存資源 id 和操作狀態，不保存答案、附件或金鑰。重新執行會使用本地狀態檔的 id，不會重建定義。狀態檔屬私人操作記錄，不能提交到公開儲存庫；若出現 `pending`，先確認前次建立操作結果。

個人報告需由管理員先建立範本；按上一步再取得連結。不要把測試 id 搬去正式環境：正式金鑰要建立自己的表單及合集。

## 8．正式接入及日常維護

先完成測試環境驗收：欄位及型別一致、空群組、多人群組、更新和重試、人數、個人報告隔離，以及個人欄位不進報表。通過後由負責人切換正式金鑰，保存來源編號與 OpenDashboard id／版本的對應。

同步器應記錄不含秘密和個人答案的結果，採有限次重試及告警。429 按 `Retry-After` 等待；5xx／逾時依各端點的冪等規則重試。表單或合集結構變了就建新定義；金鑰輪換後更新伺服器設定。

私人原文或歷史資料可用 Records API 的 `data`；它不進入報表。檔案需要 identified mode 的私人 uploads API，不能只貼一個網址便當作已備份附件。[完整 API 參考](reference.md)及[常見問題](FAQ.md)列出限制、錯誤和操作方法。

## 資料變更通知與自動同步

同步由外部網站的後端觸發。資料儲存與待送事件應放在同一交易；提交成功後喚醒背景 worker，只處理變動的穩定 respondent。用單回覆查詢取得目前版本，再送完整答案及 expected_version。失敗保留待辦、延遲重試；重啟先補送待辦。通知本身不是耐久佇列，不應在瀏覽器保存應用 key 或只依賴一次網絡呼叫。低頻全量對帳可補漏；OpenDashboard 不會因建立表單而自動監看外部資料庫。


## 流程資料副本

外站需要接收 OpenDashboard 報名與作品資料時，使用[流程資料同步](workflow-sync.md)：核對表單與步驟對應、沿用固定申請編號，並在「網站接入」設定資料變更通知。
