feat(axis): 품셈 판 pum_edition 칸 — 자원·공종 쪽, 판이 다르면 값을 안 씀
공종 마스터·자원 축 줄·B08 인계 줄마다 공종 코드가 본 판을 실음(명세 17장 규칙 3). 판이 다른 인계 줄은 내역서에서 「확인 대기」로 빠지고, 코드에 박힌 절 번호(합산 단계·표 형태 판정·가드 목록)는 마스터 판과 다르면 마스터 빌드·단가 조립을 멈춤(규칙 4). 판 기준은 common_util_pum_edition 한 곳. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -34,6 +34,7 @@ from B09_Estimation.B09_Estimation_Guards import (
|
||||
check_material_surcharge_once,
|
||||
)
|
||||
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
|
||||
from common_util.common_util_pum_edition import edition_waiting_reason
|
||||
from B09_Estimation.B09_Estimation_QuantityDigits import round_quantity
|
||||
from B09_Estimation.B09_Estimation_Rounding import OutputPlace, round_at
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import (
|
||||
@@ -115,6 +116,8 @@ class HandoffWorkItem:
|
||||
#: **근거는 산출근거 칸에 그대로 적는다** — 우리가 다시 지어내지 않는다.
|
||||
spec_class: str = ""
|
||||
spec_class_basis: str = ""
|
||||
#: 이 줄의 공종 코드가 본 품셈 판 — 마스터 판과 다르면 값을 안 씀(명세 17장).
|
||||
pum_edition: str = ""
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
@@ -295,6 +298,7 @@ def parse_handoff(payload: dict[str, Any]) -> tuple[list[HandoffWorkItem], list[
|
||||
variant_value=str(row.get("variant_value") or ""),
|
||||
spec_class=row.get("spec_class") or "",
|
||||
spec_class_basis=row.get("spec_class_basis") or "",
|
||||
pum_edition=str(row.get("pum_edition") or ""),
|
||||
)
|
||||
for row in payload["work_items"]
|
||||
]
|
||||
@@ -381,7 +385,8 @@ def build_bill(
|
||||
"""
|
||||
work_items, materials = parse_handoff(payload)
|
||||
unit_prices = build or cached_build()
|
||||
index = _master_index(master or load_work_item_master())
|
||||
master = master or load_work_item_master()
|
||||
index = _master_index(master)
|
||||
result = BillResult()
|
||||
|
||||
# ── 1) 쓰인 공종의 조상만 남긴 가지치기 나무 ────────────────────────────────
|
||||
@@ -414,6 +419,19 @@ def build_bill(
|
||||
# 구조물도 호표(PLAN 6장 ②) — 공종 마스터에 없는 자체 확장 코드라 나무에 안 끼움.
|
||||
templated.append(item)
|
||||
continue
|
||||
# 품셈 판이 다른 줄 — 절 번호가 같아도 같은 공종이라는 보장이 없어 값을 안 씀(명세 17장 규칙 4).
|
||||
waiting = edition_waiting_reason(item.pum_edition, str(master.get("effective_date", "")))
|
||||
if waiting:
|
||||
result.missing.append(
|
||||
{
|
||||
"name": item.display_name,
|
||||
"unit": item.unit,
|
||||
"quantity": str(item.quantity),
|
||||
"reason": waiting,
|
||||
"blocked_kind": "edition_waiting",
|
||||
}
|
||||
)
|
||||
continue
|
||||
if (item.composite_parts or item.composite_not_ready) and not item.work_item_code:
|
||||
# 묶음 줄 — 품셈에 그 공종이 없어 **조각을 합쳐** 한 줄로 세운다
|
||||
# (옹벽 = 타설 + 거푸집 + 철근 + 잡석). 「코드 없음」으로 세면 안 된다.
|
||||
|
||||
@@ -240,6 +240,8 @@ class ResourceRow:
|
||||
#: ⚠ **이 값을 안 보면 단가가 조용히 틀린다** — 인력 몫 원단위를 전량에 곱하게 된다
|
||||
#: (2026-09-08 실측: 측구터파기 39,575.6원/㎥ 이 인력 10 % 몫만이었다).
|
||||
group_ratio_pct: Decimal | None = None
|
||||
#: 이 줄을 읽어 낸 품셈 판 — `pum_table_id`·`work_item_code` 가 판에 묶임(명세 17장).
|
||||
pum_edition: str = ""
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -258,6 +260,7 @@ class ResourceRow:
|
||||
None if self.alternative_amount is None else str(self.alternative_amount)
|
||||
),
|
||||
"group_ratio_pct": None if self.group_ratio_pct is None else str(self.group_ratio_pct),
|
||||
"pum_edition": self.pum_edition,
|
||||
}
|
||||
|
||||
|
||||
@@ -666,6 +669,8 @@ def build_resource_axis(master: dict[str, Any], catalog: ResourceCatalog) -> Axi
|
||||
for node in master.get("work_items", []):
|
||||
for table in node.get("tables", []):
|
||||
match_table(node, table, catalog, result)
|
||||
for row in result.rows:
|
||||
row.pum_edition = edition
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -583,6 +583,12 @@ def build_unit_prices(
|
||||
)
|
||||
|
||||
master = load_work_item_master()
|
||||
# 합산 단계·표 읽기 선언·가드 목록이 절 번호로 박혀 판에 묶임 — 판이 다르면 조립 안 함(명세 17장).
|
||||
from common_util.common_util_pum_edition import require_code_edition
|
||||
|
||||
require_code_edition(
|
||||
str(master.get("effective_date", "")), "B09 단가 조립 — 코드에 박힌 절 번호"
|
||||
)
|
||||
if factor_choices is None:
|
||||
factor_choices = chosen_values(scan_range_factors(master))
|
||||
# 「…와 동일」 참조로 이어 온 계수 — **사용자가 고른 값이 있으면 그것이 이긴다.**
|
||||
|
||||
Reference in New Issue
Block a user