Files
Aislo/resources/tester/test_build_pum.py
T
eomsangdonandClaude Opus 5 1614c545bf feat(data): 원천 벌 추출기 — 품셈 md → pum_forest·pum_const 를 줄 번호에 안 기대고 다시 뽑음
브레인 일감(막힌 자리). 코덱스가 md 에 줄을 보태자 원천 벌의 옛 줄 번호가 밀려 산림 밑수가 238 → 105 로
떨어졌고, 머리글로 다시 맞추니 12장 같은 머리글 이웃 표를 집어 직접공사비가 움직였음. 추출기가 저장소에 없었음.

- `resources/data_cost_input_value/_build_pum.py` — md `|` 표를 칸 글자 그대로(2단 머리 안 펼침)
  · 표 번호·절은 옛 벌에서 이어받음 — ① 글자 같은 표를 차례 지키며 맞댐(이웃으로 못 건너뜀)
    ② 맞은 표 사이 수가 같으면 같은 차례끼리(글자만 고쳐진 표) ③ 다르면 그 안 머리 같은 표끼리
  · 새 표는 가장 큰 번호 다음 · 사라진 표는 번호 버리고 알림 · 줄 번호·sha256 은 새 md 에서
  · 옛 md(177d8844)에 돌리면 산림 476 · 건설 2192 표가 원천 벌과 글자까지 같음(표 번호·절·줄·칸)
- 다시 뽑음 — 산림: 줄만 밀린 351 · 글자 바뀐 표 0 / 건설: 줄만 밀린 199 · 새 표 10(C2193~C2202 ·
  코덱스가 뭉친 글을 표로 되살린 제8장 손료표 · 뒤 번호 안 밀림)
-  금액 불변 — 다시 뽑고 공종 마스터를 다시 지어 원가계산서를 잼: 직접공사비 5,983,724 → 5,983,724 ·
  총원가 9,203,637 · 합계 10,124,000 그대로(원가계산서 열세 칸 전부 같음 · `test_work_item_key_gate`) · 열쇠 새 발급 0
  · 이 커밋만으로는 원문 맞대기 둘이 빨강(마스터 줄 번호가 옛 md 기준 — 받기 전부터 빨강) · 마스터 다시 지으면 초록
  · ⚠ 마스터 다시 짓기는 데스크탑 서브 몫이라 커밋에 안 넣음 — 지으면 밑수 238 → 242 · 미확보 92 → 88
    (코덱스가 되살린 밑수 줄: 5-19-1 ㎡ · 5-27 10㎡ · 5-28-1 100㎡ · 9-21 100㎡) · 지문 시험은 그때 갱신
- `test_build_pum.py` — 원천 벌이 md 와 같음 · 줄 37 보태도 번호·절 그대로 · 표가 끼어도 뒤 번호 안 밀림 ·
  12-17-1 같은 머리글 표 가운데를 고쳐도 제 번호 · 빠진 표는 알림 · 2단 머리 안 펼침
- 원문 맞대기 알려진 목록을 마스터 다시 지은 뒤 모습에 맞춤 —
  ⚠ 5-26-1·2·3 코덱스 복원 「(인/992㎡당)」 ↔ 마스터 사람 판정 100㎡ 어긋남(사람 판단 필요) ·
  5-28-2 「(일 당)」 은 작업조 표라 마스터가 일부러 비움

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-18 02:15:05 +09:00

110 lines
5.6 KiB
Python

"""원천 벌 뽑기(`_build_pum.py`) 시험 — 2026-09-18 브레인 일감(랩탑 메인).
못박는 것
- 원천 벌이 **지금 md 와 같음** — md 가 고쳐졌는데 안 뽑았으면 빨강(뽑기를 돌리라는 뜻).
- ⚠ **줄이 밀려도 표 번호·절이 그대로** — 이번 막힘(밑수 238 → 105)의 원인 자리.
- 표가 끼어들어도 **뒤 번호가 안 밀림** · 글자만 고쳐진 표는 **제 번호** · 사라진 표는 **알림**.
- ⚠ 머리글이 같은 이웃 표(12-17-1 펌프카 타설 일곱 표)로 **건너뛰지 않음** — 서브가 겪은 사고.
금액 불변 — 뽑은 뒤 공종 마스터를 다시 짓고 `test_work_item_key_gate.py` 원가계산서 시험
(직접공사비 5,983,724)이 잼. 이 파일은 그 앞 단계(원천 벌 표 번호·절·칸 글자)가 안 흔들림을 잼.
"""
from __future__ import annotations
import copy
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "resources" / "data_cost_input_value"))
import _build_pum as build # noqa: E402
FOREST = build.CATALOGS["pum_forest"]
@pytest.fixture(scope="module")
def forest() -> dict:
"""지금 md 에서 뽑은 산림 벌 — 아래 시험은 이것에 줄을 보태고·표를 끼우고·고치고·뺌."""
committed = json.loads(FOREST.read_text(encoding="utf-8"))
return build.extract(committed, build._read, "pum_forest")[0]
def _lines_of(doc: dict) -> list[str]:
return (ROOT / doc["sources"][0]["path"]).read_text(encoding="utf-8").splitlines()
def _reader(lines: list[str]):
return lambda path: "\n".join(lines).encode("utf-8")
def _tables(doc: dict) -> list[dict]:
return doc["variables"]["pum"]["tables"]
@pytest.mark.parametrize("dataset", list(build.CATALOGS))
def test_원천_벌이_지금_md_와_같음(dataset: str) -> None:
"""md 가 고쳐졌으면 `_build_pum.py` 를 돌릴 것. sha 는 줄끝(CRLF) 따라 PC 마다 갈려 안 봄."""
old = json.loads(build.CATALOGS[dataset].read_text(encoding="utf-8"))
doc, report = build.extract(old, build._read, dataset)
stale = {k: v for k, v in report.items() if k != "same" and v}
assert _tables(doc) == _tables(old), (
f"{dataset} 원천 벌이 md 와 다름 — _build_pum.py 돌릴 것: {stale}"
)
def test_줄이_밀려도_표_번호와_절이_그대로(forest: dict) -> None:
lines = _lines_of(forest)
at = next(i for i, x in enumerate(lines) if x.startswith("### 5-1."))
shifted = lines[:at] + ["보탠 줄"] * 37 + lines[at:]
doc, report = build.extract(forest, _reader(shifted), "pum_forest")
before, after = _tables(forest), _tables(doc)
assert [(t["table_id"], t["section"]) for t in after] == [(t["table_id"], t["section"]) for t in before] # fmt: skip
assert [t["line"] - b["line"] for t, b in zip(after, before)] == [37 if b["line"] > at else 0 for b in before] # fmt: skip
assert report["changed"] == report["new"] == report["removed"] == []
def test_표가_끼어들어도_뒤_번호가_안_밀리고_새_번호를_받음(forest: dict) -> None:
lines = _lines_of(forest)
at = next(i for i, x in enumerate(lines) if x.startswith("### 9-2."))
inserted = lines[: at + 1] + ["", "| 새 | 표 |", "|---|---|", "| 1 | 2 |", ""] + lines[at + 1 :]
doc, report = build.extract(forest, _reader(inserted), "pum_forest")
ids = [t["table_id"] for t in _tables(doc)]
assert [t["table_id"] for t in _tables(forest)] == [i for i in ids if i != "F0477"]
assert [(n["table_id"], n["section"]) for n in report["new"]] == [("F0477", "9-2. 노선 굴진 보조원")] # fmt: skip
assert report["removed"] == [] and report["changed"] == []
def test_글자만_고쳐진_표는_제_번호_머리글_같은_이웃으로_안_건너뜀(forest: dict) -> None:
"""12-17-1 펌프카 타설 — 같은 머리글 표가 줄지어 있음. 가운데 표 한 칸을 고쳐도 번호가 제자리."""
lines = _lines_of(forest)
target = next(t for t in _tables(forest) if t["table_id"] == "F0359")
row = target["line"] + 1 # 첫 몸 줄(0부터 셈: 머리 line-1 · 구분줄 line · 몸 line+1)
edited = copy.copy(lines)
edited[row] = edited[row].replace("|", "| 고침", 1) # 첫 칸 글자만 바꿈
doc, report = build.extract(forest, _reader(edited), "pum_forest")
assert [t["table_id"] for t in _tables(doc)] == [t["table_id"] for t in _tables(forest)]
assert report["changed"] == [{"table_id": "F0359", "line": target["line"]}]
def test_표가_빠지면_번호를_버리고_알림(forest: dict) -> None:
lines = _lines_of(forest)
target = next(t for t in _tables(forest) if t["table_id"] == "F0358")
end = target["line"] + 1 + len(target["rows"]) # 머리·구분줄·몸
removed = lines[: target["line"] - 1] + lines[end:]
doc, report = build.extract(forest, _reader(removed), "pum_forest")
ids = [t["table_id"] for t in _tables(doc)]
assert "F0358" not in ids and ids == [t["table_id"] for t in _tables(forest) if t["table_id"] != "F0358"] # fmt: skip
assert [r["table_id"] for r in report["removed"]] == ["F0358"] and report["new"] == []
def test_2단_머리를_펼치지_않음() -> None:
"""윗머리 줄은 표 머리(`headers`)로, 아랫머리는 첫 몸 줄로 **글자 그대로** 남음."""
lines = ["### 8-3-1 손료", "", "| 분류 번호 | 시 간 당(10-7) | |", "|---|---|---|", "| | 상각비 계수 | 계 |", "| 0101-0007 | 1,811 | 2,000 |"] # fmt: skip
(table,) = build.md_tables(lines)
assert table["headers"] == ["분류 번호", "시 간 당(10-7)", ""]
assert table["rows"][0] == ["", "상각비 계수", "계"]