FR-145 · 設計定案 v1 · 2026-10-17
稽核報告掛稽核次、階段結案報告與專案總報告自動建立、六章文件版面、報告包含凍結證據與問卷 PDF、定稿與下載日誌
稽核報告(Word/PDF/Excel)掛在每個稽核次後面,結案就能下載。「專案調查結果」改造成結案報告:輪次結案仍有不符合時自動建該輪的階段結案報告、專案完成時自動建專案總報告;六章只有「總結與建議」由人寫,其餘從稽核計畫、稽核結果、改善計畫即時算。下載報告包拿 Word、PDF、證據對照表、各稽核次報告與工作表、依控制族群/控制項/評估目標分資料夾的凍結證據(問卷填答轉 PDF)。定稿鎖總結記版號;Word 給顧問自行修改交付,系統只記下載日誌。本文件寫定案規則、為什麼這樣定、踩到會怎樣。
🔴 這份是設計決策,不是現況。 內文停在 2026-10-17 定稿當時,實作改動不回頭改寫這裡。
狀態:設計定案,已開卡|討論稿:
discussion.md|雛形:prototype/index.html
檔案路徑與行號以 2026-10-17 為準,查不到時以函式名重新定位。路徑慣例:BE 路徑相對
compliance-manager-be/;FE 路徑相對compliance-manager-fe/;pkg/代表~/Projects/Jedicogy/module/jedi-python-package/<套件>/<套件底線名>/;三個 repo 都在feature/FR-139。
名詞:輪次=compliance.project_audit_rounds 一筆;稽核次=compliance.audit_round_passes 一筆,各自一組 AP/AR/POA&M;階段結案報告=一輪一份;專案總報告=一專案一份;定稿=鎖總結段、記版號、報告包改正式版;報告包=zip;證據對照表=報告包內的 Excel 索引;凍結佐證=FR-142 結案時 ar_close_service._freeze 寫進 assessment_observations 的採用佐證,帶 content-hash 與 frozen-at props。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-17 | v1:D1~D16 定案;資料模型、事件掛點、讀模型、端點契約、報告包契約、六張卡、端到端驗收 | 討論稿定案 |
整案一句話:稽核報告掛在稽核次後面結案即可下載;「專案調查結果」改成結案報告,一輪一份階段報告(輪次結案仍有不符合才建)、專案完成一份總報告,六章只有總結由人寫,其餘系統算;報告包含 Word/PDF、證據對照表、各稽核次報告與工作表、依控制結構分資料夾的凍結證據。
| 卡 | 做什麼 | 產出 | 完成怎麼判定(決策者親手做什麼、看到什麼) |
|---|---|---|---|
| 1 | 稽核作業清單每列「下載報告」下拉;BE 選檔端點;FR-142 Word 附件後加「改善計畫」一節 | FE 清單欄、新 route、ar_docx_generator 加節 |
稽核作業清單已結案列點「下載報告」選 Word,開檔最後一節是改善計畫表(任務、執行人員、到期日、措施);無缺失稽核次該節印「本次稽核無不符合項目,無改善計畫」;矯正中印「改善計畫尚未送出」;稽核中列印「結案後開放」;Excel 有改善計畫給改善計畫工作表否則給稽核結果工作表 |
| 2 | migration 加欄;輪次結案/專案完成掛「建報告」;清單端點一專案一列+子列;FE 清單頁重做;選單改名 | scripts/sql/ 一支、app/project_summary_report/ 事件與清單、FE ProjectSummaryReportManage.vue 重做 |
DEV 跑一輪有缺失結案 → 清單該專案展開多一列「第 N 輪結案報告 待定稿」;跑一輪無缺失結案 → 只多「專案總報告 待定稿」;側選單顯示「結案報告」;既有舊列仍在、點開唯讀 |
| 3 | 階段結案報告讀模型(六章+附件一)、報告頁 FE、存檔/定稿/修訂、Word/PDF、下載日誌 | 新 closing_report_* service、docx 範本、FE 報告頁 |
開報告頁六章數字與 DEV 該輪 AP/AR/POA&M 對得上(符合數、不符合清單、改善任務);寫總結存檔→定稿後第 5 章唯讀、狀態「已定稿 v1」;下載 Word 開檔六章加附件一;側欄下載紀錄多一列;修訂→再定稿變 v2 |
| 4 | 報告包 zip:Word、PDF、證據對照表、各稽核次 Word 與 Excel、證據資料夾(凍結版本)、問卷填答 PDF;定稿版存檔 | closing_report_bundle_service.py、survey_answer_pdf.py |
下載 zip 解開:資料夾是 族群/控制項/評估目標;對照表每列路徑在 zip 內找得到;問卷佐證是 PDF 且題目答案齊;連結只在對照表;未採用佐證有列無檔;定稿後再下載檔名帶 _v1 |
| 5 | 專案總報告:七章讀模型(各輪演進、跨多次未改善、最終符合度逐條、各輪總結帶入)、總報告包收各輪子包+合併對照表 | 沿用卡 3/4 加 final 分支 |
專案完成後清單多「專案總報告 待定稿」;開頁第 2 章一列一次稽核、跨多次未改善列出判定軌跡;第 5 章帶入第 1 輪已定稿總結;zip 內有 第1輪/、覆核輪/ 與合併對照表(多「輪次」欄) |
| 6 | FR-143 改善計畫工作表匯出加「列全部評估目標」開關(追加到 FR-143 卡 4 CM-2844,不另開) | poam_workbook_* |
匯出後符合與不適用的列也在、改善欄空 |
依賴:1、2 可先派(1 的改善計畫節讀 FR-143 CM-2841 的 POA&M,若 CM-2841 未落地先用「尚未送出」分支);3 等 2 與 FR-143 CM-2842;4 等 3;5 等 3、4;6 隨 FR-143 CM-2844。
定案理由與被排除方案見討論稿 D1~D16,此處只列結論供實作對照。
| D | 結論 |
|---|---|
| D1 | 稽核報告掛稽核次;closed 或 remediation 可下載,auditing 印「結案後開放」 |
| D2 | Word=FR-142 報告+「改善計畫」節(三態:有/無/尚未送出);Excel=有 POA&M 給改善計畫工作表否則稽核結果工作表,一律列全部評估目標;PDF 同 Word |
| D3 | 階段結案報告綁 audit_round_id 一輪一份;專案總報告綁 project_id 一專案一份 |
| D4 | 輪次結案時最終稽核 not_met > 0 才建階段報告;= 0 專案完成只建總報告;覆核又沒過再開一輪照建 |
| D5 | 系統自動建,不設建立鈕;建立即 draft |
| D6 | 狀態 draft/issued 兩態;清單分段鈕依專案:待撰寫/已完成/尚未結案 |
| D7 | 清單一專案一列,展開子列依時間序,總報告固定最後 |
| D8 | 文件版面;階段六章+附件一,總報告七章+附件一;只有總結由人寫、不標撰寫者 |
| D9 | 階段報告定論取該輪最後一次稽核;總報告最終符合度逐評估目標取最後一次判定 |
| D10 | 存檔/定稿/下載(下拉:Word、PDF、zip、報告包內容)三分開;定稿後存檔隱藏、定稿變修訂 |
| D11 | Word 與 PDF 都給;每次下載記稽核事件;系統不介入後續 |
| D12 | 報告包結構與對照表欄位(見「報告包契約」);問卷轉 PDF;連結只進對照表;證據取凍結版本 |
| D13 | 版本沿用 project_summary_report_history;定稿加 version_no/issued;還原只還總結段;定稿版 zip 存儲存後端 |
| D14 | 總報告定稿不擋各輪未定稿;第 5 章該輪標「尚未定稿」 |
| D15 | 能力點沿用 grc.summary-report.*:read/export 給 manager 與 auditor,update(存檔、定稿、修訂)只給 manager;稽核作業下載沿用 FR-142 守門 |
| D16 | Word 套 FR-141/142 同一份 docx 範本;編輯器沿用 TipTap;第 5 章 HTML 走既有 bleach 白名單 |
| 元件 | 現況(2026-10-17 實查) | 本案動作 |
|---|---|---|
compliance.project_summary_reports |
infra/project_summary_report/models/project_summary_report.py:uid、project_id、name、summary、is_delete |
卡 2 加 report_type、audit_round_id、status、issued_version、issued_at、issued_user |
compliance.project_summary_report_history |
models/project_summary_report_history.py:report_id、type(U/R)、name、summary 快照 |
卡 3 加 version_no、issued、bundle_ref |
| 端點 | api/project_summary_report/__init__.py:23-31:list(POST)、CRUD、export(PDF)、histories、history、history-revert |
卡 2 改 list 回模型;卡 3 加 issue/revise/export?format=docx|pdf、下載日誌;卡 4 加 bundle |
| App service | app/project_summary_report/service/project_summary_report_service.py:_require_participant(:38)守門、CRUD(:69-176) |
卡 2 加 create_for_round/create_for_project、清單讀模型;卡 3 新檔 closing_report_app_service.py(讀模型)不往既有檔塞 |
| 側選單 | ui_routes id 15:report-manage /project-summary-report 群組 42 sort 70;子頁 74(tab-view)、75(history) |
卡 2 seed migration 改 name/i18n key 為 closing-report;路徑不動 |
| FE | src/views/project-summary-report/:Manage 329 行、Form 344、TabView 52、History 159、TipTapEditor 323 |
卡 2 重做 Manage;卡 3 新 ClosingReportView.vue(文件版面)取代 Form/TabView;History 沿用加定稿標記 |
| 稽核次結案與凍結 | app/flow_control/service/ar_close_service.py:31 close():守門→_freeze(:54)→mark_closed→推進站;凍結項寫 assessment_observations 經 _observations.upsert_adopted,每項 props 帶 content-hash、frozen-at |
卡 4 讀凍結項:relevant-evidence[].href 定位檔案/問卷/連結,props 取雜湊 |
| 輪次結案與專案完成 | pkg/jedi-compliance-audit/app/service/audit_round_app_service.py:finalize_audit(:470,terminal=True 才 closed)、_sync_project_status(:157,全輪 closed → ProjectStatus.COMPLETED,呼叫點 :283/:435/:490) |
卡 2 在主專案 audit_round_route 對應 app service 呼叫後掛 hook(不改套件):輪次轉 closed → create_for_round;專案轉 COMPLETED → create_for_project |
| 稽核結果報告 | ar_report_export_service.py(135 行)組 ArReportModel;ar_docx_generator.py(192 行):generate(:68)、_append_record_table(:85)橫式附件 |
卡 1 在 _append_record_table 之後加 _append_poam_section;model 加 poam_rows、poam_state |
| 稽核結果 Excel | ar_workbook_export_service.py:FORMATS=("xlsx","docx","pdf","zip"),export(round_uid, fmt, …) |
卡 1 新 route 依 POA&M 存在選 poam_workbook_*(FR-143 CM-2844)或此 |
| 稽核計畫匯出 | ap_report_export_service.py、ap_workbook_export_service.py,GET /ap/<uid>/export?format= |
卡 4 直接呼叫組 zip |
| 稽核作業清單 | FR-142.8 稽核作業 表格,歸屬 project_participants role in (auditor, manager, reviewer) |
卡 1 FE 加「稽核報告」欄 |
| 問卷填答 | pkg/jedi-survey/app/service/question_answer_service.py:95 get_answer_list_by_task_survey_uid;凍結項 href urn:guidant:task-survey:<survey_uid>(ar_objective_view.py:77) |
卡 4 新 survey_answer_pdf.py:題目+答案排表經 render_pdf |
| PDF 渲染 | render_pdf(WeasyPrint,project_summary_report_route.py:171);SspLibreOfficeConverter(docx→pdf) |
卡 3 Word 經 LibreOffice 轉 PDF,與 FR-141/142 一致 |
| 稽核事件日誌 | 既有埋點(reference_audit_event_logging_pattern) |
卡 3 下載日誌 closing_report_download |
| 儲存後端 | 既有 storage 設定(FR-039 系列) | 卡 4 定稿版 zip 存檔 |
端點:GET /audit-round/<round_uid>/pass/<pass_uid>/report?format=docx|pdf|xlsx,守門沿用 audit_round_route.py 的 AR 匯出。
選檔邏輯(app service 層):
pass.status in (closed, remediation) else 412 GRC_PASS_NOT_CLOSED
poam = poam_app_service.get_submitted(pass) # FR-143 CM-2842;None=尚未送出或無缺失
format xlsx:
poam and poam.submitted → poam_workbook_export(pass, all_objectives=True) # CM-2844 開關
else → ar_workbook_export(pass)
format docx|pdf:
model = ar_report_export_service.build(pass)
model.poam_state = 'has' | 'none' | 'pending' # none=最終 not_met==0;pending=有不符合但 POA&M 未送出
model.poam_rows = [(ctrl, task, owners, due, measure)]
docx = ar_docx_generator.generate(model); pdf 經 SspLibreOfficeConverter
ar_docx_generator:generate() 在 _append_record_table 後呼叫 _append_poam_section(doc, model):直式新節、標題「改善計畫」、poam_state 三態文案、has 時五欄表(控制項、改善任務、執行人員、到期日、改善措施),表頭重印沿用 _repeat_header。檔已 192 行,新節邏輯放新檔 ar_docx_poam_section.py 由 generator 一行呼叫。
FE:稽核作業清單加「稽核報告」欄,closed/remediation 放「下載報告」下拉(Word/PDF/Excel,Excel 項文字依後端回的 workbook_kind 顯示「改善計畫工作表」或「稽核結果工作表」);auditing 印「結案後開放」。清單端點回每列加 report_available: bool、workbook_kind: 'poam'|'ar'。
migration(主線 phase=active,一支):
ALTER TABLE compliance.project_summary_reports
ADD COLUMN report_type varchar(20) NOT NULL DEFAULT 'legacy', -- stage | final | legacy
ADD COLUMN audit_round_id integer REFERENCES compliance.project_audit_rounds(id) ON DELETE CASCADE,
ADD COLUMN status varchar(20) NOT NULL DEFAULT 'draft', -- draft | issued
ADD COLUMN issued_version integer NOT NULL DEFAULT 0,
ADD COLUMN issued_at timestamptz,
ADD COLUMN issued_user varchar(50);
CREATE UNIQUE INDEX uq_psr_round ON compliance.project_summary_reports(audit_round_id) WHERE audit_round_id IS NOT NULL AND is_delete = 0;
CREATE UNIQUE INDEX uq_psr_final ON compliance.project_summary_reports(project_id) WHERE report_type = 'final' AND is_delete = 0;
ALTER TABLE compliance.project_summary_report_history
ADD COLUMN version_no integer, ADD COLUMN issued boolean NOT NULL DEFAULT false, ADD COLUMN bundle_ref varchar(500);
-- ui_routes id 15 name report-manage → closing-report(i18n key 跟著改)既有列 report_type='legacy'、status='issued'(視為已定稿、唯讀)。
自動建立:主專案 app/project/service/(呼叫 audit_round_app_service.finalize_audit/close_round 的那層)在套件回傳後,同一 @transaction 內呼叫 closing_report_creator.on_round_closed(round):
if round.status != closed: return
not_met = ar_objective_app_service.final_not_met_count(round.last_pass)
if not_met > 0: create_for_round(round) # stage,name=f"第 {round.round_no} 輪結案報告"
project = reload; if project.status == COMPLETED: create_for_project(project) # final,name="專案總報告"
兩個 create 都 idempotent(查唯一索引已有就跳過)。不改套件;套件事件已有 _sync_project_status 寫 COMPLETED,主專案只需在呼叫後重讀。
清單端點:POST /project-summary-reports(既有)回模型改為一專案一列:
{"project_uid","project","framework","manager","consultant","progress":{"round_no","stage_label","at"},
"bucket":"pending|done|none","draft_count","issued_count","latest_issued_at",
"reports":[{"uid","report_type","label","covers":[稽核次標籤],"status","not_met_final","issued_at","issued_version"}]}bucket 由後端算;分段鈕筆數走 meta.bucket_counts。歸屬:_require_participant 既有邏輯(manager/auditor),reviewer 不列(D15)。
FE:ProjectSummaryReportManage.vue 重做為 DataTable 行展開(expandedRows 用物件 key);子列欄照雛形;點子列進卡 3 報告頁。選單 i18n report-manage 改「結案報告」。
讀模型 closing_report_app_service.get_stage_report(report_uid),全部即時算:
| 章 | 資料來源與算法 |
|---|---|
| 封面/1 專案概況 | project、round、audit_round_passes(nature、seq、audit_start/end)、最後一次 AP 的 terms-and-conditions(受稽單位、部門、顧問、稽核人員);期間=第一次 AP audit_start ~ 輪次 closed_at |
| 2 整體符合度 | 每個 pass 的 AR findings 統計(met/not_met/na/total,符合率=met/(total−na));族群矩陣=findings 依 control family 分組 × passes,縮範圍未涵蓋為 null;定論=最後一個 pass |
| 3 主要缺失與風險 | 最後一個 pass 的 finding state=not-satisfied,join risk characterizations severity(高→中→低排)、observation 稽核發現、remediation(recommendation) 稽核建議 |
| 4 改善計畫摘要 | FR-143 POA&M(CM-2842 讀端點):poam-item → response(planned) → task(執行人員、到期日、改善措施);無 POA&M 印「本輪最終稽核無不符合項目」 |
| 5 總結與建議 | summary 欄 HTML |
| 6 附件清單 | 卡 4 bundle manifest |
| 附件一 | 逐 part_id:各 pass 的 finding state(null=未稽核),最終=最後一個 pass |
端點:
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | /closing-report/<uid> |
讀模型(六章+附件一)+ status/version/下載紀錄 |
| PUT | /closing-report/<uid>/summary |
存檔第 5 章(status=draft 才可;記 history type U) |
| POST | /closing-report/<uid>/issue |
定稿:守門 summary 非空→status=issued、issued_version+1、history 記 version_no/issued=true、卡 4 產定稿版 zip 存 bundle_ref。manager 守門 |
| POST | /closing-report/<uid>/revise |
修訂:status=draft(版號不退)。manager 守門 |
| GET | /closing-report/<uid>/export?format=docx|pdf |
完整報告;draft 時 PDF 加「草稿」浮水印、檔名不帶版號;issued 帶 _v<N>;記事件 closing_report_download |
Word:新 closing_report_docx_generator.py,範本 app/oscal/templates/closing/stage_report_template.docx(從 FR-142 ar_report_template.docx 複製改章節);docxtpl 渲染六章,附件一用 python-docx 橫式表(沿用 ar_docx_generator._landscape_section、_repeat_header)。第 5 章 HTML → docx 走既有 bleach 白名單後以 html2docx 風格轉段落(先查 SSP 匯出是否已有 HTML→docx helper,有就用)。
FE ClosingReportView.vue:文件版面照雛形 round-closing-report.html(封面、章節、表格、右側目次與下載紀錄);頁首存檔/定稿/下載下拉;TipTap 第 5 章;定稿後唯讀+修訂。超過 800 行拆子元件(封面、章節表、附件表、側欄)。
closing_report_bundle_service.build(report) -> BytesIO:
<專案>_<第N輪|專案總報告>_結案報告[_v<N>]/
├─ 00-完整報告.docx、00-完整報告.pdf ← 卡 3 export
├─ 01-證據對照表.xlsx ← 本卡
├─ 第<seq>次<內部|外部>稽核/ ← 每個 pass
│ ├─ 稽核計畫.docx ← ap_report_export_service
│ ├─ 稽核報告.docx ← 卡 1 選檔(含改善計畫節)
│ └─ 改善計畫工作表.xlsx | 稽核結果工作表.xlsx ← 卡 1 選檔
└─ 證據/<族群代碼 族群名>/<控制項>/<評估目標>/
├─ <原檔名> ← upload_files 以凍結 content-hash 對應版本
└─ 問卷填答_<問卷名>.pdf ← survey_answer_pdf
凍結項來源:assessment_observations 該 AR result 的採用項(_observations.list_adopted(ar_result_id),CM-2826 已有讀法),每項 relevant-evidence[].href:/files/<uid>→檔案、urn:guidant:task-survey:<uid>→問卷、其他→連結;props 取 content-hash、frozen-at。檔案以 content-hash 對 upload_files.sha256 取版本,取不到(檔被刪)在對照表標「檔案已不存在」不中斷。
證據對照表欄:稽核次、控制族群、控制項、評估目標、類型(檔案/問卷/連結)、名稱、包內路徑、雜湊/快照識別、採用(是/否)、備註。未採用項來自同 observation 的 adopted=false 紀錄,留列不收檔。
問卷 PDF survey_answer_pdf.render(task_survey_uid) -> bytes:get_answer_list_by_task_survey_uid 取題目與答案,HTML 表(問卷名、填答人、填答時間、題號/題目/答案)經 render_pdf。凍結時記的是 survey_uid,答案取當下最新一筆 history(問卷填答在稽核後不會再改,FR-142 D10 已鎖)。
定稿版存檔:issue 時 build 一次存儲存後端,history.bundle_ref 記路徑;歷史頁該筆可重下載。draft 下載即時 build 不存。
大小:證據檔總量超過 1 GB 時改背景工作(background_jobs)產檔後通知,本案先同步產、超過門檻回 413 提示;門檻進 config。
同卡 3 讀模型加 report_type=final 分支:
| 章 | 算法 |
|---|---|
| 1 概況 | 全部輪次與 passes;最終狀態 |
| 2 各輪演進 | 每輪每 pass 統計一列;跨多次未改善=同 part_id 連續 ≥2 個 pass not-satisfied,列判定軌跡與最終通過日 |
| 3 最終符合度 | 逐 part_id 取最後一次 finding(任一輪),族群分組 |
| 4 缺失與改善紀錄 | 全部曾 not-satisfied 的 part_id:首次發現 pass、POA&M 任務、覆核結果 |
| 5 各輪總結 | 各 stage 報告 summary(issued 才帶,draft 標「尚未定稿」) |
| 6 專案總結與建議 | 本報告 summary |
| 7 附件清單、附件一 | 附件一各欄=全部 passes+最終 |
總報告包:第1輪/、覆核輪/ 各放該輪 stage 報告包內容(有定稿版用定稿版,否則即時 build);01-證據對照表.xlsx 合併各輪加「輪次」欄;00-專案總報告.docx/pdf。
poam_workbook_export(..., all_objectives: bool = False):True 時列全部評估目標,符合/不適用列改善欄空、灰底唯讀;頁面匯出鈕旁勾選「列全部評估目標」預設勾;卡 1 呼叫固定 True。
_v2;每次下載側欄「下載紀錄」多一列(人、時間、格式);DB 稽核事件表多 closing_report_download。第1輪/ 與 覆核輪/、合併對照表有「輪次」欄。test/ 下 ar 相關測試與 FR-142 e2e 不壞;稽核結果頁「結案」行為不變。| 卡 | Notion | 做什麼 | 主要檔 | 依賴 | 規模 |
|---|---|---|---|---|---|
| 1 | CM-2855 | 稽核作業清單「下載報告」+選檔端點+Word 改善計畫節 | api/project/routes/audit_round_route.py、新 ar_docx_poam_section.py、FE 清單元件 |
FR-143 CM-2841/2842(POA&M 讀);未落地先走 pending 分支 | 中 |
| 2 | CM-2856 | migration、自動建立 hook、清單端點、FE 清單重做、選單改名 | scripts/sql/2026-10-17-closing-report-columns.sql、app/project_summary_report/、app/project/service/(hook)、FE Manage |
— | 中 |
| 3 | CM-2857 | 階段報告讀模型、端點、Word/PDF、FE 報告頁、下載日誌 | 新 app/closing_report/、docx 範本、FE ClosingReportView.vue |
2、FR-143 CM-2842 | 大 |
| 4 | CM-2858 | 報告包:zip、證據對照表、證據資料夾、問卷 PDF、定稿版存檔 | closing_report_bundle_service.py、survey_answer_pdf.py |
3 | 大 |
| 5 | CM-2859 | 專案總報告七章、總報告包 | 卡 3/4 加 final 分支 | 3、4 | 中 |
| 6 | CM-2860 | FR-143 Excel 列全部開關 | poam_workbook_* |
FR-143 CM-2844 | 小 |
序列組:卡 1 與卡 2 可平行;卡 3 之後序列。卡 1 與卡 3 都動 ar_docx_generator 周邊,卡 3 等卡 1 合回。
assessment_observations 採用項 props 的 content-hash 才是報告包要用的;用 upload_files 現在的檔會在結案後被換。對應版本取不到要在對照表標出,不中斷打包。urn:guidant:task-survey:<uid>(ar_objective_view.py:77),不是檔案路徑;分類型時先判 scheme。has(POA&M 已送出)、none(最終 not_met==0)、pending(有不符合但未送出)。判 none 看 AR 不看 POA&M。close_round 與 finalize_audit 都會 _sync_project_status),用唯一索引擋重複,不靠流程保證只叫一次。_sync_project_status 在 jedi-compliance-audit;本案在主專案呼叫套件 app service 的那層之後掛,避免動套件發版。report_type 要有預設:NOT NULL DEFAULT 'legacy',否則 migration 在有資料的庫炸。ar_docx_generator 已 192 行、ProjectSummaryReportManage.vue 329 行:新節與新頁一律新檔,不往裡塞(CLAUDE.md 800/60 行規則)。expandedRows 用物件 {[uid]: true},不是陣列。