fix(search): handle markdown/fileless docs without marker conversion

PR-DocSrv-LargeDoc-Split-Markdown-1 commit 5 (plan brisk-paging-quokka.md).

이미 마크다운인 문서는 marker 변환 불필요 → _process_markdown_passthrough 로
파일 내용(없으면 extracted_text)을 md_content 에 직접 적재(success), 비면 skipped.
- _is_markdown_doc: file_format=md/markdown 또는 .md/.markdown 확장자
- 분기 위치 = file_path validation 이전 (fileless md = file_path NULL 처리 위함)
- engine=passthrough 로 marker 변환본과 구분

기존 버그 해소: fileless md 43건=「no file_path」 fail / .md 파일=unsupported extension
skip → 둘 다 md_content 미생성이었음.

검증(docker cp 격리): 13948(.md+file_path)→success md_len=1805(파일) /
23409(fileless 931자)→success(extracted_text) / 20237(fileless 6자)→success.
PDF 경로 무영향(_is_markdown_doc=False).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyungi
2026-05-24 08:02:30 +00:00
parent 7aaabe2c75
commit cf0d75fe84
+70 -1
View File
@@ -3,7 +3,9 @@
플로우:
classify_worker 완료 → enqueue 'markdown' stage (또는 reprocess 스크립트가 force=True 로 enqueue)
→ marker_worker.process()
→ doc_type / 확장자 / page_count gauge
→ doc_type / handwritten skip
→ 이미 마크다운(file_format=md / .md) = _process_markdown_passthrough (변환 없이 내용 직접 적재)
→ PDF: 확장자 / page_count gauge
→ 소형(≤SPLIT_THRESHOLD_PAGES) = _process_single 통째 1-shot /convert
대형(>SPLIT_THRESHOLD_PAGES) = _process_split page-range BATCH_PAGES 윈도우 분할
→ 응답 이미지 NAS persist + document_images UPSERT + md_content ref 정규화
@@ -118,6 +120,16 @@ def _get_page_count(file_path: str) -> int | None:
return None
def _is_markdown_doc(doc: Document) -> bool:
"""이미 마크다운 형식인 문서 판정 (file_format=md/markdown 또는 .md/.markdown 확장자)."""
fmt = (doc.file_format or "").lower()
if fmt in ("md", "markdown"):
return True
if doc.file_path:
return Path(doc.file_path).suffix.lower() in (".md", ".markdown")
return False
async def process(document_id: int, session: AsyncSession) -> None:
"""markdown stage 워커 진입점. queue_consumer 가 호출."""
doc = await session.get(Document, document_id)
@@ -151,6 +163,13 @@ async def process(document_id: int, session: AsyncSession) -> None:
)
return
# ---- (1.7) 이미 마크다운인 문서 = marker 변환 불필요, 내용 직접 적재 (commit 5) ----
# fileless md(file_path NULL) + .md/.markdown 파일 둘 다 처리. 기존엔 전자=「no file_path」
# fail, 후자=unsupported extension skip → md_content 미생성이었음.
if _is_markdown_doc(doc):
await _process_markdown_passthrough(doc, document_id, session)
return
# ---- (2) file_path validation ----
if not doc.file_path:
await _fail(session, document_id, "no file_path")
@@ -298,6 +317,56 @@ async def _process_single(
)
async def _process_markdown_passthrough(
doc: Document, document_id: int, session: AsyncSession
) -> None:
"""마크다운 문서 — 변환 없이 파일 내용(없으면 extracted_text)을 md_content 로 직접 적재.
내용이 비면 skipped. engine='passthrough' 로 marker 변환본과 구분.
"""
content = ""
if doc.file_path:
path = _to_marker_path(doc.file_path)
try:
content = Path(path).read_text(encoding="utf-8", errors="replace")
except OSError as exc:
logger.warning(
f"[marker] md passthrough file read failed id={document_id} "
f"path={path}: {type(exc).__name__}: {exc}"
)
content = ""
if not content.strip():
content = doc.extracted_text or "" # fileless md = extracted_text 사용
content = content.strip()
if not content:
await _set_skipped(session, document_id, "skipped: markdown doc with no content")
return
quality = _compute_quality(content, doc.extracted_text or "", {"page_count": None})
await session.execute(
update(Document).where(Document.id == document_id).values(
md_content=content,
md_status="success",
md_extraction_engine="passthrough",
md_extraction_engine_version=None,
md_extraction_quality=quality,
md_content_hash=hashlib.sha256(content.encode("utf-8")).hexdigest(),
md_source_hash=doc.file_hash,
md_generated_at=_now(),
md_extraction_error=None,
md_frontmatter=doc.md_frontmatter or {},
md_format_version="1.0",
content_origin="extracted",
)
)
await session.commit()
logger.info(
f"[marker] md passthrough success id={document_id} len={len(content)} "
f"src={'file' if doc.file_path else 'extracted_text'}"
)
async def _process_split(
doc: Document,
document_id: int,