Files
Aislo/resources/tester/test_m02_hopyo_basis.py
T
eomsangdonandClaude Opus 5.5 193e03927b feat(M02 일위대가): 338-2b STmate 꼴 읽기 · 거창 흩어진 칸 이음 · 이음 넓힘 · 노임 갈래 · 문서 보안 400 · STmate 격자
- 새 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
2026-10-15 23:28:57 +09:00

172 lines
7.7 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.
"""일위대가 실무자료 단가산출 근거 맞춤(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["글"])