feat(B09): 제잡비를 경비로 붙임 + ㉥ 물빼기 파이프 가드 + 성분 빠짐 차단 확대
제잡비 (품셈 13-6-1·13-7-1 [주]③, 원문은 메인이 찾아 줌) - **노무비 합계 × 비율이 경비(J)로** 붙음. 재료·노무가 아님 - 기본은 **아랫단**(물빼기 파이프 미설치). 「상한」이라 산출근거에 그 사실을 적음 - `PriceDetail.percent_of_labor` 신설 — 밑수가 3분할 전체인 `percent_of_parent` 와 다름. 비율 줄은 참조를 풀기 전에 처리(자기 자신을 가리켜 순환으로 잡혔음) - 표시 합계에도 넣음 — 안 넣으면 화면 합계와 실제 단가가 어긋남 ㉥ 가드 신설 — 제잡비 윗단(파이프 설치)을 쓰면서 물구멍을 자재로 또 실으면 멈춤 (원문이 「상단에는 파이프 설치의 노무비·재료비를 포함한다」고 명시). 짝 시험 4건 — 아랫단·파이프 없음·비슷한 이름은 안 걸림 성분 빠짐 차단 확대 - 시간·대수로 세는 줄(장비 몫)을 못 맞추면 그 공종 금액을 안 만듦. 초본류 시비가 「트럭(2.5t) 2.6시간」을 빼고 33.1원/㎡ 로 서 있었음 - 자재 줄(kg·매)은 이미 알려진 미결이라 막지 않고 드러내기만 함 - 요약의 「의심 저가」에서 막아 둔 공종은 제외 — 새로 살펴야 할 것과 섞이지 않게 뭉친 표 두 곳 수정 - 칸 전체를 한 이름으로 먼저 시도 — 「굴착기 (무한궤도)」가 안 풀려 찰쌓기 13-6-2 의 장비 몫이 빠지고 공종이 막혀 있었음 - ⚠ 라벨 줄 판정에 **순수 숫자 칸** 검사 추가 — 「자재 | 종자 | kg | 0.025」를 라벨 줄로 오해해 씨앗뿜어붙이기 표를 가로챘음(회귀, 시험이 잡음) 결과: 자원 축 354 → 418, 일위대가 176 → 203, 막힌 공종 32 찰쌓기 13-6-2 = 118,794.9원/㎡ (노무 + 굴착기 0.48h + 제잡비 3 %) 검증: pytest 190 통과(신규 6) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,23 @@ def build_unit_prices(axis: AxisResult | None = None) -> UnitPriceBuild:
|
||||
share = _share_of(row)
|
||||
build.book.add_detail(PriceDetail(title_code, ref, row.amount * share))
|
||||
|
||||
# 제잡비 — **노무비 합계의 %가 경비로** 붙는다(품셈 13-6-1 [주]③).
|
||||
# ⚠ 기본은 **아랫단**(물빼기 파이프 미설치)이다. 윗단을 쓰면 파이프를 따로 세면
|
||||
# 안 되므로(㉥ 가드), 그 선택은 설계 조건이 들어올 때 한다.
|
||||
# ⚠ **「상한」이다** — 곱한 값 이하로 계상하는 값이라 산출근거에 그 사실을 적는다.
|
||||
ratio = axis.overhead_ratio.get(work_item_code)
|
||||
if ratio is not None and not variant_key.startswith("__"):
|
||||
lower = ratio[1]
|
||||
build.book.add_detail(
|
||||
PriceDetail(
|
||||
title_code,
|
||||
title_code,
|
||||
_ZERO,
|
||||
note=f"제잡비 노무비의 {lower}% (상한, 물빼기 파이프 미설치 기준)",
|
||||
percent_of_labor=lower,
|
||||
)
|
||||
)
|
||||
|
||||
# 장비 몫은 자원 수량이 아니라 **시공능력 공식**으로 온다 (품셈 8-1-4).
|
||||
machine_share = (
|
||||
_ZERO if variant else _attach_machine_share(build, master, work_item_code, title_code)
|
||||
@@ -504,10 +521,13 @@ def build_summary(build: UnitPriceBuild) -> dict:
|
||||
"median": _money_text(totals[len(totals) // 2]),
|
||||
"max": _money_text(totals[-1]),
|
||||
}
|
||||
# ⚠ **막아 둔 공종은 여기 안 센다** — 「성분이 빠져 싸다」를 이미 아는 값이라
|
||||
# 목록에 남으면 새로 살펴야 할 것과 섞인다. 막힌 것은 `partial_ratio` 로 따로 센다.
|
||||
low = [
|
||||
{"code": code, "name": title.name, "total": _money_text(money)}
|
||||
for code, title in build.book.titles.items()
|
||||
if title.kind is PriceKind.UNIT_PRICE
|
||||
and code[2:].split("#")[0] not in build.partial_ratio
|
||||
and (money := build.book.resolve(code).total) < SUSPICIOUSLY_LOW_KRW
|
||||
]
|
||||
|
||||
@@ -525,6 +545,8 @@ def build_summary(build: UnitPriceBuild) -> dict:
|
||||
"unit_price_totals": stats,
|
||||
# 값이 서기는 했는데 **크기가 이상한** 것 — 성분이 빠졌을 가능성이 크다.
|
||||
"suspiciously_low": low,
|
||||
# 성분이 빠져 **금액을 안 만드는** 공종 — 화면이 사유째 보인다.
|
||||
"blocked_items": len(build.partial_ratio),
|
||||
# 기준 단위가 없는 채로 큰 값 — 값이 틀린 게 아니라 **기준을 모르는 것**이다.
|
||||
"unknown_basis_high": high,
|
||||
"unknown_basis": sum(
|
||||
@@ -579,6 +601,29 @@ def detail_of(build: UnitPriceBuild, code: str) -> dict:
|
||||
money = build.book.resolve(code)
|
||||
rows: list[dict] = []
|
||||
for detail in build.book.details.get(code, []):
|
||||
if detail.percent_of_labor is not None:
|
||||
# 제잡비 — 지금까지 쌓인 **노무비**의 %가 경비로 붙는다. 표시 합계에도 넣어야
|
||||
# 화면 합계와 실제 단가가 어긋나지 않는다.
|
||||
labor_so_far = sum((Decimal(str(r["labor"])) for r in rows), _ZERO)
|
||||
amount = labor_so_far * detail.percent_of_labor / Decimal(100)
|
||||
rows.append(
|
||||
{
|
||||
"code": detail.ref_code,
|
||||
"name": "제잡비",
|
||||
"spec": f"노무비의 {detail.percent_of_labor}%",
|
||||
"unit": "%",
|
||||
"quantity": str(detail.percent_of_labor),
|
||||
"material": "0",
|
||||
"labor": "0",
|
||||
"expense": str(amount),
|
||||
"total": _money_text(amount),
|
||||
"source": "품셈 [주]",
|
||||
"drillable": False,
|
||||
"note": detail.note,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
child = build.book.title(detail.ref_code)
|
||||
unit_money = build.book.resolve(detail.ref_code)
|
||||
line = unit_money.scaled(detail.quantity)
|
||||
|
||||
Reference in New Issue
Block a user