# 合規框架 PDF 匯入兩階段化 — 實作計畫

> **文件版本**：1.0
> **建立日期**：2026-05-03
> **對應 SD**：[design.md](./design.md) ｜ **對應 SA**：[api-spec.md](./api-spec.md) ｜ **對應 FE**：[frontend-spec.md](./frontend-spec.md)

---

## 0. 執行原則

- **單一執行者**：本 plan 由 main agent 一氣呵成執行，phase 間有 dependency 不平行；每個 phase 結束 self-check（CLAUDE.md 規範清單）
- **Code review 粒度**：所有 phase 完成後一次跑（依 memory `feedback_review_granularity.md`）
- **Poetry 策略**：開發期間用 `path = "/Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-oscal", develop = true`；最終驗收前才進版到 0.0.16 pin（依使用者明示）
- **Reference PDFs**：`docs/features/FR-024-2605-compliance-framework-pdf-import-v2/referance/CMMC 2.0 Level 1 Assessment Guide.pdf` + `Level 2 Assessment Guide.pdf`，兩者共用 `ParserAdapterType.CMMC_2` adapter
- **DB 直接作業**：使用者已備份；migration 完成後直接以 cmmgr 帳號執行（依 memory `feedback_sql_migration_use_cmmgr.md`）
- **遇 blocker**：手機 push notification 通知使用者

---

## Phase A — DB Schema + Domain (BE)

依賴：無

### A.1 Migration
- 新增 `scripts/sql/2026-05-03-oscal-framework-parse-jobs.sql`
- 內容：建表 + index（tenant_id+is_active+status / uid / created_at）+ RLS policy（仿 ssp_docx_parse_jobs）+ GRANT
- 直接以 cmmgr 帳號執行

### A.2 ORM Model + Mapper
- `infra/oscal/model/oscal_framework_parse_job.py` — `OscalFrameworkParseJob`（schema=`oscal`）
- `infra/oscal/mapper/oscal_framework_parse_job_mapper.py`

### A.3 Domain Entity + Query + Repo Interface
- `domain/oscal/entity/oscal_framework_parse_job_entity.py`
- `domain/oscal/entity/oscal_framework_parse_job_query_entity.py`
- `domain/oscal/repository/oscal_framework_parse_job_repo.py`

### A.4 Domain Service + Repo Impl
- `domain/oscal/service/oscal_framework_parse_job_domain_service.py`
- `infra/oscal/repository/oscal_framework_parse_job_repo_impl.py`（繼承 `BaseRepositoryImpl`，session lazy）

### A.5 Error Codes
- 在 `common/code/grc_error_code.py` 新增 7 個 GRC error code（見 api-spec §3）

### A.6 DI 串接
- `di_containers/oscal/oscal_containers.py`：新增 repo / domain service / app service providers

**Self-check**：
- [ ] DDD 分層：repo `__init__` 不寫 `self.session = get_session()`
- [ ] migration 含日期註解 + `GRANT` 給 cm_app
- [ ] error code 序號不衝突

---

## Phase B — jedi-oscal 拆分（外部套件）

依賴：A 完成（不過 jedi-oscal 改動可平行；嚴格說 B 只在 Phase D BE parse endpoint 之前要好就行）

### B.1 切到 develop path
- BE `pyproject.toml`：jedi-oscal pin 改為註解，啟用 `[tool.poetry.dev-dependencies]` 區塊的 path 行
- 提示使用者跑 `poetry update`

### B.2 jedi-oscal 加 `parse_pdf_to_dict`
- `jedi_oscal/app/services/oscal_import_service.py`：
  - 新 method `parse_pdf_to_dict(file_stream, parser_type) -> dict`
  - 內部呼叫 `oscal_parser_factory.get_oscal_parser_adapter(parser_type)` 取 adapter，呼叫 `pdf_parser()` 拿 raw list[dict]
  - 再用 adapter 的 `convert_to_oscal_catalog_entity()` 轉 CatalogEntity，序列化成 OSCAL-shaped dict（含 catalog metadata + groups[] + controls[] + assessments[]，每筆給 stable uid）
  - **不寫 DB、不開 session**

### B.3 jedi-oscal 加 `import_from_dict`
- `jedi_oscal/app/services/oscal_import_service.py`：
  - 新 method `import_from_dict(oscal_dict, payload, curr_user, locale=None, version_uid=None) -> FrameworkVersionEntity`
  - 從 dict 重建 CatalogEntity → 沿用既有「寫 DB」路徑（FrameworkVersion / Catalog / Profile）
  - 寫入需 `@transaction`，但本方法已假設 caller 在 transaction scope 內（不再加裝飾器）

### B.4 重構 legacy method
- `import_or_update_oscal_from_pdf()` 內部改成兩段呼叫（保留簽章不變）

### B.5 新增 `ImportFrameworkVersionPayload` dataclass
- `jedi_oscal/app/dto/...`：替代散裝 dict 參數

### B.6 jedi-oscal 自我測試
- 暫不 publish，由 BE develop path 引用即可
- 對 reference PDFs 跑一次 `parse_pdf_to_dict` smoke test，確認 dict 結構符合預期

---

## Phase C — BE App Service + Routes

依賴：A、B

### C.1 App Service
- `app/oscal/service/oscal_framework_parse_job_service.py`：
  - `list_jobs(filter)` — list with summary count
  - `parse(file, payload, curr_user)` — 建 parse_job + 同步呼叫 `OscalImportService.parse_pdf_to_dict` + 處理失敗 fallback to status=failed
  - `get_detail(uid, curr_user)` — 取單筆，jedi-oscal framework_uid → name 補欄位
  - `confirm(uid, decisions, overrides, curr_user)` — 套用 → 呼叫 `import_from_dict`
  - `discard(uid)` — 軟刪
  - 全部 `@transaction`

### C.2 Marshmallow Schemas
- `api/oscal/serializers/framework_parse_job/`：
  - request：`FrameworkParseRequestSchema`、`FrameworkParseJobConfirmRequestSchema`
  - response：`FrameworkParseJobListResponseSchema`、`FrameworkParseJobDetailResponseSchema`、`FrameworkParseJobConfirmResponseSchema`、`FrameworkParseResponseSchema`

### C.3 Route 層
- `api/oscal/routes/framework/oscal_framework_parse_job_route.py`：
  - 5 個 Resource classes（依 api-spec §1）
- 在 `api/oscal/__init__.py` 註冊 4 個 url（`api.add_resource`）

### C.4 RBAC 資源註冊
- `ui_routes` migration 加新前端路由（依 SOP `reference_permission_system_sop`）
- `route_capabilities` 對應 BE 4 個 endpoint

**Self-check**：
- [ ] Route 不查 DB / 不 import ORM model
- [ ] App Service `@transaction`
- [ ] 沒裸 `raise ValueError`
- [ ] 審計欄位 `created_user_name` 一併回傳

---

## Phase D — 排程清理（BE）

依賴：A

- `core/scheduler.py`：新 cron job `framework_parse_job_cleanup`，每天 01:00 跑
  - 軟刪 `is_active=true AND created_at < now() - interval '7 days'`
  - 設 `is_active=false`、`status='expired'`、`updated_user='system'`
- 仿既有 `ssp_docx_parse_job_cleanup`（若有）；無則沿用 scheduler 模式

---

## Phase E — FE 主流程

依賴：A/B/C 完成（FE 需要實際端點才能 e2e）

### E.1 路由 + service + composable
- `src/router/index.ts`：兩條動態路由
- `src/config/api/api.js`：`OSCAL_FRAMEWORK_PARSE_JOBS` 常數
- `src/service/FrameworkImportService.js`：5 個方法
- `src/composables/useFrameworkImportDraft.js`

### E.2 Page 元件
- `src/views/compliance-framework/FrameworkImportPage.vue`（page wrapper + step 狀態機）
- `src/components/compliance-framework/import/`：
  - `DraftBanner.vue`
  - `FrameworkImportUploadStep.vue`
  - `FrameworkImportPreviewStep.vue`
  - `GroupTreeEditor.vue`
  - `ControlListEditor.vue`
  - `AssessmentListEditor.vue`
  - `ErrorPanel.vue`

### E.3 i18n
- `src/config/locales/i18n/zh-tw/compliance-framework-version-import.json`
- `src/config/locales/i18n/en/compliance-framework-version-import.json`

### E.4 入口替換
- `ComplianceFrameworkVersionManage.vue`：
  - 「匯入版本」按鈕 click → router.push
  - 移除 `importVersionDialog` 整段（template + script + handlers）
  - 移除 `createDocByImportFile` / `updateDocByImportFile` 兩個 handler

### E.5 PDF 預覽
- 上傳檔案保 `URL.createObjectURL(file)`，預覽用 `<iframe :src="blobUrl">`
- 重整 / blob 失效時顯示 placeholder

---

## Phase F — Changelog + 文件

依賴：A–E 完成後

- `docs/changelog/2026-05-XX-feat-framework-pdf-import-v2-be.md`
- `docs/changelog/2026-05-XX-feat-framework-pdf-import-v2-fe.md`
- `docs/changelog/2026-05-XX-feat-jedi-oscal-import-service-split.md`（jedi-oscal 改動）
- `docs/api/oscal/api-spec.md` / `design.md` 補 framework_parse_job 端點段落
- `compliance-manager-fe/docs/features/compliance-framework-pdf-import/release-notes.md`
- 對話歷史最後一輪封存到 `docs/conversation-history/2026-05-03-compliance-framework-pdf-import/`

---

## Phase G — 最終 code review

依賴：A–F 完成

- 對 BE 改動跑 `pr-review-toolkit:code-reviewer`
- 對 FE 改動跑 `pr-review-toolkit:code-reviewer`
- 對 jedi-oscal 改動單獨跑一輪（外部套件 quality gate）
- 修補回饋；spec compliance + code quality 雙審
- 確認後再決定是否 jedi-oscal 進版到 0.0.16 + BE pin（依使用者明示，不自動進）

---

## 開放追蹤項

| 項目 | 說明 |
|------|------|
| `audit_file_type` 路徑舊端點處置 | 暫保留，待新流程穩定後再評估 deprecate |
| Excel 匯入兩階段化 | 本 spec 不做（明確 out of scope） |
| Adapter L1/L2 區分 | 共用 `CMMC_2` adapter 即可，PDF 結構規律相同 |
| jedi-oscal 0.0.16 publish | 開發期 develop path，release 前再進版 |
