#!/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 [ ...] # 예: 현 코퍼스 백필 후보(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:]))