Files
Aislo/resources/tester/test_b08_structure_sheet_router.py
T
eomsangdonandClaude Opus 5 1375d8c290 feat(b08): 구조물도 탭에 양식 식을 실음 — 산출 기초·식·갈 곳·안 섬
- 양식이 있는 종류(찰쌓기)는 장마다 양식으로 줄을 다시 세움(L=1 m당) — 식·설명·반올림·
  갈 곳·출처·안 섬 까닭을 줄에 실음. 양식 없는 종류는 지금 전개 그대로(고정형 모양)
- 화면 표: 산출 근거 밑에 식 한 줄 · 갈 곳 칸 · 안 선 줄은 「안 섬」과 까닭
- 전개 결함 둘 고침(브레인 판정): 「실무 관행」 계수가 돌종류를 지우던 것 ·
  기초잡석이 서는데 「두께가 없어 안 섬」 사유가 함께 뜨던 것
- 검증 프로젝트 찰쌓기 15줄 — 양식 m당×10 = 원단위 탭 합계 전부 일치

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

139 lines
5.5 KiB
Python

"""구조물도 창구(B08) — B07 에서 옮긴 뒤 조회·제원 저장이 한 벌로 도는지 (2026-09-13, PLAN 3장 ②).
겨누는 것 셋
① 조회가 장을 냄 — 옛 B07 `/standard-sheets` 자리
② ⚠ 기초잡석 두께가 **산출 조건 값을 따름** — 옛 B07 창구는 두께를 안 넘겨 늘 0.2 로 섰음
(원단위 탭과 구조물도가 같은 구조물에서 다른 값을 냈음)
③ 제원 저장이 그 장의 개소에 걸리고, 다시 조회하면 새 제원으로 섬
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
import B08_Quantity.B08_Quantity_Router_Material as material_module # noqa: E402
import B08_Quantity.B08_Quantity_Router_StructureSheet as router_module # noqa: E402
from B05_Profile.B05_Profile_Structures_Repository import save_structures # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance # noqa: E402
PROJECT_ID = "22222222-2222-2222-2222-222222222222"
SHEETS = f"/api/projects/{PROJECT_ID}/quantity/structure-sheets"
@pytest.fixture()
def project(tmp_path: Path) -> Path:
root = tmp_path / "project"
root.mkdir()
wall = StructureInstance.model_validate(
{
"type_id": "masonry_wet",
"placement": "interval",
"start_m": 100.0,
"end_m": 110.0,
"options": {"height_m": 2.5, "back_len_cm": 45},
}
)
save_structures(str(root), [wall], base_revision=0)
return root
@pytest.fixture()
def client(project: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient:
async def fake_root(project_id):
return str(project)
async def no_route(project_id):
return {}
monkeypatch.setattr(router_module, "_project_root", fake_root)
monkeypatch.setattr(material_module, "_section_modes", no_route)
monkeypatch.setattr(material_module, "_ground_types", no_route)
app = FastAPI()
app.include_router(router_module.router)
return TestClient(app)
def _sheet(client: TestClient) -> dict:
response = client.get(SHEETS)
assert response.status_code == 200, response.text
sheets = response.json()["sheets"]
assert len(sheets) == 1, sheets
return sheets[0]
def _unit_amount(sheet: dict, name: str) -> float:
return next(row["unit_amount"] for row in sheet["rows"] if row["name"] == name)
def test_조회가_장을_낸다(client: TestClient) -> None:
sheet = _sheet(client)
assert sheet["type_id"] == "masonry_wet"
assert sheet["member_count"] == 1
assert sheet["billing_unit"] == "m" and sheet["billing_total"] == pytest.approx(10.0)
assert all("spec" in row for row in sheet["rows"])
def test_양식이_있는_종류는_줄마다_식을_싣는다(client: TestClient) -> None:
"""PLAN 3장 ④ · 명세 13장 — 찰쌓기는 양식으로 줄이 서고 식·설명·갈 곳·출처가 실림."""
sheet = _sheet(client)
assert sheet["library_item"]["type_id"] == "masonry_wet"
rows = {row["name"]: row for row in sheet["rows"]}
돌쌓기 = rows["돌쌓기"]
assert 돌쌓기["formula"] == "H*L*SQRT(1+N^2)"
assert "비탈면적" in 돌쌓기["basis"]
assert 돌쌓기["source"] == "library" and 돌쌓기["destination"] == "unit_price"
assert all(row["destination"] for row in sheet["rows"]) # 갈 곳 빈 줄 없음
# 돌 줄은 이름 고정 — 종류를 안 골라 규격이 빔(매칭 성공 아님, 명세 13장 Ⓒ).
assert rows["돌"]["spec"] == ""
# m당 값 — 제원(H=2.5·뒷길이 45·1:0.3)으로 다시 셈한 값.
assert 돌쌓기["unit_amount"] == pytest.approx(2.5 * (1 + 0.3**2) ** 0.5)
def test_안_선_줄은_안_섬으로_실린다(client: TestClient) -> None:
"""버림을 「안 넣음」으로 저장하면 버림·기초잡석이 0 이 아니라 「안 섬」으로 옴."""
sheet = _sheet(client)
saved = client.put(
f"{SHEETS}/spec",
json={"sheet_key": sheet["key"], "base_revision": 1, "blinding_concrete": "안 넣음"},
)
assert saved.status_code == 200, saved.text
rows = {row["name"]: row for row in _sheet(client)["rows"]}
for name in ("버림콘크리트", "기초잡석"):
assert rows[name]["skipped"] is True and rows[name]["unit_amount"] is None, rows[name]
assert rows[name]["reason"], rows[name]
def test_기초잡석_두께가_산출_조건을_따른다(client: TestClient, project: Path) -> None:
기본 = _unit_amount(_sheet(client), "기초잡석")
(project / "project_settings.json").write_text(
json.dumps({"quantity": {"rubble_base_thickness_m": 0.3}}), encoding="utf-8"
)
바꿈 = _unit_amount(_sheet(client), "기초잡석")
# 기본 0.2 → 0.3 — 버림 폭에 두께 비를 곱하는 식이라 1.5 배.
assert 바꿈 == pytest.approx(기본 * 1.5)
def test_제원_저장이_그_장에_걸린다(client: TestClient) -> None:
sheet = _sheet(client)
saved = client.put(
f"{SHEETS}/spec",
json={"sheet_key": sheet["key"], "base_revision": 1, "back_len_cm": "55"},
)
assert saved.status_code == 200, saved.text
assert saved.json()["changed"] == 1
assert _sheet(client)["options"]["back_len_cm"] == 55
def test_없는_장이면_404(client: TestClient) -> None:
response = client.put(f"{SHEETS}/spec", json={"sheet_key": "없는 장", "base_revision": 1})
assert response.status_code == 404