Files
Aislo/resources/tester/test_b08_stmate_fixed_chain.py
T
eomsangdonandClaude Opus 5 3886f3bfc1 feat(b08): 고정형 항목도 일위대가 구성을 가짐 — 공종 코드는 비워 두고 사용자가 고르개로 고름(㉮) · 고르기 전엔 내역 줄이 서되 「미완 — 공종 코드 미정」 금액 밖 · 찾기는 코드 없는 줄 이름으로 후보만 띄움
936be972 실측: 봉화 제6호표 가져오기 → 내역 「기슭막이 H=2.0m 10m」 2줄이 「막힌 줄 9: 공종 코드 미정」으로 섬(전엔 줄이 사라짐) → 코드 3줄 고르고 6줄 수동 단가(검증용) → 일위대가 482,766원/m · 내역 4,827,660원 × 2줄 「수동 단가 6건 미확정」 · 되돌려 본체·인계·내역·원단위·장 전부 같음

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-14 16:24:33 +09:00

122 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""고정형 항목 사슬 ㉮ — 인계·내역으로 가는 문(2026-09-14 브레인 판정 「공종 코드가 없으면 어디에도 안 감」).
실측(936be972): 뽑은 고정형엔 일위대가 구성(`unit_price.rows`)이 없어 `priced_sheets` 가 그 장을 안 셈 →
구조물 줄 수량 0 · 내역 줄 사라짐 · 본체 −4,062,480. 고친 길: 일위대가로 가는 줄마다 구성 줄을 두되
**공종 코드는 비움** → 그 장은 호표 줄로 인계되고 금액은 「미완 — 공종 코드 미정」(조용한 0 아님) →
사용자가 고르개로 코드를 고르면 금액이 섬. 자원 축은 후보만(이름 자동 확정 금지 · 명세 2장).
"""
from __future__ import annotations
import sys
from decimal import Decimal
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_StmateRecipe import recipe_item # noqa: E402
from B08_Quantity.B08_Quantity_Engine_StructurePriceLink import ( # noqa: E402
price_sheets,
priced_sheets,
)
from B08_Quantity.B08_Quantity_Engine_StructureUnitPrice import with_rows # noqa: E402
from B09_Estimation.B09_Estimation_PriceBook import Money3, PriceBookError # noqa: E402
HOPYO = {
"no": 6,
"source_code": "B00010",
"name": "기슭막이",
"spec": "H=2.0m",
"unit": "M",
"basis": [],
"contract_rows": 0,
"rows": [
{
"name": "깬잡석채집",
"spec": "L=45cm",
"amount": 2.09,
"unit": "M2",
"remark": "",
"source_code": "",
},
{
"name": "고임돌채집",
"spec": "기계",
"amount": 0.31,
"unit": "M3",
"remark": "",
"source_code": "",
},
],
}
class Book:
titles = {"B-FP-X#채집": ("깬잡석 채집", "㎡", Money3(Decimal(0), Decimal(1000), Decimal(0)))}
def title(self, code: str) -> SimpleNamespace:
if code not in self.titles:
raise PriceBookError(f"단가표에 없는 코드입니다: {code}")
name, unit, _money = self.titles[code]
return SimpleNamespace(name=name, spec="", unit=unit)
def resolve(self, code: str) -> Money3:
return self.titles[code][2] if code in self.titles else self.title(code)
def _sheet(item: dict) -> dict:
"""`apply_templates` 가 낸 장 모양 — 고정형 줄 = 박힌 단위량."""
return {
"type_id": item["type_id"],
"title": "돌쌓기(찰) H=2.5",
"formula_sheet": {"vars": {}},
"billing_unit": "m",
"members": [{"structure_id": "s1", "billing_quantity": 10}],
"rows": [
{
"no": row["seq"],
"unit_amount": float(row["amount"]),
"unit": row["unit"],
"destination": row["destination"],
}
for row in item["rows"]
],
}
def test_고정형_장이_호표_줄로_인계되고_코드_없으면_미완() -> None:
item = {
**recipe_item(HOPYO, type_id="masonry_wet", file_name="a.xlsx", project="p"),
"code": "AX-ST-0000abcd",
}
entries = priced_sheets([_sheet(item)], {"masonry_wet": item})
assert [e["structure_ids"] for e in entries] == [["s1"]] # 전엔 빈 목록 — 인계에서 끊겼음
prices = price_sheets(entries, Book(), lambda code, value: None, [item])
(entry,) = prices.values()
assert entry["blocked"] == 2 and entry["total"] == 0
assert all("공종 코드 미정" in reason for reason in entry["reasons"])
def test_찾기는_코드_없는_줄_이름으로_후보만_띄운다() -> None:
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_StructureSheet_UnitPriceEdit.ts").read_text(
encoding="utf-8"
)
assert "query.value = row.name" in ui and "!row.ref_code && !row.work_item_code" in ui
def test_사용자가_코드를_고르면_금액이_선다() -> None:
item = {
**recipe_item(HOPYO, type_id="masonry_wet", file_name="a.xlsx", project="p"),
"code": "AX-ST-0000abcd",
}
picked = [
dict(row, ref_code="B-FP-X#채집", unit="㎡") for row in item["unit_price"]["rows"][:1]
]
template = with_rows(item, picked)
entries = priced_sheets([_sheet(item)], {"masonry_wet": template})
(entry,) = price_sheets(entries, Book(), lambda code, value: None, [template]).values()
assert entry["blocked"] == 0 and entry["total"] == Decimal("2090") # 2.09㎡ × 1,000원