feat(b08): 양식이 안 품은 토공·버림 성분은 전개 값을 남김(㉯) — 남길 목록을 코드에 못박음(터파기·되메우기·잔토처리·버림콘크리트) · 목록 밖은 양식으로 갈음 · 목록 안이라도 양식이 그 줄을 품으면 양식 값 · 구조물도 장에도 남긴 줄을 사유와 함께 보임
936be972 실측: 고정형(봉화 제6호표) 가져오기 → 구조물터파기·되메우기·잔토·버림 타설 전과 같음(전엔 −6,037,712원) · 본체 차이는 미완으로 금액 밖이 된 돌쌓기 2줄 −4,062,480 뿐 · 되돌려 전부 같음 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -153,6 +153,20 @@ def template_sheet(
|
||||
#: 양식이 m당으로 풀리는 단위 — 연장 L=1 로 풀면 곧 단위당 값(모든 줄이 L 에 비례).
|
||||
_PER_LENGTH_UNITS = frozenset({"m"})
|
||||
|
||||
#: ㉯ 양식에 **그 이름 줄이 없으면 전개 값을 남기는** 성분(2026-09-14 브레인 판정 · 목록으로 못박음).
|
||||
#: STmate 호표(고정형)는 토공·버림을 안 품음 — 통째로 갈음하면 구조물터파기 85.25→25㎥(−6,037,712원 실측).
|
||||
#: 목록 밖 성분은 양식으로 갈음 · 목록 안이라도 양식이 그 줄을 품으면 양식 값(겹쳐 세지 않음).
|
||||
KEEP_ENGINE_COMPONENTS = ("터파기", "되메우기", "잔토처리", "버림콘크리트")
|
||||
KEPT_ROW_REASON = "전개 값 그대로 — 양식이 이 줄을 안 품음(토공·버림은 전개가 셈)"
|
||||
|
||||
|
||||
def _kept(rows: list[Any], template_rows: list[dict[str, Any]], name_of: Any) -> list[Any]:
|
||||
"""목록 이름 중 양식에 없는 것만 — 줄(dict)·성분(Component) 둘 다 이름 꺼내는 손잡이로."""
|
||||
names = {str(row.get("name") or "") for row in template_rows}
|
||||
return [
|
||||
row for row in rows if name_of(row) in KEEP_ENGINE_COMPONENTS and name_of(row) not in names
|
||||
]
|
||||
|
||||
|
||||
def _library_rows(
|
||||
body: dict[str, Any], solved: list[dict[str, Any]], billing: float
|
||||
@@ -295,7 +309,9 @@ def replace_with_templates(
|
||||
spec=str(result.get("spec") or ""),
|
||||
)
|
||||
)
|
||||
quantity.components = components
|
||||
quantity.components = components + _kept(
|
||||
quantity.components, body["rows"], lambda component: component.name
|
||||
)
|
||||
# 「양식 있음/없음」을 화면이 가리게 — 조용히 섞이면 왜 값이 다른지 못 찾음.
|
||||
quantity.library_item = str(template.get("name") or quantity.type_id)
|
||||
|
||||
@@ -343,7 +359,23 @@ def apply_templates(
|
||||
continue
|
||||
members = sheet.get("members") or []
|
||||
billing = float(members[0].get("billing_quantity") or 0.0) if members else 0.0
|
||||
sheet["rows"] = _library_rows(body, solved[index], billing)
|
||||
library_rows = _library_rows(body, solved[index], billing)
|
||||
start = max((row["no"] for row in library_rows), default=0)
|
||||
kept = _kept(sheet.get("rows") or [], body["rows"], lambda row: row.get("name"))
|
||||
sheet["rows"] = library_rows + [
|
||||
{
|
||||
**row,
|
||||
"no": start + offset,
|
||||
"formula": "",
|
||||
"default_formula": "",
|
||||
"rounding": None,
|
||||
"default_rounding": None,
|
||||
"skipped": False,
|
||||
"reason": KEPT_ROW_REASON,
|
||||
"error": "",
|
||||
}
|
||||
for offset, row in enumerate(kept, start=1)
|
||||
]
|
||||
sheet["unpriced_rows"] = [
|
||||
row["name"]
|
||||
for row in sheet["rows"]
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""㉯ 양식이 안 품은 토공·버림 성분은 전개 값을 남김 (2026-09-14 브레인 판정 · 목록으로 못박음).
|
||||
|
||||
실측(936be972): STmate 호표(고정형)를 가져오니 전개 성분이 통째로 갈음돼 구조물터파기 85.25→25㎥
|
||||
(−6,037,712원) · 되메우기·잔토·버림 타설도 줄었음. STmate 호표는 원래 토공을 안 품음(토공 따로 셈).
|
||||
규칙 — `KEEP_ENGINE_COMPONENTS` 목록에 있는 이름은 **양식에 그 이름 줄이 없을 때** 전개 값을 보존,
|
||||
목록에 없는 성분은 양식으로 갈음. 양식형(돌쌓기 찰)은 그 줄을 스스로 품어 겹치지 않음.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance
|
||||
from B08_Quantity.B08_Quantity_Engine_StmateRecipe import recipe_item
|
||||
from B08_Quantity.B08_Quantity_Engine_StructureSheet import build_standard_sheets
|
||||
from B08_Quantity.B08_Quantity_Engine_StructureTemplate import (
|
||||
KEEP_ENGINE_COMPONENTS,
|
||||
apply_templates,
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table
|
||||
|
||||
HOPYO = {
|
||||
"no": 6,
|
||||
"source_code": "B00010",
|
||||
"name": "기슭막이",
|
||||
"spec": "H=2.0m",
|
||||
"unit": "M",
|
||||
"basis": [],
|
||||
"contract_rows": 0,
|
||||
"rows": [
|
||||
{
|
||||
"name": "깬잡석찰쌓기",
|
||||
"spec": "L3=45",
|
||||
"amount": 2.09,
|
||||
"unit": "M2",
|
||||
"remark": "",
|
||||
"source_code": "",
|
||||
},
|
||||
],
|
||||
}
|
||||
WALL = StructureInstance.model_validate(
|
||||
{
|
||||
"structure_id": "a",
|
||||
"type_id": "masonry_wet",
|
||||
"placement": "interval",
|
||||
"start_m": 0.0,
|
||||
"end_m": 10.0,
|
||||
"options": {"height_m": 2.5, "back_len_cm": 45, "foundation": "기초유"},
|
||||
}
|
||||
).model_dump()
|
||||
NAMES = {"masonry_wet": "돌쌓기(찰)"}
|
||||
|
||||
|
||||
def _fixed() -> dict:
|
||||
return {
|
||||
**recipe_item(HOPYO, type_id="masonry_wet", file_name="a", project="p"),
|
||||
"code": "AX-ST-0000abcd",
|
||||
}
|
||||
|
||||
|
||||
def test_목록이_코드에_못박혀_있다() -> None:
|
||||
assert set(KEEP_ENGINE_COMPONENTS) >= {"터파기", "되메우기", "잔토처리", "버림콘크리트"}
|
||||
|
||||
|
||||
def test_고정형을_가져와도_토공_버림_성분은_남는다() -> None:
|
||||
engine = build_table([WALL], NAMES, {}, {}, None, use_templates=False)["structures"][0]
|
||||
fixed = build_table([WALL], NAMES, {}, {}, None, structure_templates={"masonry_wet": _fixed()})
|
||||
components = {c["name"]: c for c in fixed["structures"][0]["components"]}
|
||||
for name in ("터파기", "되메우기", "잔토처리", "버림콘크리트"):
|
||||
before = next(c for c in engine["components"] if c["name"] == name)
|
||||
assert components[name]["amount"] == before["amount"], name
|
||||
assert "깬잡석찰쌓기" in components and "돌" not in components # 목록 밖은 양식으로 갈음
|
||||
|
||||
|
||||
def test_양식형은_그_줄을_스스로_품어_겹치지_않는다() -> None:
|
||||
table = build_table([WALL], NAMES, {}, {}, None)
|
||||
counts = Counter(c["name"] for c in table["structures"][0]["components"])
|
||||
assert all(count == 1 for count in counts.values()), counts
|
||||
|
||||
|
||||
def test_구조물도_장에도_남긴_줄이_보인다() -> None:
|
||||
templates = {"masonry_wet": _fixed()}
|
||||
table = build_table([WALL], NAMES, {}, {}, None, structure_templates=templates)
|
||||
payload = build_standard_sheets(table, {})
|
||||
apply_templates(payload, {}, templates)
|
||||
names = [row["name"] for row in payload["sheets"][0]["rows"]]
|
||||
assert names[0] == "깬잡석찰쌓기" and "터파기" in names and "버림콘크리트" in names
|
||||
numbers = [row["no"] for row in payload["sheets"][0]["rows"]]
|
||||
assert len(numbers) == len(set(numbers)) # 남긴 줄 차례가 양식 줄과 안 겹침
|
||||
Reference in New Issue
Block a user