自動證據分類功能 — 白話需求 (Phase 0 草稿)

規劃日期:2026-05-27 撰寫人:raymond + Claude 狀態:Phase 0 草稿,待 brainstorm 後搬至 docs/features/<feature-name>/

§1

一句話描述

在專案頁加一個「自動分類證據」按鈕,按下後系統會把這輪 AP 上傳到 Drive 的所有證據檔,透過 AI 自動分到對應的 CMMC 控制項 / AO 資料夾,user 在審閱頁微調確認後完成歸檔。

§2

為什麼要做

目前 user 跑完一輪 AP 後,要把幾十到幾百份證據手動分類到 17 個控制項 × 59 個 AO 的對應資料夾,耗時且容易漏分。POC 證明 Claude AI 可以做到 ~98% 自動分類準確度,可大幅降低人工。

§3

整體流程(六步)

① 上傳證據到 Drive 暫存
       ↓
② Web 按按鈕觸發分類 (BE 收 request)
       ↓
③ BE 開 Docker container 跑分類 (~10 分鐘 / 130 檔)
       ↓
④ Container 把檔自動 copy 到對應 AO 資料夾 (in Drive)
       ↓
⑤ User 進審閱 UI 微調 / 確認
       ↓
⑥ User 儲存後 BE 把調整落實寫回 Drive,完成歸檔

§4

Step ① — 上傳證據到 Drive 暫存

現況沿用,不需新功能

  • User 把這輪 AP 的證據檔放到自己專案的 Drive Evidences 資料夾
    • 可從 Drive Web / Desktop / mobile 拖檔
    • 或從 compliance-manager 上傳 UI
  • 既有 drive_sync 自動同步檔案 metadata 進 BE
§5

Step ② — Web 按按鈕觸發分類

介面變化(compliance-manager-fe 專案總覽頁)

新增 「🪄 自動分類證據」 按鈕

按鈕狀態邏輯:

條件 按鈕狀態
專案沒連 Drive 隱藏
Evidences 資料夾沒檔案 灰 disabled,提示「請先上傳證據檔」
上次分類 job 還在跑 灰 disabled,顯示「分類中... 已處理 23/130 (預估剩 7 分鐘)」
上次分類完成 正常啟用,hover 提示「上次執行 2026-05-27 14:30 · 點此重新分類」

點按鈕:

  1. 跳確認 dialog:「將分析 Evidences 資料夾內 N 個證據檔,預估 X 分鐘,是否開始?」
  2. 確認後送 POST /api/projects/{uid}/auto-classify-evidence 給 BE
  3. BE 立刻回 202 Accepted + job_uid
  4. Web 切換按鈕顯示進度狀態,每 10 秒 polling job 狀態

權限

  • 預設只有專案 manager 可觸發
  • Auditor 可以審閱結果但不能觸發新 job
  • 一個專案同時只能有一個分類 job 在跑(重複觸發擋掉)
§6

Step ③ — BE 啟動 Docker container 跑分類

BE 內部動作

  1. 驗證:權限 OK、Drive 連線 OK、無 active job、Evidences 有檔
  2. 建 job 紀錄:(job_uid, project_uid, framework_id, status=queued, started_by, started_at)
  3. 從 OSCAL catalog table 撈該專案使用的框架(CMMC L1)AO 清單,dump 成 JSON 寫 shared volume
  4. 起 container:
    docker run --rm \
      -v /var/lib/cm/jobs/<job_uid>:/job \
      -e DB_HOST=... -e DB_SECRET=... \
      -e DRIVE_TOKEN_ENCRYPTION_KEY=... \
      -e ANTHROPIC_API_KEY=... \
      cmmc-classifier:latest \
      classify --project-uid X --tenant-id Y \
               --catalog /job/catalog.json \
               --output /job/report.json
  5. Subprocess block 等 container 退出
  6. /job/report.json + 更新 job status = completedfailed
  7. Push 完成通知給 user(in-app notification)

Container 內部動作

  1. 讀 BE 給的 catalog JSON
  2. 從 DB compliance.tenant_drive_integrations 撈 tenant 的 OAuth refresh token,解密 + 換 access token
  3. List Drive Evidences 資料夾根層的檔案
  4. 對每個檔:下載 → 抽文字(docx / csv / log / txt)→ 呼叫 Claude API 分類(with prompt caching)→ 寫 report 條目
  5. 全部跑完寫 /job/report.json 退出(不直接寫 Drive,留給 BE)

為什麼用 Docker?

  • 隔離(classifier 與 BE 跑不同 Python 環境,分類期間 BE 不被影響)
  • 可控制資源(CPU / memory 上限)
  • 之後容易換掉(換 model、改 prompt 不用動 BE)
  • 部署簡單(image 一次 build,多次跑)

時間估算

POC 實測 134 檔 + Claude Sonnet 4.6 + 5 workers 並行 = ~10 分鐘

§7

Step ④ — 自動分類到各 AO 資料夾

Drive 結構

每次分類都建一個獨立 run 資料夾放結果,互不覆蓋:

Project Drive/
  AP-Q1-2026/
    Evidences/
      ├── A.docx                            ← 原始檔(保留不動)
      ├── B.csv
      ├── ...
      │
      ├── 自動分類_2026-05-27_14-30/        ← 第一次跑 (run folder)
      │   ├── [AC] Access Control/
      │   │   ├── [AC.L1-3.1.1] Authorized Access Control/
      │   │   │   ├── [a] authorized users are identified/
      │   │   │   │   └── A.docx            ← copy of original
      │   │   │   ├── [b] .../
      │   │   │   └── ...
      │   │   └── [AC.L1-3.1.2] .../
      │   ├── [IA] .../
      │   ├── ...
      │   ├── _report.json                  ← Claude reasoning 完整報告
      │   └── _summary.md                   ← 人可讀統計摘要
      │
      └── 自動分類_2026-05-27_15-00/        ← 第二次跑(re-classify)
          └── ... (同上結構)

Run 資料夾命名

格式:自動分類_YYYY-MM-DD_HH-MM(依 BE timezone)

如果同分鐘內重跑:加 _2_3 後綴避免衝突。

BE 行為

  1. 解析 report,計算 run folder 名稱
  2. 在 Evidences 下建立 run folder
  3. 對每筆 file × matched_AO(高信心 ≥ 0.80):
    • 在 run folder 內建立 AO 資料夾結構(domain → control → AO)
    • 用 Drive API files.copy 把原檔複製到對應 AO 資料夾
  4. _report.json_summary.md 寫到 run folder 根(給未來 audit / 機器讀)
  5. Evidences 根目錄的原檔完全不動
  6. 既有 drive_sync 偵測 run folder + 內容,同步進 BE
  7. job 紀錄 run folder 名稱 + Drive folder ID + status = completed

為什麼用 run folder 而不直接展開在 Evidences/

優點 說明
多次重跑不衝突 第二次跑不會覆蓋第一次的結果,user 可比對哪次更準
完整 audit trail 過去每次 AI 分類的結果 + reasoning 都保留
Drive 結構乾淨 Evidences 根只有原始檔 + N 個 run 資料夾,不會跟 AO 結構混在一起
可獨立刪 / 歸檔 舊的分類 run 可整資料夾砍掉或搬到 archive
Job 對應清楚 一個 job = 一個 run folder,1:1 mapping

重跑邏輯(user 觸發 re-classify)

  • 預設:跑全部 Evidences 根目錄的檔,產出新 run folder(舊的保留)
  • 進階選項(v1.x 再加):「只跑跟上次有差異的檔」 / 「只重跑未分類檔」
§8

Step ⑤ — User 審閱 UI 微調 / 確認

入口

  • 收到分類完成通知 → 點通知
  • 或專案頁的「審閱分類結果」按鈕(job 完成後出現)

進入剛剛雛形設計的審閱 UI

功能(已雛形實作):

  • 顯示 stats:134 檔 / 128 已分類 / 2 未分類 / 共 484 個 AO 配對
  • 檔案清單(左):依 filter / search 列檔,顯示信心 + 配對數
  • 詳細審閱(右):
    • 已分類檔:列出每個 AO placement + Claude reasoning,可逐項 ✓/✗ 移除
    • 未分類檔:列出 Claude 評估過的所有候選 AO(含 reasoning + 信心),可一鍵採納
    • 加入其他 AO:開 modal,搜尋 + 從全 59 AO 挑選
  • 每檔額外動作
    • 標為非證據(從審閱隊列移除)
    • 從證據池刪除(連 Drive 原檔一起 trash)
  • 快捷鍵:↑↓ 或 j/k 跳上下一筆,加速逐檔審閱
  • Inbox mode:「未分類審閱」按鈕快速切換到只看未分類的 review queue

Pending edits 模型

審閱期間 user 的每個動作是 pending 狀態,沒寫回 Drive。Save bar 顯示「N 個變更待儲存」。 User 可隨時 discard 或 save。

§9

Step ⑥ — 儲存後歸檔

User 點「儲存變更」:

所有編輯都套用在「目前審閱中的 run folder」內(不影響其他 run)。

  1. BE 把 pending edits 落實到 Drive:
編輯類型 Drive 動作(在 run folder 內)
新增 AO 配對 files.copy 把原檔複製到 <run-folder>/[XX]/[YY]/[z]/
移除 AO 配對 <run-folder>/[XX]/[YY]/[z]/ trash 副本
標為非證據 不動 Drive,只更新 BE flag(該 run 的未分類隊列隱藏)
從證據池刪除 trash 原檔(Evidences 根) + 所有 run folder 內的副本
  1. 寫 audit log:(user_id, job_uid, run_folder, file_id, action, before_aos, after_aos, timestamp)
  2. 既有 drive_sync 自動同步最新狀態進 BE
  3. UI 顯示「儲存完成,共 N 個變更已套用至 <run folder 名稱>」

§10

資料儲存方案(實驗階段:JSON-in-Drive)

實驗階段不動 DB,全部 state 都用 JSON 存在 Drive 的 run folder 內,BE 當 thin proxy。

Run folder 內檔案

Evidences/
  自動分類_2026-05-27_14-30/
    ├── _state.json              ← UI 讀寫的 single source of truth
    ├── _report-original.json    ← Claude 原始輸出(immutable 留底)
    ├── [AC] Access Control/...   ← 實體檔副本(依 _state.json 內容生成)
    └── [IA] ...

_state.json 結構

⚠️ 以下為 FR-030 初版 schema。實際欄位已隨 FR-030.2/030.3 擴充(token_usage / archive_files / archived_at / error 等),且 run_uid / edit_log 實際未實作。完整最新 schema 以 docs/api/evidence-classification/state-json-schema.md 為準。

{
  "metadata": {
    "run_uid": "abc-123",
    "drive_run_folder_id": "1xyzABC...",
    "drive_run_folder_name": "自動分類_2026-05-27_14-30",
    "framework_id": "cmmc-l1",
    "confidence_threshold": 0.80,
    "model": "claude-sonnet-4-6",
    "triggered_at": "2026-05-27T14:30:15Z",
    "completed_at": "2026-05-27T14:40:22Z",
    "last_edited_at": "2026-05-27T15:05:33Z",
    "last_edited_by": "user@example.com"
  },
  "files": [
    {
      "file_drive_id": "1yktzl...",
      "file_name": "authorized-device-registry.csv",
      "is_na": false,
      "is_deleted": false,
      "matches": [
        {"ao_id": "AC.L1-3.1.1[c]", "confidence": 0.97, "reasoning": "..."},
        {"ao_id": "AC.L1-3.1.1[f]", "confidence": 0.95, "reasoning": "..."}
      ],
      "placements": [
        {"ao_id": "AC.L1-3.1.1[c]", "placement_drive_id": "1b36g1...", "source": "ai", "added_at": "2026-05-27T14:35:01Z"}
      ]
    }
  ],
  "edit_log": [
    {"at": "2026-05-27T15:02:11Z", "by": "user@...", "file_drive_id": "1yktzl...", "action": "remove_placement", "ao_id": "AC.L1-3.1.1[f]"}
  ]
}

兩個 key 概念分開:

  • matches:Claude 原始評估,immutable。降閾值重看都靠這個算
  • placements:當前真實狀態,mutable。UI edit 就是改這個

BE 最小工作量(3 個 endpoint,不動 DB)

Endpoint 行為
POST /api/projects/<uid>/classify-evidence 起 container,回 { run_folder_id, status: "queued" }
GET /api/classification-runs/<folder-id>/state 從 Drive 拉 _state.json 回傳
PUT /api/classification-runs/<folder-id>/state 收新 state → diff 舊 placements vs 新 → Drive copy/trash → 覆寫 _state.json

編輯流程

① Container 跑完
   ↓ 寫 _state.json + _report-original.json 到 run folder

② User 進審閱 UI
   ↓ GET _state.json → 載入瀏覽器 in-memory

③ User 編輯
   ↓ 所有 pending changes 累積在瀏覽器 state.files[].placements / is_na 等

④ User 按儲存變更
   ↓ PUT 整份新 state
   BE:
     a. diff 舊 vs 新 placements
     b. 對每筆 diff 跑 Drive copy / trash
     c. 把新版 _state.json 覆寫回 Drive
     d. Drive 自動保留舊版 revision(內建 audit log)

好處 vs 取捨

好處 說明
零 schema migration 完全不動 DB,純 Drive 操作
開發快 BE 3 個 endpoint + UI 接 GET/PUT
Audit 內建 Drive file revisions = 免費 audit log
跨 run 自然 isolated 不同 run folder 各自有 _state.json
取捨 應對
不能 cross-run 跨查 實驗期沒需求,正式版再轉 DB
並發編輯衝突 加 ETag / If-Match 鎖;後改的拒絕
大 JSON 全寫 134 檔 ~400KB OK,過萬檔再考慮
Drive API rate limit 編輯 batch save,不要每次 edit 都打

演進路徑

[現在 — 實驗階段]                  純 JSON in Drive,BE 當 thin proxy
       ↓
[v1.0 正式版]                      DB schema (3 tables),Drive 結構照舊
                                   _state.json 仍寫但變 derived export
       ↓
[v2.0 性能版]                      DB 為主,可跨 run 查 + filter
                                   _state.json 純 export 不再 source

v1.0 之後的 DB schema(後階段才做)

此段為未來規劃,v1 實驗階段不實作

3 個新表:

  • evidence_classification_runs — run metadata
  • evidence_classification_results — 每檔每 AO match(含被切掉的低信心)
  • evidence_classification_edits — user 編輯 audit log

具體 schema 詳見後續 Phase 3 implementation plan。


§11

Job 狀態機

queued → running → completed
                ↘ failed (可重試)
                ↘ cancelled (user 主動取消)
狀態 說明
queued 剛建,尚未起 container(通常很短)
running container 跑中,BE 可以顯示「已處理 X/Y」進度
completed 分類完成,user 可進入審閱
failed container 異常退出,user 可重試
cancelled user 主動取消(少見)
§12

Edge cases

情境 處理
檔案太多(>500) BE 分批起 container(每批 100 檔),UI 顯示批次進度
單檔太大(>50MB) 跳過 + log 警示,列在「未處理」清單供 user 知悉
不支援檔型(.zip / .mp4 / .pdf 純圖等) 跳過 + 顯示在「未處理」清單
同名檔案多次上傳 Drive 允許重名,UI 用 Drive 內部 fileId 區分顯示
Container 中途失敗 保留已處理的 report 部分,user 可選「採用部分結果」或「重跑全部」
Review 期間 user 又上傳新檔 新檔不在本 job 範圍,需要再跑一次自動分類
同專案重複觸發 第二次點按鈕擋下,顯示「已有分類 job 在跑」
Drive OAuth 失效 Container 偵測到 401 → fail job + 提示 user 重新連線 Drive
AO 資料夾已被 user 手動建過 重用既有資料夾,不重建
AO 資料夾下已有同名檔 跳過 copy(避免重複),report 標記「已存在」
§13

v1 範圍

✅ 包含:

  • CMMC L1 框架支援
  • 單專案單 framework
  • 一次完整 batch 分類 + 完整審閱
  • Manager 觸發 / Auditor 審閱
  • 自動 AO 資料夾結構建立
  • Audit log

❌ v1 不包含(後續再說):

  • 多框架(NIST 800-171 / ISO 27001 / CMMC L2-3)
  • 真即時分類(檔案一上傳就分類)
  • Mobile UI
  • 自訂 prompt / 自訂閾值 per AO
  • 跨專案批次操作
  • 多人協作審閱(同時編輯同一份 job 結果)
  • 分類結果版本比對(這次 vs 上次)
§14

待釐清(給 brainstorm phase 解)

  1. 權限細節:除了 manager,PM / auditor / 其他角色各有什麼權限?
  2. 通知機制:完成時要 in-app notification、email、Drive 通知?哪些必要?
  3. 失敗重試策略:是「全部重跑」還是「只跑失敗的」?是 user 手動還是自動?
  4. 歷史 jobs:要保留歷次分類結果做比對 / audit 嗎?儲存多久?
  5. 成本控管:Claude API call 成本,要設 per-project quota?月度上限?超量怎麼處理?
  6. N/A 檔的處理:標為「非證據」後是純 flag、隱藏?還是搬到 _archived 資料夾?
  7. 若 AO 資料夾已有手動分的檔:自動分類會 merge(不刪舊只加新)還是 skip?
  8. Container 失敗的觀測:BE 如何拿到 container 內的 error log?mount volume 寫日誌?
  9. 批次規模 sweet spot:BE 起 1 個 container 跑 130 檔,還是起 5 個 container 各跑 26 檔(平行)?
  10. 信心閾值要不要可調:UI 上要不要讓 user 在按按鈕時調 confidence(預設 0.80)?
  11. Run folder 保留策略:多次跑後 Drive 會累積 N 個 run folder,要不要設「保留最近 X 次 / 自動歸檔到 archive 子資料夾 / 超過 Y 天自動 trash」?
  12. 多 run 並存的 UI:審閱頁要不要支援切換不同 run 看?預設看最新 run,可下拉切到歷史 run?
  13. 第二次跑的 base:第二次跑分類時,要拿「目前 Evidences 根目錄」當輸入,還是「上次 run folder 內的檔」?影響到 user 新增 / 刪除原檔後的行為。
§15

建議下一步

  1. 跟 stakeholder 過一遍上述「待釐清」清單
  2. 進 Phase 1 brainstorm(用 superpowers:brainstorming skill)細究 UX 流程
  3. 進 Phase 2 歸檔到 docs/features/FR-030-2605-auto-evidence-classification/
  4. Phase 3 寫 implementation plan(含 BE schema 變更 / API 設計 / FE 元件 / Docker image build 流程)
§16

相關既有資產

  • POC script: scripts/evidence/classify/classify_evidence_drive.py
  • CMMC L1 AO catalog: docs/reference/CMMC-Level 1-Evidences/cmmc_l1_aos.json
  • 雛形 UI: scripts/evidence/classify/prototype-ui/index.html
  • 分類結果範例: scripts/evidence/classify/classification_report_drive.json
  • 證據 sample data: docs/reference/CMMC-Level 1-Evidences/CMMC-Level 1-Evidences-sample/
  • AO 分類分歧分析: docs/analysis/2026-05-27-cmmc-ao-classification-disputes.md