A3 Session D — SUMMARY(T3 + T4 implementation)

Session 範圍:SSP 匯入匯出 Phase 2 / A3 phase / Session D(第 4 站 / 5) 產出:T3 A2 串接 reconcile + T4 fuzzy 演算法完整落地 → 2 commits + handoff 下站:Session E(T5 test 補齊 + T6 cucumber + T7 收尾)— 2026-05-20-a3-session-D-to-E-implementation.md

1. 完成項目

T3 — A2 confirm flow 串接 reconcile()

改動 位置 細節
_write_all_data 內補 self._reconciliation.reconcile(...) app/oscal/service/ssp_excel_import_app_service.py:373-379 write_parties 前;if self._reconciliation is not None 守門;try/except logger.warning swallow(沿用 docx flow line 258-264 既有 pattern)
tenant_id 來源 user_context.tenant_id 與 docx flow line 261 / 490 一致
新增 6 個 integration test tests/test_a3_reconciliation_a2_integration.py superset / update / mixed list / tenant_id / no-parties skip / reconcile-exception-swallowed

plan vs reality 偏差(待 Session E §11 補)

  • Plan §3.2/3.3 寫「插入 _confirm_*_flow」;實際 _write_all_data 為兩 flow 共用 helper,插一處 DRY 受惠
  • Plan 假設 test 用 decisions=[], overrides={} 簽章;實際走 confirm_import 完整 entry 比較自然
  • 既有 test_ssp_excel_import_app_service.py fixture 不需改 — party_reconciliation_service 預設 None backward-compat

T4 — Fuzzy 演算法完整落地

Reconciler Stage 演算法
PersonReconciler normalized _strip_plus_alias (alice+work@acme.comalice@acme.com) → 二次 get_users(email=normalized) exact 查詢
PersonReconciler fuzzy client-side filter:拉全 tenant user list(cache)→ email domain 對齊 + nicknamelogin_name 對齊(透過 _normalize_name)→ confidence 0.7
OrganizationReconciler normalized _normalize_name(全形空格→半形 / collapse 連續空白)→ 二次 query exact
OrganizationReconciler fuzzy client-side filter:拉全 tenant org_unit list → 雙邊 _strip_org_suffix 後比對 → confidence 0.7

新增 ~13 case

  • PersonReconciler 8(4 normalized 主流 + alias-strip / no-plus skip / inactive / domain-exception 邊界;4 fuzzy 主流 + login_name / no-email / no-@ / no-domain / inactive-candidate / cache-exception 邊界)
  • OrganizationReconciler 5(3 normalized 全形 / collapse / no-diff;5 fuzzy 中文suffix / 英文suffix / no-suffix-skip / cache-exception / no-match)

Test 全綠

A3 Session D pytest 結果:
  tests/test_a3_reconciliation_base.py             7 passed
  tests/test_a3_reconciliation_facade.py           3 passed
  tests/test_a3_reconciliation_normalizers.py     13 passed
  tests/test_a3_reconciliation_person.py          15 passed
  tests/test_a3_reconciliation_organization.py    13 passed
  tests/test_a3_reconciliation_a2_integration.py   6 passed  ← T3 新增
  tests/test_party_reconciliation_service.py       8 passed
  tests/test_ssp_excel_import_app_service.py      29 passed
  ─────────────────────────────────────────────────────────
  累計                                            96 passed

A3 累計新 test:57(13 normalizers + 7 base + 3 facade + 15 person + 13 org + 6 A2 integration); 本 session 新增:19(6 A2 integration + 8 person normalized/fuzzy + 5 org normalized/fuzzy)。

2. Commits 清單(feature/ssp-import-export-phase2)

773e108  feat(oscal): A3 T4 fuzzy match algorithm
854b238  feat(oscal): A3 T3 wire A2 confirm flows to reconciliation

外加本 SUMMARY + handoff prompt commit 一次(docs-only)。

3. 行為差異(A3 ship 後系統行為變化)

Caller A3 前 A3 後
docx flow(parse-time + confirm-time) email exact / name exact 二態 三階段降級(exact / normalized / fuzzy_email_domain / fuzzy_name_prefix / unmatched)
A2 confirm flow(_write_all_data 不呼叫 reconcile(A2 phase 跳過串接) 寫入 oscal_parties 前先 reconcile,行為與 docx 一致
ParsedParty match_method / match_confidence 欄位 match_method(StrEnum)+ match_confidence(float 0.0~1.0)
A2 fuzzy 行為 N/A 自動寫入(行為等同 matched)— A5 改 user 拍板(F2 follow-up)

4. 規範文件清單(Session D 端)

文件 狀態
docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A3.md 未動(§11 reconciliation 待 Session E 補 7 條)
docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A3.md 未動(plan 早已 ship;Session E 不再 retroactive 改)
docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md tracker 未動(待 Session E 統一 mark A3 done)
docs/changelog/ 未建立(A3 完工 Session E T7.1 寫)
docs/issues/ 未動(無新 issue)
docs/analysis/ 未動(A3 重大決策已落在 design-A3)

5. 已知 follow-up(沿用累計)

ID 內容 處理時機
F1 docx parser role normalize map A3 後獨立 commit / issue resolution
F2 A2 fuzzy user 拍板路徑 A5 phase 預覽 UI
F3 A4 加 5 個新 reconciler A4 phase
F4 jedi-auth Query Entity 加 _in_email_domain DB-side filter Prod 大 tenant feedback 後評估
F5 OSCAL party ↔︎ project_participant 雙寫 未來 OSCAL 匯出規劃
F6 Dev DB oscal_responsible_parties.role_id 髒資料 audit A3 ship 後
F7 docx parser fuzzy 預覽 UI 互動 A5 phase
F-env .env JSON quote 規格化 獨立 issue
F-T1-pre 2 個 pre-existing docx test fail(與 A3 無關) 獨立 issue
T8 cucumber GitLab env 配齊 Session E partial pass 時列

6. 部署 handover

項目 說明
BE 重啟必做 T3 改動 app/oscal/service/ssp_excel_import_app_service.py — BE 沒 hot reload,user 必 lsof -ti:8000 | xargs kill -9
jedi-oscal version 不動 — path-dep dev,A3 完工不 bump(Phase 2 整體 ship 才 bump)
migration 無(ParsedParty 是 dataclass,不涉 schema)
rollback path revert 854b238 + 773e108 兩 commit — 不留遺跡,因 reconcile 結果只標在 ParsedParty(in-memory)+ oscal_parties.matched_user_id/matched_org_unit_id(既有欄位,rollback 後依然合法 null)
smoke 提醒 T4 commit 純 domain layer 改動,BE process 不重啟下啟動 OscalContainer 仍 wire 對;但用 manual e2e 走 confirm_import path 必先重啟 BE

7. Session D 規範遵守 ✓

8. Session D → E 銜接

Session E 是 A3 收尾站,預估 ~1.25d。Task:T5 test 補洞 + T6 cucumber + T7 收口(changelog / design §11 reconciliation / tracker / final SUMMARY)。

A3 完工後:

  • BE branch feature/ssp-import-export-phase2 累計 ~39 commits 未 push(user 點頭才 push)
  • 可接 A4 phase(5 個新 reconciler — devices / info_systems / leveraged / catalog controls / AOs)