- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)
시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
162 lines
5.9 KiB
Python
162 lines
5.9 KiB
Python
"""785-1(642-1) 지킴 — 출처 풀이 · 조항 색인(`M02_Hopyo_Clause`) · 블록 글 → 절 나무 · 상수 ·
|
||
대입식 · 절 몫(`M02_Hopyo_Formula`) · 북부청 견본 엑셀(설계 642-A 5-3 첫 줄 기대).
|
||
읽기만 · 서버 없음.
|
||
"""
|
||
|
||
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_Clause as clause # noqa: E402
|
||
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["단산"]}
|
||
|
||
|
||
@lru_cache(maxsize=None)
|
||
def got(no: int) -> dict:
|
||
return clause.basis(blocks()[no])
|
||
|
||
|
||
def keys(no: int) -> set[str]:
|
||
return {o["키"] for o in got(no)["후보"]}
|
||
|
||
|
||
# ── 조항 색인 → 후보 ──────────────────────────────────────────────────
|
||
@pytest.mark.parametrize(
|
||
"no, want",
|
||
[
|
||
(10, {"GC001140", "GC001141", "GC001142"}),
|
||
(46, {"GC000198"}),
|
||
(24, {"GC000211"}),
|
||
(47, {"GC000206", "GC000207"}),
|
||
(39, {"GF000224", "GF000225"}),
|
||
],
|
||
)
|
||
def test_후보(no, want):
|
||
assert keys(no) == want
|
||
assert got(no)["표지"] is None
|
||
|
||
|
||
def test_윗마디_절마다():
|
||
"""47 「6-2」 → 절 「1. 현장가공」 GC000206 · 「2. 조립」 GC000207 · 공장가공은 뺌."""
|
||
by = {o["키"]: (o["맞음"], o["절"]) for o in got(47)["후보"]}
|
||
assert by == {"GC000206": ("윗마디", "1"), "GC000207": ("윗마디", "2")}
|
||
assert {o["키"] for o in got(47)["뺌"]} >= {"GC000208"}
|
||
|
||
|
||
@pytest.mark.parametrize("no, mark", [(33, "준용"), (6, "옛 번호"), (3, "번호 없음")])
|
||
def test_표지(no, mark):
|
||
assert got(no)["표지"] == mark
|
||
|
||
|
||
def test_준용_결과단위_거름_절_번호():
|
||
"""33 「산림 12-12 준용」 → GF000177(원/식) 뺌 · 절 4 「표준품셈3-4-8」 → GC000110."""
|
||
r = got(33)
|
||
assert {o["키"]: o["까닭"][:7] for o in r["뺌"]} == {"GF000177": "결과단위 다름"}
|
||
assert [(o["키"], o["절"]) for o in r["후보"]] == [("GC000110", "4")]
|
||
|
||
|
||
def test_색인():
|
||
assert set(clause.index()[("건설품셈 공통", "1-2-7")]) == {"GC001140", "GC001141", "GC001142"}
|
||
assert clause.nature("원/10㎡") == "면적" and clause.nature("원/자재 단위") is None
|
||
|
||
|
||
# ── 절 나무 ───────────────────────────────────────────────────────────
|
||
def _works(no: int) -> list[str]:
|
||
nodes = blocks()[no]["절"]
|
||
ids = {id(w) for w in fm.works(nodes)}
|
||
return [p for p, one in clause._paths(nodes) if id(one) in ids]
|
||
|
||
|
||
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
|