fix(B05): 크로스체크 5건 반영 — 마이그레이션 문자열 판별·서버 검증·기본값 원칙·404·STALE 정합
외부 AI 교차검증 미통과 지적을 전부 수정한다. 1. 마이그레이션: 확정 저장분은 structure 문자열뿐(structureType 없음) — 라벨 파싱 판별 추가(기성막이/대피로 X.Xm/관종 D직경). 명시 필드가 라벨 파싱보다 우선. 2. 서버 검증 강화(_validate_types 확장): 레지스트리 배치형태 대조, 미정의 옵션 거절, number 옵션 유한·0 이상, select 선택지 검사, required 옵션 누락 거절, structure_id 중복 거절, 노선 연장 범위 검증(라우터가 get_latest_route로 총연장 주입, 없으면 생략). 3. 기본값 원칙: 법정 명시값(별표2 측구 30cm·대피소 5/15m 등)·사용자 기확정값(골막이)만 default 유지. 옹벽·돌쌓기 높이, 사토장·토취장 면적/용량, 포장·쇄석 두께 등 미확정 수치는 default 제거 + required (화면 placeholder "필수 입력"+빈 값 추가 차단, 서버도 거절). 4. 404: get_project_storage_relative_path는 없는 프로젝트에서 LookupError를 던짐 — _project_root에서 잡아 None, 라우터 예외 사다리에도 LookupError→404 분기. 5. STALE 정합: _invalidate_downstream이 성공 여부 반환 — invalidated_downstream은 실제 전파 성공 시에만 true. pytest 42건(tmp/tests) 통과 · tsc 0 · ruff 통과. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
최소 구간을 임시로 주고, 사용자가 화면에서 종점을 조정한다 — 값을 지어내는 것보다 낫다.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Any, Iterable
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance, structure_type_map
|
||||
@@ -26,15 +27,50 @@ LEGACY_TYPE_MAP = {
|
||||
# 종점을 모르는 구간형 항목에 주는 임시 길이(m). 화면에서 조정하라는 표시값이다.
|
||||
DEFAULT_INTERVAL_LENGTH_M = 15.0
|
||||
|
||||
# 확정 저장분은 표시 라벨 문자열뿐이다(예: "파형강관 D800", "대피로 2.0m").
|
||||
# 라벨 생성 규칙은 B05_Profile_UI_IrregularStations.ts의 structureLabel()이 정본.
|
||||
_PIPE_LABEL = re.compile(r"^(배관|.*관)\s+D\d+", re.UNICODE)
|
||||
_ESCAPE_LABEL = re.compile(r"^대피로(?:\s+([\d.]+)\s*m)?", re.UNICODE)
|
||||
|
||||
|
||||
def _is_pipe(entry: dict[str, Any]) -> bool:
|
||||
label = str(entry.get("structure", "")).strip()
|
||||
return (
|
||||
entry.get("origin") == "pipe"
|
||||
or entry.get("structureType") == PIPE_STRUCTURE_NAME
|
||||
or str(entry.get("structure", "")).strip() == PIPE_STRUCTURE_NAME
|
||||
or label == PIPE_STRUCTURE_NAME
|
||||
or bool(_PIPE_LABEL.match(label))
|
||||
)
|
||||
|
||||
|
||||
def _legacy_type_of(entry: dict[str, Any]) -> str | None:
|
||||
"""기존 종류 이름을 알아낸다 — structureType이 없으면 라벨 문자열에서 판별한다.
|
||||
|
||||
확정 저장 형식(`{chainage_m, structure}`)에는 structureType이 없다(크로스체크
|
||||
지적 1, 2026-08-16). 라벨은 structureLabel()이 만든 고정 형식이라 되짚을 수 있다.
|
||||
"""
|
||||
explicit = entry.get("structureType")
|
||||
if explicit in LEGACY_TYPE_MAP:
|
||||
return str(explicit)
|
||||
label = str(entry.get("structure", "")).strip()
|
||||
if label == "기성막이":
|
||||
return "기성막이"
|
||||
if _ESCAPE_LABEL.match(label):
|
||||
return "대피로"
|
||||
return None
|
||||
|
||||
|
||||
def _escape_width_of(entry: dict[str, Any]) -> float | None:
|
||||
"""대피로 폭 — 명시 필드가 우선, 없으면 라벨("대피로 2.0m")에서 파싱한다."""
|
||||
width = entry.get("escapeWidthM")
|
||||
if width is not None:
|
||||
return float(width)
|
||||
match = _ESCAPE_LABEL.match(str(entry.get("structure", "")).strip())
|
||||
if match and match.group(1):
|
||||
return float(match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
def migrate_irregular_stations(entries: Iterable[dict[str, Any]]) -> list[StructureInstance]:
|
||||
"""기존 비정규 측점 목록을 구조물 인스턴스로 옮긴다(같은 입력이면 같은 결과)."""
|
||||
types = structure_type_map()
|
||||
@@ -46,8 +82,8 @@ def migrate_irregular_stations(entries: Iterable[dict[str, Any]]) -> list[Struct
|
||||
continue
|
||||
|
||||
chainage = float(entry.get("chainage_m", 0.0))
|
||||
legacy_type = entry.get("structureType")
|
||||
type_id = LEGACY_TYPE_MAP.get(legacy_type, "etc")
|
||||
legacy_type = _legacy_type_of(entry)
|
||||
type_id = LEGACY_TYPE_MAP.get(legacy_type or "", "etc")
|
||||
definition = types[type_id]
|
||||
|
||||
key = (type_id, round(chainage, 3))
|
||||
@@ -75,8 +111,8 @@ def migrate_irregular_stations(entries: Iterable[dict[str, Any]]) -> list[Struct
|
||||
|
||||
def _options_for(type_id: str, entry: dict[str, Any]) -> dict[str, Any]:
|
||||
if type_id == "refuge":
|
||||
width = entry.get("escapeWidthM")
|
||||
return {"width_m": float(width)} if width is not None else {}
|
||||
width = _escape_width_of(entry)
|
||||
return {"width_m": width} if width is not None else {}
|
||||
if type_id == "etc":
|
||||
name = entry.get("customName") or entry.get("structure") or "기타 구조물"
|
||||
return {"name": str(name).strip()}
|
||||
|
||||
Reference in New Issue
Block a user