Skip to content

Commit 077e555

Browse files
committed
Expose File Data
1 parent ec3d91a commit 077e555

14 files changed

Lines changed: 774 additions & 0 deletions

expose/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# 문서 핵심 데이터 추출 시스템
2+
3+
다양한 문서 형식(PDF, Word, HWP)에서 핵심 내용을 추출하고 구조화된 형식으로 출력하는 시스템입니다.
4+
5+
## 현재 구현 상태
6+
7+
### 1. 파일 읽기 모듈
8+
- PDF 파일 읽기 (`PDFReader`)
9+
- Word 문서 읽기 (`WordReader`)
10+
- 한글 문서 읽기 (`HWPReader`)
11+
12+
### 2. 파일 내보내기 모듈
13+
- Excel 파일 내보내기 (`ExcelWriter`)
14+
- PDF 파일 내보내기 (`PDFWriter`)
15+
16+
### 3. 핵심 내용 추출 모듈
17+
- 기본 추출기 (`BaseExtractor`)
18+
- TextRank 알고리즘
19+
- BERT 기반 의미적 분석
20+
- 위치 기반 가중치
21+
- 도메인 특화 점수
22+
- PDF 특화 추출기 (`PDFExtractor`)
23+
- 페이지 번호 제거
24+
- 헤더/푸터 제거
25+
- 표 구조 분석
26+
- Word 특화 추출기 (`WordExtractor`)
27+
- 스타일 태그 처리
28+
- 특수 문자 정규화
29+
- 목차 분석
30+
- 한글 특화 추출기 (`HWPExtractor`)
31+
- 한글 특수 문자 처리
32+
- 각주/미주 제거
33+
- 단락 구조 분석
34+
35+
### 4. 추출기 팩토리
36+
- 파일 확장자에 따른 적절한 추출기 선택
37+
- 확장 가능한 구조
38+
39+
## 남은 작업
40+
41+
### 1. OCR 통합
42+
- [ ] Tesseract OCR을 활용한 스캔본 문서 처리
43+
- [ ] 이미지 기반 텍스트 추출 최적화
44+
- [ ] 다국어 OCR 지원
45+
46+
### 2. 성능 최적화
47+
- [ ] 병렬 처리 구현
48+
- [ ] 캐싱 시스템 도입
49+
- [ ] 메모리 사용량 최적화
50+
51+
### 3. 사용자 경험 개선
52+
- [ ] 사용자 정의 패턴 등록 기능
53+
- [ ] 추출 결과 실시간 미리보기
54+
- [ ] 사용자 피드백 기반 학습
55+
56+
### 4. 기능 확장
57+
- [ ] 실시간 협업 기능
58+
- [ ] 다국어 지원 강화
59+
- [ ] 도메인 특화 모델 추가
60+
61+
## 향후 방향성
62+
63+
### 1. AI/ML 기반 고도화
64+
- 딥러닝 모델을 활용한 더 정교한 내용 추출
65+
- 사용자 피드백 기반 지속적 학습
66+
- 도메인 특화 모델 개발
67+
68+
### 2. 확장성 강화
69+
- 새로운 문서 형식 지원
70+
- 클라우드 기반 확장
71+
- 마이크로서비스 아키텍처 도입
72+
73+
### 3. 보안 강화
74+
- 문서 암호화 지원
75+
- 접근 제어 구현
76+
- 감사 로그 시스템 구축
77+
78+
### 4. 통합성 향상
79+
- API 서비스 제공
80+
- 다양한 플랫폼 연동
81+
- 표준 형식 지원 확대
82+
83+
## 설치 및 사용 방법
84+
85+
1. 의존성 설치:
86+
```bash
87+
pip install -r requirements.txt
88+
```
89+
90+
2. 기본 사용 예시:
91+
```python
92+
from document_processor import DocumentProcessor
93+
94+
processor = DocumentProcessor()
95+
extracted_data = processor.process_file("input.pdf")
96+
processor.export_file(extracted_data, "output.xlsx")
97+
```
98+
99+
## 라이선스
100+
MIT License
101+
102+
## 기여 방법
103+
1. Fork the repository
104+
2. Create your feature branch
105+
3. Commit your changes
106+
4. Push to the branch
107+
5. Create a new Pull Request
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
from abc import ABC, abstractmethod
2+
from typing import List, Dict, Any
3+
import re
4+
from sentence_transformers import SentenceTransformer
5+
import numpy as np
6+
from sklearn.metrics.pairwise import cosine_similarity
7+
from transformers import BertModel, BertTokenizer
8+
import torch
9+
10+
class BaseExtractor(ABC):
11+
def __init__(self):
12+
# 다국어 BERT 모델 초기화
13+
self.model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
14+
self.bert_model = BertModel.from_pretrained('klue/bert-base')
15+
self.tokenizer = BertTokenizer.from_pretrained('klue/bert-base')
16+
17+
# 도메인별 전문 용어 사전
18+
self.domain_keywords = {
19+
'legal': ['제1조', '당사자', '계약기간', '의무', '권리'],
20+
'medical': ['투여량', '부작용', '진단코드', '처방', '증상'],
21+
'technical': ['사양', '구성', '설치', '운영', '관리']
22+
}
23+
24+
@abstractmethod
25+
def preprocess_text(self, text: str) -> str:
26+
"""문서 타입별 전처리 메서드"""
27+
pass
28+
29+
def calculate_sentence_importance(self, sentence: str, position: int, total_sentences: int) -> float:
30+
"""
31+
문장의 중요도를 계산합니다.
32+
33+
Args:
34+
sentence (str): 분석할 문장
35+
position (int): 문장의 위치
36+
total_sentences (int): 전체 문장 수
37+
38+
Returns:
39+
float: 문장 중요도 점수
40+
"""
41+
# TextRank 점수
42+
textrank_score = self._calculate_textrank_score(sentence)
43+
44+
# BERT 임베딩 기반 의미적 중요도
45+
semantic_score = self._calculate_semantic_score(sentence)
46+
47+
# 위치 기반 가중치 (문서의 시작과 끝 부분에 더 높은 가중치)
48+
position_weight = self._calculate_position_weight(position, total_sentences)
49+
50+
# 도메인 특화 점수
51+
domain_score = self._calculate_domain_score(sentence)
52+
53+
# 종합 점수 계산 (가중치: TextRank 0.3, BERT 0.3, 위치 0.2, 도메인 0.2)
54+
total_score = (textrank_score * 0.3 +
55+
semantic_score * 0.3 +
56+
position_weight * 0.2 +
57+
domain_score * 0.2)
58+
59+
return total_score
60+
61+
def _calculate_textrank_score(self, sentence: str) -> float:
62+
"""TextRank 알고리즘을 적용한 중요도 점수 계산"""
63+
# 구현은 기존 코드와 동일
64+
pass
65+
66+
def _calculate_semantic_score(self, sentence: str) -> float:
67+
"""BERT 기반 의미적 중요도 점수 계산"""
68+
inputs = self.tokenizer(sentence, return_tensors="pt", truncation=True, max_length=512)
69+
with torch.no_grad():
70+
outputs = self.bert_model(**inputs)
71+
72+
# [CLS] 토큰의 임베딩을 문장 표현으로 사용
73+
sentence_embedding = outputs.last_hidden_state[:, 0, :].numpy()
74+
75+
# 의미적 중요도 점수 계산 (예: 다른 문장들과의 평균 유사도)
76+
return np.mean(cosine_similarity(sentence_embedding, self.sentence_embeddings))
77+
78+
def _calculate_position_weight(self, position: int, total: int) -> float:
79+
"""문장 위치 기반 가중치 계산"""
80+
# 문서의 시작과 끝 부분에 더 높은 가중치
81+
normalized_position = position / total
82+
if normalized_position < 0.1 or normalized_position > 0.9:
83+
return 1.0
84+
elif normalized_position < 0.2 or normalized_position > 0.8:
85+
return 0.8
86+
else:
87+
return 0.5
88+
89+
def _calculate_domain_score(self, sentence: str) -> float:
90+
"""도메인 특화 점수 계산"""
91+
max_score = 0.0
92+
for domain, keywords in self.domain_keywords.items():
93+
score = sum(1 for keyword in keywords if keyword in sentence)
94+
max_score = max(max_score, score / len(keywords))
95+
return max_score
96+
97+
def extract_key_sentences(self, text: str, top_n: int = 5) -> List[str]:
98+
"""
99+
텍스트에서 핵심 문장을 추출합니다.
100+
101+
Args:
102+
text (str): 원본 텍스트
103+
top_n (int): 추출할 문장 수
104+
105+
Returns:
106+
List[str]: 핵심 문장 리스트
107+
"""
108+
# 문장 분리
109+
sentences = re.split(r'[.!?]+', text)
110+
sentences = [s.strip() for s in sentences if len(s.strip()) > 0]
111+
112+
# 문장 임베딩 생성
113+
self.sentence_embeddings = self.model.encode(sentences)
114+
115+
# 각 문장의 중요도 계산
116+
sentence_scores = []
117+
for i, sentence in enumerate(sentences):
118+
score = self.calculate_sentence_importance(sentence, i, len(sentences))
119+
sentence_scores.append((sentence, score))
120+
121+
# 중요도 순으로 정렬
122+
ranked_sentences = sorted(sentence_scores, key=lambda x: x[1], reverse=True)
123+
return [sentence for sentence, _ in ranked_sentences[:top_n]]
124+
125+
def extract_keywords(self, text: str, top_n: int = 10) -> Dict[str, float]:
126+
"""
127+
텍스트에서 키워드를 추출합니다.
128+
129+
Args:
130+
text (str): 원본 텍스트
131+
top_n (int): 추출할 키워드 수
132+
133+
Returns:
134+
Dict[str, float]: 키워드와 중요도 점수
135+
"""
136+
# 단어 분리 및 정규화
137+
words = re.findall(r'\w+', text.lower())
138+
word_freq = {}
139+
140+
# 단어 빈도수 계산
141+
for word in words:
142+
if len(word) > 1: # 1글자 단어 제외
143+
word_freq[word] = word_freq.get(word, 0) + 1
144+
145+
# TF-IDF 스타일 점수 계산
146+
max_freq = max(word_freq.values())
147+
keywords = {word: freq/max_freq for word, freq in word_freq.items()}
148+
149+
# 도메인 특화 키워드 가중치 적용
150+
for word in keywords:
151+
for domain_keywords in self.domain_keywords.values():
152+
if word in domain_keywords:
153+
keywords[word] *= 1.5 # 도메인 키워드 가중치 증가
154+
155+
# 상위 키워드 선택
156+
return dict(sorted(keywords.items(), key=lambda x: x[1], reverse=True)[:top_n])
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Type
2+
from .base_extractor import BaseExtractor
3+
from .pdf_extractor import PDFExtractor
4+
from .word_extractor import WordExtractor
5+
from .hwp_extractor import HWPExtractor
6+
7+
class ExtractorFactory:
8+
@staticmethod
9+
def get_extractor(file_extension: str) -> Type[BaseExtractor]:
10+
"""
11+
파일 확장자에 따라 적절한 추출기를 반환합니다.
12+
13+
Args:
14+
file_extension (str): 파일 확장자 (예: '.pdf', '.docx', '.hwp')
15+
16+
Returns:
17+
Type[BaseExtractor]: 해당 파일 형식에 맞는 추출기 클래스
18+
19+
Raises:
20+
ValueError: 지원하지 않는 파일 형식인 경우
21+
"""
22+
extractors = {
23+
'.pdf': PDFExtractor,
24+
'.docx': WordExtractor,
25+
'.hwp': HWPExtractor
26+
}
27+
28+
if file_extension.lower() not in extractors:
29+
raise ValueError(f"지원하지 않는 파일 형식입니다: {file_extension}")
30+
31+
return extractors[file_extension.lower()]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from typing import List, Dict, Any
2+
import re
3+
from .base_extractor import BaseExtractor
4+
5+
class HWPExtractor(BaseExtractor):
6+
def preprocess_text(self, text: str) -> str:
7+
"""
8+
한글 문서 텍스트 전처리
9+
- 한글 특수 문자 처리
10+
- 표 내용 정규화
11+
- 각주/미주 제거
12+
"""
13+
# 한글 특수 문자 처리
14+
text = re.sub(r'[\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uD7B0-\uD7FF]', '', text) # 한글 자모 제거
15+
text = re.sub(r'[\uFF00-\uFFEF]', '', text) # 반각 문자 제거
16+
17+
# 각주/미주 패턴 제거
18+
text = re.sub(r'\[각주\d+\].*?\[/각주\]', '', text)
19+
text = re.sub(r'\[미주\d+\].*?\[/미주\]', '', text)
20+
21+
# 표 내용 정규화
22+
text = re.sub(r'┌[─┬┐]+┐', '', text) # 표 상단 테두리 제거
23+
text = re.sub(r'├[─┼┤]+┤', '', text) # 표 중간 테두리 제거
24+
text = re.sub(r'└[─┴┘]+┘', '', text) # 표 하단 테두리 제거
25+
text = re.sub(r'│', '|', text) # 세로선 정규화
26+
27+
return text.strip()
28+
29+
def extract_structure(self, text: str) -> Dict[str, Any]:
30+
"""
31+
한글 문서의 구조를 추출합니다.
32+
- 한글 스타일 기반 제목 식별
33+
- 표 내용 추출
34+
- 단락 구조 분석
35+
"""
36+
structure = {
37+
'headings': [],
38+
'tables': [],
39+
'paragraphs': [],
40+
'content': []
41+
}
42+
43+
lines = text.split('\n')
44+
current_paragraph = []
45+
46+
for line in lines:
47+
# 제목 패턴 (한글 제목 스타일)
48+
if re.match(r'^[가-힣]+\s*[0-9]*\.?\s*$', line.strip()):
49+
if current_paragraph:
50+
structure['paragraphs'].append('\n'.join(current_paragraph))
51+
current_paragraph = []
52+
structure['headings'].append(line.strip())
53+
54+
# 표 내용 (|로 구분된 경우)
55+
elif '|' in line:
56+
structure['tables'].append(line.strip())
57+
58+
# 단락 구분 (빈 줄)
59+
elif not line.strip():
60+
if current_paragraph:
61+
structure['paragraphs'].append('\n'.join(current_paragraph))
62+
current_paragraph = []
63+
64+
# 일반 텍스트
65+
else:
66+
current_paragraph.append(line)
67+
68+
if current_paragraph:
69+
structure['paragraphs'].append('\n'.join(current_paragraph))
70+
71+
# 내용 추출 (단락을 문장 단위로 분리)
72+
for para in structure['paragraphs']:
73+
sentences = re.split(r'[.!?]+', para)
74+
structure['content'].extend([s.strip() for s in sentences if s.strip()])
75+
76+
return structure

0 commit comments

Comments
 (0)