Phase:A3(Track A:Excel 匯入第三階段) 狀態:BE shipped ✅(Session A→E 共 5 session 全完成) Ship 日期:2026-05-20 Task arc 跨度:2026-05-20 同日 5 session(A 設計 / B 計畫 / C 架構 / D fuzzy / E 收口) 依賴本 phase:A4(5 個新 reconciler)建在本 phase
BaseReconciliationService上
feature/ssp-import-export-phase2)| Commit | 內容 | Session |
|---|---|---|
b011f2f |
feat(oscal): A3 T1 base + reconcilers skeleton | C |
82f0dc8 |
feat(oscal): A3 T2 facade + DI wiring | C |
854b238 |
feat(oscal): A3 T3 wire A2 confirm flows to reconciliation | D |
773e108 |
feat(oscal): A3 T4 fuzzy match algorithm | D |
761fd7c |
test(oscal): A3 T5 unit + integration tests | E |
(本 commit) |
docs(ssp-import-export-phase2): A3 收尾 — changelog + design §11 + SUMMARY | E |
外加 4 個 Session 收尾 docs commit(A→B / B→C / C→D / D→E handoff prompt + Session SUMMARY)。
feature/a3-ssp-party-match-regression)| Commit | 內容 |
|---|---|
27e52fe |
test(compliance-manager-test): A3 T6 cucumber party match regression |
不動(design §10 鎖定範圍)。jedi-oscal 維持 path-dep(feature 完成才一次 bump + Nexus)。
Domain layer(6 個 .py):
domain/oscal/service/reconciliation/
├── __init__.py # export BaseReconciliationService + MatchMethod
├── base.py # BaseReconciliationService[TParsed, TEntity] (97 行)
├── match_method.py # MatchMethod StrEnum (EXACT/NORMALIZED/FUZZY_EMAIL_DOMAIN/FUZZY_NAME_PREFIX/UNMATCHED)
├── _normalizers.py # _strip_plus_alias + _normalize_name + _strip_org_suffix (41 行)
├── person_reconciler.py # PersonReconciler 三階段 (85 行)
└── organization_reconciler.py # OrganizationReconciler 三階段 (84 行)
Test layer(7 個 test 檔,67 個 A3 新 test):
tests/
├── test_a3_reconciliation_normalizers.py # 13 case
├── test_a3_reconciliation_base.py # 7 case
├── test_a3_reconciliation_facade.py # 3 case
├── test_a3_reconciliation_person.py # 16 case
├── test_a3_reconciliation_organization.py # 16 case
├── test_a3_reconciliation_a2_integration.py # 6 case
└── test_a3_parsed_party_fields.py # 4 case (T5.2 新)
Changelog + tracker:
docs/changelog/2026-05-20-feat-party-matcher-shared.md
docs/features/FR-011.2-2605-ssp-import-export-phase2/handoff/2026-05-20-a3-SUMMARY.md (本文件)
| 檔案 | 改動 |
|---|---|
domain/oscal/service/party_reconciliation_service.py |
改門面(facade),dispatch by party_type 到兩個 reconciler;公開 method reconcile(parties, tenant_id) 簽章不變 |
domain/oscal/parser/ssp_intermediate.py |
ParsedParty 加 match_method(default MatchMethod.UNMATCHED)+ match_confidence(default 0.0)optional 欄位 |
app/oscal/service/ssp_excel_import_app_service.py |
_write_all_data 補 self._reconciliation.reconcile(all_parties, tenant_id=...) 呼叫(在 write_parties 前;try/except logger.warning swallow) |
di_containers/oscal/oscal_containers.py |
新增 person_reconciler / organization_reconciler Factory;門面改注入兩 reconciler |
docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md |
A3 row → BE shipped ✅ + commit chain |
docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A3.md |
§11 reconciliation 填 9 條偏差條目 + 已對齊條目 |
features/regression/module-frame/05-ssp-docx-import-party-match.feature
steps/module-frame/ssp-docx-import-party-match.steps.js
| Caller | A3 前 | A3 後 |
|---|---|---|
Docx flow parse-time(SspDocxImportAppService line 258-264) |
exact match only | exact + normalized + fuzzy 三階段 fallback |
| Docx flow confirm-time(line 489-490) | 同上 | 同上 |
A2 superset flow(_confirm_superset_flow → _write_all_data) |
不呼叫 reconcile | 呼叫 reconcile,fuzzy 自動寫入 |
A2 update flow(_confirm_update_flow → _write_all_data) |
不呼叫 reconcile | 呼叫 reconcile,fuzzy 自動寫入 |
ParsedParty dataclass |
無 match_method / match_confidence 欄位 |
加 2 個 optional 欄位(default UNMATCHED / 0.0),既有 caller 不破壞 |
PartyReconciliationService 公開介面 |
直接接 jedi-auth domain service | 改門面,內部 dispatch 兩 reconciler,公開 method 簽章 100% 不變 |
Fuzzy 演算法:
+alias 移除 → fuzzy 拉全 tenant cache → email domain + nickname/login_name 對齊Confidence + UI 互動:
EXACT / NORMALIZED confidence=1.0 → A3 期間都自動寫入FUZZY_* confidence=0.7 → A3 期間也自動寫入;A5 phase 預覽 UI 補 user 拍板(follow-up F2)| 項目 | 路徑 | 狀態 |
|---|---|---|
| Changelog | docs/changelog/2026-05-20-feat-party-matcher-shared.md |
✅ 完成(commit chain + behaviour diff + follow-up F1~F7+T8) |
| Tracker | docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md |
✅ A3 row → BE shipped |
| Design §11 reconciliation | docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A3.md |
✅ 9 條偏差條目 + 已對齊條目(分 11.1~11.5 5 子段) |
| Implementation plan | docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A3.md |
✅ Session B 已 ship(~2,045 行) |
| Design.md | docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A3.md |
✅ Session A 已 ship(13 章 / ~670 行 + §11 補完) |
| 對話歷史 dump | docs/conversation-history/2026-05-20/ssp-import-export-phase2-A3/ |
⚠ 留待 user export 後 dump(不二次加工 / 原樣寫入) |
| Issue 修補關係 | 對應 docs/issues/pending/2026-05-19-person-role-cross-domain-inconsistency.md 修補 A(A1 已 ship 修補)— A3 不整併修補 B(role normalize map),列 follow-up F1 |
✅ |
| # | 項目 | 處理時機 |
|---|---|---|
| F1 | Issue 修補 B(docx parser role normalize map)— role 是 enum 規範問題、不是 matcher 問題 | A3 後獨立 commit / 或 issue resolution 階段 |
| F2 | A5 phase 補 A2/docx fuzzy 的 user 拍板路徑(A3 期間 fuzzy 自動寫入) | A5 phase |
| F3 | A4 加 5 個新 reconciler(device / info_system / leveraged / catalog control / AO)建在 BaseReconciliationService 上 | A4 phase(直接擴 base) |
| F4 | jedi-auth Query Entity 加 _in_email_domain / _in_name_prefix 補 fuzzy DB-side filter |
Prod 大 tenant feedback 後評估 |
| F5 | OSCAL party ↔︎ project_participant 雙寫設計(issue 修補 D) | 未來 OSCAL 匯出規劃時 |
| F6 | Dev DB oscal_responsible_parties.role_id 髒資料 audit / 清理(issue 修補 C) |
A3 ship 後執行 audit SQL |
| F7 | docx parser 拿到 fuzzy 標記後預覽 UI 互動(A5-similar UI) | A5 phase |
| T8 | Cucumber partial ship:GitLab env 配齊後跑 5 scenarios + 補 fixture user/org seeding helper + docx fixture ASIA-CMMC-SSP-DRAFT-party-match.docx |
env 配齊後 |
| F-T1-pre | 既有 docx 2 個 pre-existing fail(test_create_mode_get_parse_result_all_controls_added / test_get_parse_result_party_added_when_ssp_has_no_parties) |
與 A3 無關,獨立排查 |
| F-env | .env JWT_SECRET={...} 沒 quote 導致 source .env syntax error |
工具修補 |
pyproject.toml dev-path 改動不 commit;feature 完成才一次 bump version + 推 Nexus)pyproject.toml:A3 期間 dev-path 改動 working tree 仍未 commit(正常 — Phase 2 整體完工才一起還原 + commit)T3 commit 854b238 改動了 app service 層(app/oscal/service/ssp_excel_import_app_service.py)— BE 沒 hot reload,下次 manual smoke 測 A2 reconcile 串接前必先重啟:
lsof -ti:8000 | xargs kill -9
nohup poetry run python main_app.py > /dev/null 2>&1 &oscal_parties / oscal_responsible_parties schema 不動oscal_responsible_parties.role_id 髒資料 audit / 清理列 F6 follow-up(與 A3 無關)A3 完工後:
BaseReconciliationService[TParsed, TEntity];template 在 domain/oscal/service/reconciliation/{person,organization}_reconciler.py 樣板上 copy 即可feature/a3-ssp-party-match-regression 尚未 merge 回 mainfixtures/cmmc/imports/ssp-docx/ASIA-CMMC-SSP-DRAFT-party-match.docx(含預期 5 scenarios party 內容)npm run cucumber -- features/regression/module-frame/05-ssp-docx-import-party-match.feature✅ A3 phase 完整 ship — 5 session(A→E)on schedule,partial ship cucumber 允許
進入 A4 phase 條件已就緒:
下一個 phase:A4 — devices / info_systems / leveraged / 控制項 / AO 鉤稽(5 個新 reconciler 建在 BaseReconciliationService 上)。