- [수정] 모달 = Labor_Table(읽기 · 덧칸 표지 · 엑셀 금액) · 줄 = 로직(단가산출 · 일위대가) / 요소(연결됨 · 후보 · 못 이음) · 단가산출 줄 = 가져온 식 SK 수식 탭 모달(Labor_SkEdit) · 서버 hopyo-view(M02_Hopyo_View) - 엑셀 보기 목록 · 칠 = 줄 표지(View.listing) · 단가산출 탭 = 블록 목록 + [가져온 식으로 열기] · [내 단가산출로] - 걷음(요구 703): 판정 체계 785(Judge · Solve · Bind · San_Basis 다섯 · Make.Move · Row 1차 판정 · 화면 Check · CheckRows · Rows · Block · JudgeApi · Picks · Judge.css) · [우리 기초로 바꾸기](to-base) · 근거 기초 제안 · Clause 기초 판정 - 시험 지움: 642_2(Bind · Solve) · 642_3(Row 1차 판정) · 642_5(옮기기 Move) · 642_6(판정 화면) · 786_2(옛 [수정] 모달) · 793_3a(근거 → 기초 제안) — 모두 걷은 기초 판정 길 몫 - 시험 고침: 642_1 · 794_2a2 · 794_2b(근거 제안 부분만 뺌) · 793_2 · 793_3b(기초 바꾸기 기대 → 없음) · 627 · 779_2 · 785_4 · 807_1 · m02_hopyo_excel · grid · stmate(judge 길 → view 길) - 새 시험 test_809_1_import_labor(기초 길 0 · 표지 1,350 줄 · 가져온 식 SK · 모달 = Labor_Table · 금액 = 엑셀) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
4.3 KiB
Python
120 lines
4.3 KiB
Python
"""785-1(642-1) 지킴 — 블록 글 → 절 나무 · 상수 · 대입식 · 절 몫(`M02_Hopyo_Formula`) · 북부청 견본 엑셀.
|
||
조항 색인 → 기초 후보(`Clause.basis` · `index`) 시험은 809-1 에서 지움(경쟁사 로직 인정 · 요구 703).
|
||
읽기만 · 서버 없음.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import warnings
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Excel as hx # noqa: E402
|
||
from M02_MasterTemplete import M02_Hopyo_Formula as fm # noqa: E402
|
||
|
||
warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl")
|
||
XLS = (
|
||
ROOT
|
||
/ "resources/knowledge/original/실무문서/2026_실무일위대가 모음"
|
||
/ "2026년_북부지방산림청_임도사업_설계내역서(공통단가).xls"
|
||
)
|
||
|
||
|
||
@lru_cache(maxsize=1)
|
||
def blocks() -> dict[int, dict]:
|
||
if not XLS.exists():
|
||
pytest.skip("견본 엑셀 없음")
|
||
book = hx.read_book(XLS.name, XLS.read_bytes())
|
||
return {b["번"]: b for b in book["단산"]}
|
||
|
||
|
||
# ── 절 나무 ───────────────────────────────────────────────────────────
|
||
def _works(no: int) -> list[str]:
|
||
nodes = blocks()[no]["절"]
|
||
ids = {id(w) for w in fm.works(nodes)}
|
||
return [p for p, one in _paths(nodes) if id(one) in ids]
|
||
|
||
|
||
def _paths(nodes: list[dict], up: str = "") -> list[tuple[str, dict]]:
|
||
out = []
|
||
for one in nodes:
|
||
path = f"{up}.{one['번']}" if up else one["번"]
|
||
out += [(path, one), *_paths(one["아래"], path)]
|
||
return out
|
||
|
||
|
||
def test_절_나무():
|
||
assert _works(47) == ["1", "2"]
|
||
assert _works(6) == ["1.가", "1.나", "1.다", "3"]
|
||
assert [n["번"] for n in blocks()[6]["절"]] == ["1", "3", "4"] # 「2.」 빠짐 그대로
|
||
assert _works(24) == [] # 재료비 · 인건비 · 합계뿐 = 블록 통째
|
||
b = blocks()[47]
|
||
assert len(b["글행"]) == len(b["글"])
|
||
one = fm.find(b["절"], "1")
|
||
assert one["소계"] == {"노무비": 217540.1, "재료비": 19578.6, "경비": None, "계": 237118.7}
|
||
lo, hi = b["행"]
|
||
assert all(lo < r <= hi for r in one["행"])
|
||
|
||
|
||
# ── 상수 · 대입식 · 몫 ─────────────────────────────────────────────────
|
||
def _consts(no: int, path: str) -> dict[str, dict]:
|
||
return {c["기호"]: c for c in fm.find(blocks()[no]["절"], path)["상수"]}
|
||
|
||
|
||
def test_상수():
|
||
c = _consts(10, "1")
|
||
assert (c["L"]["값"], c["L"]["단위"]) == (6, "m")
|
||
assert (c["경사도"]["값"], c["경사도"]["단위"]) == (35, "°")
|
||
assert (c["n"]["값"], c["n"]["이름"]) == (42, "운반회수")
|
||
c = _consts(6, "1.나")
|
||
assert {k: c[k]["값"] for k in ("q", "K", "f", "E", "Cm")} == {
|
||
"q": 0.6,
|
||
"K": 0.55,
|
||
"f": 1,
|
||
"E": 0.35,
|
||
"Cm": 18,
|
||
}
|
||
assert (c["q"]["단위"], c["Cm"]["단위"], c["Cm"]["덧"]) == ("㎥", "sec", "90˚")
|
||
assert isinstance(c["q"]["행"], int)
|
||
|
||
|
||
def test_대입식():
|
||
p = next(f for f in fm.find(blocks()[10]["절"], "1")["식"] if f["기호"] == "P")
|
||
assert p["꼴"] == "곱" and p["값"] == 0.547619
|
||
assert [(f["수"], f["단위"]) for f in p["분자"]] == [
|
||
(1, "㎡"),
|
||
(575, "㎏/㎡"),
|
||
(1, "회"),
|
||
(1, "일"),
|
||
]
|
||
assert p["분모"] == [{"수": 25, "단위": "㎏"}, {"기호": "n", "단위": ""}]
|
||
assert p["대입"] == {"n": 42}
|
||
|
||
|
||
def test_절_몫():
|
||
nodes = blocks()[6]["절"]
|
||
share = fm.find(nodes, "1.나")["몫"]
|
||
assert share["글"] == "× 575 / 1750 × 90%"
|
||
assert share["값"] == pytest.approx(575 / 1750 * 0.9, abs=1e-6)
|
||
assert fm.find(nodes, "1.가")["몫"]["값"] == 0.1
|
||
assert fm.find(nodes, "3")["몫"]["값"] == pytest.approx(575 / 1750, abs=1e-6)
|
||
assert fm.find(blocks()[47]["절"], "1")["몫"] is None
|
||
|
||
|
||
def test_인자_괄호():
|
||
got_ = fm.factors("Cm × n / (60 × Es)")
|
||
assert [(w, f.get("기호") or f.get("수")) for w, f in got_] == [
|
||
("곱", "Cm"),
|
||
("곱", "n"),
|
||
("나눔", 60),
|
||
("나눔", "Es"),
|
||
]
|
||
assert fm.factors("t1 + t2") is None and fm.factors("1 2") is None
|