feat(B09): ③단가산출·일위대가 조립 + 네 번째 이중계상 규칙(㉣) 가드
**㉣ 작업효율을 두 곳에 넣지 않는다 — 계획서에 없던 네 번째 규칙** - 품셈은 작업효율 `E` 를 **작업량 산정**에 넣음(`Q = n·q·f·E`, 8-1-4). 시간당 사용료는 **1일 8시간**으로 나눔(8-1-6 「관리비는 1일 8시간 초과해도 8시간으로 계산」). **양쪽에 넣으면 같은 효율이 두 번 곱해짐.** - 실무 관측이 효율을 사용료 쪽에 미리 넣어 두어(굴착기 0.7㎥ 노무 55,700/hr = 일당 ÷ 5.09h), 그 값을 보고 나눗수를 고치는 사고가 정확히 이 자리에서 남. - `reject_efficiency_in_hourly_rate()` · `check_operator_hours_basis()` 추가하고 **`hourly_machine_cost()` 안에서 실제로 호출** — 함수만 있고 안 부르면 없는 것과 같음. - PLAN 9-6 의 금지 규칙을 셋 → **넷**으로 늘리고 근거·경위 기록. 기계 사용료 11 % 차이는 **「설명된 차이」**임을 표로 못 박음(맞추려 손대지 말 것). **`B09_Estimation_UnitPrice.py` 신설 — 층을 실제로 쌓음** - `S 취득가 · L 노임 · M 연료 → X 시간당 사용료 → B 일위대가`. **시간당 사용료를 미리 계산해 넣지 않음** — 쌓아야 화면이 「무엇으로 이루어졌나」를 보일 수 있음(8-13 계산 과정을 감추지 않음). - 조종원 수량 = `인/일 ÷ 8` 로 상세에 들어가 ㉣이 데이터로도 드러남. - 자원이 하나도 안 붙은 공종은 **빈 줄로 안 세움** — 0 원 일위대가가 내역에 서면 안 됨. - ㉠ 가드를 `verify_surcharge_once()` 로 실제 호출 — 일위대가 재료비는 **할증 전** 값. 산출 — 제목 **189**(일위대가 64 · 기계 층 3 · 노임·연료), 건너뛴 공종 0. 실측 `X-0201-0070` 굴착기 0.7㎥ = 재 26,130 + 노 35,412 + 경 24,554. 자체검증 — 신규 5건 포함 `pytest tmp/tests/ -q` **116 passed** · ruff 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
"""B09 원가계산 — 이중계상 감시 (거울 테스트 3종).
|
||||
"""B09 원가계산 — 이중계상 감시 (거울 테스트 4종).
|
||||
|
||||
**왜 있는가** — 수량(B08)과 원가(B09)의 담당이 갈렸다가 합쳐졌다가 다시 갈리는 동안,
|
||||
「할증을 두 번 붙인다·20 m 운반을 또 센다·콘크리트를 두 번 쪼갠다」 세 자리가 반복해서
|
||||
위험 항목으로 올라왔다(PLAN 8-7 금지 규칙). 주석은 읽히지 않으므로 **수치로 깨지는
|
||||
검사**를 두어, 규칙을 어기면 계산이 멈추게 한다.
|
||||
|
||||
세 규칙 (원문 = PLAN 8-7 ㉠㉡㉢)
|
||||
네 규칙 (㉠㉡㉢ 원문 = PLAN 8-7 · ㉣ = PLAN 9-6, 2026-09-07 구현 중 발견)
|
||||
㉠ **할증은 자재총괄에서 딱 한 번.** 일위대가 재료비 구성은 **할증 전** 값을 쓴다
|
||||
(품셈 1-3-1 「할증 중복 적용 금지」).
|
||||
㉡ **소운반 20 m 이내(`free_haul`)는 내역 줄에 단가를 붙이지 않는다.** 품에 이미
|
||||
포함돼 있고, 품셈에 20 m 이내 운반 품목 자체가 없다(1-2-7 · 인력운반 10-6).
|
||||
㉢ **콘크리트·모르터는 한 번만 쪼갠다.** 원단위표는 「㎥」까지 내고, 시멘트·모래
|
||||
분해는 일위대가에서 한 번만 한다.
|
||||
㉣ **작업효율 `E` 를 두 곳에 넣지 않는다.** 품셈은 작업량 산정에 넣는다
|
||||
(`Q = n·q·f·E`, 8-1-4). 시간당 사용료는 1일 8시간으로 나눈다(8-1-6).
|
||||
실무 관측이 사용료 쪽에 미리 넣어 두어, 그 값을 보고 나눗수를 고치는 사고가 난다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -109,3 +112,44 @@ def check_mix_decomposed_once(
|
||||
f"{cement_per_m3_kg} kg/㎥ = {expected:,.2f} kg 를 넘습니다 — 배합을 두 번 "
|
||||
"쪼갰습니다. 원단위표는 「콘크리트 ㎥」까지만 냅니다 (PLAN 8-7 ㉢)."
|
||||
)
|
||||
|
||||
|
||||
def reject_efficiency_in_hourly_rate(factor: Decimal | None, *, where: str) -> None:
|
||||
"""㉣ 작업효율 `E`(실작업시간율)가 **시간당 사용료 쪽**에 들어오면 멈춘다.
|
||||
|
||||
품셈은 작업효율을 **작업량 산정**에 넣는다 — `Q = n·q·f·E` (8-1-4).
|
||||
시간당 사용료는 **1일 8시간**으로 나눈다 (8-1-6 「관리비는 1일 8시간 초과해도
|
||||
8시간으로 계산」). **양쪽에 다 넣으면 같은 효율이 두 번 곱해진다.**
|
||||
|
||||
실무 관측(STC 2024)은 효율을 사용료 쪽에 미리 넣어 굴착기 0.7㎥ 노무가
|
||||
55,700 원/hr(일당 ÷ 약 5.09시간)로 나온다. 그 값을 보고 「우리가 틀렸다」며
|
||||
나눗수를 고치는 것이 정확히 이 자리에서 일어난다.
|
||||
"""
|
||||
if factor is None:
|
||||
return
|
||||
if Decimal(0) < Decimal(str(factor)) < Decimal(1):
|
||||
raise DoubleCountError(
|
||||
f"{where}: 작업효율({factor})이 시간당 사용료 계산에 들어왔습니다 — "
|
||||
"효율은 작업량 산정(Q = n·q·f·E)에만 넣습니다. 양쪽에 넣으면 두 번 곱해집니다 "
|
||||
"(PLAN 9-6 ㉣)."
|
||||
)
|
||||
|
||||
|
||||
def check_operator_hours_basis(
|
||||
*,
|
||||
labor_per_hour: Decimal,
|
||||
daily_wage: Decimal,
|
||||
person_days: Decimal = Decimal(1),
|
||||
hours_per_day: int = 8,
|
||||
) -> None:
|
||||
"""㉣ 보조 — 조종원 노임 나눗수가 8시간인가.
|
||||
|
||||
나눗수를 몰래 줄이는 것이 곧 효율을 사용료에 넣는 것과 같다.
|
||||
"""
|
||||
expected = daily_wage * person_days / Decimal(hours_per_day)
|
||||
if abs(labor_per_hour - expected) > _TOLERANCE:
|
||||
raise DoubleCountError(
|
||||
f"조종원 시간당 노무비 {labor_per_hour:,.2f} 가 "
|
||||
f"{daily_wage:,.0f} × {person_days} ÷ {hours_per_day}h = {expected:,.2f} 와 다릅니다 — "
|
||||
"나눗수를 줄이면 작업효율을 사용료에 넣은 것이 됩니다 (PLAN 9-6 ㉣)."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user