- Bind: B2 선택값 줄 = 낱말 같음 고름(「사용횟수」 ≠ 「사용」) · B3 상수 이름 · 덧(90˚) · 단위 맞춤(sec = 초 · km → m) · 경사도 → 경사각도 · B4 식 분자 · 분모 {수|기호} + 대입
- Solve.parts: Formula.works 절 + Clause 후보 {키, 절} → 풂 절(절 표 없는 후보뿐이면 블록 통째)
- 새 test_642_2_bind_solve: 예시 1 · 2 · 3 + 단산46 · 39 입력값 · 출처 꼬리표 · 입력 필요 · 엇갈림 · 지어 넣은 값 0 · 품 같음 · defaults 규칙
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
195 lines
7.7 KiB
Python
195 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""642-2(PLAN 785-2) 옵션값 읽기 · 검산 · 판정 — 북부청 임도 예시 1 · 2 · 3 + 단산46 · 39 기대표
|
||
(설계 642-A 2-10 · 5-3) · 출처 꼬리표 · 지어 넣은 값 0 · 입력 필요 · 품 같음 · master_auto.defaults.
|
||
읽기만(엑셀 · M01) · storage 안 씀."""
|
||
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Bind as hb
|
||
from M02_MasterTemplete import M02_Hopyo_Excel as hx
|
||
from M02_MasterTemplete import M02_Hopyo_Solve as hs
|
||
|
||
NORTH = (
|
||
Path(__file__).resolve().parents[1]
|
||
/ "knowledge/original/실무문서/2026_실무일위대가 모음"
|
||
/ "2026년_북부지방산림청_임도사업_설계내역서(공통단가).xls"
|
||
)
|
||
PATHS = {"B1", "B2", "B3", "B4", "B5", "B6"}
|
||
# 후보 = 785-1 Clause 몫(조항 색인) — 여기선 설계 2-10 후보 열 그대로
|
||
CANDS = {
|
||
10: ["GC001140", "GC001141", "GC001142"],
|
||
24: ["GC000211"],
|
||
46: ["GC000198"],
|
||
47: [{"키": "GC000206", "절": "1"}, {"키": "GC000207", "절": "2"}],
|
||
39: ["GF000224", "GF000225"],
|
||
}
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def master():
|
||
return hb.m01.cm.master()
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def book():
|
||
return hx.read_book(NORTH.name, NORTH.read_bytes())
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def solved(master, book):
|
||
blocks = {b["번"]: b for b in book["단산"]}
|
||
out = {}
|
||
for no, cands in CANDS.items():
|
||
hopyo = next(
|
||
h for h in book["호표"] if any((x.get("참조") or {}).get("번") == no for x in h["줄"])
|
||
)
|
||
line = next(x for x in hopyo["줄"] if (x.get("참조") or {}).get("번") == no)
|
||
same = [x for x in hopyo["줄"] if x is not line]
|
||
blk = blocks[no]
|
||
out[no] = hs.block(master, blk, hs.parts(blk, cands), {"줄": line, "같은줄": same})
|
||
return out
|
||
|
||
|
||
def _cand(solved, no, key, sec=0):
|
||
return next(c for c in solved[no]["절"][sec]["후보"] if c["키"] == key)
|
||
|
||
|
||
def _where(cand):
|
||
return {k: v["길"] for k, v in cand["바인딩"]["출처"].items()}
|
||
|
||
|
||
def test_예시1_단산10_소운반(solved):
|
||
got = solved[10]
|
||
assert got["판정"] == "기초로 풂" and got["고를수"] == 0
|
||
assert got["절"][0]["고름"] == "GC001140"
|
||
c = _cand(solved, 10, "GC001140")
|
||
b = c["바인딩"]
|
||
assert b["입력값"] == {
|
||
"운반무게": 575,
|
||
"회당운반량": 25,
|
||
"운반거리": 6,
|
||
"경사각도": 35,
|
||
"운반회수": 42,
|
||
}
|
||
assert _where(c) == {
|
||
"운반무게": "B4",
|
||
"회당운반량": "B4",
|
||
"운반거리": "B3",
|
||
"경사각도": "B3",
|
||
"운반회수": "B3",
|
||
}
|
||
assert b["입력필요"] == [] and b["엇갈림"] == {}
|
||
assert set(b["안씀"]) == {"재료종별", "재료형상", "재료갈래", "운반로", "실작업시간"}
|
||
(row,) = c["줄"]
|
||
assert row["판정"] == "품 같음"
|
||
assert Decimal(str(row["우리"]["품"])).quantize(Decimal("0.000001")) == Decimal("0.547619")
|
||
assert round(c["금액"]["계"], 1) == 94227.7
|
||
|
||
|
||
def test_예시2_단산47_절_둘(solved):
|
||
got = solved[47]
|
||
one, two = got["절"]
|
||
assert (one["고름"], two["고름"]) == ("GC000206", "GC000207")
|
||
a = _cand(solved, 47, "GC000206", 0)["바인딩"]
|
||
assert a["입력값"] == {"Type": "Type-Ⅰ"} and a["출처"]["Type"]["길"] == "B1"
|
||
c = _cand(solved, 47, "GC000207", 1)
|
||
b = c["바인딩"]
|
||
assert b["입력값"] == {"Type": "Type-Ⅰ", "호": "Ⅰ-1", "소량감률": 50}
|
||
assert _where(c) == {"Type": "B1", "호": "B5", "소량감률": "B5"}
|
||
assert [x["값"] for x in b["엇갈림"]["구분"]] == ["토목", "건축"]
|
||
assert b["기본"]["구분"] == "토목"
|
||
assert [r["판정"] for r in c["줄"]] == ["품 같음"] * 4
|
||
assert got["판정"] == "고를 것" and got["고를수"] == 1 # 구분 하나만 사용자
|
||
|
||
|
||
def test_예시3_단산24_합판거푸집(solved, master, book):
|
||
c = _cand(solved, 24, "GC000211")
|
||
b = c["바인딩"]
|
||
assert b["입력값"] == {
|
||
"사용횟수": "4회",
|
||
"유형": "보통",
|
||
"지붕슬래브": "아님",
|
||
"폼타이": "아님",
|
||
}
|
||
assert _where(c) == {"사용횟수": "B1", "유형": "B2", "지붕슬래브": "B2", "폼타이": "B2"}
|
||
assert b["입력필요"] == ["높이감률", "구멍땜"]
|
||
assert b["기본"]["높이감률"] is None and b["기본"]["구멍땜"] is None # 규칙에 맞는 칸 없음
|
||
assert set(b["안씀"]) == {"폼타이규격", "측압"} and b["비움"] == ["자재지역"]
|
||
assert solved[24]["판정"] == "고를 것" and solved[24]["고를수"] == 2
|
||
# 사용자가 0 넣음 → 노무 줄 품 같음 · 노무비 38,291 = 엑셀
|
||
given = {**b["입력값"], "높이감률": 0, "구멍땜": 0}
|
||
items = [x for x in next(b for b in book["단산"] if b["번"] == 24)["항목"] if not x.get("밖")]
|
||
rows = hs.pair(hb.quantities(master, "GC000211", given), items)
|
||
assert {r["판정"] for r in rows if r["엑셀"]} == {"품 같음"}
|
||
fill = {n: b["기본"][n] for n in b["안씀"]} | {"자재지역": None}
|
||
money = hb.mf.run(master, "GC000211", {**fill, **given})
|
||
assert round(money["노무비"]) == 38291
|
||
|
||
|
||
def test_단산46_레미콘타설(solved):
|
||
got = solved[46]
|
||
c = _cand(solved, 46, "GC000198")
|
||
b = c["바인딩"]
|
||
assert b["입력값"] == {"타설방법": "장비사용타설", "구조물": "철근구조물", "소량감률": 50}
|
||
assert _where(c) == {"타설방법": "B1", "구조물": "B1", "소량감률": "B5"}
|
||
assert b["입력필요"] == ["굴착기"] and b["비움"] == ["지역"] # 굴착기 규격 = B3 · 사용자
|
||
mach = next(r for r in c["줄"] if r["엑셀"] and r["엑셀"]["묶음"] == "X")
|
||
assert mach["판정"] == "품 같음" and mach["표지"] == "기계 갈래 엇갈림"
|
||
assert [d["이름"] for d in got["덧"]] == ["레미콘"] # 별도계상 = 덧 줄
|
||
|
||
|
||
def test_단산39_찰붙임(solved):
|
||
sec = solved[39]["절"][0]
|
||
assert sec["고름"] == "GF000225" and solved[39]["판정"] == "기초로 풂"
|
||
c = _cand(solved, 39, "GF000225")
|
||
assert c["바인딩"]["입력값"] == {
|
||
"뒷길이": 35,
|
||
"굵은골재최대치수": 25,
|
||
"강도": 21,
|
||
"슬럼프": "120",
|
||
"채움콘크리트량": 0.12,
|
||
"보정작업": "해당 없음",
|
||
}
|
||
assert _where(c)["뒷길이"] == "B1" and _where(c)["채움콘크리트량"] == "B6"
|
||
assert _cand(solved, 39, "GF000224")["판정"] == "품 다름"
|
||
|
||
|
||
def test_지어_넣은_값_없음(solved):
|
||
for got in solved.values():
|
||
for sec in got["절"]:
|
||
for c in sec["후보"]:
|
||
b = c["바인딩"]
|
||
assert set(b["입력값"]) == set(b["출처"]) # 출처 없는 값 0
|
||
assert {v["길"] for v in b["출처"].values()} <= PATHS
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"excel, ours, want",
|
||
[
|
||
(0.67, 0.6667, True),
|
||
(1.18, 1.1765, True),
|
||
(3.53, 3.5294, True),
|
||
(0.1111111111, 0.11111, True),
|
||
(0.67, 0.69, False),
|
||
(0.547619, 0.5476190476, True),
|
||
(None, 1, False),
|
||
],
|
||
)
|
||
def test_품_같음(excel, ours, want):
|
||
assert hs.same(excel, ours) is want
|
||
|
||
|
||
def test_defaults_규칙(master):
|
||
ma = hb.ma
|
||
jige = ma.defaults(master, master.logic("GC001140"))
|
||
assert jige["실작업시간"] == 0 and jige["운반무게"] == 0 # 가드
|
||
assert jige["경사각도"] == 0 and jige["운반거리"] is None # 고르기 첫째 · 못 정함
|
||
pour = ma.defaults(master, master.logic("GC000198"))
|
||
assert pour["소량감률"] == 0 and pour["타설방법"] == "인력운반타설" # 0 을 품은 범위
|
||
form = ma.defaults(master, master.logic("GC000211"))
|
||
assert form["높이감률"] is None and form["구멍땜"] is None
|
||
assert 1 not in [v for v in jige.values() if not isinstance(v, str)] # 지어 넣는 1 없음
|