# A3 Phase Final SUMMARY — 共用 matcher 抽取 + fuzzy 三態 + A2 串接

> **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` 上

---

## 1. Commit 清單

### compliance-manager-be（branch `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）。

### compliance-manager-test（branch `feature/a3-ssp-party-match-regression`）

| Commit | 內容 |
|--------|------|
| `27e52fe` | test(compliance-manager-test): A3 T6 cucumber party match regression |

### jedi-* 套件

**不動**（design §10 鎖定範圍）。jedi-oscal 維持 path-dep（feature 完成才一次 bump + Nexus）。

---

## 2. 改動範圍（檔案清單）

### 新建檔案（compliance-manager-be）

**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 條偏差條目 + 已對齊條目 |

### compliance-manager-test 新建

```
features/regression/module-frame/05-ssp-docx-import-party-match.feature
steps/module-frame/ssp-docx-import-party-match.steps.js
```

---

## 3. 行為差異（A3 前 vs A3 後）

| 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 演算法**：
- **Person**：normalized `+alias` 移除 → fuzzy 拉全 tenant cache → email domain + nickname/login_name 對齊
- **Organization**：normalized 全形→半形 / collapse → fuzzy 拉全 tenant cache → 雙邊 strip suffix 後比對

**Confidence + UI 互動**：
- `EXACT` / `NORMALIZED` confidence=1.0 → A3 期間都自動寫入
- `FUZZY_*` confidence=0.7 → **A3 期間也自動寫入**；A5 phase 預覽 UI 補 user 拍板（follow-up F2）

---

## 4. 規範文件齊全度

| 項目 | 路徑 | 狀態 |
|------|------|------|
| 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 | ✅ |

---

## 5. 已知 follow-up

| # | 項目 | 處理時機 |
|---|------|---------|
| 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 | 工具修補 |

---

## 6. 部署 Handover

### 6.1 套件狀態

- **jedi-oscal**：維持 path-dep（`pyproject.toml` dev-path 改動不 commit；feature 完成才一次 bump version + 推 Nexus）
- **jedi-auth / jedi-common**：A3 不動 — 維持 Nexus pin
- **主專案 `pyproject.toml`**：A3 期間 dev-path 改動 working tree 仍未 commit（正常 — Phase 2 整體完工才一起還原 + commit）

### 6.2 BE 重啟需求

**T3 commit `854b238` 改動了 app service 層**（`app/oscal/service/ssp_excel_import_app_service.py`）— BE 沒 hot reload，下次 manual smoke 測 A2 reconcile 串接前**必先重啟**：

```bash
lsof -ti:8000 | xargs kill -9
nohup poetry run python main_app.py > /dev/null 2>&1 &
```

### 6.3 DB / Migration

- A3 **無 schema 異動**（純 in-memory 行為改動）
- 既有 `oscal_parties` / `oscal_responsible_parties` schema 不動
- `oscal_responsible_parties.role_id` 髒資料 audit / 清理列 F6 follow-up（與 A3 無關）

### 6.4 後續 phase 開工建議

A3 完工後：
- **A4 phase**（5 個新 reconciler）直接擴 `BaseReconciliationService[TParsed, TEntity]`；template 在 `domain/oscal/service/reconciliation/{person,organization}_reconciler.py` 樣板上 copy 即可
- **A5 phase**（預覽 UI + Confirm）補 fuzzy 拍板路徑，drift fuzzy 自動寫入 → 預覽攔截

### 6.5 Cucumber T6 partial ship 注意

- compliance-manager-test branch `feature/a3-ssp-party-match-regression` **尚未 merge 回 main**
- Env 配齊後：
  1. 補 `fixtures/cmmc/imports/ssp-docx/ASIA-CMMC-SSP-DRAFT-party-match.docx`（含預期 5 scenarios party 內容）
  2. seeding helper（admin API or test data hook 種 user/org_unit）
  3. 跑 `npm run cucumber -- features/regression/module-frame/05-ssp-docx-import-party-match.feature`
  4. 全綠後 merge

---

## 7. A3 task arc 規範遵守清單

- [x] 顯式 `git add <file>`，禁 `-am` / `-A`（6 個 BE commit + 1 個 test repo commit 都用 explicit list）
- [x] Commit 含 `Co-Authored-By: Claude Opus 4.7 (1M context)` footer
- [x] jedi-* 不動
- [x] `pyproject.toml` dev-path 改動不 commit
- [x] DDD 嚴格分層 — reconciler 純 domain layer 不接 session；reconcile 串接點在 app service `@transaction` scope 內
- [x] T5 commit 後跑全套 pytest（694 passed / 40 pre-existing fail，0 A3 相關 regression）
- [x] BE 重啟需求已標 §6.2
- [x] 不寫 docstring / 註解除非真有 non-obvious 的 why（base.py 4 hook + `_get_or_load_candidates` exception 行為兩處屬例外）
- [x] design §11 reconciliation 不空段 — 9 條偏差條目 + 已對齊條目 ship
- [x] handoff 文件統一放 `docs/features/<feature>/handoff/`（CLAUDE.md 2026-05-20 新規）
- [x] 跨 repo 切換時提醒：cucumber 在 compliance-manager-test repo，branch / commit 分開
- [x] 階段性 commit 不用問

---

## 8. A3 phase 收口確認

✅ **A3 phase 完整 ship — 5 session（A→E）on schedule，partial ship cucumber 允許**

進入 A4 phase 條件已就緒：
- BaseReconciliationService 抽象基類 + 兩個 reference reconciler（person / organization）+ A2/docx 兩條 caller 串接 + 67 個 A3 新 test 全綠 + cucumber regression baseline 撰寫完成（env 配齊後跑通即 ship）

下一個 phase：**A4 — devices / info_systems / leveraged / 控制項 / AO 鉤稽**（5 個新 reconciler 建在 BaseReconciliationService 上）。
