# Track B 前置分析報告
> 日期：2026-05-21
> 狀態：分析完成，待 raymond 確認決策點後開工
> 範圍：B1 ~ B6 六個 phase 的現況盤點、設計選項、疑問清單

---

## 一、現有基礎設施盤點

### 已可直接利用

| 資源 | 路徑 | B 階段用途 |
|------|------|-----------|
| `SystemSecurityPlanYamlMapper` | `jedi-oscal/.../ssp_yaml_mapper.py` | B5 YAML/JSON 序列化基礎 |
| `ModuleFramePartyService.list_parties()` | `app/module_frame/service/` | B2 MF 來源 parties |
| `ModuleFrameSspResourcesService.list_resources()` | `app/module_frame/service/` | B2 MF 來源 devices/info_systems |
| `ModuleFrameControlDefaultService` | `app/module_frame/service/` | B2 MF 來源控制項說明 |
| `ModuleFrameControlObjectiveDefaultService` | `app/module_frame/service/` | B2 MF 來源 AO 說明 |
| `ModuleFrameReferenceDocumentService` | `app/module_frame/service/` | B2 MF 來源程序書 |
| `SspVersioningService` | `app/oscal/service/` | B2 SSP 版本來源 |
| `SspControlImplImportService.export_excel()` | `app/oscal/service/` | 唯一現有 export 模式參考 |
| `ControlImplementationObjectiveMapper` | `jedi-oscal/.../ssp/` | B5 AO 序列化參考 |
| CMMC SSP reference docx | `docs/features/.../reference/ASIA-CMMC-SSP-DRAFT-with-user-info-202604.docx` | B1 章節結構來源 |

### yaml_mapper 現有覆蓋範圍

| OSCAL block | 現況 | B5 需補 |
|-------------|------|--------|
| `metadata.title/version/published/last-modified/oscal-version` | ✅ 已支援 | — |
| `metadata.parties[]` | ❌ **未序列化** | 需補 |
| `metadata.responsible-parties[]` | ❌ **未序列化** | 需補 |
| `metadata.roles[]` | ❌ 未序列化 | 視需求補 |
| `system-characteristics.system-name/description/status` | ✅ 基礎支援 | — |
| `system-characteristics.security-sensitivity-level` | ✅ | — |
| `system-characteristics.authorization-boundary` | ❌ **未序列化** | 需補（SC 有 scope_description） |
| `system-characteristics.security-impact-level` | ❌ **未序列化** | 有 CIA 三欄需補 |
| `system-implementation.components[]` | ✅ | — |
| `system-implementation.inventory-items[]` | ✅ | — |
| `system-implementation.leveraged-authorizations[]` | ✅ | — |
| `system-implementation.users[]` | ❌ 不做（requirement-understanding §2 決議） | — |
| `control-implementation.implemented-requirements[].description/status` | ✅ | — |
| `control-implementation.implemented-requirements[].statements[]` | ❌ **未序列化**（ci.objectives 沒走） | 需補（AO = OSCAL statements） |
| `back-matter.resources[]` | ❌ **未序列化**（程序書） | 需補 |

**結論**：`ssp_yaml_mapper.py` 約完成了 55% 的 OSCAL SSP 序列化。B5 需在 jedi-oscal 裡補 4 個區塊：parties、statements（AO）、back-matter、部分 system-characteristics 欄位。

---

## 二、B1 — Docx 樣板 + Generator 骨架

### 章節結構建議（從 CMMC SSP draft 推導）

```
封面 (Cover)
版本更新紀錄表 (Version History)
目錄 (TOC — Word auto field)
├── 1. 系統概述 (System Overview)
│   ├── 1.1 系統基本資料 (system-characteristics: name / abbreviation / description)
│   ├── 1.2 系統邊界 (authorization-boundary)
│   ├── 1.3 網路架構 (network-architecture)
│   └── 1.4 資料流 (data-flow)
├── 2. FIPS 199 安全分類 (Security Classification)
│   └── 機密性 / 完整性 / 可用性 + overall sensitivity
├── 3. 系統所有人與聯絡人 (Parties / Roles)
│   ├── 3.1 組織 (parties type=organization)
│   └── 3.2 人員 (parties type=person)
├── 4. 授權邊界系統元件 (System Implementation)
│   ├── 4.1 設備清單 (inventory-items / hardware)
│   ├── 4.2 資訊系統 / 元件 (components)
│   └── 4.3 外部利用服務 (leveraged-authorizations)
├── 5. 控制項實作說明 (Control Implementation)
│   └── 5.x <control_id>: description + status
│       └── 5.x.y <statement_id> (AO): description + status
└── 6. 附錄：參考程序書 (Back Matter / Reference Documents)
```

### 技術選型

**建議**：延用 CLAUDE.md 規範的 python-docx 手寫模式（不用 pandoc，跟既有 system-design DOCX 規範一致）。

generator 分兩層：
1. **`SspDocxGenerator`**（jedi-oscal 或主專案）— 純版面/結構，只知道 python-docx API
2. **`SspContentAssembler`**（主專案）— 組裝資料，產出 `SspExportDataModel`，傳給 generator

### 待決問題 B1-Q1

> **要不要讀真實的 .docx 樣板檔（template mode）vs 純 python-docx 程式碼生成？**

| 選項 | 優點 | 缺點 |
|------|------|------|
| A：template.docx 作為樣式基礎 | 字型/色彩/間距可用 Word 調 | 部署需帶樣板檔；樣板改版要重建 |
| B：純 python-docx 程式碼 | 無外部依賴；自動化友好 | 版面調整要改 code |

**建議選 B**，與既有 system-design DOCX 模式一致，部署簡單。

---

## 三、B2 — 中間層 data model 設計（最重要的架構決策）

### 問題核心

兩個來源的資料存放位置不同：

| 資料領域 | MF 來源 | SSP 版本來源 |
|---------|---------|------------|
| Metadata | `module_frame` 本身的欄位 | `ssp.oscal_metadata` → `MetadataEntity` |
| Parties | `oscal_parties` (context_type='module_frame') | `ssp.oscal_metadata.parties[]` |
| 控制項說明 | `module_frame_control_defaults` | `ssp.control_implementations[]` |
| AO 說明 | `module_frame_control_objective_defaults` | `ssp.control_implementations[].objectives[]` |
| Devices | `ssp_system_implementation_items` (scope_type='module_frame' 或 via MF 的 SSP) | `ssp.system_implementations[]` (hardware) |
| 程序書 | `module_frame_reference_documents` | `ssp_reference_documents` (context_type='ssp') |

### 選項分析

#### 選項 A：統一 `SspExportDataModel` 中間層（推薦）

```python
@dataclass
class SspExportDataModel:
    # 來源識別
    source_type: Literal["module_frame", "ssp_version"]
    source_uid: str  # mf.uid 或 ssp_group_uid

    # 組裝後的欄位（格式一致）
    title: str
    version: str
    system_name: str
    system_name_short: Optional[str]
    description: str
    sensitivity_level: str
    authorization_boundary: Optional[str]
    # ... metadata 欄位

    parties: List[SspPartyExportItem]
    inventory_items: List[SspInventoryItemExportItem]
    components: List[SspComponentExportItem]
    leveraged_authorizations: List[SspLeveragedExportItem]
    control_implementations: List[SspControlImplExportItem]
    reference_documents: List[SspRefDocExportItem]
```

- 優點：Docx generator 和 OSCAL serializer 只依賴 `SspExportDataModel`，不知道來源差異
- 缺點：需要寫兩個 loader（`MfSspContentLoader` + `SspVersionContentLoader`）
- **這是 B2 的主要工作**

#### 選項 B：直接組裝 `SystemSecurityPlanEntity`

從 MF 資料「偽造」一個 `SystemSecurityPlanEntity`，讓現有 yaml_mapper 直接用。

- 優點：直接複用 yaml_mapper
- 缺點：`SystemSecurityPlanEntity` 是 DB ORM 對應的 entity，「偽造」會讓型別語義混亂；且 yaml_mapper 本身還有缺口需補

#### 選項 C：在 yaml_mapper 加 MF 來源支援（jedi-oscal 層改）

在 `SystemSecurityPlanYamlMapper` 加一個 `module_frame_to_dict(mf_data)` 方法。

- 缺點：把業務邏輯（MF vs SSP）推進了套件層，不合適

**結論：選 A，主專案定義 `SspExportDataModel`，兩個 loader 分別組裝。**

### 待決問題 B2-Q1

> **`SspExportDataModel` 要放在哪一層？**
>
> - 放 `domain/oscal/export/` — 純 domain dataclass，DDD 合規
> - 放 `app/oscal/service/export/` — 跟 loader 放在一起
>
> 建議放 `domain/oscal/export/ssp_export_model.py`，因為 Docx generator 和 OSCAL serializer 都可能在 jedi-oscal 層使用它。

### 待決問題 B2-Q2

> **MF 來源的 devices/info_systems 要從哪裡撈？**
>
> `ModuleFrameSspResourcesService` 是透過 MF 的 linked SSP 撈 `ssp_system_implementation_items`。
> 但如果一個 MF 沒做過 Excel 匯入（沒有 SSP shell），這裡會回傳空清單。
>
> 匯出時，MF 沒有 Excel 匯入過的設備應該怎麼處理？
> - 選項 A：只匯有資料的，空清單正常（最小成本）
> - 選項 B：提示 user「設備/系統資料尚未匯入」

---

## 四、B3 — Docx Export API

### Endpoint 設計

```
POST /api/1.0/module-frame/<uid>/export?format=docx
POST /api/1.0/projects/<project_uid>/ssp/<version_id>/export?format=docx|pdf|odt|json|xml|yaml
```

> 注意：`format` 放 query string（RESTful，不改 URL 路徑）。

### 回傳格式

Flask `send_file` + `BytesIO`，Content-Disposition 帶檔名：

```python
# 範例
filename = f"SSP_{system_name}_{date.today().isoformat()}.docx"
return send_file(
    BytesIO(docx_bytes),
    mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    as_attachment=True,
    download_name=filename,
)
```

### 待決問題 B3-Q1

> **兩個 endpoint 要放在哪個 Blueprint？**
>
> - 選項 A：`api/oscal/routes/ssp_export_route.py`（新 route，擴 oscal blueprint）
> - 選項 B：`api/module_frame/routes/mf_export_route.py`（MF 端） + `api/oscal/routes/ssp_export_route.py`（SSP 版本端）
>
> 建議選 B，因為兩個 endpoint 的 URL 前綴不同（/module-frame vs /projects）。

---

## 五、B4 — PDF + ODT 補上

### 技術選型問題

| 方案 | 優點 | 缺點 | 現有依賴 |
|------|------|------|---------|
| **A：LibreOffice headless** | 高品質轉換；支援 docx→pdf/odt；開源免費 | 需要在 server 安裝 LibreOffice；spawn 子行程 | 無 |
| **B：既有 preview-as-pdf endpoint** | 不需新依賴；直接複用 | 需先上傳再轉換 → 兩次 HTTP；語義不對（export ≠ preview） | `upload-file/<uid>/preview-as-pdf` |
| **C：python-docx2pdf** | Python 原生 | 在 Linux server 需要 Word 或 LibreOffice | 無 |
| **D：Gotenberg / WeasyPrint** | 容器化，方便 | 額外服務依賴 | 無 |

**建議選 A（LibreOffice headless）**，指令：
```bash
libreoffice --headless --convert-to pdf --outdir /tmp /tmp/ssp.docx
libreoffice --headless --convert-to odt --outdir /tmp /tmp/ssp.docx
```

### 待決問題 B4-Q1

> **部署環境是否已安裝 LibreOffice？**
>
> 這決定了 B4 是 1 天工作還是要先處理環境。
> 請 confirm：`which libreoffice` 在 prod/staging server 上的結果。

---

## 六、B5 — OSCAL JSON / XML / YAML 匯出（重型）

### 現有缺口清單（需補到 jedi-oscal）

#### 缺口 1：AO statements 序列化

```python
# 現況（ssp_yaml_mapper._control_implementation_to_dict）
r: Dict = {"control-id": str(control_id)}
# 只有 description + status + remarks
# ❌ 缺 ci.objectives → OSCAL statements[]

# 需補
if ci.objectives:
    r["statements"] = [
        {
            "statement-id": obj.statement_identifier,
            "uuid": _stringify_uuid(obj.uid),
            "description": obj.implementation_description or "",
            "remarks": obj.remarks,
        }
        for obj in ci.objectives
    ]
```

#### 缺口 2：parties + responsible-parties 序列化

`_metadata_to_dict` 目前只輸出 title/version 等標量欄位。需補：

```python
# MetadataEntity 已有 .parties[] (List[PartyEntity])
# 和 .responsible_parties[] (List[ResponsiblePartyEntity])

if metadata.parties:
    out["parties"] = [_party_to_dict(p) for p in metadata.parties]
if metadata.responsible_parties:
    out["responsible-parties"] = [_rp_to_dict(rp) for rp in metadata.responsible_parties]
```

#### 缺口 3：back-matter 序列化（程序書）

OSCAL back-matter.resources[]：

```yaml
back-matter:
  resources:
    - uuid: "..."
      title: "程序書名稱"
      rlinks:
        - href: "..."
```

`SystemSecurityPlanEntity` 目前沒有 `reference_documents` 屬性。
**需要在 ssp_entity 加 relationship** 或由 B2 的 assembler 填充。

#### 缺口 4：system-characteristics 現有 DB 欄位限制

已確認 `OscalSystemSecurityPlanSystemCharacteristic` ORM 欄位實際清單：

| 欄位 | 現有 | mapper 有無輸出 |
|------|------|---------------|
| `name` | ✅ | ✅ (`system-name`) |
| `description` | ✅ | ✅ |
| `system_identifier` | ✅ | ✅ (`system-id`) |
| `security_sensitivity_level` | ✅ | ✅ |
| `target_type` | ✅ | ✅ |
| `scope_description` | ✅ | ✅（輸出為 `scope.description`）|
| `status` | ✅ | ✅ (`status.state`) |
| `network_architecture` | **❌ 不存在** | — |
| `data_flow` | **❌ 不存在** | — |
| `authorization_boundary` | **❌ 不存在**（只有 scope_description）| — |
| CIA 三欄 (`confidentiality`/`integrity`/`availability`) | **❌ 不存在** | — |

**結論**：system-characteristics 的 B5 缺口比預期小 — DB 根本沒有這些欄位，無法序列化。
mapper 目前的 7 個欄位已涵蓋現有 DB 資料的全部。**缺口 4 實際上不需要補**。

若未來需要 CIA 分類和 network-architecture，那是另一個 feature（schema 擴充 + UI）。

### OSCAL JSON / XML 的做法

| 格式 | 做法 | 成本 |
|------|------|------|
| **YAML** | 現有 `yaml.safe_dump(entity_to_dict(...))` | ✅ 已有 |
| **JSON** | `import json; json.dumps(entity_to_dict(...), ensure_ascii=False, indent=2)` | 極低（1行） |
| **XML** | 見下方選項 | 中 |

#### XML 選項

| 方案 | 做法 | OSCAL 合規性 |
|------|------|------------|
| **A：xmltodict** | `xmltodict.unparse({"system-security-plan": {...}})` | 低（namespace 缺失） |
| **B：lxml etree 手寫** | 逐層 `SubElement`，可加 namespace | 高，但繁瑣 |
| **C：xmltodict + 手補 namespace** | 先轉再後處理加 xmlns | 中等 |

OSCAL XML 根元素需要：
```xml
<system-security-plan xmlns="http://csrc.nist.gov/ns/oscal/1.0"
                       uuid="...">
```

**建議選 C（xmltodict + namespace patch）**，成本低，足夠滿足結構正確性要求。若後續需要完整 schema validation 再改 B。

### 待決問題 B5-Q1

> **OSCAL 標準合規程度：要完整符合 OSCAL 1.1.x spec（含所有 required 欄位），還是「最大努力」輸出？**
>
> OSCAL 1.1.x 對 `system-security-plan` 的 required 欄位：
> - `uuid` ✅
> - `metadata.title` ✅
> - `metadata.last-modified` ✅
> - `metadata.version` ✅
> - `metadata.oscal-version` ✅
> - `system-characteristics.system-id[]` （我們有 `system_identifier`）
> - `system-characteristics.system-name` ✅
> - `system-characteristics.description` ✅
> - `system-characteristics.security-sensitivity-level` ✅
> - `system-characteristics.status` ✅
> - `system-implementation.components[]`（at least 1 required） — 有資料時 ✅，空 MF 時 ❌
> - `control-implementation.implemented-requirements[].uuid` ✅
> - `control-implementation.implemented-requirements[].control-id` ✅
>
> **建議**：「最大努力」— 輸出我們有的資料，缺少的 required 欄位補空字串或省略；加一個 `_validate_oscal_completeness()` 方法輸出 warning log 但不擋住匯出。

### 待決問題 B5-Q2

> **AO statements 資料路徑確認**
>
> `ControlImplementationEntity.objectives` 這個 list 是否在現有 SSP query 時有被 eager load？
> 現有 yaml_mapper 呼叫端是哪裡？需要確認 caller 在 load SSP entity 時有無帶 `objectives`。

### 待決問題 B5-Q3

> **MF 來源的 control-implementation 資料路徑**
>
> MF 的控制項說明在 `module_frame_control_defaults`（不是 `system_security_plan_control_implementations`）。
> OSCAL yaml_mapper 接 `ControlImplementationEntity` — 但 MF source 的資料型態是
> `ModuleFrameControlDefaultEntity`。
>
> B2 的 assembler 必須把 `ModuleFrameControlDefaultEntity` → `ControlImplementationEntity`（或
> 直接 map 到 `SspControlImplExportItem` dataclass）才能餵給 yaml_mapper。
>
> **這是 B2 最重要的轉換工作，需要確認兩個 entity 欄位對應關係。**

---

## 七、B6 — 前端 UI（輕型）

### 入口位置（需確認 FE 現有頁面）

| 頁面 | 入口 | 元件類型 |
|------|------|---------|
| 合規資源庫列表/詳細頁 | 右上 action button 或詳細頁 header | SplitButton（同 A1 的下載樣板）|
| 專案規劃頁（AP / SSP 視圖） | 頁面 header action 區 | Button + Dialog（選版本 + 格式）|

### 格式選項

```
[ docx ]  [ pdf ]  [ odt ]  [ OSCAL-JSON ]  [ OSCAL-XML ]  [ OSCAL-YAML ]
```

建議用 dialog + radio group，UX 一致，不用 6 個按鈕。

---

## 八、整體決策清單（開工前需要 raymond 拍板）

### 必須決策（阻 B1/B2 開工）

| # | 問題 | 決策 | 確認日期 |
|---|------|------|---------|
| **D1** | B4 轉換工具：LibreOffice 是否在 server 上可用？ | ✅ **已安裝**，現有 PDF 轉換已在用 | 2026-05-22 |
| **D2** | B2 中間層：用 `SspExportDataModel` dataclass 還是直接 reuse `SystemSecurityPlanEntity`？ | ✅ **SspExportDataModel** dataclass（選項 A）| 2026-05-22 |
| **D3** | B1 Generator：純 python-docx code vs 讀 .docx 樣板？ | ✅ **docxtpl**（Jinja2-in-Word .docx 模板），非純 code | 2026-05-22 |

> **D3 補充**：匯出 docx 不設計成 round-trip 匯入格式，優先做可讀交付物。round-trip 是未來獨立 feature。

### 可先開工、途中決策（不阻 B1）

| # | 問題 | 選項 | 傾向 |
|---|------|------|------|
| **D4** | B5 OSCAL 合規程度：「最大努力」vs 完整 required 欄位？ | 最大努力 / 完整 | 傾向「最大努力」|
| **D5** | B5 XML 方案：xmltodict+patch vs lxml 手寫？ | C（xmltodict+patch）/ B（lxml）| 傾向 C |
| **D6** | MF 沒有 Excel 匯入時的空設備/系統：靜默空清單 vs 提示？ | A（空清單）/ B（提示）| 傾向 A |
| **D7** | B3 endpoint 位置：單一 blueprint vs 分開？ | B（分開）| 傾向 B |

---

## 九、建議開工順序

```
B1（1~2 天）
  → 分析 CMMC reference docx 章節結構（需讀 .docx 內容）
  → 建 SspDocxGenerator 骨架 + 空殼輸出（封面/TOC/章節框架）

B2（2~3 天）  ← 最重要，其他都等這個
  → 定義 SspExportDataModel
  → MfSspContentLoader（MF 來源，7 個 service 聚合）
  → SspVersionContentLoader（SSP 版本來源，從 SSP entity）
  → SspDocxGenerator 填入真實資料

B3（0.5 天）  ← B2 完成後很快
  → 兩支 export endpoint + streaming response

B4（0.5~1 天，視 LibreOffice 環境）
  → LibreOffice headless subprocess wrapper

B5（2~3 天）  ← 平行於 B3/B4 可開
  → jedi-oscal yaml_mapper 補 4 個缺口
  → SspExportDataModel → ControlImplementationEntity 映射
  → JSON/XML 輸出方法

B6（1 天）  ← 等 B3/B4/B5 全部完成
  → FE 匯出按鈕 + dialog + 格式選擇
```

預估總工時：B1~B5 約 7~10 天（視 B4 環境複雜度），B6 FE 另外 1 天。

---

## 十、明天開工前建議的一次性準備

1. **讀 CMMC reference docx**：用 python-docx 寫一個 dump 腳本，把所有 heading 列出來，確認章節結構（我沒辦法直接讀 .docx binary）
2. **確認 LibreOffice 環境**：`which libreoffice` on dev server
3. **拍板 D1~D3**：三個必要決策
4. **確認 `ssp_system_characteristics` DB schema**：確認 CIA 三欄（confidentiality/integrity/availability）和 network-architecture/data-flow 是否存在

```bash
# dev DB 確認 schema 指令
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
  -c "\d oscal.system_security_plans_system_characteristics"
```
