FR-031 — 證據分類「分析結果報表」設計書(SD)

項目 內容
FR FR-031(relates to FR-030 證據自動分類 arc)
Branch feature/ai-analysis-result-landing(BE + FE 同名)
白話需求 raw-requirement.md(Phase 0,已收斂)
狀態 Phase 1 設計 — 待 spec review + user 確認 → 進 Phase 3 implementation-plan
範圍 BE(新增 DDD 持久層 + 報表計算 + 2 報表 API)+ FE(2 報表頁 + 2 入口連結),跨 2 repo;jedi-* 不動

白話「為什麼做 / 客戶情境 / 兩份報表內容」見 raw-requirement.md,本文件只談技術設計


一句話描述

把既有獨立 Python 報表專案(validate.py / gen_ao_adjudication.py)的兩份報表演算法移植進 BE,每次分類 run 的結果落 DB,並提供 2 個系統內獨立唯讀報表頁(PrimeVue 重畫),從 run 詳情頁 + AP 總覽 Dialog 兩處進入。


0. 已拍板的決策(discussion 收斂)

# 決策 選擇
D1 run 結果儲存 DB + Drive 兩邊都存(這階段);DB 主表 JSONB 鏡像三個 Drive JSON
D2 報表頁「即時讀」後端接法 (b) 在既有 put_state / archive_run / _finalize_drive_output 補寫 DB;報表讀 DB(DB 為唯一真實來源)
D3 CANON 固定基準存放 (a) repo JSON 資源檔(cmmc_l1_canon.json),零 migration、可直接編輯
D4 報告① ground truth run 的 _state.json placements(User 審閱後);即時讀,不做確認按鈕/快照
D5 報告① 完整 vs 降級 讀 metadata last_edited_at/archived_at:任一非空 = 完整版;皆空 = 降級「純 AI 檢視」
D6 動態 CANON / 規則表 / 維護 UI / 核可流程 不在本期(未來 FR)
D7 報表輸出形態 BE 算結構化 JSON、FE 渲染(規格 Option B);HTML/PDF 匯出不做

1. 資料流(已追 code 驗證)

POST classify-evidence (既有)
  → container write_outputs:  _report-original.json (AI 原始, immutable)
                              _state.json (placements 初始 = matches≥threshold, source="ai")
  → BE _finalize_drive_output: 建 run folder、上傳兩 JSON、(完整歸檔時) 複製檔進 AO 樹
       ★ 新增:UPSERT compliance.evidence_classification_runs(鏡像兩 JSON + container_log)
  → [既有審閱頁] put_state: diff placements、Drive copy/trash、覆寫 Drive _state.json
       ★ 新增:UPDATE runs.state + last_edited_at/by
  → [既有] archive_run: 補複製、翻 archive_files
       ★ 新增:UPDATE runs.state + archive_files/archived_at/by
  → [新報表頁] GET .../report/{validation|adjudication}
       讀 DB run row(report_original + state + container_log)
        × cmmc_l1_aos.json (catalog) × cmmc_l1_canon.json (CANON)
        → 報表計算 → 結構化 JSON → FE 渲染

對應關係(標準工具 → 系統內,免 crosswalk,系統內已是 NIST):

概念 系統內來源
AI 放置 runs.report_original → 每檔 matches confidence ≥ threshold
User 正解(ground truth) runs.state → placements(ao_id → files,審閱後)
CANON(報告②) cmmc_l1_canon.json(檔名 → 控制項集合 + 理由)
catalog 既有 cmmc_l1_aos.json(ao_id / control / 名稱 / AO 文字);family 用本模組靜態 map
Docker log runs.container_log(text)

附帶解掉的 v0 限制:目前 _resolve_tenant_for_run_folder 靠 in-memory job 表反查 tenant,BE restart 後失效(api-spec「v0 限制」段)。有了 DB run 表,報表/state 端點可直接從 runs.tenant_id 反查 → restart 後仍可解析。


2. DDD 分層 — 新增 / 異動檔案

新增(持久層,模組目前完全沒有 DB infra)

檔案 角色
domain domain/evidence_classification/entity/classification_run_entity.py run entity + ClassificationRunQueryEntity
domain domain/evidence_classification/repository/classification_run_repository.py repo interface(abstract)
domain domain/evidence_classification/service/classification_run_domain_service.py domain service
infra infra/evidence_classification/model/classification_run_model.py SQLAlchemy ORM model
infra infra/evidence_classification/mapper/classification_run_mapper.py entity ↔︎ model
infra infra/evidence_classification/repository/classification_run_repository_impl.py 繼承 BaseRepositoryImpl

新增(報表計算 + 資源)

檔案 角色
app app/evidence_classification/service/report/validation_report_builder.py 報告① 演算法(移植 validate.py
app app/evidence_classification/service/report/adjudication_report_builder.py 報告② 演算法(移植 gen_ao_adjudication.py
app app/evidence_classification/service/report/report_common.py 共用:doc_key / normalize_aoid / catalog 載入 / family map / container_log 解析
資源 app/evidence_classification/resources/cmmc_l1_canon.json CANON 固定基準(移植現有 63 檔 → 通用結構)
api api/evidence_classification/routes/evidence_classification_route.py 異動:加 2 個 report 端點

異動(既有 service 補 DB 寫入點 — 決策 D2)

檔案 異動
app/evidence_classification/service/evidence_classification_service.py __init__ 注入 classification_run_domain_service_finalize_drive_output / put_state / archive_run 各補一次 DB upsert/update;新增 get_validation_report / get_adjudication_report 兩個 @transaction method
di_containers/evidence_classification/evidence_classification_containers.py wire run repo / domain service;service 加參數
common/code/evidence_classification_error_code.py 補 error code(見 §6)

既有審閱/歸檔/比較畫面與其行為零改動;DB 寫入是後端旁路(不改 user-facing 行為),符合「不動既有畫面」。


3. DB Schema

主表 compliance.evidence_classification_runs

compliance schema(與 compliance.projects 同 schema)。跨 schema 用 Integer soft reference(不建 FK constraint,依專案慣例)。

-- Date: 2026-05-31
-- FR-031 證據分類分析結果報表 — run 結果落 DB
CREATE TABLE compliance.evidence_classification_runs (
    id                      BIGSERIAL    PRIMARY KEY,                       -- (2026-05-31)
    run_folder_id           VARCHAR(128) NOT NULL,                          -- Drive run folder id(自然鍵)
    run_folder_name         VARCHAR(255),
    tenant_id               INTEGER      NOT NULL,                          -- soft ref
    project_id              INTEGER      NOT NULL,                          -- soft ref compliance.projects.id
    project_uid             VARCHAR(36),                                    -- FE 列表/URL 用
    ap_uid                  VARCHAR(36),                                    -- soft ref(AP uid;FE 列表用。改 uid 因 _finalize 只 thread 得到 uid)
    org_unit_id             INTEGER,
    framework_id            VARCHAR(64)  NOT NULL DEFAULT 'cmmc-l1',
    model                   VARCHAR(64),
    confidence_threshold    NUMERIC(4,2),
    status                  VARCHAR(32)  NOT NULL DEFAULT 'completed',      -- queued/running/completed/failed
    archive_files           BOOLEAN      NOT NULL DEFAULT TRUE,
    input_file_count        INTEGER,
    classified_count        INTEGER,
    estimated_cost_usd      NUMERIC(10,4),                                  -- 另留 metadata.token_usage 內
    triggered_at            TIMESTAMPTZ,
    completed_at            TIMESTAMPTZ,
    last_edited_at          TIMESTAMPTZ,                                    -- 報告① 降級判定用
    archived_at             TIMESTAMPTZ,
    triggered_by_user_id    INTEGER,
    last_edited_by_user_id  INTEGER,
    archived_by_user_id     INTEGER,
    report_original         JSONB,                                         -- _report-original.json 全文(AI 原始, immutable)
    state                   JSONB,                                         -- _state.json 全文(當前狀態, 與 Drive 同步)
    container_log           TEXT,                                          -- _container-log.txt
    -- audit 欄位由 jedi_common BaseModel 提供,欄名固定為下列(勿改名)
    created_at              TIMESTAMPTZ  NOT NULL DEFAULT now(),
    updated_at              TIMESTAMPTZ  NOT NULL DEFAULT now(),
    created_user            VARCHAR(50),
    updated_user            VARCHAR(50)
);

CREATE UNIQUE INDEX uq_ecr_run_folder_id ON compliance.evidence_classification_runs (run_folder_id);  -- (2026-05-31)
CREATE INDEX ix_ecr_project_ap ON compliance.evidence_classification_runs (project_uid, ap_uid);      -- (2026-05-31)
CREATE INDEX ix_ecr_tenant ON compliance.evidence_classification_runs (tenant_id);                    -- (2026-05-31)

-- 權限(CLAUDE.md 規範)
GRANT SELECT, INSERT, UPDATE, DELETE ON compliance.evidence_classification_runs TO cm_app;
GRANT USAGE, SELECT ON SEQUENCE compliance.evidence_classification_runs_id_seq TO cm_app;

設計理由

  • 三個 Drive 產物(report_original / state / container_log)整包進 JSONB/TEXT → 滿足「欄位一直調整」零改 schema。
  • 只把要查/排序/列表/降級判定的少數欄位升正式 column。
  • run_folder_id UNIQUE → upsert 自然鍵;報表/state 端點用它查 tenant(解 restart 問題)。
  • 不需 approved_* 欄位(報告① 走即時,D4)。
  • RLS:依專案慣例,DB 層 RLS 處理 tenant 隔離,Python 查詢不另加 tenant filter;tenant_id 欄位供 RLS policy / 列表用。

Entity(domain,乾淨欄位 + 兩個 JSONB 當 dict)

ClassificationRunEntity:對應上表欄位,report_original / statedict 持有、container_logstrClassificationRunQueryEntity(run_folder_id / project_uid / ap_uid / tenant_id)

Phase 0 驗證修正_finalize_drive_output 只有 tenant_idproject_id/project_uid/model/confidence_threshold/framework_id/archive_files/triggered_byJobRegistry.get(job_uid) 取;org_unit_idget_user_context() 取;ap_uid 需在 trigger_classifyJobRegistry.create(...) 補存 ap_uid=ap_uid(registry 原本沒存)。故 soft ref 用 ap_uid(非 ap_id,省一次 AP 查詢)。


4. 既有 service 的 DB 寫入整合(決策 D2)

三個既有 method 各補一次 DB 操作,不改既有 Drive 行為、不改回傳

Method 既有行為 新增 DB 操作
_finalize_drive_output 建 run folder、上傳兩 JSON、(完整歸檔) 複製檔 run_ds.upsert() 建 row:寫入 project/ap/tenant/model/threshold/status/archive_files/timestamps + report_original + state + container_log
put_state diff、Drive copy/trash、覆寫 Drive _state.json run_ds.update_state(run_folder_id, new_state, user_id):更新 state JSONB + last_edited_at/last_edited_by_user_id
archive_run 補複製、翻 archive_files=true run_ds.update_state(...) + archive_files/archived_at/archived_by_user_id
  • 三者皆已在 @transaction scope 內(既有),domain service 沿用同 transaction。
  • 容錯:DB 寫入失敗不可讓既有 Drive 流程回滾(Drive 已成功是事實)。包 try/except + logger.error,但仍照專案「不 silent fail」原則記錄;報表頁讀不到 DB row 時回 404(EC_RUN_NOT_PERSISTED)。
  • 歷史 run 回填:既有 Drive 上已存在、DB 沒有的 run → GET state / report 時 lazy 從 Drive 載入並 upsert 一次(沿用既有 Drive scan recovery 概念)。避免本期之前的 run 看不到報表。

5. 報表計算演算法(移植,純函式)

兩個 builder 吃 (report_original: dict, state: dict, catalog, canon) → 回結構化 JSON。純計算、無 DB/Drive 副作用,好測。

5.1 共用(report_common.py

移植自兩支 Python,砍掉 crosswalk(系統內已 NIST):

  • doc_key(fn):去副檔名 / 6+ 位日期 / 已簽名 / 空白底線括號 → 小寫(同文件不同格式判定)。
  • normalize_aoid(ao_id, valid_aoids):AI 偶發漏 [字母],單一 AO 自動補。
  • load_catalog(framework_id):讀 cmmc_l1_aos.json(結構 domains[] → controls[],含 id/name(英文)/statement/assessment_objectives)→ 攤平成 {ao_id: {control_id, control_name, ao_text}} + valid_aoids
  • family 不需要:報告①(控制項/AO 層級)與報告②(控制項精確比對,「即使同家族也算」)都不依賴 family;family 僅標準工具 gen_problem_files(未納入)使用。
  • 控制項中文名:catalog 的 name 是英文;報表頁要中文顯示,用本模組小靜態 map 或 i18n 補(移植 gen_ao_adjudication.py::CTRL_NAME 16 條)。
  • parse_container_log(text):exit_code / processed / classified / elapsed / stderr / errors / 逐檔 unclassified。
  • ai_placements(report_original, threshold, valid_aoids){ao_id: [file]}unplacedfile_diag(候選/best/primary)。
  • user_placements(state){ao_id: [file]}(讀 state.files[].placements,可選排除 is_deleted/is_na)。

5.2 報告①(validation_report_builder.py,移植 validate.py

降級判定(D5):state.metadata.last_edited_atarchived_at 皆空 → mode="degraded"

{
  "mode": "full" | "degraded",
  "run": { run_folder_id, run_folder_name, model, confidence_threshold,
           triggered_at, completed_at, last_edited_at, archived_at },
  "cost": { input_tokens, output_tokens, cache_read_input_tokens,
            total_tokens, estimated_cost_usd } | null,   // 受 FE flag 控制顯示
  "summary": {                                            // 僅 full
    "ao_level":      { recall_strict, recall_soft, precision_strict, gt_total, ai_total, match, soft_match },
    "control_level": { recall, precision, hit, gt, ai, miss, extra },
    "missing": { total, unclassified, wrong_control, wrong_ao, version_format },
    "extra":   { total, never, wrong_control, wrong_ao, version_format }
  },
  "control_rows": [ { control_id, control_name, gt, ai, hit, miss:[file], extra:[file] } ],
  "unclassified": {                                       // 池子層級三類診斷(恆有,degraded 也算)
    pool_count, unplaced_count,
    "catA": [ { file, user_locations:[{ao_id}], candidates:[{ao_id,confidence}], recover:{type,text} } ],
    "catB": [ { file, ai_sibling, candidates } ],
    "catC": [ { file, candidates } ],
    "zero_files": [ { file, category, user_locations } ]
  },
  "docker_log": { exit_code, processed, classified, elapsed, stderr, errors:[], not_logged:[], fail:[] } | null,
  "ao_detail": [ { control_id, control_name, ao_id, ao_text, status,
                   gt:[file], ai:[file], match:[file],
                   missing:[{file,label}], extra:[{file,label}] } ]
}
  • 缺漏四級 label:版本/格式差異 / AO選錯(同控制項) / 分到其他控制項 / AI完全未分類;多餘四級對稱。
  • 控制項層級用 doc_key 聯集比對;AO 層級精確檔名。
  • degradedsummary / control_rows 的 recall/precision 省略,只給 AI 放置清單 + unclassified + cost

5.3 報告②(adjudication_report_builder.py,移植 gen_ao_adjudication.py

CANON 缺(framework 無對應 JSON)→ available=false,FE 顯示「此框架尚未建立正解基準」。

{
  "available": true | false,
  "run": { run_folder_id, model, confidence_threshold },
  "user_wrong": [ { file, placed_control, placed_control_name, canon:[control_id], ai_has_canon: bool, reason } ],
  "ai_wrong":   [ { file, placed_control, placed_control_name, canon:[control_id], reason } ],
  "out_scope":  [ { file, user:[control_id], ai:[control_id], reason } ],
  "undecided":  [ { file } ],                              // 不在 CANON(未裁定)
  "by_control": [ { control_id, control_name, has_issue,
                    aos:[ { ao_id, ao_text,
                            files:[ { file, user_verdict, ai_verdict, conclusion, canon:[control_id] } ] } ] } ],
  "note_user_equals_ai": bool                              // last_edited 為空時 = true(User 側=AI,提醒避免誤讀,開放問題4)
}
  • 判定:控制項精確比對(CANON 列出=對、放別的=錯,即使同家族);CANON 空集合=超範圍;不在 CANON=未裁定(不判對錯)。
  • User 側 = user_placements(state);AI 側 = ai_placements(report_original)

5.4 CANON 資源結構(cmmc_l1_canon.json

從現有 gen_ao_adjudication.pyCANON dict 移植,通用結構化

{
  "framework_id": "cmmc-l1",
  "rules": {
    "Login_Fido_Setting.png": { "controls": ["IA.L1-3.5.2"], "rationale": "FIDO 登入設定=身分鑑別機制" },
    "NTP_Setting.png":        { "controls": [], "rationale": "時鐘同步屬 L2(AU.L2-3.3.7),L1 無對應" }
  }
}
  • 比對 key = 檔名(NFC);controls 空陣列=超出範圍。
  • 這階段固定 seed,直接編檔即改判定(同現行做法)。未來 FR 才做規則表/維護 UI(D6)。

6. API 端點

沿用既有路由前綴 /api/1.0/classification-run/<run_folder_id>/...。權限同既有 GET state(v0:僅檢 JWT,不檢 project 角色 — 與既有報表/審閱頁一致;正式版再收緊)。

GET /classification-run/<run_folder_id>/report/validation

回 §5.2 結構。

GET /classification-run/<run_folder_id>/report/adjudication

回 §5.3 結構。

Errors(新增 code)

Code HTTP When
EC_RUN_NOT_PERSISTED 404 DB 無此 run row 且 Drive 回填也失敗
EC_CATALOG_NOT_FOUND 404 framework catalog 載入失敗(沿用 / 或新增)

報告② 無 CANON 不報錯,回 available=false(正常情境,非錯誤)。


7. FE — 兩個獨立報表頁 + 兩入口

路由(新增,獨立頁)

name path 元件
evidence-classification-report-validation .../classify-evidence/run/:runFolderId/report/validation EvidenceClassificationValidationReport.vue
evidence-classification-report-adjudication .../classify-evidence/run/:runFolderId/report/adjudication EvidenceClassificationAdjudicationReport.vue
  • 唯讀;用 EvidenceClassificationService 新增 getValidationReport(runFolderId) / getAdjudicationReport(runFolderId)
  • PrimeVue 重畫:KPI 卡(Card)、控制項表(DataTable)、逐 AO 明細(Accordion/Panel)、四級/判定用 Tag severity + design tokens。不沿用標準工具 inline-CSS HTML。
  • 成本卡受既有 SHOW_RUN_COST 概念 flag 控制。
  • degraded:頂部 Message 提示「尚未經人工審閱,召回/精確僅供參考」;隱藏 KPI/control 表,只顯示放置清單 + 未分類 + 成本。

入口連結(僅加導覽連結,不改既有行為

  1. run 詳情頁 = 既有審閱頁 EvidenceClassificationReview.vue header → 加兩個 outlined Button「驗證報表」「各 AO 誰對誰錯」router.push 到上面兩 route。
  2. AP 總覽 → 自動分類證據 Dialog AIEvidenceClassificationDialog.vue 歷史執行每筆 job row(歷史執行資料後面)→ 既有「審閱」Button 旁加兩個報表連結(status==='completed' && run_folder_id)。

這是唯一觸及既有元件的地方,且只新增 navigation,不動歸檔/比較/審閱邏輯


8. 降級與邊界

  • 報告① modelast_edited_at/archived_at 決定(D5);即時讀 DB state(D2 確保 DB 同步)。
  • 報告② note_user_equals_ai:未審閱時 User 側=AI,FE 標示避免誤讀(開放問題 4)。
  • 比對以檔名為準(不比內容);同名不同內容無法分辨(沿用標準工具限制)。
  • 框架範圍外(L2 證據)→ CANON 空集合 → 標超範圍,不硬塞。
  • 多 run 跨批比較(不同 prompt/model)這階段不做跨 run 彙整頁(gen_summary 對應),未來再說。

9. 測試(pytest,在 BE)

  • report builder 純函式測試(重點):餵造好的 report_original + state + 迷你 catalog + 迷你 CANON dict → 斷言 recall/precision/四級/未分類三類/User標錯/AI多放/超範圍/未裁定。涵蓋:
    • 完整版 vs 降級版(last_edited_at 有無)
    • doc_key 同文件不同格式
    • CANON 空集合(超範圍)、不在 CANON(未裁定)
    • 報告② User=AI(未審閱)case
  • DB 整合測試_finalize_drive_output / put_state / archive_run 後 run row 欄位正確(mock Drive ops)。
  • app service test 必加 logger patch autouse fixture(jedi DBLogHandler 對 SessionLocal=None 會炸,見 memory feedback_test_logger_patch_db_handler)。
  • E2E(在 compliance-manager-test,後續):跑分類 → 開兩報表頁 → 斷言關鍵數字/區塊。

10. 整合點 / 既有資產

資產 用途
app/.../service/catalog_builder.py + cmmc_l1_aos.json catalog 來源(控制項/AO 定義)
infra/.../evidence_drive_ops.py 既有 Drive 讀寫(回填歷史 run 時讀 Drive JSON)
BaseRepositoryImpl(jedi_common) run repo 繼承
state-json-schema.md state / report_original 權威欄位 schema
container_entrypoint.py::write_outputs 兩 JSON 寫出格式來源

11. 範圍邊界

:2 報表頁(含降級)、2 報表 API、run 落 DB(DB+Drive)、CANON/catalog 固定資源、2 入口連結、pytest。

不做(未來 FR):動態 CANON / canon_rules 規則表 / 稽核員維護 UI / 管理員核可流程 / HTML·PDF 匯出 / 多框架 / 跨 run 彙整總表 / 停用 Drive 只用 DB。


12. 開放問題(待 user 拍板,不卡主結構)

  1. 歷史 run 回填:本期之前已在 Drive 的舊 run,要不要在首次開報表時 lazy upsert 進 DB?(傾向要,否則舊 run 無報表)
  2. report builder 移植方式:純 Python 重寫(建議,融入 DDD、好測)vs 直接包現有腳本當 subprocess(快但難測、難維護)。design 採重寫
  3. report② User=AI 未審閱時的 UI 標示文案。
  4. catalog 是否含 family 已驗證:catalog 結構 domains[]→controls[]id/name英文/statement/AOs);報告①②用不到 family(已從 §5.1 移除)。唯一補充:控制項中文名 catalog 沒有 → 移植 CTRL_NAME 16 條小 map。
  5. 成本顯示 flag:沿用現有 SHOW_RUN_COST 思路,報表頁是否預設開(測試期)/ 關(對外)。

13. 實作後對齊(Reconciliation,2026-05-31 收尾補)

實作期間(含實機對照外部 script + user 回饋)對原設計的重大偏離,逐條記錄:

13.1 新增「正解匯入」(Option B) — 偏離 D4

  • 原設計:報告① 的 ground truth = 審閱後的 _state placements(D4)。
  • 實際:實機看到未審閱的 run 報告①極稀疏(_state==AI → 召回/精確無意義、缺漏多餘全空)。而驗證情境本來就有現成的 User 正解(亞航 Source_Evidences)。
  • 改動:新增 per-tenant 正解基準
    • 新表 compliance.evidence_classification_ground_truth(tenant_id, framework_id, mapping JSONB)mapping = {file_name: [ao_id]},unique (tenant_id, framework_id);migration scripts/sql/2026-05-31_fr031_evidence_classification_ground_truth.sql(已套 DEV)。完整 DDD 持久層(entity/model/mapper/repo/domain service)。
    • POST /api/1.0/classification-ground-truth(body {framework_id, mapping, note?},per-tenant upsert)。
    • 報告① + 報告② 都改成:有匯入正解就用它當 User 側build_*_report(..., ground_truth=)),否則 fallback 審閱後 _state。有正解 → 永遠 full、gt_source="import"
    • 亞航 63 檔正解已從 Source_Evidences(crosswalk 在產製時做掉、存 NIST)seed 進 DB(tenant 102)。
  • 未做(follow-up):FE 正解上傳 UI(目前只有 API + 手動 seed);動態正解(user 審閱後自動成正解)。

13.2 報告② 也吃匯入正解 — 修「兩份報告矛盾」

  • Bug:報告① 用匯入正解當 User、報告② 仍用 _state(未審閱=AI) → 同一 run 兩個不同的「User」,互相矛盾(報告②顯示 User=AI、只有 5 格;報告①顯示 User 分了一大堆)。
  • 修正build_adjudication_reportground_truth 參數,與報告① 同源。實機 run 對外部 script 13-53User標錯/AI多放/超範圍 = 30/5/2,完全一致(修正前因 User=AI 只有 5/5)。

13.3 報告① 結構強化 + 移除 attention — 對齊 script HTML

  • 逐 AO 明細從扁平 ao_detail 改成 ao_by_control(依控制項分組 + 每控制項 summary:n_diff/n_aos/缺漏/多餘),且全列(含雙方皆空/完全一致)。
  • 未分類診斷加:4 路救回彙整recover_summary:降門檻可救/救控制項/候選不符/0分)+ 「降門檻救不回」清單no_recover)+ 「AI評0分」表(zero_files)。
  • 加「重點解讀」note。
  • 移除「需要關注的項目(依嚴重度)」段(attention)——原本已做,user 指示拿掉

13.4 FE 渲染方式改變 — 偏離 D7

  • 原設計 D7:FE 用 PrimeVue「重畫」結構化 JSON。
  • 實際:為了跟外部 script 兩份 HTML 視覺一致(user 要求),FE 改成1:1 移植 script HTML 的版面 + CSS 配色(custom scoped CSS:KPI 卡著色 / 缺漏多餘彩色 chip / 召回長條 / 巢狀 <details> / verdict 著色),PrimeVue 只保留 Button/LoadingState。字級放大兩階、內容寬 1140px。
  • 驗證方法:Playwright serve script HTML → 截圖 → Read 比對(解決「Claude 看不到 render 結果」的盲點)。

13.5 其它

  • 新增 error code EC_GROUND_TRUTH_INVALID (EC_400002)(除 EC_RUN_NOT_PERSISTED EC_404008)。
  • evidence_classification_runs.project_id 放寬 nullable(Drive 回填舊 run 拿不到 project_id)。
  • 報告② 移植來源確認:外部 各AO誰對誰錯.html 與當前 gen_ao_adjudication.py(CANON 控制項精確比對版)同源;我移植的即此版(非更舊的 family 版)。

參考

  • 白話需求:raw-requirement.md
  • 標準工具:~/Desktop/AirAsia 真實證據/CMMC佐證/_驗證/validate.py / gen_ao_adjudication.py / WEB整合_報告產生規格.md
  • 既有模組:app/evidence_classification/service/evidence_classification_service.pydocs/api/evidence-classification/{api-spec,state-json-schema}.md
  • arc 收尾:docs/features/FR-030.3-2605-evidence-classify-xlsx-pptx/handoff/2026-05-30-evidence-classify-arc-SUMMARY.md