chore(요구): 조사 AO — 312 관(자동 횡단배수) 빈 목록이 「없음」으로 읽혀 마지막 하나가 되살아남(원인 넷 · 고칠 자리 제안)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
This commit is contained in:
2026-10-13 18:35:54 +09:00
co-authored by Claude Opus 5.5
parent 8cf68188b7
commit e12c4d6c28
@@ -0,0 +1,72 @@
# 312 분석 — B05 · B06 좌측 구조물 목록: 마지막 하나 지움이 새로고침 뒤 되살아남
sub5 · 조사만 · 코드 고치지 않음 · 2026-10-13
## 한 줄 결론
관(자동 생성 횡단배수 = `pipe_points.json`) 목록에서 **「빈 목록」이 세 군데에서 「값 없음」으로 읽힘** — 화면 초안 읽기 · 서버 계산 · 서버 저장. 그래서 마지막 관을 지우면 화면은 자동 배치로 다시 차고(「초기화」), 새로고침 · [저장] 뒤에는 저장돼 있던 마지막 하나가 되살아남. 손으로 놓은 구조물(`structures.json`)은 빈 목록을 바르게 다룸 — 흠 없음.
## 재현
방법 — 검증_sub5(`bec7fe41-…`) storage **사본**(임시 폴더) + DB 읽기 전용 세션으로 서버 API 를 화면 차례대로 부름(기존 `test_b05_b06_save_roundtrip.py` 와 같은 틀). 화면(ORCA)은 띄우지 않음 — 화면 쪽은 코드 길을 따라 확인.
시작 상태 — 저장된 관 11개(전부 자동 배치 `spacing`·`stream`) · 구조물 0개.
| 단계 | 화면에서 하는 일 | 서버 요청 | 결과 |
|---|---|---|---|
| P1 | 하나씩 지우고 [저장] → 2개 | PUT pipe-points [2개] | 저장 2 · 파일 2 ✔ |
| P2 | 하나 더 지우고 [저장] → 1개 | PUT [1개] | 저장 1 · 파일 1 ✔ |
| P3 | **마지막 하나 지움** | POST detail-basins `[]` (화면 다시 나눔) | **응답 관 11개 = 자동 배치** → 목록 「초기화」 |
| P4 | 새로고침([저장] 안 함) | 초안 `[]` 를 「없음」으로 봄 → GET pipe-points | **1개(85.05) 되살아남** |
| P5 | [저장] | PUT `[]` | **저장 1 · 파일 1 그대로** (빈 목록 안 써짐) |
| P6 | (파일이 `points: []` 라면) | GET | **자동 배치 11개 · saved=false** |
| S1~S3 | 손으로 놓은 구조물 1개 → 지우고 [저장] → 새로고침 | PUT structures `[]` → GET | 0개 ✔ (정상) |
화면 길별 결과(코드 추적):
| 화면 | [저장] 안 함 · 새로고침 | [저장] 함 |
|---|---|---|
| B05 (배수유역도 · 좌측 목록) | 지운 직후 자동 11개로 참(P3) · 새로고침 → 저장분 1개(P4) | 관 초안 `[]` PUT → 서버가 저장분 1개 유지(P5) · 이어서 `side.savePipes()` 가 **화면 목록(자동 11개)을 PUT** → 자동 11개가 정본에 저장됨 |
| B06 (구조물 목록) | B06 목록은 `??` 라 빈 목록 유지 · 그러나 초안 쓰기 뒤 `side.reload()` → 배수유역도가 저장분 1개를 다시 읽어 B05 쪽 목록 · 종단 알약에 1개 | 관 초안 `[]` PUT → 1개 유지 · `side.savePipes()` 가 배수유역도 목록(1개) PUT → 1개 되살아남 |
## 원인 — 「빈 목록 = 없음」 자리
1. **화면 초안 읽기** — `B05_Profile/B05_Profile_UI_Drainage_Panel.ts:424` `return pending?.length ? computeDetailBasins(...) : fetchDetailPipePoints(...)` — 초안 `[]` 를 「초안 없음」으로 보고 저장분을 읽음. 같은 초안을 B06 은 `readPendingPipes(projectId) ?? 저장분`(`B06_Section_UI_Page_Structures_Panel.ts:388`)으로 읽어 두 보기가 갈림.
2. **서버 관 목록 고르기** — `B04_PreProcess/B04_PreProcess_Router_Basins.py:373` `points = requested or stored or None` — 요청 `[]` 는 저장분으로, 저장분 `[]` 는 `None`(자동 배치)으로 넘어감. PUT 도 이 줄을 거쳐 **빈 목록이 절대 저장되지 않음**(P5).
3. **서버 계산** — 같은 파일 `:70` `chainages = [...] if points else None` · `common_util/common_util_drainage_detail.py:189` `if confirmed_chainages:` — 빈 확정 목록을 「기본 관 + 간격 보충(자동 배치)」으로 바꿈(P3 · P6). 사용자가 말한 「배수유역 자동 계산이 다시 돎」이 이 자리.
4. 덤 — `A00_Common/design/design_save.ts` 의 관 차례는 초안 내보내기 뒤 `side.savePipes()`(`design_shared.ts:301`)로 **배수유역도 화면 목록을 한 번 더 PUT** — 1~3 때문에 화면 목록이 자동 11개 · 저장분 1개로 바뀐 뒤라 그 값이 정본이 됨.
정상인 자리(빈 목록을 바르게 다룸): 세션 캐시 `readState`(`[]` 그대로) · `flushPendingPipes`(`if (!pending)` — `[]` 도 보냄) · 구조물 PUT · GET(`structures.json` `[]` 저장 · 읽기) · B05 계획선 · 횡단 측점의 관 읽기(`B05_Profile_Engine_Sections.py:175` — `[]` 그대로). `initial_snapshot` · [초기화](DELETE pipe-points)는 이 흐름과 무관.
곁 흠(같은 꼴 · 표시만) — `B06_Section/B06_Section_Section_Store.ts:81` `if (!drafts || !drafts.length)` — 구조물 초안 `[]` 를 「초안 없음」으로 봐 저장 전 횡단도에 저장분 벽(C군)이 남음. [저장] 뒤에는 정본이 `[]` 라 풀림.
## 고칠 자리 제안 (한 벌 · ③ 서버 단독 + 화면 한 줄)
뜻 정함 — **`None`(파일 없음 · 노선 바뀜 · 요청에 칸 없음) = 자동 배치 · `[]` = 사용자가 다 지움(관 0개)**. 자동 배치로 되돌리기는 [초기화](DELETE)만.
1. 서버 `B04_PreProcess_Router_Basins.py` `_resolve` — 요청 본문에 `points` 칸이 있으면(빈 목록 포함) 그 값 · 없으면 `None`. `points = requested if requested is not None else stored`. 응답 `saved` 판정(`bool(stored) and not requested`)도 `is None` 으로.
2. 서버 같은 파일 `_build:70 · :74` — `if points is not None` 로.
3. 서버 `common_util_drainage_detail.py:189` `build_detail` — `if confirmed_chainages is not None:` (빈 목록이면 관 0 · 유역 0 — 아래 `if not pipes: return detail` 가 이미 받음).
4. 화면 `B05_Profile_UI_Drainage_Panel.ts:424` — `pending !== null` 로(B06 `??` 와 같은 규칙).
5. (선택) 곁 흠 `B06_Section_Section_Store.ts:81` — 「`[]` 도 초안 없음」 주석(2026-09-09)이 막던 결함(저장분 벽을 통째로 지움)을 다시 보고 `drafts === null` 로 바꿀지 판단 필요 — 그 결함의 까닭이 「빈 초안」이 아니라 다른 곳이었는지 먼저 확인.
짝 파일 — 없음(관 목록 고르기는 서버 단독 · 화면은 한 줄). `load_pipe_points` 는 이미 `[]` 와 `None` 을 가름 — 고칠 것 없음.
영향:
- B04 배수유역 편집(`B04_PreProcess_UI_Basins.ts:334` 다시 나눔 · `:479` 저장)도 관을 다 지우면 이제 0개로 남음(지금은 자동 배치로 다시 참) — 같은 뜻으로 맞춰짐.
- 관 0개 정본 → B05 계획선 정착 · 구조물 측점 · B06 암거 횡단 · B08 수량에서 관이 빠짐 — 사용자가 다 지운 뜻 그대로.
- 문서 줄 고침 — `computeDetailBasins` 머리 주석(`B04_PreProcess_Api_Fetch.ts:586~588`) · `post_detail_basins` 독스트링 「points 를 비우면 자동 배치」.
시험 거리(`resources/tester/test_312_empty_pipes.py` 가칭 · 위 재현과 같은 사본 틀):
- PUT `[]` → 응답 0 · 파일 `points: []` · GET 0 · `saved=true`.
- POST detail-basins `[]` → 관 0 · 유역 0.
- 파일 없음 → GET 자동 배치(지금대로) · DELETE 뒤 자동 배치(지금대로).
- 노선 지문 바뀌고 좌표 없는 저장분 → 자동 배치(지금대로 · `None` 길).
- 화면 소스 — `loadSaved` 가 `pending !== null` 로 고르는지(기존 `test_298_add_reanalyze.py` 문자열 시험 그대로 통과).
## 되돌림 확인
- 실제 프로젝트 파일 손 안 댐 — 재현은 임시 사본에서 · 끝나고 사본 지움.
- `pipe_points.json` sha1 `503f60d6…` · `structures.json` sha1 `a12ee824…` — 재현 앞뒤 같음.
- DB — 읽기 전용 세션(`tx_read_only=1`) · 하류 무효화 함수는 가짜로 갈음 · 쓰기 없음.
- 화면 탭 · 서버 재시작 없음 · git 작업 트리 깨끗(커밋 없음).