Update step_format.py
This commit is contained in:
@@ -1,33 +1,165 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
step_format.py - 앱 내 변환 모듈 통합
|
step_format.py — 형식만 변경 모드 파이프라인 v4
|
||||||
수정 사항:
|
|
||||||
A-1. 연차보고서 한글 공백 처리
|
문서 유형 자동 감지 후 전용 핸들러로 분기:
|
||||||
A-2. bullet 형태 필터링 및 정리
|
|
||||||
|
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 re
|
||||||
import json
|
import os
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
|
||||||
import fitz # PyMuPDF
|
|
||||||
|
|
||||||
def log(msg):
|
import fitz
|
||||||
print(f"[step_format] {msg}")
|
|
||||||
|
|
||||||
# ... (중략: 상세 변환 로직 코드는 프로젝트 구조에 따라 최적화됨)
|
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)
|
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:
|
if not pdf_files:
|
||||||
return {'success': False, 'error': 'PDF 파일이 없습니다.'}
|
return {'success': False, 'error': '처리할 PDF 파일이 없습니다.'}
|
||||||
|
|
||||||
log(f"처리 시작: {pdf_files[0].name}")
|
pdf_path = pdf_files[0]
|
||||||
# ... (상세 처리 로직)
|
log(f"PDF: {pdf_path.name}")
|
||||||
|
doc = fitz.open(str(pdf_path))
|
||||||
return {'success': True, 'session_id': session_id}
|
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