diff --git a/docs/raw/요구_1025/_분석_389-A_생성_기준지정.md b/docs/raw/요구_1025/_분석_389-A_생성_기준지정.md new file mode 100644 index 000000000..783ff0823 --- /dev/null +++ b/docs/raw/요구_1025/_분석_389-A_생성_기준지정.md @@ -0,0 +1,157 @@ +# 분석 389-A — 예정노선 생성 칸 · 기준 지정 · 자동 이름 · 지도 · 이동 저장 (항목 1 · 2 · 3 · 4 · 15) + +읽기 전용 분석. 재현(브라우저) 안 함 — 15번 「빈 화면」은 코드 추정. + +--- + +## 1. D01 생성 칸 빼기 (사업연도 끝 · 종류 · 임도 종류 · 설계속도 · 시작 · 종료 측점) + +「임도 종료」 = 화면 칸 「임도 종류」(`B02_Proj_Field_RoadType`)로 봄. + +### 지금 + +| 칸 | 화면(`D01_PlanRegister_UI_Page.ts`) | 서버(`D01_PlanRegister_Router.py`) · DB | 읽는 곳 | 빼면 | +| --- | --- | --- | --- | --- | +| 사업연도 끝 | 45-51 · 검사 121 · 이름 연도 「시작~끝」 96-99 | 요청 22 · 없으면 = 시작 41-43 · `projects.project_year_end`(`db_management/020_project_groups.sql:12-13`) | D02 연도 범위 `D02_PlanBase_Router.py:97 · 162 · 199` · D03 지점 저장 `D03_PlanAnalysis_Router.py:339-340` · 대시보드 상세 범위 표시 `B01_Dashboard_UI_ProjectDetail.ts:107` · 목록 `B01_Dashboard_Router_Overview.py:30` | 서버가 이미 끝 = 시작으로 채움 → D02 가 한 해만 보임(2번과 맞음) · 깨짐 없음 | +| 종류 | 57-58 읽기 전용 글 「노선 계획」 | 서버로 안 감 · `project_kind='PLAN'` 은 서버가 박음(라우터 54 `kind="PLAN"`) | 없음 | 영향 없음 | +| 임도 종류 | 59-66 · 속도 목록 86-92 · 이름 조각 104 | 요청 24 **필수**(`^(main\|fire\|work)$`) · 49 · `projects.road_type` NULL 허용(`001_create_schema.sql:183`) | D03 추천 기준 `D03_PlanAnalysis_Router.py:212` → `D03_PlanAnalysis_Analyze.py:73-81`(없으면 `work`) · D04 연결 새 임도 종류 `D04_PlanConfirm_Link.py:258`(없으면 `work`) · 상세 표시 `B01_Dashboard_UI_ProjectDetail.ts:40 · 117`(null 안전) · E 그룹은 ROAD 만 원본(`E01_StabRegister_Router.py:30-36`) → 무관 | 화면만 지우면 서버 422 → 서버 요청 칸 선택으로 | +| 설계속도 | 67 · 86-92 | 요청 25 · `resolve_design_speed(grade_class_of(...))` 49 → `projects.design_speed_kph`(`020:14-15`) | D03 추천 기준(최소 R · 기울기 상한) · D04 새 임도 B05 단계 2 씨앗 `D04_PlanConfirm_Link.py:266-279 · 308` · `record_link` COALESCE 134 · 318 · 상세 116 · 202 | 임도 종류 없으면 `grade_class_of(None)` = `work` → 20 km/h(`B05_Profile_PageSettings.py:54-61` · `B05_Profile_Engine_Grade.py:110-121`) | +| 노선 시작 측점 | 69-83(옆 측점 표기 포함) · 검사 124 | 요청 26 · 44-48 · `projects.route_start_m`(`015_route_range.sql:13`) | D03 이은 누가 시작 `D03_PlanAnalysis_Router.py:258 · 322` · D04 내보내기 측점 시작 `D04_PlanConfirm_Router.py:205 · 312` · `D04_PlanConfirm_Repository.py:19` · 연결 SELECT `D04_PlanConfirm_Link.py:108`(값 안 씀) | 모두 `or 0.0` → No.0 부터 · 깨짐 없음 | +| 노선 종료 측점 | 70 · 검사 125 | 요청 27 · `projects.route_end_m`(`015:15-16`) | D03 `end_chainage_target_m` `D03_PlanAnalysis_Router.py:324-326` → 화면 「끝 · 목표 · 차이」 줄 `D03_PlanAnalysis_UI_Page.ts:398-409` | null 이면 그 줄만 안 보임 | + +### 바꿀 곳 + +- `D01_PlanRegister/D01_PlanRegister_UI_Page.ts` — 칸 여섯 · 속도 목록 · 측점 표기 · 검사 줄 · 보내는 값 · `SPEED_CHOICES` · `GRADE_CLASS` · `stationLabel` import 지움 · 머리 주석. +- `D01_PlanRegister/D01_PlanRegister_Router.py` — `road_type` 선택(`str | None = None`) · 끝 연도 · 측점 검사는 그대로 두어도 무해(안 오면 건너뜀). +- DB 칸은 **그대로 둠**(기본 NULL · 마이그레이션 없음). 끝 연도는 D03 연도 나누기 [저장] 때 씀(2번). +- 시험 — `resources/tester/test_528_project_groups.py:100-128`(`road_type` 필수 전제) · `test_553_4_de_parts.py:19-23`(측점 표기 소스 검사 → 칸 지우면 실패 · 시험도 지움) · `test_557_2_part_fixes.py:112-121`(폼 격자 — 그대로 통과 예상). + +### 위험 · 물음 + +- 임도 종류를 빼면 D04 연결로 만든 새 임도 프로젝트가 늘 「작업임도」(`D04_PlanConfirm_Link.py:258`) · 설계속도 20 씨앗 — 업체가 B 에서 고쳐야 함. 씨앗을 아예 안 넣으려면 서버가 속도를 NULL 로 저장(→ `seed_design_speed` 건너뜀 271). +- 항목 11(추천 엔진 빼고 손 직선)이면 D03 `plan_criteria` 자체가 안 쓰임 → 임도 종류 · 설계속도는 D 안에서 쓸 곳 없음. + +--- + +## 2. D02 기준 지정 = 전체 시점 · 종점 하나 + +### 지금 + +- 정본 = 표 `plan_route_years`(PK project_id + year) 연도마다 `start_x/y · end_x/y · epsg`(TM m) + 분석 `route_json · fingerprint · confirmed_at`(`020_project_groups.sql:21-36`). +- API = `GET/PUT /api/projects/{id}/plan/points`(`D02_PlanBase_Router.py:183-206`) — 연도 범위 = `project_year ~ project_year_end`(`_view` 160-180) · 잇기 규칙 `chain_years` 45-68(직전 종점 = 다음 시점 · 빈 연도 거절) · TM 띠 = 평균 경도 `with_tm` 71-80 · 바뀐 연도만 쓰고 그 연도 분석 비움 `_store` 115-150 · 범위 밖 연도 행 지움 202. +- 화면 = 경계점 P0..Pn(`D02_PlanBase_UI_Page.ts:61-65` · 이름 92-97 · 연도 색 43 · 연도 선 125-142) · 캐시 = 날 `sessionStorage` 열쇠 `d02_points_`(47 · 102-103 · 226 · 256-259 — `b_page_state` 등록표 밖). +- D03 가 쓰는 길 + - 서버 `_plan_inputs`(`D03_PlanAnalysis_Router.py:98-140`) — 연도 행 = 구간(segments) · 빈 연도 · 좌표계 섞임 거절. + - 끈 지점 덮기 `_override` 76-95 · [저장] 때 D02 규칙으로 정본에 씀 `_store_points` 330-353. + - 지형 = 연도마다 DEM 폴더 `models_{year}`(`D03_PlanAnalysis_Terrain.py:54-56 · 195-210`) · 화면은 경로 정규식 `models_(\d+)/` 로 연도 읽음(`D03_PlanAnalysis_Api.ts:111-121`). + - 분석 저장본 열쇠 = 연도 + 두 점(`D03_PlanAnalysis_Analyze.py:93-98`). + - 화면 `loadPoints`(`D03_PlanAnalysis_UI_Page.ts:630-649`) → `nodesOf` · `pointsOf` · `nodesInReach`(`logic/D03_PlanAnalysis_Plan.ts:191-230`). +- D04 이후는 연도 행 단위(확정 · 업체 지정 `plan_route_assignees.year` · 내보내기 `by_year` · 대시보드 「받은 예정노선」 · 연결). + +### 바꿀 곳 + +- 정본 자리(갈래 — 끝 목록) · `D02_PlanBase/D02_PlanBase_Router.py` — 연도 잇기 · 범위 빼고 두 점 하나 GET/PUT · TM 셈은 그대로. +- `D02_PlanBase/D02_PlanBase_UI_Page.ts` — 점 둘(시점 · 종점) · 선 하나 · 연도 색 · 이름 정리 · 캐시는 등록표(`b_page_state` `STATE_REGISTRY`)로 옮김. +- `D03_PlanAnalysis/D03_PlanAnalysis_Router.py` — `_plan_inputs` · `_override` · `_store_points` 를 「전체 두 점 + (나눈 뒤) 연도 조각」으로. +- `D03_PlanAnalysis/D03_PlanAnalysis_Terrain.py` · `D03_PlanAnalysis_Api.ts` — 연도별 DEM → 전체 한 벌(폴더 이름 · 정규식). +- `D03_PlanAnalysis/D03_PlanAnalysis_UI_Page.ts` · `logic/D03_PlanAnalysis_Plan.ts` — 연도 노드 대신 두 점 + 손 직선(항목 11) + 연도 표시(항목 14). +- `D03_PlanAnalysis_Analyze.py` — 항목 11 로 추천 엔진이 빠지면 연도 구간 열쇠 자체가 없어짐. +- `projects.project_year_end` = D03 연도 나누기 [저장] 때 마지막 연도로 씀 → 대시보드 범위 표시 그대로 삶. +- D04(`D04_PlanConfirm_*`)는 연도 행이 D03 에서 만들어지면 손 안 댐. +- 시험 — `test_529_3_plan_points.py`(연도 잇기 규칙 전부) · `test_530_*`(D03 연도 구간) · `test_553_4_de_parts.py:26-34`(D02 부품 소스 검사). + +### 위험 · 물음 + +- 이미 만든 PLAN 프로젝트의 연도 행 — 옮김 규칙(첫 연도 시점 + 마지막 연도 종점 = 전체 두 점?) 물음. +- 두 점이 바뀌면 D03 손 직선 · 연도 나눔을 어떻게 할지(지금 규칙 = 그 연도 분석 비움) 물음. +- `plan_route_assignees.year` 는 연도 int(FK 없음) — 연도 나눔이 바뀌면 지정 업체 연도가 남음 → 처리 규칙 물음. + +--- + +## 3. 자동 이름 = 사업연도 + 사업지역 + 「노선 계획」 + +### 지금 + +- `D01_PlanRegister_UI_Page.ts:95-111` — B02 조합기(`B02_ProjRegister/B02_ProjRegister_UI_Name.ts:18-55` · 조각 year · region · type · 손대기 전 자동 · 고친 뒤 그 조각만 갈아 끼움) · 조각 = 연도 「시작~끝」 · 사업 지역 · 임도 종류 글. +- 사업 지역 칸 = `region`(라벨 `B02_Proj_Field_Region` 「사업 지역」 `ui_template_locale_b1.ts:210`) → `projects.region` VARCHAR(100)(`001_create_schema.sql:182`). +- 서버는 이름을 받기만(조립 안 함). + +### 바꿀 곳 + +- `D01_PlanRegister_UI_Page.ts:96-109` 만 — year = 시작 연도 · type 조각 = `t("DE_Reg_KindPlan")`(「노선 계획」 `ui_template_locale_de.ts:91`) 고정 · 듣개에서 끝 연도 · 임도 종류 뺌. 조합기 · 서버 손 안 댐. 결과 꼴 「2026 홍천군 ○○면 노선 계획」. + +### 위험 · 물음 + +- D04 연결 새 임도 이름 = `{예정노선 이름} {연도}`(`D04_PlanConfirm_Link.py:256`) → 「2026 홍천 노선 계획 2027」처럼 연도 두 번. +- 「자동 입력정보」 = 이름 칸만으로 봄 — 다른 칸까지인지 물음. + +--- + +## 4. D02 지도 · 위성 + +### 지금 + +- D02 지도 = **카카오맵 JS SDK** 일반 지도(ROADMAP) — `D02_PlanBase_UI_Page.ts:22 · 105-115` → 공용 `ui_template/ui_template_kakao_map.ts:18`(SDK 주소) · 60-92(싣기) · 149 `new maps.Map(box, { center, level: 7 })`(지도 종류 지정 없음 = 일반). 형 정의 39-55 에 `MapTypeId` · `MapTypeControl` 없음. +- D02 코드에 국토지리정보원 · VWorld 타일 없음. 「국토지리정보원」 인상 = D03 평면(국토지리정보원 수치지형도 도엽 등고선 + VWorld 위성) 또는 카카오 지도 저작권 표기로 추정. +- 키 자리(값 안 적음) — 카카오 JS 키 = `.env` `KAKAO_JS_KEY` → `common_util/common_util_map_router.py:15-21`(`GET /api/map/kakao-key`) · VWorld 키 = `config_system.py` 에 없음 → 소스 하드코딩 폴백 `B04_PreProcess/B04_PreProcess_Engine_VWorld.py:22-29` · `B04_PreProcess_Engine_GisVector.py:17-22`. +- 이미 있는 위성 코드 + - D03 · B04 · B05 — 프로젝트별로 **미리 받아 둔** VWorld 위성 한 장(`D03_PlanAnalysis_UI_Page.ts:456-491` · `B04_PreProcess_UI_MapLayers.ts:14 · 35` · B05 배수유역 위성 겹) — 지점 범위가 정해진 뒤에야 생김 → D02(범위 정하기 전) 에 못 씀. + - B01 대시보드 VWorld **Base** 타일 대리 `GET /api/dashboard/map/tile/{z}/{x}/{y}`(`B01_Dashboard_Router.py:218-230` · `B01_Dashboard_Map.py:17 · 56-58`) — 정지 그림 격자 · 끌기 · 확대 지도 아님. + - `maplibre-gl` 은 받아 두었으나 페이지에서 안 씀(`config/package.json:9` · `vite.config.ts:30`). +- 이동 쉬운 길 = 카카오 SDK 기본 기능 — `map.setMapTypeId(kakao.maps.MapTypeId.HYBRID)`(위성 + 지명) / `SKYVIEW` · 지형 음영 겹 `addOverlayMapTypeId(MapTypeId.TERRAIN)`(항목 5 참고감). + +### 바꿀 곳 + +- `ui_template/ui_template_kakao_map.ts` — 형 정의에 `MapTypeId` · `setMapTypeId` 더함 · `editMap` 에 지도 종류 바꾸기 손잡이(선택 인자 · 기본 꺼짐 → B08 거리이정표 네 화면 그대로). +- `D02_PlanBase/D02_PlanBase_UI_Page.ts` — 「일반 · 위성」 바꿈 단추(부품 상자 버튼 · 고른 값 = 취향 `aislo:pref:` 로 기억 여부 물음). + +### 위험 · 물음 + +- 카카오 내장 단추(`MapTypeControl`)는 우리 부품 상자 밖 꼴 — CLAUDE.md 「화면 요소는 부품 상자만」 → 우리 단추로 `setMapTypeId` 부르는 쪽 권함. +- 카카오 위성은 D03 VWorld 위성과 출처 · 촬영 시점이 다름(같은 자리라도 그림 다름). + +--- + +## 15. D03 → D02 돌아가면 빈 화면 · 바뀐 지점 안 보임 + +### 지금 + +- D02 → D03: [>] 만 저장 뒤 이동(`D02_PlanBase_UI_Page.ts:239-245` · `ui-step-nav` detail 1 가로챔). 단계 아이콘(🧭) 누름은 `A00_Common/group_workflow.ts:72` `navigateTo` 직행 → 저장 없음 · D02 캐시 `d02_points_` 남음. +- D03 → D02: D03 에 떠남 처리 없음 → [<] = `ui_template_workflow_layout.ts:253-260` 이 단계 단추 click → `group_workflow.ts:72` 직행. 저장 없음. +- D03 지점 끌기 = 캐시 `d03-plan` 의 `nodes`(TM m · `D03_PlanAnalysis_UI_Page.ts:197-202` · 등록 `A00_Common/b_page_state.ts:202`). 정본 반영은 D03 [저장](`PUT plan/analysis` → `_store_points`)뿐이고, [저장]은 모든 연도 분석이 있어야 켜짐(`D03_PlanAnalysis_UI_Page.ts:311-314`) → 분석 전 지점 고침은 정본에 갈 길 없음. +- D02 열 때 = 서버 GET + 자기 캐시만(`D02_PlanBase_UI_Page.ts:255-259`) · D03 캐시(TM) 안 읽음(D02 는 경위도 · 브라우저 좌표 변환 없음 — `_분석_363` A). +- → 「바뀐 정보 안 보임」은 확정 경로. 거기에 D02 옛 캐시가 길이만 맞으면 서버 값을 덮음(259) → D03 에서 저장한 새 지점 대신 옛 D02 값이 보일 수 있음. +- 「빈 화면」 후보(재현 전 추정 · 확정 못함) + 1. D02 첫 읽기(세션 · `plan/points`) 실패 → catch 가 표 자리에 글 한 줄만 · `paint()` 안 불려 카카오 지도가 아예 안 만들어짐(`D02_PlanBase_UI_Page.ts:247-264`) → 지도 칸 통째로 빔. + 2. 카카오 SDK 못 받음(키 · 도메인) → 지도 칸 숨김 + 안내 한 줄(`ui_template_kakao_map.ts:216-219`). + 3. 라우터 · 바깥 틀 문제는 없음 — 이동마다 아울렛을 새로 만들고(`app_shell.ts:332-345`) D03 고리는 `root.isConnected` 로 멈춤(`D03_PlanAnalysis_UI_Page.ts:538 · 574 · 675`) · 잠금판은 세는 방식 · 짝 맞음. + - 확정 방법 = ORCA 로 D03 지점 끌기 → [<] → D02 에서 `orca eval` 로 `.ui-kakao-map__box` 크기 · hidden · 표 자리 글 · 콘솔 오류 확인. +- 쓸 만한 선례 + - D02 [>] 자체(위) · B09 같은 꼴(`B09_Estimation/B09_Estimation_UI_Shell.ts:356-367`). + - B05 · B06 — 단계 이동 = `moveStage(projectId, target, steps)`(`A00_Common/b_workflow_nav.ts:133-190` · 잠금판 + 단계 차례 + 실패면 머묾 · target 에 함수를 주면 B 전용 길을 안 탐) · 부르는 자리 `A00_Common/design/design_page.ts:205-210` · 머리 메뉴 · 뒤로 가기 = `guardDesignLeave`(`A00_Common/design/design_leave.ts:102-129` · `setLeaveGuard` + `leaveOrStay` [취소][폐기][저장] `ui_template/ui_template_leave_dialog.ts:68`) · 라우터 쪽 `A00_Common/main.ts:89-99`. + - `design_pipeline.ts` 는 조작 중 뒤 계산 한 방향 — 페이지 이동 저장과 무관 · 재사용 대상 아님. + +### 바꿀 곳 + +- `A00_Common/group_workflow.ts` — `mountGroupStep` 에 `beforeLeave?: () => Promise` 하나 · 72 줄 `onStepClick` 에서 기다린 뒤 이동. [<] [>] 와 단계 아이콘이 모두 이 한 길(ui-step-nav 도 결국 단계 단추 click). 머리 메뉴 · 브라우저 뒤로는 같은 함수를 `setLeaveGuard` 로. +- `D02_PlanBase_UI_Page.ts` — 239-245 따로 듣개 지우고 `beforeLeave = store`(산림청 · 바뀐 것 있을 때만). +- `D03_PlanAnalysis_UI_Page.ts` — `beforeLeave` = 지점(두 점 · 손 직선) 저장 · 분석 없이도 되는 저장 길 필요 → `D03_PlanAnalysis_Router.py` 에 지점만 저장(또는 D02 PUT 이 TM 도 받음). 항목 11 재설계와 한 묶음. +- 저장 뒤 상대 캐시 비움(D02 `d02_points` · D03 `d03-plan` nodes) → 돌아간 쪽이 서버 새 값을 봄. + +### 위험 · 물음 + +- 저장 실패 시 머묾 · 알림으로 갈지, B05 처럼 [취소][폐기][저장] 물음으로 갈지. +- 보기 전용(지정 회사)은 저장 없이 이동 — 지금 규칙 그대로. + +--- + +## 갈래 갈리는 결정 + +1. **전체 시점 · 종점 정본 자리** — ⓐ `projects` 에 JSON 칸 하나(`plan_base_json` · 마이그레이션 한 줄) ⓑ 새 표 ⓒ `plan_route_years` 첫 연도 행을 임시로 씀(뜻이 섞임) ⓓ storage `D02_PlanBase/` 정본 파일. 권함 = ⓐ. +2. **연도 행(`plan_route_years`)을 누가 만드나** — D03 연도 표시(항목 14) [저장] 때 만들고 `project_year_end` 도 그때 씀 · 맞는지. +3. **옛 PLAN 프로젝트 옮김** — 첫 연도 시점 + 마지막 연도 종점 = 전체 두 점으로 접을지 · 버릴지. +4. **임도 종류 · 설계속도** — D 에서 아예 안 받음(D04 연결 새 임도 = 작업임도 · 속도 씨앗 없음) vs 숨은 기본값 유지. +5. **노선 시작 측점** — 늘 No.0 으로 충분한지(연차 사업 이어 셈이 필요했던 요구서 345 3-1 근거가 사라짐). +6. **자동 이름 범위** — 이름 칸만인지 · D04 연결 새 임도 이름의 연도 두 번 꼴 정리 여부. +7. **위성 바꿈 단추** — 우리 부품 단추 vs 카카오 내장 단추 · 위성(SKYVIEW) vs 위성 + 지명(HYBRID) · 고른 값 기억 여부. +8. **이동 저장 꼴** — 말없이 저장 후 이동(D02 [>] 꼴) vs 묻기(B05 [취소][폐기][저장] 꼴). diff --git a/docs/raw/요구_1025/_분석_389-B_노선분석.md b/docs/raw/요구_1025/_분석_389-B_노선분석.md new file mode 100644 index 000000000..14206c6dc --- /dev/null +++ b/docs/raw/요구_1025/_분석_389-B_노선분석.md @@ -0,0 +1,276 @@ +# 분석 389-B — 노선분석(D03) 요구 6~14 + +근거 = 코드 file:line · 실패 프로젝트 재현(읽기만) · 지식DB. 고친 파일 없음. + +요약 + +- D03 = B05 노선 추천 엔진 · 갈래 넷 · 꺾임점 R 고침 · 예상 횡단을 들여온 화면 → 11번대로 걷어 내고 「직선 꺾은선 손 긋기」 화면으로 다시 세움. +- 8번 토스트 뿌리 = B04 도엽 파서가 `.cpg` 없는 도엽 dbf 를 latin-1 로 읽어 `등고수치` 칸이 깨짐 → 등고선 높이 0 → 지형면 실패. 7번 「더 받기」가 늘 켜진 것도 이 실패의 뒤끝. +- 아래 단계(D04 · B03 가져오기 · D01 · B01)는 모두 「연도 한 행 = 꺾은선 한 줄 · 연도끼리 이어짐」을 전제 → 갈래(6) · 조각(14)과 부딪힘 = 가장 큰 결정거리. + +--- + +## A. 지금 구조 · 버릴 것 · 남길 것 · 아래 단계가 읽는 꼴 + +지금 — B05 노선 추천에서 온 덩이 + +| 덩이 | 자리 | B05 에서 빌린 것 | +| --- | --- | --- | +| 분석 엔진 | `D03_PlanAnalysis_Analyze.py:34-49` · 갈래 넷 `:53` · 구간 분석 `:179-220` · 비용면 캐시 `_derived/cost_<연도>` `:251` · 나란히 프로세스 `:277-330` | `generate_initial_route` · `load_terrain_and_streams` · `request_fingerprint` · 법정 기울기 · R 함수 | +| 파생 파일(543-4) | `route_<연도>_<지점 열쇠>.json` `Analyze.py:93-98` · 거르기 `:127-139` | — | +| 분석 API | `Router.py:208-222` POST analyze · `:294-327` GET analysis · `:356-415` PUT analysis(갈래 이름 + 꺾임점 고침 → 서버 다시 셈) | — | +| 갈래 고름 · 이은 누가 | 카드 `UI_Page.ts:85-90 · 320-371` · `logic/Plan.ts:71-147`(짝 `Analyze.py:333-400`) | — | +| 꺾임점 · R 고침(543) | `D03_PlanAnalysis_Edit.py` 전부 · `logic/D03_PlanAnalysis_Edit.ts` · `UI_Edit.ts` · `UI_Page.ts:223-237 · 258-279 · 373-395` | `node_polyline` · `route_metrics` · `hard_limits` · `RouteEdit_Edits` · `_Curve` · 집기 함수 | +| 연도 경계 지점 끌기 | `UI_Map.ts:316-357` · `UI_Page.ts:411-438` · `Router.py:76-95 _override` · `:330-353 _store_points` | — | +| 하 횡단 · 3D 코리도(544) | `UI_Cross.ts` · `logic/Expect.ts` · `UI_3D.ts:105-117` | `buildCrossSvg` · `sectionsGround` · `buildCorridor` · `createRouteViewer` | +| 설정 | `config_system_terrain.PLAN_ANALYZE_WORKERS` | — | + +버릴 것 + +- `Analyze.py` · `Edit.py` · `logic/Edit.ts` · `UI_Edit.ts` · `UI_Cross.ts` · `logic/Expect.ts` · 갈래 카드 · [분석] 단추 · POST analyze · PUT 의 `choices`/`edits` · 연도 경계 지점 끌기 · `PLAN_ANALYZE_WORKERS`. +- storage `_derived/route_*` · `cost_*` · `models_<연도>` — 새로 안 씀 · 안 읽음(손으로 지우지 않음). +- 시험: `test_530_2_plan_analyze` · `test_530_3_plan_logic` · `test_543_plan_edit` · `test_544_1_plan_expect` 지움 · `test_530_2_plan_router` 대부분 다시 · `test_553_4_de_parts.py:36-50` D03 단언 고침(갈래 카드 · 3D 체크 칸 단언). + +남길 것 + +- 지형 받기 골격 — `Terrain.py`(도엽 · 위성 · DEM · `plan.prj`) · 일 진행 `Router.py:143-166 · 225-231` · 모델 행 `:169-191`. +- 평면 바탕 — `UI_Map.ts` 위성 · 등고선 · 측점 눈금 · 좌표 변환 · 확대 이동(`bindRouteEditNavigation`). +- 측점 — `stationChainages` · `leadInLine`(`Plan.ts:150-176`) · `stationLabel`. +- 3D 모달(지형 + 선 + 측점 막대). + +아래 단계가 읽는 꼴(`plan_route_years.route_json`) + +| 읽는 곳 | 자리 | 읽는 칸 | +| --- | --- | --- | +| D04 내보내기 · 확정 | `D04_PlanConfirm_Router.py:159-175` | `planned`([x,y] 꺾은선) · `start_chainage_m` · `end_chainage_m` · 행 `epsg` | +| D04 요약 표 | `D04_PlanConfirm_Router.py:246-263` | `length_m` · `mode` · `planned[0]` · `planned[-1]` | +| D04 shp · xlsx | `D04_PlanConfirm_Export.py:204-270` | 연도 = LineString 한 줄 · 「전체」 = 연도 차례로 이어 붙임(`:255-260` 직전 종점 = 다음 시점 전제) | +| D04 연결 → 임도 | `D04_PlanConfirm_Link.py:74-81` 지문(`planned` 만) · `:153-158` shp 한 벌 · `:250-263` `length_m` | 같음 | +| B03 [예정노선에서 가져오기] | `B03_FileInput_UI_PlanImport.ts:54` → POST link(위 D04 Link 길) · B01 `UI_PlanLink.ts:32` 같음 | shp 한 벌 = 노선 하나 | +| D01 받은 예정노선 | `D01_PlanRegister_Router.py:67-99` | 지문 · `confirmed_at` | +| B01 산림청 홈 | `B01_Dashboard_Router_Home.py:140-157` | `route_json IS NOT NULL` · `confirmed_at` | +| E01 안정성평가 | `E01_StabRegister_Router.py:35` — ROAD 프로젝트만 복사 | D03 직접 소비 없음(D → 임도 연결 → E 간접) | + +- 전제 = 연도 한 행 · 꺾은선 한 줄 · 연도끼리 이어짐 · 업체 지정도 연도 단위(`020_project_groups.sql:38-56`). + +위험 · 물음 + +- D03 지점 읽기 = 연도별 `plan/points`(`Api.ts:71`) — 2번(D02 = 전체 시점 · 종점 하나)과 묶여 바뀜 · 389-A 몫과 꼴 맞춤. +- 안내 화면 `A05_EduDetail/guide/D03/` 02 · 04 · 07 통째 낡음. + +--- + +## B. 지형 받기(7) + +지금 + +- 열면 자동 받기 — `UI_Page.ts:693-694`(받을 범위를 먼저 안 보임). +- 범위 = 서버가 정함 — 도엽 = 지점 + 1 km 표본마다 3×3(`Terrain.py:43 · 181-187`) · DEM = 연도 구간 상자 + 350 m(`dem_outline :96-102` + B 여유 300 m) · 연도마다 `models_<연도>/`(`:195-210`). +- 「범위 밖 — 더 받기」 보임 = `UI_Page.ts:302-304` `!editable || !outside || busy` · outside = 마디가 연도 DEM 상자(300 m 안쪽) 밖 · **DEM 없으면 밖**(`Plan.ts:212-227`). +- 늘 켜진 까닭 — 지형 받기가 2026년 지형면에서 실패(C) → 연도 지형면 0 → 모든 마디 「밖」 → 단추 늘 보임 · 열 때마다 자동 다시 받기 → 같은 실패 반복(`tmp/server.log` 1330524~1331398 · 9번). 단추 글 「범위 밖」은 틀린 안내(실은 받기 실패). + +바꿀 곳 + +- 두 걸음 — 범위 보기 → [이 범위 받기]. + - 서버 ③ 새 `GET …/plan/terrain/extent` — 시점 · 종점(그은 뒤면 선 bbox) + 여유 → DEM 상자 네 귀(경위도) · 받을 도엽 번호 · 도곽 · 칸 수 어림 · 이미 받은 상자. 셈 = `Terrain.py` 함수 그대로(`neighbors_for_points` · `sheet5k_to_bounds` · `dem_outline`). + - 그림 자리 — 받기 전엔 위성 · 등고선이 없어 D03 캔버스는 빈 바탕(`fakeMeta UI_Map.ts:99-121`) → 카카오 지도 부품 `editMap.set(pins, lines)`(`ui_template_kakao_map.ts:105-114`) 에 닫힌 선으로 상자 · 도곽. + - 여유(m) 칸 하나 — 기본값 협의. +- DEM = 사업 전체 한 벌(`models_plan/` 류) — 연도는 D03 나눔 표(14)로 나중에 정해짐. `fetchYearSurfaces`(`Api.ts:111-127` `models_(\d+)` 열쇠) → 한 벌. +- 「더 받기」 = 그은 꺾임점 · 커서가 받은 DEM 밖 · 일 안 돎 · 직전 실패가 아님. 실패면 「지형 받기 실패 — 까닭」 + [다시 받기] 로 따로. +- 자동 받기(`:693-694`) 지움. + +위험 · 물음 + +- 한 벌 DEM 크기 — 1 m 격자(`SHEET_SURFACE_GRID_M`) · 실패 프로젝트 넓이(약 4.7 × 2.5 km)면 약 1,200만 칸 · 라플라스 400번 → 시간 · 메모리 실측 필요 · 넓으면 D 만 2 m 격자 갈래. +- 카카오 지도 · 위성(4 · 5번)과 같은 부품 — 389-A 와 겹침 조율. + +--- + +## C. 토스트 「구간 지형면을 만들지 못했습니다」(8) + +지금 + +- 글 자리 — `D03_PlanAnalysis_Terrain.py:209-210` `raise ValueError(f"{year}년 구간 지형면을 만들지 못했습니다 — 도엽 등고선 확인")` → `Router.py:157-159` 진행 `error` → `UI_Page.ts:543` 토스트. +- 나는 조건 = `build_sheet_surface_model` 빈 목록(`B04_PreProcess_Engine_SheetSurface.py:475-554`): + 1. 병합 등고선 파일 없음 · 못 읽음(`:64-80 · 502-504`) + 2. 상자 안 등고 높이 2단 미만(`:513-517`) + 3. 보간 방식 모두 예외(`:534-536`) + 4. 유효 칸 0(`:353-356`) +- 실제 까닭(재현 · 읽기만) + - 실패 프로젝트 `4250c7bc…`(storage/1/3) 로그 「등고 라인 94단을 격자에 굽어 0셀」 → 2번. + - 2026년 상자(x 284818~286552 · y 409459~410472) 안을 지나는 등고선 96 개 모두 `등고수치 = null`. + - 병합 도엽 20매 중 15매 등고선 칸 이름 깨짐(`µî°í¼öÄ¡` = cp949 「등고수치」를 latin-1 로 읽은 글자). + - 깨진 도엽 zip 은 `.cpg` 없음(`36712088.zip`) · 성한 도엽은 `.cpg` 있음(`36716009.zip`). +- 뿌리 = `B04_PreProcess_Engine_SheetParser.py:60` `gpd.read_file(...)` 인코딩 지정 없음 → `.cpg` 없는 dbf = latin-1 → 높이 칸 못 찾아 `_rasterize_contour_levels`(`SheetSurface.py:246-251`)가 그 선을 버림. +- 번짐 — B 도엽 지형면 · 배수유역 등고선 · D03 등고선 라벨(`prepareLayer … ["등고수치"]` `UI_Page.ts:485`)도 같은 도엽이면 같은 흠(잠재). storage 병합 파일 16 개 앞머리 표본에서 깨진 것은 이 D 프로젝트만. + +바꿀 곳 + +- `SheetParser.py:60` — `.cpg` 없으면 `encoding="cp949"`(공용 한 자리 · 한 줄). +- 병합 열쇠에 파서 판 번호 — `B04_PreProcess_Engine.py:531` · `D03 Terrain.py:116-117`(`{"sheets": …}` 그대로면 옛 깨진 병합본을 계속 씀). +- 시험 — `.cpg` 없는 cp949 dbf 한 장 → 칸 이름 `등고수치`. +- (덤) 토스트 글에 까닭 — 「상자 안 등고선 높이 못 읽음(도엽 N 매)」. + +위험 · 물음 + +- B04 = 남의 페이지 공용(`B04_PreProcess/CLAUDE.md`) → B 쪽 시험도 같이. +- 고친 뒤 이 D 프로젝트는 받기 한 번 더(열쇠 바뀜 → 다시 병합 → 지형면 열쇠도 따라 바뀜). + +--- + +## D. 하 횡단 판 지움(9) + +지금 — 닿는 자리 + +- `UI_Page.ts:58`(import) · `:119` · `:122` · `:172` · `:218-221` · `:228` · `:389` · `:433-435` · `:440-454`(onStation) · `:658`(검증 창구). +- `UI_Cross.ts` 전부 · `UI_Style.css:28-66` · `UI_3D.ts:22`(`loadExpectStandard` 를 Cross 에서 가져옴) · `:63-73 · 105-117`(예상 코리도). +- `logic/Expect.ts` = Cross · 3D 만 씀 · 시험 `test_544_1` · `test_553_4:45`(3D `createCheckbox` 단언) · 안내 D03 04 · 06 · `_scenes.json`(`d03-plan__cross*` 11곳). + +바꿀 곳 + +- 횡단 판 · `UI_Cross.ts` 지움 → 평면이 본문 전체. +- 3D 남김(G) · 예상 코리도는 같은 「예상 횡단」 셈 → `Expect.ts` · 코리도 체크 칸 · `test_544_1` 같이 지움. +- 측점 누름 = 할 일 사라짐 → 지움(측점 눈금 · 이름은 남김). + +위험 · 물음 + +- 3D 코리도도 「원했던 방식 아님」에 드는지 — 추정 = 듦(G). + +--- + +## E. B05 평면 상단 단추 · 마우스 · 단축키(10) + +| 단추 | B05 자리 | 묶임 | D03 쓰는 법 | +| --- | --- | --- | --- | +| 되돌리기 · 다시하기 | `RouteEdit_Chrome.ts:86-89` · 스택 `RouteEdit_History.ts:98-141` · 단추 `:148-183` | 단추 · 단축키가 B05 페이지 사진첩(`onPageHistory :174`)에 묶임 | 안 씀 · D03 자체 스택(`UI_Page.ts:177 · 205-216 · 674-682`)에 다시하기만 더함 | +| 3D | `Chrome.ts:91` 부른 쪽 단추 자리 | B05 3D 보기 | D03 자체 `openPlan3d` 그대로 | +| 좌 · 우 회전 | `RouteEdit_Rotate.ts:52-97` · 단추 찾기 `:72-79`(`[data-act="rotate-ccw|cw"]`) · 15° · Ctrl+휠 5°(`RouteEdit_Input.ts:58-59` `rotateWheel`) | B05 의존 없음(단추 둘 + 캔버스 크기) | import 그대로 · `UI_Map` 그리기에 캔버스 회전 · 포인터 `at()`에 `unrotate` · 탐색에 `unrotateDelta` · `rotateWheel` | +| 거리 재기 | `RouteEdit_Measure.ts:1-17 · 184-` | 노선 위 두 점 · 지반고 = B05 서버 `/route/elevations`(`Api_Replan.ts:206`) | 안 씀 · F 의 순수 셈 + `heightAt` 로 두 점 길이 · 기울기 | + +- 단추 모양 — Chrome 의 날 `