"""품셈 PDF → 절별 md 도구(`_pipeline/pum_md_tool.py`) — 순수 함수 · 산림 차례 개수. 규칙: `resources/knowledge/original/원가계산/_품셈md_작성규칙.md`. """ import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "resources/knowledge/original/_pipeline")) import pum_md_tool as tool # noqa: E402 def test_parse_toc_strips_leaders_and_reads_appendix(): lines = ["제1장", "적용기준 ·········", "1", "1-1", "일반사항 ·····", "1", "부록 ······", "204"] got = tool.parse_toc(lines) assert [(e.ident, e.name, e.page) for e in got] == [ ("제1장", "적용기준", 1), ("1-1", "일반사항", 1), ("부록", "", 204), ] with pytest.raises(ValueError): tool.parse_toc(["엉뚱한 줄", "1"]) def test_key_ignores_spaces_dot_after_number_and_middle_dots(): assert tool.key("9-5. 발파암") == tool.key("9-5 발파암") assert tool.key("[부록1]할인") == tool.key("부록1 할인") assert tool.key("보수·복구") == tool.key("보수․복구") def test_heading_pattern_by_number_only(): sec = tool.heading_pattern("7-1") assert sec.match("7-1. 벌도") assert not sec.match("7-11. 동력상하차기") # 번호가 앞머리만 같음 assert not sec.match("7-1-1. 항") assert tool.heading_pattern("제1장").match("제1장적용기준") assert not tool.heading_pattern("제1장").match("제10장자재") assert tool.heading_pattern("부록1").match("[부록1]할인") assert not tool.heading_pattern("부록1").match("[부록10]x") def test_name_of_and_safe(): assert tool.name_of("9-5", "9-5. 발파암") == "발파암" assert tool.name_of("제2장", "제2장 소요재료 및 기계손료") == "소요재료 및 기계손료" assert tool.safe("목재 운반/적재") == "목재_운반적재" def test_head_round_trip(tmp_path): path = tmp_path / "a.md" path.write_text( tool.head_text({"절": "9-5. 발파암", "상태": "대기"}) + "\n본문\n", encoding="utf-8" ) head = tool.read_head(path) assert list(head) == list(tool.HEAD_KEYS) assert head["절"] == "9-5. 발파암" and head["상태"] == "대기" and head["작성"] == "" def test_forest_toc_counts(): """산림 차례 — 14장 · 189절(PDF 3~12쪽).""" pymupdf = pytest.importorskip("pymupdf") book = tool.BOOKS["산림"] with pymupdf.open(book["pdf"]) as doc: entries = tool.toc_entries(doc, *book["toc"]) assert sum(e.ident.startswith("제") for e in entries) == 14 assert sum(e.ident.count("-") == 1 for e in entries) == 189 def test_forest_headings_follow_page_spacing(): """머리 글자 — 글자층은 빈칸을 잃음(「설계및수량」) · 쪽 그림대로 되살림 · 차례 오타(조림)는 안 따름.""" pymupdf = pytest.importorskip("pymupdf") with pymupdf.open(tool.BOOKS["산림"]["pdf"]) as doc: assert tool.find_heading(doc, "1-2", 14)[2] == "1-2. 설계 및 수량" assert tool.find_heading(doc, "12-3", 172)[2] == "12-3. 철근 현장가공 및 조립" assert tool.find_heading(doc, "제1장", 13)[2] == "제1장 적용기준" page, row, _ = tool.find_heading(doc, "9-6", 136) assert (page, row) == (136, 0) # 쪽 맨 위 — 앞 절은 135 쪽에서 끝남 def test_md_lines_drop_markup_keep_escaped_text(): body = "\n".join( [ "## 9-5. 발파암", "", "(단위: ㎥당)", "", "| 구분 | 수량 |", "|---|---|", "| 보통인부 | 0.50 |", r"| a\|b | 1,000
2 |", "", "> 【예시】 10^-7", r"\- 원문 줄표", "![그림 3](pic/9-05_1.png)", ] ) text = " ".join(line for _, line in tool.md_lines(body)) assert "#" not in text and "---" not in text and "
" not in text and "병합" not in text assert "a|b" in text # 막음표 뗀 원문 글자 assert "- 원문 줄표" in text and "그림" not in text and "pic" not in text assert "10 -7" in text # 첨자 표시 ^ 는 뺌 def test_compare_counts_numbers_and_chars_both_ways(): pdf = [(135, "보통인부 0.50 1,000"), (135, "비고")] same = tool.compare(pdf, tool.md_lines("| 보통인부 | 0.50 | 1,000 |\n비고")) assert tool.verdict(same).startswith("통과") bad = tool.compare(pdf, [(1, "보통인부 0.5 1000 비고 가")]) assert set(bad["num_missing"]) == {"0.50", "1,000"} # 자릿수·쉼표 바꾸면 결손 assert set(bad["num_extra"]) == {"0.5", "1000"} assert bad["char_extra"]["가"] == 1 and bad["char_missing"][","] == 1 assert tool.verdict(bad).startswith("불통") and bad["spots"] def test_fingerprint_ignores_line_endings_and_tool_fields(): text = "---\n상태: 확정\n기계검사: 통과\n확정지문:\n---\n본문\n" same = text.replace("\n", "\r\n").replace("기계검사: 통과", "기계검사: 다시") assert tool.fingerprint(text) == tool.fingerprint(same) assert tool.fingerprint(text) != tool.fingerprint(text.replace("본문", "본문!")) assert "확정지문: abc" in tool.set_head(text, "확정지문", "abc")