feat(documents): S1-ADD dedup·원본명 3컬럼 + md_status success→completed 매핑 (A) + office→md PoC (C-1)
plan ds-s1-backend-1 (r5 수렴). 코드만 스테이징 — migration 미적용(restart 보류, E-2 Soft Lock 예외창). A (앱 v1 디코딩 비파괴 최소선): - A-1 migrations/287_documents_dedup_fields.sql: original_filename TEXT / duplicate_of BIGINT FK ON DELETE SET NULL / duplicate_count INTEGER NOT NULL DEFAULT 0. 단일 statement·PG16 fast-path·BEGIN/COMMIT 금지. backfill 미포함(B-4). - A-2 app/models/document.py: 1계층 블록에 3 mapped_column (+ ForeignKey import). md_* 는 기존. - A-3 app/api/documents.py: DocumentResponse 3필드(duplicate_count=0 non-opt) + DocumentDetailResponse field_validator(success→completed, mode=before) — read-time DB→API 단방향, write(ORM) 미적용. - A-4 tests/test_s1_dedup_shape.py: success→completed 동작 + 비-success 통과 + 3필드 디폴트/roundtrip + ds-app contract fixture 디코드(skip-if-absent). py_compile OK. ★ backend 절반 — 전체 비파괴는 S3 render 테스트와 AND. C-1 PoC (워커 미연결 — C-2 에서 marker_worker 분기 연결): - app/workers/office_md.py: OOXML=markitdown(신규 dep, lazy) / hwp·hwpx=LibreOffice headless→HTML→markdownify(기존 dep). 실패·빈출력·타임아웃·dep부재 → OfficeMdError raise (success+빈md 금지 = C-5 postcondition 의 변환기 계약). - scripts/poc_office_md.py: 표 fidelity 측정 하니스. E-1 = prod LibreOffice 버전핀 안전컨텍스트 실행(hwpx 필터 버전 의존). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""C-1 PoC 하니스 — office/hwp → md 변환 품질(특히 표 fidelity) 측정.
|
||||
|
||||
plan ds-s1-backend-1 C-1/E-1:
|
||||
- hwp/hwpx 결과는 LibreOffice 버전 의존 → **prod extract_worker 와 동일 버전(버전핀 안전컨텍스트)** 에서 실행해야
|
||||
신호가 transfer 됨. live worker 에 job 태우는 것 아님(점유 0).
|
||||
- OOXML 은 markitdown(신규 dep): `pip install markitdown`.
|
||||
- 샘플은 trivial 말고 **대표 복잡본**(법령·KGS 표 중심 .hwp/.hwpx, 병합셀/다중시트 xlsx).
|
||||
|
||||
사용:
|
||||
python scripts/poc_office_md.py <file_or_dir> [<file_or_dir> ...]
|
||||
# 예: 현 코퍼스 백필 후보(doc/docx/xls/xlsx/hwp) 샘플 디렉토리
|
||||
python scripts/poc_office_md.py ~/poc_samples/
|
||||
|
||||
각 파일: 변환 성공 시 char/표 행수/heading 지표 + 본문 미리보기.
|
||||
실패(OfficeMdError) 시 FAILED 출력 — 이것이 C-5 가 md_status='failed' 로 라우팅할 케이스(설계대로).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# app/ 를 path 에 (모듈 import 용).
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))
|
||||
|
||||
from workers.office_md import SUPPORTED, OfficeMdError, convert_office_to_md, table_fidelity # noqa: E402
|
||||
|
||||
|
||||
def _iter_targets(args: list[str]):
|
||||
for a in args:
|
||||
p = Path(a).expanduser()
|
||||
if p.is_dir():
|
||||
for child in sorted(p.rglob("*")):
|
||||
if child.is_file() and child.suffix.lower() in SUPPORTED:
|
||||
yield child
|
||||
elif p.is_file():
|
||||
yield p
|
||||
else:
|
||||
print(f" (skip, 경로 없음: {p})")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if not argv:
|
||||
print(__doc__)
|
||||
return 2
|
||||
targets = list(_iter_targets(argv))
|
||||
if not targets:
|
||||
print("변환 대상(.docx/.xlsx/.pptx/.hwp/.hwpx) 없음.")
|
||||
return 1
|
||||
|
||||
ok = fail = 0
|
||||
for path in targets:
|
||||
print(f"\n=== {path.name} ({path.suffix.lower()}) ===")
|
||||
try:
|
||||
md = convert_office_to_md(path)
|
||||
except OfficeMdError as e:
|
||||
fail += 1
|
||||
print(f" FAILED → (C-5 가 md_status='failed' 라우팅) : {e}")
|
||||
continue
|
||||
ok += 1
|
||||
fid = table_fidelity(md)
|
||||
print(f" OK chars={fid['chars']} lines={fid['lines']} "
|
||||
f"table_rows={fid['table_pipe_rows']} (sep≈표수 {fid['table_separator_rows']}) "
|
||||
f"heading={fid['has_heading']}")
|
||||
preview = "\n".join(f" | {ln}" for ln in md.splitlines()[:12])
|
||||
print(preview)
|
||||
|
||||
print(f"\n--- 합계: OK {ok} / FAILED {fail} / 총 {len(targets)} ---")
|
||||
print("표 fidelity 가 낮으면(table_rows 0 등) 해당 포맷은 변환기/필터 재검토 — "
|
||||
"OOXML↔markitdown, hwp/hwpx↔LibreOffice 경계를 데이터로 확정(C-1).")
|
||||
return 0 if fail == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user