feat(B03,B04,B05): 초기 설계가 도는 동안 B05·B06 진입을 막는다
파일을 올리면 지표면 확정 직후 "노선 설계로 이동하세요"를 적고 그 뒤에 초기 설계 체인이 돈다. 그 사이 B05에 들어가 만지면 반쯤 계산된 값을 편집하게 되고, 그 편집이 섞인 채 초기값 스냅샷이 찍힌다. 계산이 끝날 때까지 준비 화면(B11)에서 기다리게 한다. - initial_snapshot: 마커(initial_design.lock) 생성·해제·조회와 스냅샷 무효화, 편집 정본 제거를 한곳에 둔다. 마커는 스냅샷 4트리 밖(프로젝트 루트)이라 복원에 딸려 들어가지 않는다. - 체인 두 곳(자동·재설계): 시작에 마커, finally에서 해제. 재설계 체인은 끝에 스냅샷을 무효화한다 — 사용자 입력을 유지한 채 재계산하므로 그 결과를 찍으면 가짜 초기값이 된다. 다음 [초기화]가 폴백을 타며 새 초기값을 만들고 그때 촬영한다. - /surface/status: 마커가 있으면 in_progress + initial_design으로 답한다. stage 1이 COMPLETE면 진행률 파일을 무시하는 기존 분기를 건드리지 않으려고 별도 확인이다. - 진입 게이트(routeAfterPreloadCheck)와 B11이 그 상태를 보고 기다린다. 상태 조회가 실패하면 막지 않고, B11 대기는 20분 상한을 둔다. - [초기화] 폴백: 체인 전에 structures.json과 drainage/edits를 지운다. 남기면 구조물 측점이 사용자 편집분에서 다시 파생돼 초기값이 오염된다(2026-08-29 실측). 테스트 4건 추가. 249 passed·8 failed(기슭막이 이관 때 생긴 기존 실패, 변경 전 동일).
This commit is contained in:
@@ -19,6 +19,8 @@ import aiomysql
|
||||
# 스냅샷 폴더는 워크플로우 단계가 아니므로 PROJECT_STORAGE_LAYOUT_V2에 넣지 않는다.
|
||||
SNAPSHOT_DIRNAME = "initial_snapshot"
|
||||
_DB_DUMP_NAME = "db.json"
|
||||
# 초기 설계 체인이 도는 동안만 존재하는 마커(진입 차단 판정용).
|
||||
DESIGNING_LOCK_NAME = "initial_design.lock"
|
||||
|
||||
# 프로젝트 루트 기준 상대 경로 — 정본 파일이 사는 자리 전부.
|
||||
_FILE_TREES = (
|
||||
@@ -36,6 +38,51 @@ def snapshot_dir(project_root: Path) -> Path:
|
||||
return Path(project_root) / SNAPSHOT_DIRNAME
|
||||
|
||||
|
||||
def designing_lock_path(project_root: Path) -> Path:
|
||||
"""초기 설계 체인이 도는 동안만 존재하는 마커.
|
||||
|
||||
이게 있으면 B05·B06은 아직 들어갈 때가 아니다(CLAUDE.md 5장 — 계산 중 편집이 섞이면
|
||||
초기값이 오염된다). 스냅샷 대상 4트리 **밖**인 프로젝트 루트에 두어 복원에 딸려
|
||||
들어가지 않게 한다. `workflow_state`의 stage 2는 체인이 끝나도 IN_PROGRESS라 판정에
|
||||
못 쓰고, route 존재 여부도 체인 3단계에서 이미 생겨 못 쓴다.
|
||||
"""
|
||||
return Path(project_root) / DESIGNING_LOCK_NAME
|
||||
|
||||
|
||||
def is_designing(project_root: Path) -> bool:
|
||||
return designing_lock_path(project_root).is_file()
|
||||
|
||||
|
||||
def mark_designing(project_root: Path) -> None:
|
||||
path = designing_lock_path(project_root)
|
||||
try:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text("designing", encoding="utf-8")
|
||||
except OSError:
|
||||
pass # 마커 실패가 체인을 막지는 않는다 — 문이 일찍 열릴 뿐이다.
|
||||
|
||||
|
||||
def clear_designing(project_root: Path) -> None:
|
||||
try:
|
||||
designing_lock_path(project_root).unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def discard_initial_snapshot(project_root: Path) -> bool:
|
||||
"""초기값을 무효화한다 — 지표면·노선이 바뀌어 옛 초기값이 더는 기준이 아닐 때.
|
||||
|
||||
그 자리에서 다시 뜨지 않는다. 재설계 체인은 **사용자 입력을 유지한 채** 재계산하므로
|
||||
그 결과를 찍으면 사용자 편집이 섞인 가짜 초기값이 된다. 지워 두면 다음 [초기화]가
|
||||
재계산 폴백을 타면서 새 초기값을 만들고, 체인이 그때 촬영한다(2026-08-29 사용자 확정).
|
||||
"""
|
||||
target = snapshot_dir(Path(project_root))
|
||||
if not target.exists():
|
||||
return False
|
||||
shutil.rmtree(target, ignore_errors=True)
|
||||
return True
|
||||
|
||||
|
||||
def has_initial_snapshot(project_root: Path) -> bool:
|
||||
return (snapshot_dir(project_root) / _DB_DUMP_NAME).is_file()
|
||||
|
||||
@@ -93,6 +140,31 @@ async def save_initial_snapshot(
|
||||
(target / _DB_DUMP_NAME).write_text(json.dumps(dump, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
|
||||
def wipe_edited_masters(project_root: Path) -> list[str]:
|
||||
"""사용자 편집 정본을 걷어낸다 — 재계산으로 **진짜 초기값**을 만들기 위한 사전 정리.
|
||||
|
||||
자동설계 체인은 계획노선 CSV와 config 기본값으로 도는 결정적 절차라, 이 둘만 없으면
|
||||
원래 나왔어야 할 값이 나온다. 남겨 두면 `resolve_extra_stations`가 사용자가 편집한
|
||||
관·구조물에서 측점을 다시 파생해 "초기값"이 오염된다(2026-08-29 실측에서 확인).
|
||||
|
||||
스냅샷 복원 경로에서는 부르지 않는다 — 거기서는 스냅샷본이 통째로 덮어쓴다.
|
||||
"""
|
||||
removed: list[str] = []
|
||||
root = Path(project_root)
|
||||
for rel in ("B05_Profile/route/structures.json", "B04_PreProcess/drainage/edits"):
|
||||
path = root / rel
|
||||
try:
|
||||
if path.is_dir():
|
||||
shutil.rmtree(path)
|
||||
removed.append(rel)
|
||||
elif path.is_file():
|
||||
path.unlink()
|
||||
removed.append(rel)
|
||||
except OSError:
|
||||
pass # 지우지 못하면 그만큼 초기값이 덜 깨끗할 뿐, 재계산은 계속한다.
|
||||
return removed
|
||||
|
||||
|
||||
def restore_snapshot_files(project_root: Path) -> None:
|
||||
"""스냅샷 파일을 작업본 자리에 되돌린다. 스냅샷에 없던 트리는 비워 둔다."""
|
||||
root = Path(project_root)
|
||||
|
||||
Reference in New Issue
Block a user