SSP Docx Import Parity — Design

項目 內容
版本 v1.0
日期 2026-05-24
作者 Claude (overnight session)
Branch feature/real_doc_import
Status Stage 1 in progress
對應功能 /module-frame/import-docx 解析能力對齊 /module-frame/import-excel

1. 背景

v1.1.0 release 後,SSP Excel 匯入(A1~A5 phases)已能解析以下 OSCAL 欄位:

  • 基本資料(metadata)
  • 受評標的(system_characteristic — A4 加 owner / status / target_type / scope_description)
  • 單位 / 參與人員(parties org + person)
  • 設備(devices)
  • 資訊系統(info_systems / components)
  • 外部利用服務(leveraged services,含 category / status / description)
  • 控制項與 AO
  • 程序書(reference documents)

Docx 匯入今天只解控制項 + AO + 部分 parties / leveraged,metadata / system_characteristic / 多數 party 欄位都還沒 reach。客戶上傳真實 SSP docx 時 BE 解出的資料量遠少於 excel — UX 不一致。

2. 目標

讓 Docx 匯入解出與 Excel 對齊的 OSCAL 欄位範圍(樣板加表議題押後,本期只在「樣板已經有的 anchor」內把欄位補完整)。

實作分兩階段:

Stage 範圍 預期成果
Stage 1 (本次) Parser 補欄位 + Adapter 塞進既有 ParsedSsp shape 上傳 docx → parse_job.parsed_result 含 metadata / parties / leveraged 完整欄位 + revision_history;preview API 回傳給 FE 看到資料
Stage 2 (後續) Adapter 重構輸出 ParsedExcelEntityBundle、confirm 切 SspEntityReconciliationOrchestrator + WriteStrategy、parse_jobs.schema_version 獨立 column + 雙 path 並存、FE 預覽改 reuse excel component Docx confirm 流程完全 unified 到 excel pipeline;新欄位 (devices/info_systems) 一次寫成一條

3. Stage 1 範圍細節

3.1 樣板 anchor 對應(依客戶實例 亞航-CMMC-SSP-20260520-1會議討論版.docx 結構驗證)

Anchor 位置 OSCAL / ParsedSsp 對應
Table #0 文件首個 table(H1 Introduction 前) metadata.{document_serial_no, version, published, title} + system_characteristics.organization_name
Tables #1~#5 H1 Introduction 下、H2 之前的 K/V tables parties (org + person) — name/title/address/phone/email
Table #6 H2 Leveraged External Systems 下,col0 = CSP/CSO Name leveraged service (provider/service_name + 新增 4 欄塞 props 風格)
Table #7 H2 Leveraged External Systems 下,col0 = 類別 Category leveraged service (category + name + purpose + protocol + security_auth)
Table #24 H1 APPENDIX 2 Revision History 下 metadata.revision_history (list of {version, date, amendment, description})
控制項 + AO H3 + 下方 AO check 表 matched_controls — 既有實作沿用,不改

不在 Stage 1 範圍

  • Table #23 (APPENDIX 1 Approvals) — 樣板格式特殊、客戶填寫率低
  • devices / info_systems / ref_docs — 樣板沒對應表格 → 押到 Stage 2「樣板加表」phase

3.2 欄位 detection 規則

詳見 implementation-plan-stage1.md 各 extractor 規格。核心原則:

  1. label normalize:半形/全形/中英/大小寫 fuzzy match(re.sub(r'[\s::\.]', '', txt).lower()
  2. 空容忍:cell 空白 → 該欄位 None,不噴錯;整 row 空 → skip
  3. placeholder 偵測:cell 全是 [...] 形式 → 視為樣板 placeholder skip
  4. 多 table 容忍:parties anchor 區段內可有 1~N 個 table(樣板樣本 5 個、客戶可能 3 個或 10 個)

3.3 Adapter 改動

直接在既有 domain/oscal/adapter/cmmc_ssp_adapter.py「ParsedSsp 組裝」階段呼叫 docx_section_extractors 一輪:

existing adapter.adapt(parsed, structure, candidates)
  ├─ existing logic (matched_controls / 基本 parties / 基本 leveraged)
  └─ NEW: section_orchestrator.parse_all(doc, structure)
        ├─ extract_metadata_table → 補 ParsedMetadata 欄位
        ├─ extract_party_tables → 補 ParsedParty 欄位(telephone/address 等)
        ├─ extract_leveraged_csp_table → 補 ParsedLeveragedService 欄位 (CMMC props 走 framework_specific_props dict)
        ├─ extract_leveraged_category_table → 補 ParsedLeveragedService(category 等)
        └─ extract_revision_history_table → ParsedMetadata.revision_history

注意ParsedLeveragedService dataclass 目前沒有 props/framework_specific_props 欄位。Stage 1 解法是把 CMMC-only 欄位塞進 remarks(free text)或在 dataclass 加一個 props: Dict[str, Any] 欄位。後者較乾淨,但會影響其他 caller。本期採折衷:在 ParsedLeveragedServiceprops: Optional[Dict[str, Any]] = None 欄位(純加、不影響既有 caller — 沒讀就是 None)。

3.4 兼容性

Stage 1 不動 confirm path — confirm 流程仍走既有 ssp_docx_import_app_service 邏輯。新解出的 metadata / parties 補欄位 / leveraged 新欄位會出現在 parse_job.parsed_result JSONB,但 confirm 時是否寫入 DB 看既有 write logic 是否處理該欄位(多數有,部分如 leveraged 新 props 欄位可能會被忽略 — 等 Stage 2 重構寫入 path 才完整 propagate)。

parse_jobs.schema_version column 押到 Stage 2(Stage 1 不需要 — 因為沒分流)。

3.5 FE 改動

Stage 1 不動 FE。明早 user 試上傳,可從以下方式驗 BE 解出資料:

  1. Swagger UI / curl 直接打 POST /api/1.0/ssp-docx-imports/parse
  2. FE 上傳後在 Network tab 看 GET /api/1.0/ssp-docx-import/<uid> response JSON
  3. 直接 query DB SELECT parsed_result FROM oscal.ssp_docx_parse_jobs WHERE uid=...

如果 FE 既有 preview component 顯示「正常」欄位 — 那解出來新欄位也會帶上去(但可能沒漂亮 render,只是 raw key 出現)。

4. Stage 2 預告(不在本期實作)

工作項 動到的範圍 預估工時
ParsedExcelEntityBundle 擴 parsed_parties + parsed_metadata A4 dataclass + 各 reconciler S
Docx adapter 重構輸出 ParsedExcelEntityBundle 整個 docx adapter M
Confirm 切 SspEntityReconciliationOrchestrator confirm app service path M
parse_jobs.schema_version column + 雙 path 並存 DB migration + service router S
FE 預覽 reuse excel SspImportPreview component FE Vue M
樣板加表(System Components / Hardware Inventory) + template download endpoint 樣板設計 + BE generator + FE 下載 L

5. 風險與緩解

風險 緩解
extractor regex / heuristic 對客戶 docx 失敗 用客戶實際檔當 fixture,所有 extractor 都跑過
既有 docx import e2e test 被改壞 S6 commit 前跑全套既有 test,紅就 revert
BE 重啟卡住、user 早上 endpoint 不可用 S8 重啟後 curl 自我驗證,fail 時寫詳細 handoff 不靜默
半夜 push 推到 main 絕對不 push,commit on local branch only
客戶檔被 commit 進 git history .gitignoredocs/reference/亞航* + git rm --cached(已在 S0 完成)

6. Success Criteria

Stage 1 上線後(明早 user 確認):

    • metadata.title = System Security Plan (SSP) | 系統安全計畫(草稿)
    • metadata.revision_history = 1 筆 (V1.0 / 2026.0X.XX / Initial / NA)
    • leveraged_services ≥ 2 筆(T6 row2 + T7 rows)
    • parties 結構含 column 對應(即使值空 — 5 個 placeholder party)

7. 參考檔案

  • 客戶實例(local-only):docs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docx
  • 標準樣板(git-tracked):docs/features/FR-011.2-2605-ssp-import-export-phase2/reference/ASIA-CMMC-SSP-DRAFT-with-user-info-202604.docx
  • Excel 端 dataclass:domain/oscal/parser/ssp_intermediate.py
  • Docx 端 dataclass:同上 ParsedSsp 系列
  • 現有 docx parser entry:domain/oscal/parser/docx_parser_core.py
  • 現有 docx adapter:domain/oscal/adapter/cmmc_ssp_adapter.py
  • 現有 docx app service:app/oscal/service/ssp_docx_import_app_service.py