Files
Aislo/resources/tester/test_b08_handoff_contract.py
T
eomsangdonandClaude Opus 5 d7cb14f416 test(B08): tmp/tests 중 tester 에 없던 47 개를 resources/tester 로 옮김
tmp/ 가 창끼리 안 건너가는 것이 확정돼(랩탑이 시간 두고 두 번 확인) 시험·예외가
저절로 건너가도록 git 안으로 옮김. 사용자 확정.

- 내용은 하나도 안 고침 — 자리만 옮김. tmp/tests 는 남겨 둠.
- 같은 이름이 이미 있던 64 개는 랩탑 것을 그대로 두고 건너뜀.
- helper_b05_*.js 둘은 랩탑이 .cjs 로 이미 올린 것과 **줄바꿈만 다른 같은 내용**이라
  복사본을 도로 뺌(시험이 .cjs 를 부름).
- resources/tester/ 에서 전체 1176 통과 · 29 건너뜀 · 실패 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:15:57 +09:00

212 lines
7.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-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",
}
#: **자재 줄**이 내보내는 칸. ⚠ 여기에 `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_UNIT_DATA_MISSING,
)
allowed = {None, BLOCKED_INPUT_MISSING, BLOCKED_UNIT_DATA_MISSING, BLOCKED_FORMULA_MISSING}
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"]