- 새 M02_Hopyo_Stmate — 일위대가표 · 단가산출근거 · 목록표 → 엑셀꼴과 같은 HP(줄 코드 = M~Z 열 코드 꼴 첫 것 · 블록 글 · 자원 품 · 절 · 출처 · 직종 · 호표 근거글) - 여섯 파일 올라감 — 봉화 45 · 영월 23 · 울진대흥 설계 · 착공 34 · 영덕 9 · 울진소광 25 호표 · 노임 이름 어긋남 0 - Basis.read 거창 꼴 — 빈 산출근거 칸은 오른쪽 칸 이어 붙임 · 돈 머리 맨 오른쪽 묶음 · 단산 줄 21 · 출처 17 · 직종 21 - Excel 이음 넓힘(단산 · 산근 · 단가산출 / 일위 · 대가 · 일위대가 N · 참조 · 호표) · 빈 비고 줄 이름으로 이음 · 노임 갈래 · DRM 400 글(장수 · 진안) - Grid STmate 시트 · 숨은 칸 글 뺌 - 시험 stmate · excel · basis · grid · match · build 68 통과 · 원본 엑셀 · master_data sha256 그대로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
172 lines
7.7 KiB
Python
172 lines
7.7 KiB
Python
"""일위대가 실무자료 단가산출 근거 맞춤(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.match_line(_line(book, no), [], hm.units_of(book))
|
||
assert got["상태"] == "후보" and got["들어감"] is False # 자동 확정 없음
|
||
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.match_line(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.match_line(line, [], {}) == hm.match_line(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["글"])
|