fix(472 덧 관 지움 ↶): 서버 상세 읽기 관 세트 = 지금 노선에 얹은 관 자리 — 측점 계획 · 관 지점 GET · 화면과 한 벌
- 관 지점 정본 지문이 지금 노선과 어긋난 저장분(검증_sub4 · 파일 394 · 좌표 351) — 측점 계획(_load_pipe_points) · GET 은 351 에 얹고 서버 상세 읽기(attach_saved_shapes)만 파일 누가거리로 찾아 351 관 세트 빠짐 → 새로고침 = 관 세트 없음 · 관 지움 ↶ = 화면이 다시 얹어 관 세트 있음(누가토량 갈림) - saved_pipes(노선 경로) — 상세 GET · 재생성 응답 · 설계 미리보기가 노선 경로를 넘김 · 노선 못 읽으면 저장분 그대로 - 시험 test_472_d_pipe_route_attach(옛 코드 실패) · golden 헬퍼도 같은 읽기 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -141,9 +141,32 @@ def attach_shapes(
|
||||
return misses
|
||||
|
||||
|
||||
def attach_saved_shapes(project_root: Path, sections: list[dict[str, Any]]) -> None:
|
||||
"""저장분 현황(관 지점 · 구조물 정본)으로 얹음 — 구조물 정본을 못 읽으면 관만(횡단 조회는 이어 감)."""
|
||||
pipes = read_pipe_points_file(pipe_points_file(project_root))
|
||||
def saved_pipes(project_root: Path, route_data_path: str | None = None) -> list[Any]:
|
||||
"""관 지점 정본 — 노선 경로를 주면 그 노선에 얹어 읽음(472 덧) · 없으면 파일 그대로.
|
||||
|
||||
측점 계획(`_load_pipe_points`) · 관 지점 GET · 화면과 같은 이월 — 지문이 어긋난 옛 저장분도
|
||||
측점이 선 자리에 관 세트가 얹힘."""
|
||||
if route_data_path:
|
||||
from B05_Profile.B05_Profile_Engine_Sections import (
|
||||
_load_pipe_points,
|
||||
_load_route_polyline,
|
||||
)
|
||||
|
||||
try:
|
||||
return _load_pipe_points(
|
||||
project_root, _load_route_polyline(project_root, route_data_path)
|
||||
)
|
||||
except (FileNotFoundError, ValueError):
|
||||
logger.warning("B06 관 지점: 노선을 못 읽어 저장분 그대로 얹음 (%s)", route_data_path)
|
||||
return read_pipe_points_file(pipe_points_file(project_root))
|
||||
|
||||
|
||||
def attach_saved_shapes(
|
||||
project_root: Path, sections: list[dict[str, Any]], route_data_path: str | None = None
|
||||
) -> None:
|
||||
"""저장분 현황(관 지점 · 구조물 정본)으로 얹음 — 관 = 그 노선에 얹은 것(`saved_pipes`) ·
|
||||
구조물 정본을 못 읽으면 관만(횡단 조회는 이어 감)."""
|
||||
pipes = saved_pipes(project_root, route_data_path)
|
||||
try:
|
||||
structures = load_structures(str(project_root))[1]
|
||||
except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 간다
|
||||
|
||||
@@ -232,7 +232,9 @@ async def get_sections(project_id: UUID, route_id: int) -> SectionSummaryRespons
|
||||
)
|
||||
|
||||
|
||||
def _read_section_detail(project_root: Path, longitudinal_file_path: str) -> dict:
|
||||
def _read_section_detail(
|
||||
project_root: Path, longitudinal_file_path: str, route_data_path: str | None = None
|
||||
) -> dict:
|
||||
"""검증된 프로젝트 루트 안의 종단 및 횡단 JSON을 읽는다."""
|
||||
root = project_root.resolve()
|
||||
longitudinal_path = (root / longitudinal_file_path).resolve()
|
||||
@@ -258,7 +260,7 @@ def _read_section_detail(project_root: Path, longitudinal_file_path: str) -> dic
|
||||
):
|
||||
raise ValueError("종횡단 상세 파일 형식이 올바르지 않습니다.")
|
||||
# 측점 그림 자료(관 세트 · 벽 · 포장 · 맹암거 · 빈칸 경고) = 형상 얹기 한 벌(323-4 · 화면 짝 attachShapes)
|
||||
attach_saved_shapes(root, cross_sections)
|
||||
attach_saved_shapes(root, cross_sections, route_data_path)
|
||||
relabel_pipe_stations(root, cross_sections) # 저장으로 종류만 바꾼 관 — 카드 제목(54-4)
|
||||
return {"longitudinal": longitudinal, "cross_sections": cross_sections}
|
||||
|
||||
@@ -314,10 +316,11 @@ async def get_section_detail(
|
||||
try:
|
||||
# 서로 기다릴 이유가 없는 읽기 셋 — 원격 DB 라 순차로 내면 왕복이 그대로 더해진다
|
||||
# (질의 하나 약 12ms, 2026-09-06 실측). 같이 보내 가장 느린 하나의 시간만 쓴다.
|
||||
longitudinal, stored_path, designs = await asyncio.gather(
|
||||
longitudinal, stored_path, designs, source = await asyncio.gather(
|
||||
run_with_connection(get_longitudinal_section, project_id, route_id),
|
||||
run_with_connection(get_project_storage_relative_path, project_id),
|
||||
run_with_connection(get_cross_section_designs, route_id),
|
||||
run_with_connection(get_route_generation_source, project_id, route_id),
|
||||
)
|
||||
if not longitudinal:
|
||||
return JSONResponse(
|
||||
@@ -329,6 +332,9 @@ async def get_section_detail(
|
||||
_read_section_detail,
|
||||
project_root,
|
||||
str(longitudinal["longitudinal_file_path"]),
|
||||
(source or {}).get(
|
||||
"route_data_path"
|
||||
), # 관 = 이 노선에 얹은 것(측점 계획 · 화면과 같음)
|
||||
)
|
||||
# DB에만 있는 잠정 설계 지정을 chainage 근사로 각 횡단에 얹어 화면 복원을 돕는다.
|
||||
for record in designs:
|
||||
@@ -461,7 +467,7 @@ async def regenerate_route_sections(
|
||||
raise
|
||||
result = sections["result"]
|
||||
# 재생성 응답도 상세 조회와 같은 형상 얹기 한 벌(323-4) — 화면이 어긋나지 않게.
|
||||
attach_saved_shapes(project_root, result["cross_sections"])
|
||||
attach_saved_shapes(project_root, result["cross_sections"], source["route_data_path"])
|
||||
return SectionDetailResponse(
|
||||
longitudinal=result["longitudinal"], cross_sections=result["cross_sections"]
|
||||
)
|
||||
@@ -517,12 +523,14 @@ async def preview_cross_designs(
|
||||
)
|
||||
stored_path = await get_project_storage_relative_path(connection, project_id)
|
||||
designs = await get_cross_section_designs(connection, route_id)
|
||||
source = await get_route_generation_source(connection, project_id, route_id)
|
||||
designs = _with_design_choices(designs, request.design_choices)
|
||||
project_root = Path(resolve_stored_project_path(stored_path))
|
||||
detail = await asyncio.to_thread(
|
||||
_read_section_detail,
|
||||
project_root,
|
||||
str(longitudinal_row["longitudinal_file_path"]),
|
||||
(source or {}).get("route_data_path"),
|
||||
)
|
||||
|
||||
def rebuild() -> None:
|
||||
|
||||
@@ -44,7 +44,7 @@ source: ["docs/raw/조사/2026-10-19_472_구조다시세움_목록.md", "PLAN.md
|
||||
| F1 | 시설 따라감(노선 바뀜) | `common_util_route_follow.ts:291` · `logic/B05_Profile_Pipeline_Follow.ts` · `logic/B05_Profile_Structure_Follow.ts` | `common_util_route_follow.py:120` | — | 짝 · `test_410_follow_xy` · `test_344_2_route_pairs` |
|
||||
| F2 | 추가 측점 따라감 | `B05_Profile_Api_PlusStations.ts` followPlusDraft | `B05_Profile_Plus_Stations.py:82` | — | 짝 · `test_459_route_seat_mirror` |
|
||||
| F3 | 시설 측점 계획 | `common_util_structure_station.ts` | `common_util_structure_station.py` | — | 짝 · `test_322_3_plan_mirror` |
|
||||
| F4 | 형상 얹기(관 세트 · 벽 · 포장 그림 자료) | `common_util_structure_attach.ts:130` | `B06_Section_Engine_Attach.py:114` | — | 짝 · `test_323_4_attach_mirror` |
|
||||
| F4 | 형상 얹기(관 세트 · 벽 · 포장 그림 자료) — 관 = 지금 노선에 얹은 저장분(서버 `saved_pipes` = 측점 계획 · 관 지점 GET 과 같은 이월) | `common_util_structure_attach.ts:130` | `B06_Section_Engine_Attach.py:114` | — | 짝 · `test_323_4_attach_mirror` · `test_472_d_pipe_route_attach` |
|
||||
| F5 | 관 세트(관경 · 날개벽 · 집수정) | `common_util_culvert_sets.ts` · `logic/B06_Section_UI_Cross_Culvert_Const.ts` | `B06_Section_Engine_Culvert.py` | Const = sc · co · ex | 짝 · `test_b06_culvert_sets_mirror` · `test_b06_pipe_wall_height_mirror` |
|
||||
| F6 | 벽 · 포장 | `common_util_structure_walls.ts` · `common_util_structure_pavement.ts` | `B06_Section_Engine_Structures_Wall.py` · `common_util_structure_pavement.py` | — | 짝 · `test_b06_structure_walls_mirror` · `test_b06_structure_pavement_mirror` |
|
||||
| F7 | 관 길이 · 구조물 면적 · 다단 벽 | `B06_Section_Structure_Layouts.ts:95 · 287` · `design_structure_pipe_calc.ts` · `common_util_cross_structure_areas.ts` | — | sc | ② · `test_b06_pipe_length` · `test_299_ford_span_pipe` |
|
||||
|
||||
@@ -234,7 +234,9 @@ def server_route(root: Path) -> dict[str, Any]:
|
||||
entries = compute_default_designs(root, LONG, chainages, None, inputs)
|
||||
by_m = {round(at, 3): {**design, **adjusts.get(round(at, 3), {})} for at, design in entries}
|
||||
# [확정] 상세 읽기(`get_section_detail`) — 행 설계(0.01 m) 얹기 · 없는 측점 기본값
|
||||
detail = _read_section_detail(root, LONG)
|
||||
detail = _read_section_detail(
|
||||
root, LONG, ROUTE
|
||||
) # 관 = 이 노선에 얹은 것(get_section_detail 과 같음)
|
||||
for section in detail["cross_sections"]:
|
||||
hit = next(
|
||||
(d for at, d in by_m.items() if abs(at - float(section["chainage_m"])) < 0.01), None
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""PLAN 472 덧 (요구 272) — 서버 상세 읽기 관 세트 = 측점 계획 · 화면과 같은 관 자리.
|
||||
|
||||
관 지점 정본의 노선 지문이 지금 노선과 어긋남(옛 노선 누가거리 394 · 좌표는 지금 노선 351) —
|
||||
측점 계획(`_load_pipe_points`) · 관 지점 GET · 화면은 좌표로 351 에 얹는데 서버 상세 읽기
|
||||
(`attach_saved_shapes`)만 파일 누가거리 394 로 찾아 351 측점에 관 세트를 못 붙였음(새로고침 =
|
||||
관 세트 없음 · 관 지우기 ↶ = 화면이 다시 얹어 관 세트 있음 → 누가토량 갈림).
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_472_d_pipe_route_attach.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections import ( # noqa: E402
|
||||
_load_pipe_points,
|
||||
_load_route_polyline,
|
||||
)
|
||||
from B06_Section.B06_Section_Engine_Attach import attach_saved_shapes # noqa: E402
|
||||
|
||||
ROUTE = "B05_Profile/route/route_main.geojson"
|
||||
|
||||
|
||||
def _project(root: Path) -> None:
|
||||
route = root / ROUTE
|
||||
route.parent.mkdir(parents=True)
|
||||
route.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "LineString", "coordinates": [[0, 0, 0], [1000, 0, 0]]},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
edits = root / "B04_PreProcess" / "drainage" / "edits"
|
||||
edits.mkdir(parents=True)
|
||||
pipe = {
|
||||
"id": "p35100",
|
||||
"chainage_m": 394.0, # 옛 노선 누가거리
|
||||
"x": 351.0,
|
||||
"y": 0.3,
|
||||
"options": {"pipe_kind": "파형강관", "pipe_diameter_mm": "1000"},
|
||||
}
|
||||
(edits / "pipe_points.json").write_text(
|
||||
json.dumps({"route_signature": "옛-노선", "points": [pipe]}), encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def _sections() -> list[dict]:
|
||||
return [
|
||||
{"chainage_m": at, "center_x": at, "center_y": 0.0, "structure": None}
|
||||
for at in (340.0, 351.0, 360.0, 380.0, 400.0)
|
||||
]
|
||||
|
||||
|
||||
def test_상세_읽기_관_세트_자리_측점_계획과_같음(tmp_path: Path) -> None:
|
||||
_project(tmp_path)
|
||||
planned = _load_pipe_points(tmp_path, _load_route_polyline(tmp_path, ROUTE))
|
||||
assert [round(p.chainage_m, 2) for p in planned] == [351.0] # 측점 계획 = 351 측점
|
||||
|
||||
sections = _sections()
|
||||
attach_saved_shapes(tmp_path, sections, ROUTE)
|
||||
owners = {s["chainage_m"]: s["culvert"]["chainage_m"] for s in sections if "culvert" in s}
|
||||
assert owners == {351.0: 351.0}
|
||||
|
||||
|
||||
def test_노선_모름_저장분_그대로(tmp_path: Path) -> None:
|
||||
_project(tmp_path)
|
||||
sections = _sections()
|
||||
attach_saved_shapes(tmp_path, sections) # 노선 경로 없음 = 파일 누가거리(394 · 제 측점 없음)
|
||||
assert not any("culvert" in s for s in sections)
|
||||
|
||||
|
||||
def test_노선_파일_없음_저장분_그대로(tmp_path: Path) -> None:
|
||||
_project(tmp_path)
|
||||
sections = _sections()
|
||||
attach_saved_shapes(tmp_path, sections, "B05_Profile/route/없음.geojson")
|
||||
assert not any("culvert" in s for s in sections)
|
||||
Reference in New Issue
Block a user