# 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。本期採折衷：在 `ParsedLeveragedService` 加 `props: 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 | `.gitignore` 加 `docs/reference/亞航*` + `git rm --cached`（已在 S0 完成） |

## 6. Success Criteria

Stage 1 上線後（明早 user 確認）：

- [ ] 上傳 `docs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docx` → 200 + parse_uid 回傳
- [ ] GET preview response JSON 至少含：
  - `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）
- [ ] 既有 docx import test 全綠
- [ ] BE log 無 ERROR / Traceback（除了 user 操作引發的 expected）
- [ ] Branch 留乾淨 commit history、不 push、handoff SUMMARY 詳列下一步

## 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`
