---
title: "FR-142 稽核結果逐 AO 判定重做 — 白話需求"
---

# FR-142 稽核結果逐 AO 判定重做（白話需求）

母卡 [CM-2818](https://app.notion.com/p/FR-142-AO-Excel-3fb346da4cd0819c8d6dcb40beb8185f)；第 0 棒（討論稿＋雛形）[CM-2819](https://app.notion.com/p/FR-142-0-HTML-AO-Excel-3fb346da4cd081d493fad9d80e2eb28f)。

## 這系列在做什麼

現在稽核階段的判定頁是「一個控制項一個判定」：畫面擠、填寫框小，要先到別頁建風險再回來勾，很難用。稽核人員到現場拿的是 FR-141 那份逐評估目標（AO，控制項底下一條一條要查的檢查點）的稽核計畫，照表逐條查，結果也應該逐條記。亞航顧問交回的附件 Excel 正是這樣：稽核計畫的欄位原封不動，後面多「佐證資料」「稽核結果」兩欄。

本案做四件事：

1. **判定頁整個重做**，沿用 FR-141 的頁面骨架。左側是控制族群→控制項導航（完成狀態點、「未判定／不符合」篩選），中間一次一條控制項、每條 AO 一張卡，填：判定、發現（現場看到的事實）、佐證資料、風險嚴重度、風險說明、建議。稽核計畫填好的查核方法、受評對象、稽核人員以唯讀方式帶在旁邊參考。控制項結論自動推：任一條 AO 不符合或有風險就是不符合，稽核人員不用自己填。
2. **第三個頁籤「稽核結果（文字版）」**，對應亞航內部稽核報告第 7 章「稽核結果」。上方唯讀帶出第 1～6 章（依據、目的、稽核單位、稽核日期、範圍、方法，全部從稽核計畫來），下方一個大編輯器讓稽核人員直接寫，想分幾點就分幾點。旁邊一顆「帶入不符合項目」按鈕，把這次判為不符合的 AO 連同發現文字塞進編輯器當草稿；全部符合就自己寫。
3. **Excel 匯入匯出**：就是 FR-141 那份四頁 Excel（填寫說明／計畫概要／評估目標／可選清單）。評估目標頁在稽核計畫欄（灰底唯讀）後面加「稽核結果、佐證資料、發現、風險嚴重度、風險說明、建議」六欄，可選清單加判定與嚴重度的值域。匯出的檔案就是匯入範本，隱藏的 part_id 欄當比對鍵。不另開工作表。
4. **報告匯出**：Word／PDF，第 1～6 章從稽核計畫帶、第 7 章用文字版內容、附件是逐 AO 結果表；另可匯出 Excel 或打包 zip，三選一比照 FR-141 匯出。

## 為什麼要做

- 現行判定頁難用：FE `RoundAuditReviewView.vue` 1,083 行（超過 800 行上限），判定做在控制項層，風險要跨頁建。
- 稽核人員的真實工作方式是拿稽核計畫表逐條 AO 查，系統粒度對不上。
- 目前匯出的 OSCAL 稽核結果（AR）不合規，這次一併修（見下方「套件既有錯誤」）。

## 決策（決策者 2026-10-16 已裁）

- **D1 判定值域**：畫面與 Excel 四個選項——符合／不符合／不適用／未判定。「未判定」是初始狀態、不算判定；Excel 空白＝未判定；結案時還有未判定要擋。OSCAL 存法：符合／不符合 → finding 的 `target.status.state` 存 `satisfied`／`not-satisfied`；不適用 → `target.implementation-status=not-applicable`，state 存 `satisfied`、reason 存 `other`，匯出才合規；未判定＝不建 finding，不落假值。套件自創的 `pending`／`product_state` 一併清掉。
- **D2 風險粒度**：一條 AO 就地填一筆風險（嚴重度／說明／建議三格）。每條填了風險的 AO 背後自動生一筆獨立 risk，用連結表綁該 finding；建議存 risk 的 remediation（`lifecycle=recommendation`）。保留「風險」頁籤當總覽，可在那裡把多條 AO 的風險合併成一筆（進階功能，不是主路徑）。
- **D3 第 7 章文字**：一個大編輯器直接寫，不固定子節；「帶入不符合項目」是加分的草稿功能。整段存 AR result 的一個 attestation part，匯出時原樣排進第 7 章。
- **D4 舊資料不遷移**：新開的稽核次一律用 AO 層 finding；既有控制項層 finding 的稽核次維持舊讀法。
- **D5 控制項結論不存欄位**：由 AO 即時推導。目前 FE、BE 匯入聚合、FR-140 縮範圍規則三處各寫一套，要收斂成套件裡一支。
- **Excel 帶稽核計畫欄**：同一份 Excel，稽核計畫欄灰底唯讀、匯入時忽略，讓人看表就知道這一列要查什麼。

## OSCAL 落點

| 畫面 | OSCAL AR |
|---|---|
| 每條 AO 判定 | `finding`（每 AO 一筆）`target.type=objective-id`、`target-id`=AO part_id、`status.state`／`reason`、`implementation-status` |
| 發現 | `finding.description`（判定理由）＋ `observation.description`（事實），finding 用 `related-observations` 連 |
| 佐證資料 | `observation.relevant-evidence`；`observation.methods` 從稽核計畫的查核方法帶 |
| 風險嚴重度 | `risk.characterizations[].facets[{name=severity, system, value}]`（陣列形狀） |
| 風險說明 | `risk.title`／`description`／`statement`，`status=open` |
| 建議 | `risk.remediations[]`（型別 response）`lifecycle=recommendation` |
| 第 7 章文字 | `result.attestations[].parts` |
| 第 1～6 章 | 從 AP 讀（FR-141 的 `ap_terms_and_conditions`／parties／task timing），AR 不重存 |

## 套件既有錯誤（這次要修）

1. finding 判定存成自創的 `target_status={"product_state":…}`，匯出直接吐進 `target.status`，少了必填 `state`、多了不允許的欄（jedi-oscal-v2 `app/service/io/oscal_io_service.py` 約 1603 行；`domain/service/ar/ar_finding_matrix_service.py` 43-48 行）。
2. 風險嚴重度寫成 `characterizations={"severity":…}`，官方是陣列＋origin＋facets（`app/service/ar/assessment_risk_service.py` 99-100 行）。
3. finding↔risk 連結只寫 `assessment_finding_risks` 表，匯出讀的是 `finding.related_risks` JSONB，兩邊沒同步，service 建的關聯在匯出時會消失。
4. `assessment_remediations.lifecycle` 允許空值且註解寫 recommendation/required；官方必填、值域是 recommendation／planned／completed。
5. observation、attestation、assessment-log 完全沒有 service。
6. jedi-compliance-audit `assessment_result_app_service.py` 約 204 行的 `init_ar_matrix_for_round` 是控制項層一筆 finding，`target_type` 寫 objective-id、`target_id` 卻存 control_id；要改回 AO 層。

## 前提與限制

- 頁面骨架、四頁 Excel 格式、docx 報告範本全部沿用 FR-141，不另做。FR-141 的實作碼目前在 `feature/FR-141` 分支、尚未合回 `feature/FR-139`；實作階段在哪個 branch 接由決策者裁。
- 判定與嚴重度的下拉值域進 `system_menus`，不寫死。
- 參考資料：亞航內部稽核報告第 7 章沒有表格，只有三段文字結論；附件 59 條全是 MET，沒有不符合樣本，發現／風險／建議的內容要自己擬；附件索引號被藏、沒有 part_id。
