refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)
B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음). 화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음. B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry 로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠. B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져 부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음. B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함. B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""B09 원가계산 — **수량의 소수자리**는 종목마다 다르다 (산림품셈 1-2-2의 1).
|
||||
|
||||
**왜 있는가** — 지금까지 화면이 **모든 수량을 2자리로** 찍고 있었는데, 품셈은 종목별로
|
||||
자리를 따로 정해 두었다. 체적합계·시멘트·철근은 **정수**, 돌쌓기·옹벽·떼는 **1자리**,
|
||||
철강재는 **3자리**다. 2자리 고정은 그 어느 것도 아니다 (2026-09-08 지식DB 대조에서 드러남).
|
||||
|
||||
원문 = resources/knowledge/original/원가계산/산림_표준품셈/첨부/(산림청고시 제2025-82호) 산림사업 표준품셈.md L575~660
|
||||
요약 = resources/knowledge/technical_info/01_임도/04_수량분석정보/수량산출_일반.md §2
|
||||
|
||||
⚠ **수량은 반올림이다** ([주]① 「본 표에 따르며, **반올림**하여 적용한다」).
|
||||
금액이 전부 **버림**인 것과 반대다 — 한 모듈에 섞어 두면 반드시 헷갈리므로
|
||||
금액은 `B09_Estimation_Rounding` 이 따로 맡는다.
|
||||
|
||||
⚠ **항목이 표를 이긴다** ([주]② 「품셈 각 항목에서 제시한 소숫자리가 본 표와 상이할 경우
|
||||
**항목에서 제시하는 소숫자리를 우선**」). 그래서 `override` 를 받는다. 다만 지금 공종
|
||||
마스터에 「이 항목의 소수자리」를 담은 칸이 **아직 없다** — 그 칸이 생기면 여기로 흘리면 된다.
|
||||
|
||||
⚠ **표에 없는 종목은 우리 기본 소수 2자리**(2026-09-14 브레인 판정 — 종전 「손대지 않음」 폐기).
|
||||
수량을 이 자리로 **먼저 확정한 뒤** 금액을 셈 — [주]① 이 시킨 차례이고, 명세의 「Q 를 소수 2자리로
|
||||
먼저 확정한 뒤 나눔」과 같은 원칙(내역 줄 `BillOfQuantities_Rows.settle_quantity`).
|
||||
못 맞춘 이름은 종전대로 로그로 드러냄.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
|
||||
#: (이름에 들어 있는 말, 그 단위) → 소수자리. **원문 표 순서 그대로** 옮겼다.
|
||||
#: ⚠ 값을 고칠 일이 생기면 원문을 먼저 볼 것 — 여기서 고치면 근거가 끊긴다.
|
||||
_DIGITS: tuple[tuple[tuple[str, ...], tuple[str, ...], int], ...] = (
|
||||
# 종목 이름 후보 · 단위 후보 · 소수자리
|
||||
(("공사연장", "노선연장"), ("m",), 0),
|
||||
(("공사폭원", "노폭"), ("m",), 1),
|
||||
(("직공인부",), ("인",), 2),
|
||||
(("체적합계", "토적합계"), ("㎥", "m3"), 0),
|
||||
(("떼", "평떼", "줄떼"), ("㎡", "m2"), 1),
|
||||
(("모래", "자갈", "조약돌"), ("㎥", "m3"), 2),
|
||||
(("견치돌", "깬돌"), ("㎡", "m2"), 1),
|
||||
(("야면석",), ("㎥", "m3"), 1),
|
||||
(("돌쌓기", "돌붙임"), ("㎥", "m3", "㎡", "m2"), 1),
|
||||
(("사석",), ("㎥", "m3"), 1),
|
||||
(("다듬돌", "절석", "판석"), ("개",), 2),
|
||||
(("벽돌", "블록"), ("개",), 0),
|
||||
(("시멘트",), ("kg", "㎏"), 0),
|
||||
(("모르타르", "모르터"), ("kg", "㎏"), 2),
|
||||
(("콘크리트",), ("㎥", "m3"), 2),
|
||||
(("합판",), ("장",), 1),
|
||||
(("말뚝",), ("개",), 0),
|
||||
(("철강재", "강재"), ("kg", "㎏"), 3),
|
||||
(("용접봉",), ("kg", "㎏"), 1),
|
||||
(("철근",), ("kg", "㎏"), 0),
|
||||
# 같은 줄의 단위 환산 — 원문 「철근 kg 정수」 = ton 소수 3자리. STmate 실무 원본 영월·울진
|
||||
# 설계내역서 철근·철근운반 Ton 0.264·0.008 이 그 자리(2026-09-14 골든셋 대조 · 원문 값 아님).
|
||||
(("철근",), ("ton", "톤"), 3),
|
||||
(("볼트", "너트", "꺽쇠"), ("개",), 0),
|
||||
(("철선", "철사"), ("kg", "㎏"), 2),
|
||||
(("못",), ("kg", "㎏"), 2),
|
||||
(("화약",), ("kg", "㎏"), 3),
|
||||
(("뇌관",), ("개",), 0),
|
||||
(("도화선",), ("m",), 1),
|
||||
(("수로연장",), ("m",), 1),
|
||||
(("옹벽",), ("㎡", "m2"), 1),
|
||||
(("도장", "칠하기"), ("㎡", "m2"), 1),
|
||||
(("방수",), ("㎡", "m2"), 1),
|
||||
(("보오링", "보링"), ("m",), 1),
|
||||
)
|
||||
|
||||
#: 위 표에 이름이 안 걸릴 때 **단위만으로** 줄 수 있는 자리 — 품셈 「토적」 줄이 근거다.
|
||||
#: 토적: 높이·너비 m 2 · 단면적 ㎡ 1 · 체적 ㎥ 2.
|
||||
#: ⚠ **면적을 1자리로 내리지 않는다** — 「토적(단면적)」은 횡단면적을 말하는 것이라
|
||||
#: 사면적·거푸집 면적까지 1자리로 자르면 틀린다. 확실한 ㎥ 만 잡는다.
|
||||
_UNIT_ONLY = {"㎥": 2, "m3": 2}
|
||||
#: 표에도 단위에도 안 걸리는 줄의 자리 — 우리 기본(브레인 판정 2026-09-14).
|
||||
DEFAULT_DIGITS = 2
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
#: 못 맞춘 줄을 이미 알린 (이름, 단위) — 내역을 그릴 때마다 같은 로그가 쌓이지 않게.
|
||||
_UNMATCHED_LOGGED: set[tuple[str, str]] = set()
|
||||
|
||||
|
||||
def _tight(text: str) -> str:
|
||||
return "".join(str(text or "").split())
|
||||
|
||||
|
||||
def digits_for(name: str, unit: str, spec: str = "") -> int:
|
||||
"""그 줄의 수량 소수자리. 표에 없으면 단위(㎥ 2) → 그것도 없으면 우리 기본 2.
|
||||
|
||||
⚠ 이름으로 종목을 찾는다 — 품셈 1-2-2 표 자체가 종목 이름 표라 코드로 옮길 대응표가 없음
|
||||
(금액이 아닌 표시 자리). **이름을 못 맞춘 줄은 로그**로 남긴다 — 마스터 갈래 키(1장)가
|
||||
서면 함께 정리함(2026-09-13 브레인 판정: 이름은 그대로 두고 못 맞춘 것만 드러냄).
|
||||
"""
|
||||
haystack = _tight(name) + _tight(spec)
|
||||
# 단위 대소문자는 뜻이 같음(실무 원본 「M3」·「TON」·「Ton」) — 표기만 흡수.
|
||||
unit_tight = _tight(unit).lower()
|
||||
for words, units, digits in _DIGITS:
|
||||
if unit_tight not in units:
|
||||
continue
|
||||
if any(word in haystack for word in words):
|
||||
return digits
|
||||
key = (_tight(name), unit_tight)
|
||||
if key not in _UNMATCHED_LOGGED:
|
||||
_UNMATCHED_LOGGED.add(key)
|
||||
fallback = _UNIT_ONLY.get(unit_tight)
|
||||
logger.info(
|
||||
"B09 수량 자리 — 종목 이름을 못 맞춤: 「%s」 %s → %s",
|
||||
name,
|
||||
unit,
|
||||
"단위만으로 %s자리" % fallback
|
||||
if fallback is not None
|
||||
else "기본 %s자리" % DEFAULT_DIGITS,
|
||||
)
|
||||
return _UNIT_ONLY.get(unit_tight, DEFAULT_DIGITS)
|
||||
|
||||
|
||||
def round_quantity(
|
||||
value: Decimal,
|
||||
name: str,
|
||||
unit: str,
|
||||
spec: str = "",
|
||||
override: int | None = None,
|
||||
) -> tuple[Decimal, int | None]:
|
||||
"""수량을 그 종목의 자리로 **반올림**한다.
|
||||
|
||||
돌려주는 것 — (자른 값, 쓴 자리).
|
||||
`override` 는 품셈 **항목이 따로 제시한 자리**([주]②) — 표보다 우선한다(마스터 칸 대기).
|
||||
"""
|
||||
digits = override if override is not None else digits_for(name, unit, spec)
|
||||
quantum = Decimal(1).scaleb(-digits)
|
||||
return value.quantize(quantum, rounding=ROUND_HALF_UP), digits
|
||||
Reference in New Issue
Block a user