- 기본값 정본 resources/master_template/stability/ground_defaults.json — 지층 여섯 · 값마다 출처(07 표 · 절) · KDS 11 70 05 표 4.3-1~3 · 노면 10 kPa · 지하수 규칙 · 지진 끔 · 범위
- 07 원문 대조 — 성토 φ 25 → 28(07 §4 종합 28~30 아래값) · 나머지 범위 안
- API GET · PUT /api/projects/{id}/stab/ground · POST …/reset — 작업본 E02_StabGround/ground.json · 초기값 initial_snapshot/E02_StabGround/ground.json(E 생성 직후 기본값 + B06 측점 암 경계) · 측점은 서버 값
- 검사 · 측점 적용 = 파이썬 · TS 짝(E02_StabGround_Model.py ↔ logic/E02_StabGround_Check.ts) · 거울 시험
- 화면 — 카드 넷 · 칸 위 근거 · 기본값 툴팁 · 틀린 칸 표시 · sessionStorage 캐시 · 자동저장 없음
- test_528 E01 시험 — 생성 뒤 E02 초기값 부름 확인 더함
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
157 lines
6.6 KiB
Python
157 lines
6.6 KiB
Python
"""E02 지반 조건 API (요구 347-2 · 3 · 4 · PLAN 532-3).
|
|
|
|
- `GET /api/projects/{id}/stab/ground` — 작업본(없으면 초기값) + 기본값 + 고칠 수 있는지
|
|
- `PUT …/stab/ground` — [저장] 검사 뒤 작업본 `E02_StabGround/ground.json`
|
|
· 측점(B06 암 경계)은 서버 값
|
|
- `POST …/stab/ground/reset` — [초기화] 초기값을 작업본에 덮어씀(재계산 아님)
|
|
|
|
초기값 = E 생성 직후 마스터 기본값 + B06 측점(`ensure_initial`) · 그 밖에는 쓰지 않음.
|
|
공유 회사(보기만)는 `require_project_access` 가 PUT · POST 를 막음.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import aiomysql
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
|
|
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
|
from B05_Profile.B05_Profile_Repository import get_latest_route
|
|
from common_util.common_util_auth import verify_session
|
|
from common_util.common_util_json import atomic_write_json
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
from config.config_db import get_db_pool
|
|
from config.config_system_design import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
|
|
from E02_StabGround import E02_StabGround_Model as model
|
|
|
|
router = APIRouter(prefix="/api/projects", tags=["E02 Stab Ground"])
|
|
|
|
|
|
class GroundRequest(BaseModel):
|
|
ground: dict[str, Any]
|
|
|
|
|
|
async def _b06_stations(connection: aiomysql.Connection, project_ids: list[str]) -> list[dict]:
|
|
"""측점 · 지반 라벨 · 토층 두께(암 측점 = 암 경계 깊이 · 토사 = 없음).
|
|
|
|
자기 복사본(532-1)이 먼저 · 없으면 원본 임도."""
|
|
for project_id in project_ids:
|
|
route = await get_latest_route(connection, UUID(project_id))
|
|
if not route:
|
|
continue
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"SELECT chainage_m, data FROM cross_sections WHERE route_id = %s"
|
|
" ORDER BY chainage_m",
|
|
(route["id"],),
|
|
)
|
|
rows = await cursor.fetchall()
|
|
stations = []
|
|
for chainage, data in rows:
|
|
data = json.loads(data) if isinstance(data, str) else data or {}
|
|
design = data.get("design") or {}
|
|
ground_type = design.get("ground_type") or "soil"
|
|
offset = design.get("rock_boundary_offset_m", STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M)
|
|
rock = ground_type in model.ROCK_LABELS and offset is not None
|
|
stations.append(
|
|
{
|
|
"chainage_m": round(float(chainage), 3),
|
|
"ground_type": ground_type,
|
|
"soil_thickness_m": round(abs(float(offset)), 3) if rock else None,
|
|
}
|
|
)
|
|
if stations:
|
|
return stations
|
|
return []
|
|
|
|
|
|
async def _project(project_id: UUID) -> tuple[Path, dict[str, Any]]:
|
|
async with get_db_pool().acquire() as connection:
|
|
try:
|
|
stored = await get_project_storage_relative_path(connection, project_id)
|
|
except Exception as error:
|
|
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.") from error
|
|
async with connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"SELECT company_id, project_kind, source_project_id FROM projects WHERE id = %s",
|
|
(str(project_id),),
|
|
)
|
|
project = await cursor.fetchone()
|
|
if not project or project["project_kind"] != "STABILITY":
|
|
raise HTTPException(status_code=400, detail="안정성평가 프로젝트가 아닙니다.")
|
|
return Path(resolve_stored_project_path(stored)), project
|
|
|
|
|
|
async def ensure_initial(project_root: Path, project_id: str, source_id: str | None) -> dict:
|
|
"""초기값이 없을 때만 한 번 — 기본값 + B06 측점(E 생성 직후 · 옛 E 프로젝트는 첫 GET)."""
|
|
path = model.initial_path(project_root)
|
|
if path.is_file():
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
async with get_db_pool().acquire() as connection:
|
|
ids = [project_id] + ([source_id] if source_id else [])
|
|
stations = await _b06_stations(connection, ids)
|
|
ground = model.build_initial(model.load_defaults(), stations)
|
|
atomic_write_json(path, ground)
|
|
return ground
|
|
|
|
|
|
async def seed_initial(project_id: str) -> None:
|
|
"""E01 생성 직후 부름 — 초기값 한 벌."""
|
|
root, project = await _project(UUID(project_id))
|
|
await ensure_initial(root, project_id, project["source_project_id"])
|
|
|
|
|
|
def _read_working(project_root: Path) -> dict[str, Any] | None:
|
|
path = model.ground_path(project_root)
|
|
return json.loads(path.read_text(encoding="utf-8")) if path.is_file() else None
|
|
|
|
|
|
def _editable(session: dict[str, Any], project: dict[str, Any]) -> bool:
|
|
return session["role"] == "SYSTEM_ADMIN" or session.get("company_id") == project["company_id"]
|
|
|
|
|
|
@router.get("/{project_id}/stab/ground")
|
|
async def get_ground(
|
|
project_id: UUID, session: dict[str, Any] = Depends(verify_session)
|
|
) -> dict[str, Any]:
|
|
root, project = await _project(project_id)
|
|
initial = await ensure_initial(root, str(project_id), project["source_project_id"])
|
|
working = _read_working(root)
|
|
return {
|
|
"ground": working or initial,
|
|
"saved": working is not None,
|
|
"defaults": model.load_defaults(),
|
|
"editable": _editable(session, project),
|
|
}
|
|
|
|
|
|
@router.put("/{project_id}/stab/ground")
|
|
async def put_ground(project_id: UUID, payload: GroundRequest) -> dict[str, Any]:
|
|
root, project = await _project(project_id)
|
|
defaults = model.load_defaults()
|
|
errors = model.check_ground(payload.ground, defaults["limits"])
|
|
if errors:
|
|
raise HTTPException(status_code=422, detail={"errors": errors})
|
|
ground = model.clean(payload.ground)
|
|
# 측점(B06 암 경계)은 서버 값 — 브라우저 값을 받아 적지 않음
|
|
base = _read_working(root) or await ensure_initial(
|
|
root, str(project_id), project["source_project_id"]
|
|
)
|
|
ground["stations"] = base.get("stations", [])
|
|
ground["version"] = defaults["version"]
|
|
atomic_write_json(model.ground_path(root), ground)
|
|
return {"ground": ground}
|
|
|
|
|
|
@router.post("/{project_id}/stab/ground/reset")
|
|
async def reset_ground(project_id: UUID) -> dict[str, Any]:
|
|
root, project = await _project(project_id)
|
|
initial = await ensure_initial(root, str(project_id), project["source_project_id"])
|
|
atomic_write_json(model.ground_path(root), initial)
|
|
return {"ground": initial}
|