規劃日期:2026-05-29 撰寫人:raymond + Claude 狀態:設計草稿,待 user 確認抽取預設 → spec review → implementation plan arc 關聯:↳ FR-030 子(延續「補上分類器目前跳過的常見檔型」,sibling of FR-030.2)
讓 FR-030 證據分類器支援 Excel(.xlsx)與 PowerPoint(.pptx)證據 —— 兩者 Claude API 無原生 block,沿用 FR-030.2 已建好的「本地抽文字 → text block」路線(同 docx 機制),把 cell / 投影片文字抽出來送分類,分類與歸檔流程完全不變。
合規證據裡 Excel 清單(資產清冊、帳號表、設定矩陣)與簡報(教育訓練、政策說明)很常見。FR-030.2 完工後,分類器已能吃文字檔 + 圖片 + PDF,但 .xlsx / .pptx 仍落在 build_content_blocks 的「unsupported → skip」分支,被列未處理要人工歸檔。本期補上這兩種。
| 檔型 | Claude API 原生 block | 處理路線 |
|---|---|---|
| 圖片 / PDF | 有(image / document) | base64 直送(FR-030.2 已做) |
| docx / xlsx / pptx | 無 | 本地抽純文字 → text block |
xlsx / pptx 跟 docx 同類,沒有原生 block,必須自己在本地解析成文字。所以本功能不碰 vision 路徑,只擴充 FR-030.2 的 text 分支。
原分類 / 歸檔流程不變。
_state.json結構、Drive 歸檔、review UI 全沿用。xlsx / pptx 只是多兩種能抽成文字的檔型,出分類器後與其它檔走同一條歸檔路。
FR-030.2 後,build_content_blocks 的文字分支為:
if ext in {".docx", ".csv", ".log", ".txt", ".md"}:
text = _extract_text_legacy(path)
return [{"type": "text", "text": f"Filename: {filename}\n..."}], None本期改動:
.xlsx、.pptx_extract_text_legacy 加兩個分支抽取(mirror 既有 .docx 用 python-docx 的 pattern).xlsx(openpyxl):
elif ext == ".xlsx":
from openpyxl import load_workbook
wb = load_workbook(str(path), read_only=True, data_only=True) # data_only=取值不取公式
parts = []
for ws in wb.worksheets:
parts.append(f"# Sheet: {ws.title}")
for row in ws.iter_rows(values_only=True):
cells = [str(c) for c in row if c is not None]
if cells:
parts.append(" | ".join(cells))
text_out = "\n".join(parts)| 串接(比照 docx 表格)data_only=True:取計算後的值而非公式字串read_only=True:大檔記憶體友善.pptx(python-pptx):
elif ext == ".pptx":
from pptx import Presentation
prs = Presentation(str(path))
parts = []
for i, slide in enumerate(prs.slides, 1):
parts.append(f"# Slide {i}")
for shape in slide.shapes:
if shape.has_text_frame and shape.text_frame.text.strip():
parts.append(shape.text_frame.text)
if shape.has_table:
for r in shape.table.rows:
parts.append(" | ".join(c.text for c in r.cells))
text_out = "\n".join(parts)slide.notes_slide)兩者最後沿用既有 MAX_TEXT_CHARS=8000 截斷(在 _extract_text_legacy 內)。
data_only=True)— OK?MAX_TEXT_CHARS 截斷即可(傾向後者,簡單)。✅ 本次做:
.xlsx / .pptx 走本地抽文字 → text block → 分類openpyxl / python-pptx(container requirements)_extract_text_legacy + build_content_blocks ext 集合 + 單元測試❌ 本次不做:
.xls / .ppt(舊二進位格式,需 LibreOffice 轉換,另議)_state.json schema 變更、review UI 改版、vision 路徑任何改動build_content_blocks 對 .xlsx / .pptx:用 openpyxl / python-pptx 動態建小檔,斷言抽出的 text block 含 cell / slide 文字scripts/evidence/classify/docker/container_entrypoint.py(_extract_text_legacy / build_content_blocks)docs/features/FR-030.2-2605-evidence-classify-image-vision/design.mdscripts/evidence/classify/docker/requirements.txtscripts/evidence/classify/docker/test_container_entrypoint.py(沿用,加 case)本 FR 原本只規劃 xlsx/pptx 抽文字,但 2026-05-30 的 task arc 一路長成「證據分類器全面強化」。實際交付遠超原 design,以下逐條對齊:
| # | 原設計 | 實際做了什麼 | 原因 |
|---|---|---|---|
| 1 | xlsx/pptx 抽文字 | ✅ 照做 | — |
| 2 | 「不做」內嵌圖視覺辨識 | ❌ 推翻 → 做了(office 內嵌圖 → LibreOffice→PDF→vision,cost-aware:有圖才轉) | user 提出「貼圖的文件分不到」是真痛點;BE 既有 LibreOffice converter 可借鏡 |
| 3 | (無)trigger 選項 | 新增:選模型(sonnet/opus,UI flag 隱藏預設 sonnet)+ analyze-only 歸檔模式 | user 要先驗分析效果不污染 Drive + 想試 Opus |
| 4 | (無)一次歸檔 | 新增 POST /classification-run/<id>/archive + 審閱頁「立即歸檔」按鈕/提醒 |
analyze-only 跑完需事後正式歸檔 |
| 5 | (無)可觀測性 | 新增 container log 上傳 Drive(憑證遮蔽)+ token/成本統計(UI flag 隱藏預設關) | 查圖片 Fail 免 SSH + 想知道成本 |
| 6 | (無)prompt 調校 | 用真實證據集三方對照 → 加證據提示 + AO-letter 完整性 + 過配收斂 | 真實 run 發現的分類落差 |
docs/analysis/2026-05-30-cmmc-evidence-classification-accuracy-gap.mddocs/conversation-history/2026-05-30/evidence-classify-arc/(逐輪 dump,2 parts)