- Source.stations · cross_section: 경로 칸 null · 파일 없음 · 절대 경로 · 옛 정수 m 이름이면 find_cross_file 로 찾음 · 그래도 없으면 「횡단 없음」(stations 에서 빠짐 · cross_section KeyError) · missing_stations 추가 - copy_stability: 같은 검사 → manifest missing_cross_chainages · warnings · E01 생성 응답 warnings - 실측: 검증 프로젝트 14 복사 모두 stations 성공 · 검증_브레인232 null 1 행(359 m) 살림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
5.3 KiB
Python
131 lines
5.3 KiB
Python
"""E01 안정성평가 생성 API (요구 345 · 347 · 요구서 4-1).
|
|
|
|
대상 = 같은 회사 임도(ROAD) 중 B06 횡단 단계(stage 3) 완료. 생성 때 해석에 쓰는 자료만
|
|
`E01_StabRegister/source/` 로 동결 복사(532-1 · 원본이 바뀌어도 불변)
|
|
— 실패하면 만든 행 · 폴더를 지움.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import aiomysql
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from pydantic import BaseModel, Field
|
|
|
|
from common_util.common_util_auth import require_company
|
|
from common_util.common_util_project_copy import copy_project
|
|
from common_util.common_util_project_delete import hard_delete_project
|
|
from common_util.common_util_project_kind import create_kind_project
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
from config.config_db import get_db_pool
|
|
from E02_StabGround.E02_StabGround_Router import seed_initial
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api/e01", tags=["E01_StabRegister"])
|
|
|
|
_SOURCE_SQL = """
|
|
SELECT p.id AS project_id, p.name, p.region, p.road_type, p.project_year,
|
|
DATE_FORMAT(p.updated_at, '%%Y-%%m-%%dT%%H:%%i:%%s') AS updated_at,
|
|
s.state = 'COMPLETE' AS section_complete
|
|
FROM projects p
|
|
LEFT JOIN project_workflow_stages s ON s.project_id = p.id AND s.stage_no = 3
|
|
WHERE p.company_id = %s AND p.project_kind = 'ROAD' AND p.deleted_at IS NULL
|
|
"""
|
|
|
|
|
|
class CreateStabRequest(BaseModel):
|
|
project_name: str | None = Field(default=None, max_length=255)
|
|
source_project_id: str = Field(..., min_length=36, max_length=36)
|
|
|
|
|
|
async def _sources(company_id: Any, project_id: str | None = None) -> list[dict[str, Any]]:
|
|
sql, args = _SOURCE_SQL, [company_id]
|
|
if project_id:
|
|
sql, args = sql + " AND p.id = %s", [company_id, project_id]
|
|
async with (
|
|
get_db_pool().acquire() as connection,
|
|
connection.cursor(aiomysql.DictCursor) as cursor,
|
|
):
|
|
await cursor.execute(sql + " ORDER BY p.updated_at DESC", tuple(args))
|
|
return list(await cursor.fetchall())
|
|
|
|
|
|
@router.get("/source-candidates")
|
|
async def source_candidates(
|
|
session: dict[str, Any] = Depends(require_company),
|
|
) -> dict[str, Any]:
|
|
"""B06 까지 끝난 같은 회사 임도 프로젝트."""
|
|
rows = await _sources(session.get("company_id"))
|
|
projects = [
|
|
{k: v for k, v in r.items() if k != "section_complete"}
|
|
for r in rows
|
|
if r["section_complete"]
|
|
]
|
|
return {"status": "success", "projects": projects}
|
|
|
|
|
|
@router.post("/project")
|
|
async def post_stab_project(
|
|
request: Request,
|
|
payload: CreateStabRequest,
|
|
session: dict[str, Any] = Depends(require_company),
|
|
) -> dict[str, Any]:
|
|
"""안정성평가 프로젝트 생성 — 원본 B06 미완료면 400 · 횡단 없는 측점은 warnings."""
|
|
company_id = session.get("company_id")
|
|
rows = await _sources(company_id, payload.source_project_id) if company_id else []
|
|
if not rows:
|
|
raise HTTPException(status_code=404, detail="같은 회사 임도 프로젝트가 아닙니다.")
|
|
source = rows[0]
|
|
if not source["section_complete"]:
|
|
raise HTTPException(
|
|
status_code=400, detail="원본 임도의 횡단 설계(B06)가 끝나지 않았습니다."
|
|
)
|
|
name = (payload.project_name or "").strip() or f"{source['name']} 안정성평가"
|
|
project_id = await create_kind_project(
|
|
request=request,
|
|
user_id=int(session["user_id"]),
|
|
company_id=int(company_id),
|
|
kind="STABILITY",
|
|
name=name,
|
|
region=source["region"],
|
|
road_type=source["road_type"],
|
|
project_year=source["project_year"],
|
|
source_project_id=source["project_id"],
|
|
)
|
|
try:
|
|
manifest = await _copy_source(project_id, source["project_id"])
|
|
await seed_initial(project_id) # E02 초기값(기본값 + 복사본 B06 측점)
|
|
except Exception:
|
|
logger.exception("E01 복사 실패 — 만든 프로젝트를 지움: %s", project_id)
|
|
await hard_delete_project(project_id, int(session["user_id"]), request)
|
|
raise HTTPException(status_code=500, detail="원본 자료 복사에 실패했습니다.") from None
|
|
return {
|
|
"project_id": project_id,
|
|
"source_fingerprint": manifest["source_fingerprint"],
|
|
"warnings": manifest.get("warnings", []),
|
|
}
|
|
|
|
|
|
async def _copy_source(project_id: str, source_id: str) -> dict[str, Any]:
|
|
"""원본 → E 복사본 + 지문 · 복사 시각 기록."""
|
|
async with get_db_pool().acquire() as connection:
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute("SELECT storage_path FROM projects WHERE id = %s", (project_id,))
|
|
(storage_path,) = await cursor.fetchone()
|
|
manifest = await copy_project(
|
|
connection,
|
|
source_id,
|
|
scope="stability",
|
|
new_id=project_id,
|
|
project_root=Path(resolve_stored_project_path(storage_path)),
|
|
)
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"UPDATE projects SET source_fingerprint = %s, source_copied_at = %s WHERE id = %s",
|
|
(manifest["source_fingerprint"], datetime.utcnow(), project_id),
|
|
)
|
|
return manifest
|