# C8 — `_ROLE_LABEL_MAP` 清理 + Role ID 修正

> **級別**：小
> **依賴**：C1（system_menus 已 seed，提供權威 role list）
> **被依賴**：B 階段匯出 + C2 SSP party endpoint

---

## 1. 目標

清理 SSP 匯出 generator 內 hardcoded 的 role label map，移除錯誤混入的專案角色（manager/auditor/viewer），修正 `security-officer` vs `system-security-officer` 對不上的 bug。

## 2. 範圍

### In scope

| 檔案 | 修正項 |
|------|--------|
| `app/oscal/service/export/ssp_docx_generator.py:32` `_ROLE_LABEL_MAP` | 移除 manager / auditor / viewer；修 `security-officer` → `system-security-officer`；補齊 C1 9 個角色 |
| `domain/oscal/adapter/cmmc_ssp_adapter.py:49` `_ROLE_LABEL_PATTERNS` | 擴增到 9 個角色對應的中/英文 regex pattern |

### Out of scope

- `system_menus` master data（已在 C1 處理）
- FE dropdown 改 API（屬 C2）
- Excel template `sheet_definitions.py:138` role enum（屬 C2/C5 整合改造）

## 3. 改動詳情

### 3.1 `ssp_docx_generator.py` — `_ROLE_LABEL_MAP`

**改動前**（10 條，含 3 條錯誤）：
```python
_ROLE_LABEL_MAP: dict[str, str] = {
    "responsible-organization": "Responsible Organization",
    "information-owner":        "Information Owner",
    "information-provider":     "Information Provider",
    "information-receiver":     "Information Receiver",
    "system-owner":             "System Owner",
    "security-officer":         "Security Officer",       # ⚠️ ID 對不上 adapter
    "manager":                  "Manager",                 # ⚠️ 專案角色不該出現
    "auditor":                  "Auditor",                 # ⚠️ 專案角色不該出現
    "viewer":                   "Viewer",                  # ⚠️ 專案角色不該出現
}
```

**改動後**（9 條，與 C1 master 對齊）：
```python
# 對齊 C1 system_menus group='ssp_party_role' 9 個 OSCAL 標準角色
# 此 map 僅用於 SSP docx 匯出時將 role_id 轉成英文 label（OSCAL 文件慣例）
_ROLE_LABEL_MAP: dict[str, str] = {
    "responsible-organization": "Responsible Organization",
    "system-owner":             "System Owner",
    "system-security-officer":  "System Security Officer",
    "authorizing-official":     "Authorizing Official",
    "information-owner":        "Information Owner",
    "information-provider":     "Information Provider",
    "information-receiver":     "Information Receiver",
    "prepared-by":              "Prepared By",
    "prepared-for":             "Prepared For",
}
```

**Fallback 行為**：未在 map 內的 role_id 仍直接用 role_id 當 label 顯示（既有行為不變）。

### 3.2 `cmmc_ssp_adapter.py` — `_ROLE_LABEL_PATTERNS`

**改動前**（5 條）：
```python
_ROLE_LABEL_PATTERNS = [
    ("responsible-organization", re.compile(r"Responsible Organization|專案負責單位", re.I)),
    ("information-provider", re.compile(r"Information provider|資料提供者|上游廠商", re.I)),
    ("information-receiver", re.compile(r"Information receiver|資料接收者|專案窗口", re.I)),
    ("system-owner", re.compile(r"System Owner|系統所有者", re.I)),
    ("system-security-officer", re.compile(r"System Security Officer|系統安全官", re.I)),
]
```

**改動後**（9 條，含新增 4 個 OSCAL 標準角色 pattern）：
```python
_ROLE_LABEL_PATTERNS = [
    ("responsible-organization", re.compile(r"Responsible Organization|專案負責單位", re.I)),
    ("system-owner",             re.compile(r"System Owner|系統所有者", re.I)),
    ("system-security-officer",  re.compile(r"System Security Officer|ISSO|系統安全官", re.I)),
    ("authorizing-official",     re.compile(r"Authorizing Official|授權機關|AO", re.I)),
    ("information-owner",        re.compile(r"Information Owner|資料所有者", re.I)),
    ("information-provider",     re.compile(r"Information Provider|資料提供者|上游廠商", re.I)),
    ("information-receiver",     re.compile(r"Information Receiver|資料接收者|專案窗口", re.I)),
    ("prepared-by",              re.compile(r"Prepared By|文件撰寫者", re.I)),
    ("prepared-for",             re.compile(r"Prepared For|文件對象", re.I)),
]

_ORGANIZATION_ROLES = {"responsible-organization", "prepared-for"}  # 補 prepared-for
```

**注意**：
- 順序保留語意（一般 docx 出現順序）
- regex pattern 維持 `re.I` (case-insensitive)
- `_ORGANIZATION_ROLES` 補 `prepared-for`（亞航文件慣例下這個是組織單位）

## 4. 影響面

### 4.1 SSP docx 匯出（B 階段功能）

- 既有 SSP 內若有 `role_id='security-officer'` 的歷史資料 → 不再有對應 label，匯出時直接顯示 `security-officer` 字串
- **建議**：跑一次 data audit SQL 確認既有資料有無此 role：
  ```sql
  SELECT DISTINCT role_id, COUNT(*)
  FROM oscal.oscal_responsible_parties
  WHERE role_id = 'security-officer'
  GROUP BY role_id;
  ```
- 若有資料：寫一次性 SQL 把 `security-officer` → `system-security-officer`（design 預設此 case 為零，pre-flight 驗證後再決定）

### 4.2 CMMC SSP docx 匯入

- 新版 adapter 可辨識更多角色 pattern → 解析覆蓋率提升
- 既有解析過的 SSP 不受影響（已存進 DB）

### 4.3 既有 test fixtures

- `tests/test_cmmc_ssp_adapter.py` 可能有 hardcoded role list assertion，需同步更新
- `tests/test_ssp_docx_import_diff_response.py` 同上
- `tests/test_ssp_write_strategy_v2_parties.py` 同上

## 5. 邊界條件

| 情境 | 行為 |
|------|------|
| 既有 SSP 用 `security-officer` 角色 | 匯出顯示 raw key（不會 fall through 到舊 label）|
| docx 內出現未識別的中英文 role pattern | 解析時 `role=None`（既有 fallback 不變）|
| 同一個 party 在 docx 內被標記多個 role | 沿用 adapter 既有行為（取第一個）|

## 6. 待 user 拍板的小決策

| 編號 | 問題 | 我建議 |
|------|------|--------|
| C8-D1 | DB 內若有 `role_id='security-officer'` 的歷史資料，要不要寫一次性 SQL 改成 `system-security-officer`？ | **要**（不留 broken role_id），但 pre-flight 先 audit 數量 |
| C8-D2 | `_ORGANIZATION_ROLES` 是否確定 `prepared-for` 算 organization？亞航文件用法是 person 還是 org？ | **看亞航 reference docx 內 prepared-for 區塊**，pre-flight 驗證 |

---

## 7. 開發後狀態

- `_ROLE_LABEL_MAP` 9 條乾淨對齊 OSCAL 標準
- `_ROLE_LABEL_PATTERNS` 擴增到 9 個角色
- 既有 test fixtures 更新
- （optional）一次性 SQL 修 broken role_id 資料
