# SSP 匯入匯出 Phase 2 — 需求理解整理版

> **狀態**：Phase 0+ — 需求理解 v5（A0.1 結構校正後）
> **建立日期**：2026-05-18
> **最後更新**：2026-05-19（v5：A0 後 review 發現 main 表缺、命名不一致、鉤稽雙路徑 → A0.1 結構重整補丁）
> **版本歷程**：
> - v1：草稿整理 + 11 個待釐清項
> - v2：Raymond 第一輪回覆 → Q1~Q11 resolved + 補充 OSCAL 匯出 + Excel 樣板設計
> - v3：誤判 — 主張建三張新 OSCAL mirror 表 + 廢既有通用表
> - v4：校正 — 既有表本就是 OSCAL system-implementation 多型鏡像，改走「擴充既有表」方案（→ A0 shipped）
> - **v5**：A0.1 結構重整 — main 表 / items rename / join 表 / 鉤稽路徑統一（詳見 `design-A0.1.md` + `docs/analysis/2026-05-19-ssp-system-impl-restructure-rationale.md`）
> **撰寫人**：Claude（依 `requirement.md` 草稿整理）
> **下一步**：A0.1 開工 → A1 / B1 平行

---

## ⚠️ v5 校正 — A0.1 結構重整摘要（**讀 §2 前必看**）

A0 shipped 後 review 發現幾個結構問題：
1. 既有 `system_security_plan_system_implementations` 表 = items 不是 main，缺 1:1 main 層
2. A0 加的 `information_system_id` 跟既有 `system_characteristics` 中介路徑重疊
3. OSCAL `system-implementation` 是 block（無 UUID），DB 仍應有 main 表（為 PG 規範 + block-level metadata anchor）
4. 表名 `ssp_*` 前綴慣例不一致

**v5 補丁（A0.1）**：

| 項目 | v4（A0 shipped）| v5（A0.1 校正後）|
|------|----------------|-----------------|
| Items 表名 | `system_security_plan_system_implementations` | **`ssp_system_implementation_items`** |
| Main 表 | ❌ 無 | **`ssp_system_implementations`**（1:1 per scope）|
| Join 表 | ❌ 無 | **`ssp_inventory_item_components`**（M:N）|
| Items 鉤稽 information_system | `information_system_id` 直連 tenant | **`system_characteristic_id`** 走 SSP 中介 |
| `system-implementation.users[]` | 列「視需求才做」| **不做**（既有 oscal_parties cover 人員需求）|

詳細 A0.1 規格見：
- `docs/features/FR-011.2-2605-ssp-import-export-phase2/design-A0.1.md`
- `docs/features/FR-011.2-2605-ssp-import-export-phase2/implementation-plan-A0.1.md`
- `docs/analysis/2026-05-19-ssp-system-impl-restructure-rationale.md`

---

## 0. 文件用途

這份不是正式 spec，是 Claude 把 `requirement.md` 草稿讀完之後，把「**我理解你想做的事情**」用比較結構化的方式寫回來，方便：

1. raymond 一眼看出有沒有理解錯
2. 開始切階段、寫 design 之前先對齊認知
3. 列出我看到「草稿沒寫清楚」或「需要你決策」的項目

確認過後再進入 brainstorm + design.md + implementation-plan.md + test-plan.md。

---

## 1. 需求背景 — 為什麼要做這件事

### 1.1 「合規資源庫」是什麼

`module_frame` 是專案的範本層 — 顧問先把客戶現有的安全控制現況、政策程序書、組織單位、參與人員、設備、系統等資訊整理進「合規資源庫」，之後客戶建專案時，這些資訊會被當成預設值帶入該專案的 SSP，省去重複輸入，也作為 OSCAL 匯出的資料來源。

### 1.2 目前匯入匯出能力的限制

| 路徑 | 能匯什麼 | 限制 |
|------|---------|------|
| **Docx 匯入**（已上線 v2） | metadata / parties / leveraged services / 控制項實作 / AOs | 來源是顧問訪談後手寫的 SSP 草稿 docx，客戶不一定有這份文件 |
| **Excel 匯入**（舊版） | 只匯「控制項現況說明」+「AO 現況說明」+「參考程序書」 | 涵蓋面很窄，缺基本資料、設備、單位、人員、系統 |
| **Excel 匯出** | 現況說明 Excel 樣板下載 | 同上，只覆蓋現況說明 |
| **SSP 文件匯出** | （目前沒有；OSCAL JSON 匯出有） | 客戶想要的是可讀的 docx/pdf，不是 OSCAL JSON |

### 1.3 真正的痛點

- **顧問端**：訪談時想用 Excel 收資料（客戶熟悉 Excel），但目前 Excel 只能收一小部分，其他還是要靠 docx 或手動進系統建。
- **客戶端**：想拿到一份完整可讀的 SSP 文件（docx / pdf）作為交付物，目前系統只能匯 OSCAL 結構化資料。
- **資料一致性**：Excel 匯入後沒有跟「設備 / 單位 / 人員 / 系統」做鉤稽，匯進來的是孤立字串，沒辦法被後續流程引用。

### 1.4 本期目標（一句話）

讓**「合規資源庫」的所有主要資料領域**都能透過 Excel 雙向同步（匯入 / 匯出），並讓「合規資源庫」與「專案 SSP 版本」都能匯出成可讀的 SSP 文件（docx / pdf / odt）+ OSCAL 結構化格式（JSON / XML / YAML）。

### 1.5 Raymond 第一輪回覆補充（2026-05-18）

#### 補充 1：SSP 匯出加 OSCAL 格式

除了 docx / pdf / odt 三種可讀格式，匯出**也要提供 OSCAL 結構化格式**（JSON / XML / YAML）。這部分技術上：
- jedi-oscal 既有 yaml mapper（`infra/mapper/ssp/ssp_yaml_mapper.py`），可作為 base
- 系統目前沒看到 SSP 對外的 OSCAL export endpoint（只有 OSCAL import），所以本期屬於新增功能
- JSON / XML / YAML 三種是 OSCAL 標準格式，互轉成本低，做一個其他兩個幾乎免費

#### 補充 2：Excel 匯入樣板的設計規範

樣板必須：
1. **分 sheet 做不同事情** — 每個資料領域一個 sheet（metadata / parties-organizations / parties-persons / devices / information-systems / leveraged-services / controls / AOs / reference-documents），不擠在同一個 sheet
2. **欄位顏色提示** — 必填欄位用底色標示（沿用既有 template 黃底慣例），選填用無底色或淺色
3. **系統有的資料 → Excel 下拉選單** — 對 Excel `data validation` 規格做下拉選項，使用者選的時候有 dropdown，例如：
   - `system_owner` → 下拉選 tenant 內既有 user（顯示 nickname）
   - `org_unit` → 下拉選既有組織單位
   - `device` → 下拉選既有 `public.devices`
   - `information_system` → 下拉選既有 `compliance.information_systems`
   - `impl_status` / `sensitivity_level` / `deployment_model` 等 enum → 下拉選 enum 值
4. **原本系統表單就是下拉的，Excel 也要下拉** — 對齊 UI 表單行為，避免使用者用 Excel 填了系統不接受的字串

> 💡 **設計考量**：Excel 下拉選單在資料量多時會卡（如 user 上千人），需要評估是用「name list 直接列舉」還是「named range + 工作表隱藏」處理。實作階段再決定。

---

## 2. 用語對齊

| 術語 | Tenant 層實體 | OSCAL Mirror 表 | OSCAL 規格對應 |
|------|------------|----------------|---------------|
| 合規資源庫 | `module_frame` | `system_security_plans` | system-security-plan |
| 基本資料 | `module_frame.metadata` + 引用的 `information_systems` | `system_security_plans_system_characteristics` | system-characteristics |
| 單位 | `org_units` | `oscal_parties` (type=organization)【既有，不動】 | metadata.parties |
| 參與人員 | `users` | `oscal_parties` (type=person)【既有，不動】 | metadata.parties |
| 設備（資產）| `public.devices` | **`system_security_plan_system_implementations` (implementation_type=`inventory-item`)【既有，擴充】** | system-implementation.inventory-items |
| 系統（資訊系統）| `compliance.information_systems` | **`system_security_plan_system_implementations` (implementation_type=`component` / `subsystem`)【既有，擴充】** | system-implementation.components |
| Leveraged Services | （無 tenant 層表，純文字）| **`system_security_plan_system_implementations` (implementation_type=`leveraged-authorization`)【既有，擴充】** | system-implementation.leveraged-authorizations |
| 控制項現況說明 | `module_frame_control_default.implementation_statement` | `system_security_plan_control_implementations` | control-implementation.implemented-requirements |
| AO 現況說明 | `module_frame_control_objective_default.statement` | `ssp_control_implementation_objectives` | implemented-requirements.statements |
| 參考程序書 | `module_frame_reference_document` + mapping | `ssp_reference_documents` + `ssp_reference_document_mappings`【既有】| back-matter.resources + links |

> ✅ **架構決策（v4 校正）**：**擴充既有 `system_security_plan_system_implementations` 表**，不新建三張表。既有表已是「OSCAL system-implementation 多型鏡像」的設計用意，`implementation_type` 已用於區分 system / subsystem / service / component / hardware / software（用 enum `SystemImplementationType`），且 140 筆 hardware 資料 + jedi-oscal 完整 stack 已在運作。擴充比新建更務實。

### 2.1 既有表現況盤點

**Schema**（既有）：

| 欄位 | 型別 | 用途 |
|------|-----|-----|
| id | integer PK | 主鍵 |
| uid | uuid UNIQUE | OSCAL UUID（已 uuid4 default）|
| **system_security_plan_id** | integer NOT NULL FK | **只服 SSP，CASCADE FK** |
| name | varchar(255) NOT NULL | 名稱 |
| description | text | 說明 |
| **implementation_type** | varchar(50) NOT NULL | system / subsystem / service / component / hardware / software（既有 enum）|
| responsible_party | varchar(100) | 負責角色或單位（純字串，沒鉤 oscal_parties.uid）|
| created_at / updated_at / created_user / updated_user | — | 完整稽核欄位 |

**程式碼 stack**（既有，jedi-oscal）：
- ORM：`jedi_oscal/infra/model/ssp/ssp_system_implementation.py`
- Entity / Query Entity：`jedi_oscal/domain/entity/ssp/`
- Repository interface + impl：`jedi_oscal/domain/repository/ssp/system_implementation_repo.py` + `jedi_oscal/infra/repository/ssp/`
- Mapper：`jedi_oscal/infra/mapper/ssp/system_implementation_mapper.py`
- DTO：`jedi_oscal/app/dto/ssp/ssp_system_implementation_dto.py`
- Enum：`jedi_oscal/common/enum/code_enum.py` 內的 `SystemImplementationType`
- YAML mapper：`jedi_oscal/infra/mapper/ssp/ssp_yaml_mapper.py`（OSCAL 序列化已部分接通）

**Caller**（主專案）：
- `app/associations/service/project_device_mapping_service.py`（專案 device mapping）
- `app/oscal/service/ssp_versioning_service.py`（SSP 版本服務）

**資料量**：140 筆 hardware（= 已用作 devices 的 OSCAL 鏡像）。

### 2.2 擴充內容（A0 主體工作）

#### 欄位擴充（ADD COLUMN）

| 新增欄位 | 型別 | 用途 | nullable |
|---------|-----|-----|---------|
| **scope_type** | varchar(20) | `'ssp'` 或 `'module_frame'` — 標示這筆隸屬哪邊 | NOT NULL（既有 140 筆 migration 設 'ssp'）|
| **scope_id** | integer | 對應 scope_type 指向的 id（soft FK，無 DB FK constraint）| NOT NULL |
| **device_id** | integer | soft FK → `public.devices.id`；鉤稽既有 device 時填 | nullable |
| **information_system_id** | integer | soft FK → `compliance.information_systems.id`；鉤稽既有 system 時填 | nullable |
| **title** | varchar(255) | OSCAL component.title（OSCAL 規格的人類可讀標題；與 name 區分） | nullable |
| **purpose** | text | OSCAL component.purpose（用途說明）| nullable |
| **status** | varchar(50) | OSCAL component.status（under-development / operational / disposition / other）| nullable |
| **party_uuid** | varchar(36) | OSCAL leveraged-authorization.party-uuid（授權方）；對齊既有 oscal_responsible_parties.party_uuid 型別 | nullable |
| **date_authorized** | date | OSCAL leveraged-authorization.date-authorized | nullable |

#### 既有欄位調整

| 欄位 | 變更 | 理由 |
|------|-----|-----|
| `system_security_plan_id` | NOT NULL → **nullable**；CASCADE FK 保留 | scope_type='module_frame' 時不指向 SSP，必須 nullable |
| `responsible_party` | 維持，**不動** | 既有資料用，標 deprecated（後續優化由 oscal_responsible_parties 取代）|

#### enum 擴充（v4 校正：只加 1 個值）

`SystemImplementationType` (jedi-oscal `common/enum/code_enum.py`) **只加一個新值**：

- `LEVERAGED_AUTHORIZATION = "leveraged-authorization"`（OSCAL leveraged services 用）

**既有 enum 值的延用**：

| 既有 enum 值 | 新功能用途 |
|------------|----------|
| `hardware` | devices 的鏡像（沿用既有，跟 `project_device_mapping_service` 一致；不另外造 `inventory-item` 重複概念）|
| `component` | information_systems 的鏡像（沿用既有；可選用 `system` / `subsystem` 對應 OSCAL `system-implementation.this-system` / `subsystem`）|
| `software` / `service` / `system` / `subsystem` | 維持不動 |

**為什麼不加 `inventory-item`**：

OSCAL 字面術語上 `hardware` 是 component.type 子分類，`inventory-item` 才是「具體 deployed asset」。但既有 code 已用 `hardware` 涵蓋同一概念、140 筆都是這樣，加 `inventory-item` 會讓兩個 enum 值意義重疊（dev 心智負擔 + caller 寫入分流）。OSCAL 匯出時若需要 `inventory-item` 結構，由 mapper 層把 `hardware` 翻譯成 OSCAL inventory-item JSON 即可，不必動 DB。

**既有 140 筆 hardware 資料**：
- 不轉換 implementation_type（維持 `hardware`）
- 只 migration 補 scope_type='ssp' + scope_id=system_security_plan_id（讓既有資料能被新 scope query 撈到）

#### Index 補充

| 新增 index | 用途 |
|-----------|-----|
| `ix_ssp_sys_impl_scope` (scope_type, scope_id) | 依 scope 查詢 |
| `ix_ssp_sys_impl_device_id` | 依 device 反查 |
| `ix_ssp_sys_impl_info_system_id` | 依 information_system 反查 |

### 2.3 「不做」清單（v4 校正）

| 不做的事 | 理由 |
|---------|-----|
| 新建 `oscal_inventory_items` | 既有表已有多型 implementation_type，加 column 即可 |
| 新建 `oscal_components` | 同上 |
| 新建 `oscal_leveraged_authorizations` | 同上 |
| 廢棄既有 `system_security_plan_system_implementations` | 既有表正在用，廢棄成本遠高於擴充 |
| Migration 搬資料到新表 | 不新建表，沒地方搬 |

### 2.4 表名語意爭議

**問題**：表名是 `system_security_plan_system_implementations`（含 `system_security_plan` 前綴），但新增 scope_type='module_frame' 後也會裝 module_frame 資料，名稱語意稍偏。

**處理方向**（A0 brainstorm 待決）：
- 方案 1：表 rename 為 `oscal_system_implementations`（更通用），既有 ORM / repo 同步改名
- 方案 2：保留表名，加 docstring + table COMMENT 解釋
- 方案 3：保留表名，建 view `oscal.system_implementations` 對齊新名稱供新功能讀

> 此項列入剩餘 brainstorm 題目。

---

## 3. 新需求拆解（依資料領域）

下面是「Excel 匯入」要新增的範圍，依資料領域逐項展開：

### 3.1 基本資料（Metadata）

**內容**（依 docx parser v2 已建立的欄位推估）：
- 系統名稱、簡稱、版本
- 系統概述、邊界、敏感度分類（FIPS 199）
- 授權邊界（Authorization Boundary）
- 網路架構、資料流概述

**匯入挑戰**：
- 純文字長段落，Excel 用一個 cell 還是分多個欄位？建議分多欄但長文字 cell 不限制長度。
- 部分欄位是 enum（如 FIPS 199 機敏等級），匯入時要 validate。

### 3.2 設備 / 單位 / 參與人員 / 系統 — **要鉤稽**

這四類有共同特性：**Excel 內填的是「字串名稱」，匯入時要對到系統內既有的實體**。

#### 鉤稽的兩種狀況

| 狀況 | 系統行為 |
|------|---------|
| Excel 填的名稱**完全相符**系統內既有實體 | 自動鉤上（跟 docx parser 的 party email-match / org-unit name-match 一致） |
| **找不到完全相符**的 | 需要進入「預覽 / 編輯」介面，由 user 手動 (a) 從下拉選現有的 (b) 新建一筆 (c) 暫時當純文字保留 |

#### 各領域細節

| 領域 | Tenant 層比對對象 | 建議比對 key | OSCAL Mirror 寫入目標 | 鉤不到時 |
|------|----------------|------------|---------------------|---------|
| 單位 | `org_units` | organization name 完全相符 | `oscal_parties` (type=organization) | 標 unmatched → user 可選現有 / 預覽頁新建 / 純文字 OSCAL 紀錄（party_uuid 有，soft FK null）|
| 參與人員 | `users` | email 完全相符（同 tenant）| `oscal_parties` (type=person) | 同上 |
| 設備 | `public.devices` | name 或 ip 完全相符 | **`system_security_plan_system_implementations` (implementation_type='inventory-item')**【既有表擴充】 | 同上：(a) 鉤已有 device (b) 預覽頁新建到 `public.devices` (c) 純文字 OSCAL 紀錄（device_id null）|
| 資訊系統 | `compliance.information_systems` | name 或 abbreviation 完全相符 | **`system_security_plan_system_implementations` (implementation_type='component')**【既有表擴充】 | 同上：(a) 鉤已有 (b) 預覽頁新建 (c) 純文字 OSCAL 紀錄 |
| Leveraged Services | （無 tenant 層表） | service name + authorizing party | **`system_security_plan_system_implementations` (implementation_type='leveraged-authorization')**【既有表擴充】 | 直接建一筆，authorizing party 可選既有 oscal_parties 或同步新建 |

**寫入路徑**（confirm 階段 BE 行為）：

```
            user 在預覽頁 confirm
                    │
                    ▼
       ┌────────────────────────┐
       │  鉤稽結果分流           │
       └────────────────────────┘
          │           │            │
   matched      inline 新建    純文字保留
          │           │            │
          ▼           ▼            ▼
   只寫鏡像表   先寫 tenant   只寫鏡像表
  (含 soft FK)  層表 → 再寫   (soft FK
                鏡像表        = null)
```

> 註：「鏡像表」= `system_security_plan_system_implementations`，依 implementation_type 區分 inventory-item / component / leveraged-authorization。

> ✅ **Q3 已 resolved**：docx parser 本期不擴充到 devices / information_systems，**列入「最後統整優化清單」** — 等 Phase 2 完工後再一起處理 docx parser 對齊。同樣的 OSCAL mirror 表新建後，docx parser 之後要對齊寫入這幾張表。
>
> ✅ **Q8 已 resolved**：鉤不到時除了「選現有 / 純文字保留」，**也支援直接在預覽頁面新建** — 例如 user 在預覽頁看到 unmatched device，可直接 inline 輸入 ip/os 等欄位，confirm 時先寫 `public.devices` 再寫 `oscal_inventory_items`（soft FK 串起來）。

### 3.3 控制項 / AO 現況說明

**已有功能**，但要對齊 docx 匯入體驗：

- 既有 Excel 匯入是「同樣 module_frame、同樣 control_id」做精確 join 然後寫入；
- 草稿希望改成「**用編號或名稱模糊比對**」的方式，並讓 user 在預覽介面檢查匹配結果。

**意涵**：這代表新 Excel 不再綁定某個 module_frame，使用者可以拿任意 Excel（甚至從別的 framework 來的）匯進來，由系統幫忙比對。這跟既有「先匯出某 module_frame 的樣板 → 填 → 匯回同一個」的 round-trip 模式有差異，要釐清是否兩種模式並存。

### 3.4 程序書（Reference Documents）

草稿沒明說，但既有 Excel template 有 `參考程序書` 欄位，docx parser v2 也有 SSP document pool 模式。新版要：

- 程序書名稱填在 control / AO 列上；
- 匯入時把程序書建到 pool（如果不存在），並建 control/AO ↔ document 的 mapping；
- 同 docx 模式做 name-match 鉤稽。

---

## 4. Excel 匯入體驗 — 對齊 docx parser v2

草稿明確說「匯入模式可以參考 docx 匯入模式」。把 docx 匯入流程（v2 已上線）抓出來，對應到 Excel：

| 階段 | docx 流程（既有） | Excel 流程（本期目標） |
|------|------------------|----------------------|
| 1. 選 parser | 從 framework 選 docx parser | 從 framework 選 Excel parser（或統一同一個入口）|
| 2. 上傳檔案 | 上傳 docx | 上傳 xlsx |
| 3. BE 解析 | 解析回傳 parse_uid + parsed_result（JSONB，7 天 TTL）| 同 docx 模式 |
| 4. 預覽 | iframe PDF + 右側 Section 編輯 panel | **左右比對**（左：Excel 原貌或欄位對照、右：解析後可編輯 Section） |
| 5. 鉤稽 | parties / org-unit / leveraged auto-match + 標出未匹配 | 同 docx 模式 + 涵蓋設備 / 系統 / 控制項 / AO |
| 6. user 編輯 | localStorage 暫存 + BE TTL | 同 docx 模式 |
| 7. 確認 | POST confirm → 寫入 module_frame | 同 docx 模式 |
| 8. 重新上傳 | 棄置 parse_uid，從頭來 | 同 docx 模式 |

### 4.1 左右比對的可行性

> ✅ **Q6 已 resolved**：本期**不做左右比對**。Excel 不像 docx 容易渲染成 PDF，純單欄式預覽 + 編輯介面即可（方案 C）。

### 4.2 Excel 樣板結構（依 §1.5 補充 2）

按資料領域分多 sheet，欄位顏色 + 下拉選單規格：

| Sheet | 內容 | 必填欄位（黃底）| 下拉選單欄位 |
|-------|------|--------------|------------|
| `00_說明` | 樣板版本、framework、填寫指引、必填顏色圖例 | — | — |
| `01_基本資料` | 系統 metadata（name / abbreviation / desc / sensitivity / boundary / objectives / deployment）| name / sensitivity / objectives | sensitivity (enum) / objectives (enum) / deployment_model (enum) / system_owner (既有 user list) / org_unit (既有 list) |
| `02_單位` | parties type=organization | name | parent_org (既有 org list, optional) |
| `03_參與人員` | parties type=person + 鉤稽 user | email / name | role / org_unit (既有 list) |
| `04_設備` | devices（鉤稽 `public.devices`）| name / ip | os (建議候選 list) / device_type (建議候選 list) / status (enum) |
| `05_資訊系統` | information_systems（鉤稽 `compliance.information_systems`）| name | sensitivity / objectives / deployment_model / system_owner |
| `06_外部利用服務` | leveraged authorizations | service_name / provider | — |
| `07_控制項與AO` | 控制項實作 + AO 現況（保留既有 module_frame_import_template.xlsx 的結構 + 擴充）| statement_id / control_id | impl_status (enum) |
| `08_程序書` | reference document pool | doc_name / doc_no | doc_type (enum, 若有) |

> 💡 已有資料下拉如果量大（如 user 上千人），實作階段用「named range + 隱藏 lookup sheet」處理。

---

## 5. SSP 文件匯出

### 5.1 範圍（含 OSCAL 結構化格式）

✅ **Q5 已 resolved**：**「合規資源庫」與「專案 SSP 版本」兩邊都要提供匯出**。

匯出來源 × 匯出格式組合矩陣：

| 來源 \ 格式 | docx | pdf | odt | OSCAL JSON | OSCAL XML | OSCAL YAML |
|------------|:----:|:---:|:---:|:----------:|:---------:|:----------:|
| 合規資源庫（module_frame）| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 專案 SSP 版本（project + ssp_version）| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |

> 兩個來源的差異：合規資源庫匯出的是「範本層」資料（顧問建好的 default）；專案 SSP 版本匯出的是「實際填寫的現況」（含每個 AP round 的差異）。

### 5.2 多格式支援的真實成本

✅ **Q4 已 resolved**：odt **本期要做**。

| 格式 | 實作成本 | 技術做法 |
|------|---------|---------|
| **docx** | 中 | python-docx 手寫，跟既有 system-design DOCX 規範一致（封面 / 版本紀錄 / 目錄 / Graphviz 圖） |
| **pdf** | 低 | docx 完成後用既有 `/upload-file/<uid>/preview-as-pdf` endpoint 或 LibreOffice headless convert |
| **odt** | 中 | 用 LibreOffice headless `--convert-to odt` 從 docx 轉，或用 odfpy 直接產（評估後再定）|
| **OSCAL JSON** | 中 | jedi-oscal 已有 yaml mapper（`ssp_yaml_mapper.py`）作 base，serialize 改 JSON |
| **OSCAL XML** | 中 | 同 JSON，OSCAL 標準 XML schema 已定 |
| **OSCAL YAML** | 低 | jedi-oscal 既有 yaml mapper 已可用 |

### 5.3 範本（Template）

✅ **Q10 已 resolved**：**先用 CMMC 通用樣板**，後續如果有法規資料不同的需求再抽象化。

- 從 `ASIA-CMMC-SSP-DRAFT-with-user-info-202604.docx` 拆出章節結構、版面樣式、placeholder
- 含封面頁、版本更新紀錄表、Word 自動目錄（依 CLAUDE.md DOCX 規範）
- 章節結構與 OSCAL system-security-plan 對應，方便未來轉 OSCAL JSON / XML / YAML

### 5.4 匯出資料 API vs UI

- **API**：
  - 合規資源庫：`POST /module-frame/<uid>/export?format=docx|pdf|odt|json|xml|yaml`
  - 專案 SSP：`POST /projects/<project_id>/ssp/<version_id>/export?format=...`
- **UI**：
  - 合規資源庫頁面加「匯出 SSP」按鈕 + 格式選擇 dropdown
  - 專案規劃頁面同模式加按鈕

---

## 6. 我建議的階段切分（重新規劃）

> 草稿說「不要全照我的切，要的話再分更細」。下面是我的版本，跟草稿的差異會標出。

### 6.1 切分原則

1. **可獨立 ship**：每階段完成後系統都還能正常用，不依賴後續階段
2. **可平行**：匯入 / 匯出 兩條主線可平行進行（不同人 / 不同 session）
3. **小批次驗收**：每階段 1~2 週可完成，避免長尾
4. **基礎先行**：共用的 parser 架構 / 鉤稽框架先做

### 6.2 階段規劃

#### Phase A0：基礎設施 — 擴充既有 `system_security_plan_system_implementations` 表（**前置必做** ✅ shipped (BE c4804d7 + jedi-oscal afe7035→dbb3d7c, 2026-05-18)）

| Phase | 主題 | 範圍 | 依賴 |
|-------|-----|------|-----|
| **A0** | 既有表 schema 擴充 + ORM / Entity / Repository / Mapper 同步擴充 | 在既有 `system_security_plan_system_implementations` 上加 scope_type / scope_id / device_id / information_system_id / title / purpose / status / party_uuid / date_authorized 欄位；既有 system_security_plan_id 改 nullable；enum 擴充 inventory-item / leveraged-authorization；jedi-oscal 全 stack 同步擴充 | 無 |

**A0 包含項目**：
- SQL migration（ALTER TABLE 加 9 個 column + 改 system_security_plan_id nullable + 加 3 個 index）
- 既有 140 筆 hardware 資料 migration（只補 scope_type='ssp' + scope_id=system_security_plan_id；**不轉 implementation_type，維持 hardware**）
- jedi-oscal 套件擴充（dev 期走 poetry path dependency，不立即發版）：
  - ORM model：`ssp_system_implementation.py`
  - Entity / Query Entity：`jedi_oscal/domain/entity/ssp/`
  - Repository interface + impl
  - Mapper：`system_implementation_mapper.py` + `ssp_yaml_mapper.py`（OSCAL 序列化）
  - DTO：`ssp_system_implementation_dto.py`
  - Enum：`SystemImplementationType` 加新值
- 既有 caller 驗證：`project_device_mapping_service` + `ssp_versioning_service` 確保不破壞
- 補單元測試（新欄位 CRUD + scope 篩選 + soft FK 整合）

> A0 是 Track A 與 Track B OSCAL 線（B5）共同前置 — 任何 Excel 匯入 / OSCAL 匯出邏輯都要等這層基礎建好才能接。

#### Track A：Excel 匯入（5 階段）

| Phase | 主題 | 範圍 | 依賴 |
|-------|-----|------|-----|
| **A1** | Excel 樣板設計 + 匯出下載 | 設計 multi-sheet xlsx（§4.2 9 個 sheet），含必填欄位顏色標示、enum 與既有資料下拉選單；提供「下載空白樣板」+「從現有 module_frame 下載已填樣板」兩種模式 | A0（樣板下拉要選 OSCAL mirror 既有資料）|
| **A2** | Excel Parser + BE 解析 API | 寫 ExcelParser（對應 docx 的 DocxParser），輸出 ParsedResult JSONB；提供 upload + parse endpoint，回傳 parse_uid（沿用 docx parser 的 7 天 TTL 機制）| A1 |
| **A3** | 鉤稽邏輯 — parties / org-units（抽共用層）| 把 docx parser 既有的 party / org-unit match 邏輯抽出共用 service，Excel / docx 兩條路徑都用同一份 matcher；寫入路徑寫到 `oscal_parties` | A2 |
| **A4** | 鉤稽邏輯 — devices / information_systems / leveraged + 控制項/AO 模糊比對 | 新增 devices / information_systems / leveraged / control / AO 的 match 邏輯；寫入路徑寫到 A0 建好的三張 OSCAL mirror 表 | A0, A3 |
| **A5** | 預覽 UI + Confirm 寫入（含 inline 新建）| 前端單欄預覽 + 編輯介面、localStorage 暫存、unmatched 項目支援「選現有 / 純文字 OSCAL 紀錄 / inline 新建 tenant 資料」三種處理；confirm 時依路徑寫入 module_frame + OSCAL mirror 表 | A2~A4 |

> **跟草稿的差異**：草稿把「Excel 匯入功能」當一個階段，我拆成 4 階段（A2~A5）+ A0 基礎建設，因為鉤稽邏輯是高風險區域、需要分批驗證；A3 把 docx 既有 matcher 抽出共用，避免兩套 parser 行為不一致。

#### Track B：SSP 匯出（6 階段，新增 OSCAL 線）

| Phase | 主題 | 範圍 | 依賴 |
|-------|-----|------|-----|
| **B1** | Docx 樣板拆解 + python-docx generator 骨架 | 從 `ASIA-CMMC-SSP-DRAFT-with-user-info-202604.docx` 拆出章節結構，建立 generator 骨架（章節 helper、樣式、封面、版本紀錄表、目錄）| 無 |
| **B2** | 內容組裝服務 — 兩種來源 → 共用 SSP data model | App service 把 module_frame **或** 專案 SSP 版本資料組成 generator 用的 SSP data model（metadata / parties / inventory_items / components / leveraged / controls / AOs / references），共用同一個 generator | A0, B1 |
| **B3** | Docx Export API + 下載 | `POST /module-frame/<uid>/export?format=docx` 與 `POST /projects/<pid>/ssp/<ver>/export?format=docx` 兩支 endpoint；回傳 docx file stream | B2 |
| **B4** | PDF + ODT 補上 | docx 完成後接 LibreOffice headless convert（或 odfpy）補 pdf / odt 兩種格式，同一個 export endpoint 用 `format` 參數切換 | B3 |
| **B5** | OSCAL 結構化匯出（JSON / XML / YAML）| 基於 jedi-oscal 既有 yaml mapper 擴充，序列化 OSCAL mirror 表（含 A0 三張新表）→ JSON / XML / YAML；同一個 export endpoint `format=json\|xml\|yaml` | A0, B2（與 B3/B4 平行可進行）|
| **B6** | 前端 UI + 格式選擇 | 合規資源庫頁 + 專案規劃頁加「匯出 SSP」按鈕 + 格式下拉（docx / pdf / odt / oscal-json / oscal-xml / oscal-yaml）| B3, B4, B5 |

> **跟草稿的差異**：草稿是 3 階段（樣板 / API / UI），我拆成 6 階段：
> 1. 樣板拆成 B1（generator 骨架）+ B2（data model 組裝），技術重點不同
> 2. 多了 B5 OSCAL 結構化匯出線（raymond 第一輪回覆補充）
> 3. ODT 不能等 docx 順手就拿到，獨立做 B4
> 4. UI 集中在最後 B6（含格式 dropdown）

### 6.3 階段相依關係

```
              ┌── A1 ── A2 ── A3 ── A4 ── A5  (Excel 匯入完成)
              │
A0 ───────────┤
(OSCAL mirror │
 表基礎)      │
              │   B1 ── B2 ──┬── B3 ── B4 ──┐
              └──────────────┤              ├── B6 (SSP 匯出完成)
                             └── B5 ────────┘
```

**關鍵 dependency**：
- **A0 是 Track A 與 Track B（B2 起算）共同前置** — 一定要先做
- A0 完成後，A1 / B1 可平行起手
- A 跟 B 主線可平行；B 內部 B3 / B4 / B5 可平行（都依賴 B2 data model）

### 6.4 各階段的「最小可驗收」

- **A0 ship**：既有表擴充 migration 跑通、jedi-oscal 擴充欄位 ORM + repository CRUD 測試通過、既有 caller (`project_device_mapping_service` + `ssp_versioning_service`) regression 測試通過、140 筆既有資料 scope_type='ssp' migration 完成
- **A1 ship**：可下載空白樣板 + 已填樣板（兩個檔案，含必填顏色 + 下拉選單）
- **A2 ship**：API 可解析 Excel 回傳 JSONB，postman 可測
- **A3 ship**：解析結果含 matched / unmatched parties / org-units 標記，docx 路徑同時切換到共用 matcher 行為不變
- **A4 ship**：解析結果擴充含 devices / information_systems / leveraged / controls / AOs match 標記，confirm 寫入 OSCAL mirror 表
- **A5 ship**：完整 UI flow，含 inline 新建 device / information_system / party 能力（同步寫 tenant 表 + OSCAL mirror 表）
- **B1 ship**：可產出空殼 docx（封面 / 目錄 / 章節骨架，無實際資料）
- **B2 ship**：可從 module_frame **與** 專案 SSP 版本兩種來源產出含實際資料的 docx
- **B3 ship**：兩支 export endpoint 上線（docx）
- **B4 ship**：pdf + odt 兩格式接上
- **B5 ship**：OSCAL JSON / XML / YAML 三格式接上（讀 A0 三張新表序列化）
- **B6 ship**：UI 按鈕 + 格式下拉，6 種格式都可下載

---

## 7. 待釐清項目決策結果（Q1~Q11 已全數 resolved，2026-05-18）

### 7.1 範圍類

| # | 問題 | 決策 |
|---|------|-----|
| Q1 | 「設備」對應 OSCAL 哪一項？ | ✅ **`public.devices`**（jedi-device 套件）→ OSCAL `inventory-items`（資產概念），匯入時與此表鉤稽 |
| Q2 | 「系統」對應實體？ | ✅ **`compliance.information_systems`**（既有資訊系統表），匯入時與此表鉤稽 |
| Q3 | docx parser 是否同步擴充 devices / information_systems？ | ✅ **本期不擴充**，列入「最後統整優化清單」，Phase 2 完工後再處理 |
| Q4 | odt 格式本期是否要做？ | ✅ **要做** — docx / pdf / odt 三種可讀格式 + OSCAL JSON / XML / YAML 三種結構化格式 |
| Q5 | SSP 匯出來源？ | ✅ **「合規資源庫」與「專案 SSP 版本」兩邊都要** — 矩陣詳見 §5.1 |

### 7.2 體驗類

| # | 問題 | 決策 |
|---|------|-----|
| Q6 | Excel 預覽是否左右比對？ | ✅ **不做左右比對** — 單欄式預覽 + 編輯介面即可 |
| Q7 | Excel 匯入流程？ | ✅ **對齊 docx parser** — create 模式（新建 MF）+ update 模式（補資料到既有 MF）雙模式，編輯 / TTL / 確認流程一致 |
| Q8 | 控制項 / AO 等 unmatched 行為？ | ✅ **對齊 docx + 加 inline 新建** — 標 unmatched 後 user 可選 (a) 選現有 (b) 純文字保留 (c) **預覽頁面直接 inline 輸入新建** 三種 |

### 7.3 範本類

| # | 問題 | 決策 |
|---|------|-----|
| Q9 | Excel 樣板要 framework 一份還是通用？ | ✅ **通用結構** — 一份 multi-sheet 樣板，控制項 sheet 動態依據 module_frame 的 framework 預填 |
| Q10 | SSP 匯出 docx 樣板？ | ✅ **通用，先用 CMMC 當樣板** — 後續若法規資料差異大再抽象化 |
| Q11 | 既有 Excel 匯入（只匯現況）是否保留？ | ✅ **保留**，移到批次維護「現況說明」功能下；新版定位是「匯入完整資料」，跟舊版定位不同 |

### 7.4 衍生新議題（v2+v3+v4 補充）

| # | 議題 | 處理方向 |
|---|------|--------|
| N1 | OSCAL JSON / XML / YAML 序列化的完整度 | jedi-oscal 既有 yaml mapper 是否覆蓋 SSP 全部欄位？實作前要先盤點缺哪些 mapper |
| N2 | Excel 下拉選單在資料量大時的效能 | named range + 隱藏 lookup sheet 處理；上千筆以下可直接列舉 |
| N3 | inline 新建 device / information_system 的權限檢查 | 需確認匯入者是否有建立這些實體的權限（manager / auditor 角色檢查）|
| N4 | 專案 SSP 版本匯出時，要不要跨多 AP round 合併 | 預設匯出當前最新版本；若要跨 round 比較，列為 follow-up |
| **N5** | **既有表擴充欄位細節**（v4 校正）| 9 個新欄位的 nullable / index / OSCAL 屬性對應 — 進 SDD 階段定（部分已在 §2.2 草擬）|
| ~~N6~~ | ~~既有通用表後續~~ | **v4 已 obsolete**：v3 主張廢棄既有表是誤判，v4 改成擴充既有表 |
| **N7** | **A0 在 OSCAL 寫入 race**（v3）| confirm 時：先寫 tenant 表（device）→ 寫鏡像表（含 soft FK）— 要在同一個 `@transaction` scope 內，避免半寫狀態 |
| ~~N8~~ | ~~既有 140 筆 hardware 資料 migration 策略~~ | ✅ **已 resolved**：只補 scope_type='ssp' + scope_id，不轉 implementation_type；沿用 `hardware` 不引入 `inventory-item` 重複概念 |
| **N9** | **表名語意爭議**（v4）| 表名 `system_security_plan_system_implementations` 含 ssp 前綴但要服 module_frame — rename / 不動 / view alias 三方案 brainstorm 待決 |
| **N10** | **既有 `responsible_party` 欄位後續**（v4）| 既有純字串欄位有資料、不鉤 oscal_parties.uid；新功能改用 oscal_responsible_parties（polymorphic）；既有 column 暫保留標 deprecated |

---

## 8. 風險評估

| 風險 | 影響 | 緩解 |
|------|-----|-----|
| 鉤稽邏輯複雜度高（5 種實體、多 key match + 寫入兩層 tenant + OSCAL mirror）| 開發時間翻倍、UX 設計痛苦 | 先把 docx parser 既有 party / org-unit match 抽成共用服務（A3），新領域複用；A0 把 mirror 表基礎打穩再做寫入 |
| **A0 既有表擴充欄位定錯**（v4 校正）| 後續 A4 / A5 / B5 全部要回改 | A0 SDD 階段先把 OSCAL 規格欄位列清楚再下手；既有 jedi-oscal stack 都要同步擴充 |
| **A0 破壞既有 caller**（v4 新增）| `project_device_mapping_service` / `ssp_versioning_service` regression | A0 完成後跑既有 caller 的 integration 測試；ALTER TABLE 用 ADD COLUMN 不改既有欄位語意；nullable 化既有 system_security_plan_id 要驗證所有 caller 都接受 null |
| **140 筆既有資料 migration 出錯**（v4 新增）| 既有 hardware 紀錄遺失或錯標 | Migration 加 transaction + 先 SELECT count 對帳；migration 完成後 SELECT COUNT(*) WHERE scope_type='ssp' 驗證仍 140 筆 |
| Excel 結構變動成本高（樣板改一次，parser + UI 都要動） | 後續維護負擔 | A1 樣板定版前先跟使用者（顧問）確認 sheet 結構，避免反覆改 |
| docx 樣板渲染品質（中英文混排、表格樣式） | 客戶交付品質直接影響業務 | B1 階段先做 PoC，跟既有 system-design DOCX 模式對齊 |
| docx parser 與 Excel parser 雙系統不一致（同樣資料兩種匯入路徑可能行為不同） | 維運痛苦、bug 散落 | 鉤稽 / 寫入邏輯抽 common 層，parser 只負責格式解析；A3 抽 matcher 共用 |
| **OSCAL UUID 引用一致性**（v3 新增）| implemented-requirements 引用 inventory-item UUID 對不上 | 既有表 uid 已有 unique constraint；寫入時用 transaction scope 確保鏡像 UUID 與 control_impl 引用同步 |
| 既有 Excel 匯入（舊版）使用者習慣 | 切換成本 | A 系列完成前舊版繼續可用，A5 上線時提供 migration 提示 |
| ~~舊 system_implementations 通用表與新 mirror 表雙軌期~~ | **v4 已 obsolete** — 不建新表，沒雙軌問題 | — |

---

## 9. 不在本期範圍（明確排除，避免 scope creep）

- ❌ docx parser 擴充到 devices / information_systems（Q3 決議 → 列入「最後統整優化清單」，Phase 2 完工後處理）
- ❌ Excel / docx 雙向同步（雙向同步是另一個議題）
- ❌ 跨 module_frame 的 bulk import / export（一次只處理一個 MF）
- ❌ 既有舊版 Excel 匯入功能的下線（Q11 決議：保留並移到批次維護「現況說明」下）
- ❌ 跨 AP round 合併匯出（N4：預設只匯當前最新版本，跨 round 比較列 follow-up）

### 9.1「最後統整優化清單」（Phase 2 完工後處理）

| 項目 | 來源 |
|------|-----|
| docx parser 擴充到 devices 鉤稽（寫入鏡像表 implementation_type='hardware'）| Q3 |
| docx parser 擴充到 information_systems 鉤稽（寫入鏡像表 implementation_type='component'）| Q3 |
| Excel / docx parser 共用 matcher 反向同步（如果 A3 抽得不夠乾淨）| A3 衍生 |
| 跨 AP round 的 SSP 匯出差異比對 | N4 |
| Framework-specific docx 樣板抽象化（如果出現 ISO 27001 / NIST 800-53 客戶）| Q10 |
| 既有 `responsible_party` (varchar) 欄位資料遷移到 `oscal_responsible_parties` | N10 |

---

## 10. 下一步

raymond 確認這份理解版本後，按 `docs/claude/feature-development-workflow.md` 進入：

1. **Phase 1 — Brainstorm**：針對 Q1~Q11 待釐清項目逐一收斂
2. **Phase 2 — 歸檔到 features/**：把這份搬到 `docs/features/FR-011.2-2605-ssp-import-export-phase2/raw-requirement.md`
3. **Phase 3 — design.md**：依切分後的 Track A / B 分別寫 SDD（或合一份大的）
4. **Phase 4 — implementation-plan.md + test-plan.md**：交給 feature-test-planner agent
5. **Phase 5 — 實作**：依階段 ship

---

## 附錄：與既有 feature 的關聯

| 既有 feature | 路徑 | 關聯 |
|-------------|------|-----|
| SSP Doc Parser v2 | `docs/features/FR-022-2604-ssp-doc-parser/` | 鉤稽邏輯、UI flow、parse_job model 全部可複用 |
| SSP Update Diff | `docs/features/FR-025-2605-ssp-update-diff/` | 匯入後若要 diff 與 module_frame 既有資料差異，可對齊 update diff 的 side-by-side UI |
| Module Frame Template Defaults | `docs/features/FR-018-2604-module-frame-template-defaults/` | 控制項 / AO defaults 的寫入 model 已存在，匯入直接寫這層 |
| SSP Document Pool | `docs/features/FR-010-2603-ssp-document-pool/` | 參考程序書 pool + mapping 機制已存在，Excel 匯入要對齊 |
| Compliance Framework PDF Import v2 | `docs/features/FR-024-2605-compliance-framework-pdf-import-v2/` | 框架匯入跟 SSP 匯入是不同層次（前者建框架 / 後者填內容），但 UI 模式可參考 |
