# -*- coding: utf-8 -*- """「칸은 있는데 표가 안 읽음」 — 등록부 칸 옆에 「아직 표에 안 쓰임」 (2026-09-14 브레인 차례 ①). 사용자가 값을 넣어도 수량·금액이 아무것도 안 바뀌는 칸. 사유를 표에만 두지 말고 칸 옆에도 보임 (브레인 판정 ③ 「B」). 표시가 참말인지 **값을 바꿔 돌려 보고** 지킴 — 표가 그 칸을 읽기 시작하면 여기서 깨지고, 그때 표시를 걷음(표시가 거짓말로 남지 않게). """ from __future__ import annotations import json import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402 from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402 #: 종류 → (표가 서는 기준 제원, 안 읽는 칸). 2026-09-14 값을 바꿔 전수 대조한 결과. #: ⚠ 기초(foundation)·높이는 인계 구조물터파기 심도 구간이 읽어 뺌 · 단 수·올림·이동은 횡단도 칸이라 뺌. MARKED: dict[str, tuple[dict, list[str]]] = { "erosion_check": ( {"height_m": 2.0, "top_length_m": 6.0, "bottom_length_m": 4.0, "form": "돌"}, ["thickness_top_m", "thickness_bottom_m", "stone_supply", "stone_coeff_basis"], ), "bed_sill": ( {"area_m2": 10.0, "form": "돌붙임(찰)"}, ["stone_supply", "stone_coeff_basis", "fill_concrete_mpa", "face_slope_ratio"], ), "boulder_masonry": ( {"height_m": 2.5, "length_m": 10.0, "stone_cm": "60~80", "bond": "찰쌓기"}, [ "stone_supply", "stone_coeff_basis", "fill_concrete_mpa", "weep_hole_diameter_mm", "weep_hole_area_m2", ], ), "soil_guard": ( {"height_m": 1.0, "length_m": 10.0, "form": "떼"}, [ "thickness_top_m", "thickness_bottom_m", "back_len_cm", "stone_kind", "stone_supply", "stone_coeff_basis", "fill_concrete_mpa", "face_slope_ratio", "weep_hole_diameter_mm", "weep_hole_area_m2", ], ), } FIELDS = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Fields.ts").read_text(encoding="utf-8") PANEL = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel.ts").read_text(encoding="utf-8") def _snapshot(type_id: str, options: dict) -> str: structure = { "structure_id": "p", "type_id": type_id, "start_m": 0.0, "end_m": 10.0, "chainage_m": 5.0, "options": options, } table = build_table([structure], {}, {5.0: "both_fill"}, None, 0.2) # 저장 제원을 그대로 되돌려 싣는 칸(`options`)은 뺌 — 값이 **표에 닿았는지**만 봄. for row in table["structures"]: row.pop("options", None) return json.dumps(table, ensure_ascii=False, sort_keys=True, default=str) def _probe_values(option) -> list: if option.choices: return list(option.choices) return [0.73, 1.37] if option.input == "number" else ["x"] @pytest.mark.parametrize("type_id", sorted(MARKED)) def test_등록부에_표시가_있다(type_id: str) -> None: options = {option.key: option for option in structure_type_map()[type_id].options} for key in MARKED[type_id][1]: note = options[key].not_in_table assert note and "아직 표에 안 쓰임" in note, f"{type_id}.{key}" @pytest.mark.parametrize("type_id", sorted(MARKED)) def test_표시한_칸은_정말_안_읽힌다(type_id: str) -> None: base, keys = MARKED[type_id] options = {option.key: option for option in structure_type_map()[type_id].options} reference = _snapshot(type_id, dict(base)) assert '"components": []' not in reference, "기준 제원으로 표가 안 섬 — 시험이 무의미" for key in keys: for value in _probe_values(options[key]): assert _snapshot(type_id, {**base, key: value}) == reference, ( f"{type_id}.{key}={value!r} 로 표가 바뀜 — 이제 읽힘, 표시를 걷을 것" ) def test_폼_칸_이름에_붙는다() -> None: assert "not_in_table" in FIELDS and "not_in_table" in PANEL