fix(b09): 조종원 시간당 노임을 식 좌→우 순차 + 절사로 — 골든셋 시험 첫 벌
- 계수로 미리 접으면 267,360 → 55,699 로 1원 틀림 — 식 1/8*16/12*25/20 을 차례로 풀어 55,700 - 중기 호표 운전원 줄은 시간당 노임 제목(#시간) × 인수로 부름(STmate 운전원 1 × 55,700 모양) - 골든셋 시험 test_b09_golden_stmate — 실무 6건 환율및기초자료 시간당 칸 전수 재현 (다섯 건 원 미만 절사 · 2025 울진 소광 0.1원 미만 절사 — 자리는 설정값) - 28자리 끝 반올림 차례만 다른 합 비교 시험을 허용 오차로 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -23,8 +23,9 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from decimal import Decimal
|
||||
from decimal import ROUND_FLOOR, Decimal
|
||||
from typing import Any
|
||||
|
||||
from B09_Estimation.B09_Estimation_Guards import (
|
||||
@@ -73,6 +74,30 @@ OPERATOR_HOURS_PER_DAY = 8
|
||||
#: ⚠ **기계 감가상각도 여기가 아니다** — 상각비는 손료(경비) 쪽이다(품셈 8-1-5 1호).
|
||||
OPERATOR_ALLOWANCE_FACTOR = (Decimal(16) / Decimal(12)) * (Decimal(25) / Decimal(20))
|
||||
|
||||
#: 조종원 **시간당 노임 식** — 위 8시간·계수를 **식 문자열 한 줄**로(STmate 설정 `RXNM_`).
|
||||
#: ⚠⚠ **왼쪽부터 차례로** 평가하고 **원 미만 절사**(명세 7장 · STmate 18번 §2.1).
|
||||
#: 계수로 미리 접으면 `267,360 × 0.20833…` = 55,699.99… → 절사 55,699 로 **1원 틀림**.
|
||||
#: 차례로 하면 33,420 → 534,720 → 44,560 → 1,114,000 → **55,700**(6건×3직종 18/18 일치).
|
||||
#: ⚠ 위 `OPERATOR_ALLOWANCE_FACTOR` 는 근거 문구·검사용으로만 남김 — 금액에 곱하지 말 것.
|
||||
OPERATOR_WAGE_FORMULA = "1/8*16/12*25/20"
|
||||
|
||||
|
||||
def hourly_operator_wage(
|
||||
daily_wage: Decimal, formula: str = OPERATOR_WAGE_FORMULA, *, digits: int | None = 0
|
||||
) -> Decimal:
|
||||
"""일 노임 → 시간당 노임. 식을 **좌→우 순차**로 풀고 `digits` 자리 아래 절사(`None` 이면 안 자름).
|
||||
|
||||
⚠ 자르는 자리는 **실무 설정**임 — 실무 여섯 건 중 다섯이 원 미만 절사(55,700),
|
||||
2025 울진 소광 한 건이 0.1원 미만 절사(57,077.2). 기본은 다수인 0 자리.
|
||||
"""
|
||||
value = Decimal(daily_wage)
|
||||
for op, number in re.findall(r"([*/]?)\s*(\d+(?:\.\d+)?)", formula):
|
||||
value = value / Decimal(number) if op == "/" else value * Decimal(number)
|
||||
if digits is None:
|
||||
return value
|
||||
return value.quantize(Decimal(1).scaleb(-digits), rounding=ROUND_FLOOR)
|
||||
|
||||
|
||||
#: 화면·표가 그대로 띄우는 노티스 한 줄. 계수를 쓴 자리마다 같은 문구가 서야 한다.
|
||||
OPERATOR_ALLOWANCE_NOTICE = (
|
||||
"조종원 노임에 제수당·상여금·퇴직급여충당금 계수 1.667배(16/12 × 25/20)를 넣었습니다 — "
|
||||
@@ -227,8 +252,6 @@ def hourly_machine_cost(
|
||||
fuel_liters_per_hour: Decimal | None = None,
|
||||
fuel_price_per_liter: Decimal | None = None,
|
||||
operator_daily_wage: Decimal | None = None,
|
||||
operator_hours_per_day: int = OPERATOR_HOURS_PER_DAY,
|
||||
operator_allowance_factor: Decimal = OPERATOR_ALLOWANCE_FACTOR,
|
||||
efficiency_factor: Decimal | None = None,
|
||||
) -> HourlyMachineCost:
|
||||
"""시간당 사용료 한 시간분.
|
||||
@@ -257,15 +280,16 @@ def hourly_machine_cost(
|
||||
# TODO(미결 PLAN 9-6): `mach_operator_map` 0건 — 기종별 운전사 직종이 품셈 본문에만 있다.
|
||||
gaps.append("운전사 직종 매핑 미확보 — 노무비 성분 비어 있음")
|
||||
else:
|
||||
# 나눗수는 8시간 그대로 두고 **계수를 곱한다** — 나눗수를 줄이는 것과 다르다.
|
||||
labor = (operator_daily_wage / Decimal(operator_hours_per_day)) * operator_allowance_factor
|
||||
# ㉣ 보조 — 나눗수를 몰래 줄이면 효율을 사용료에 넣은 것이 된다.
|
||||
# 나눗수는 8시간 그대로 두고 **계수를 곱한다** — 식 문자열을 좌→우로 풂(명세 7장).
|
||||
exact = hourly_operator_wage(operator_daily_wage, digits=None)
|
||||
# ㉣ 보조 — 나눗수를 몰래 줄이면 효율을 사용료에 넣은 것이 된다(자르기 전 값으로 봄).
|
||||
check_operator_hours_basis(
|
||||
labor_per_hour=labor,
|
||||
labor_per_hour=exact,
|
||||
daily_wage=operator_daily_wage,
|
||||
hours_per_day=operator_hours_per_day,
|
||||
allowance_factor=operator_allowance_factor,
|
||||
hours_per_day=OPERATOR_HOURS_PER_DAY,
|
||||
allowance_factor=OPERATOR_ALLOWANCE_FACTOR,
|
||||
)
|
||||
labor = exact.to_integral_value(rounding=ROUND_FLOOR)
|
||||
|
||||
return HourlyMachineCost(
|
||||
machine=machine,
|
||||
|
||||
Reference in New Issue
Block a user