Files
Aislo/resources/tester/test_b09_price_layers.py
T
eomsangdonandClaude Opus 5 b876ba9c14 refactor(b09): 단가 층 차례를 X → D → B 로 — 금액 불변, 자원 집계 버림 결함 고침
- 시공능력 Q 로 선 기계 줄(굴착기·도자·직접 작업량·다짐·암 잎)은 D(단가산출)에 달고 B 는 D 를 수량 1 로 부름
- 종전 PriceBasis 의 껍데기 D(D → B) 걷음 — 「단산 N」은 D 를 품은 줄에만(갈래 줄도 실제 단가 코드로 찾음)
- 자원 집계가 B→B·B→D 참조를 끝까지 풀어 기계 사용료에 닿음(종전 조용히 버림) · 모르는 종류는 드러냄
- 조합 16% 바꿔 달기·공종 근거 문구가 D 줄도 봄
- 전후 대조: B·X 420 제목 금액 전부 같음 · 검증 프로젝트 내역 본체 122,924,846 그대로

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-14 00:01:30 +09:00

78 lines
3.6 KiB
Python

"""단가 층 차례 `X → D → B` (2026-09-13, PLAN 6장 · 명세 3장 정정 · 브레인 판정).
겨누는 것
① 시공능력 Q 로 선 기계 줄은 D(단가산출)에 살고 B 는 D 를 수량 1 로 부름 — D 는 X 만 부름
② 층 차례만 바꿈 — 금액 불변(420 제목 전후 대조는 스크립트, 여기선 B = D 합)
③ 자원 집계가 가운데 층(B→B 잎 · B→D)을 **끝까지 풀어** 기계 사용료에 닿음(종전 조용히 버림)
④ 「단산 N」은 D 를 품은 줄에만 — 사람 품만 있는 일위대가엔 산출서가 없음
"""
from __future__ import annotations
from decimal import Decimal
from functools import lru_cache
from B09_Estimation.B09_Estimation_Lists import resource_summary
from B09_Estimation.B09_Estimation_PriceBasis import build_price_basis, price_basis_detail
from B09_Estimation.B09_Estimation_PriceBook import PriceKind
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices
@lru_cache(maxsize=1)
def _build():
return build_unit_prices()
def test_D_는_X_만_부르고_B_가_D_를_수량_1로_부른다() -> None:
book = _build().book
bases = [code for code, t in book.titles.items() if t.kind is PriceKind.PRICE_BASIS]
assert len(bases) >= 40, len(bases) # 2026-09-13 기본 벌 45장
for basis in bases:
refs = {book.titles[d.ref_code].kind for d in book.details[basis]}
assert refs == {PriceKind.MACHINE_HOURLY}, (basis, refs)
parent = f"B-{basis[2:]}"
calls = [d for d in book.details[parent] if d.ref_code == basis]
assert len(calls) == 1 and calls[0].quantity == Decimal(1), basis
# 금액은 B 에 D 한 줄이 그대로 — 사람 품 등 나머지 줄과 더해짐(층만 바뀜).
others = [d for d in book.details[parent] if d.ref_code != basis]
if not others:
assert book.resolve(parent) == book.resolve(basis)
# 굴착기 공식 공종 — Q 줄이 B 에 바로 안 붙음.
assert all(
book.titles[d.ref_code].kind is not PriceKind.MACHINE_HOURLY
for d in book.details["B-FP-09-03-02"]
)
def test_자원_집계가_가운데_층을_끝까지_풀어_기계에_닿는다() -> None:
build = _build()
summary = resource_summary({"B-FP-09-04#연암": Decimal(1)}, build)
machines = {row["code"] for row in summary["groups"]["machine"]}
# 암절취 부모 → 잎 B(암파쇄·집토) → D → X — 종전엔 부모의 B→B 참조를 조용히 버렸음.
assert {"X-0230-0007", "X-0201-0070#조합"} <= machines, machines
assert not summary["missing"]
def test_단산_번호는_D_를_품은_줄에만_붙는다() -> None:
build = _build()
book = build.book
labor_only = next(
code
for code, t in sorted(book.titles.items())
if t.kind is PriceKind.UNIT_PRICE
and book.details.get(code)
and all(book.titles[d.ref_code].kind is PriceKind.LABOR for d in book.details[code])
)
sheet = build_price_basis(["B-FP-09-03-02", labor_only, "B-FP-09-04#연암"], build)
assert [e.code for e in sheet.entries] == [
"D-FP-09-03-02",
"D-FP-09-04-01#연암",
"D-FP-09-04-02",
]
assert sheet.label_for("B-FP-09-03-02") == "단산 1 참조"
assert sheet.label_for(labor_only) == "" # 없는 근거를 가리키지 않음
assert sheet.label_for("B-FP-09-04#연암") == "단산 2·3 참조" # 단계 합산 부모
detail = price_basis_detail("D-FP-09-03-02", build)
assert detail["kind"] == PriceKind.PRICE_BASIS.value
assert detail["rows"] and all(r["ref_code"].startswith("X-") for r in detail["rows"])