---
title: "稽核結果逐評估目標判定重做：左樹到評估目標、稽核報告第 7 章、Excel 延伸稽核計畫、報告匯出 — 討論稿 (FR-142)"
brand: "Guidant AI · **FR-142** 稽核結果重做"
eyebrow: "FR-142 · 討論稿 · 2026-10-16"
h1: "稽核結果逐 AO 判定重做"
subtitle: "左樹三層＋控制項總覽、一次一條評估目標（佐證→稽核發現→稽核結果→風險）、稽核報告頁籤寫第 7 章、Excel 只關聯檔案、報告 Word／PDF"
lede: "現在的稽核判定頁「一個控制項一個判定」、畫面擠、風險要跨頁建；稽核人員現場拿的是 FR-141 那份逐評估目標（AO）的稽核計畫，照表逐條查，結果也該逐條記。本案把判定頁整個重做：左樹到評估目標三層、中間一次一條評估目標，依序填佐證、稽核發現、稽核結果、風險；控制項結論由評估目標自動推算；「稽核報告」頁籤只寫第 7 章，預覽與匯出才組完整報告；Excel 就是稽核計畫那份往後加結果欄，佐證只關聯檔案。資料照 OSCAL 評估結果（AR）的標準欄位存，順手修掉套件三處匯出不合規。D1～D12 已裁、雛形定版，請先開 [雛形](prototype/index.html) 點一遍再看規格。"
chips: [
  {text: "D1～D12 已裁（2026-10-16）", kind: ok},
  {text: "雛形定版", kind: accent},
  {text: "動兩支套件：jedi-oscal-v2、jedi-compliance-audit", kind: crit},
  {text: "站在 FR-141（已併入 feature/FR-139）之上", kind: warn}
]
footer: "FR-142 · 討論稿 · 2026-10-16 · 輸入：決策者 2026-10-16 需求與 D1～D5 裁示、亞航 CMMC L1 內部稽核報告稿與附件稽核紀錄表、OSCAL v1.2.2 AR schema · 現況依據：程式碼行號（2026-10-16）· 沿革見 git log"
---

> 雛形審過、拍板後寫 design.md，本稿不再改寫。

**狀態**：討論稿定版（D1～D12 已裁，雛形定版，待落 design.md）　｜　**日期**：2026-10-16　｜　**母卡**：CM-2818　｜　**雛形卡**：CM-2819　｜　**雛形**：[prototype/index.html](prototype/index.html)

路徑慣例：BE 路徑相對 `compliance-manager-be/`；`pkg/` 代表 `~/Projects/Jedicogy/module/jedi-python-package/<套件>/<套件底線名>/`；FE 路徑相對 `compliance-manager-fe/`。

名詞：**評估目標**＝AO，下文兩詞混用；**AR**＝稽核結果（assessment results），OSCAL 評估層第二個文件；**AO**＝評估目標，一條控制項底下的檢查項目，全系統用 part_id 當鍵（`<控制項代碼>_obj.N`）；**finding**＝OSCAL AR 裡「一條 AO 的判定」物件；**observation**＝稽核人員看到的事實與佐證；**risk**＝風險，可連多條 finding；**remediation**＝風險底下的處置建議；**attestation**＝稽核人員署名的文字陳述；**稽核次**＝FR-140 的概念，一輪內每做一次稽核一筆，各自一份 AP 與一筆 AR result。

## 變更紀錄 {#changelog nav="變更紀錄"}

| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-16 | 定版：雛形經決策者六輪調整後定案。D2 風險改「只在不符合時出現」、拿掉風險總覽頁籤；D3 稽核報告頁籤只留第 7 章、預覽才組完整報告；新增 D6～D12（左樹到評估目標與控制項總覽、評估目標頁順序、佐證來源與採用、Excel 只關聯檔案、結案全鎖、附件橫式中文、表單文案）；P1～P3 併入定案 | 母卡 CM-2818 |
| 2026-10-16 | 初稿：需求、三份參考檔結構、已裁 D1～D5、現況盤點（含套件三處匯出不合規）、OSCAL 落點、Excel 契約、報告匯出、拆卡草案、雛形第一輪 | 母卡 CM-2818 |

---

## 分工概述（30 秒版） {#overview nav="概述"}

整案一句話：**判定頁換成左樹到評估目標、中間一次一條評估目標（佐證→稽核發現→稽核結果→風險），控制項結論自動推算；「稽核報告」頁籤只寫第 7 章，預覽與匯出才組完整報告；Excel 是稽核計畫那份往後加結果欄，佐證只關聯檔案。**

| 棒 | 做什麼 | 產出 | 完成怎麼判定（決策者親手做什麼、看到什麼） |
|---|---|---|---|
| **0 雛形** | 純前端假資料：評估目標判定、稽核報告兩頁籤，匯入預覽，範例 Excel | `prototype/`＋`sample/*.xlsx` | ✅ 已定版（2026-10-16） |
| **1 套件 AR 修正＋評估目標層矩陣** | jedi-oscal-v2：finding 狀態改存 OSCAL `status.state`／`reason`／`implementation-status`、risk 等級改 facets 陣列、finding↔risk 連結匯出改讀連結表、remediation lifecycle 必填、新增 observation／attestation service；jedi-compliance-audit：新稽核次不預建 finding、控制項結論規則收斂成一支 | 套件 migration、service、匯出合規 | DEV 開新稽核次，`oscal.assessment_findings` 該 result 筆數＝0；判一條不符合後匯出 OSCAL JSON 用官方 schema 驗過 |
| **2 BE 評估目標判定端點** | 讀取（合成逐評估目標列＋候選佐證＋控制項結論＋統計）、整批存、稽核報告第 7 章、結案守門與鎖定、佐證凍結 | 新 app service 檔、route、schema | Swagger 存三條評估目標再讀回一致；結案 API 在有未判定或第 7 章空白時回 412；結案後寫入回 409 |
| **3 FE 頁面重做** | 新頁殼（左樹三層、控制項總覽、評估目標頁、稽核報告頁籤、報告預覽）、舊 `RoundAuditReviewView.vue` 退役 | FE 新檔、路由 | 對照雛形逐頁操作一遍，每個動作對應後端真資料 |
| **4 Excel 匯入匯出** | 稽核計畫 workbook 加結果欄讀寫、佐證檔名比對、匯入預覽就地處理 | `ar_workbook_*.py`、FE 預覽模式 | 匯出改兩格與一個檔名匯回，預覽標出、確認後畫面更新；寫錯檔名預覽要求處理 |
| **5 報告 Word／PDF** | 沿用 FR-141 docx 範本，正文直式 1～7 章、附件橫式 8 欄；匯出三選一 | `ar_report_export_service.py`、範本 | Word 開檔看到 1～7 章與橫式附件，判定為中文 |
| **6 FR-140 對齊與選單** | 縮範圍規則讀評估目標層 finding；判定與風險等級選單 seed | `pass_scope_rule.py`、seed migration | 第 2 次內稽新 AP 只剩不符合評估目標所屬控制項；後台看到新選項 |

依賴：1 先做（2～5 都靠它的資料形狀）→ 2、4、5 可平行 → 3 等 2 → 6 等 1。全部接在 `feature/FR-139`（FR-141 已併入）。

```{.mermaid cap="圖 1 — 棒次依賴"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
    P[棒 0 雛形<br/>CM-2819] --> R{決策者審}
    R --> D[design.md<br/>開 1～6 的卡]
    D --> B1[棒 1 套件 AR 修正<br/>AO 層矩陣]
    B1 --> B2[棒 2 BE 判定端點]
    B1 --> B4[棒 4 Excel]
    B1 --> B5[棒 5 報告]
    B2 --> B3[棒 3 FE 三頁籤]
    B1 --> B6[棒 6 FR-140 對齊]
    F141[FR-141 已併入<br/>feature/FR-139] -.骨架、Excel、docx 範本.-> B3
    F141 -.-> B4
    F141 -.-> B5
```

---

## 需求 {#need nav="需求"}

### 決策者原話（2026-10-16）

> 目前稽核階段的 UI 也是只能針對控制項來填寫，應該也是要比照稽核計畫，要 by AO，且針對每項 AO 去寫他通過不通過、finding、風險，以及建議等等 OSCAL 需要的資訊；該控制項如果有一個 AO 不符合或是有風險，那就表示該控制項不符合，預設就幫控制項帶不符合。
> 這邊也要可以用 Excel 匯入匯出的方式，讓稽核人員可以利用 Excel 去填寫，內容可以參考稽核計畫，因為稽核人員應該就是會拿著稽核計畫那份去現場按照表做稽核，所以要用它來延伸需要欄位來填寫。
> 這邊的 UI 要整個重新設計，現在的也是有夠難使用，畫面小小的、填寫處只有一點點位置。請參考 FR-141 的逐項計畫填寫處那邊的模式：左側導航模式、中間改成稽核專用的；再多開一個 tab 是拿來寫文字版稽核結果的，就是接續原本 AP 的 word 的第 7 大項，1～6 看有沒有需要放在畫面上讓 user 參考，完成後就可以匯出一份 word／PDF 報告了。
> 資料盡量放入 OSCAL 正確的欄位，不要亂使用，一樣可以修改套件。

### 三份參考檔長什麼樣

::: grid2
::: {.card .ok}
**亞航內部稽核報告（docx）**

八個編號段：1 依據、2 目的、3 稽核單位、4 稽核日期、5 稽核範圍、6 稽核方法、**7 稽核結果**、8 附件（只有一行標題）。第 7 段是三條純文字結論（7.1 現場網路與架構圖一致、7.2 實體管控依 SSP 實施、7.3 各項實務要求皆取得證據），**沒有表格、沒有缺失統計、沒有建議**——因為該次全 MET。1～6 段與 FR-141「計畫概要」頁 1:1。
:::
::: {.card .ok}
**亞航附件稽核紀錄表（xlsx）**

一張表 59 列（一列一條 AO）、8 欄：序號、索引號（**隱藏**）、實務要求、執行說明（只在控制項第一列填）、控制目標（AO）、「Status實作狀態」（實為查核方法與指引）、**Evidence佐證資料**、**稽核結果**。59 筆全 `MET`，**沒有不符合樣本、沒有發現／風險／建議欄**。沒有 part_id，對位靠「同索引號第幾列」；索引號尾端有多餘空白。
:::
:::

**對照 FR-141 範例 Excel**：兩份的 AO 排法一致（每 AO 一列、控制項層欄只在第一列填），顧問附件比 FR-141 多的只有「佐證資料」「稽核結果」兩欄。所以「結果檔」直接沿用 FR-141 的列結構、用隱藏的 part_id 當比對鍵，後面加欄即可。發現／風險嚴重度／風險說明／建議四欄兩份都沒有，值域由本案定。

### 新畫面長什麼樣

雛形 [`prototype/ar-authoring.html`](prototype/ar-authoring.html) 兩個頁籤：

1. **評估目標判定**
   - **左樹三層**：控制族群 → 控制項 → 評估目標，每層最前面一顆燈號（符合綠實心、不符合紅實心、不適用灰圈加斜線、未判定空心），hover 顯示稽核結果與全文；評估目標文字最多兩行。篩選：全部／未判定／不符合。
   - **點控制項＝控制項總覽**：控制項結論、判定依據（例「評估目標 [a][c] 不符合」）、四類計數，下方逐評估目標列稽核結果、稽核發現、風險等級，點列進入該評估目標。
   - **點評估目標＝評估目標頁**，由上往下：稽核計畫（摺疊參考）→ 佐證 → 其他佐證 → 稽核發現 → 稽核結果＊ → 風險（只有判不符合才出現，符合時留「＋ 新增風險」連結）。底部上一條／下一條在評估目標間跳。
2. **稽核報告**：只有第 7 章「稽核結果」一個編輯器，旁邊「帶入不符合事項」「預覽報告」。預覽開全螢幕：第 1 頁直式正文 1～7 章，第 2 頁橫式附件 8 欄，右上角下載 Word／PDF。

頁首：匯入 Excel、匯出（稽核報告／稽核結果工作表（Excel）／全部檔案）、儲存、結案；頂部六格統計（控制項、符合、不符合、不適用、未判定、風險）。

---

## 已裁決策 {#decisions nav="已裁"}

::: {.callout .decided}
**D1 判定值域＝符合／不符合／不適用三值，加「未判定」當初始狀態但不算判定**

- 畫面與 Excel 四個選項都看得到；Excel 空白＝未判定；送出結案時擋「還有未判定」。
- OSCAL 存法：符合／不符合 → `finding.target.status.state`＝`satisfied`／`not-satisfied`；不適用 → `finding.target.implementation-status.state`＝`not-applicable`，`status.state` 同時填 `satisfied`、`reason`＝`other` 讓匯出合規；未判定＝**不建 finding**，不落假值。
- 清掉套件自創的 `pending`／`not-applicable` 狀態 token 與 `target_status={"product_state":…}`。

**為什麼**：稽核人員現場一定有「還沒查到」的狀態，不能逼他先選；但 OSCAL 的 `status.state` 是封閉值域（只有 satisfied／not-satisfied），「未判定」「不適用」沒有對應值，硬塞進去匯出就不合規。`implementation-status` 官方語意是「控制項實作到什麼程度」，`not-applicable` 是它的正規值，放這裡最貼。

**被排除**：維持現狀四值全進 `status.state`（匯出不合規）；「不適用」改成不建 finding（報告附件要印 N/A，沒 finding 印不出來）。
:::

::: {.callout .decided}
**D2 風險在評估目標上就地填，只有判不符合時出現；不設風險總覽頁籤**

- 評估目標頁判「不符合」時展開風險區：風險等級（無／低／中／高）、風險說明、改善建議。判符合時收起，留「＋ 新增風險」連結給「符合但有疑慮」的少數情況。
- 存法：每條填了風險的評估目標自動生一筆 `risk`（`status`＝`open`），用 `assessment_finding_risks` 連結表綁到該 finding；改善建議存 `risk.remediations[]` 一筆 `lifecycle`＝`recommendation`。
- 判準「有風險」＝風險等級或風險說明任一有值；有風險的評估目標，所屬控制項判不符合（D5）。

**為什麼**：顧問習慣一列填完；「先建風險再勾評估目標」是現在頁面難用的主因。大多數評估目標是符合，風險區收起畫面短一半。

**被排除**：維持「風險獨立建、再勾」為主路徑（難用）；風險總覽頁籤與合併多筆（雛形第一版有，決策者看過後拿掉：風險已逐評估目標呈現、報告附件也逐列印，合併功能 Excel 無法來回）。
:::

::: {.callout .decided}
**D3 「稽核報告」頁籤只寫第 7 章；1～6 章與附件只在預覽與匯出時組出完整報告**

- 頁籤只有一個大編輯器，像在 Word 打第 7 章一樣寫，想分幾點分幾點。
- 「帶入不符合事項」把判不符合的評估目標依「控制項代碼 標題 [評估目標]：稽核發現／風險（等級）／改善建議」格式接在現有內容後面；無不符合時提示「無不符合事項」。
- 「預覽報告」開全螢幕：第 1～6 章來自稽核計畫（FR-141 的 `ap_terms_and_conditions` 等），第 7 章為本編輯器內容，附件為逐評估目標結果表（D11）。
- 存法：整段存 `ar_results.attestations[]` 一筆，`parts[]` 一個 part（name 固定 `audit-conclusion`），`responsible-parties` 帶稽核人員。

**為什麼**：1～6 章與附件放在頁籤裡頁面太長，使用者會誤以為沒地方寫；第 7 章才是這一頁要做的事。OSCAL 的 `attestation` 官方語意是「評估者寫的文字陳述」，比 `result.description` 或 `remarks` 貼切。

**被排除**：固定三個子節（看不懂）；左右分欄唯讀 1～6 章（難看）；1～6 章與附件全攤在頁籤裡（太長）。
:::

::: {.callout .decided}
**D4 舊資料不遷移，只影響新開的稽核次**

- 稽核次開始時若該次 AR result 已有 finding（舊控制項層）就維持舊讀法；新開的稽核次一律 AO 層。判斷依據：finding 的 `target_id` 是否含 `_obj.`。
- 舊頁 `RoundAuditReviewView.vue` 退役後，既有控制項層稽核次用新頁唯讀顯示（每控制項一條合成卡），不開放編輯。

**為什麼**：舊輪資料只有 DEV 與少數測試專案；把控制項判定拆到每條 AO 要猜，猜錯比不猜糟。FR-140 已有「每次稽核一筆 result」的分界點可借用。
:::

::: {.callout .decided}
**D5 控制項結論不存欄位，由 AO 即時推導；三處各自實作收斂成套件一支**

- 規則：任一 AO 不符合或掛風險 → 不符合；全部 AO 不適用 → 不適用；還有未判定 → 未判定；其餘 → 符合。
- 落點：jedi-compliance-audit 一支純函式（`ar_import/aggregation.py` 的 `aggregate_verdict` 擴充「掛風險」條件），FE 不再自己算、FR-140 縮範圍規則改呼叫它。
- 統計（控制項數／符合／不符合／不適用／未判定／風險數）由 BE 回傳，FE 只顯示。

**為什麼**：現在 FE（`RoundAuditReviewView.vue:411`）、BE 匯入（`aggregation.py:10`）、縮範圍（`pass_scope_rule.py:12`）三套規則各寫一次，「掛風險也算不符合」是新條件，三處改必漏。OSCAL AR 本來就沒有控制項層判定欄，存了反而要維護一致性。
:::

::: {.callout .decided}
**D6 左樹到評估目標三層，點控制項看控制項總覽**

- 左樹：控制族群 → 控制項 → 評估目標，與專案規劃頁同（FR-141 稽核計畫只到控制項，是 FR-141 做淺了）。
- 燈號放最前面，顏色加形狀區分四態，色弱可辨；hover 顯示「稽核結果：X」與全文；樹上方一列圖例。評估目標文字最多兩行。
- 點控制項：中間顯示控制項總覽（結論、依據、計數、逐評估目標結果表）；點表格列進評估目標頁；評估目標頁頁首有返回箭頭。
- 中間頁首「控制項結論」保留文字膠囊。

**為什麼**：右側膠囊把評估目標與控制項說明擠到被截斷；中間一次只放一條評估目標，資訊才不會擠在一起；點控制項要看得到控制項結果。

**被排除**：膠囊放右側（擠）；文字無限換行（樹太長）；點控制項直接跳第一條評估目標（看不到控制項結果）。
:::

::: {.callout .decided}
**D7 評估目標頁照稽核人員實際做事的順序：佐證 → 其他佐證 → 稽核發現 → 稽核結果 → 風險**

- 欄位：佐證（清單）、其他佐證（訪談、現場觀察等文字）、稽核發現、稽核結果＊（四選一）、風險（D2）。
- 「其他佐證」與佐證清單分開；拿掉檔案旁的備註框——要指出檔案哪一頁寫在稽核發現。

**為什麼**：稽核人員是先看證據、寫下看到什麼、最後才判過不過；第一版把判定放最上面等於先下結論。備註框與「其他佐證」太像，使用者分不出來。
:::

::: {.callout .decided}
**D8 佐證＝這條評估目標對到的任務佐證（檔案、連結、問卷），一張平的清單，預設全部採用，取消勾選＝不採用**

- 來源：`public.workflow_execution_control_mapping` 對到該控制項／評估目標的任務，底下 `compliance.job_evidences` 類型 FILE／LINK／SURVEY 的項目，合成一張清單、同筆去重；不分任務顯示，每列後面標上傳者與日期。
- 只有檔案、連結、問卷可採用；任務討論（`job_execution_comments`）與報告類型不列。
- 「加入其他佐證」：從整個專案其他任務的佐證挑；「現場上傳」：稽核人員現場拍的照片，存為本次稽核的佐證，清單上標「現場」。
- **「採用」的語意**：記在稽核結果這邊，表示「本評估目標判定採用這些證據」，不改任務端資料、不標有效無效；同一份證據可被多條評估目標各自採用。
- 判「符合」但沒有任何佐證：提示「未附佐證」，不擋。

**為什麼**：舊頁 `RoundAuditReviewView` 是「上面頁籤瀏覽、下面觀察表單另外多選關聯」兩塊；併成一張清單，看與勾在同一處。稽核人員不需要知道任務分組；討論是溝通紀錄、不是證據，有用的內容應寫進稽核發現。

**被排除**：自編證據池（第一版，未對應系統資料）；依任務分組（看不出需要）；討論可勾選採用（決策者裁：只供參考，後再裁拿掉）；回到舊頁頁籤式（一條評估目標佐證通常三五筆，分頁籤要一直切）。
:::

::: {.callout .decided}
**D9 Excel：與稽核計畫同一份四頁，計畫欄灰底唯讀，後接結果欄；佐證只關聯檔案**

- 結果欄：稽核結果、佐證檔案、其他佐證、稽核發現、風險等級、風險說明、改善建議。
- **佐證檔案**一行一個檔名，只含檔案與現場上傳；刪一行＝不採用、加一行＝採用。連結與問卷只在畫面設定，Excel 不出現也不讀。
- 「可選清單」頁新增「佐證檔案」欄，列出專案內可關聯的檔名供複製。
- 匯入時檔名比對專案檔案；對不到或同名多筆時，預覽頁就地處理：對應到某個檔案／改為其他佐證文字／略過。

**為什麼**：稽核人員不會照「類型：名稱」格式寫；檔名是唯一實際會出現在 Excel 的證據形式，也是亞航附件的寫法。
:::

::: {.callout .decided}
**D10 結案＝本次稽核整份鎖定**

- 結案守門三條：尚有評估目標未判定、不符合項目未填稽核發現（或有風險等級未填風險說明）、第 7 章未填寫。任一成立即擋，列出原因。
- 通過後再確認一次；結案後判定、風險、佐證、第 7 章全部唯讀，畫面顯示「本次稽核已於 YYYY-MM-DD 結案，內容不可修改」。
- 結案時凍結每筆採用佐證當下的版本（檔案記雜湊值與版本；連結記網址；問卷記填答快照識別），之後受稽單位更換檔案不影響稽核紀錄。
- 要改只能在下一次稽核重做（FR-140 流程）。

**為什麼**：決策者原話「過了就過了，那不是結案報告，不然一直改等等會有爭議」。結案後產生 POA&M，若仍可改判定會與 POA&M 對不上。
:::

::: {.callout .decided}
**D11 報告附件橫式 8 欄、判定印中文**

- 正文 1～7 章直式；附件獨立一節橫式（docx 分節換方向）。
- 8 欄：序號、索引號、實務要求與執行說明（合一欄）、控制目標、查核方法：指引、佐證資料、稽核結果、稽核發現與風險（合一欄）。
- 稽核結果印「符合／不符合／不適用／未判定」，不印 MET。不符合列淡紅底。
- 預覽與匯出可取消「含稽核發現與風險」，取消後為亞航原表 7 欄。

**為什麼**：10 欄直式 A4 每欄不到 2 公分不可讀；合併兩組欄後與亞航原表寬度相近。
:::

::: {.callout .decided}
**D12 表單文案用專業用語，不放說明句**

- 欄位、按鈕、提示訊息用表單慣用詞（佐證、稽核發現、稽核結果、風險等級、改善建議、選擇文件、上傳、結案、已儲存、匯入完成）。
- 不放 placeholder 範例句、欄位旁說明句、頁首說明列；驗證訊息寫成「尚有 N 項評估目標未判定」「不符合項目須填寫稽核發現」這類格式。
- 判定類 badge 與燈號全頁同一套樣式；不使用左側粗色邊框卡片（見 CLAUDE.md UI 禁令）。
:::

---

## 現況盤點：接入點 × 現況 × 本案動作 {#inventory nav="盤點"}

座標以 2026-10-16 為準，runner 開工先開檔核一次。**FR-141 已完整併入 `feature/FR-139`**（`git merge-base --is-ancestor` 成立），其頁面骨架、六支 `app/flow_control/service/ap_*.py`、docx 範本都在主工作樹。

### ① AR 資料模型（套件 jedi-oscal-v2，schema `oscal`，`pkg/jedi-oscal-v2/infra/model/ar/`）

| 表 | 現況 | 本案動作 |
|---|---|---|
| `assessment_results`（`oscal_ar.py:27`） | `uid`、`metadata_id`、`status`、`import_ap_id`、`local_definitions` | 不改 |
| `ar_results`（`oscal_ar_results.py:25`） | 每稽核次一筆（FR-140）；`reviewed_controls` NN、`attestations`／`assessment_log` JSONB **無任何 service 寫入** | 棒 1：加 attestation 寫入 service；`reviewed_controls.control-objective-selections` 寫入本次 AO 清單 |
| `assessment_findings`（`oscal_ar_finding.py:26`） | `target_type` 永遠 `objective-id` 但新輪 `target_id` 存 control_id；`target_status_state` 存自創 token（`pending`／`not-applicable`）；`target_status` JSONB 存 `{"product_state":…}`；**沒有** `target.status.reason`／`implementation-status`／`target.description` 欄 | 棒 1：每 AO 一筆、`target_id`＝part_id；`target_status` JSONB 改存 OSCAL 形狀 `{state, reason, remarks}`；加 `implementation_status` JSONB；`description` 存判定理由 |
| `assessment_observations`（`oscal_ar_observation.py:26`） | 欄位齊全（`methods` NN、`relevant_evidence`、`subjects`、`collected`）；**無 service**；finding→observation 只有 JSONB 軟參照 | 棒 1：加 observation service；每條有發現或佐證的 AO 一筆，`methods` 從 AP activity 的方法帶、`relevant_evidence[].description` 放佐證文字 |
| `assessment_risks`（`oscal_ar_risk.py:25`） | `characterizations` 被寫成 `{"severity":…}`（`assessment_risk_service.py:99-100`），**不是 OSCAL 陣列形狀** | 棒 1：改 `[{origin:{actors:[稽核人員]}, facets:[{name:'severity', system:'http://csrc.nist.gov/ns/oscal', value:'高'}]}]` |
| `assessment_finding_risks`（`oscal_ar_finding_risk.py:25`） | `link_findings` 只寫這張表；匯出讀 `finding.related_risks` JSONB，**沒同步**，service 建的關聯匯出會消失 | 棒 1：寫連結表時同步回填 JSONB（或匯出改讀連結表，二擇一，建議後者） |
| `assessment_remediations`（`oscal_ar_remediation.py`） | `lifecycle` 可空、註解寫 recommendation/required；官方必填且值域 recommendation／planned／completed | 棒 1：NOT NULL＋CHECK 三值；建議一律 `recommendation` |
| `ar_assessment_subjects` | AR 層受評對象 | 不改 |

### ② 套件服務與主專案端點

| 元件 | 現況 | 本案動作 |
|---|---|---|
| `pkg/jedi-oscal-v2/domain/service/ar/ar_finding_matrix_service.py:40-48` | `init_finding_matrix` 每個 id 一筆 finding、初始 `pending`；`_STATE_TO_TOKEN` 四值對照 | 棒 1：初始化**不建** finding（未判定＝無 finding）；upsert 改寫 OSCAL 形狀 |
| `pkg/jedi-oscal-v2/app/service/io/oscal_io_service.py:1603-1608` | 匯出時 `target_status` 有值就原樣吐進 `target.status` | 棒 1：改組 `{state, reason}`＋`implementation-status` |
| `pkg/jedi-compliance-audit/.../assessment_result_app_service.py`（860 行，超標） | `init_ar_matrix_for_round`（:204）傳 control_ids；`judge_finding`（:536）；observation／risk／remediation CRUD（:572～:802）；`finalize_assessment`（:834）看 pending 數擋 | 棒 1：`init` 改傳 AO part_id 清單；棒 2：**新開檔** `ar_objective_app_service.py` 放整批存、統計、結案守門，不往 860 行裡加 |
| `pkg/jedi-compliance-audit/.../ar_import/aggregation.py:10` | `aggregate_verdict` 四規則，只有 FR-044 匯入在用 | 棒 1：加「掛風險」條件，成為 D5 唯一規則 |
| `pkg/jedi-compliance-audit/.../pass_scope_rule.py:12` | `carried_control_ids`：AO 沒自己的 finding 時退回控制層 | 棒 6：直接讀 AO 層 finding＋D5 規則 |
| 主專案 route `api/project/routes/audit_round_route.py:235-395` | `/audit-round/<round_uid>/ar/findings`（GET）、`finding/<uid>`（PUT 單筆）、observations／risks CRUD、`risk/<uid>/findings`（PUT） | 棒 2：加 `PUT …/ar/objectives`（整批）、`PUT …/ar/attestation`、`GET …/ar/summary`、`POST …/ar/finalize` 守門改版；舊單筆端點留給舊稽核次 |
| `ar_import_route.py`＋`ar_import_app_service.py`（621 行）＋`airasia_cmmc_l1_ar_v1.py` | FR-044 的亞航格式 xlsx 匯入（序號、索引號、實務要求、說明、AO、方法、佐證、稽核結果） | 棒 4：新主路徑是 FR-142 格式（有 part_id）；亞航原格式 parser 留作「沒有 part_id 的舊檔」退路 |

### ③ FE 現況

| 元件 | 現況 | 本案動作 |
|---|---|---|
| `src/views/project/RoundAuditReviewView.vue`（1,083 行，超標） | 路由 `/project/projects/:id/round/:roundUid/audit`；兩頁籤「控制項稽核判定」「風險」；讀 `ao_findings` 聚合成控制項狀態（:409-424）、存檔把同一判定寫到該控制項全部 finding（:551-555）；風險建在「風險」頁籤再勾 finding | 棒 3：**全新檔** `RoundAuditResultView.vue`＋`ar-result/` 子元件（左樹、控制項總覽、評估目標頁、稽核報告、報告預覽），路由改指；舊檔退役（D4 舊稽核次用新頁唯讀） |
| `src/components/grc/ar-import/ArImportDialog.vue`（720 行） | FR-044 匯入對話框 | 棒 4：改成預覽模式（同 FR-141 做法，不另開清單） |
| FR-141 的 `ObjectiveTab.vue` 等左樹右表單元件 | 已在主工作樹 | 棒 3：抽共用（左樹、控制項頁首、上一條下一條）或直接 copy，由 runner 看耦合度決定 |

### ④ 可沿用的匯出匯入能力

| 能力 | 座標 | 本案動作 |
|---|---|---|
| 稽核計畫 Excel 讀寫六支 | `app/flow_control/service/ap_workbook_{layout,writer,reader,diff,export_service,import_service}.py` | 棒 4：layout 加 `COL_VERDICT…COL_REC` 六欄常數；writer／reader 加結果欄；diff 加結果欄比對；新開 `ar_workbook_export_service.py`／`ar_workbook_import_service.py` 組合 |
| 報告稿 docx＋pdf | `app/flow_control/service/ap_docx_generator.py`、`ap_report_export_service.py`、範本 `app/oscal/templates/ap/ap_report_template.docx`、`SspLibreOfficeConverter` | 棒 5：範本複製成 `ar_report_template.docx`（第 7 章佔位改真內容、附件表橫式加欄）；新開 `ar_report_export_service.py` |
| 解析暫存 | `oscal.ap_docx_parse_jobs`，`source_type` 區分 | 棒 4：`source_type='ar_xlsx'` |

---

## 資料模型草案 {#model nav="資料模型"}

### OSCAL 落點（AR，v1.2.2）

| 畫面欄位 | OSCAL | 落表 |
|---|---|---|
| 稽核結果 | `result.findings[]`（每評估目標一筆）：`target.type`＝`objective-id`、`target.target-id`＝part_id、`target.status.state`＝satisfied／not-satisfied、`target.status.reason`＝pass／fail／other、`target.implementation-status.state`＝not-applicable（不適用時）；未判定＝不建 finding | `assessment_findings`：`target_id`、`target_status`（JSONB 改 OSCAL 形狀）、新欄 `implementation_status` |
| 稽核發現 | `finding.description`（必填；符合且空白時系統補「經檢視佐證，符合本評估目標」）、`finding.title`＝評估目標文字 | `assessment_findings.description`／`title` |
| 佐證（採用的檔案、連結、問卷、現場上傳） | `observation` 一筆：`methods`＝`EXAMINE`、`relevant-evidence[]` 每筆 `href` 指向該系統物件（檔案預覽、連結網址、問卷填答）、`description`＝名稱；`collected`＝存檔時間；finding 用 `related-observations` 連 | `assessment_observations`（`relevant_evidence` JSONB） |
| 其他佐證 | `observation` 另一筆：`description`＝文字、`methods` 照稽核計畫該評估目標的查核方法映射（訪談→INTERVIEW、文件檢查／現場觀察→EXAMINE、測試／設定比對→TEST） | `assessment_observations` |
| 風險等級 | `risk.characterizations[]`：`{origin:{actors:[稽核人員]}, facets:[{name:'severity', system:'http://csrc.nist.gov/ns/oscal', value:'低／中／高'}]}` | `assessment_risks.characterizations`（陣列） |
| 風險說明 | `risk.title`（取說明前段）、`risk.description`＝`risk.statement`＝說明全文、`risk.status`＝open | `assessment_risks` |
| 改善建議 | `risk.remediations[]` 一筆：`lifecycle`＝recommendation、`title`／`description`＝建議文 | `assessment_remediations` |
| 評估目標↔風險 | `finding.related-risks[]` | `assessment_finding_risks` 連結表（匯出改讀此表） |
| 第 7 章稽核結果 | `result.attestations[]` 一筆：`parts[{name:'audit-conclusion', prose:全文}]`、`responsible-parties`＝稽核人員 | `ar_results.attestations` JSONB |
| 本次判定範圍 | `result.reviewed-controls.control-objective-selections[]`＝本次評估目標 part_id 清單 | `ar_results.reviewed_controls` |
| 第 1～6 章 | 從 AP 讀（FR-141） | 不重存 |
| 控制項結論 | **無欄位**，D5 即時推導 | — |

**官方必填核對（v1.2.2 JSON schema）**：finding（uuid／title／description／target，target 再需 type／target-id／status）、observation（uuid／description／methods／collected）、risk（uuid／title／description／statement／status）、remediation（uuid／lifecycle／title／description）、result（uuid／title／description／start／reviewed-controls）、attestation（parts）——上表每一項都有來源，畫面不需多填。

```{.mermaid cap="圖 2 — 一條評估目標存成哪些 OSCAL 物件"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
    AO[評估目標頁<br/>佐證／其他佐證／稽核發現<br/>稽核結果／風險] --> F[finding<br/>target-id=part_id<br/>status.state / reason<br/>implementation-status]
    AO --> O1[observation 佐證<br/>EXAMINE<br/>relevant-evidence=檔案／連結／問卷]
    AO --> O2[observation 其他佐證<br/>methods 從稽核計畫帶<br/>description=文字]
    AO -->|判不符合且有填| K[risk<br/>characterizations.facets severity<br/>status=open]
    K --> M[remediation<br/>lifecycle=recommendation]
    F -.related-observations.-> O1
    F -.related-observations.-> O2
    F -.related-risks 連結表.-> K
    T[稽核報告頁籤第 7 章] --> A[result.attestations<br/>part name=audit-conclusion]
```

### Excel 契約（填寫說明、計畫概要、評估目標、可選清單四頁，延伸 FR-141）

「評估目標」頁欄序：

| 欄 | 內容 | 規則 |
|---|---|---|
| A（隱藏） | part_id | 比對鍵；不在本次稽核範圍的列略過 |
| B～J | 序號、控制項代碼、實務要求、執行說明、評估目標、查核方法與指引、受評人員、受評元件／資產、稽核人員 | **FR-141 計畫欄，灰底唯讀，匯入忽略** |
| K | 稽核結果 | 下拉：符合／不符合／不適用；空白＝未判定；無效值預覽就地對應 |
| L | 佐證檔案 | 一行一個檔名，只含檔案；匯出列已採用者；匯入比對專案檔案，對不到預覽三選一 |
| M | 其他佐證 | 自由文字 |
| N | 稽核發現 | 不符合必填（匯入不擋，結案才擋） |
| O | 風險等級 | 下拉：低／中／高；空白＝無 |
| P | 風險說明 | 與 O 任一有值＝有風險 |
| Q | 改善建議 | → remediation |

「可選清單」頁：稽核結果、風險等級、佐證檔案三欄（黃底），其餘沿用 FR-141。「計畫概要」頁唯讀、匯入不讀。範例：[`prototype/sample/FR-142-稽核結果範例.xlsx`](prototype/sample/FR-142-稽核結果範例.xlsx)（產生腳本 `make_sample_xlsx.py`）。

匯入預覽（[`prototype/import-preview.html`](prototype/import-preview.html)）：同一畫面預覽模式；異動的評估目標標示、原值劃掉；無效值與找不到的檔名標「待確認」就地處理；對不到 part_id 的列略過；全部處理完才能匯入。

### 匯出三選一

`GET /audit-round/<round_uid>/ar/export?format=docx|pdf|xlsx|zip&with_findings=true|false`：

- **稽核報告**（Word／PDF）：標題帶稽核次；第 1～6 章從 AP 帶；第 7 章＝attestation 全文；附件依 D11。
- **稽核結果工作表（Excel）**：上面四頁。
- **全部檔案**（ZIP）：docx＋xlsx。

---

## 端到端流程 {#flow nav="流程"}

```{.mermaid cap="圖 3 — 稽核次內從開始稽核到結案"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
    participant U as 稽核人員
    participant FE as 稽核結果頁
    participant BE as BE ar_objective_app_service
    participant PKG as jedi-oscal-v2 AR services
    U->>FE: 進入稽核次（status=auditing）
    FE->>BE: GET /ar/objectives
    BE->>PKG: 讀 AP activities（AO 清單＋方法）＋ 既有 findings／observations／risks
    PKG-->>BE: 合成逐 AO 列（未判定＝無 finding）
    BE-->>FE: AO 列＋控制項結論（D5）＋統計
    U->>FE: 逐條判定、填發現／佐證／風險／建議；或匯入 Excel
    FE->>BE: PUT /ar/objectives（整批）
    BE->>PKG: upsert finding（OSCAL 形狀）／observation／risk＋remediation／連結表
    U->>FE: 稽核報告頁籤寫第 7 章（可帶入不符合事項）
    FE->>BE: PUT /ar/attestation
    U->>FE: 送出結案
    FE->>BE: POST /ar/finalize
    BE->>BE: 守門：未判定>0 或 不符合無發現 → 412
    BE->>PKG: finalize_assessment（有不符合→POA&M）
    U->>FE: 匯出報告 docx／pdf
    FE->>BE: GET /ar/export?format=docx
    BE-->>U: 1～6 章（AP）＋7 章（attestation）＋附件（findings）
```

---

## 拆卡草案 {#cards nav="拆卡"}

第 0 棒已建卡（CM-2819）。第 1～6 棒在 design.md 定案後一次建完。排序以「決策者何時看得到畫面」為準，但棒 1 是資料形狀的地基必須先做。

| 棒 | 卡 | 做什麼 | 碰哪裡 | 套件 | 依賴 | 驗收 |
|---|---|---|---|---|---|---|
| 0 | CM-2819 | 討論稿＋雛形＋Excel 範例 | `docs/features/FR-142…/` | — | — | ✅ 定版 |
| 1 | 1a | finding 改 OSCAL 形狀（`target_status` JSONB、新欄 `implementation_status`）、清 pending token、匯出改組；risk facets 陣列；匯出改讀連結表；remediation lifecycle NOT NULL＋CHECK；migration | `pkg/jedi-oscal-v2` model／migration／`ar_finding_matrix_service`／`oscal_io_service`／`assessment_risk_service` | jedi-oscal-v2 | design | 判一條不符合匯出 JSON 用官方 schema 驗過 |
| 1 | 1b | observation、attestation service；新稽核次不預建 finding；D5 規則收斂進 `aggregation.py` | 兩支套件 | 兩支 | 1a | 新稽核次 finding 數＝0，判定後＝判過的評估目標數 |
| 2 | 2a | `ar_objective_app_service.py`（新檔）：讀取（合成列、候選佐證走 `workflow_execution_control_mapping`＋`job_evidences` FILE／LINK／SURVEY、控制項結論、統計）、整批存、第 7 章、結案守門＋鎖定＋佐證凍結；route＋schema | `app/flow_control/service/`、`api/project/routes/` | — | 1b | Swagger 存三條讀回一致；未判定或第 7 章空白擋 412；結案後寫入 409 |
| 3 | 3a | FE 新頁：左樹三層＋燈號、控制項總覽、評估目標頁、稽核報告頁籤、報告預覽；路由改指；舊頁退役與舊稽核次唯讀 | FE 新檔 | — | 2a | 對照雛形逐頁操作 |
| 4 | 4a | workbook 加 K～Q 結果欄；檔名比對；`ar_workbook_export_service.py`／`ar_workbook_import_service.py`；可選清單；FE 預覽模式 | `app/flow_control/service/`、FE | — | 1b、3a | 匯出改兩格與一個檔名匯回，預覽標出、確認後畫面更新 |
| 5 | 5a | `ar_report_template.docx`（正文直式、附件分節橫式 8 欄）、`ar_report_export_service.py`、三選一 | `app/flow_control/service/`、`app/oscal/templates/ap/` | — | 2a | Word 開看 1～7 章與橫式附件、判定中文 |
| 6 | 6a | `pass_scope_rule.py` 讀評估目標層；稽核結果與風險等級選單 seed（`system_menus`）＋i18n | `pkg/jedi-compliance-audit`、`scripts/sql/*-seed-*.sql` | jedi-compliance-audit | 1b | 第 2 次內稽新 AP 只剩不符合評估目標所屬控制項 |

---

## 不做清單 {#out nav="不做"}

| 像但不做 | 為什麼 |
|---|---|
| POA&M 改版 | POA&M 另開 FR 整個重做；本案只保證交接點（不符合評估目標的 finding、其 risk 與 remediation，均有固定 uuid）與過渡期「結案有不符合就生 POA&M」照樣能跑 |
| 舊稽核次資料遷移到評估目標層 | D4 |
| CRA 的「系統資料當證據」（風險評估、適用性等 evidence-source） | CRA 另案調整為要求上傳文件；本案以 CMMC 為準 |
| 任務討論列為佐證 | D8：只是溝通紀錄 |
| 連結、問卷在 Excel 關聯 | D9：只在畫面設定 |
| `assessment-log`（現場活動時間軸） | 亞航報告沒有；observation 的 methods 已足 |
| 風險 deadline、risk-log、threat-ids、mitigating-factors | 畫面沒欄位；留 NULL |
| AR 的 OSCAL JSON 匯出入口 | 套件已有 `export_oscal` AR 分支，本案只修它合規，不加前端按鈕 |
| 亞航原格式（無 part_id）xlsx 匯入改版 | FR-044 parser 留作退路，不動 |

---

## 坑與陷阱總表 {#pitfalls nav="坑"}

| # | 坑 | 影響 | 對策 |
|---|---|---|---|
| 1 | `init_ar_matrix_for_round` 現在預建 finding 且 `target_id` 存 control_id（2026-06-17 拍板） | FR-140 縮範圍、FE 聚合、FR-044 匯入都假設「每控制項一筆」 | 1b 改 AO 層時 grep `target_id` 全部讀取者（pass_scope_rule、aggregation、ar_import、FE）一次改；D4 用 `_obj.` 判新舊 |
| 2 | `target_status` JSONB 現存 `{"product_state"}`，匯出直接吐 | 改形狀後舊資料匯出仍不合規 | 1a migration 把舊值轉成 `{state, reason}`；舊 `pending` 列刪除（＝未判定） |
| 3 | finding↔risk 連結表與 JSONB 不同步 | 匯出遺失關聯 | 1a 匯出改讀連結表，JSONB 欄停用（不刪） |
| 4 | `assessment_result_app_service.py` 860 行、`RoundAuditReviewView.vue` 1,083 行、`ArImportDialog.vue` 720 行皆超標 | 不能往裡加 | 2a／3a／4a 全部新開檔；舊檔只改一行導向或退役 |
| 5 | 「有風險」判準＝風險等級或風險說明任一有值 | 只填改善建議 → 不算風險、控制項不會變不符合 | 填寫說明明寫；匯入預覽標「有改善建議但無風險等級」提醒（不擋） |
| 6 | 「帶入不符合事項」接在現有內容後 | 按兩次內容重複 | 實作同雛形：接在後面，不覆蓋 |
| 7 | OSCAL `status.state` 封閉值域；「不適用」要同時填 `satisfied`＋`reason=other`＋`implementation-status` | 漏任一匯出不合規或報告印錯 | 1a 寫在 upsert 一個地方；驗收用官方 JSON schema 跑 |
| 8 | 報告附件欄多 | 直式放不下 | D11：附件節橫式、欄數 8；取消「含稽核發現與風險」為 7 欄 |
| 9 | 同一支 `audit_round_app_service.py` 周邊 FR-140 卡與本案 6a 都要改 | 平行派互撞 | 6a 排在 FR-140 相關卡之後，母卡標序列組 |
| 10 | finding `description` 官方必填，但符合時稽核人員常不寫發現 | 匯出不合 schema | 存檔時空白自動補「經檢視佐證，符合本評估目標」，畫面不顯示這句 |
| 11 | 佐證候選清單要走 `workflow_execution_control_mapping` 權威 scope | 同名檔案跨控制項被吸進來（FR-044 STG 實踩） | 依 memory `feedback_evidence_per_control_scope_wf_control_mapping`：二次過濾 `workflow_execution_id` 真的對到該控制項；`relevant-evidence` 存字串 `file_uid` 不存數字 `file_id` |
| 12 | 結案凍結佐證版本 | 受稽單位結案後換檔，稽核紀錄指向新檔 | 結案時把每筆採用佐證的 `content_hash`／版本寫進 `relevant-evidence.props` |
| 13 | Excel 檔名比對同名多筆 | 兩個任務交了同名檔案 | 預覽就地讓使用者選，不自動挑 |
