Files
Aislo/B05_Profile/B05_Profile_Engine_Sections_Names.py
T
eomsangdonandClaude Opus 5.5 b0e38de44c refactor(476-1 ②): 측점 · 1차 계획선 셈과 파일 쓰기 나눔(동작 같음)
- Engine_Sections `build_sections`(측점 틀 · 지반 · 438 1차 계획선 · 포장 제안 · 파일 안 씀) · run_section_generation = 읽기 → 셈 → 쓰기
- 횡단 이름 규칙 → 새 `B05_Profile_Engine_Sections_Names`(700줄 · 다시 내보냄)
- resolve_extra_stations 사본 칸(structures · plus) · 따라감 셈 몫 `followed_structures` · `followed_plus_rows` · `followed_pipes` · `section_seed` · `seat_base_pvi` 뗌
- 체인 · 추천 초기 종단이 같은 조각을 부를 자리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:36:26 +09:00

83 lines
3.6 KiB
Python

"""횡단 이름 규칙 한 벌 — 파일 이름 · B07 도면 id · 찾기 · 잔재 거름.
`B05_Profile_Engine_Sections` 에서 나눔(700줄) · 그 모듈이 다시 내보냄."""
import re
from pathlib import Path
from typing import Any
# ── 횡단 이름 규칙(한 벌) — 파일 이름 · B07 도면 id · 찾기가 모두 이것을 씀 ────────────
#: 이름 속 측점 글 — 정수 m `00120` · 소수 측점 `00119.80`
CROSS_KEY_PATTERN = r"(\d+(?:\.\d{2})?)"
def cross_station(chainage_m: float) -> int | float:
"""측점 → 이름 열쇠 값 — 정수 m(0.005m 안)면 int(옛 이름 · 키 그대로) · 아니면 소수 2자리."""
value = round(float(chainage_m), 2)
whole = round(value)
return int(whole) if abs(value - whole) < 0.005 else value
def cross_key(chainage_m: float) -> str:
"""이름 속 측점 글 — `00120`(정수 · 옛 이름 그대로) · `00119.80`(소수 측점)."""
station = cross_station(chainage_m)
return f"{station:05d}" if isinstance(station, int) else f"{station:08.2f}"
def parse_cross_key(text: str) -> int | float:
"""`cross_key` 되읽기."""
return cross_station(float(text))
def cross_filename(chainage_m: float) -> str:
"""측점 chainage에 대응하는 횡단면 파일명(단일 규칙)."""
return f"cross_{cross_key(chainage_m)}m.json"
def find_cross_file(cross_dir: Path, chainage_m: float) -> Path:
"""읽기용 — 지금 이름이 없으면 옛 정수 m 이름(소수 측점이 정수로 붙던 옛 프로젝트)."""
path = cross_dir / cross_filename(chainage_m)
legacy = cross_dir / f"cross_{int(round(float(chainage_m))):05d}m.json"
return path if path.exists() or not legacy.exists() else legacy
def valid_cross_names(cross_dir: Path, stations: list[Any]) -> set[str]:
"""stations 에 맞는 cross_*.json 파일명 집합 — 읽기 전용(지우지 않음 · GET 이 씀).
측점 정보가 비면 빈 집합(호출부는 빈 집합이면 필터를 생략한다).
"""
# 옛 프로젝트는 소수 측점 파일이 정수 m 이름이라 찾기 규칙(find_cross_file)으로 가림
return {
find_cross_file(cross_dir, station["chainage_m"]).name
for station in stations
if isinstance(station, dict) and isinstance(station.get("chainage_m"), (int, float))
}
def cross_files_in_order(cross_dir: Path, stations: list[Any]) -> list[Path]:
"""잔재를 걸러 읽을 cross_*.json — **측점 차례**(이름 차례면 `01080.16` 이 `01080` 앞)."""
valid = valid_cross_names(cross_dir, stations)
def chainage(path: Path) -> tuple[float, str]:
match = re.fullmatch(rf"cross_{CROSS_KEY_PATTERN}m", path.stem)
return (float(match.group(1)) if match else float("inf"), path.name)
files = [path for path in cross_dir.glob("cross_*.json") if not valid or path.name in valid]
return sorted(files, key=chainage)
def prune_stale_cross_files(cross_dir: Path, stations: list[Any]) -> set[str]:
"""stations에 없는 잔재 cross_*.json을 삭제하고 유효 파일명 집합을 반환한다.
쓰는 쪽(측점 만들기 · 측점 목록 고침)만 부른다 — 조회(GET)는 `valid_cross_names`.
측점 정보가 비어 있으면 오삭제를 피하기 위해 아무것도 지우지 않고
빈 집합을 반환한다(호출부는 빈 집합이면 필터를 생략한다).
"""
valid = valid_cross_names(cross_dir, stations)
if not valid:
return valid
for path in cross_dir.glob("cross_*.json"):
if path.name not in valid:
path.unlink(missing_ok=True)
return valid