- Router_Design enforce 셋 · 넓힘 뒤 설계 — 파이썬은 입력 조립만 · 측점 묶음을 Node 한 번(B06_Section_Engine_Design_Node · cross.design) · Node 못 부르면 파이썬 짝 - 소단 구간 셈을 화면 파일에서 common_util_berm_spans.ts 로 · 서버도 같은 TS(cross.berm_spans) · Persist 가 다시 내보냄 - 값: 고정 자료 기준값 0 차(면적 · 계획고 · 누가토량 −17,570.7) · 설계선 점 13 곳 1e-4 m(반올림 동점 · 브레인 승인) — 기준값 시험에 판정 더함 - 시험: compute_cross_design 갈아 끼우던 시험 다섯을 묶음 자리로(까닭: [저장] 다시 셈이 묶음으로 부름) · 소단 글 시험 자리 옮김 - 등록부 · 위키 계산 목록 · B06 지침 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""B06 횡단 [상세] 모달(PLAN 138-4) — 구조물 글 새 키 저장 왕복 · B07 수량표 값 POST 한 길.
|
|
|
|
- 글(`structure_note`): 브라우저 세션 초안 → [저장] patch → 서버 스키마 → 정본 병합 →
|
|
재계산(포장 · 세월교 · 사토장)에서도 남음.
|
|
- 표: `cross-quantity-table` 이 B07 `_quantity_table` 을 캐시 설계로 돌린 값과 칸 배치를 줌.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_138_cross_detail.py -q
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from fastapi import FastAPI # noqa: E402
|
|
from fastapi.testclient import TestClient # noqa: E402
|
|
|
|
from B06_Section import B06_Section_Router_CrossTable as table_mod # noqa: E402
|
|
from B06_Section import B06_Section_Router_Design as design_mod # noqa: E402
|
|
from B06_Section.B06_Section_Schema import CrossSectionPatch # noqa: E402
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Table import QUANTITY_VALUE_KEYS # noqa: E402
|
|
|
|
# ── 글 저장 왕복 ─────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_글은_patch_스키마를_지나_정본_병합_값으로_남는다():
|
|
patch = CrossSectionPatch(chainage_m=40.0, structure_note="L형 측구 · 집수정 보강")
|
|
dumped = patch.model_dump()
|
|
# 확정 · 저장 경로와 같은 걸름(`_apply_section_edits`) — 최상위 None 만 걷음
|
|
merged = {k: v for k, v in dumped.items() if k != "chainage_m" and v is not None}
|
|
assert merged == {"structure_note": "L형 측구 · 집수정 보강"}
|
|
# 빈 글 = 지움 — 걸러지지 않고 실림
|
|
empty = CrossSectionPatch(chainage_m=40.0, structure_note="").model_dump()
|
|
assert empty["structure_note"] == ""
|
|
|
|
|
|
def test_글은_재계산에서도_남는다(monkeypatch):
|
|
monkeypatch.setattr(design_mod, "pavement_ranges", lambda root: [(0.0, 100.0)])
|
|
monkeypatch.setattr(design_mod, "ford_surface_drops", lambda root: [])
|
|
monkeypatch.setattr(design_mod, "design_elevation_from_longitudinal", lambda lon, ch: 100.0)
|
|
monkeypatch.setattr(design_mod, "curve_widening_args", lambda section: {})
|
|
# 472-4 ② — [저장] 다시 셈은 측점 묶음(Node) 한 번 · 그 자리를 갈아 끼움
|
|
monkeypatch.setattr(
|
|
design_mod,
|
|
"compute_cross_designs",
|
|
lambda jobs, standard: [{"paved": kw["paved"]} for _s, _z, kw in jobs],
|
|
)
|
|
sections = [{"chainage_m": 40.0, "design": {"paved": False, "structure_note": "메모"}}]
|
|
assert design_mod.enforce_pavement_ranges({}, sections, Path("."), None) == 1
|
|
assert sections[0]["design"]["structure_note"] == "메모"
|
|
|
|
|
|
def test_브라우저가_글을_저장_patch_에_싣는다():
|
|
persist = (PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert "readStructureNotes(" in persist and ".structure_note = note" in persist
|
|
state = (PROJECT_ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8")
|
|
assert '"structure-notes": { bucket: "draft", scope: "route" }' in state
|
|
|
|
|
|
# ── 수량표 값 ────────────────────────────────────────────────────────────
|
|
|
|
SOURCE = {"chainage_m": 40.0, "center_z": 101.5, "samples": []}
|
|
DESIGN = {
|
|
"design_elevation_m": 100.25,
|
|
"cut_soil_area_m2": 1.234,
|
|
"cut_rock_area_m2": 0.5,
|
|
"fill_area_m2": 2.0,
|
|
}
|
|
|
|
|
|
def test_표_값은_B07_수량표_한_벌():
|
|
sheet = table_mod.cross_quantity_sheet(SOURCE, DESIGN)
|
|
table = sheet["quantity_table"]
|
|
assert set(QUANTITY_VALUE_KEYS) <= set(table)
|
|
assert table["ground"] == 101.5 and table["planned"] == 100.25
|
|
assert round(table["cut"], 6) == 1.25 and table["fill"] == 0.0
|
|
assert table["cut_soil"] == 1.234 and table["embankment"] == 2.0
|
|
# 글은 B07 표에 찍히는 꼴 그대로 · 값 없음 = 「-」
|
|
assert sheet["text"]["cut_soil"] == "1.23"
|
|
assert sheet["text"]["fence"] == "-"
|
|
layout = sheet["layout"]
|
|
assert [row[1] for row in layout["header"]] == ["ground", "planned", "fill", "cut"]
|
|
assert len(layout["left"]) == len(layout["middle"]) == len(layout["right"]) == 6
|
|
|
|
|
|
def test_설계_없으면_원본_칸만():
|
|
sheet = table_mod.cross_quantity_sheet(SOURCE, None)
|
|
assert sheet["quantity_table"]["ground"] == 101.5
|
|
assert sheet["quantity_table"]["planned"] is None
|
|
assert sheet["text"]["cut_soil"] == "-"
|
|
|
|
|
|
class _Pool:
|
|
def acquire(self):
|
|
pool = self
|
|
|
|
class _Ctx:
|
|
async def __aenter__(self):
|
|
return pool
|
|
|
|
async def __aexit__(self, *exc):
|
|
return False
|
|
|
|
return _Ctx()
|
|
|
|
|
|
def test_POST_한_길이_캐시_설계로_표를_낸다(monkeypatch):
|
|
seen = {}
|
|
|
|
async def longitudinal(connection, project_id, route_id):
|
|
return {"longitudinal_file_path": "B05_Profile/route/longitudinal.json"}
|
|
|
|
async def storage(connection, project_id):
|
|
return "proj"
|
|
|
|
def read_inputs(root, long_path, chainage):
|
|
seen["chainage"] = chainage
|
|
return [], 100.0, False, SOURCE
|
|
|
|
monkeypatch.setattr(table_mod, "get_db_pool", lambda: _Pool())
|
|
monkeypatch.setattr(table_mod, "get_longitudinal_section", longitudinal)
|
|
monkeypatch.setattr(table_mod, "get_project_storage_relative_path", storage)
|
|
monkeypatch.setattr(table_mod, "resolve_stored_project_path", lambda rel: "C:/x")
|
|
monkeypatch.setattr(table_mod, "read_cross_design_inputs", read_inputs)
|
|
app = FastAPI()
|
|
app.include_router(table_mod.router)
|
|
client = TestClient(app)
|
|
url = "/api/projects/00000000-0000-0000-0000-000000000001/sections/7/cross-quantity-table"
|
|
response = client.post(url, json={"chainage_m": 40.0, "design": DESIGN})
|
|
assert response.status_code == 200, response.text
|
|
body = response.json()
|
|
assert seen["chainage"] == 40.0
|
|
assert body["quantity_table"]["cut_soil"] == 1.234
|
|
assert body["text"]["planned"] == "100.25"
|
|
|
|
# 측점 파일이 없으면 404 · 사유 그대로
|
|
def missing(*args):
|
|
raise FileNotFoundError("해당 측점의 횡단 상세 파일을 찾을 수 없습니다.")
|
|
|
|
monkeypatch.setattr(table_mod, "read_cross_design_inputs", missing)
|
|
response = client.post(url, json={"chainage_m": 41.0, "design": None})
|
|
assert response.status_code == 404
|
|
assert "횡단 상세 파일" in response.json()["message"]
|