| 日期 | 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 |
User 早上可直接做的事:
/module-frame/import-docx~/Desktop/亞航-CMMC-SSP-20260520-1會議討論版.docx(或 docs/reference/亞航-...docx)驗證已透過 curl 完成 — BE 處理該檔可以 200 並寫入完整 parsed_result:
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 可看 |
/module-frame/import-docxdocs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docxssp-docx-import/<uid> 看 response JSON(最完整資料)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;"poetry run python scripts/smoke_docx_import.py docs/reference/亞航-CMMC-SSP-20260520-1會議討論版.docxDump 解出的 parsed_dict — 不寫 DB、不打 HTTP,純 in-memory pipeline 測試。
b338939b-13d0-4145-bf8c-ce074a25a432 — 半夜 0:08 透過 curl 上傳的客戶檔結果,parsed_result 含 5 parties + 4 leveraged + 15 controls。可直接查它的 parsed_result。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 是否該調整。
客戶實際檔有 track changes 未 accept — 已在 BE 端自動 accept(in-memory only,原始 bytes 保留在 MinIO 不變)。如果 user 想要看「沒 accept revisions」的解析結果(譬如 audit 用途),需 Stage 2 加 toggle。
.gitignore 已加 docs/reference/亞航* — 客戶實際檔絕對不要 commit。Scrubbed 版本在 tests/data/oscal/customer_sample_scrubbed.docx。
scrub_customer_docx.py 的 SCRUB_RULES 含「accept revisions 後揭露的客戶私人資料」(屏東飛機維修廠 / Sierra Nevada Corporation / 真實人名電話地址 / Crowdstrike / FR18078583629 / Fortinet 等)— 若日後拿到別份客戶 docx 做 fixture,要重新跑一次 scrub script + 補規則。
本 commit set 未 push — 明早 user 看完滿意再 push。
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)。
Stage 1 不動 confirm path — 解出來的新欄位(props / revision_history)能否完整寫入 DB 看 existing write strategy 支援度。Stage 2 重構時整體一條搞定。
樣板沒對應表的欄位仍解不出來 — devices / info_systems / ref_docs / authorization_boundary / deployment_model 等。Stage 2 + 樣板加表 phase 處理。
| 工作項 | 預估 | 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 |
# 確認 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_importBE-only 改動、無 DB migration、無 jedi-* 套件異動、無 FE 動 — push 後 staging deploy 不需要額外步驟。