Files
Aislo/resources/tester/test_b08_handoff_contract.py
T
eomsangdonandClaude Opus 5 81286400bd fix(b08,b09): 기본값으로 선 구조물은 줄만 서고 금액 밖 — 「미확정 N건 — 금액에 안 들어감」
- 브레인 판정: 값 없음을 감추면 조용히 빠지거나 조용히 부풂 — 없으면 없다고 보이기
- 관 지점 벽 치수를 등록부 기본값으로 채운 줄 = 미확정: 원단위 합·자재·터파기·되메우기·기초잡석·타설·잔토·채집석에서 뺌
- 인계 막힘 갈래 넷째 값 unconfirmed(계약 칸은 안 늘림) · B09 는 내역 제자리에 빈 금액 + 빨간 테두리 · 머리 뱃지
- 구조물도 그림에 표와 같은 기본값 사유 줄 · 「횡단도와 같은 값」 문구는 틀려 고침(B06 관 벽 높이는 관경 기준)
- 936be972 내역 184,291,506 → 124,424,547 · 미확정 18건

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

222 lines
7.9 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-07 ㉒ 앞).
⚠ 오늘 이 병이 계약 양쪽에서 하나씩 났다.
· 우리 쪽 — 레지스트리 키(`back_len_cm`)와 엔진이 읽는 키가 달라 **저장값이 안 닿음**.
· 받는 쪽 — 우리가 보낸 `composite_parts`·`quantity_gross` 등을 **안 읽고 버림**.
**「보내는 쪽은 보냈는데 받는 쪽이 안 읽는」 자리는 양쪽 다 조용하다.**
⇒ 키를 늘릴 때 **계약에 안 적고 늘리면 받는 쪽이 영영 모른다.** 이 시험이 그것을 막는다.
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff # noqa: E402
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table as build_unit # noqa: E402
#: **작업 공종 줄**이 내보내는 칸. 늘릴 때는 계약(8-2)에도 적고 받는 쪽에 알릴 것.
WORK_ITEM_KEYS = {
"work_item_code",
"name",
"spec",
"unit",
"quantity",
"quantity_gross",
"application_ratio_pct",
"application_ratio_breakdown",
"quantity_breakdown",
"ground_class",
"excavation_method",
"haul_distance_m",
"haul_equipment",
"station_from",
"station_to",
"spec_detail",
"composite_parts",
"structure_kind",
"variant_axis",
"variant_value",
# ⚠ 갈래 축이 둘 이상인 자리(돌쌓기 = 뒷길이 × 돌 종류) — 2026-09-08 추가.
"secondary_axes",
"spec_class",
"spec_class_basis",
"blocked_kind",
"blocked_reason",
"composite_not_ready",
"in_bill",
"in_bill_reason",
"origin",
# 공종 코드가 본 품셈 판 — B09 가 마스터 판과 다르면 값을 안 씀(명세 17장 · 2026-09-13 추가).
"pum_edition",
}
#: **자재 줄**이 내보내는 칸. ⚠ 여기에 `work_item_code` 가 들어가면 안 된다(축이 다르다).
MATERIAL_KEYS = {
"material_name",
"spec",
"unit",
"net_amount",
"total_amount",
"surcharge_pct",
"surcharge_note",
"supply_type",
"install_by",
"source_structure",
}
def _rows() -> dict:
unit = build_unit(
[
{
"structure_id": "w1",
"type_id": "retaining_wall",
"start_m": 100.0,
"end_m": 110.0,
"options": {"form": "반중력식", "height_m": 2.0, "length_m": 10.0},
}
],
{"retaining_wall": "옹벽"},
)
from B08_Quantity.B08_Quantity_Engine_MaterialSummary import build_table as build_material
haul = {
"rows": [
{
"equipment": "dozer",
"ground": "토사",
"volume_m3": 10.0,
"average_distance_m": 40.0,
"in_bill": True,
}
]
}
summary = {"rows": [{"group": "흙깎기", "item": "토사", "unit": "㎥", "amount": 5.0}]}
return build_handoff(
summary_table=summary,
haul_table=haul,
unit_quantity_table=unit,
material_table=build_material(unit),
)
def test_작업_공종_줄의_칸이_계약과_같을것() -> None:
"""⚠ 계약에 없는 칸을 늘리면 받는 쪽이 영영 모른다."""
for row in _rows()["work_items"]:
extra = set(row) - WORK_ITEM_KEYS
assert not extra, f"계약에 없는 칸: {extra}"
missing = WORK_ITEM_KEYS - set(row)
assert not missing, f"계약에 있는데 안 보내는 칸: {missing}"
def test_자재_줄의_칸이_계약과_같을것() -> None:
for row in _rows()["materials"]:
assert set(row) == MATERIAL_KEYS, set(row) ^ MATERIAL_KEYS
def test_자재_줄에_공종코드가_없을것() -> None:
"""축이 둘이라는 것은 주석이 아니라 계약으로 지킨다."""
assert "work_item_code" not in MATERIAL_KEYS
for row in _rows()["materials"]:
assert "work_item_code" not in row
def test_막힌_까닭이_세_갈래_중_하나일것() -> None:
"""받는 쪽이 「입력하면 풀리는 것」과 「우리가 만들어야 하는 것」을 갈라야 한다."""
from B08_Quantity.B08_Quantity_Engine_Handoff import (
BLOCKED_FORMULA_MISSING,
BLOCKED_INPUT_MISSING,
BLOCKED_UNCONFIRMED,
BLOCKED_UNIT_DATA_MISSING,
)
# ⭐ 넷째 갈래(2026-09-14 브레인 판정) — 치수 없이 기본값으로 선 줄: 수량은 보이되 금액 밖.
allowed = {
None,
BLOCKED_INPUT_MISSING,
BLOCKED_UNIT_DATA_MISSING,
BLOCKED_FORMULA_MISSING,
BLOCKED_UNCONFIRMED,
}
for row in _rows()["work_items"]:
assert row["blocked_kind"] in allowed
def test_막힌_줄에는_사유가_반드시_있을것() -> None:
"""갈래만 있고 사유가 없으면 받는 쪽 화면이 빈다."""
for row in _rows()["work_items"]:
if row["blocked_kind"]:
assert row["blocked_reason"], row["name"]
# ── 갈래 키를 우리가 조립하지 않는다 (2026-09-07 계약 변경) ─────────
#
# ⚠ 품셈 원문이 물결표를 섞어 쓴다 — 13-06-01·02 는 ``(U+223C), 13-06-03 은 ``(U+FF5E).
# 두 창이 각자 키 문자열을 만들면 **글자 하나로 영영 안 맞는다.**
# ⇒ 우리는 **축과 저장 원본값**만 보내고, 원문을 읽는 쪽이 그 표기를 흡수한다.
def _structure_rows() -> list[dict]:
out = []
for type_id, options in (
("masonry_wet", {"height_m": 1.5, "length_m": 10.0, "back_len_cm": "45"}),
(
"boulder_masonry",
{"height_m": 2.0, "length_m": 10.0, "stone_cm": "60~80", "bond": "찰쌓기"},
),
):
unit = build_unit(
[
{
"structure_id": "x",
"type_id": type_id,
"start_m": 0.0,
"end_m": 10.0,
"options": options,
}
],
{type_id: type_id},
)
out.extend(build_handoff(unit_quantity_table=unit)["work_items"])
return out
def test_축과_원본값을_그대로_보낼것() -> None:
rows = {row["variant_axis"]: row for row in _structure_rows() if row["variant_axis"]}
assert rows["back_len_cm"]["variant_value"] == "45"
assert rows["stone_cm"]["variant_value"] == "60~80"
def test_갈래_키_문자열을_코드에_붙이지_말것() -> None:
"""⚠ 이 시험이 계약 변경을 지킨다 — `#55cm이하`·`#직경60㎝이상∼80㎝미만` 을 만들면 깨진다.
⚠ **메/찰은 예외다** — 그것은 키 표기가 아니라 **공종 자체가 갈리는 의미 판정**이라
코드가 `FP-13-06-01`/`-02` 로 통째로 달라진다.
"""
for row in _structure_rows():
code = str(row["work_item_code"] or "")
assert "#" not in code, f"갈래 키를 코드에 붙였다: {code}"
assert "㎝" not in code and "cm" not in code
def test_원본값을_가공하지_말것() -> None:
"""물결표·공백을 우리가 손대면 받는 쪽이 원문과 대조를 못 한다."""
rows = {row["variant_axis"]: row for row in _structure_rows() if row["variant_axis"]}
raw = rows["stone_cm"]["variant_value"]
assert raw == "60~80" # 물결표 종류를 바꾸거나 「㎝」를 붙이지 않는다
def test_돌_종류를_둘째_축으로_보낼것() -> None:
"""⚠ 품셈 13-4 품은 [주]② 로 **깬돌·깬잡석 전용**이고 돌 종류로 갈리는 표는 13-5 다.
어느 공종으로 볼지는 사용자 확정 대기라 **공종을 바꾸지 않고 원본값만** 실어 보낸다.
"""
rows = {row["variant_axis"]: row for row in _structure_rows() if row["variant_axis"]}
axes = rows["back_len_cm"]["secondary_axes"] or []
assert [item["axis"] for item in axes] == ["stone_kind"]