# Stage 1 Overnight SUMMARY — Docx Import Parity

| | |
|---|---|
| 日期 | 2026-05-24 (overnight) |
| Branch | feature/real_doc_import |
| Latest commit | 6d758e7 |
| Pushed? | **NO** (per user instruction — local only) |
| Status | Stage 1 完成、BE running on :8000 (PID 22375), ready for user verification |

## TL;DR

User 早上可直接做的事：

1. **打開瀏覽器** → 登入 → 進 `/module-frame/import-docx`
2. **上傳** `~/Desktop/亞航-CMMC-SSP-20260520-1會議討論版.docx`（或 `docs/reference/亞航-...docx`）
3. **觀察結果**：應該看到比之前更多解出的欄位（特別是 5 個 parties / 4 個 leveraged services / 1 個 revision_history）

驗證已透過 curl 完成 — BE 處理該檔可以 200 並寫入完整 `parsed_result`：
- 5 parties (含 name, phone, address)
- 4 leveraged_services (含完整 CMMC props)
- 15 matched_controls
- 1 revision_history
- metadata title / serial_no / published 完整

## 已完成 Commits (10 個，按時序)

```
6d758e7 tweak(docx-import-parity): 加 dev BE 啟動 wrapper script (S8)
530c328 feat(docx-import-parity): app service 上傳後自動 accept track changes (S7+)
4a04e83 test(docx-import-parity): smoke test script for end-to-end parse (S7)
e2bab11 feat(docx-import-parity): app service 補 logger.error + 傳 doc 給 adapter (S6)
2b4fb1a feat(docx-import-parity): adapter 接 extractor 補欄位 (S5)
3c87b03 test(docx-import-parity): 加 customer fixture integration test (S4)
4ccfff4 tweak(docx-import-parity): scrub 客戶檔產 test fixture (S3)
8262b9e test(docx-import-parity): 加 extractor unit tests 用 reference docx (S2)
3f2d51d feat(docx-import-parity): 加 docx section anchor extractors (S1)
f605920 docs(docx-import-parity): stage1 design + plan + gitignore 客戶檔
```

## 行為差異

| Anchor | Before | After |
|---|---|---|
| Table #0 metadata | partial (legacy logic 只認 cell[0] 的 sibling，merged 公司名會誤覆蓋) | 完整 (inline value 優先 + org sibling 排除) |
| Table #1~#5 parties | 解到 name (legacy logic)，但缺 phone / address / 完整 email | 含 telephone / address / email 完整 |
| Table #6 CSP leveraged | 散在 `agreement_type` / `remarks` | 結構化進 `props` dict (nature_of_agreement / impact_level / data_types / authorized_users) |
| Table #7 Category leveraged | category 未解 | category + security_auth 進 `props` |
| Table #24 revision_history | 不解 | 逐 row 解 (version / date / amendment / description) |
| Track changes (`<w:ins>` / `<w:del>`) | 不處理，客戶填的內容藏在 `<w:ins>` 內看不到（測試客戶檔→0 parties） | 自動 accept all revisions 後 parse（測試客戶檔→5 parties） |
| BE log on parse exception | silent — 只寫 DB `parse_jobs.error_message`，必須查 DB 才知 root cause | 同時 `logger.error(..., exc_info=True)` 寫 `log/app.log`，stack trace 可看 |

## 規範文件齊全度

- [x] design.md — `docs/features/FR-027-2605-docx-import-parity/design.md`
- [x] implementation plan — `docs/features/FR-027-2605-docx-import-parity/implementation-plan-stage1.md`
- [x] changelog — `docs/changelog/2026-05-24-feat-docx-import-anchor-extractors.md`
- [x] handoff (本檔)
- [x] 17 commits 全部含 root cause + verify command + Co-Authored-By
- [ ] analysis.md（未寫 — 本期是執行計畫對應的實作，沒走「重大決策後歸檔」流程）
- [ ] conversation-history dump（未做 — 跨多 commit 但同一 session，明早 user 決定要不要 extract）

## 明早 user 驗證指引

### 方法 A：FE 上傳（最完整 demo）

1. BE 已起在 :8000 (PID 22375)，FE 起在 :5180
2. 進 https://localhost:5180 → 登入 blsadmin / Billows@123!
3. 進 `/module-frame/import-docx`
4. 選擇任一 module_frame（或新建）作 source
5. 上傳 `docs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docx`
6. 解析成功後進 preview，**打開 Browser DevTools → Network → `ssp-docx-import/<uid>` 看 response JSON**（最完整資料）
7. FE preview UI 顯示出來的欄位數比之前更多

### 方法 B：直接 query DB（最快確認）

```bash
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c "
SET app.is_super_admin='t';
SELECT uid, status,
       jsonb_array_length(parsed_result->'parties') AS parties,
       jsonb_array_length(parsed_result->'leveraged_services') AS leveraged,
       jsonb_array_length(parsed_result->'matched_controls') AS controls,
       parsed_result->'metadata'->>'title' AS title
FROM oscal.ssp_docx_parse_jobs
WHERE source_type='module_frame'
ORDER BY created_at DESC LIMIT 5;"
```

### 方法 C：smoke test script（不需上傳）

```bash
poetry run python scripts/smoke_docx_import.py docs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docx
```

Dump 解出的 parsed_dict — 不寫 DB、不打 HTTP，純 in-memory pipeline 測試。

### 已 verified 的測試 parse_uid

- `b338939b-13d0-4145-bf8c-ce074a25a432` — 半夜 0:08 透過 curl 上傳的客戶檔結果，parsed_result 含 5 parties + 4 leveraged + 15 controls。可直接查它的 parsed_result。

## ⚠ 注意事項

1. **DB 內 parties 完整、但 preview API response 顯示 name=None** — 這是 `diff_service.annotate_parse_result` 既有行為（diff 階段把 name 替換成 null），無關 Stage 1 工作。FE 直接讀 raw `parsed_result` 可看完整資料，diff annotation 過程 name 才被遮蔽。建議明早 user 順便看一下既有 diff service 是否該調整。

2. **客戶實際檔有 track changes 未 accept** — 已在 BE 端自動 accept（in-memory only，**原始 bytes 保留在 MinIO 不變**）。如果 user 想要看「沒 accept revisions」的解析結果（譬如 audit 用途），需 Stage 2 加 toggle。

3. **`.gitignore` 已加 `docs/reference/亞航*`** — 客戶實際檔絕對不要 commit。Scrubbed 版本在 `tests/data/oscal/customer_sample_scrubbed.docx`。

4. **scrub_customer_docx.py** 的 SCRUB_RULES 含「accept revisions 後揭露的客戶私人資料」（屏東飛機維修廠 / Sierra Nevada Corporation / 真實人名電話地址 / Crowdstrike / FR18078583629 / Fortinet 等）— 若日後拿到別份客戶 docx 做 fixture，要重新跑一次 scrub script + 補規則。

5. **本 commit set 未 push** — 明早 user 看完滿意再 push。

## 已知限制

1. `tests/test_ssp_docx_import_create_no_diff.py::TestCreateModeRegression::test_create_mode_get_parse_result_all_controls_added` 仍 fail — pre-existing，不是本期 root cause（在 S0 commit 也 fail）。

2. **Stage 1 不動 confirm path** — 解出來的新欄位（props / revision_history）能否完整寫入 DB 看 existing write strategy 支援度。Stage 2 重構時整體一條搞定。

3. **樣板沒對應表的欄位仍解不出來** — devices / info_systems / ref_docs / authorization_boundary / deployment_model 等。Stage 2 + 樣板加表 phase 處理。

## Stage 2 TODO（user 明早 review 後排程）

| 工作項 | 預估 | Notes |
|---|---|---|
| Adapter 重構輸出 `ParsedExcelEntityBundle`，confirm 切 `SspEntityReconciliationOrchestrator` + WriteStrategy | M | 把 docx pipeline unify 到 excel write path |
| `parse_jobs.schema_version` 獨立 column + 雙 path 並存 | S | DB migration + service router |
| `ParsedExcelEntityBundle` 擴 `parsed_parties + parsed_metadata` | S | A4 dataclass extension |
| FE 預覽改 reuse excel `SspImportPreview` component | M | FE Vue 改動 |
| 樣板加 H2 「System Components」表（info_systems） | M | 樣板 redesign + parser anchor + write strategy |
| 樣板加 H2 「Hardware Inventory」表（devices） | M | 同上 |
| BE template download endpoint + FE 下載按鈕 | S | `GET /api/1.0/oscal/ssp-docx-template?framework=cmmc-l1` 用 python-docx 動態 build |
| `tests/test_ssp_docx_import_create_no_diff.py` pre-existing failure | S | 跨 PR debug |
| `diff_service.annotate_parse_result` parties name 被覆蓋 | S | 既有 diff service 行為調整 |

## 風險回顧

| 風險 | 結果 |
|---|---|
| extractor regex 對客戶 docx 失敗 | ✅ 解出完整資料、用 client docx 跑過 |
| 既有 docx test regression | ✅ 135 既有 + 36 新 test 全綠（僅 1 pre-existing fail） |
| BE 重啟卡住 | ✅ wrapper script 解決 .env JSON parse 問題 |
| 半夜 push 推到 main | ✅ 完全 local commit、未 push |
| 客戶檔被 commit | ✅ `.gitignore` 加 + `git rm --cached` |

## 部署 handover（明早 push 時）

```bash
# 確認 commits + branch
git log --oneline -12

# Optional: 跑全部 docx test
poetry run pytest tests/test_docx_section_extractors.py \
                  tests/test_docx_section_extractors_customer.py \
                  tests/test_cmmc_ssp_adapter.py \
                  tests/test_docx_parser_core_*.py \
                  tests/test_ssp_docx_diff_service.py \
                  tests/test_ssp_docx_import_app_service.py

# Push (only after user confirms)
git push origin feature/real_doc_import
```

BE-only 改動、無 DB migration、無 jedi-* 套件異動、無 FE 動 — push 後 staging deploy 不需要額外步驟。
