"""일위대가 실무자료 단가산출 근거 맞춤(PLAN 330 · `M02_Hopyo_Basis` · 맞춤 후보 `근거`). 블록 북부청 95 · 사방 67 · 단산93 근거 [건설 3-2-1] · [특별인부] · 후보 93 → GC000083 · 21 → GC000198 · 64 → GC000143 위 둘 · 49 → GC000085 · 잔토처리에 철골재 철거 없음 · 근거 없는 줄 그대로 · 상태 「후보」(자동 확정 없음) · 블록 글 HP(338-2a · 268 · 139 이음 그대로) · `clause` 윗마디 0.9 · 조항 오타 13-114 · 거창 꼴 흩어진 칸 이어 붙임(단산 줄 21 · 출처 17 · 직종 21 · 338-2b) · master_data 그대로. """ import pytest from M02_MasterTemplete import M02_Hopyo_Basis as hb from M02_MasterTemplete import M02_Hopyo_Excel as hx from M02_MasterTemplete import M02_Hopyo_Match as hm from resources.tester import test_m02_hopyo_excel as base from resources.tester.test_m02_hopyo_excel import ( NORTH, SABANG, URL, _post, master_hash, ) client = base.client # 공용 고정물(임시 층) @pytest.fixture(scope="module") def north() -> dict: return hx.read_book(NORTH.name, NORTH.read_bytes()) def _line(book: dict, no: int) -> dict: """단산 no 를 가리키는 첫 줄.""" return next(x for h in book["호표"] for x in h["줄"] if x["참조"] == {"종류": "단산", "번": no}) def test_블록_수_와_근거(north): assert len(north["단산"]) == 95 assert [b["번"] for b in north["단산"]] == list(range(1, 96)) sabang = hx.read_book(SABANG.name, SABANG.read_bytes()) assert len(sabang["단산"]) == 67 assert _line(north, 93)["근거"] == { "출처": [["건설품셈", "3-2-1"]], "직종": ["특별인부"], "행": [4229, 4244], } assert _line(north, 21)["근거"]["직종"] == ["콘크리트공", "보통인부"] # 「보 통 인부」 빈칸 뺌 assert _line(north, 64)["근거"]["출처"][0] == ["건설품셈", "4-1-1"] lines = [x for h in north["호표"] for x in h["줄"] if x["갈래"] == "단가산출"] assert all("근거" in x for x in lines) # 268 줄 모두 블록 있음 def test_출처_글(): assert hb.sources("[건설공사표준품셈 3-2-1 인력굴착]") == [["건설품셈", "3-2-1"]] assert hb.sources("건설표줌품셈(3-1-3) 자갈") == [["건설품셈", "3-1-3"]] assert hb.sources("[2025년 산림사업표준품셈 9-14]") == [["산림품셈", "9-14"]] assert hb.sources("흙운반 [임도표준품셈 4-2]") == [["산림품셈", "4-2"]] assert hb.sources("[건설표준품셈 준용]") == [] def _top(book: dict, no: int) -> list[dict]: got = hm.b_order(_line(book, no)) # 338 — 이름 · 근거 차례(숫자 판정의 덤) assert got["상태"] == "후보" # 자동 확정 없음 return got["후보"] def test_근거_후보(north): before = master_hash() dig = _top(north, 93) assert dig[0]["로직"] == "GC000083" assert dig[0]["근거"] == { "조항": ["건설품셈 공통 3-2-1"], "직종": ["특별인부"], "단위": True, "점수": 1.75, "까닭": "조항 같음 · 건설 공통 · 직종 특별인부 · 단위 맞음", } codes = [c["로직"] for c in dig] assert "GF000101" in codes and "GC000346" not in codes # 이름 1순위 남김 · 낱말 안 겹침 뒤로 assert all("_겹침" not in c and "_낱말" not in c for c in dig) assert _top(north, 21)[0]["로직"] == "GC000198" assert "GC000143" in [c["로직"] for c in _top(north, 64)[:2]] assert _top(north, 49)[0]["로직"] == "GC000085" soil = next(x for x in north["호표"][2]["줄"] if x["이름"].startswith("잔토처리")) names = [c["이름"] for c in hm.b_order(soil)["후보"]] assert not any("철골재" in n for n in names) assert master_hash() == before def test_이름_1순위_자리(): ordered = [{"로직": k} for k in "ABCDEFG"] assert [c["로직"] for c in hb.pick(ordered, "G", 5)] == ["A", "B", "C", "D", "G"] assert [c["로직"] for c in hb.pick(ordered, "B", 5)] == ["A", "B", "C", "D", "E"] assert [c["로직"] for c in hb.pick(ordered, None, 2)] == ["A", "B"] assert hb.pick(ordered, "G", 0) == [] def test_근거_없는_줄_그대로(north): bare = [ x for h in north["호표"] for x in h["줄"] if x["갈래"] == "단가산출" and not x["근거"]["출처"] ] assert bare for line in bare: plain = {k: v for k, v in line.items() if k != "근거"} assert hm.b_order(line) == hm.b_order(plain) def test_길_응답_근거_칸(client): before = master_hash() assert _post(client).status_code == 200 res = client.get(f"{URL.format('personal')}/HP-000001/3") lines = {x["이름"]: x for x in res.json()["호표"]["줄"]} dig = lines["구조물터파기(인력100%)"] assert dig["근거"]["출처"] == [["건설품셈", "3-2-1"]] assert dig["맞춤"]["후보"][0]["근거"]["까닭"].startswith("조항 같음") assert "근거" not in lines["임도표지석(받침,글새김포함)"] # 자재 줄 assert master_hash() == before def test_블록_글_HP(north): """338-2a — 단산마다 출처 · 직종 · 글(머리 뺀 몸 줄 · 200 줄 · 300 자 안) · 꼴.""" assert north["꼴"] == "엑셀꼴" b93 = north["단산"][92] assert b93["번"] == 93 and b93["출처"] == [["건설품셈", "3-2-1"]] assert b93["직종"] == ["특별인부"] assert b93["글"][0] == " [건설공사표준품셈 3-2-1 인력굴착]" assert " 특별인부 : 226,122 × (3.6+2.7+2.2)/3 ÷ 8 = 80,084.8" in b93["글"] assert all(b["글"] and len(b["글"]) <= hb.GLINES for b in north["단산"]) assert all(len(t) <= hb.GCHARS for b in north["단산"] for t in b["글"]) lines = [x for h in north["호표"] for x in h["줄"]] assert sum(1 for x in lines if x.get("근거")) == 268 sabang = hx.read_book(SABANG.name, SABANG.read_bytes()) assert sum(1 for h in sabang["호표"] for x in h["줄"] if x.get("근거")) == 139 assert all(b["글"] for b in sabang["단산"]) def test_조항_윗마디_오타(north): our = {"책": "건설품셈", "구분": "건설품셈 토목", "조항": {"6-2-2", "6-2-3"}} assert hb.clause([["건설품셈", "6-2-2"]], our) == (1.0, ["건설품셈 토목 6-2-2"], "조항 같음") assert hb.clause([["건설품셈", "6-2"]], our) == ( 0.9, ["건설품셈 토목 6-2-2", "건설품셈 토목 6-2-3"], "윗마디 같음", ) assert hb.clause([["건설품셈", "6-2-9"]], our)[0] == 0.5 assert hb.clause([["산림품셈", "6-2"]], our) == (0.0, [], "") # 다른 책 assert hb.clause([["건설품셈", "6-22"]], our)[0] == 0.0 # 6-2 윗마디 아님 assert hb.sources("[산림사업표준품셈 13-114]") == [["산림품셈", "13-11-4"]] assert hb.sources("[산림사업표준품셈 9-14]") == [["산림품셈", "9-14"]] stone = next(b for b in north["단산"] if b["번"] == 63) assert stone["출처"] == [["산림품셈", "13-11-4"]] def test_거창_흩어진_칸(): path = ( base.ROOT / "resources/knowledge/original/실무문서/01.산불거창(거창북상병곡)/01.내역서" / "00.25년_산불_거창(거창,북상,병곡,산8외)내역+수량.xlsx" ) book = hx.read_book(path.name, path.read_bytes()) lines = [x for h in book["호표"] for x in h["줄"] if x["갈래"] == "단가산출"] assert len(lines) == 21 and all(x.get("근거") for x in lines) assert sum(1 for x in lines if x["근거"]["출처"]) == 17 assert sum(1 for x in lines if x["근거"]["직종"]) == 21 first = book["단산"][0] assert first["이름"] == "측구터파기(토사)" and first["직종"] == ["보통인부"] assert any(t.startswith("보통인부 : 172,068") for t in first["글"])