Files
Aislo/resources/tester/test_work_item_master_const.py
T

152 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""건설 공종 축(`CW-00001`) 뽑기 — 2026-09-17 브레인 8-2.
못박는 것
① 목차 글줄 가르기 — 쪽 번호가 다음 번호에 붙은 꼴(「131-2-6」 = 13쪽 + 1-2-6) · 부문마다 장 번호 1부터
② 표는 **본문 절 제목**으로 붙음 — 인용(「3-2-4 터파기(기계)’를 참고하여」)은 제목이 아님
③ 목차 오기 없음 — 연혁 표시(「('20년 보완)」)가 붙은 본문 제목이 모두 목차 번호·이름과 같음
(산림에서 목차가 번호를 잘못 적어 표가 딴 줄에 붙은 병 · 건설도 같은 함정)
④ 못 붙인 표는 목록으로 · 열쇠 CW- 안 겹침 · 다시 뽑아도 같은 열쇠
⑤ 산출 파일 = 지금 코드로 뽑은 것(산림도 같이 — 두 뽑기가 표 읽기 한 벌을 나눠 씀 · 금액 불변)
"""
from __future__ import annotations
import json
import re
import pytest
from B08_Quantity import B08_Quantity_Build_WorkItemMaster as forest
from B08_Quantity import B08_Quantity_Build_WorkItemMaster_Const as const
OUT = forest.OUT_DIR
CONST_MASTER = OUT / "const_work_item_master_2026-01-01.json"
@pytest.fixture(scope="module")
def built() -> tuple:
return const.build_const()
@pytest.fixture(scope="module")
def master() -> dict:
return json.loads(CONST_MASTER.read_text(encoding="utf-8"))
def _same_except_time(fresh: dict, saved: dict) -> None:
fresh = json.loads(json.dumps(fresh, ensure_ascii=False))
for doc in (fresh, saved):
doc.pop("generated_at")
doc["stats"].pop("keys_newly_issued") # 첫 뽑기만 발급 · 다시 뽑으면 0
assert fresh == saved
def test_목차_글줄_가르기_쪽_번호가_붙은_꼴() -> None:
text = "제1장적용기준31-1 일반사항····31-1-1 목적····131-2-6 공구 및 경장비····19제2장가설공사352-1 가설물의 한도 ····35"
assert const.toc_entries(text) == [
("1", "1", "적용기준"),
("1", "1-1", "일반사항"),
("1", "1-1-1", "목적"),
("1", "1-2-6", "공구 및 경장비"),
("2", "2", "가설공사"),
("2", "2-1", "가설물의 한도"),
]
assert const.toc_entries("제11장칠공사61111-1 공통사항····611")[1] == ("11", "11-1", "공통사항")
def test_인용은_제목이_아님() -> None:
names = {("공통부문", "3-2-4"): "터파기(기계)", ("공통부문", "3-2-5"): "되메우기"}
chapters = {"2", "3", "4"}
assert (
const.titles_in("3-2-4 터파기(기계)’를 참고하여 적용한다.", "공통부문", chapters, names)
== []
)
assert const.titles_in("…경우3-2-4 터파기(기계)('25년 신설)", "공통부문", chapters, names) == [
"3-2-4"
]
assert const.titles_in("853-2-5 되메우기Q = n·q", "공통부문", chapters, names) == ["3-2-5"]
def test_목차_부문_다섯_장_마흔다섯(built: tuple) -> None:
items = built[0]["work_items"]
roots = [it for it in items if it["parent_code"] is None]
assert [r["name"] for r in roots] == list(const.DIVISIONS)
assert sum(1 for it in items if it["level"] == 2) == 45
codes = [it["work_item_code"] for it in items]
assert len(set(codes)) == len(codes)
def test_표는_다_붙고_못_붙인_표는_목록(built: tuple) -> None:
master = built[0]
stats = master["stats"]
assert stats["tables_total"] == 2192
assert stats["tables_attached"] + len(master["orphan_tables"]) == stats["tables_total"]
assert stats["tables_orphan"] == len(master["orphan_tables"]) == 0
def test_목차_오기_없음_연혁_붙은_본문_제목이_목차와_같음(built: tuple) -> None:
"""본문 제목 번호가 목차에 없거나 이름이 다르면 그 절 표가 **앞 절에 조용히 붙음** — 빨강."""
items = built[0]["work_items"]
names = {(it["division"], it["number"]): it["name"] for it in items if it["level"] > 1}
titled = re.compile(r"(\d+(?:-\d+)+)\s*([^'\[\]()]{1,40}?)\s*\(\s*['‘’]\d\d")
data = json.loads(const.SOURCE.read_text(encoding="utf-8"))
files = sorted({t["source_file"] for t in data["variables"]["pum"]["tables"]})
seen, bad = 0, []
for source_file in files:
division, chapter = const.file_place(source_file)
chapters = {str(chapter - 1), str(chapter), str(chapter + 1)}
for line in (const.ROOT / source_file).read_text(encoding="utf-8").splitlines():
for found in titled.finditer(line):
raw, name = found.group(1), "".join(found.group(2).split())
number = next(
(
raw[cut:]
for cut in range(len(raw.split("-")[0]))
if raw[cut:].split("-")[0] in chapters
),
None,
)
if number is None:
continue
seen += 1
toc = "".join(names.get((division, number), "").split())
if not toc or not (name.startswith(toc) or toc.startswith(name)):
bad.append(
f"{division} {number}{found.group(2)}」 목차 「{names.get((division, number))}」"
)
assert seen > 600
assert not bad, bad
def test_열쇠_CW_안_겹침_다시_뽑아도_같음(built: tuple, master: dict) -> None:
keys = [it["work_item_key"] for it in master["work_items"]]
assert all(re.fullmatch(r"CW-\d{5}", k) for k in keys)
assert len(set(keys)) == len(keys)
assert built[3][1] == [] # 장부가 있으니 새로 안 냄
assert [it["work_item_key"] for it in built[0]["work_items"]] == keys
def test_산출_파일이_지금_코드로_뽑은_것(built: tuple, master: dict) -> None:
_same_except_time(built[0], master)
def test_산림_산출도_그대로_금액_불변() -> None:
"""표 읽기를 떼어내고(`_Table`) 건설과 나눠 써도 산림 산출은 한 글자도 안 바뀜."""
saved = json.loads((OUT / "work_item_master_2026-01-01.json").read_text(encoding="utf-8"))
master, undetermined, basis_missing, _ = forest.build()
_same_except_time(master, saved)
for name, fresh in (("form_undetermined", undetermined), ("basis_missing", basis_missing)):
saved_items = json.loads((OUT / f"{name}_2026-01-01.json").read_text(encoding="utf-8"))[
"items"
]
assert json.loads(json.dumps(fresh, ensure_ascii=False)) == saved_items
def test_산림을_고르는_쪽이_건설_파일을_안_집음() -> None:
"""⚠ `work_item_master_*` 로 끝 파일을 고르는 자리가 있음(B08 밑수 대조 · B09 근거표) —
건설 산출을 `work_item_master_const_…` 로 두면 그쪽이 건설을 집어 밑수 대조가 통째로 빠졌음(2026-09-17)."""
from B08_Quantity.B08_Quantity_Engine_BasisUnit import load_master
assert load_master()["dataset_id"] == "work_item_master_forest"
assert not list(OUT.glob("work_item_master_*const*.json"))