# SSP DOCX Format Handoff — 2026-05-22

## 接手 Prompt

接手這個 session 請直接從「待辦事項」開始，不需要重新分析。branch = `feature/ssp-import-export-phase2`。

---

## 本 Session 完成內容

### B6：SSP DOCX 格式升級（對齊 CMMC 樣板）

這個 session 完整重寫了 SSP DOCX 匯出的模板與渲染器，對齊正式 CMMC SSP 文件格式。

#### 關鍵 bug fix：docxtpl `get_docx()` 回傳未渲染原件

**Root cause**：`DocxTemplate.get_docx()` 回傳的是未 render 的原始 `Document` 物件。正確做法是 `tpl.save(buf)` → `Document(buf)` 重新開啟。
```python
# 修正前（錯的）
tpl.render(context)
doc = tpl.get_docx()   # ← 未渲染！

# 修正後
tpl.render(context)
rendered_buf = BytesIO()
tpl.save(rendered_buf)
rendered_buf.seek(0)
doc = Document(rendered_buf)  # ← 渲染後的文件
```

#### 格式改動摘要

| 區段 | 變更 |
|------|------|
| 封面 | 標題居中（28pt bold）、系統名稱、下方 metadata 表（Version/Date/Prepared by） |
| 所有標題 / 欄位名稱 | 全改純英文，移除中英夾雜（如「Field 欄位」→「Field」、「系統資訊」→移除） |
| System Information | 動態 loop（只顯示有值的列），移除 "Field/Value" 深色表頭 |
| Responsible Organization | 動態 loop（只顯示有值的列：Name / Address / Phone / Email） |
| Parties | 移除 6 張個別角色表，改為一張 Participants 合併表（Name / Role / Email），`party_type=organization` 過濾掉只出現在 Org 區塊 |
| Assessment Objectives | 改為 bullet list（`ao_name` 直接列，不再有 statement_id 前綴重複問題） |
| 控制項 heading | "REQUIREMENTS 安全要求" → "Requirements" |
| 欄位比例 | kv 表格 label=5cm / value=11cm；各 loop 表格均設定欄寬 |
| 檔名 | 修正 ASCII-only（Python `isalnum()` 對中文回傳 True，改用 `c.isascii() and c.isalnum()`） |

---

## 異動檔案清單

| 檔案 | 類型 | 說明 |
|------|------|------|
| `scripts/generate_ssp_docx_template.py` | 完整重寫 | 封面 + 所有 section，純英文，動態 loop 表格 |
| `app/oscal/templates/ssp/ssp_cmmc_template.docx` | 重新產生 | 執行上方腳本產出 |
| `app/oscal/service/export/ssp_docx_generator.py` | 大改 | render 流程修正、`_build_org_rows`、`_build_participants`、`_build_system_info_rows`、AO bullet list |
| `app/oscal/service/export/ssp_export_app_service.py` | 小改 | 檔名 ASCII-only fix（2 處 `safe_name` 生成） |
| `app/oscal/service/export/ssp_export_model.py` | 小改 | `SspPartyExportItem.role_id`、`SspControlImplExportItem.control_requirement` 新增欄位 |
| `app/oscal/service/export/ssp_mf_content_loader.py` | 中改 | parties 填 `role_id`；controls 填 `control_requirement` |
| `app/oscal/service/export/ssp_version_content_loader.py` | 大改 | `_build_parties()` 改從 `oscal_responsible_parties` 載入（而非舊的 metadata.parties），帶正確 `role_id` |
| `di_containers/oscal/oscal_containers.py` | 小改 | `ssp_version_content_loader` 注入 `responsible_party_domain_service` + `party_domain_service` |
| `pyproject.toml` | dev-only | jedi-oscal path dep（不 commit） |
| `docs/changelog/2026-05-22-feat-ssp-docx-template-rebuild.md` | 新增 | changelog |

---

## Context 重要資訊

### Generator 架構

```
SspDocxGenerator.generate()
  ↓
  DocxTemplate(template_path)        ← app/oscal/templates/ssp/ssp_cmmc_template.docx
  tpl.render(context)                ← 渲染靜態段落（封面～System Environment）
  tpl.save(rendered_buf)             ← 必須用 save()，get_docx() 是未渲染原件
  Document(rendered_buf)             ← 重新開啟
  _append_control_sections(doc)      ← python-docx 動態 append 每個控制項
  _append_reference_docs(doc)        ← python-docx 動態 append 附錄
  doc.save(buf)
```

### Context Keys（傳給 docxtpl 的）

```python
{
    "system_name": str,
    "version": str,
    "export_date": str,        # "YYYY-MM-DD"
    "author": str,
    "general_description": str,
    "network_architecture": str,
    "data_flow": str,
    "system_info_rows": [{"label": str, "value": str}],   # 只含有值的列
    "org_rows": [{"label": str, "value": str}],            # 只含有值的列
    "participants": [{"name": str, "role_label": str, "email": str}],  # person only
    "inventory_items": [...],
    "components": [...],
    "leveraged_authorizations": [...],
}
```

### 測試帳號 / 資料

- Dev DB：`192.168.50.188:25432 / guidant_ai_dev`
- MF 測試資料：`module_frame_uid = 3699fc01-3398-40af-be20-ef7913451a27`（TEST Excel Import）
- 登入：`blsadmin / Billows@123!`

---

## 待辦事項

### 立即要做（接手後第一件事）

1. **BE 尚未重啟**（本 session 沒有重啟 BE）
   ```bash
   # 先找舊 process
   lsof -ti:8000 | xargs kill -9
   # 重啟
   python main_socketio.py
   ```

2. **Smoke test**（重啟後）
   - MF export：`GET /oscal/ssp/export?module_frame_uid=3699fc01-3398-40af-be20-ef7913451a27&format=docx`
   - SSP version export：找一個有控制項的 SSP uid 測試
   - 確認：封面有系統名稱、System Information 只顯示有值的列、Participants 只有 person、控制項格式正確

3. **Commit 所有異動**（smoke test 通過後）
   ```
   git add app/oscal/service/export/ssp_docx_generator.py \
           app/oscal/service/export/ssp_export_app_service.py \
           app/oscal/service/export/ssp_export_model.py \
           app/oscal/service/export/ssp_mf_content_loader.py \
           app/oscal/service/export/ssp_version_content_loader.py \
           app/oscal/templates/ssp/ssp_cmmc_template.docx \
           di_containers/oscal/oscal_containers.py \
           scripts/generate_ssp_docx_template.py \
           docs/changelog/2026-05-22-feat-ssp-docx-template-rebuild.md \
           docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-docx-template-rebuild.md
   # pyproject.toml 的 path dep 變更不 commit
   ```

### 後續可能的調整

- 使用者看到 DOCX 後可能還有格式調整需求（欄寬、字體、顏色）
- 若需要新增更多 metadata 欄位（如 org contact info），在 `ssp_export_model.py` 加欄位後更新兩個 loader

---

## 已知限制

- AO 表格目前無翻譯（catalog 尚無 ZH 翻譯），只有英文 `ao_name`
- MF 來源的 Responsible Organization 通常只有 Name，Address/Phone 留空
- `network_architecture` 和 `data_flow` 欄位目前 MF / SSP version loader 都回傳空字串（尚無對應 DB 欄位）
- `pyproject.toml` 有 jedi-oscal path dep（dev 用），不要 commit
