fix: 修復多頁PDF頁碼分配錯誤和logging配置問題
Critical Bug #1: 多頁PDF頁碼分配錯誤 問題: - 在處理多頁PDF時,雖然text_regions有正確的頁碼標記 - 但layout_data.elements(表格)和images_metadata(圖片)都保持page=0 - 導致所有頁面的表格和圖片都被錯誤地繪製在第1頁 - 造成嚴重的版面錯誤、元素重疊和位置錯誤 根本原因: - ocr_service.py (第359-372行) 在累積多頁結果時 - text_regions有添加頁碼:region['page'] = page_num - 但images_metadata和layout_data.elements沒有更新頁碼 - 它們保持單頁處理時的默認值page=0 修復方案: - backend/app/services/ocr_service.py (第359-372行) - 為layout_data.elements中的每個元素添加正確的頁碼 - 為images_metadata中的每個圖片添加正確的頁碼 - 確保多頁PDF的每個元素都有正確的page標記 Critical Bug #2: Logging配置被uvicorn覆蓋 問題: - uvicorn啟動時會設置自己的logging配置 - 這會覆蓋應用程式的logging.basicConfig() - 導致應用層的INFO/WARNING/ERROR log完全消失 - 只能看到uvicorn的HTTP請求log和第三方庫的DEBUG log - 無法診斷PDF生成過程中的問題 修復方案: - backend/app/main.py (第17-36行) - 添加force=True參數強制重新配置logging (Python 3.8+) - 顯式設置root logger的level - 配置app-specific loggers (app.services.pdf_generator_service等) - 啟用log propagation確保訊息能傳遞到root logger 其他修復: - backend/app/services/pdf_generator_service.py - 將重要的debug logging改為info level (第371, 379, 490, 613行) 原因:預設log level是INFO,debug log不會顯示 - 修復max_cols UnboundLocalError (第507-509行) 將logger.info()移到max_cols定義之後 - 移除危險的.get('page', 0)默認值 (第762行) 改為.get('page'),沒有page的元素會被正確跳過 影響: ✅ 多頁PDF的表格和圖片現在會正確分配到對應頁面 ✅ 詳細的PDF生成log現在可以正確顯示(座標轉換、縮放比例等) ✅ 能夠診斷文字擠壓、間距和位置錯誤的問題 測試建議: 1. 重新啟動後端清除Python cache 2. 上傳多頁PDF進行OCR處理 3. 檢查生成的JSON中每個元素是否有正確的page標記 4. 檢查終端log是否顯示詳細的PDF生成過程 5. 驗證生成的PDF中每頁的元素位置是否正確 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -356,12 +356,19 @@ class OCRService:
|
||||
total_confidence_sum += page_result['average_confidence'] * page_result['total_text_regions']
|
||||
total_valid_regions += page_result['total_text_regions']
|
||||
|
||||
# Accumulate layout data
|
||||
# Accumulate layout data and update page numbers
|
||||
if page_result.get('layout_data'):
|
||||
all_layout_data.append(page_result['layout_data'])
|
||||
layout_data = page_result['layout_data']
|
||||
# Update page number for all layout elements
|
||||
if layout_data.get('elements'):
|
||||
for element in layout_data['elements']:
|
||||
element['page'] = page_num
|
||||
all_layout_data.append(layout_data)
|
||||
|
||||
# Accumulate images metadata
|
||||
# Accumulate images metadata and update page numbers
|
||||
if page_result.get('images_metadata'):
|
||||
for img_meta in page_result['images_metadata']:
|
||||
img_meta['page'] = page_num # Update page number for multi-page PDFs
|
||||
all_images_metadata.extend(page_result['images_metadata'])
|
||||
|
||||
# Store OCR dimensions for each page
|
||||
|
||||
@@ -368,7 +368,7 @@ class PDFGeneratorService:
|
||||
ocr_x_right = bbox[2][0] # Right X
|
||||
ocr_y_bottom = bbox[2][1] # Bottom Y in OCR coordinates
|
||||
|
||||
logger.debug(f"[文字] '{text[:20]}...' OCR原始座標: L={ocr_x_left:.0f}, T={ocr_y_top:.0f}, R={ocr_x_right:.0f}, B={ocr_y_bottom:.0f}")
|
||||
logger.info(f"[文字] '{text[:20]}...' OCR原始座標: L={ocr_x_left:.0f}, T={ocr_y_top:.0f}, R={ocr_x_right:.0f}, B={ocr_y_bottom:.0f}")
|
||||
|
||||
# Apply scale factors to convert from OCR space to PDF space
|
||||
scaled_x_left = ocr_x_left * scale_w
|
||||
@@ -376,7 +376,7 @@ class PDFGeneratorService:
|
||||
scaled_x_right = ocr_x_right * scale_w
|
||||
scaled_y_bottom = ocr_y_bottom * scale_h
|
||||
|
||||
logger.debug(f"[文字] '{text[:20]}...' 縮放後(scale={scale_w:.3f},{scale_h:.3f}): L={scaled_x_left:.1f}, T={scaled_y_top:.1f}, R={scaled_x_right:.1f}, B={scaled_y_bottom:.1f}")
|
||||
logger.info(f"[文字] '{text[:20]}...' 縮放後(scale={scale_w:.3f},{scale_h:.3f}): L={scaled_x_left:.1f}, T={scaled_y_top:.1f}, R={scaled_x_right:.1f}, B={scaled_y_bottom:.1f}")
|
||||
|
||||
# Calculate bbox dimensions (after scaling)
|
||||
bbox_width = abs(scaled_x_right - scaled_x_left)
|
||||
@@ -487,7 +487,7 @@ class PDFGeneratorService:
|
||||
ocr_x_right_raw = table_bbox[2][0]
|
||||
ocr_y_bottom_raw = table_bbox[2][1]
|
||||
|
||||
logger.debug(f"[表格] OCR原始座標: L={ocr_x_left_raw:.0f}, T={ocr_y_top_raw:.0f}, R={ocr_x_right_raw:.0f}, B={ocr_y_bottom_raw:.0f}")
|
||||
logger.info(f"[表格] OCR原始座標: L={ocr_x_left_raw:.0f}, T={ocr_y_top_raw:.0f}, R={ocr_x_right_raw:.0f}, B={ocr_y_bottom_raw:.0f}")
|
||||
|
||||
# Apply scaling
|
||||
ocr_x_left = ocr_x_left_raw * scale_w
|
||||
@@ -502,11 +502,11 @@ class PDFGeneratorService:
|
||||
pdf_x = ocr_x_left
|
||||
pdf_y = page_height - ocr_y_bottom
|
||||
|
||||
logger.info(f"[表格] {len(rows)}行x{max_cols}列 → PDF位置: ({pdf_x:.1f}, {pdf_y:.1f}), 寬x高: {table_width:.0f}x{table_height:.0f}")
|
||||
|
||||
# Build table data for ReportLab
|
||||
# Convert parsed structure to simple 2D array
|
||||
max_cols = max(len(row['cells']) for row in rows)
|
||||
|
||||
logger.info(f"[表格] {len(rows)}行x{max_cols}列 → PDF位置: ({pdf_x:.1f}, {pdf_y:.1f}), 寬x高: {table_width:.0f}x{table_height:.0f}")
|
||||
reportlab_data = []
|
||||
|
||||
for row in rows:
|
||||
@@ -610,7 +610,7 @@ class PDFGeneratorService:
|
||||
ocr_x_right_raw = bbox[2][0]
|
||||
ocr_y_bottom_raw = bbox[2][1]
|
||||
|
||||
logger.debug(f"[圖片] '{image_path_str}' OCR原始座標: L={ocr_x_left_raw:.0f}, T={ocr_y_top_raw:.0f}, R={ocr_x_right_raw:.0f}, B={ocr_y_bottom_raw:.0f}")
|
||||
logger.info(f"[圖片] '{image_path_str}' OCR原始座標: L={ocr_x_left_raw:.0f}, T={ocr_y_top_raw:.0f}, R={ocr_x_right_raw:.0f}, B={ocr_y_bottom_raw:.0f}")
|
||||
|
||||
# Apply scaling
|
||||
ocr_x_left = ocr_x_left_raw * scale_w
|
||||
@@ -759,7 +759,7 @@ class PDFGeneratorService:
|
||||
self.draw_text_region(pdf_canvas, region, target_height, scale_w, scale_h)
|
||||
|
||||
# Draw tables for this page
|
||||
page_tables = [t for t in table_elements if t.get('page', 0) == page_num - 1]
|
||||
page_tables = [t for t in table_elements if t.get('page') == page_num - 1]
|
||||
logger.info(f"第 {page_num} 頁: 繪製 {len(page_tables)} 個表格")
|
||||
for table_elem in page_tables:
|
||||
self.draw_table_region(pdf_canvas, table_elem, images_metadata, target_height, scale_w, scale_h)
|
||||
|
||||
Reference in New Issue
Block a user