# FR-032 Phase E-a 換 session 接手 handoff — Excel round-trip（系統資產匯入回灌 + 名稱比對）

> ✅ **DONE — 2026-06-04 收尾**。E-a 全部完成並驗證（系統資產匯入回灌 + 名稱比對 master、
> 範本下載填值修復、資產→設備資產改名、設備 + 系統資產 picker round-trip + 雙向回填）。
> BE 5 commits（`1359ce48` / `743fc2a3` / `e51bd809` / `9116e5df` / `22929d6e`）+
> FE 2 commits（`8c888e2` / `f983e38`），皆未 push。收尾報告見同目錄
> `2026-06-04-ea-SUMMARY.md`；偏差紀錄見 design.md §11.11~11.19。

| 項目 | 內容 |
|------|------|
| 緣由 | FR-032 A/B/C/D/F 已完成（系統資產 + 設備鉤稽 CRUD + clone + docx 匯出）；本份交接 **E-a：Excel 匯入回灌系統資產 + 依名稱比對 information_systems master** |
| BE branch | `feature/FR-032`（compliance-manager-be） |
| FE branch | `feature/FR-032`（compliance-manager-fe） |
| 套件 repo | `~/Projects/Jedicogy/module/jedi-python-package/`（**單一 monorepo**，含 jedi-oscal / jedi-device），branch `feature/FR-032` |
| 接手前必讀 | 本檔 → design.md（全） → §5 列的匯入管線檔 |
| 預估時間 | BE 4 層 ~0.5–1 天 + FE preview ~0.5 天 |
| 性質 | **完整縱切，碰專案最敏感的 Excel 匯入管線** — 慢工出細活，每層加測試 |

---

## §0 接手讀序（按此順序讀）

1. **本檔**（全）
2. `docs/features/FR-032-2606-system-asset-inventory/design.md`（重點 §2 設計原則 / §3 資料模型 / §6 匯出 / §10 驗證結果）
3. `common/util/system_asset_snapshot.py`（**核心共用邏輯**，E-a 寫入要重用 `snapshot_information_system`）
4. `app/module_frame/excel_template/sheet_definitions.py`（樣板 sheet spec，看 `SHEET_INVENTORY_ITEMS` + `ALL_SHEETS`）
5. `app/oscal/service/excel_parser/sheet_handlers.py`（parser，看 `parse_inventory_items_sheet`）
6. `domain/oscal/parser/ssp_intermediate.py`（`ParsedInventoryItem`）
7. `domain/oscal/service/write_strategy/inventory_item_write_strategy.py`（SSP 寫入）
8. `app/oscal/service/ssp_excel_import_app_service.py`（MF defaults 寫入，搜 `_write_mf_defaults`）
9. FE：`src/components/grc/ssp-excel-import/SheetPreviewInfoSystems.vue`（**廢棄但可當 preview 範本**）

---

## §1 現況：FR-032 已完成 A/B/C/D/F

| 塊 | 內容 | 狀態 |
|----|------|------|
| A | 範本層（module_frame）系統資產表 + picker（hybrid soft-ref） | ✅ |
| B | 啟動專案 clone 帶 asset_type/ref（`module_frame_template_copy_service.py` Step 6） | ✅ |
| C | 專案 SSP view 系統資產表 + 編輯（`SspInventoryItemsAppService`） | ✅ |
| D1-A | docx 匯出獨立「System Assets」表 + 設備表 repoint live 表 | ✅ |
| F | 設備 inventory 鉤稽 jedi-device（device picker，帶 hostname/ip/os） | ✅ |

**資料模型（已落地）**：`compliance.module_frame_inventory_item_defaults` + `oscal.ssp_inventory_items` 各有 `asset_type`('hardware'/'information_system') / `ref_type`('information_system'/'device') / `ref_id`(master uid)。DEV 已套 migration `scripts/sql/2026-06-04-fr032-inventory-asset-type.sql`。

**E-a 要做什麼**：目前 Excel 匯入（`資產` sheet）解析的 inventory 列**寫死 asset_type='hardware'**，沒有「系統資產」的匯入路徑。E-a 要讓使用者能**從 Excel 匯入系統資產**，並**依系統名稱比對 `compliance.information_systems` master 自動鉤稽（設 ref_id）**，配不到則自由輸入。docx 匯出已有獨立 System Assets 表，Excel 端要對稱補上。

---

## §2 前次教訓（E-a 必看，別重蹈覆轍）

1. **共用 snapshot 邏輯已存在** — `common/util/system_asset_snapshot.py` 有 `snapshot_information_system(info_ds, ref_id)`（回 `(description, props, ref_id_str)`）+ `snapshot_device`。E-a 寫入時**重用**，不要重寫。
2. **error code 先查名稱再加** — pre-flight 曾漏看 `GRC_INFORMATION_SYSTEM_NOT_FOUND` 已存在（GRC_404020），差點加重複碼。新 code 先 `grep` 名稱。
3. **jedi packages 是 monorepo + 安裝形式不一** — jedi-oscal 是 venv **symlink**（改 source 重啟即生效）；jedi-device 是 **pin**，dev 要改 `pyproject.toml` 為 path（已改、未 commit）+ user 跑 `poetry update jedi-device`。E-a 若不動套件就無此問題。
4. **改 BE service 必提醒 user 重啟**（無 hot reload）；服務一律 user 自己起，不要附啟動命令。
5. **每個 sheet 有專屬 preview 元件** — 新增 sheet 一定要配一個 `SheetPreview*.vue`，否則匯入預覽該 sheet 不顯示。
6. **annotate / 多 call site 要全 wire** — 匯入有 MF defaults + SSP 兩條寫入路徑，兩條都要設 asset_type/ref（漏一條那 path 匯入的系統資產退化成 hardware）。

---

## §3 E-a 五層縱切設計 + 各層驗證命令

> 建議：系統資產走**獨立「系統資產」sheet**（對稱 UI 兩表 + docx System Assets 表），不要塞進「資產」sheet 加 asset_type 欄（欄位不同：系統是 name/abbreviation/owner/deployment/status，設備是 IP/MAC/FQDN）。

### Layer 1 — 樣板 sheet（`app/module_frame/excel_template/`）
- `sheet_definitions.py`：新增 `SHEET_SYSTEM_ASSETS`（`sheet_name="系統資產"`），columns 對齊 docx System Assets：`name`(required) / `abbreviation` / `description` / `owner_name` / `deployment_model`(enum) / `system_status`(enum)。加進 `ALL_SHEETS`（放在「資產」sheet 後）。
- `header_i18n.py`：加新欄位 header i18n key。
- `generator.py`：確認新 sheet 會被產生；**若匯出樣板需填入現有系統資產**（round-trip），找匯出填值邏輯補上（檢查 `downloadSspExcelTemplate` 'filled' 模式怎麼填其他 sheet）。
- deployment_model / system_status enum 值對照 `jedi_information_system/common/enum/information_system_enum.py`（on-premise/cloud/hybrid/co-location；operational/under-development/under-major-modification/disposition/other）。
- **驗證**：`python -c "from app.module_frame.excel_template.sheet_definitions import ALL_SHEETS; print([s.sheet_name for s in ALL_SHEETS])"` 應含「系統資產」。

### Layer 2 — parser（`app/oscal/service/excel_parser/sheet_handlers.py`）
- 仿 `parse_inventory_items_sheet`（~line 191）寫 `parse_system_assets_sheet`，回 `list[dict]`。
- 在 sheet → handler dispatch 處註冊（搜 parser 怎麼依 sheet_name 分派）。
- **驗證**：對一份含「系統資產」sheet 的 xlsx 跑 parser，確認回出列。

### Layer 3 — 中間表示 / bundle（`domain/oscal/parser/ssp_intermediate.py` + `app/oscal/service/excel_parser/v2_bundle.py`）
- 加 `ParsedSystemAsset` dataclass（name/abbreviation/description/owner_name/deployment_model/system_status）。
- bundle 加 `parsed_system_assets` list。
- **驗證**：bundle 測試確認新 list 有值。

### Layer 4 — 寫入 + 名稱比對（**核心**）
- 兩條寫入路徑都要處理（**漏一條 = 該 path 匯入的系統資產退化 hardware**）：
  - **MF defaults**：`app/oscal/service/ssp_excel_import_app_service.py` 搜 `_write_mf_defaults`（~1714），寫 `ModuleFrameInventoryItemDefaultEntity` 時設 `asset_type='information_system'` + ref。
  - **SSP**：`domain/oscal/service/write_strategy/inventory_item_write_strategy.py`（~124），寫 `InventoryItemEntity` 時設 asset_type + ref。
- **名稱比對 master**（E-a 的「a」）：對每筆 parsed 系統資產，用 `information_system_domain_service.get_information_systems(InformationSystemQueryEntity(name=<sheet name>))` 比對：
  - 配到 → 設 `ref_type='information_system'`, `ref_id=<master uid>`，並用 `snapshot_information_system(info_ds, ref_id)` 回填 props（master 為準）。
  - 配不到 → 自由輸入：asset_type='information_system'、props 取 sheet 值、ref 為 null。
  - 建議把「parsed system asset → entity（含比對）」抽成共用 helper（mirror `snapshot_information_system` 風格），MF + SSP 兩路徑共用。
  - **注意 RLS / tenant**：比對 master 要在對的 tenant scope（匯入流程已在 @transaction + tenant context 內，確認 query 不漏 tenant）。
- **error code**：ref 比對失敗不該炸（配不到 = 自由輸入，正常路徑）；只有「使用者明確給了 ref_id 但查無」才考慮錯誤，但 Excel 匯入通常給名稱不給 uid → 配不到就 fallback，不丟錯。
- **驗證**：app service 測試（mock info_ds）：名稱配到 → ref_id 設 + props 來自 master；配不到 → free input。加 logger patch autouse fixture（抄 `test_ssp_excel_import_app_service.py:46`）。

### Layer 5 — FE 匯入 preview（`compliance-manager-fe`）
- 新增 `src/components/grc/ssp-excel-import/SheetPreviewSystemAssets.vue`（仿 `SheetPreviewInfoSystems.vue` 廢棄檔，但欄位對齊新 sheet + 顯示「比對到的 master / 自由輸入」狀態）。
- 匯入 preview 頁把「系統資產」sheet 接到新元件（搜 preview 頁怎麼 map sheet→component，可能在 `ImportExcelPreviewPage.vue` 或 store `sspExcelImportStore.js`）。
- **驗證**：FE serve 後跑一次匯入，preview 顯示系統資產 sheet。

---

## §4 開工順位

1. **pre-flight**（§6）：對 branch / working tree / origin / pytest smoke
2. **verify A-F 真的在**（§7）：DB 三欄存在、BE 測試綠
3. Layer 1 樣板 sheet → 驗證
4. Layer 2 parser → 驗證
5. Layer 3 bundle → 驗證
6. Layer 4 寫入 + 名稱比對（**核心，加測試**）→ 驗證
7. Layer 5 FE preview → 驗證
8. 跨 repo 各自 commit（顯式 git add、禁 -am、不 push）
9. 問 user push / 收尾

---

## §5 該讀的檔案 / 預期改動範圍

**BE**
- `app/module_frame/excel_template/sheet_definitions.py`（加 SHEET_SYSTEM_ASSETS + ALL_SHEETS）
- `app/module_frame/excel_template/header_i18n.py`（header keys）
- `app/module_frame/excel_template/generator.py`（產生 + 可能填值）
- `app/oscal/service/excel_parser/sheet_handlers.py`（parse_system_assets_sheet + dispatch）
- `app/oscal/service/excel_parser/v2_bundle.py`（bundle 加 list）
- `domain/oscal/parser/ssp_intermediate.py`（ParsedSystemAsset）
- `app/oscal/service/ssp_excel_import_app_service.py`（MF defaults 寫入路徑 ~1714）
- `domain/oscal/service/write_strategy/inventory_item_write_strategy.py`（SSP 寫入 ~124）
- `common/util/system_asset_snapshot.py`（**重用** snapshot_information_system；可加 import 比對 helper）
- 可能 DI：寫入路徑若要 info_system_domain_service，確認已注入（excel import app service 是否已有；沒有就在 `di_containers/oscal/oscal_containers.py` 補）

**FE**
- `src/components/grc/ssp-excel-import/SheetPreviewSystemAssets.vue`（新）
- 匯入 preview 頁 / store（接 sheet → 元件）
- i18n（新欄位 / sheet 名）

---

## §6 Pre-flight Command（必跑）

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current          # 應為 feature/FR-032
git status --short                 # pyproject.toml M 是 dev-only（jedi-device path），勿 commit
# pytest smoke（FR-032 既有測試應全綠）
python -m pytest test/test_fr032_mf_inventory_asset_type.py test/test_fr032_ssp_inventory_asset_type.py \
  test/test_fr032_inventory_export_mapping.py test/test_fr032_device_picker.py -q | tail -5
# jedi monorepo branch
cd ~/Projects/Jedicogy/module/jedi-python-package && git branch --show-current   # feature/FR-032
```

---

## §7 Verify A-F 確實完成（必跑）

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
# DB 三欄存在（密碼查 .env DB_SECRET，psql 必帶 -p 25432）
PW=$(python3 -c "import json;print([json.loads(l[len('DB_SECRET='):])['rds_master_password'] for l in open('.env') if l.startswith('DB_SECRET=')][0])")
PGPASSWORD="$PW" psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -tA -c "
 SELECT table_name||'.'||column_name FROM information_schema.columns
 WHERE column_name IN ('asset_type','ref_type','ref_id')
   AND table_name IN ('module_frame_inventory_item_defaults','ssp_inventory_items') ORDER BY 1;"
# 應印出 6 列（兩表 × 三欄）
```

---

## §8 行為規範重要提醒（CLAUDE.md + memory）

- **不切 branch**：發現 branch 不對停下問 user（FR-032 是新 branch，user 自己切的）。
- **可自行 commit，但 push 永遠等 user 明示**；各 repo 分開 commit；**顯式 git add 檔名、禁用 `-am`**。
- **jedi monorepo**：改套件 source；jedi-oscal symlink 即生效、jedi-device 是 pin（已改 pyproject path、待 user `poetry update`）。E-a 若不動套件就不碰這塊。**pyproject.toml 的 jedi-device path 改動是 dev-only，不 commit。**
- **改 BE service 後提醒 user 重啟**（無 hot reload）；服務 user 自己起。
- **收尾等 user 明示**（changelog / SUMMARY / design §11 / memory 都等「收尾」指令）。
- **不晶晶體**（動詞連接詞用中文，技術名詞保留英文）。
- **跨 repo 動 FE 前先讀 FE CLAUDE.md**。
- **SQL migration 用 cmmgr + 日期註解 + GRANT**（E-a 大概不需新表，但若有則照規範）。
- **寫 app service test 加 logger patch autouse fixture**（DBLogHandler 雷，抄 `test_ssp_excel_import_app_service.py:46`）。
- **plan 假設先 verify**：本 handoff 列的行號（~1714 / ~124 等）是 2026-06-04 當下，開工先 grep 確認沒位移。

---

## §9 收尾流程（E-a 完成、user 說收尾才做）

1. design.md 加 §11 E-a reconciliation 段（實作偏差）
2. changelog `docs/changelog/YYYY-MM-DD-feat-fr032-excel-roundtrip.md`
3. 本 handoff 加 `> ✓ DONE` 標頭
4. 對話紀錄歸檔（若 user 要）
5. commit 收尾文件、問 user push

---

## §10 不在 E-a scope（不要順手做）

- **E-b**：docx 匯入回灌系統資產（走 per-framework adapter，複雜，獨立階段）
- **OSCAL-native（json/yaml/xml）匯出**：讀空 `system_implementations`，是跟本功能無關的既有深層缺陷（design §6.3 follow-up）
- **docx components/leveraged repoint**：同上，本期只做了 inventory
- **G**：jedi_information_system 拆套件（獨立 task）
- device 的 Excel round-trip（F 只做了 picker，沒做 device 的 excel 匯入比對）— 若要可比照系統資產，但**不在 E-a，先確認 user**

---

## §11 本 arc 已產出 commit（皆未 push）

**BE（`feature/FR-032`）**
```
2f167300 feat(FR-032 F): DeviceMenuResponse 加 hostname / os
02ac0662 feat(FR-032 F): 設備 inventory 支援鉤稽 jedi-device（BE）
a49f6b41 fix(FR-032): docx 匯出系統資產獨立成 System Assets 表
fe30ce52 fix(FR-032): ssp_version 匯出 system_implementations 改 getattr
ce71c84a feat(FR-032): docx 匯出 repoint 到 live inventory 表（D1-A）
0afb037e feat(FR-032): SSP 層系統資產 hybrid soft-ref（C）
58100d31 feat(FR-032): 範本層系統資產 hybrid soft-ref（A + clone）
cfcd0345 feat(FR-032): migration 加 inventory asset_type/ref_type/ref_id
0bfa1c0f / f10b8dcc / 420f5e19 docs(FR-032): design + spec sharpen + 對齊 live
```
（FR-032 BE 測試檔：`test/test_fr032_*.py` 共 4 檔 23 測試綠）

**jedi monorepo（`feature/FR-032`，`~/Projects/Jedicogy/module/jedi-python-package`）**
```
9277d5b feat(device): device menu 加 hostname / os
d3af930 feat(inventory): OscalInventoryItem 加 asset_type/ref_type/ref_id
```

**FE（`feature/FR-032`）**
```
c8bf554 fix(FR-032 F): 設備 dialog「描述」label + 驗證提示改「名稱」
1a97a98 feat(FR-032 F): device picker 帶入 hostname/os + 設備資產文案
68a5de2 feat(FR-032 F): 設備資產 dialog 加 device picker
841c2a9 Revert SspTabPanel 跑版 fix（user 覺得醜，還原原樣式待議）
91b55b4 feat(FR-032): 專案 SSP view 分「資產清冊 + 系統資產」兩表
7338cd9 / 941fb59 fix: 來源欄 Tag 統一 / enum label 顯示
c39138d feat(FR-032): 範本層系統元件 tab 新增「系統資產」表 + picker
```

**已知未決（非 E-a）**：FE `SspTabPanel.vue` Excel 按鈕 layout（user 嫌新版醜，已 revert 回原樣式，待議調整）；`.env.production` 是 FE 本機既有改動勿碰。

---

## §12 給 fresh session 的超短 prompt

```
請閱讀 docs/features/FR-032-2606-system-asset-inventory/handoff/2026-06-04-ea-excel-roundtrip-handoff.md，
按 §0 讀序了解現況，接手做 FR-032 Phase E-a（Excel 匯入回灌系統資產 + 依名稱比對
information_systems master 設 ref_id）。先跑 §6 pre-flight + §7 verify，再依 §4 順位
五層縱切實作（每層加測試）。BE/FE/jedi 在 feature/FR-032。不切 branch、不自動 push、
收尾等我明示。
```
