# 證據分類「分析結果報表」— 白話需求（Phase 0 draft）

| 項目 | 內容 |
|------|------|
| 提出日 | 2026-05-31 |
| Branch | `feature/ai-analysis-result-landing`（BE + FE 同名）|
| 預計 FR | **FR-031**（新整數號，relates to FR-030 證據自動分類 arc）|
| 範圍 | BE（主專案 + DB + 報表計算）+ FE（兩個新報表頁 + 兩處入口連結），跨 2 repo |
| 狀態 | Phase 0 白話需求，**討論已收斂**，待搬 `docs/features/` 進 Phase 1 |
| 來源 | 既有獨立 Python 報表專案 `~/Desktop/AirAsia 真實證據/CMMC佐證/_驗證/`（`validate.py` / `gen_ao_adjudication.py` + `WEB整合_報告產生規格.md`）|

---

## 0. 一句話

把目前「另一個 Python 專案產出的兩份 HTML 分析報表」，**搬進系統內變成兩個獨立可瀏覽的報表頁**（用前端既有設計樣式重畫），並把每次分類 run 的結果**存進資料庫**（這階段 Drive / DB 兩邊都存）。

---

## 1. 背景與動機（客戶情境）

- 目前證據分類流程：系統 trigger Claude API → 對 Google Drive 上的證據檔做分析、歸類 → 產出幾個 JSON 結果存在 Drive 上（`_report-original.json`、`_state.json`、`_container-log.txt`）。
- 現在要看「這次 AI 分得好不好」，得**跑另一個獨立 Python 專案**手動產出兩份 HTML 報表 → 流程斷裂、非工程人員看不到。
- 我們正處於**分類器調校期**：持續調整 prompt / 換 AI model（sonnet ↔ opus），手上有一份 **User 自己人工分類好的正解資料**，想拿來驗證「AI 與 User 雙方的判斷誰對誰錯」。
- 因此需要把報表能力**整進系統**，讓管理員在系統內就能直接瀏覽分析結果報表，邊調邊看。

---

## 2. 要做什麼 — 兩份報表

### 報告①：驗證報表（量化）

> 對應獨立專案 `validate.py`

- **在問什麼**：AI 這次分類，跟「User 認定的正解」差多少？
- **內容**：
  - 召回率 / 精確率，分**兩種顆粒度**：AO 層級（要中 `[a]/[b]` 子項，較嚴）+ 控制項層級（忽略子項，較高）
  - **缺漏四級**（User 有、AI 此 AO 沒放，嚴重度遞增）：版本/格式差異 → AO 選錯(同控制項) → 分到其他控制項 → AI 完全未分類
  - **多餘四級**（AI 放了、User 此 AO 沒有）：對稱四級
  - **AI 未分類診斷**：未放置檔分三類（真正該補 / 格式替代已涵蓋 / User 從未歸檔），A 類附「可否降門檻救回」
  - **Docker log 健康**：exit_code、處理/分類數、耗時、STDERR、錯誤關鍵字
  - **成本**：token 用量 + 估算費用（可開關，沿用既有 flag 概念）

### 報告②：各 AO 誰對誰錯（質化）

> 對應獨立專案 `gen_ao_adjudication.py`

- **在問什麼**：以**中立的正解基準（CANON）**看，User 跟 AI **同一把尺**檢視，誰把證據放錯了？
- **為什麼需要它**：報告① 預設「User 是對的」，但 User 自己也會誤放（實測這批約 30 格誤放）→ 報告① 會**高估 AI 的錯**。報告② 用固定 CANON 當中立裁判，才看得出 AI 的**真實對齊度**。
- **內容**：
  - 🔴 User 標錯的格子（應修正的人工誤放）
  - 🟠 AI 多放 / 過度的格子
  - ⚫ 超出框架範圍（如 L1 收到 L2 證據，應標 N/A）
  - 逐 AO 逐檔的 User / AI 判定明細
- **判定原則**：控制項精確比對（放在 CANON 列出的控制項＝對，放別的＝錯，即使同家族也算）；CANON 採寬列原則（合理的都列、只排除明確錯的）。

### 兩者關係

並存、互補，**不是二選一切換**：① 給「對照 User 的數字成績」，② 給「AI 對中立正解的真實對齊度」。

---

## 3. 報表的資料來源（已追 code 驗證 ✅）

獨立專案是 offline 跑，靠外部 Excel + `Source_Evidences/` 資料夾。**搬進系統後，這些外部依賴大多不需要**，對應如下：

| 報表概念 | 獨立專案（offline）來源 | **系統內來源** |
|---|---|---|
| **AI 放置** | `_report-original.json` matches≥門檻 | run 的 `_report-original.json`（AI 第一次分析的原始輸出，immutable）|
| **User 正解 / ground truth** | `Source_Evidences/` 外部人工資料夾 | run 的 **`_state.json` placements**（User 在**既有審閱頁**修正後的當前狀態）|
| **CANON**（報告② 基準）| 腳本內寫死 63 檔 | **固定 seed 一份**（per-framework，這階段先固定，不做動態維護）|
| catalog（控制項/AO 定義）| Excel | 系統既有 `cmmc_l1_aos.json`（已是 NIST 編碼）|
| FAR↔NIST crosswalk | `INDEX_TO_NIST` / `SPECIAL_IX` | **不需要** — 系統內 AI 與 ground truth 都已是 NIST ao_id |

### 報告① 走「即時」，不做確認按鈕

- `_state.json` 本來就會隨 User 在審閱頁編輯而持續異動；報表頁是 **on-demand 計算**，每次打開就拿**當下最新的 `_state`** 重算 → 永遠反映即時分類狀態。
- 因此**不加**「確認為正解」按鈕、**不存** approved 快照 / `approved_at` / `approved_by` 欄位。
- **唯一邊界**：User 完全還沒動過的 run，`_state` 還等於 AI 原始 → 即時算會是召回/精確 100%、缺漏多餘 0（無意義）。此時報表頁**自動偵測**（讀 `last_edited_at == null`）→ 顯示一行純資訊提示：「此 run 尚未經人工審閱，目前分類等同 AI 原始輸出，召回/精確僅供參考」。不擋、不需動作。
- **沒人工審閱時要看準度，看報告②**：CANON 是固定 seed、不依賴任何人確認 → 報告② 隨時可跑，剛跑完就能評「這次 prompt/model 調得好不好」，正好服務調校期需求。

---

## 4. 進系統的形態

### 兩個獨立報表頁（唯讀）

- 用**前端既有設計系統（Vue 3 + PrimeVue）重畫**，不沿用獨立專案那套 inline-CSS HTML。
- BE 算出**結構化 JSON**、FE 渲染（規格 Option B；HTML 匯出未列入這階段）。
- **完全獨立、純讀取**：讀該 run 的 `_report-original.json` + `_state.json` + 固定 CANON + catalog → 算 → 顯示。**不寫回、不觸發任何既有流程。**

### 強制約束：不動既有畫面 ⚠️

- **既有的審閱頁 / 歸檔流程 / 比較畫面一律不改**。報表是**額外**的瀏覽能力，不是改造既有功能。

### 兩處入口連結

1. **分析結果頁（run 詳情頁）**
   `/project/projects/<project_uid>/ap/<ap_uid>/classify-evidence/run/<run_folder_id>`
2. **專案總覽 → 自動分類證據 Dialog → 歷史執行資料後面**
   入口頁 `/project/projects/<project_uid>/ap/<ap_uid>`，在「自動分類證據」Dialog 內、歷史執行資料清單**後面**掛上每筆 run 的兩個報表連結。

---

## 5. 範圍

### 這次做 ✅

- 報告①（含完整版 + 未審閱降級版）+ 報告②（固定 CANON 基準）兩頁
- 兩份報表的 BE 結構化 JSON 計算邏輯（移植兩支 Python 演算法）
- 每次分類 run 結果**存進 DB**（這階段 Drive / DB 兩邊都存）
- CANON 與 catalog 走**固定 seed**（一份 cmmc-l1）
- 兩處入口連結

### 這次不做（未來再說）❌

- **動態 CANON**：上線後「記錄每次 User 調整完的分類當 CANON」← 明確延後
- **CANON 規則表 + 稽核員維護 UI**（`canon_rules` 證據類型→控制項規則表）← 延後
- **管理員「核可為正解」流程 / ground_truth 核可表**（因為報告① 走即時，不需要）
- 報表 HTML / PDF 匯出
- 多框架（這階段只 cmmc-l1）
- 完全停用 Drive、只用 DB（這階段兩邊都存，之後才考慮收斂）

---

## 6. 待討論 / 待設計階段敲定的問題

1. **報表頁「即時讀」的後端接法**（不影響需求，留 Phase 2 design）：
   - 方案 a：報表頁直接讀 Drive 當前 `_state.json`（零碰既有 `put_state`）
   - 方案 b：在既有 `put_state` 補一行同步 DB `state` 欄位，報表讀 DB（較貼合「移到 DB」目標，但動到既有後端 method —— 不改畫面）
2. 「未審閱降級提示」那行要不要顯示（純 UX，可最後決定）。
3. CANON 固定 seed 放哪：repo JSON 資源檔（如 `cmmc_l1_canon.json`，零 migration、可直接編輯）vs DB 表。傾向 JSON 資源檔。
4. 報表②「User 側」在 User 尚未審閱時 == AI（兩欄相同）→ 顯示上是否需特別標示（避免誤讀成「User 也放這」）。

---

## 附錄 A. 技術可行性分析（初步，正式版進 Phase 2 design.md）

| 報告 | 可行性 | 關鍵點 |
|------|--------|--------|
| **報告①** | ✅ 高度可行 | 輸入（AI=`_report-original.json`、User=`_state.json`、catalog=`cmmc_l1_aos.json`）系統內全有；系統內已是 NIST → 免 crosswalk；演算法（doc_key 去重 / 四級 / 未分類三類診斷 / Docker log）只吃 JSON，零阻礙 |
| **報告②** | ✅ 演算法可行 | 唯一前提是 **CANON 要存在**；這階段用固定 seed（移植現有 63 檔版本）即可，不需規則表/維護 UI |

**已追 code 驗證的流程串接**：
```
POST classify-evidence → container write_outputs 寫 _report-original.json(AI原始) + _state.json(初始=AI)
   → BE _finalize_drive_output 建 run folder、上傳兩 JSON
   → [既有審閱頁] User 編輯 → put_state 改 source=manual、覆寫 _state.json  ← User 正解落地點
   → [新報表頁] 唯讀讀 _report-original + _state(當前) + 固定CANON + catalog → 算 → 顯示
```

---

## 附錄 B. 資料儲存初步構想（DB，正式 schema 進 Phase 2）

> 原則：**欄位會一直調整 → 大 payload 走 JSONB**；只把要查/排序/顯示的少數欄位升成正式 column。報表**不另存、on-demand 算**（改演算法不用洗舊資料）。

### 主表 `evidence_classification_runs`（鏡像整個 Drive run folder）

| 欄位 | 型別 | 說明 |
|---|---|---|
| `id` | BIGSERIAL PK | |
| `run_folder_id` | VARCHAR | Drive run folder id（自然鍵，現在 URL 用的就是這個）|
| `run_folder_name` | VARCHAR | |
| `tenant_id` / `project_id` / `ap_id` / `org_unit_id` | INT | 查詢 / RLS / 列表索引欄 |
| `framework_id` / `model` | VARCHAR | |
| `confidence_threshold` | NUMERIC | |
| `status` | VARCHAR | queued/running/completed/failed |
| `archive_files` | BOOLEAN | analyze-only vs 已歸檔 |
| `input_file_count` / `classified_count` | INT | 列表顯示 |
| `estimated_cost_usd` | NUMERIC | 跨批比成本（亦留 metadata json 內）|
| `triggered_at` / `completed_at` / `archived_at` / `last_edited_at` | TIMESTAMPTZ | `last_edited_at` 供報告① 判降級 |
| `triggered_by_user_id` / `archived_by_user_id` / `last_edited_by_user_id` | INT | |
| **`report_original`** | **JSONB** | `_report-original.json` 全文（AI 原始，immutable）|
| **`state`** | **JSONB** | `_state.json` 全文（當前狀態，與 Drive 同步）|
| `container_log` | TEXT | `_container-log.txt`（報告① 的 Docker log 診斷段用）|
| 審計欄位 | | created_user / created_dt / updated_user / updated_dt（依專案規範含 *_name）|

- **不需要** `approved_at` / `approved_by`（報告① 走即時，已砍）。
- 報表快取表（`..._report_cache`）這階段先不做，需要時再加。

### 兩份基準（固定 seed，這階段）

| 基準 | 範圍 | 存法 | 未來 |
|---|---|---|---|
| **CANON**（報告② 用）| per-framework | 固定 seed JSON（`file_name → [control_id] + 理由`，移植現有 63 檔）| 規則表 + 維護 UI |
| **catalog**（控制項/AO 定義）| per-framework | 沿用既有 `cmmc_l1_aos.json` | — |

> 報告① 的 ground truth **不另建表** —— 它就是 run 的 `state` JSONB（即時），不是獨立資料集。

---

## 參考

- 獨立報表專案：`~/Desktop/AirAsia 真實證據/CMMC佐證/_驗證/`
  - 報告① 演算法：`validate.py`
  - 報告② 演算法 + CANON：`gen_ao_adjudication.py`
  - 整合權威規格：`WEB整合_報告產生規格.md`
- 系統既有：
  - 證據分類 arc 收尾：`docs/features/FR-030.3-2605-evidence-classify-xlsx-pptx/handoff/2026-05-30-evidence-classify-arc-SUMMARY.md`
  - 輸出 JSON schema：`docs/api/evidence-classification/state-json-schema.md`
  - API 端點：`docs/api/evidence-classification/api-spec.md`
  - BE service：`app/evidence_classification/service/evidence_classification_service.py`
  - container 寫出：`scripts/evidence/classify/docker/container_entrypoint.py` `write_outputs`
