· 손료 = 자재가 × 표 수량 곧장(패널 0.089 · 내부 패널 0.003매/㎡) + 부자재 24/52/79% · 소모자재 5%(주자재비 = 패널 + 내부 패널) — 봉화 2024 호표 재료비 보통 4,500 · 간단 3,697 과 일치 · 임대료 = 「유로폼 임대료 ㎡당」 한 줄 · 부자재 % 는 원문이 안 적어 안 걺 · 둘 다 · 한쪽만 반쯤 · 아무것도 — 안 세우고 사유(부모 12-38 까지 그대로) · 12-38-1 잔존율은 안 곱함 — 표 수량이 이미 12회·25% 몫으로 보임(우리 역산 · 차 2.5% 는 추정이라 비고에 밝힘) · 이중계상 ③ 가드에서 12-38-02 만 뺌(패널이 자재총괄에 줄 없음) · 유로폼이 자재총괄로 가면 빨강 시험 · 합산형 부모 — 갈래 벌이 같으면 여러 단계 갈래끼리 합산(12-38 = 사용수량 + 설치·해체) · 작업조 표 7 은 밑수 없음 목록에서 뺌(1단위당 = 인원 ÷ 시공량) — 12-38-3 설치·해체가 막혀 있던 자리 · 마스터 재생성 — 12-38 · 12-38-02 갈래 키만 바뀜 기본 조립 제목 금액 변화 0 · 프로젝트 내역 변화 0 · 잴 시험 일곱 빨강→초록 · 끄기 넷 빨강 · 전체 시험 1677 + 318 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
125 lines
5.8 KiB
Python
125 lines
5.8 KiB
Python
"""유로폼 사용수량 12-38-2 — 2026-09-14 브레인 301 판정 ①~⑥ · ③′.
|
||
|
||
① 산림 12-38-2 정본 ② 「임대료 또는 손료」 설계자 선택 · 제안값 없음
|
||
③′ 손료 = 자재가 × 표 수량 곧장(실무 넷 — 봉화 「31,500 × 0.89 / 10」) · 12-38-1 잔존율은 안 곱함
|
||
④ 이중계상 ③ 가드에서 12-38-02 만 뺌 — 패널이 B08 자재총괄에 줄이 없어서. 되살아날 자리를 시험으로 걺
|
||
⑤ 고르는 자리 = 「자재 단가」 탭(넣은 쪽으로 섬 · 둘 다면 안 섬) ⑥ 임대료 갈래엔 % 안 걺
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import Decimal
|
||
|
||
from B09_Estimation.B09_Estimation_UnitPrice import cached_build, detail_of
|
||
|
||
PANEL = "AR-M-10aba455"
|
||
INNER = "AR-M-5b89b294"
|
||
RENT = "AR-M-d00a6b1a"
|
||
LOSS = ((PANEL, "31500", "봉화 2024"), (INNER, "21000", "봉화 2024"))
|
||
|
||
|
||
def _titles(build, code: str) -> list[str]:
|
||
return [t for t in build.book.titles if t == f"B-{code}" or t.startswith(f"B-{code}#")]
|
||
|
||
|
||
def test_아무것도_안_넣으면_안_서고_설계자_선택_사유() -> None:
|
||
build = cached_build()
|
||
assert not _titles(build, "FP-12-38-02")
|
||
reason = build.component_gaps.get("FP-12-38-02", "")
|
||
assert "임대료 또는 손료" in reason, reason
|
||
assert "임대료 또는 손료" in build.component_gaps.get("FP-12-38", "") # 부모까지 까닭이 올라감
|
||
for material in (PANEL, INNER, RENT):
|
||
assert "FP-12-38-02" in build.material_uses.get(material, []), material
|
||
|
||
|
||
def test_손료는_표_수량_곧장_부자재_소모자재는_주자재비의_퍼센트() -> None:
|
||
"""봉화 2024 호표 「유로폼 설치 및 해체」 재료비 보통 4,500 · 간단 3,697 과 같은 값 — 실무 실증."""
|
||
build = cached_build(material_prices=LOSS)
|
||
assert sorted(_titles(build, "FP-12-38-02")) == [
|
||
"B-FP-12-38-02#간단",
|
||
"B-FP-12-38-02#보통",
|
||
"B-FP-12-38-02#복잡",
|
||
]
|
||
detail = detail_of(build, "B-FP-12-38-02#보통")["rows"]
|
||
rows = {r["ref_code"]: Decimal(r["quantity"]) for r in detail if r.get("ref_code")}
|
||
assert rows[PANEL] == Decimal("0.089") and rows[INNER] == Decimal("0.003"), rows
|
||
assert build.book.resolve("B-FP-12-38-02#보통").material == Decimal(4500)
|
||
assert build.book.resolve("B-FP-12-38-02#간단").material == Decimal(3697) # 봉화 간단 3,697
|
||
# 부모 12-38 = 사용수량 + 설치 및 해체 — 두 단계 갈래가 같으면 갈래끼리 합산
|
||
parent = {r["ref_code"] for r in detail_of(build, "B-FP-12-38#보통")["rows"]}
|
||
assert parent == {"B-FP-12-38-02#보통", "B-FP-12-38-03#보통"}, parent
|
||
|
||
|
||
def test_작업조_표는_밑수가_시공량이라_밑수_없음이_아님() -> None:
|
||
"""12-38-3 「(단위: 일 당) 형틀목공 4인 · 시공량 35㎡」 — 1㎡당 = 인원 ÷ 시공량(CrewOutput).
|
||
|
||
밑수 「N㎡당」 문구가 없다고 `basis_missing` 에 들어 설치·해체가 통째로 막혀 있었음(작업조 표 넷).
|
||
"""
|
||
build = cached_build()
|
||
crew = ("FP-05-28-02", "FP-12-07-02", "FP-12-38-03", "FP-13-16-02")
|
||
assert not [code for code in crew if code in build.basis_missing]
|
||
rows = {
|
||
r["name"]: Decimal(r["quantity"]) for r in detail_of(build, "B-FP-12-38-03#보통")["rows"]
|
||
}
|
||
assert rows["형틀목공"] == Decimal(4) / Decimal(35), rows
|
||
|
||
|
||
def test_잔존율은_안_곱하고_역산은_추정이라_밝힘() -> None:
|
||
build = cached_build(material_prices=LOSS)
|
||
note = next(
|
||
r["note"]
|
||
for r in detail_of(build, "B-FP-12-38-02#보통")["rows"]
|
||
if r.get("ref_code") == PANEL
|
||
)
|
||
assert "우리 역산" in note and "추정" in note and "25회" in note, note
|
||
|
||
|
||
def test_임대료는_한_줄만_퍼센트_안_걺() -> None:
|
||
build = cached_build(material_prices=((RENT, "5000", "견적"),))
|
||
assert _titles(build, "FP-12-38-02") == ["B-FP-12-38-02"]
|
||
rows = detail_of(build, "B-FP-12-38-02")["rows"]
|
||
assert [r["ref_code"] for r in rows] == [RENT], rows
|
||
assert build.book.resolve("B-FP-12-38-02").material == Decimal(5000)
|
||
assert "B-FP-12-38#보통" in build.book.titles
|
||
|
||
|
||
def test_둘_다_넣거나_한쪽만_반쯤_넣으면_안_섬() -> None:
|
||
both = cached_build(material_prices=(*LOSS, (RENT, "5000", "견적")))
|
||
assert not _titles(both, "FP-12-38-02")
|
||
assert "둘 다" in both.component_gaps["FP-12-38-02"]
|
||
half = cached_build(material_prices=(LOSS[0],))
|
||
assert not _titles(half, "FP-12-38-02")
|
||
assert "내부 패널" in half.component_gaps["FP-12-38-02"]
|
||
|
||
|
||
def test_유로폼이_자재총괄에_줄로_서면_가드를_되살릴_것() -> None:
|
||
"""④ 12-38-02 를 가드에서 뺀 전제 — 패널·유로폼이 B08 자재총괄(`material`)에 줄이 없음.
|
||
|
||
누가 유로폼을 자재총괄로 보내는 날 할증이 두 번 붙음 → 여기서 빨강 · 가드에 12-38-02 를 되돌릴 것.
|
||
"""
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Base import DESTINATION
|
||
from B09_Estimation.B09_Estimation_Guards import SURCHARGE_INCLUDED_ITEMS
|
||
|
||
assert "FP-12-38-02" not in SURCHARGE_INCLUDED_ITEMS
|
||
words = ("유로폼", "패널")
|
||
assert all(DESTINATION[n] != "material" for n in DESTINATION if any(w in n for w in words))
|
||
root = Path(__file__).resolve().parents[2] / "resources" / "data_structure_unit"
|
||
for path in root.glob("*.json"):
|
||
text = path.read_text(encoding="utf-8")
|
||
|
||
def walk(node):
|
||
if isinstance(node, dict):
|
||
name = str(node.get("name") or "")
|
||
if any(w in name for w in words):
|
||
assert node.get("destination") != "material", (path.name, node)
|
||
for value in node.values():
|
||
walk(value)
|
||
elif isinstance(node, list):
|
||
for value in node:
|
||
walk(value)
|
||
|
||
walk(json.loads(text))
|