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
§1

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 完整
§2

已完成 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 客戶檔
§3

行為差異

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 可看
§4

規範文件齊全度

§5

明早 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(最快確認)

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(不需上傳)

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。
§6

⚠ 注意事項

  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。

§7

已知限制

  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 處理。

§8

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
ParsedExcelEntityBundleparsed_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 行為調整
§9

風險回顧

風險 結果
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
§10

部署 handover(明早 push 時)

# 確認 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 不需要額外步驟。