- v16 「덤프 운반은 아직 안 선다」가 지키던 것 = 운반거리(설계 입력) 없이 운반이 조용히 금액을 갖지 않기
→ 「거리가 와야 선다」로: 거리 없는 기본 조립엔 운반(#L…m) 없음 · 거리 무관 적재(#적재)만 섬
- 갈래 제목 ⊂ 마스터 갈래 키: 10-12 는 표가 아니라 본문 식이라 마스터 표에 갈래가 없음
(#L…m 는 프로젝트 거리마다 생겨 마스터에 둘 수도 없음) → 식이 내는 FP-10-12 갈래만 까닭 적고 제외
- 58afe8f2 를 일부 시험만 돌리고 올려 난 빨강 · 전체 1763 통과
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
98 lines
4.9 KiB
Python
98 lines
4.9 KiB
Python
"""검증 리스트 셋을 시험으로 못 박음 — V-11 · V-16 · V-17 (2026-09-09 밤).
|
|
|
|
「사람이 한 번 봤다」로 두면 다음 사람이 또 봐야 한다. **본 것을 시험으로 옮긴다.**
|
|
|
|
V-11 인계 계약 검사가 **새 칸에도** 걸리나 — 계약 시험이 실제로 무는지(가드의 가드)
|
|
V-16 불도저 운반이 **금액으로** 서나 — 갈래(토사·리핑암)마다 단가가 서는지
|
|
V-17 공구손료가 **이중으로 안 붙나** — 제잡비와 밑수가 겹치지 않는지
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B09_Estimation.B09_Estimation_PriceBook import PriceKind # noqa: E402
|
|
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices, cached_build # noqa: E402
|
|
|
|
|
|
# ── V-11 ────────────────────────────────────────────────────────────
|
|
def test_v11_계약_검사는_양쪽으로_문다() -> None:
|
|
"""⚠ 가드의 가드 — 칸을 하나 늘리거나 줄이면 계약 시험이 **반드시** 걸려야 한다.
|
|
|
|
「보내는 쪽은 보냈는데 받는 쪽이 안 읽는」 자리는 양쪽 다 조용해서, 계약 시험이
|
|
무는지 자체를 시험으로 남긴다.
|
|
"""
|
|
from resources.tester.test_b08_handoff_contract import WORK_ITEM_KEYS
|
|
|
|
row = {key: None for key in WORK_ITEM_KEYS}
|
|
|
|
# ① 계약에 없는 칸을 늘리면 잡는다.
|
|
grown = dict(row, 새_칸="값")
|
|
assert set(grown) - WORK_ITEM_KEYS == {"새_칸"}
|
|
|
|
# ② 계약에 있는 칸을 빠뜨려도 잡는다.
|
|
shrunk = dict(row)
|
|
shrunk.pop("quantity")
|
|
assert WORK_ITEM_KEYS - set(shrunk) == {"quantity"}
|
|
|
|
|
|
# ── V-16 ────────────────────────────────────────────────────────────
|
|
def test_v16_불도저_운반이_갈래마다_금액으로_선다() -> None:
|
|
"""⚠ 세워 놓고 한 번도 안 돌려 본 채로 두지 않는다 — 갈래마다 단가가 서야 한다."""
|
|
build = cached_build()
|
|
codes = [code for code in build.book.titles if code.startswith("B-FP-10-11#")]
|
|
assert len(codes) >= 2, codes
|
|
grounds = {code.split("#", 1)[1] for code in codes}
|
|
assert {"토사", "파쇄암"} <= grounds, grounds
|
|
for code in codes:
|
|
assert build.book.resolve(code).total > 0, code
|
|
|
|
|
|
def test_v16_덤프_운반은_거리가_와야_선다() -> None:
|
|
"""⚠ 안 서는 것도 사실대로 못 박는다 — 운반은 운반거리(설계 입력)가 와야 선다.
|
|
|
|
2026-09-14 — 적재(10-12 「1. 적재」)는 거리 무관이라 늘 서고, 운반(`#L…m`)은 거리가 온 조립에만.
|
|
"""
|
|
build = cached_build()
|
|
dump = [code for code in build.book.titles if code.startswith("B-FP-10-12")]
|
|
assert dump and all(code.endswith("#적재") for code in dump)
|
|
|
|
|
|
# ── V-17 ────────────────────────────────────────────────────────────
|
|
def test_v17_공구손료와_제잡비는_밑수가_겹치지_않는다() -> None:
|
|
"""⚠ 둘 다 「%로 붙는 줄」이라 겹치면 조용히 부푼다.
|
|
|
|
공구손료 = **주재료비**의 % → 재료비 / 제잡비 = **노무비**의 % → 경비.
|
|
밑수도 가는 자리도 달라 한 공종에 둘 다 있어도 이중계상이 아니다.
|
|
"""
|
|
build = build_unit_prices(misc_material_percent=Decimal(3))
|
|
both = 0
|
|
for code, rows in build.book.details.items():
|
|
title = build.book.titles.get(code)
|
|
if title is None or title.kind is not PriceKind.UNIT_PRICE:
|
|
continue
|
|
has_overhead = any(row.percent_of_labor is not None for row in rows)
|
|
has_tool = any(row.percent_of_material is not None for row in rows)
|
|
if not (has_overhead and has_tool):
|
|
continue
|
|
both += 1
|
|
before = build.book.resolve(code)
|
|
# 제잡비는 경비로, 공구손료는 재료비로 간다 — 성분이 갈리므로 겹칠 수 없다.
|
|
assert before.expense >= 0 and before.material >= 0
|
|
assert both > 0, "제잡비와 공구손료가 함께 있는 공종이 하나도 없습니다"
|
|
|
|
|
|
def test_v17_지금은_공구손료가_한_원도_안_붙는다() -> None:
|
|
"""⚠ 오늘 상태를 못 박는다 — 칸이 비어 있고, 붙을 주재료비도 아직 0 이다."""
|
|
base = cached_build()
|
|
with_percent = build_unit_prices(misc_material_percent=Decimal(3))
|
|
for code, title in base.book.titles.items():
|
|
if title.kind is not PriceKind.UNIT_PRICE:
|
|
continue
|
|
assert base.book.resolve(code).total == with_percent.book.resolve(code).total, code
|