feat(B05·B06·B07): 소수 측점 횡단 — 관 · 구조물 자리마다 따로 · 이름 소수 2자리 · 화면 표기만 정수
PLAN 20장 소수 측점 횡단(sub6). 사용자 확정: 관 · 구조물이 놓인 소수 측점(예 119.8)에 그 자리 횡단을
따로 둠 · 백엔드 · 파일 · 저장값은 소수 그대로 · 화면 표기만 「측점 + 거리」의 거리를 정수로(5+20).
- 이름 규칙 한 벌(B05_Profile_Engine_Sections): cross_key · cross_filename · parse_cross_key ·
CROSS_KEY_PATTERN — 정수 m 는 옛 이름 그대로(cross_00120m) · 소수 측점은 cross_00119.80m.
find_cross_file = 읽기용(새 이름이 없으면 옛 정수 m 이름) · prune_stale_cross_files 도 이것으로
가려 옛 프로젝트의 정수 이름 파일을 지우지 않음
- generate_sections: 정수 m 격자 붙임을 풂 — 같은 이름이 되는 0.005m 안 겹침만 하나로
- B06: 찾기 · 패치 입력(Router_Design 둘) · 샘플 넓힘(SampleExtend)이 find_cross_file ·
DB 설계 찾기 · 병합(get_cross_section_design · merge_…_by_round)은 ROUND(chainage_m)=정수 →
0.005m 안 매칭
- B07: 도면 id · 장 id(cross_{key}m · cross_s{key}m) 정규식 · 측점 키(cross_station — 정수는 int
그대로라 옛 id · 수량표 키 안 바뀜) · 측점은 파일 속 값으로(옛 정수 이름 파일도) · 횡단 파일 찾기
- 화면 표기 한 벌 common_util/common_util_station.ts(stationLabel · stationPartsLabel · 순수 함수) —
common_util_svg 가 다시 내보내고 B05 formatStation 을 대신함(B05 목록 · 표시 · 폼 범위 · 계획노선
편집 · 비정규 측점 라벨 · B06 카드 · 종단 · 유토) · 입력 칸 · 저장값은 소수 그대로
- B05_Profile_UI_Structures_Panel 700줄 → 690(순수 도우미 셋을 Panel_Types 로)
- M02_Table_Fill 관연장 찾기는 이미 측점 값(정확 먼저 · 0.5m 안)이라 그대로
시험: test_b05_station_roundtrip(표기 5+20 · 옛 정수 이름 찾기 · 안 지움 · 119.8 과 120 따로) ·
test_b05_extra_stations_supplier(새 규칙) · test_b06_patch_station(119.8 한 건) ·
test_b05_b06_save_roundtrip(보낸 관 측점마다 소수 이름 횡단 파일 있음) 등 고친 대상 108 통과 ·
test_b05_station_merge 통과 · 타입검사 통과.
ORCA(IP 5181 · 사본 프로젝트 sub6_검증 — 끝나고 DB 행 · 폴더 지움 · 원본 안 씀): 노출형 횡단수로를
5+19.8 에 놓고 저장 → structures.json 119.8 · 종단 측점 119.8(irregular) + 120 · 파일
cross_00119.80m.json 과 cross_00120m.json · B05 목록 「5+20」 · B06 카드 「5+20 · 119.8m」 열림.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVM3xZ1aUPcUTuW2WZSnwg
This commit is contained in:
@@ -13,6 +13,11 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
||||
from B05_Profile.B05_Profile_Engine_Sections import (
|
||||
CROSS_KEY_PATTERN,
|
||||
cross_station,
|
||||
parse_cross_key,
|
||||
)
|
||||
from B06_Section.B06_Section_Repository import (
|
||||
get_confirmed_route_context,
|
||||
get_cross_section_design,
|
||||
@@ -65,7 +70,7 @@ from config.config_db import get_db_pool
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/projects", tags=["B07 Design Detail"])
|
||||
|
||||
_CROSS_ID = re.compile(r"^cross_(\d+)m$")
|
||||
_CROSS_ID = re.compile(rf"^cross_{CROSS_KEY_PATTERN}m$")
|
||||
_LONG_ID = re.compile(r"^longitudinal(?:_(\d+))?$")
|
||||
|
||||
|
||||
@@ -249,13 +254,13 @@ async def title_block_fields(project_id: UUID) -> dict[str, str]:
|
||||
return {key: str(value) for key, value in fields.items() if value}
|
||||
|
||||
|
||||
async def _designs_by_chainage(route_id: int) -> dict[int, dict[str, Any]]:
|
||||
async def _designs_by_chainage(route_id: int) -> dict[int | float, dict[str, Any]]:
|
||||
"""노선 전체의 측점별 설계 지정 {측점(m): design}. 장 배치·목록이 함께 쓴다."""
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
rows = await get_cross_section_designs(connection, route_id)
|
||||
return {
|
||||
int(round(float(row["chainage_m"]))): row["design"]
|
||||
cross_station(row["chainage_m"]): row["design"]
|
||||
for row in rows
|
||||
if isinstance(row, dict) and isinstance(row.get("design"), dict)
|
||||
}
|
||||
@@ -304,7 +309,7 @@ async def get_design_drawing(
|
||||
pool = get_db_pool()
|
||||
async with pool.acquire() as connection:
|
||||
design = await get_cross_section_design(
|
||||
connection, route_id, int(cross_match.group(1))
|
||||
connection, route_id, parse_cross_key(cross_match.group(1))
|
||||
)
|
||||
source_design = design
|
||||
elif CROSS_SHEET_ID.fullmatch(drawing_id):
|
||||
@@ -448,25 +453,25 @@ async def confirm_design_drawing(
|
||||
# 트림과 사용자 입력(측구 끔)이 사라졌다(2026-09-14 936be972 실측 62측점 · 브레인 ②).
|
||||
cross_match = _CROSS_ID.fullmatch(drawing_id)
|
||||
pool = get_db_pool()
|
||||
targets: list[int] = []
|
||||
targets: list[int | float] = []
|
||||
if sheet is not None:
|
||||
targets = list(sheet["chainages"])
|
||||
elif item.kind == "cross" and cross_match:
|
||||
targets = [int(cross_match.group(1))]
|
||||
targets = [parse_cross_key(cross_match.group(1))]
|
||||
confirmed_designs = [
|
||||
(chainage_int, {**designs[chainage_int], "status": "confirmed"})
|
||||
for chainage_int in targets
|
||||
if designs.get(chainage_int)
|
||||
(chainage, {**designs[chainage], "status": "confirmed"})
|
||||
for chainage in targets
|
||||
if designs.get(chainage)
|
||||
]
|
||||
|
||||
async with pool.acquire() as connection:
|
||||
await connection.begin()
|
||||
try:
|
||||
for chainage_int, _design in confirmed_designs:
|
||||
for chainage, _design in confirmed_designs:
|
||||
await merge_cross_section_design_by_round(
|
||||
connection,
|
||||
route_id=route_id,
|
||||
chainage_int=chainage_int,
|
||||
chainage=chainage,
|
||||
patch={"status": "confirmed"},
|
||||
)
|
||||
async with connection.cursor() as cursor:
|
||||
@@ -519,9 +524,9 @@ async def invalidate_design_drawing(
|
||||
await asyncio.to_thread(_invalidate_drawing, project_root, drawing_id)
|
||||
|
||||
cross_match = _CROSS_ID.fullmatch(drawing_id)
|
||||
stale: list[int] = []
|
||||
stale: list[int | float] = []
|
||||
if cross_match:
|
||||
stale = [int(cross_match.group(1))]
|
||||
stale = [parse_cross_key(cross_match.group(1))]
|
||||
elif CROSS_SHEET_ID.fullmatch(drawing_id):
|
||||
plan = await asyncio.to_thread(
|
||||
_cross_sheet_plan, project_root, longitudinal_path, designs
|
||||
@@ -533,11 +538,11 @@ async def invalidate_design_drawing(
|
||||
await connection.begin()
|
||||
try:
|
||||
# 확정 도면을 편집하면 담긴 측점 설계도 잠정 상태로 되돌린다.
|
||||
for chainage_int in stale:
|
||||
for chainage in stale:
|
||||
await merge_cross_section_design_by_round(
|
||||
connection,
|
||||
route_id=route_id,
|
||||
chainage_int=chainage_int,
|
||||
chainage=chainage,
|
||||
patch={"status": "provisional"},
|
||||
)
|
||||
async with connection.cursor() as cursor:
|
||||
|
||||
Reference in New Issue
Block a user