163 lines
6.5 KiB
Markdown
163 lines
6.5 KiB
Markdown
|
|
# Phase 1 — Trafilatura 통합 (다음 작업자 핸드오프)
|
||
|
|
|
||
|
|
본 문서는 Phase 0이 완료된 시점에서 Phase 1 작업을 이어받는 AI 에이전트 또는 개발자가 즉시 작업을 시작하기 위한 핸드오프 노트다.
|
||
|
|
|
||
|
|
## 시작 전 확인 사항
|
||
|
|
|
||
|
|
- [ ] **Phase 0 Acceptance Gate**가 모두 ✅인가? [PHASE0_ACCEPTANCE_GATE.md](PHASE0_ACCEPTANCE_GATE.md) 참조. 통과 전에는 Phase 1 진행 금지.
|
||
|
|
- [ ] `tests/unit`와 `tests/integration` 전체가 PASS인가?
|
||
|
|
- [ ] git log에 Phase 0 commit들이 PR 단위로 분리되어 있는가? (0.1 vendored / 0.2 bug fix / 0.3 multi-file / 0.4 FastAPI / 0.5 config / 0.6 e2e tests / 0.7 gate)
|
||
|
|
|
||
|
|
## Phase 1 목표
|
||
|
|
|
||
|
|
URL이 입력일 때 원본 페이지에서 본문, 제목, 저자, 날짜, 언어, canonical URL을 정확히 뽑아 OntoCast의 `ContentUnit` metadata에 채워 넣는다.
|
||
|
|
|
||
|
|
**근거**: 통합설계서 §5 Phase 1, Trafilatura 분석 §11~§17.
|
||
|
|
|
||
|
|
**왜 Trafilatura를 가장 먼저 통합하는가**: 가장 작은 통합 — 단일 함수 호출(`bare_extraction`)만으로 끝남. 의존성도 명확하며 라이선스 동일 (Apache 2.0).
|
||
|
|
|
||
|
|
## 작업 단위 (PR 분해)
|
||
|
|
|
||
|
|
### 1.1: Trafilatura 의존성 활성화
|
||
|
|
|
||
|
|
`pyproject.toml`에 이미 `trafilatura[all]>=2.0.0`이 명시되어 있다. 활성화 절차:
|
||
|
|
|
||
|
|
```powershell
|
||
|
|
pip install -e ".[dev]" # 의존성 재설치 시 trafilatura 자동 설치
|
||
|
|
python -c "import trafilatura; print(trafilatura.__version__)"
|
||
|
|
```
|
||
|
|
|
||
|
|
**확인**: `2.0.0` 이상이 출력되어야 한다.
|
||
|
|
|
||
|
|
### 1.2: `web_extractor.py` 어댑터 작성
|
||
|
|
|
||
|
|
**위치**: `platform/core/extractors/web_extractor.py`
|
||
|
|
|
||
|
|
**근거**: Trafilatura 분석 §17의 `extract_for_ontology` 함수를 거의 그대로 사용.
|
||
|
|
|
||
|
|
**필수 동작**:
|
||
|
|
- 입력: `html: str`, `url: str`, `lang: str | None = None`
|
||
|
|
- 출력: `ExtractedWebDocument` (dataclass)
|
||
|
|
- `url`, `title`, `author`, `date`, `sitename`, `description`
|
||
|
|
- `text` (정제 본문)
|
||
|
|
- `body_xml` (Trafilatura `Document.body`)
|
||
|
|
- `metadata` (raw dict)
|
||
|
|
- `fingerprint` (SimHash)
|
||
|
|
- 실패 시 `None` 반환
|
||
|
|
|
||
|
|
**호출 옵션** (Trafilatura 분석 §12 권장값 그대로):
|
||
|
|
```python
|
||
|
|
Extractor(
|
||
|
|
output_format="python",
|
||
|
|
url=url,
|
||
|
|
with_metadata=True,
|
||
|
|
comments=False,
|
||
|
|
tables=True,
|
||
|
|
formatting=True,
|
||
|
|
links=True,
|
||
|
|
images=True,
|
||
|
|
dedup=True,
|
||
|
|
lang=lang,
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.3: `ContentUnit` 모델 확장
|
||
|
|
|
||
|
|
OntoCast의 `vendored/ontocast/ontocast/onto/content_unit.py`를 **직접 수정하지 말고**, 우리 쪽에 wrapper 모델을 만든다.
|
||
|
|
|
||
|
|
**위치**: `platform/models/content_unit.py`
|
||
|
|
|
||
|
|
**필드** (통합설계서 §7.1 참조):
|
||
|
|
- 기존 OntoCast 필드 (`text`, `index`, `doc_iri`, `graph`, `type`, `iri`) 유지/위임
|
||
|
|
- 추가: `source_url`, `title`, `author`, `publish_date`, `language`, `sitename`, `fingerprint`, `content_hash`, `metadata`, `retrieved_at`, `extracted_by`
|
||
|
|
|
||
|
|
**호환성**: 기존 OntoCast 코드가 받는 `ContentUnit`과 인터페이스 호환되도록 `as_ontocast()` 메서드 제공.
|
||
|
|
|
||
|
|
### 1.4: OntoCast `ConverterTool` 분기 추가 (URL/HTML 입력)
|
||
|
|
|
||
|
|
**문제**: OntoCast `ConverterTool`은 PDF/DOCX/MD만 처리. URL 또는 HTML 입력은 처리 못 함.
|
||
|
|
|
||
|
|
**조치 옵션**:
|
||
|
|
- **옵션 A (권장)**: OntoCast의 `convert_document.py` 모듈에 새 분기 추가 — `.html`, `.htm` 확장자 또는 `state.source_url`이 있으면 Trafilatura로 처리. **vendored 수정이지만 매우 작음**.
|
||
|
|
- **옵션 B**: API 레이어(`platform/api/`)에서 입력이 URL이면 미리 fetch + Trafilatura 처리한 뒤 그 결과를 JSON envelope로 ToolBox에 넘김.
|
||
|
|
|
||
|
|
**권장**: 옵션 B. vendored 수정을 늘리지 않고 platform 코드로 끝낼 수 있음.
|
||
|
|
|
||
|
|
새 endpoint:
|
||
|
|
- `POST /process/url` — body: `{"url": "...", "ontology_user_instruction": "...", ...}` — 내부적으로 `web_extractor`로 본문 추출 후 OntoCast workflow 실행.
|
||
|
|
|
||
|
|
### 1.5: Fingerprint 기반 dedup
|
||
|
|
|
||
|
|
- `tests/fixtures/`에 같은 본문의 두 URL fixture 만들기
|
||
|
|
- `web_extractor` 결과의 `fingerprint`가 일치하면 OntoCast 처리 skip
|
||
|
|
- 저장 위치: 일단 in-memory set (`platform/storage/dedup_cache.py`), Phase 2에서 Redis로 이전
|
||
|
|
|
||
|
|
### 1.6: 한국어 페이지 3종 추출 검증
|
||
|
|
|
||
|
|
**테스트 fixture 수집**:
|
||
|
|
- 한국어 뉴스 1개 (예: 연합뉴스/조선/한겨레)
|
||
|
|
- 한국어 블로그 1개 (예: 네이버 블로그)
|
||
|
|
- 한국어 쇼핑 페이지 1개 (예: 쿠팡 상품 페이지)
|
||
|
|
|
||
|
|
각각 raw HTML을 `tests/fixtures/korean/`에 저장 (실제 fetch는 운영 환경에서 한 번만, 그 결과를 fixture로 박제).
|
||
|
|
|
||
|
|
**테스트**: `tests/integration/test_web_extractor_korean.py`
|
||
|
|
- 본문 길이 > 200자
|
||
|
|
- title 추출 성공
|
||
|
|
- language 감지: `ko`
|
||
|
|
- author 또는 date 중 하나 이상 추출
|
||
|
|
|
||
|
|
### 1.7: Acceptance Gate 1 체크
|
||
|
|
|
||
|
|
통합설계서 §5 Phase 1 Acceptance Gate 4개 항목:
|
||
|
|
|
||
|
|
- [ ] URL 입력 → 본문/메타데이터가 정확히 추출되어 `ContentUnit`에 저장됨
|
||
|
|
- [ ] 한국어 뉴스/블로그/쇼핑 페이지 각각 1개씩 본문 추출 정확도 수동 검증
|
||
|
|
- [ ] 동일 URL 재입력 시 fingerprint 기반 dedup으로 skip
|
||
|
|
- [ ] Phase 0의 모든 기능이 여전히 정상 동작 (회귀 없음)
|
||
|
|
|
||
|
|
Phase 0의 `tests/unit/`, `tests/integration/` 전체가 여전히 PASS여야 함.
|
||
|
|
|
||
|
|
## Phase 1에서 만들 새 산출물
|
||
|
|
|
||
|
|
```
|
||
|
|
platform/
|
||
|
|
core/
|
||
|
|
extractors/
|
||
|
|
web_extractor.py ← 1.2
|
||
|
|
models/
|
||
|
|
content_unit.py ← 1.3
|
||
|
|
storage/
|
||
|
|
dedup_cache.py ← 1.5
|
||
|
|
api/
|
||
|
|
routes/
|
||
|
|
url_ingest.py ← 1.4 (POST /process/url)
|
||
|
|
tests/
|
||
|
|
fixtures/
|
||
|
|
korean/ ← 1.6
|
||
|
|
news_yonhap.html
|
||
|
|
blog_naver.html
|
||
|
|
shop_coupang.html
|
||
|
|
unit/
|
||
|
|
test_web_extractor.py ← 1.2
|
||
|
|
test_dedup_cache.py ← 1.5
|
||
|
|
integration/
|
||
|
|
test_url_ingest.py ← 1.4
|
||
|
|
test_web_extractor_korean.py ← 1.6
|
||
|
|
docs/
|
||
|
|
phases/
|
||
|
|
PHASE1_ACCEPTANCE_GATE.md ← 1.7 (PHASE0과 동일 형식)
|
||
|
|
PHASE2_NEXT_STEPS.md ← 다음 작업자에게 넘김
|
||
|
|
```
|
||
|
|
|
||
|
|
## 작업 시 준수사항 (PHASE0과 동일)
|
||
|
|
|
||
|
|
1. **PR 단위 분리**: 1.1~1.7 각각 별도 PR/커밋.
|
||
|
|
2. **PR 설명에 근거 인용**: 예) "통합설계서 §5 Phase 1 (1.2)에 따라 Trafilatura adapter 작성. Trafilatura 분석 §17 인용."
|
||
|
|
3. **vendored/ontocast/** 수정 최소화. 본 Phase에서는 옵션 B 사용 시 vendored 수정 0건이 목표.
|
||
|
|
4. **Phase 2로 넘어가지 말 것**: Acceptance Gate 1 통과 전까지 Crawl4AI 의존성을 코드에서 import하지 않는다.
|
||
|
|
|
||
|
|
## Phase 2 이후 핸드오프
|
||
|
|
|
||
|
|
Phase 1 완료 후 다음 작업자에게 동일한 형식의 `PHASE2_NEXT_STEPS.md`를 작성한다. 통합설계서 §12 Phase 2 작업 단위(2.1~2.8)를 참조.
|