first commit
This commit is contained in:
114
docs/backend/API_CONTRACT_MASTER.md
Normal file
114
docs/backend/API_CONTRACT_MASTER.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# API Contract Master
|
||||
|
||||
## 目標
|
||||
|
||||
固定 `v1` 對外契約,避免前後端與 SDK 在開發中漂移。
|
||||
|
||||
> 參數必填/可選與路由實際狀態,請優先看:
|
||||
> [PYTHON_API_PARAMETER_GUIDE.md](/Users/chirs/Documents/workspace/marketing/mkt.ose.tw/docs/backend/PYTHON_API_PARAMETER_GUIDE.md)
|
||||
|
||||
## 這輪契約調整(2026-03-22)
|
||||
|
||||
- `experiment_key` 改為系統生成(`EX+timestamp`),不接受使用者輸入。
|
||||
- `variant_key` 改為系統生成(`VA+timestamp`),不接受使用者輸入。
|
||||
- Variant 不再暴露 `status`、`is_control` 作為使用者可編輯欄位。
|
||||
- `variant_changes` 移除 `enabled` 欄位(每筆 change 預設啟用)。
|
||||
- 建立 Experiment API 成功時,需同步建立一個原始版本 Variant(系統管理)。
|
||||
- Runtime 實驗匹配需基於 URL 規則與裝置限制。
|
||||
|
||||
## 分區
|
||||
|
||||
- Admin API
|
||||
- Editor API
|
||||
- Runtime API
|
||||
|
||||
## Admin API(v1)
|
||||
|
||||
### Sites
|
||||
|
||||
- `GET /api/admin/sites`
|
||||
- `GET /api/admin/sites/{site_id}`
|
||||
|
||||
### Experiments
|
||||
|
||||
- `GET /api/admin/experiments`
|
||||
- `GET /api/admin/experiments/{experiment_id}`
|
||||
- `POST /api/admin/experiments`
|
||||
- `PATCH /api/admin/experiments/{experiment_id}`
|
||||
- `GET /api/admin/experiments/{experiment_id}/activity`
|
||||
|
||||
`POST/PATCH /api/admin/experiments` 使用者輸入重點:
|
||||
|
||||
- `name`
|
||||
- `module_type`
|
||||
- `status`
|
||||
- `targeting_config.url_rules`
|
||||
- `targeting_config.device_targets`
|
||||
|
||||
使用者不輸入:
|
||||
|
||||
- `experiment_key`(系統生成)
|
||||
|
||||
### Variants
|
||||
|
||||
- `GET /api/admin/variants/{variant_id}`
|
||||
- `GET /api/admin/variants?experiment_id=...`
|
||||
- `POST /api/admin/variants`
|
||||
- `PATCH /api/admin/variants/{variant_id}`
|
||||
|
||||
`POST/PATCH /api/admin/variants` 使用者輸入重點:
|
||||
|
||||
- `name`
|
||||
- `traffic_weight`
|
||||
|
||||
使用者不輸入:
|
||||
|
||||
- `variant_key`(系統生成)
|
||||
- `status`
|
||||
- `is_control`
|
||||
|
||||
### Releases
|
||||
|
||||
- `GET /api/admin/releases?experiment_id=...`
|
||||
- `GET /api/admin/releases/{release_id}`
|
||||
- `POST /api/admin/releases/build`
|
||||
- `POST /api/admin/releases/{release_id}/publish`
|
||||
- `POST /api/admin/releases/{release_id}/rollback`
|
||||
- `POST /api/admin/releases/{release_id}/archive`
|
||||
|
||||
### Goals / SDK Config
|
||||
|
||||
- `GET /api/admin/goals`
|
||||
- `GET /api/admin/goals/{goal_id}`
|
||||
- `GET /api/admin/sdk-configs`
|
||||
- `GET /api/admin/sdk-configs/{sdk_config_id}`
|
||||
|
||||
## Editor API(v1)
|
||||
|
||||
- `POST /api/editor/sessions`
|
||||
- `GET /api/editor/sessions/{session_id}`
|
||||
- `PATCH /api/editor/sessions/{session_id}`
|
||||
- `DELETE /api/editor/sessions/{session_id}`
|
||||
- `GET /api/editor/variants/{variant_id}/changes`
|
||||
- `PUT /api/editor/variants/{variant_id}/changes`
|
||||
- `POST /api/editor/previews/build`
|
||||
|
||||
`PUT /api/editor/variants/{variant_id}/changes`:
|
||||
|
||||
- change 欄位不含 `enabled`
|
||||
- change 是否生效由是否存在於 change set 決定
|
||||
|
||||
## Runtime API(v1)
|
||||
|
||||
- `POST /api/runtime/bootstrap`
|
||||
- `POST /api/runtime/assign`
|
||||
- `POST /api/runtime/payload`
|
||||
- `POST /api/runtime/events/impression`
|
||||
- `POST /api/runtime/events/conversion`
|
||||
|
||||
## 契約原則
|
||||
|
||||
- 所有 API 回傳統一 DTO,不回傳 Directus raw item
|
||||
- editor 專用 DTO 與 runtime 專用 DTO 分離
|
||||
- breaking change 需升 API 版本
|
||||
- 對外 `C/U/D` 一律經 FastAPI,禁止 frontend 直接對 Directus 做新業務寫入
|
||||
82
docs/backend/BACKEND_ARCHITECTURE_SPEC.md
Normal file
82
docs/backend/BACKEND_ARCHITECTURE_SPEC.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Backend Architecture Spec
|
||||
|
||||
## 目標
|
||||
|
||||
定義 `mktapi.ose.tw` 的後端分層、責任邊界與核心資料模型,讓 FastAPI 成為穩定產品 API 層。
|
||||
|
||||
## 分層架構
|
||||
|
||||
### Domain Layer
|
||||
|
||||
- 核心概念與聚合邊界
|
||||
- 不直接依賴 Directus 原始 schema
|
||||
|
||||
### Application Layer
|
||||
|
||||
- use case / command / query
|
||||
- 權限驗證、狀態流轉、流程編排
|
||||
|
||||
### Repository Layer
|
||||
|
||||
- Directus-backed repositories(內容型資料)
|
||||
- Native repositories(系統型高頻資料)
|
||||
|
||||
### API Layer
|
||||
|
||||
- Admin API
|
||||
- Editor API
|
||||
- Runtime API
|
||||
|
||||
## Directus / FastAPI 邊界
|
||||
|
||||
### Directus 保留
|
||||
|
||||
- Identity / Access
|
||||
- CMS / schema / content
|
||||
- revision/audit 能力(內容層版本追蹤)
|
||||
|
||||
### FastAPI 接手
|
||||
|
||||
- 實驗與發布業務規則
|
||||
- editor 協作流程
|
||||
- runtime assignment 與 payload
|
||||
- 事件收集與整合
|
||||
|
||||
## 寫入責任(CUD)
|
||||
|
||||
- Frontend/SDK 的業務寫入一律先進 FastAPI。
|
||||
- 內容型 `C/U/D`:FastAPI 驗證後透過 Directus repository 寫入 Directus(保留 revision)。
|
||||
- 高頻系統型寫入:FastAPI 直接寫 native repository(不依賴 Directus revision)。
|
||||
- 禁止在前端直接對 Directus 做新業務寫入。
|
||||
|
||||
## 核心資料模型(DATA_MODEL)
|
||||
|
||||
- Site
|
||||
- Experiment
|
||||
- Variant
|
||||
- VariantChange
|
||||
- ExperimentRelease
|
||||
- Goal
|
||||
- SdkConfig
|
||||
- Assignment
|
||||
- AuditLog
|
||||
- SystemLog
|
||||
|
||||
### 模型欄位約束(這輪)
|
||||
|
||||
- `Experiment.experiment_key`:系統生成(`EX+timestamp`)
|
||||
- `Variant.variant_key`:系統生成(`VA+timestamp`)
|
||||
- `Variant` 不含使用者可編輯 `status` 與 `is_control`
|
||||
- `VariantChange` 不含 `enabled`
|
||||
|
||||
## 設計原則
|
||||
|
||||
- API DTO、Domain Model、Persistence Schema 分開管理
|
||||
- 不將 Directus raw item 直接當對外契約
|
||||
- 嚴格限制跨層耦合
|
||||
|
||||
## 參照文件
|
||||
|
||||
- [API_CONTRACT_MASTER.md](/Users/chirs/Documents/workspace/marketing/mkt.ose.tw/docs/backend/API_CONTRACT_MASTER.md)
|
||||
- [DATA_OWNERSHIP_MATRIX.md](/Users/chirs/Documents/workspace/marketing/mkt.ose.tw/docs/backend/DATA_OWNERSHIP_MATRIX.md)
|
||||
- [RUNTIME_SDK_SPEC.md](/Users/chirs/Documents/workspace/marketing/mkt.ose.tw/docs/backend/RUNTIME_SDK_SPEC.md)
|
||||
49
docs/backend/DATA_OWNERSHIP_MATRIX.md
Normal file
49
docs/backend/DATA_OWNERSHIP_MATRIX.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Data Ownership Matrix
|
||||
|
||||
## 目的
|
||||
|
||||
這份文件用來固定「資料放哪裡、誰負責寫入、誰能直接讀取」,避免新模組把資料邊界混在一起。
|
||||
|
||||
## 規則總結
|
||||
|
||||
- 對外業務 API 只走 FastAPI。
|
||||
- 內容型資料可由 FastAPI 經 Directus repository 寫入 Directus(保留 revision)。
|
||||
- 高頻系統型資料由 FastAPI native repository 直接處理,不走 Directus revision。
|
||||
- Frontend/SDK 不直接做 Directus 新業務寫入。
|
||||
- 變更模型簡化:`Variant` 不使用 `is_control` / `status`,`VariantChange` 不使用 `enabled`。
|
||||
|
||||
## 資料歸屬矩陣
|
||||
|
||||
| Entity | 主儲存 | 寫入責任 | 讀取入口 | Revision | 頻率特性 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| site | Directus | FastAPI -> Directus repo | Admin API | Yes | 低頻 |
|
||||
| experiment | Directus | FastAPI -> Directus repo | Admin API | Yes | 中頻 |
|
||||
| variant | Directus | FastAPI -> Directus repo | Admin/Editor API | Yes | 中頻 |
|
||||
| variant_change | Directus | FastAPI -> Directus repo | Editor API | Yes | 中頻 |
|
||||
| experiment_release | Directus | FastAPI -> Directus repo | Admin/Runtime API | Yes | 中頻 |
|
||||
| goal | Directus | FastAPI -> Directus repo | Admin API | Yes | 低頻 |
|
||||
| sdk_config | Directus | FastAPI -> Directus repo | Admin/Runtime API | Yes | 低頻 |
|
||||
| editor_session | Native DB | FastAPI native repo | Editor API | No | 中高頻 |
|
||||
| assignment | Native DB | FastAPI native repo | Runtime API | No | 高頻 |
|
||||
| runtime_event | Native DB | FastAPI native repo | Runtime API / analytics pipeline | No | 高頻 |
|
||||
| audit_log | Native DB | FastAPI native repo | Internal ops API | No | 中高頻 |
|
||||
|
||||
## 新 Entity 判斷流程
|
||||
|
||||
1. 是否需要 CMS/revision 與人工管理?
|
||||
- 是:優先 Directus(由 FastAPI 轉寫)
|
||||
- 否:走 FastAPI native
|
||||
|
||||
2. 是否高頻(每請求或每曝光級)?
|
||||
- 是:禁止放 Directus revision 路徑
|
||||
- 否:可評估 Directus
|
||||
|
||||
3. 是否要給 runtime 低延遲讀取?
|
||||
- 是:優先 native 或快取層
|
||||
- 否:可走內容層
|
||||
|
||||
## 禁止事項
|
||||
|
||||
- 新功能直接在 frontend 直寫 Directus。
|
||||
- 同一份資料同時承擔 CMS 欄位與 runtime payload 責任。
|
||||
- 把 runtime 高頻事件塞進 Directus revision 流。
|
||||
54
docs/backend/ENV_SECURITY_SPEC.md
Normal file
54
docs/backend/ENV_SECURITY_SPEC.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Environment & Security Spec
|
||||
|
||||
## 目標
|
||||
|
||||
定義 local / staging / production 的環境設定、token 策略與安全基線。
|
||||
|
||||
## 環境分層
|
||||
|
||||
### local
|
||||
|
||||
- 本機開發與 smoke 驗證
|
||||
- 允許 local CORS
|
||||
|
||||
### staging
|
||||
|
||||
- 全鏈路驗證環境
|
||||
- 使用獨立 token 與資料集
|
||||
|
||||
### production
|
||||
|
||||
- 正式流量
|
||||
- 嚴格 CORS、權限與監控
|
||||
|
||||
## FastAPI 必備環境變數
|
||||
|
||||
- `APP_NAME`
|
||||
- `APP_VERSION`
|
||||
- `API_PREFIX`
|
||||
- `DATABASE_URL`
|
||||
- `DIRECTUS_BASE_URL`
|
||||
- `DIRECTUS_ADMIN_TOKEN`
|
||||
- `DIRECTUS_TIMEOUT`
|
||||
- `CORS_ALLOWED_ORIGINS`
|
||||
- `LOG_LEVEL`
|
||||
|
||||
## Directus Token 策略
|
||||
|
||||
- admin read 優先使用使用者 bearer token
|
||||
- editor/system write 使用 service token
|
||||
- runtime 不依賴商家登入 token
|
||||
|
||||
## 權限檢查基線
|
||||
|
||||
- 未登入 -> 401
|
||||
- 已登入但無權限 -> 403
|
||||
- 已登入且有權限 -> 200
|
||||
|
||||
## 上線前安全檢查
|
||||
|
||||
- secrets 正確注入且不落在 repo
|
||||
- CORS 與 allowed origins 對齊實際域名
|
||||
- API 審計與錯誤日誌可追蹤
|
||||
- rollback 路徑可執行
|
||||
|
||||
77
docs/backend/EXPERIMENT_SYSTEM_SPEC.md
Normal file
77
docs/backend/EXPERIMENT_SYSTEM_SPEC.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Experiment System Spec
|
||||
|
||||
## 目標
|
||||
|
||||
定義銷售實驗主線的最小可上線系統範圍(MVP):
|
||||
|
||||
- Experiment
|
||||
- Variant
|
||||
- Change Set
|
||||
- Release
|
||||
- Runtime delivery
|
||||
|
||||
## 這輪規格調整(2026-03-22)
|
||||
|
||||
1. 實驗匹配規則必須同時支援:
|
||||
- URL 規則(contains / equals / starts_with / regex)
|
||||
- 裝置限制(mobile / tablet / desktop)
|
||||
|
||||
2. 建立 Experiment 時,自動建立一個「原始版本 Variant」:
|
||||
- 系統自動建立,不讓使用者手動調整該原始版本內容
|
||||
|
||||
3. 移除 `is_control`:
|
||||
- 不再讓使用者手動指定對照組
|
||||
|
||||
4. 移除 `variant_changes.enabled`:
|
||||
- 每筆 change 預設生效,不提供啟用/停用欄位
|
||||
|
||||
5. key 全改為系統生成:
|
||||
- `experiment_key`:`EX+timestamp`
|
||||
- `variant_key`:`VA+timestamp`
|
||||
- 使用者不輸入 key
|
||||
|
||||
6. Variant 簡化:
|
||||
- 不再有 `status`
|
||||
- 使用者建立/編輯 variant 只可調整 `name` 與 `traffic_weight`
|
||||
|
||||
## 核心流程
|
||||
|
||||
1. 建立 Experiment
|
||||
2. 系統自動建立原始版本 Variant(不可編輯內容)
|
||||
3. 建立其他 Variant(只編輯 name / traffic_weight)
|
||||
4. 在 Editor 編輯 Variant changes
|
||||
5. Build Release(產生 runtime payload)
|
||||
6. Publish / Rollback
|
||||
7. Runtime assign + payload 套用 + event 回傳
|
||||
|
||||
## Experiment 管理
|
||||
|
||||
- 列表、建立、編輯、狀態流轉(schedule / pause / resume / archive)
|
||||
- 支援 module_type 與 targeting_config
|
||||
- targeting_config 最少要包含 `url_rules` 與 `device_targets`
|
||||
|
||||
## Variant 管理
|
||||
|
||||
- 列表、建立、編輯、刪除
|
||||
- 使用者可調整:`name`、`traffic_weight`
|
||||
- 系統管理欄位:`variant_key`
|
||||
- 不提供 `status`、`is_control`
|
||||
- change set 可讀可寫(不含 enabled 開關)
|
||||
|
||||
## Release 管理
|
||||
|
||||
- build:由 variant_changes 聚合 payload
|
||||
- publish:將 release 生效
|
||||
- rollback:回到前一可用 release
|
||||
|
||||
## Assignment 規則
|
||||
|
||||
- 以 stable seed 決定 bucket
|
||||
- bucket 對映 variant weight
|
||||
- assignment 結果需可追蹤
|
||||
|
||||
## 第一版成功標準
|
||||
|
||||
- 後台可完成「建立實驗 -> 建立變體 -> 編輯 -> build -> publish」
|
||||
- runtime 可正確回傳 assignment 與 payload
|
||||
- rollback 可恢復上一版本
|
||||
401
docs/backend/PYTHON_API_PARAMETER_GUIDE.md
Normal file
401
docs/backend/PYTHON_API_PARAMETER_GUIDE.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# Python API 參數速查(FastAPI)
|
||||
|
||||
這份文件對齊目前程式實作(`backend/app/api/*` + `backend/app/schemas/*`),給你在看 `http://127.0.0.1:8000/docs` 時快速判斷:
|
||||
|
||||
- 哪些欄位是必填
|
||||
- 哪些欄位可選
|
||||
- 有哪些業務限制會造成 4xx
|
||||
|
||||
> Base URL(local):`http://127.0.0.1:8000`
|
||||
> API Prefix:`/api`
|
||||
|
||||
---
|
||||
|
||||
## 1. 驗證與權限
|
||||
|
||||
### Authorization Header
|
||||
|
||||
- Admin / Editor API 幾乎都需要:`Authorization: Bearer <DIRECTUS_ACCESS_TOKEN>`
|
||||
- Runtime API 目前不要求 Bearer token(公開給 snippet / SDK)
|
||||
|
||||
### 常見錯誤碼
|
||||
|
||||
- `401`:缺少或無效 token
|
||||
- `403`:token 有效但缺少對應 permission
|
||||
- `404`:資源不存在
|
||||
- `422`:請求 body 結構不符 schema
|
||||
|
||||
### Permission 對照(路由層)
|
||||
|
||||
| 模組 | 權限旗標 |
|
||||
|---|---|
|
||||
| Sites | `can_manage_sites` |
|
||||
| Experiments | `can_manage_experiments` |
|
||||
| Variants | `can_manage_variants` |
|
||||
| Releases | `can_manage_releases` |
|
||||
| Goals | `can_manage_goals` |
|
||||
| SDK Configs | `can_manage_sdk_configs` |
|
||||
| Editor | `can_use_editor` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 必要參數總表(Swagger 快速對照)
|
||||
|
||||
> 這張表是「你在 `/docs` 要先看哪個欄位必填」的最短路徑。
|
||||
|
||||
| 端點 | 必要 Path / Query | 必要 Body | 主要限制 |
|
||||
|---|---|---|---|
|
||||
| `GET /api/auth/me` | 無 | 無 | 必須帶 `Authorization: Bearer <token>` |
|
||||
| `POST /api/admin/experiments` | 無 | `site_id`, `name` | `experiment_key` 系統生成,不可自填 |
|
||||
| `PATCH /api/admin/experiments/{experiment_id}` | `experiment_id` | JSON body(欄位可全選填) | body 本身必帶(可傳 `{}`) |
|
||||
| `POST /api/admin/variants` | 無 | `experiment_id`, `name` | 權重總和必須 = 100 |
|
||||
| `PATCH /api/admin/variants/{variant_id}` | `variant_id` | JSON body(欄位可全選填) | body 本身必帶(可傳 `{}`);若改 `traffic_weight` 需滿足總和 = 100 |
|
||||
| `POST /api/admin/releases/build` | 無 | `experiment_id` | 會產生新 `draft` release,`version_no` 遞增 |
|
||||
| `POST /api/editor/sessions` | 無 | `variant_id`, `base_url` | 需 editor 權限 |
|
||||
| `PATCH /api/editor/sessions/{session_id}` | `session_id` | JSON body(`status`/`draft_changes` 皆可不帶) | body 本身必帶(可傳 `{}`) |
|
||||
| `PUT /api/editor/variants/{variant_id}/changes` | `variant_id` | `items[]` | `items[].change_type`、`items[].selector_value` 必填;整包 full-replace |
|
||||
| `POST /api/editor/previews/build` | 無 | `variant_id`, `items[]` | 欄位名稱必須是 `items`(不是 `changes`) |
|
||||
| `POST /api/runtime/bootstrap` | 無 | `url`, `visitor_id` | 實務上建議必帶 `site_id` 或 `site_key`,否則常拿不到候選實驗 |
|
||||
| `POST /api/runtime/assign` | 無 | `visitor_id`, `experiment` | `experiment` 內 `experiment_id/experiment_key/status/variants[]` 必填;無可分流 variant 會 `422` |
|
||||
| `POST /api/runtime/payload` | 無 | 同 `/assign` | 同 `/assign`;回傳 assigned variant + payload |
|
||||
| `POST /api/runtime/events/impression|conversion` | 無 | `visitor_id`, `event_name` | 其餘欄位可選(建議盡量帶 site/experiment/variant) |
|
||||
|
||||
---
|
||||
|
||||
## 3. Health / Auth
|
||||
|
||||
### `GET /health`
|
||||
|
||||
- 必填:無
|
||||
- 用途:服務存活檢查
|
||||
|
||||
### `GET /health/ready`
|
||||
|
||||
- 必填:無
|
||||
- 用途:環境就緒檢查(DB URL / Directus URL / Token)
|
||||
|
||||
### `GET /api/auth/me`
|
||||
|
||||
- 必填 Header:`Authorization`
|
||||
- 回傳:目前使用者與 permission context
|
||||
|
||||
---
|
||||
|
||||
## 4. Admin API(實際已掛載)
|
||||
|
||||
## Sites
|
||||
|
||||
### `GET /api/admin/sites`
|
||||
|
||||
- 必填:無(需 Authorization)
|
||||
|
||||
### `GET /api/admin/sites/{site_id}`
|
||||
|
||||
- Path 必填:`site_id`
|
||||
|
||||
## Experiments
|
||||
|
||||
### `GET /api/admin/experiments`
|
||||
|
||||
- 必填:無
|
||||
|
||||
### `GET /api/admin/experiments/{experiment_id}`
|
||||
|
||||
- Path 必填:`experiment_id`
|
||||
|
||||
### `POST /api/admin/experiments`
|
||||
|
||||
- Body 必填:
|
||||
- `site_id: string`
|
||||
- `name: string`
|
||||
- Body 可選:
|
||||
- `module_type`(預設 `visual`)
|
||||
- `status`(預設 `draft`)
|
||||
- `start_at`
|
||||
- `end_at`
|
||||
- `targeting_config`
|
||||
- 注意:
|
||||
- `experiment_key` 由後端系統生成(`EX+timestamp`)
|
||||
- 建立成功後會自動建立一筆「原始版本」Variant
|
||||
|
||||
### `PATCH /api/admin/experiments/{experiment_id}`
|
||||
|
||||
- Path 必填:`experiment_id`
|
||||
- Body 全部可選:
|
||||
- `name`
|
||||
- `module_type`
|
||||
- `status`
|
||||
- `start_at`
|
||||
- `end_at`
|
||||
- `targeting_config`
|
||||
- 注意:
|
||||
- JSON body 本身是必填(即使欄位都可選,仍需送 `{}` 或至少一個欄位)
|
||||
|
||||
### `GET /api/admin/experiments/{experiment_id}/activity`
|
||||
|
||||
- Path 必填:`experiment_id`
|
||||
- 用途:讀 Directus activity log(操作紀錄)
|
||||
|
||||
## Variants
|
||||
|
||||
### `GET /api/admin/variants`
|
||||
|
||||
- Query 可選:
|
||||
- `experiment_id`
|
||||
|
||||
### `GET /api/admin/variants/{variant_id}`
|
||||
|
||||
- Path 必填:`variant_id`
|
||||
|
||||
### `POST /api/admin/variants`
|
||||
|
||||
- Body 必填:
|
||||
- `experiment_id: string`
|
||||
- `name: string`
|
||||
- Body 可選:
|
||||
- `traffic_weight`(預設 0)
|
||||
- `content_config`
|
||||
- 注意:
|
||||
- `variant_key` 由後端系統生成(`VA+timestamp`)
|
||||
- 業務限制:同一 experiment 全部 variants 權重總和必須等於 100(不符會 `400`)
|
||||
|
||||
### `PATCH /api/admin/variants/{variant_id}`
|
||||
|
||||
- Path 必填:`variant_id`
|
||||
- Body 可選:
|
||||
- `name`
|
||||
- `traffic_weight`
|
||||
- `content_config`
|
||||
- 注意:
|
||||
- 若更新 `traffic_weight`,同樣會觸發總和=100 驗證
|
||||
- JSON body 本身是必填(即使欄位都可選,仍需送 `{}` 或至少一個欄位)
|
||||
|
||||
## Releases
|
||||
|
||||
### `GET /api/admin/releases`
|
||||
|
||||
- Query 可選:
|
||||
- `experiment_id`
|
||||
|
||||
### `GET /api/admin/releases/{release_id}`
|
||||
|
||||
- Path 必填:`release_id`
|
||||
|
||||
### `POST /api/admin/releases/build`
|
||||
|
||||
- Body 必填:
|
||||
- `experiment_id: string`
|
||||
- 用途:
|
||||
- 讀取該 experiment 下所有 variant changes
|
||||
- 產出 `runtime_payload`
|
||||
- 建立新 release(`status=draft`,`version_no` 遞增)
|
||||
|
||||
### `POST /api/admin/releases/{release_id}/publish`
|
||||
|
||||
- Path 必填:`release_id`
|
||||
- 業務行為:
|
||||
- 同 experiment 其他 `published` release 會先降為 `draft`
|
||||
- 目標 release 再升為 `published`
|
||||
|
||||
### `POST /api/admin/releases/{release_id}/rollback`
|
||||
|
||||
- Path 必填:`release_id`
|
||||
- 業務行為:
|
||||
- 目標 release 先降為 `draft`
|
||||
- 嘗試把同 experiment、版本號較小、最新的一筆 `draft` 升為 `published`
|
||||
|
||||
### `POST /api/admin/releases/{release_id}/archive`
|
||||
|
||||
- Path 必填:`release_id`
|
||||
- 業務行為:把 release 狀態改成 `archived`
|
||||
|
||||
## Goals
|
||||
|
||||
### `GET /api/admin/goals`
|
||||
|
||||
- Query 可選:
|
||||
- `site_id`
|
||||
|
||||
### `GET /api/admin/goals/{goal_id}`
|
||||
|
||||
- Path 必填:`goal_id`
|
||||
|
||||
## SDK Configs
|
||||
|
||||
### `GET /api/admin/sdk-configs`
|
||||
|
||||
- 必填:無
|
||||
|
||||
### `GET /api/admin/sdk-configs/{sdk_config_id}`
|
||||
|
||||
- Path 必填:`sdk_config_id`
|
||||
|
||||
---
|
||||
|
||||
## 5. Editor API
|
||||
|
||||
## Session
|
||||
|
||||
### `POST /api/editor/sessions`
|
||||
|
||||
- Body 必填:
|
||||
- `variant_id: string`
|
||||
- `base_url: string`
|
||||
- Body 可選:
|
||||
- `mode`(預設 `edit`)
|
||||
|
||||
### `GET /api/editor/sessions/{session_id}`
|
||||
|
||||
- Path 必填:`session_id`
|
||||
|
||||
### `PATCH /api/editor/sessions/{session_id}`
|
||||
|
||||
- Path 必填:`session_id`
|
||||
- Body 可選:
|
||||
- `status`
|
||||
- `draft_changes`
|
||||
- 注意:
|
||||
- JSON body 本身是必填(即使欄位都可選,仍需送 `{}` 或至少一個欄位)
|
||||
|
||||
### `DELETE /api/editor/sessions/{session_id}`
|
||||
|
||||
- Path 必填:`session_id`
|
||||
|
||||
## Variant Changes
|
||||
|
||||
### `GET /api/editor/variants/{variant_id}/changes`
|
||||
|
||||
- Path 必填:`variant_id`
|
||||
|
||||
### `PUT /api/editor/variants/{variant_id}/changes`
|
||||
|
||||
- Path 必填:`variant_id`
|
||||
- Body 必填:
|
||||
- `items: EditorChangeWrite[]`
|
||||
- `EditorChangeWrite` 每筆必填:
|
||||
- `change_type`
|
||||
- `selector_value`
|
||||
- `EditorChangeWrite` 每筆可選:
|
||||
- `id`
|
||||
- `selector_type`(預設 `css`)
|
||||
- `sort_order`(預設 `0`)
|
||||
- `payload`
|
||||
- 注意:
|
||||
- 目前是「完整覆蓋」語意:request 沒帶到的舊 change 會被刪除
|
||||
- `enabled` 欄位已移除
|
||||
|
||||
## Preview
|
||||
|
||||
### `POST /api/editor/previews/build`
|
||||
|
||||
- Body 必填:
|
||||
- `variant_id: string`
|
||||
- `items: EditorChangeWrite[]`
|
||||
- 注意:
|
||||
- 欄位名稱是 `items`,不是 `changes`
|
||||
|
||||
---
|
||||
|
||||
## 6. Runtime API
|
||||
|
||||
## Bootstrap
|
||||
|
||||
### `POST /api/runtime/bootstrap`
|
||||
|
||||
- Body 必填:
|
||||
- `url: string`
|
||||
- `visitor_id: string`
|
||||
- Body 可選:
|
||||
- `site_id`
|
||||
- `site_key`
|
||||
- `user_agent`
|
||||
- 注意:
|
||||
- `site_id` / `site_key` 至少建議帶一個,否則通常拿不到 candidate experiments
|
||||
|
||||
## Assign
|
||||
|
||||
### `POST /api/runtime/assign`
|
||||
|
||||
- Body 必填:
|
||||
- `visitor_id`
|
||||
- `experiment`
|
||||
- `experiment` 必填:
|
||||
- `experiment_id`
|
||||
- `experiment_key`
|
||||
- `status`
|
||||
- `variants`(陣列,可空但可能回 `422`)
|
||||
- `variants[]` 每筆必填:
|
||||
- `id`
|
||||
- `variant_key`
|
||||
- `traffic_weight`
|
||||
- 注意:
|
||||
- 若 `variants` 為空,或全部 `traffic_weight <= 0`,會回 `422 No assignable variants were provided.`
|
||||
|
||||
## Payload
|
||||
|
||||
### `POST /api/runtime/payload`
|
||||
|
||||
- Body 結構與 `/assign` 相同(`visitor_id + experiment`)
|
||||
- 回傳包含:
|
||||
- `assigned_variant_id`
|
||||
- `assigned_variant_key`
|
||||
- `payload`
|
||||
|
||||
## Events
|
||||
|
||||
### `POST /api/runtime/events/impression`
|
||||
### `POST /api/runtime/events/conversion`
|
||||
|
||||
- Body 必填:
|
||||
- `visitor_id`
|
||||
- `event_name`
|
||||
- Body 可選:
|
||||
- `site_id`
|
||||
- `site_key`
|
||||
- `experiment_id`
|
||||
- `experiment_key`
|
||||
- `variant_id`
|
||||
- `variant_key`
|
||||
- `payload`
|
||||
|
||||
---
|
||||
|
||||
## 7. 資料取得時,如何判斷「是缺參數還是被規則擋」
|
||||
|
||||
### `bootstrap` 回傳 `candidate_experiments = []` 常見原因
|
||||
|
||||
1. `site_id/site_key` 無法解析成站點。
|
||||
2. 該 site 沒有 `status=running` 的實驗。
|
||||
3. URL 規則不匹配(`contains/equals/starts_with/regex`)。
|
||||
4. 裝置不匹配(`device_targets` 與 user-agent 判斷不一致)。
|
||||
5. 實驗下沒有 variants。
|
||||
|
||||
### `assign` 回 `422` 常見原因
|
||||
|
||||
1. `experiment.variants` 空陣列。
|
||||
2. `experiment.variants` 全部權重為 `0` 或負值。
|
||||
|
||||
### `variants` 建立/更新回 `400` 常見原因
|
||||
|
||||
1. 同一 experiment 下,所有 variant 的 `traffic_weight` 加總不等於 `100`。
|
||||
|
||||
### `editor PUT changes` 看起來「少資料」的常見原因
|
||||
|
||||
1. 你沒有把舊 change id 一起送回去,會被 full-replace 刪掉。
|
||||
2. 你送的是 `changes` 欄位而不是 `items`。
|
||||
|
||||
---
|
||||
|
||||
## 8. 你在 Swagger(/docs)建議先看哪裡
|
||||
|
||||
1. 先看 `auth -> GET /api/auth/me`,確認 token 與 permission 正常。
|
||||
2. 再看 `admin/experiments` 與 `admin/variants` 的 request body schema。
|
||||
3. 看 `editor -> PUT changes` / `POST previews/build`,確認你送的是 `items`。
|
||||
4. 看 `runtime -> bootstrap/assign/payload` 的 nested schema,特別是 `experiment.variants[]`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 已知注意事項(目前版本)
|
||||
|
||||
- `API_CONTRACT_MASTER.md` 已同步目前路由;這份文件偏重「必填參數 + 業務限制」。
|
||||
- 自動化測試(P2)尚未補齊,建議以 staging smoke + 這份欄位表當暫時驗證基準。
|
||||
50
docs/backend/RUNTIME_SDK_SPEC.md
Normal file
50
docs/backend/RUNTIME_SDK_SPEC.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Runtime SDK Spec
|
||||
|
||||
## 目標
|
||||
|
||||
定義 SDK 的部署、模式、payload 套用與追蹤邏輯,支援網站端實驗運行。
|
||||
|
||||
## SDK 雙模式
|
||||
|
||||
### Edit Mode
|
||||
|
||||
- 配合 editor 做選取與變更預覽
|
||||
- 提供 overlay / bridge 事件
|
||||
|
||||
### Runtime Mode
|
||||
|
||||
- 正式流量分流
|
||||
- 套用 payload 變更
|
||||
- 回傳 impression / conversion 事件
|
||||
|
||||
## 部署拓撲
|
||||
|
||||
- 商家網站 -> 載入 SDK(CloudFront)
|
||||
- SDK -> 呼叫 FastAPI Runtime API
|
||||
- Origin 由自有 VPS 提供靜態檔
|
||||
|
||||
## 變更規則(DOM Change)
|
||||
|
||||
- change_type:replace_text / set_attribute / set_style / insert / remove / toggle_visibility
|
||||
- selector 必須穩定可重建
|
||||
- payload 必須可序列化與回放
|
||||
|
||||
## Assignment 與 Payload
|
||||
|
||||
- assignment 由 runtime API 決策
|
||||
- payload 由 release 快照提供
|
||||
- 套用前必須驗 selector 是否存在
|
||||
|
||||
## 事件策略
|
||||
|
||||
- FastAPI 先收事件,再依策略轉送 GA4/GTM
|
||||
- 事件層次:audit event、runtime event、forwarded analytics event
|
||||
|
||||
## 風險與檢查
|
||||
|
||||
- CSP
|
||||
- iframe / script injection
|
||||
- DOM 穩定性
|
||||
- style 汙染與 z-index 衝突
|
||||
- cookie / storage 相容性
|
||||
|
||||
Reference in New Issue
Block a user