fix(B06): 상세 조회에서 계획을 다시 계산하던 자리를 저장 시점으로 옮김
사용자 확정(2026-09-06): 읽을 때는 영구저장소에서 가져오기만 하고, 계산은 화면(캐시)에서 하며, 저장·확정에서만 영구저장소에 쓴다. - get_section_detail 이 조회할 때마다 돌리던 포장 구간 보정·세월교 노면 하강 보정을 제거. 조회는 저장분을 그대로 싣는다. - 두 보정을 recompute_server_side 로 옮겨 [저장]·[확정]·자동설계 체인에서 정본에 남긴다. 보정된 설계 위에서 구조물 면적·유토곡선이 나오도록 순서를 잡았다. - 설계 미지정 측점 폴백(attach_default_designs)만 조회에 남긴다 — 저장분이 있으면 아무 일도 하지 않는다. 검증: pytest 387 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,8 +42,6 @@ from B06_Section.B06_Section_Router_Design import (
|
||||
from B06_Section.B06_Section_Router_Design import (
|
||||
attach_default_designs as _attach_default_designs,
|
||||
compute_default_designs as _compute_default_designs,
|
||||
enforce_pavement_ranges as _enforce_pavement_ranges,
|
||||
enforce_ford_surface_drops as _enforce_ford_surface_drops,
|
||||
stored_standard_cross_section as _stored_standard_cross_section,
|
||||
pavement_ranges as _pavement_ranges,
|
||||
paved_at as _paved_at,
|
||||
@@ -313,27 +311,14 @@ async def get_section_detail(
|
||||
if abs(float(section.get("chainage_m", 0.0)) - record["chainage_m"]) < 0.01:
|
||||
section["design"] = record["design"]
|
||||
break
|
||||
# 포장 구간·물넘이 범위는 저장분이 비포장이어도 포장으로 맞춘다(2026-08-28).
|
||||
# 표준횡단면은 확정 때 저장해 둔 사용자 값을 쓴다 — 없으면 config 기본값.
|
||||
# 포장 구간·세월교 노면 하강 보정은 **저장 때**로 옮겼다(2026-09-06 사용자 확정:
|
||||
# 「읽을 때는 영구저장소에서 가져오기만」). 조회는 저장분을 그대로 싣는다 —
|
||||
# 보정은 `B06_Section_Server_Calc_Prebuild.recompute_server_side` 가 [저장]·[확정]과
|
||||
# 자동설계 체인에서 돌려 정본에 남긴다.
|
||||
standard = _stored_standard_cross_section(longitudinal)
|
||||
await asyncio.to_thread(
|
||||
_enforce_pavement_ranges,
|
||||
detail["longitudinal"],
|
||||
detail["cross_sections"],
|
||||
project_root,
|
||||
standard,
|
||||
)
|
||||
# 세월교 측점은 구체 위 노면이 월류 높이만큼 낮게 앉는다 — 저장분이 옛 계획고면
|
||||
# 여기서 다시 계산한다(2026-08-30 사용자 확정).
|
||||
await asyncio.to_thread(
|
||||
_enforce_ford_surface_drops,
|
||||
detail["longitudinal"],
|
||||
detail["cross_sections"],
|
||||
project_root,
|
||||
standard,
|
||||
)
|
||||
# 지정값이 없는 측점은 기본값(토사/좌절토)으로 즉석 계산해 프리뷰로 채운다.
|
||||
# (미저장 프리뷰: 실제 저장은 사용자가 카드를 조작하거나 확정할 때 이뤄진다.)
|
||||
# 저장분이 있으면 **아무것도 하지 않는다**(측점마다 design 유무만 본다) — 아직
|
||||
# 저장된 적 없는 비정규 측점만 이 폴백을 탄다.
|
||||
await asyncio.to_thread(
|
||||
_attach_default_designs,
|
||||
detail["longitudinal"],
|
||||
|
||||
@@ -18,17 +18,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
||||
from B06_Section.B06_Section_Repository import (
|
||||
get_longitudinal_section,
|
||||
merge_cross_section_design_patch,
|
||||
merge_longitudinal_section_data,
|
||||
update_cross_section_design,
|
||||
)
|
||||
from common_util.common_util_node_bundle import run_bundle_json
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from config.config_db import get_db_pool
|
||||
from config.config_system import (
|
||||
EARTHWORK_CONVERSION_FACTORS,
|
||||
@@ -57,6 +61,26 @@ def _mass_haul_context() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _enforce_stored_designs(
|
||||
longitudinal: dict[str, Any],
|
||||
sections: list[dict[str, Any]],
|
||||
project_root: Path,
|
||||
standard: dict[str, Any] | None,
|
||||
) -> None:
|
||||
"""저장분 설계를 **쓰는 시점에** 바로잡는다 — 포장 구간·세월교 노면 하강.
|
||||
|
||||
예전에는 상세를 **읽을 때마다** 돌려 화면이 볼 때만 맞았다(저장분은 낡은 채로).
|
||||
2026-09-06 사용자 확정대로 「읽기는 영구저장소에서 가져오기만」이므로 이쪽으로 옮겼다.
|
||||
"""
|
||||
from B06_Section.B06_Section_Router_Design import (
|
||||
enforce_ford_surface_drops,
|
||||
enforce_pavement_ranges,
|
||||
)
|
||||
|
||||
enforce_pavement_ranges(longitudinal, sections, project_root, standard)
|
||||
enforce_ford_surface_drops(longitudinal, sections, project_root, standard)
|
||||
|
||||
|
||||
async def recompute_server_side(project_id: UUID | str, route_id: int) -> int:
|
||||
"""구조물 면적·유토곡선을 다시 계산해 정본에 얹는다. 고친 측점 수를 돌려준다."""
|
||||
from B06_Section.B06_Section_Router import get_section_detail
|
||||
@@ -67,12 +91,33 @@ async def recompute_server_side(project_id: UUID | str, route_id: int) -> int:
|
||||
if payload is None: # JSONResponse = 실패
|
||||
logger.warning("서버 재계산: 종횡단 상세를 못 받음 (route_id=%s)", route_id)
|
||||
return 0
|
||||
detail = payload(mode="json")
|
||||
sections = detail.get("cross_sections") or []
|
||||
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
stored_path = await get_project_storage_relative_path(connection, project_uuid)
|
||||
longitudinal_row = await get_longitudinal_section(connection, project_uuid, route_id)
|
||||
project_root = Path(resolve_stored_project_path(stored_path))
|
||||
from B06_Section.B06_Section_Router_Design import stored_standard_cross_section
|
||||
|
||||
standard = stored_standard_cross_section(longitudinal_row)
|
||||
# 포장 구간·세월교 보정 — 고쳐진 설계 위에서 면적·유토곡선이 나와야 한다.
|
||||
before = [json.dumps(item.get("design"), sort_keys=True, default=str) for item in sections]
|
||||
await asyncio.to_thread(
|
||||
_enforce_stored_designs, detail.get("longitudinal") or {}, sections, project_root, standard
|
||||
)
|
||||
fixed = [
|
||||
item
|
||||
for index, item in enumerate(sections)
|
||||
if json.dumps(item.get("design"), sort_keys=True, default=str) != before[index]
|
||||
]
|
||||
|
||||
output = await asyncio.to_thread(
|
||||
run_bundle_json,
|
||||
BUNDLE,
|
||||
_NPM_SCRIPT,
|
||||
{"detail": payload(mode="json"), "context": _mass_haul_context()},
|
||||
{"detail": detail, "context": _mass_haul_context()},
|
||||
)
|
||||
if not isinstance(output, dict):
|
||||
return 0
|
||||
@@ -80,7 +125,6 @@ async def recompute_server_side(project_id: UUID | str, route_id: int) -> int:
|
||||
mass_haul = output.get("mass_haul")
|
||||
|
||||
updated = 0
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
# balloon 위치는 **사용자가 끌어 옮긴 화면값**이다 — 서버가 만들지 않으므로
|
||||
# 저장분에서 떼어 새 유토곡선에 도로 붙인다(2026-09-06).
|
||||
@@ -93,6 +137,14 @@ async def recompute_server_side(project_id: UUID | str, route_id: int) -> int:
|
||||
|
||||
await connection.begin()
|
||||
try:
|
||||
for item in fixed:
|
||||
await update_cross_section_design(
|
||||
connection,
|
||||
route_id=route_id,
|
||||
chainage_m=float(item.get("chainage_m") or 0.0),
|
||||
design=item["design"],
|
||||
project_id=project_uuid,
|
||||
)
|
||||
for row in rows if isinstance(rows, list) else []:
|
||||
patch: dict[str, Any] = {
|
||||
key: float(row[key])
|
||||
@@ -117,8 +169,9 @@ async def recompute_server_side(project_id: UUID | str, route_id: int) -> int:
|
||||
await connection.rollback()
|
||||
raise
|
||||
logger.info(
|
||||
"서버 재계산: route_id=%s 구조물 측점 %s곳 갱신, 유토곡선 %s",
|
||||
"서버 재계산: route_id=%s 설계 보정 %s곳, 구조물 면적 %s곳, 유토곡선 %s",
|
||||
route_id,
|
||||
len(fixed),
|
||||
updated,
|
||||
"갱신" if isinstance(mass_haul, dict) else "없음",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user