Update step_format.py
This commit is contained in:
@@ -1,33 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
step_format.py - 앱 내 변환 모듈 통합
|
||||
수정 사항:
|
||||
A-1. 연차보고서 한글 공백 처리
|
||||
A-2. bullet 형태 필터링 및 정리
|
||||
step_format.py — 형식만 변경 모드 파이프라인 v4
|
||||
|
||||
문서 유형 자동 감지 후 전용 핸들러로 분기:
|
||||
|
||||
fmt_gpd_report.py ← GPD 보고서형 (헤더 2개↑ + 공백 기반 들여쓰기)
|
||||
fmt_outline.py ← 목차/개요형 (1./a./Ⅰ. 번호 패턴 40%↑)
|
||||
fmt_table_report.py ← 표 중심형 (표가 페이지 면적 60%↑)
|
||||
fmt_generic.py ← 범용 fallback (위 유형 해당 없을 때, 페이지별 자동 선택)
|
||||
|
||||
판별 기준:
|
||||
1. 헤더 패턴 2개 이상 → GPD 보고서형
|
||||
2. 표 면적 비율 60% 이상 → 표 중심형
|
||||
3. 번호/기호 패턴 비율 40% 이상 → 목차/개요형
|
||||
4. 해당 없음 → 범용 fallback
|
||||
"""
|
||||
import os
|
||||
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import fitz # PyMuPDF
|
||||
|
||||
def log(msg):
|
||||
print(f"[step_format] {msg}")
|
||||
import fitz
|
||||
|
||||
# ... (중략: 상세 변환 로직 코드는 프로젝트 구조에 따라 최적화됨)
|
||||
from fmt_base import (
|
||||
log, detect_header_footer_patterns, build_html,
|
||||
extract_tables_from_page, ZW_PATTERN
|
||||
)
|
||||
import fmt_gpd_report
|
||||
import fmt_outline
|
||||
import fmt_table_report
|
||||
import fmt_generic
|
||||
|
||||
def process_pdf_to_html(input_dir, output_dir, session_id):
|
||||
"""PDF를 분석하여 구조화된 HTML로 변환하는 메인 함수"""
|
||||
input_path = Path(input_dir)
|
||||
gen_dir = Path(output_dir) / session_id
|
||||
|
||||
# ============================================================
|
||||
# 문서 유형 판별
|
||||
# ============================================================
|
||||
|
||||
_NUM_P = re.compile(r'^\d+[\.\uff0e\)]\s*[^\d\.\s]')
|
||||
_ALPHA_P = re.compile(r'^[a-z][\.\uff0e\)]\s*\S')
|
||||
_ROMAN_P = re.compile(r'^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴ]+[\.\s]')
|
||||
_DASH_P = re.compile(r'^-\s*[^\s\(]')
|
||||
|
||||
|
||||
def _doc_pattern_ratio(doc, header_patterns, footer_patterns):
|
||||
"""전 페이지 평균 번호/기호 패턴 비율"""
|
||||
total_hits, total_lines = 0, 0
|
||||
for page in doc:
|
||||
ph = page.rect.height
|
||||
tb = ph * 0.85
|
||||
# 표 bbox 수집
|
||||
table_rects = []
|
||||
try:
|
||||
for t in page.find_tables().tables:
|
||||
table_rects.append(t.bbox)
|
||||
except: pass
|
||||
|
||||
def in_table(x, y):
|
||||
for tx0,ty0,tx1,ty1 in table_rects:
|
||||
if tx0-5<=x<=tx1+5 and ty0-5<=y<=ty1+5: return True
|
||||
return False
|
||||
|
||||
for b in page.get_text("dict")["blocks"]:
|
||||
if b.get("type") != 0: continue
|
||||
for line in b.get("lines",[]):
|
||||
x, parts = None, []
|
||||
for s in line.get("spans",[]):
|
||||
t = ZW_PATTERN.sub('', s.get("text",""))
|
||||
if t.strip() and x is None: x = s["bbox"][0]
|
||||
parts.append(t)
|
||||
text = ''.join(parts).strip()
|
||||
y0 = line['bbox'][1]
|
||||
if text and y0<tb and text not in header_patterns and text not in footer_patterns:
|
||||
if x is None or not in_table(x, y0):
|
||||
total_lines += 1
|
||||
if (_NUM_P.match(text) or _ALPHA_P.match(text) or
|
||||
_ROMAN_P.match(text) or _DASH_P.match(text)):
|
||||
total_hits += 1
|
||||
return total_hits/total_lines if total_lines else 0.0
|
||||
|
||||
|
||||
def _table_area_ratio(doc):
|
||||
"""전 페이지 평균 표 면적 비율"""
|
||||
ratios = []
|
||||
for page in doc:
|
||||
page_area = page.rect.width * page.rect.height
|
||||
try:
|
||||
table_area = sum((t.bbox[2]-t.bbox[0])*(t.bbox[3]-t.bbox[1])
|
||||
for t in page.find_tables().tables)
|
||||
except: table_area = 0
|
||||
ratios.append(table_area / page_area if page_area else 0)
|
||||
return sum(ratios)/len(ratios) if ratios else 0.0
|
||||
|
||||
|
||||
def detect_doc_type(doc, header_patterns, footer_patterns):
|
||||
"""
|
||||
반환:
|
||||
'gpd_report' — GPD 보고서형
|
||||
'table_report' — 표 중심형
|
||||
'outline' — 목차/개요형
|
||||
'generic' — 범용 fallback
|
||||
"""
|
||||
n_headers = len(header_patterns)
|
||||
table_ratio = _table_area_ratio(doc)
|
||||
pattern_ratio = _doc_pattern_ratio(doc, header_patterns, footer_patterns)
|
||||
|
||||
log(f"유형 판별: 헤더={n_headers}개 표면적={table_ratio:.2f} 패턴={pattern_ratio:.2f}")
|
||||
|
||||
if n_headers >= 2:
|
||||
return 'gpd_report'
|
||||
if table_ratio >= 0.6:
|
||||
return 'table_report'
|
||||
if pattern_ratio >= 0.4:
|
||||
return 'outline'
|
||||
return 'generic'
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 메인 진입점
|
||||
# ============================================================
|
||||
|
||||
def run_format_only(session_id: str, input_dir: str) -> dict:
|
||||
gen_dir = Path(f'/tmp/{session_id}/format_out')
|
||||
gen_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
pdf_files = list(input_path.glob("*.pdf"))
|
||||
pdf_files = list(Path(input_dir).rglob('*.pdf')) + \
|
||||
list(Path(input_dir).rglob('*.PDF'))
|
||||
if not pdf_files:
|
||||
return {'success': False, 'error': 'PDF 파일이 없습니다.'}
|
||||
return {'success': False, 'error': '처리할 PDF 파일이 없습니다.'}
|
||||
|
||||
log(f"처리 시작: {pdf_files[0].name}")
|
||||
# ... (상세 처리 로직)
|
||||
|
||||
return {'success': True, 'session_id': session_id}
|
||||
pdf_path = pdf_files[0]
|
||||
log(f"PDF: {pdf_path.name}")
|
||||
doc = fitz.open(str(pdf_path))
|
||||
log(f"총 {len(doc)}페이지")
|
||||
|
||||
header_patterns, footer_patterns, meta = detect_header_footer_patterns(doc)
|
||||
log(f"메타: {meta}")
|
||||
|
||||
doc_type = detect_doc_type(doc, header_patterns, footer_patterns)
|
||||
log(f"문서 유형: {doc_type}")
|
||||
|
||||
handler_map = {
|
||||
'gpd_report': fmt_gpd_report,
|
||||
'outline': fmt_outline,
|
||||
'table_report': fmt_table_report,
|
||||
'generic': fmt_generic,
|
||||
}
|
||||
handler = handler_map[doc_type]
|
||||
pages_html = handler.process(doc, header_patterns, footer_patterns, meta)
|
||||
|
||||
if not pages_html:
|
||||
return {'success': False, 'error': '생성된 페이지가 없습니다.'}
|
||||
|
||||
final_html = build_html(pages_html)
|
||||
out_path = gen_dir / 'result.html'
|
||||
out_path.write_text(final_html, encoding='utf-8')
|
||||
log(f"완료: {len(pages_html)}페이지 → {doc_type}")
|
||||
|
||||
return {
|
||||
'success': True, 'html': final_html,
|
||||
'page_count': len(pages_html),
|
||||
'doc_type': doc_type,
|
||||
'session_id': session_id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user