# AI 證據自動分類 — 部署指引

> **適用版本**:v1.2.1+
> **狀態**:experimental,適用 dev / STG;production 部署等 DB 化(v1)後再評估
> **相關文件**:`spec.md`(行為規格)、`design.md`(設計決策)

---

## 1. 部署架構

```
┌─────────────────────────────────────────────────┐
│  BE host(STG 主機)                              │
│                                                 │
│  ┌──────────────┐   spawn      ┌──────────────┐│
│  │ BE process   │─────────────▶│ Docker daemon││
│  │ (Flask)      │              │              ││
│  │              │              │ ┌──────────┐ ││
│  │ ANTHROPIC_   │              │ │ classifier│││
│  │ API_KEY env  │              │ │ container│ ││
│  └──────┬───────┘              │ │ (-rm)    │ ││
│         │                      │ └──────────┘ ││
│         │ shell out            └──────────────┘│
│         │ libreoffice --convert                 │
│         ▼                                       │
│  ┌──────────────┐                              │
│  │ LibreOffice  │ (預覽 docx→pdf 用)            │
│  └──────────────┘                              │
└─────────────────────────────────────────────────┘
         │
         │ HTTPS
         ▼
┌──────────────────┐        ┌────────────────────┐
│ Google Drive API │        │ Anthropic Claude API│
└──────────────────┘        └────────────────────┘
```

**重點**:
- Classifier docker container 跑在 **BE host 本機 daemon**(同機),不另外起 registry / orchestrator
- BE 用 `subprocess.run(["docker", "run", "--rm", ...])` 拉起 → 拿結果 → container 自動清掉
- **BE 跑的 user 必須在 docker group**(可不用 sudo 起 container)

---

## 2. STG 主機一次性確認

### 2.1 Docker daemon

```bash
docker ps           # 不該噴 permission denied / cannot connect
docker info         # 應正常列出 daemon 資訊
```

如果 BE 跑的 user 沒權限:
```bash
sudo usermod -aG docker <BE-user>
# 重新登入該 user(group 變更需要新 session)
```

### 2.2 LibreOffice(預覽 docx → PDF 用)

```bash
which libreoffice || which soffice
# 應該回 /usr/bin/libreoffice 或類似
libreoffice --version    # 確認版本
```

如果未安裝:
- Ubuntu/Debian:`sudo apt install -y libreoffice --no-install-recommends`
- 與 SSP 匯出共用,通常 STG 已有

### 2.3 環境變數

BE process 必須能讀到:

```bash
ANTHROPIC_API_KEY=sk-ant-...    # Anthropic API key,classifier 用
```

放法(擇一):
- `systemd` service 用 `EnvironmentFile=/etc/guidant/.env`
- supervisord `environment=ANTHROPIC_API_KEY=...`
- 直接寫進啟動 script 的 export

**禁止 commit `.env` / API key 任何含憑證的檔到 git**(per CLAUDE.md)。

### 2.4 磁碟空間

Container 跑時會在 BE host `/tmp/cm-jobs/<job_uid>/` 放 catalog.json + _state.json + _report-original.json(每 job 約 1-2 MB)。

```bash
df -h /tmp     # 至少預留 1GB,長期堆積要加定期清理(下方 §6)
```

### 2.5 Drive OAuth Redirect URI

進 Google Cloud Console → 該 OAuth Client 的 **Authorized redirect URIs**,加上 STG 域名的 callback,例如:
```
https://stg.your-domain.com/api/1.0/integrations/google-drive/oauth-callback
```

否則 STG 連 Drive 時會被 Google 擋掉。

---

## 3. 每次發版

```bash
# 1. SSH 進 STG
ssh <user>@stg-host

# 2. Pull 最新 code(切到目標 tag / branch)
cd /path/to/compliance-manager-be
git fetch
git checkout v1.2.1     # 或對應 branch

# 3. 套件更新(若有改 pyproject.toml)
poetry update

# 4. 跑 DB migration(本期 v1.2.1 需要)
PGPASSWORD='<cmmgr-pwd>' psql \
    -h <stg-db-host> -p <port> -U cmmgr -d <db-name> \
    -f scripts/sql/2026-05-28-add-evidences-drive-folder-scope.sql

# 5. Build classifier image(就地 build,不推 registry)
cd scripts/evidence/classify/docker
docker build -t cmmc-classifier:latest .
# 約 1-3 分鐘
cd -

# 6. 重啟 BE
lsof -ti :8000 | xargs -r kill -9
nohup python main_socketio.py > log/app.log 2>&1 &
# 或用既有 systemd / supervisord 機制

# 7. 確認 BE 起來
sleep 3
curl -fsS http://localhost:8000/swagger-ui/ > /dev/null && echo OK
tail -20 log/app.log    # 確認沒 stack trace
```

---

## 4. 既有專案 backfill(本期需要)

v0.4 init folders 流程才會建 `Evidences/` subfolder。**v0.3 之前 init 的專案沒有**,要重跑 init 補上。

由 PM 在 UI 操作:
1. 進專案 → ProjectPlanning → Cloud Integrations tab
2. 點「初始化專案資料夾」(`POST /api/1.0/integrations/google-drive/projects/<project_uid>/init-folders`)
3. handler 是 idempotent — 既有的資料夾用 find-or-create 接管,只多建 `Evidences/`(以及對應的 EVIDENCES drive_folder_mappings 紀錄)

不重跑的話 → 該專案的「自動分類證據」按鈕不會顯示(`drive_ready=false`,因為 `_derive_evidence_folder_id` 找不到 Evidences 資料夾)。

---

## 5. Smoke test 流程

```bash
# 1. Classifier image 可正常起
docker run --rm cmmc-classifier:latest --help
# 預期:列出 service-classify 子命令的 help 訊息

# 2. LibreOffice headless 可用(預覽 docx)
echo "test" > /tmp/test.txt
libreoffice --headless --convert-to pdf --outdir /tmp /tmp/test.txt
ls /tmp/test.pdf && rm -f /tmp/test.txt /tmp/test.pdf

# 3. BE API 健康
curl -fsS http://localhost:8000/swagger-ui/ > /dev/null && echo "BE up"

# 4. UI 流程
#   - 登入 STG → 進已 init 的專案總覽
#   - 確認「自動分類證據」按鈕出現
#   - 用小測試集(< 10 檔)跑一次完整流程:觸發 → 等完成 → 進審閱頁 → 預覽 docx → 編輯 → 儲存
```

---

## 6. 維運注意

### 6.1 Container 殘留清理

`--rm` 旗標讓 container 跑完自動刪,但偶發 daemon crash 可能留 dead container:
```bash
docker container prune -f     # 清掉所有 stopped containers
```

### 6.2 `/tmp/cm-jobs/` 清理

每跑一次分類會留 catalog + state + report,長期堆積。建議排程清理:
```bash
# 例:每週清 7 天前的 job dir
find /tmp/cm-jobs -mindepth 1 -maxdepth 1 -type d -mtime +7 -exec rm -rf {} +
```

放 cron 或 systemd timer。

### 6.3 BE log 監控

關鍵 log pattern(`log/app.log`):
- `[classifier] job=<uid> container done` — 容器成功跑完
- `[classifier] job=<uid> completed; run_folder=<id> classified=N` — 整個 job 收尾完成
- `[classifier] job=<uid> container failed` / `unexpected failure` — 失敗

grep 查最近狀態:
```bash
grep "\[classifier\]" log/app.log | tail -20
```

### 6.4 並發監控

目前無全機資源上限。多 user 同時觸發不同 project 會同時起多個 container:
```bash
docker ps --filter "ancestor=cmmc-classifier:latest"
```

每個 container 跑時用 ~500MB RAM + 1 vCPU。若 STG 規格小,建議目前限 demo 用戶,觀察行為再考慮加 queue。

### 6.5 API quota

- Anthropic API:跟 user 自己的 plan 走;134 檔 ~ $0.5-1 USD(含 prompt caching)
- Google Drive API:每 tenant 一個 OAuth,daily quota 充足
- 監控:Anthropic console / Google Cloud Console

---

## 7. 回滾

若 v1.2.1 上 STG 出問題:

```bash
# 1. 切回上一版 tag
git checkout v1.2.0
poetry update

# 2. DB migration 是 ADD ONLY(只擴 CHECK constraint 允許值),無需 rollback
#    但若要乾淨 rollback CHECK:
PGPASSWORD='...' psql -h ... -U cmmgr -d ... -c "
ALTER TABLE compliance.drive_folder_mappings
    DROP CONSTRAINT IF EXISTS chk_drive_folder_mappings_scope;
ALTER TABLE compliance.drive_folder_mappings
    ADD CONSTRAINT chk_drive_folder_mappings_scope
    CHECK (scope_type IN ('ROOT','PROJECT','AP','CONTROL_GROUP','CONTROL','AO','TASK','ARCHIVE'));
"
# (若已有 EVIDENCES scope row,DROP CHECK 不會擋,但加回去會;需先 DELETE row)
DELETE FROM compliance.drive_folder_mappings WHERE scope_type = 'EVIDENCES';

# 3. 重啟 BE
lsof -ti :8000 | xargs -r kill -9 && nohup python main_socketio.py > log/app.log 2>&1 &

# 4. 既有 Drive 上的 Evidences 資料夾不會自動刪,人工處理(或保留)
```

---

## 8. AWS STG 場景(若未來移植)

目前 STG 是 premise(BE 跑在 VM),docker 在本機。若改用 STAGING_AWS_BILLOWS 之類:

| 部分 | premise STG | AWS STG |
|------|-------------|---------|
| Classifier 跑哪 | host docker daemon | 改用 ECS Fargate Task / k8s Job / AWS Batch |
| BE → 觸發 | `subprocess docker run` | 改用 boto3 `ecs.run_task` 或 k8s API |
| Image 來源 | local build | 推到 ECR,Task 從 ECR pull |
| 環境變數注入 | host env | Task definition env / Secrets Manager |

這是非平凡的改動,正式遷移時要規劃 `infra/evidence_classification/classifier_container_runner.py` 抽象化(目前只有 subprocess 一條路徑)。先列在 follow-up。

---

## 9. 已知部署陷阱

| 問題 | 症狀 | 解 |
|------|------|---|
| BE user 沒 docker group 權限 | trigger 後 BE log 噴 `permission denied... docker.sock` | `usermod -aG docker <user>`,重新登入 |
| `ANTHROPIC_API_KEY` 沒讀到 | container 跑出來 `_state.json` 為空 / container exit code != 0 | 檢查 BE process env,確認 systemd / supervisord 注入 |
| LibreOffice 沒裝 | 預覽 docx 顯示「不支援內嵌預覽」 fallback | 裝 libreoffice 或 soffice |
| Drive OAuth redirect URI 未加 | 連結 Drive 跳轉時被 Google 擋 redirect_uri_mismatch | Cloud Console 加 STG callback URL |
| `cmmc-classifier:latest` image 缺 | trigger 後 BE log:`Unable to find image 'cmmc-classifier:latest'` | 跑 build 步驟 |
| DB migration 沒跑 | trigger 後 `chk_drive_folder_mappings_scope` violation(寫 EVIDENCES row 時)| 跑 `2026-05-28-add-evidences-drive-folder-scope.sql` |
| 既有專案沒重跑 init | 「自動分類證據」按鈕不顯示 | PM 在 UI 重跑「初始化專案資料夾」 |
| 並發太多 container 撞 RAM | container 起不來 / OOM kill | 限 demo user 範圍,v1 加 queue |
