Files
Aislo/resources/tester/test_b08_observed_unit.py
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

197 lines
8.1 KiB
Python
Raw Permalink 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.
"""관측 원단위표 검사 — PLAN 8-6·8-8 · 2026-09-07 3자 승인.
이 일감의 위험은 계산이 아니라 **관측값을 늘려 쓰는 것**이다.
· 관측값은 그 규격에서만 맞다 — 벽·기초는 높이에 비례하지 않는다.
· 규격이 표에 없으면 가까운 값을 갖다 쓰지 않고 「원단위 미확보」로 드러낸다.
· 두 근거(식에서 나온 값 / 관측값)가 한 표에 섞이므로 줄마다 근거를 단다.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_ObservedUnit import ( # noqa: E402
BASIS_OBSERVED,
NOTE_UNIT_MISSING,
ObservedUnitTable,
expand_observed,
load_observed_table,
scale_for,
)
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
BASIS_DERIVED,
build_table,
expand,
)
def 옹벽(height: float = 2.0, length: float = 10.0, form: str = "반중력식") -> dict:
return {
"structure_id": "w1",
"type_id": "retaining_wall",
"start_m": 100.0,
"end_m": 100.0 + length,
"options": {"form": form, "height_m": height, "length_m": length},
}
def 성분(result, name: str):
return next(c for c in result.components if c.name == name)
# ── 규격이 맞을 때만 쓴다 ───────────────────────────────────────────
def test_규격이_맞으면_연장만큼_곱해짐() -> None:
"""`H=2.0 옹벽 10m` 는 같은 단면이 10m 이어진 것 — 개수를 세는 것이지 규격을 늘리는 게 아니다."""
result = expand(옹벽(length=10.0))
assert 성분(result, "콘크리트").amount == pytest.approx(13.5) # 1.35 ㎥/m × 10
assert 성분(result, "유로폼").amount == pytest.approx(32.0)
def test_높이가_다르면_비례로_늘리지_않고_미확보() -> None:
"""⚠ 이 시험이 이 파일의 핵심 — H=1.6 은 표에 없다. 1.35 × 0.8 로 만들지 않는다."""
result = expand(옹벽(height=1.6))
assert result.components == []
# ⚠ 사용자에게 뜨는 말로 잰다 — 키 이름이 새면 안 된다(㉑).
assert any("자료에 없습니다" in note for note in result.notes)
def test_형식이_다르면_미확보() -> None:
result = expand(옹벽(form="캔틸레버식"))
assert result.components == []
# ⚠ 사용자에게 뜨는 말로 잰다 — 키 이름이 새면 안 된다(㉑).
assert any("자료에 없습니다" in note for note in result.notes)
def test_미확보_알림에_있는_규격을_같이_알려줄것() -> None:
"""「없다」만 말하면 사용자가 무엇을 고쳐야 할지 모른다.
⚠ 규격도 **사람 말**로 보인다 — `form`·`height_m` 이 아니라 「옹벽 형식」·「높이(m)」."""
result = expand(옹벽(height=1.6))
note = next(n for n in result.notes if "자료에 있는 규격" in n)
assert "옹벽 형식 반중력식" in note and "높이(m) 2.0" in note
assert "form" not in note and "height_m" not in note
def test_규격이_비어_있으면_고르지_못함을_알림() -> None:
result = expand({"type_id": "retaining_wall", "options": {"length_m": 5.0}})
assert result.components == []
assert result.notes
# ── 근거를 줄마다 단다 ──────────────────────────────────────────────
def test_관측값에는_observed_근거가_붙음() -> None:
result = expand(옹벽())
for component in result.components:
assert component.basis_kind == BASIS_OBSERVED
assert "관측 원단위" in component.basis
assert component.source
def test_식에서_나온_값은_derived_로_남음() -> None:
"""두 근거가 한 표에 섞이므로 갈라 보여야 한다."""
stone = expand(
{
"type_id": "masonry_wet",
"start_m": 0.0,
"end_m": 10.0,
"options": {"height_m": 1.5, "length_m": 10.0},
}
)
assert all(c.basis_kind == BASIS_DERIVED for c in stone.components)
def test_표에도_근거가_실림() -> None:
"""⚠ 관측 줄에 **파생 줄 하나가 섞인다** — 기초잡석은 버림 폭에서 나오는 값이라
관측이 아니라 `derived` 다(2026-09-09 확정 3차 ②). 근거가 갈려 실리는 것이 맞다."""
table = build_table([옹벽()])
components = [c for s in table["structures"] for c in s["components"]]
kinds = {c["basis_kind"] for c in components if c["name"] != "기초잡석"}
assert kinds == {BASIS_OBSERVED}
잡석 = next(c for c in components if c["name"] == "기초잡석")
assert 잡석["basis_kind"] == BASIS_DERIVED
# ── 이중계상 규칙은 그대로 ──────────────────────────────────────────
def test_관측값에도_배합이_섞이지_않음() -> None:
"""㉢ — 콘크리트 ㎥ 까지만. 시멘트·모래로 쪼개는 것은 B09 몫이다."""
assert build_table([옹벽()])["mix_components_found"] == []
def test_터파기는_토공으로_되메우기는_토공으로() -> None:
"""관측 원단위의 터파기도 내역 줄이 아니라 토공 합산이다."""
table = load_observed_table()
entry = table.find("pipe_inlet_basin", {"inlet_basin_form": "돌집수정 ㄷ형"})
destinations = {c["name"]: c["destination"] for c in entry["components"]}
assert destinations["터파기"] == "earthwork"
assert destinations["잔토처리"] == "earthwork"
assert destinations["콘크리트"] == "unit_price"
def test_할증은_여전히_자재총괄_몫() -> None:
assert build_table([옹벽()])["surcharge_applied"] is False
# ── 표 자체 ─────────────────────────────────────────────────────────
def test_실제_데이터판이_읽힘() -> None:
table = load_observed_table()
assert table.effective_date
assert table.find("retaining_wall", {"form": "반중력식", "height_m": 2.0})
def test_숫자와_글자_규격을_같이_본다() -> None:
"""입력 폼이 `"800"` 을 문자열로 준다 — 그렇다고 `2.0` 과 `1.6` 을 같다고 보면 안 된다."""
table = load_observed_table()
assert table.find(
"pipe_inlet_basin",
{
"inlet_basin_form": "□형(기본형)",
"inlet_basin_material": "콘크리트",
"pipe_diameter_mm": 800,
},
)
assert table.find("retaining_wall", {"form": "반중력식", "height_m": 1.9999}) is None
def test_BOX암거는_미확보로_남음() -> None:
"""⚠ 두께가 저장돼 있지 않아 식도 못 세우고 관측값도 없다 — 지어내면 콘크리트·거푸집·
철근으로 번져 나간다. 사용자 확정 대기."""
table = load_observed_table()
assert table.find("box_culvert", {"body_width_m": 3.0, "body_height_m": 1.2}) is None
reasons = [item["type_id"] for item in table.not_found["items"]]
assert "box_culvert" in reasons
def test_파일이_없으면_전부_미확보() -> None:
empty = ObservedUnitTable()
components, notes = expand_observed("retaining_wall", {"form": "반중력식"}, 옹벽(), empty)
assert components == []
# 자료가 통째로 없으면 「표준 물량 자료가 없다」로 말한다.
assert "표준 물량 자료가 아직 없습니다" in notes[0]
def test_곱할_연장이_0이면_내지_않음() -> None:
result = expand(옹벽(length=0.0))
assert result.components == []
def test_개소당_원단위는_안_곱해짐() -> None:
"""집수정은 1개소가 1개소다 — 연장을 곱하면 값이 부푼다."""
table = load_observed_table()
entry = table.find("pipe_inlet_basin", {"inlet_basin_form": "돌집수정 ㄴ형"})
scale, note = scale_for(entry, {"options": {"length_m": 10.0}})
assert scale == 1.0
assert "개소" in note