日期:2026-05-22 Session:B1 generator 骨架 → B2 content loader → B3 export API → B4 LibreOffice converter 分支:
feature/ssp-import-export-phase2
| Phase | 主題 | 狀態 |
|---|---|---|
| B1 | docxtpl 模板 + SspDocxGenerator 骨架 |
✅ shipped |
| B2 | MfSspContentLoader + SspVersionContentLoader |
✅ shipped |
| B3 | SspExportAppService + 2 routes + DI wiring |
✅ shipped |
| B4 | SspLibreOfficeConverter subprocess wrapper |
✅ shipped |
app/oscal/service/export/
├── __init__.py (B1 同步建立)
├── ssp_export_model.py (B1) 中間層 dataclass
├── ssp_docx_generator.py (B1) docxtpl generator
├── ssp_mf_content_loader.py (B2) MF 來源 loader
├── ssp_version_content_loader.py (B2) SSP 版本來源 loader
├── ssp_export_app_service.py (B3) 匯出 facade
└── ssp_libreoffice_converter.py (B4) LibreOffice subprocess wrapper
app/oscal/templates/ssp/
└── ssp_cmmc_template.docx (B1) docxtpl 模板(進版控)
scripts/
└── generate_ssp_docx_template.py (B1) 模板產生腳本
api/module_frame/routes/
└── mf_ssp_export_route.py (B3) GET /module-frame/<uid>/ssp-export
api/oscal/routes/ssp/
└── ssp_export_route.py (B3) GET /ssp/<ssp_uid>/export
pyproject.toml docxtpl 相依新增
common/code/grc_error_code.py GRC_SSP_EXPORT_INVALID_FORMAT (GRC_400075)
di_containers/oscal/oscal_containers.py 6 個 SSP export providers
api/module_frame/__init__.py route 掛入
api/oscal/__init__.py route 掛入
GET /api/1.0/module-frame/<uid>/ssp-export?format=docx|pdf|odt
GET /api/1.0/ssp/<ssp_uid>/export?format=docx|pdf|odt
send_file(非 envelope)ssp_uid = system_security_plans.uid(SSP 版本 UID)format=docx兩個來源(MF / SSP 版本)都組裝成同一 dataclass,generator 不知道來源差異。
模板 .docx 進版控;改版面直接在 Word 編輯,不需改 Python code; 腳本 scripts/generate_ssp_docx_template.py 重跑可重建。
{%tr for %} 是 row-level preprocessor,不可嵌套在段落層 {% for %} 內。 解法:所有迴圈移進 table row;all_objectives 在 context 層展平(含 control_id)。
MF 來源:
module_frame_service.get_module_frame(uid)
module_frame_party_service.list_parties(uid)
module_frame_ssp_resources_service.list_resources(uid) → devices / info_systems
module_frame_control_default_service.list_all(uid)
module_frame_control_objective_default_service.list_all(uid)
module_frame_reference_document_service.list_pool(uid)
→ MfSspContentLoader.load(uid) → SspExportDataModel
→ SspDocxGenerator.generate(model) → BytesIO
→ SspLibreOfficeConverter.convert(buf, "pdf") → BytesIO(B4)
SSP 版本來源:
ssp_domain_service.get_by_uid(ssp_uid) → ssp.system_characteristic / oscal_metadata.parties
ctrl_domain_service.get_all_by_ssp_id(id) → ControlImplementationEntity[](含 objectives)
→ SspVersionContentLoader.load(ssp_uid) → SspExportDataModel
→ (同上 generator)
| 項目 | 說明 |
|---|---|
| MF leveraged 空清單 | MF 沒有 leveraged_authorizations 資料路徑,目前回傳空清單。若需要,要在 ssp_system_implementation_items 補 MF scope leveraged 的查詢 |
| SSP version 程序書空清單 | ssp_reference_documents 路徑留給 B5 補;目前 SspVersionContentLoader 回傳空清單 |
libreoffice 路徑 |
DI 預設 "libreoffice";若 prod server 路徑不同,需調整 SspLibreOfficeConverter(libreoffice_cmd=...) |
| B5 OSCAL JSON/XML/YAML | 重型 phase,需修改 jedi-oscal ssp_yaml_mapper.py(補 parties / AO statements / back-matter);下個 session 獨立處理 |
| B6 FE UI | export 按鈕 + 格式 dialog,等 B5 完成後接 |
| Phase | 主題 | 說明 |
|---|---|---|
| B5 | OSCAL JSON/XML/YAML | 重型,需修 jedi-oscal;見下方交接 prompt |
| B6 | FE export 按鈕 | 等 B5 完成 |
✅ Docx generated: 38,645 bytes
✅ No unrendered Jinja2 blocks
✅ 'Guidant AI Platform' in output
✅ 'Raymond Chou' in output
✅ 'AC.1.001' in output (control)
✅ 'AC.1.001.a' in output (AO)
✅ 'AWS GovCloud' in output (leveraged)
✅ 'SEC-001' in output (reference doc)
B2~B4 為純 service 層組裝,無法在本 session 做 E2E smoke(需 DB context)。 建議 B5 開工前手動測試:用 dev 環境的 MF uid 呼叫 GET /api/1.0/module-frame/<uid>/ssp-export。
下個 session 貼以下內容:
接手 SSP Import/Export Phase 2 Track B — B5 OSCAL 序列化
已完成:B1(docxtpl generator)、B2(content loaders)、B3(export API)、B4(LibreOffice)
目前 API 支援 docx / pdf / odt,B5 要加 json / xml / yaml 格式。
B5 工作範圍:
1. jedi-oscal ssp_yaml_mapper.py 補 4 個缺口:
- AO statements:ci.objectives → OSCAL statements[]
路徑:ssp_control_implementation_objectives,ControlImplementationObjectiveEntity
欄位:statement_identifier, implementation_description, implementation_status, remarks
- parties + responsible-parties:metadata.parties(PartyEntity 已有 email_address/telephone_number/address)
- back-matter resources:ssp_reference_documents(context_type='ssp')
目前 SspVersionContentLoader.reference_documents 回傳空清單,需補路徑
- (system-characteristics 缺口 4 已確認不需補:DB 無 CIA / network-architecture 欄位)
2. SspExportAppService 加 format=json|yaml|xml 支援:
- 先用 ssp_domain_service.get_by_uid(ssp_uid) 取 entity
- 呼叫 jedi-oscal yaml_mapper 或新的序列化方法
- JSON: json.dumps(entity_to_dict(...))
- YAML: yaml.safe_dump(entity_to_dict(...))
- XML: xmltodict.unparse + xmlns namespace patch
3. Routes 加 json/yaml/xml mimetype + filename
開發規範提醒:
- jedi-oscal 修改期間在 pyproject.toml 用 path dependency(取消 tool.poetry.dev-dependencies 的 jedi-oscal 那行註解)
- 完成才發版,不提前 bump
- 每個 phase ship 後更新 docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md
關鍵檔案座標:
- tracker: docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md
- B phase 分析: docs/features/FR-011.2-2605-ssp-import-export-phase2/handoff/2026-05-21-b-phase-analysis.md
- yaml_mapper: ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/app/services/ssp/ssp_yaml_mapper.py(路徑待確認)
- content loaders: app/oscal/service/export/ssp_*_content_loader.py
- export app service: app/oscal/service/export/ssp_export_app_service.py