chore(docs): docs 폴더 git 추적 전환 · PLAN·OWNERS 최상위 이관
- `docs/` 를 .gitignore 에서 빼 git 추적 대상으로 전환 (위키·완료 이력·검증 기록) - `docs/raw/PLAN.md` · `docs/raw/OWNERS.md` 를 저장소 최상위로 이동 후 .gitignore 에 등록 — 창끼리 공유하되 저장소에는 안 올리는 장부 - 살아 있는 경로 참조 7개 파일 정정 (아카이브 107건은 그때 사실이라 그대로 둠) - graphify 날짜별 산출물(`docs/wiki/graphify-out/20*/`) 제외 — `graphify update` 가 다시 만듦 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
## 나는 누구인가
|
||||
- 이름:
|
||||
- 하는 일: 임도설계 실무자. Aislo(라이다 데이터 기반 임도 설계·견적 자동화 웹앱) 프로젝트의 기획·설계자.
|
||||
- 핵심 가치: 코드를 직접 작성하거나 깊이 이해하지는 못하지만, 도메인 전문성을 바탕으로 결과물(화면, 산출물)을 보고 실무적으로 맞는지 검증할 수 있다. 이 검증력을 근거로 AI에게 방향성과 구체적 지시를 내린다.
|
||||
|
||||
## 나의 역할들
|
||||
|
||||
### 임도설계 실무자 (도메인 전문가)
|
||||
- 하는 일: 임도 설계 실무 지식을 바탕으로 요구사항을 정의하고, AI가 만든 결과물이 실무 기준에 맞는지 검증한다.
|
||||
- 주요 관심사: 결과물의 실무적 타당성, 설계 로직의 정확성.
|
||||
|
||||
### 프로젝트 기획·설계자
|
||||
- 하는 일: AI 코드 어시스턴트에게 방향성과 지시를 제공하고, 계획서 작성과 검토를 주도한다.
|
||||
- 주요 관심사: AI와의 소통 효율, 문서·컨텍스트 구조화, 반복되는 방향성 이탈 방지.
|
||||
|
||||
## 나의 비전과 목표
|
||||
- 이루고자 하는 것: 라이다 데이터를 활용해 임도 설계를 자동화하는 웹앱(Aislo)을 완성한다. 동시에, 개발 과정에서 문서(백엔드/프론트엔드/DB/구조/계획서/검증보고서)가 비대해져 컨텍스트·토큰을 낭비하지 않도록, 페이지 단위로 구조화·연결된 위키 체계를 구축한다.
|
||||
- 타겟 독자/고객:
|
||||
- 1차: AI 코드 어시스턴트 자신 — 코드 작성 전후로 필요한 부분만 효율적으로 찾아 읽는 용도.
|
||||
- 2차: 6개월~1년 후의 AI 세션 및 팀원 — 프로젝트 구조, 설계 이유, 사용된 함수·변수, DB 정보를 스스로 파악해 개선하거나 인수인계받는 용도. (본인이 직접 위키를 다시 찾아볼 일은 거의 없음)
|
||||
|
||||
## AI에게 기대하는 것
|
||||
- 코드 작성 전후로 관련된 위키·계획서만 효율적으로 찾아 읽고, 문서 전체를 뒤지지 않기.
|
||||
- 계획서 작성 → 코드 작업 → 계획서 체크리스트 점검 → 코드 검증 → 검증보고서 작성 → 계획서 업데이트의 흐름을 주기적으로 관리하기.
|
||||
- 같은 실수나 배경 설명을 반복하지 않기 — 이전 결정사항과 컨텍스트를 유지하기.
|
||||
- 함수·변수 단위, DB 컬럼 단위까지 추적 가능하도록 문서화하기.
|
||||
|
||||
## 작업 규칙
|
||||
- 톤: 간결하고 명확하게. 불필요한 수식어 없이 핵심만
|
||||
- 언어: 한국어 기본
|
||||
- 결과물 형태: 계획서와 검증보고서는 각각 하나의 파일로 구성하여 지속 업데이트한다. 문서는 비대해지지 않도록 페이지 단위로 구조화하고, 하위에 관련 백엔드·프론트엔드·DB·API·의존성 정보를 연결한다.
|
||||
|
||||
---
|
||||
|
||||
## Wiki Schema (위키 운영 규칙)
|
||||
|
||||
> 이 규칙은 `docs/` 볼트에만 적용된다. 프로젝트 루트의 docs 폴더 상위의 `.agent/` 폴더(실제 코딩 라우팅용)와는 완전히 독립적이며, 서로 건드리지 않는다. 위키가 충분히 성숙하면 그때 `.agent`를 대체할지 판단한다.
|
||||
|
||||
### 레이어 구조
|
||||
- **raw/** — 원본 보관 영역. 기존 계획 아카이브·검증보고서·행동지침은 수정하지 않는다. 단, 위키 관리 전 2차 교차 검증이 통과한 경우에 한해 위키 관리자는 상시 계획서 `raw/PLAN.md`의 완료 구간을 잘라 `raw/plans/`에 새 아카이브로 옮기고 `raw/verification/`에 새 검증보고서를 작성할 수 있다.
|
||||
- **wiki/** — AI가 raw를 읽고 편집·유지하는 산출물. 페이지(B01~B09) 기준 폴더 + 개념(concepts) 폴더로 구성.
|
||||
- **output/** — 최종 결과물(보고서, 슬라이드 등).
|
||||
|
||||
### 위키 관리자(Librarian) 역할
|
||||
AI는 이 볼트에서 "위키 관리자"로 행동한다. 단순 챗봇이 아니라, raw를 읽고 wiki를 지속적으로 편집·정리·연결하는 책임을 진다. wiki는 사용자가 아니라 AI가 쓰고 관리한다.
|
||||
|
||||
### 운영 방식
|
||||
- **Ingest** (원본 반영): raw/에 새 파일이 들어오면 → 내용을 읽고 핵심을 파악 → 관련된 `wiki/pages/B0x_*/`와 `wiki/concepts/*.md`를 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
|
||||
- **Query** (질의 응답): 코드 작업 전후로 질문을 받으면 → `wiki/index.md`를 먼저 확인 → 관련 페이지만 열어서 답변 → raw/는 위키에 없는 정보가 필요할 때만 최후 수단으로 참조. 답변 중 새로 정리된 내용(비교, 분석 등)은 대화에 흘려보내지 말고 관련 wiki 페이지에 다시 파일링한다.
|
||||
- **Lint** (건강 점검): 요청 시 위키 전체를 점검 — 페이지 간 모순, 오래된 내용(최근 raw로 갱신되지 않은 부분), 고아 페이지(연결 없음), 빠진 크로스링크를 찾아 보고한다.
|
||||
|
||||
### 위키 규칙
|
||||
1. **raw의 기존 원본은 수정 금지**. 다음 완료 처리만 예외로 허용한다.
|
||||
- 개발 담당 AI의 자체 검증 기록을 근거로 실제 구현과 테스트를 2차 교차 검증한다.
|
||||
- 교차 검증에 통과한 완료 구간만 `raw/PLAN.md`에서 제거하여 `raw/plans/`의 새 파일로 보존한다.
|
||||
- 교차 검증 결과는 `raw/verification/`의 새 파일로 작성한다.
|
||||
- 기존 계획 아카이브와 기존 검증보고서는 수정하거나 덮어쓰지 않는다.
|
||||
- 실패·누락·근거 불충분 항목과 미완료·후속 항목은 `raw/PLAN.md`에 유지하고 위키에 완료 사항으로 반영하지 않는다.
|
||||
2. wiki 페이지 생성·삭제 시 **index.md 필수 업데이트**
|
||||
3. 모든 오퍼레이션마다 **log.md에 기록**
|
||||
4. 내부 참조는 **위키링크(`[[페이지명]]`)** 형식 사용
|
||||
5. 모든 wiki 페이지에 **YAML frontmatter** 작성. `status`는 반드시 아래 3개 값 중 하나만 사용:
|
||||
- `draft` — 계획서만 반영됨, 아직 검증 안 됨
|
||||
- `stable` — 검증보고서로 확인된 내용까지 반영됨
|
||||
- `stale` — raw에 새 자료가 들어왔는데 아직 이 페이지에 반영 못함 (Lint 시 최우선 처리 대상)
|
||||
그 외 `related_pages`, `last_updated`도 함께 기록한다. **`wiki/pages/B0x_이름/`, `wiki/pages/A0x_이름/` 하위 파일은 다음 규칙을 따른다:**
|
||||
- **파일명 형식:** `{page_id}_{기능명}.md` (예: `A01_frontend.md`, `B05_backend.md`, `B03_db.md`)
|
||||
- **frontmatter에 `page_id` 필드 필수:** `page_id: A01_Home` 또는 `page_id: B05_wf2_Route` — 폴더 밖에서 파일 하나만 봐도 어느 페이지 소속인지 식별 가능해야 한다.
|
||||
- concepts 페이지에는 이 규칙을 적용하지 않는다(페이지 단위가 아니므로).
|
||||
6. 모순 발견 시 **양쪽 소스 모두 인용**하고 어느 쪽이 최신인지 명시. **계획서와 검증보고서가 충돌하면 검증보고서를 채택한다** (검증보고서 = 실제 코드가 검증된 상태, 계획서 = 그 시점의 의도). 계획서 쪽 내용은 지우지 말고 "계획 당시 의도"로 표시해 남긴다.
|
||||
7. 소스 요약은 사실만 기록, 해석·종합은 concepts 페이지에서
|
||||
8. 질의 시 **index.md 먼저**, raw/는 마지막 수단
|
||||
9. **새 페이지 생성보다 기존 페이지 업데이트를 우선**
|
||||
10. index 항목은 한 줄, 120자 이내로 요약
|
||||
11. **공유 자원은 1차 정의를 concepts에만 둔다.** 공유 자원이란 두 페이지 이상이 함께 쓰는 것 전부를 말한다 — DB 테이블·컬럼, 공통 함수(`common_util_*`), API 엔드포인트, Pydantic 요청/응답 스키마, 외부 라이브러리(의존성), workflow 상태 등. 페이지의 `db.md`/`backend.md`/`api.md`/`dependencies.md`에는 정의를 복사하지 않고, "이 페이지가 무슨 용도로 쓰는지" + `[[concepts/해당개념#항목명]]` 위키링크만 남긴다.
|
||||
12. **concepts 페이지에는 사용처(역참조) 목록을 직접 적는다.** 예: `db_schema.md`의 "routes 테이블" 항목 아래에 `사용처: [[B05_wf2_Route]], [[B06_wf3_ProfileCross]]`. 새 페이지가 그 자원을 쓰게 되면 concepts 쪽 사용처 목록도 함께 갱신한다 (Obsidian 자동 백링크에만 의존하지 않음).
|
||||
13. **함수·API·테이블 등 개별 항목은 표 형식으로만 기록한다** (서술문 금지). 최소 컬럼: `항목(고유 심볼) | 위치(상대 경로) | 역할`. 코드 변경에 따른 줄번호 누출 및 비정합을 방지하기 위해 수동 줄번호 기재는 생략하고 고유 함수/클래스명만 유지하며, 코드 AI는 실시간 AST/Grep 조회로 대상 라인을 직접 탐색한다.
|
||||
|
||||
| 항목 | 위치 | 역할 |
|
||||
|---|---|---|
|
||||
| `generate_dem()` | `B04_wf1_Surface/B04_wf1_Surface_Engine.py` | 포인트클라우드 → DEM 변환 |
|
||||
|
||||
14. **페이지 하나(파일 하나)는 100줄을 넘지 않는다.** 넘으면 즉시 하위 주제별로 분할하거나, 실제 소스코드 파일과 1:1 대응되는 하위 폴더 및 파일 구조로 세분화한다 (예: `B05_wf2_Route/backend/B05_wf2_Route_Router.md`). 분할 시 상위 페이지는 하위 페이지로 가는 링크 목록만 남기고, `index.md`도 함께 갱신한다.
|
||||
|
||||
### 폴더 구조
|
||||
```
|
||||
docs/
|
||||
├── raw/ (불변 원본)
|
||||
│ ├── plans/ 계획서
|
||||
│ ├── verification/ 검증보고서
|
||||
│ └── guidelines/ 행동지침
|
||||
├── wiki/
|
||||
│ ├── index.md 전체 카탈로그 (카테고리별 한 줄 목록)
|
||||
│ ├── log.md 연대기 로그 (append-only)
|
||||
│ ├── pages/ 페이지(B01~B09) 기준 — 프로젝트 구조의 B01~B09 폴더와 1:1 대응
|
||||
│ │ └── B0x_이름/
|
||||
│ │ ├── frontend.md
|
||||
│ │ ├── backend.md
|
||||
│ │ ├── db.md
|
||||
│ │ ├── api.md
|
||||
│ │ └── dependencies.md
|
||||
│ └── concepts/ 페이지를 관통하는 공통 개념 (DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸, API 공통, 의존성, 공통 스키마, workflow 상태 등)
|
||||
└── output/ 최종 산출물
|
||||
```
|
||||
- 페이지 하위 파일은 raw 반영 시 **필요한 것만 생성**한다 (5개를 미리 다 만들지 않음).
|
||||
- `wiki/pages/`의 카테고리는 프로젝트의 실제 B01~B09 폴더명과 항상 일치시킨다.
|
||||
|
||||
## graphify
|
||||
|
||||
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
||||
|
||||
Rules:
|
||||
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
||||
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
||||
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
||||
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). (Note: Code verifier does NOT run graphify, ingest, or lint. These are the responsibility of the wiki manager/librarian only.)
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
## 나는 누구인가
|
||||
- 이름:
|
||||
- 하는 일: 임도설계 실무자. Aislo(라이다 데이터 기반 임도 설계·견적 자동화 웹앱) 프로젝트의 기획·설계자.
|
||||
- 핵심 가치: 코드를 직접 작성하거나 깊이 이해하지는 못하지만, 도메인 전문성을 바탕으로 결과물(화면, 산출물)을 보고 실무적으로 맞는지 검증할 수 있다. 이 검증력을 근거로 AI에게 방향성과 구체적 지시를 내린다.
|
||||
|
||||
## 나의 역할들
|
||||
|
||||
### 임도설계 실무자 (도메인 전문가)
|
||||
- 하는 일: 임도 설계 실무 지식을 바탕으로 요구사항을 정의하고, AI가 만든 결과물이 실무 기준에 맞는지 검증한다.
|
||||
- 주요 관심사: 결과물의 실무적 타당성, 설계 로직의 정확성.
|
||||
|
||||
### 프로젝트 기획·설계자
|
||||
- 하는 일: AI 코드 어시스턴트에게 방향성과 지시를 제공하고, 계획서 작성과 검토를 주도한다.
|
||||
- 주요 관심사: AI와의 소통 효율, 문서·컨텍스트 구조화, 반복되는 방향성 이탈 방지.
|
||||
|
||||
## 나의 비전과 목표
|
||||
- 이루고자 하는 것: 라이다 데이터를 활용해 임도 설계를 자동화하는 웹앱(Aislo)을 완성한다. 동시에, 개발 과정에서 문서(백엔드/프론트엔드/DB/구조/계획서/검증보고서)가 비대해져 컨텍스트·토큰을 낭비하지 않도록, 페이지 단위로 구조화·연결된 위키 체계를 구축한다.
|
||||
- 타겟 독자/고객:
|
||||
- 1차: AI 코드 어시스턴트 자신 — 코드 작성 전후로 필요한 부분만 효율적으로 찾아 읽는 용도.
|
||||
- 2차: 6개월~1년 후의 AI 세션 및 팀원 — 프로젝트 구조, 설계 이유, 사용된 함수·변수, DB 정보를 스스로 파악해 개선하거나 인수인계받는 용도. (본인이 직접 위키를 다시 찾아볼 일은 거의 없음)
|
||||
|
||||
## AI에게 기대하는 것
|
||||
- 코드 작성 전후로 관련된 위키·계획서만 효율적으로 찾아 읽고, 문서 전체를 뒤지지 않기.
|
||||
- 계획서 작성 → 코드 작업 → 계획서 체크리스트 점검 → 코드 검증 → 검증보고서 작성 → 계획서 업데이트의 흐름을 주기적으로 관리하기.
|
||||
- 같은 실수나 배경 설명을 반복하지 않기 — 이전 결정사항과 컨텍스트를 유지하기.
|
||||
- 함수·변수 단위, DB 컬럼 단위까지 추적 가능하도록 문서화하기.
|
||||
|
||||
## 작업 규칙
|
||||
- 톤: 간결하고 명확하게. 불필요한 수식어 없이 핵심만
|
||||
- 언어: 한국어 기본
|
||||
- 결과물 형태: 계획서와 검증보고서는 각각 하나의 파일로 구성하여 지속 업데이트한다. 문서는 비대해지지 않도록 페이지 단위로 구조화하고, 하위에 관련 백엔드·프론트엔드·DB·API·의존성 정보를 연결한다.
|
||||
|
||||
---
|
||||
|
||||
## Wiki Schema (위키 운영 규칙)
|
||||
|
||||
> 이 규칙은 `docs/` 볼트에만 적용된다. 프로젝트 루트의 docs 폴더 상위의 `.agent/` 폴더(실제 코딩 라우팅용)와는 완전히 독립적이며, 서로 건드리지 않는다. 위키가 충분히 성숙하면 그때 `.agent`를 대체할지 판단한다.
|
||||
|
||||
### 레이어 구조
|
||||
- **raw/** — 불변 원본. 계획서·검증보고서·행동지침 등을 유형별로 넣는다. AI는 이 폴더를 절대 수정하지 않는다.
|
||||
- **wiki/** — AI가 raw를 읽고 편집·유지하는 산출물. 페이지(B01~B09) 기준 폴더 + 개념(concepts) 폴더로 구성.
|
||||
- **output/** — 최종 결과물(보고서, 슬라이드 등).
|
||||
|
||||
### 위키 관리자(Librarian) 역할
|
||||
AI는 이 볼트에서 "위키 관리자"로 행동한다. 단순 챗봇이 아니라, raw를 읽고 wiki를 지속적으로 편집·정리·연결하는 책임을 진다. wiki는 사용자가 아니라 AI가 쓰고 관리한다.
|
||||
|
||||
### 운영 방식
|
||||
- **Ingest** (원본 반영): raw/에 새 파일이 들어오면 → 내용을 읽고 핵심을 파악 → 관련된 `wiki/pages/B0x_*/`와 `wiki/concepts/*.md`를 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
|
||||
- **Query** (질의 응답): 코드 작업 전후로 질문을 받으면 → `wiki/index.md`를 먼저 확인 → 관련 페이지만 열어서 답변 → raw/는 위키에 없는 정보가 필요할 때만 최후 수단으로 참조. 답변 중 새로 정리된 내용(비교, 분석 등)은 대화에 흘려보내지 말고 관련 wiki 페이지에 다시 파일링한다.
|
||||
- **Lint** (건강 점검): 요청 시 위키 전체를 점검 — 페이지 간 모순, 오래된 내용(최근 raw로 갱신되지 않은 부분), 고아 페이지(연결 없음), 빠진 크로스링크를 찾아 보고한다.
|
||||
|
||||
### 위키 규칙
|
||||
1. **raw/는 절대 수정 금지** (불변 원본)
|
||||
2. wiki 페이지 생성·삭제 시 **index.md 필수 업데이트**
|
||||
3. 모든 오퍼레이션마다 **log.md에 기록**
|
||||
4. 내부 참조는 **위키링크(`[[페이지명]]`)** 형식 사용
|
||||
5. 모든 wiki 페이지에 **YAML frontmatter** 작성. `status`는 반드시 아래 3개 값 중 하나만 사용:
|
||||
- `draft` — 계획서만 반영됨, 아직 검증 안 됨
|
||||
- `stable` — 검증보고서로 확인된 내용까지 반영됨
|
||||
- `stale` — raw에 새 자료가 들어왔는데 아직 이 페이지에 반영 못함 (Lint 시 최우선 처리 대상)
|
||||
그 외 `related_pages`, `last_updated`도 함께 기록한다. **`wiki/pages/B0x_이름/`, `wiki/pages/A0x_이름/` 하위 파일은 다음 규칙을 따른다:**
|
||||
- **파일명 형식:** `{page_id}_{기능명}.md` (예: `A01_frontend.md`, `B05_backend.md`, `B03_db.md`)
|
||||
- **frontmatter에 `page_id` 필드 필수:** `page_id: A01_Home` 또는 `page_id: B05_wf2_Route` — 폴더 밖에서 파일 하나만 봐도 어느 페이지 소속인지 식별 가능해야 한다.
|
||||
- concepts 페이지에는 이 규칙을 적용하지 않는다(페이지 단위가 아니므로).
|
||||
6. 모순 발견 시 **양쪽 소스 모두 인용**하고 어느 쪽이 최신인지 명시. **계획서와 검증보고서가 충돌하면 검증보고서를 채택한다** (검증보고서 = 실제 코드가 검증된 상태, 계획서 = 그 시점의 의도). 계획서 쪽 내용은 지우지 말고 "계획 당시 의도"로 표시해 남긴다.
|
||||
7. 소스 요약은 사실만 기록, 해석·종합은 concepts 페이지에서
|
||||
8. 질의 시 **index.md 먼저**, raw/는 마지막 수단
|
||||
9. **새 페이지 생성보다 기존 페이지 업데이트를 우선**
|
||||
10. index 항목은 한 줄, 120자 이내로 요약
|
||||
11. **공유 자원은 1차 정의를 concepts에만 둔다.** 공유 자원이란 두 페이지 이상이 함께 쓰는 것 전부를 말한다 — DB 테이블·컬럼, 공통 함수(`common_util_*`), API 엔드포인트, Pydantic 요청/응답 스키마, 외부 라이브러리(의존성), workflow 상태 등. 페이지의 `db.md`/`backend.md`/`api.md`/`dependencies.md`에는 정의를 복사하지 않고, "이 페이지가 무슨 용도로 쓰는지" + `[[concepts/해당개념#항목명]]` 위키링크만 남긴다.
|
||||
12. **concepts 페이지에는 사용처(역참조) 목록을 직접 적는다.** 예: `db_schema.md`의 "routes 테이블" 항목 아래에 `사용처: [[B05_wf2_Route]], [[B06_wf3_ProfileCross]]`. 새 페이지가 그 자원을 쓰게 되면 concepts 쪽 사용처 목록도 함께 갱신한다 (Obsidian 자동 백링크에만 의존하지 않음).
|
||||
13. **함수·API·테이블 등 개별 항목은 표 형식으로만 기록한다** (서술문 금지). 최소 컬럼: `항목 | 위치(파일:줄번호) | 역할`. 코드 AI가 grep 없이 바로 해당 위치로 이동할 수 있어야 한다.
|
||||
|
||||
| 항목 | 위치 | 역할 |
|
||||
|---|---|---|
|
||||
| `generate_dem()` | `B04_wf1_Surface/B04_wf1_Surface_Engine.py:142` | 포인트클라우드 → DEM 변환 |
|
||||
|
||||
14. **페이지 하나(파일 하나)는 100줄을 넘지 않는다.** 넘으면 즉시 하위 주제별로 분할한다 (예: `concepts/db_schema.md` → `concepts/db_schema/routes.md`, `concepts/db_schema/input_files.md` ...). 분할 시 상위 페이지는 하위 페이지로 가는 링크 목록만 남기고, `index.md`도 함께 갱신한다.
|
||||
|
||||
### 폴더 구조
|
||||
```
|
||||
docs/
|
||||
├── raw/ (불변 원본)
|
||||
│ ├── plans/ 계획서
|
||||
│ ├── verification/ 검증보고서
|
||||
│ └── guidelines/ 행동지침
|
||||
├── wiki/
|
||||
│ ├── index.md 전체 카탈로그 (카테고리별 한 줄 목록)
|
||||
│ ├── log.md 연대기 로그 (append-only)
|
||||
│ ├── pages/ 페이지(B01~B09) 기준 — 프로젝트 구조의 B01~B09 폴더와 1:1 대응
|
||||
│ │ └── B0x_이름/
|
||||
│ │ ├── frontend.md
|
||||
│ │ ├── backend.md
|
||||
│ │ ├── db.md
|
||||
│ │ ├── api.md
|
||||
│ │ └── dependencies.md
|
||||
│ └── concepts/ 페이지를 관통하는 공통 개념 (DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸, API 공통, 의존성, 공통 스키마, workflow 상태 등)
|
||||
└── output/ 최종 산출물
|
||||
```
|
||||
- 페이지 하위 파일은 raw 반영 시 **필요한 것만 생성**한다 (5개를 미리 다 만들지 않음).
|
||||
- `wiki/pages/`의 카테고리는 프로젝트의 실제 B01~B09 폴더명과 항상 일치시킨다.
|
||||
|
||||
## graphify
|
||||
|
||||
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
||||
|
||||
Rules:
|
||||
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
||||
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
||||
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
||||
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). (Note: Code verifier does NOT run graphify, ingest, or lint. These are the responsibility of the wiki manager/librarian only.)
|
||||
@@ -0,0 +1,7 @@
|
||||
# output/ 폴더 규칙
|
||||
|
||||
wiki/ 내용을 바탕으로 생성한 최종 산출물(보고서, 슬라이드, 요약 문서 등)을 둔다.
|
||||
|
||||
## 규칙
|
||||
- 여기 있는 파일은 wiki/의 특정 시점 스냅샷이다. 원본이 아니므로, 내용 갱신이 필요하면 wiki/를 먼저 갱신한 뒤 다시 생성한다.
|
||||
- 파일명에 생성 날짜를 포함해 버전을 구분한다.
|
||||
@@ -0,0 +1,7 @@
|
||||
# output/ 폴더 규칙
|
||||
|
||||
wiki/ 내용을 바탕으로 생성한 최종 산출물(보고서, 슬라이드, 요약 문서 등)을 둔다.
|
||||
|
||||
## 규칙
|
||||
- 여기 있는 파일은 wiki/의 특정 시점 스냅샷이다. 원본이 아니므로, 내용 갱신이 필요하면 wiki/를 먼저 갱신한 뒤 다시 생성한다.
|
||||
- 파일명에 생성 날짜를 포함해 버전을 구분한다.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
type: graph-relationship-audit
|
||||
status: draft
|
||||
last_updated: 2026-08-16
|
||||
---
|
||||
|
||||
# Graphify 저연결 노드 감사 — 2026-08-16
|
||||
|
||||
## 기준선
|
||||
|
||||
| 항목 | 수 |
|
||||
|---|---:|
|
||||
| 전체 노드 | 86 |
|
||||
| 차수 0 | 17 |
|
||||
| 차수 1 | 40 |
|
||||
| 저연결 합계 | 57 |
|
||||
|
||||
## 판정
|
||||
|
||||
| 분류 | 수 | 처리 |
|
||||
|---|---:|---|
|
||||
| 관계 보강 대상 | 24 | 상위 지도·공유 자원·canonical 페이지에 직접 관계 추가 |
|
||||
| 정상 말단 문서 | 32 | 개별 Router·UI·하위 컴포넌트이므로 유지 |
|
||||
| 의도적 참고 문서 | 1 | 외부 WebCAD 비교환경; 현행 구현 관계와 분리 |
|
||||
|
||||
## 관계 보강 대상
|
||||
|
||||
| 유형 | 대상 | 보강 관계 |
|
||||
|---|---|---|
|
||||
| 상위 지도 | 공개·인증·관리 지도, 프로젝트 지도, 구현 현황 | 프로젝트 지도와 상호 연결 |
|
||||
| 공통 개념 | 인증, 배수, 유토, 저장 경로, UI Templates, dependencies | 공유 자원 지도와 실제 소비 단계 연결 |
|
||||
| 공통 프레임워크 | A00 Common | App Shell·Router·UI Templates 연결 |
|
||||
| 공개 화면 | A01~A05 Frontend | 공개·인증·관리 지도와 연결 |
|
||||
| 단계 API | B01·B03·B04·B05·B06 API | 해당 Backend와 연결 |
|
||||
| 세부 엔진 | B06 Engine Areas | B06 Backend와 연결 |
|
||||
| B07 DB | workflow 상태와 연결 | 전용 수량 DB가 없음을 명시 |
|
||||
| B08 Dependencies | B08 Backend/Frontend와 dependencies 연결 | CAD 의존성 범위 명시 |
|
||||
|
||||
## 정상 말단 판정 원칙
|
||||
|
||||
- 파일 단위 Router는 상위 Backend 하나와 연결돼 있으면 정상이다.
|
||||
- 파일 단위 UI 컴포넌트는 상위 Frontend 또는 조립 컴포넌트 하나와 연결돼 있으면 정상이다.
|
||||
- 연결 수를 늘리기 위해 호출·데이터 관계가 없는 링크를 추가하지 않는다.
|
||||
- 저연결 수 자체를 품질 목표로 삼지 않고, 상위 탐색 경로가 존재하는지를 기준으로 삼는다.
|
||||
|
||||
## 의도적 참고 문서
|
||||
|
||||
`B07 외부 WebCAD 비교 실행환경`은 과거 후보 비교 기록이다. 현행 B08 OpenWebCAD 구현의 직접 의존성으로 연결하지 않고, `historical_reference`로만 분류한다.
|
||||
|
||||
## 보강 후 결과
|
||||
|
||||
| 항목 | 보강 전 | 보강 후 |
|
||||
|---|---:|---:|
|
||||
| 전체 노드 | 86 | 87 |
|
||||
| 전체 관계 | 58 | 75 |
|
||||
| 차수 0 | 17 | 0 |
|
||||
| 차수 1 | 40 | 50 |
|
||||
|
||||
- 차수 0은 모두 해소됐다.
|
||||
- 차수 1 증가는 API·Router·UI·페이지 요약을 별도 canonical 노드로 정확히 분리한 결과다.
|
||||
- 최종 차수 1 구성은 page summary 21, backend file 10, frontend file 8, concept 9, architecture 2다.
|
||||
- 말단 문서를 억지로 다중 연결하지 않고 상위 탐색 경로만 보장하는 원칙을 유지한다.
|
||||
- 모든 노드와 관계의 `source_file`은 `docs/wiki/` 아래이며 누락 근거는 0이다.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
type: wiki-lint-report
|
||||
status: draft
|
||||
last_updated: 2026-08-16
|
||||
---
|
||||
|
||||
# 위키 의미 린트 보고서 — 2026-08-16
|
||||
|
||||
## 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---:|
|
||||
| 검사 문서 | 157 |
|
||||
| error | 0 |
|
||||
| warning | 18 |
|
||||
| info | 0 |
|
||||
|
||||
> error가 남아 있으면 Graphify 재생성을 진행하지 않는다.
|
||||
|
||||
## error (0)
|
||||
|
||||
- 없음
|
||||
|
||||
## warning (18)
|
||||
|
||||
| 규칙 | 파일 | 내용 |
|
||||
|---|---|---|
|
||||
| `line-limit-exception` | `log.md` | 운영 문서가 992줄이다. |
|
||||
| `source-missing` | `log.md` | 존재하지 않는 근거: docs/raw/plans/2026-07-12_plan_add_phase4_role.md |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A08_Support/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A07_Register/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: B01_Dashboard/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A09_Security/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: ui_template_locale |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: ui_template_elements |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A06_Login/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A00_Common/app_shell_framework |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: ../../concepts/... |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: db_schema/... |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: db_schema |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: 페이지/backend/파일명 |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: B02_ProjRegister/backend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: A01_Home/frontend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: B09_Estimation/frontend |
|
||||
| `wikilink-unresolved` | `log.md` | 대상: B08_DesignDetail/frontend |
|
||||
|
||||
## info (0)
|
||||
|
||||
- 없음
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# raw/ 폴더 규칙
|
||||
|
||||
이 폴더는 불변 원본 저장소다. 계획서, 검증보고서, 행동지침 등 사용자가 넣거나 세션 중 생성된 원본 자료를 유형별로 보관한다.
|
||||
|
||||
## 제약
|
||||
- **절대 수정하지 않는다.** 이 폴더의 파일은 읽기 전용 소스로만 취급한다. 오탈자나 오래된 내용이 있어도 여기서 고치지 않고, wiki/ 쪽에서 반영·정정한다.
|
||||
- 파일을 삭제하지 않는다.
|
||||
|
||||
## 하위 폴더
|
||||
- `plans/` — 계획서
|
||||
- `verification/` — 검증보고서. **계획서와 내용이 충돌하면 이쪽을 채택한다** (실제 코드가 검증된 상태이므로).
|
||||
- `guidelines/` — 행동지침
|
||||
|
||||
## 이 폴더가 업데이트되면
|
||||
`docs/CLAUDE.md`의 Wiki Schema에 따라 Ingest 절차를 수행한다: 새 파일을 읽고 → 관련 `wiki/pages/`, `wiki/concepts/` 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
|
||||
@@ -0,0 +1,15 @@
|
||||
# raw/ 폴더 규칙
|
||||
|
||||
이 폴더는 불변 원본 저장소다. 계획서, 검증보고서, 행동지침 등 사용자가 넣거나 세션 중 생성된 원본 자료를 유형별로 보관한다.
|
||||
|
||||
## 제약
|
||||
- **절대 수정하지 않는다.** 이 폴더의 파일은 읽기 전용 소스로만 취급한다. 오탈자나 오래된 내용이 있어도 여기서 고치지 않고, wiki/ 쪽에서 반영·정정한다.
|
||||
- 파일을 삭제하지 않는다.
|
||||
|
||||
## 하위 폴더
|
||||
- `plans/` — 계획서
|
||||
- `verification/` — 검증보고서. **계획서와 내용이 충돌하면 이쪽을 채택한다** (실제 코드가 검증된 상태이므로).
|
||||
- `guidelines/` — 행동지침
|
||||
|
||||
## 이 폴더가 업데이트되면
|
||||
`docs/CLAUDE.md`의 Wiki Schema에 따라 Ingest 절차를 수행한다: 새 파일을 읽고 → 관련 `wiki/pages/`, `wiki/concepts/` 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
|
||||
@@ -0,0 +1,200 @@
|
||||
# 프로젝트 행동지침 및 기술 스택 명세 (agent.md)
|
||||
|
||||
## 프로젝트 & DB 정보
|
||||
|
||||
### 📌 프로그램명
|
||||
**Aislo (아이슬로)**
|
||||
- **의미**: AI (인공지능) + Slotti (핀란드어: 임도/산림 도로)
|
||||
- **콘셉트**: 산림의 미래를 열어가는 인공지능 경로 설계 솔루션
|
||||
|
||||
---
|
||||
|
||||
## 1. 기술 환경 및 기본 스택 (Technical Stack Baseline)
|
||||
프로젝트의 코드를 작성, 테스트 또는 검증할 환경 명확화 및 호환 기준 제시.
|
||||
|
||||
### A. 백엔드 프레임워크 (Python)
|
||||
* **Runtime:** Python v3.12.7
|
||||
* **Framework:** FastAPI / Pydantic
|
||||
* **Geometry/GIS Engine:** Trimesh, Whitebox, Geopandas, Shapely, Rasterio, Laspy
|
||||
|
||||
### B. 데이터베이스 (MariaDB)
|
||||
* **DBMS:** MariaDB v10.6+
|
||||
* **Character Set:** utf8mb4 (한글 완벽 지원)
|
||||
* **Collation:** utf8mb4_unicode_ci
|
||||
* **DB Driver (비동기):** `aiomysql` (순수 Python, Windows 호환. asyncmy는 Cython 빌드 필요로 미채택)
|
||||
* **쿼리 방식:** Raw SQL (ORM 사용 금지)
|
||||
* **공간 데이터:** JSON 기반 저장 (MariaDB는 PostGIS 미지원)
|
||||
|
||||
### C. 프론트엔드 & WebCAD (TypeScript & WebGL)
|
||||
* **Language/Runtime:** TypeScript / Node.js
|
||||
* **Rendering:** HTML5 Canvas 및 WebGL 기반 WebCAD 시스템 구현
|
||||
|
||||
### D. 인코딩 및 다국어 표준
|
||||
* **인코딩:** 텍스트 파일은 UTF-8 표준으로 통일
|
||||
|
||||
---
|
||||
|
||||
## 2. 문맥 라우팅 및 문서 구조 (Context Routing)
|
||||
코드 작성 및 기술 검증 시 문서 우선순위에 따른 계층적 레퍼런스 필수 읽기.
|
||||
|
||||
* **1단계 (필수):** `.agent/structure.md` 읽기 후 신규 폴더 생성 기준 분석.
|
||||
* **2단계 (선택 읽기):** 프로젝트 구현에 필요한 세부 기술 명세 읽기.
|
||||
* **그룹 A (UI, 스타일, 컴포넌트, WebCAD):** `.agent/frontend.md` 필수 추가 읽기.
|
||||
* **그룹 B (알고리즘, 저장, DB, MariaDB 값 제어):** `.agent/backend.md` 필수 추가 읽기.
|
||||
* **그룹 C (DB 구조 변경):** DB 구조 변경 시 `.agent/db_schema.md` 선택적 읽기.
|
||||
|
||||
---
|
||||
|
||||
## 3. 코드 작성 기본 제약 (Constraints)
|
||||
* **구조 준수:** `.agent/structure.md` 트리 구조 엄격 준수.
|
||||
* **700줄 제한:** 단일 파일 코드 작성/수정 시 700줄 이상이 될 경우 사전 공지. 기능별 파일 분할 후 `structure.md` 갱신 필수.
|
||||
* **안전 보관:** 백엔드 작업 시 보안 저장소 경로 명시 및 임의 삭제 방지 필수.
|
||||
* **경로 활용:** 단계별 페이지 기반 폴더 구조(B03~B09)와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
|
||||
* **일관성 검증:** 설계 단계에서부터 DB 설계, 파일 경로, API 라우팅이 모두 동일한 워크플로우 기준으로 통일되어야 한다. (불일치 발생 시 설계 단계에서 재논의 필수)
|
||||
* **코드 포맷팅 (자동화 도구):** 프로젝트의 코드 작성이 완료되는 시, 반드시 프로젝트 루트에서 각 언어별 포맷터를 재실행하여 스타일을 통일해야 한다.
|
||||
* *Python 포맷팅 명령어:* `ruff format [파일명]` 및 `ruff check --fix [파일명]` 실행
|
||||
* *TypeScript/CSS 포맷팅 명령어:* `npx prettier --write [파일명]` 실행
|
||||
* **외부 라이브러리 활용:** 외부 라이브러리 활용 시 프로젝트에 맞춰 호환 라이브러리 선정하고, 불필요한 함수는 작성하지 않도록 최소화.
|
||||
* **서버실행은 사용자가 실행:** 필요한 경우 의존성 설치까지는 OK, 단 서버실행은 사용자가 진행
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터베이스 상세 명세 (Database Specification)
|
||||
|
||||
### 4.1 스키마 기본 정보
|
||||
- **DB 명:** `aislo_db`
|
||||
- **DBMS:** MariaDB v10.6+
|
||||
- **인코딩:** utf8mb4_unicode_ci
|
||||
- **테이블 수:** 18개
|
||||
- **드라이버:** aiomysql (비동기)
|
||||
|
||||
### 4.2 파일 경로 추적 (Path Tracking)
|
||||
**모든 중간 산출물의 파일 경로를 DB에 기록:**
|
||||
- `input_files.raw_file_path` — 원본 입력 파일
|
||||
- `processed_point_cloud.converted_file_path` — 변환된 포인트클라우드
|
||||
- `surface_models.model_file_path` — 지표면 모델
|
||||
- `routes.route_data_path` — 경로 데이터
|
||||
- `longitudinal_sections.longitudinal_file_path` — 종단면
|
||||
- `cross_sections.cross_section_file_path` — 횡단면
|
||||
- `structures.structure_data_path` — 구조물 배치
|
||||
- `quantity_items.quantity_data_path` — 수량 항목
|
||||
- `outputs.outputs_directory_path` — 산출물 폴더
|
||||
- `output_files.output_file_path` — 개별 산출 파일
|
||||
|
||||
### 4.3 저장소 구조 (Workflow-based Folder Structure)
|
||||
```
|
||||
storage/{company_slug}/{user_slug}/{project_id}/
|
||||
├── B03_FileInput/input/ (WF0: 원본 입력)
|
||||
├── B04_wf1_Surface/processed/ (WF1: 변환된 포인트클라우드 & 모델)
|
||||
├── B05_wf2_Route/route/ (WF2: 경로 설계)
|
||||
├── B06_wf3_ProfileCross/ (WF3: 종단면 & 횡단면)
|
||||
├── B07_wf4_DesignDetail/structures/ (WF4: 구조물 배치)
|
||||
├── B08_wf5_Quantity/quantities/ (WF5: 수량 산출)
|
||||
└── B09_wf6_Estimation/v1,v2,.../ (WF6: 최종 산출물)
|
||||
```
|
||||
|
||||
### 4.4 공간 데이터 처리 (MariaDB 특성)
|
||||
- **지하형 기하 데이터:** GEOMETRY 타입 미지원 → JSON으로 저장
|
||||
- **좌표 저장 예:** `{"type": "Point", "coordinates": [127.5, 37.5]}`
|
||||
- **경로 저장 예:** `{"type": "LineString", "coordinates": [[127.5, 37.5], [127.6, 37.6]]}`
|
||||
- **애플리케이션 처리:** Python의 Shapely, Geopandas에서 JSON 파싱 후 기하 연산
|
||||
|
||||
### 4.5 역할 기반 접근 제어 (RBAC - Role-Based Access Control)
|
||||
|
||||
**3가지 사용자 역할:**
|
||||
|
||||
| 역할 | 영문 | 설명 | 접근 권한 |
|
||||
|------|------|------|---------|
|
||||
| 시스템 관리자 | SYSTEM_ADMIN | 최고 관리자 | 전체 회사/사용자/프로젝트 조회 및 관리 |
|
||||
| 회사 관리자 | ADMIN | 회사의 주요 아이디 (초기 로그인) | 회사 내 팀원/프로젝트 관리, 가입 요청 승인 |
|
||||
| 일반 사용자 | USER | 팀원 | 개인 프로젝트 생성/수정, 워크플로우 진행 |
|
||||
|
||||
**회사 마스터 (is_master) 플래그:**
|
||||
- TRUE: 회사 생성자 또는 ADMIN 역할 (팀원 승인/거부 권한)
|
||||
- FALSE: 일반 팀원
|
||||
|
||||
**사용자 상태 생명주기:**
|
||||
```
|
||||
PENDING_EMAIL (임시)
|
||||
↓ (이메일 인증)
|
||||
NO_COMPANY (로그인 가능, 회사 미연결)
|
||||
↓ (회사 생성 또는 참여 신청)
|
||||
PENDING (승인 대기) 또는 ACTIVE (회사 연결 완료)
|
||||
↓
|
||||
ACTIVE (모든 기능 접근)
|
||||
↓ (퇴사/비활성화)
|
||||
INACTIVE 또는 REJECTED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 워크플로우 6단계 (6-Stage Workflow)
|
||||
|
||||
```
|
||||
WF0: B03_FileInput (파일 입력)
|
||||
↓ (LAS, TIF, TFW, PRJ, DXF 업로드)
|
||||
WF1: B04_wf1_Surface (지표면 분석)
|
||||
↓ (DEM, TIN, 포인트클라우드 변환)
|
||||
WF2: B05_wf2_Route (경로 설계)
|
||||
↓ (최적 경로 계산)
|
||||
WF3: B06_wf3_ProfileCross (종횡단 생성)
|
||||
↓ (종단면, 횡단면 생성)
|
||||
WF4: B07_wf4_DesignDetail (상세 설계)
|
||||
↓ (구조물 배치)
|
||||
WF5: B08_wf5_Quantity (수량 산출)
|
||||
↓ (수량 항목 계산)
|
||||
WF6: B09_wf6_Estimation (견적·문서)
|
||||
↓ (Excel, PDF, DXF 생성)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 주요 문서 참고 순서
|
||||
|
||||
1. **계획서 읽기:** 구현 전 반드시 읽을 문서
|
||||
- `.agent/plan_register_split.md` — B01_Dashboard 설계 및 역할별 기능
|
||||
- `.agent/plan_register_split_validation.md` — 검증 항목 및 테스트 체크리스트
|
||||
|
||||
2. **구조 설계:** `.agent/structure.md` 읽기
|
||||
- 폴더/파일 명명 규칙
|
||||
- 전체 디렉토리 트리 구조
|
||||
|
||||
3. **DB 스키마:** `.agent/db_schema.md` 읽기
|
||||
- 사용자/회사/조직 테이블 (5개)
|
||||
- 프로젝트 및 워크플로우 테이블
|
||||
- 테이블 관계도
|
||||
|
||||
4. **백엔드 구현:** `.agent/backend.md` 읽기
|
||||
- 아키텍처 및 라우팅
|
||||
- DB 및 MariaDB 설정
|
||||
- 인증 시스템 (로그인/역할/인가)
|
||||
- 역할 기반 접근 제어 (RBAC)
|
||||
|
||||
5. **프론트 구현:** `.agent/frontend.md` 읽기
|
||||
- UI 요소 및 레이아웃 제약
|
||||
- 인증 및 라우팅 가드
|
||||
- 역할 기반 대시보드 렌더링
|
||||
|
||||
6. **마이그레이션:** `.agent/migration_plan.md` 읽기 (필요 시)
|
||||
|
||||
7. **프로젝트 정보:** `.agent/project_info.md` 읽기 (필요 시)
|
||||
|
||||
---
|
||||
|
||||
## 7. 요약
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| **프로그램명** | Aislo (아이슬로) |
|
||||
| **DB 명** | aislo_db |
|
||||
| **DBMS** | MariaDB v10.6+ |
|
||||
| **인코딩** | utf8mb4_unicode_ci |
|
||||
| **Backend** | Python 3.12+ / FastAPI |
|
||||
| **Frontend** | TypeScript / Node.js |
|
||||
| **드라이버** | aiomysql |
|
||||
| **워크플로우** | 6단계 (WF0~WF6) |
|
||||
| **테이블 수** | 23개 (조직/인증 5개 포함) |
|
||||
| **폴더 구조** | 워크플로우 기반 (B03~B09) |
|
||||
| **인증 방식** | HttpOnly Secure 세션 쿠키 |
|
||||
| **사용자 역할** | SYSTEM_ADMIN, ADMIN, USER (3가지) |
|
||||
| **대시보드** | B01_Dashboard (역할별 동적 렌더링) |
|
||||
@@ -0,0 +1,300 @@
|
||||
# 백엔드 제어 명세서 (backend.md)
|
||||
|
||||
## 1. 아키텍처 및 라우팅 (Architecture)
|
||||
* **수직 통합:** 백엔드 로직은 페이지 폴더(`A01_`, `B01_` 등)별로 격리. 거대 단일 모듈 금지.
|
||||
* **파일 분할:** 파일 수 제한 없음. 코드 집중 방지를 위해 기능별 분할 자율화.
|
||||
* **파일명 규칙:** `[폴더명]_Router.py`, `[폴더명]_Engine.py`, `[폴더명]_Schema.py` 형태 준수.
|
||||
* **루트 main.py:** 라우터 등록(`include_router`)만 수행. 비즈니스 로직 작성 금지.
|
||||
|
||||
---
|
||||
|
||||
## 2. DB 및 MariaDB (Database)
|
||||
* **비동기 통신:** `async/await` 및 `aiomysql` 드라이버 사용 강제. (asyncmy는 Cython 빌드 필요로 Windows 환경 미채택)
|
||||
* **쿼리 방식:** ORM 사용 금지. **Raw SQL** 작성 원칙. 파라미터 바인딩은 `%s` 플레이스홀더 사용.
|
||||
* **커넥션/트랜잭션:** 풀에서 `pool.acquire()`로 커넥션 확보 후 `connection.cursor()`로 커서 생성. 다건 쓰기는 `connection.begin()` → `commit()` / 예외 시 `rollback()`.
|
||||
* **자동 증가 ID:** `INSERT` 후 `cursor.lastrowid`로 조회 (PostgreSQL의 `RETURNING id` 미지원).
|
||||
* **공간 데이터:** GEOMETRY 타입 미지원. 좌표, 다각형, 경로 등은 JSON 형식으로 저장 후 애플리케이션에서 처리.
|
||||
|
||||
---
|
||||
|
||||
## 3. 설정 및 저장소 (Config & Storage)
|
||||
* **하드코딩 금지:** 제어 변수/파라미터 하드코딩 금지. `config/config_system.py`에서 `import` 필수.
|
||||
* **물리 파일 격리:** 포인트 클라우드, 분석 중간 파일, 메쉬, 임시 커서는 DB 저장 금지.
|
||||
* **저장 경로:** `storage/[고객사명]/[사용자명]/[프로젝트ID]/`에 물리 파일 저장 후 DB에는 경로만 기록.
|
||||
* **단계별 루트 강제:** 프로젝트 저장소 내부는 실제 워크플로우 페이지명과 동일한 `B03_FileInput/` ~ `B09_wf6_Estimation/` 폴더로 분리한다. `raw/`, `processed/`, `computed/`를 프로젝트 루트의 공용 폴더로 만들지 않는다.
|
||||
* **경로 정의 우선순위:** 단계별 세부 폴더와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
|
||||
* **경로 생성 책임:** 페이지별 백엔드는 자기 단계 폴더만 생성·수정한다. 다른 단계의 산출물을 직접 삭제하거나 덮어쓰지 않고 stale 상태를 통해 재계산 필요성을 전파한다.
|
||||
* **DB 저장 형식:** DB에는 프로젝트 루트 기준 상대 경로를 기록하고, 실제 파일 접근 시 설정의 저장소 루트와 안전하게 결합한다. 사용자 입력 경로를 직접 결합하거나 절대 경로를 DB에 저장하지 않는다.
|
||||
* **MariaDB 특성:**
|
||||
- 공간 기하 데이터(좌표, 폴리곤, 경로 등)는 GEOMETRY 타입 미지원 → JSON 또는 TEXT로 저장
|
||||
- 예: `{"type": "Point", "coordinates": [127.5, 37.5]}` (GeoJSON 형식)
|
||||
- 애플리케이션(Python)에서 JSON 파싱 후 기하 연산 처리
|
||||
|
||||
---
|
||||
|
||||
## 4. 검증 및 예외 처리 (Error Handling)
|
||||
* **데이터 검증:** 모든 JSON 요청은 라우터 진입 전 Pydantic 모델로 타입/범위 필수 검증.
|
||||
* **예외 포획:** 모든 지형/메쉬 연산은 `try-except` 필수 처리.
|
||||
* **반환 포맷:** 에러 발생 시 `{"status": "error", "message": "원인"}` 표준 포맷 반환.
|
||||
|
||||
---
|
||||
|
||||
## 5. 워크플로우 상태 동기화 (Workflow State Sync)
|
||||
|
||||
### 5.1 다중 브라우저 동시 작업 원칙
|
||||
* **브라우저 독립성:** 같은 계정/프로젝트를 여러 브라우저에서 동시 접근 가능. 각 브라우저는 서버 데이터 기반 독립 작동.
|
||||
* **클라이언트 상태 격리:** `localStorage`는 브라우저별 격리(도메인 단위 공유). 공유 데이터는 항상 서버 영구저장소 우선.
|
||||
* **영구저장소 설계:** 계산 결과는 `.agent/db_schema_simple.md`에 정의된 페이지별 단계 폴더에 저장한다.
|
||||
* `B03_FileInput/` — 원본 입력 및 파일 메타데이터
|
||||
* `B04_wf1_Surface/` — 변환 포인트클라우드, 지표면 모델 및 분석 결과
|
||||
* `B05_wf2_Route/` — 경로, 경로점 및 설계 파라미터
|
||||
* `B06_wf3_ProfileCross/` — 종단·횡단 결과 및 인덱스
|
||||
* **워크플로우 상태:** 여러 단계가 공유하는 `workflow.json`의 실제 위치는 저장소 경로 유틸에서 단일하게 정의하며, 원자적 쓰기를 적용한다. 단계별 결과 파일의 위치를 프로젝트 루트의 `result_*.json` 이름으로 추정하지 않는다.
|
||||
|
||||
### 5.2 Stale 상태 감지 및 전파
|
||||
* **workflow.json 구조:**
|
||||
```json
|
||||
{
|
||||
"current_stage": "route",
|
||||
"completed": ["scan", "surface"],
|
||||
"stale_from": "route",
|
||||
"stage1_confirmed": {...}
|
||||
}
|
||||
```
|
||||
* **Stale 발생 조건:**
|
||||
* 상위 단계(예: surface) 재계산 → 하위 단계(route, section) 자동으로 `stale_from` 설정
|
||||
* 다른 브라우저에서 변경 감지 → 기존 결과 무효화
|
||||
* **상태 업데이트 함수:**
|
||||
```python
|
||||
def _patch_workflow_stale(project_id: str, stale_from: str | None) -> None:
|
||||
"""workflow.json의 stale_from 필드만 원자적 업데이트"""
|
||||
wf_path = get_project_workflow_path(project_id)
|
||||
# 기존 상태 유지하며 stale_from만 변경
|
||||
```
|
||||
|
||||
### 5.3 클라이언트 폴링 패턴 (Polling)
|
||||
* **주기:** GET `/api/projects/{project_id}/workflow` — 3초마다 호출
|
||||
* **응답:** workflow.json 전체 반환 (현재 단계, 완료 이력, stale 상태 포함)
|
||||
* **클라이언트 동작:**
|
||||
1. stale 감지 → 사용자에게 선택지 제시
|
||||
2. "계속하기" → 기존 결과 유지 (위험 경고)
|
||||
3. "재계산하기" → 상위 단계 재실행 (모든 하위 결과 초기화)
|
||||
* **구현 예시:**
|
||||
```typescript
|
||||
setInterval(async () => {
|
||||
const workflow = await fetch(`/api/projects/${projectId}/workflow`);
|
||||
if (workflow.stale_from && workflow.stale_from !== currentStage) {
|
||||
handleStaleness(workflow.stale_from); // 사용자에게 알림
|
||||
}
|
||||
}, 3000);
|
||||
```
|
||||
|
||||
### 5.4 WebSocket 확장 (미래 선택사항)
|
||||
* **실시간 알림:** 폴링 대신 WebSocket으로 업그레이드 가능 (응답성 향상)
|
||||
* **전송 메시지:**
|
||||
```json
|
||||
{
|
||||
"type": "workflow_changed",
|
||||
"project_id": "...",
|
||||
"stale_from": "route",
|
||||
"changed_by": "other_browser"
|
||||
}
|
||||
```
|
||||
* **마이그레이션 시기:** 사용자 수 증가 또는 실시간성 요구 시 적용
|
||||
|
||||
---
|
||||
|
||||
## 6. 로그인 및 인증 시스템 (Authentication & Authorization)
|
||||
|
||||
### 6.1 이메일 기반 OTP 인증 (Email OTP)
|
||||
* **OTP 생성:** 6자리 난수 (000000~999999)
|
||||
* **저장 방식:** DB에 bcrypt/PBKDF2 해시값 저장 (평문 절대 금지)
|
||||
* **유효 시간:** 5분 (config에서 설정 가능)
|
||||
* **발송:** Google SMTP (개발) → AWS SES (상용화) 전환 예정
|
||||
* **재시도 로직:** 최대 3회 (지수 백오프: 2초, 4초, 8초)
|
||||
|
||||
### 6.2 비밀번호 정책 (Password Policy)
|
||||
* **최소 길이:** 8자 이상
|
||||
* **권장 구성:** 대문자/소문자/숫자/특수문자 (강제 아님, Google 기준)
|
||||
* **해시 알고리즘:** bcrypt (라운드 12 이상) 또는 PBKDF2
|
||||
* **저장 방식:** 해시값만 DB에 저장 (평문 절대 금지)
|
||||
|
||||
### 6.3 세션 관리 (Session Management) - 확정: Secure HttpOnly 세션 쿠키
|
||||
|
||||
* **방식:** Secure HttpOnly 세션 쿠키 (JWT 아님)
|
||||
* **쿠키 이름:** `session_id`
|
||||
* **유효 시간:** 12시간
|
||||
* **Secure 플래그:** true (HTTPS만 전송, 개발 환경 localhost에서는 false)
|
||||
* **HttpOnly 플래그:** true (JavaScript 접근 불가)
|
||||
* **SameSite:** Lax (CSRF 방지)
|
||||
* **저장소:** MariaDB `sessions` 테이블
|
||||
* **활동 감지:** 마지막 요청으로부터 4시간 (파일 업로드 등 장시간 작업 고려)
|
||||
* **세션 만료 판정:**
|
||||
```python
|
||||
if (current_time - last_activity_at > 4시간) or (current_time - created_at > 12시간):
|
||||
→ 세션 자동 만료
|
||||
```
|
||||
* **만료 처리:** 프론트엔드는 만료 5분 전 알림, 만료 후 모든 요청 거부 (401 Unauthorized)
|
||||
* **DB 풀 설정:** `autocommit=True` (pooled 커넥션의 트랜잭션 스냅샷 문제 방지 — 새 세션 INSERT 후 다른 커넥션에서 즉시 조회 가능)
|
||||
|
||||
### 6.4 로그인 이력 기록 (Audit Log)
|
||||
* **기록 항목:**
|
||||
- `user_id`, `email`, `login_timestamp`, `user_agent` (브라우저/OS), `status` (SUCCESS/FAILURE)
|
||||
* **보관 기간:** 1년 (자동 삭제)
|
||||
* **무차별 대입 방지:** 5회 이상 실패 시 계정 15분 잠금
|
||||
* **IP 주소:** 법적 이슈로 수집하지 않음 (권장)
|
||||
|
||||
### 6.4.1 사용자 상태 생명주기 (User Status Lifecycle)
|
||||
* **PENDING_EMAIL:** 회원가입 후 이메일 인증 전 (임시)
|
||||
* **NO_COMPANY:** 이메일 인증 완료, 회사 미연결 (로그인 가능, B01_Dashboard 접근 가능, B02~B11 워크플로우 차단)
|
||||
* **PENDING:** 회사 참여 신청 후 ADMIN 승인 대기 (로그인 가능, B01_Dashboard 접근 가능, B02~B11 워크플로우 차단)
|
||||
* **ACTIVE:** 회사 연결 완료, 모든 기능 접근 가능 (B01_Dashboard + B02~B11)
|
||||
* **INACTIVE:** 퇴사/계정 휴활성화
|
||||
* **REJECTED:** 회사 참여 신청 거부
|
||||
|
||||
### 6.5 회사 연결 필수화 (Mandatory Company Linking)
|
||||
* **접근 제어:**
|
||||
- B02~B11 워크플로우 라우터에 `Depends(require_company)` 의존성 추가
|
||||
- `session["company_id"] is None` 이면 403 Forbidden 반환 (`"회사 연결이 필요합니다."`)
|
||||
* **라우팅 가드 (프론트):**
|
||||
- B02~B11 진입 시 `fetchSessionUser()` → `company_id` 확인
|
||||
- 없으면 B01_ACCOUNT로 리다이렉트
|
||||
* **회사 생성/연결:**
|
||||
- `POST /api/account/company/create`: 신규 회사 생성 후 사용자를 MASTER로 자동 연결 (status='ACTIVE')
|
||||
- `POST /api/account/company/join`: 기존 회사 참여 신청 (status='PENDING', 마스터 승인 필요)
|
||||
|
||||
### 6.6 권한 체크 (Authorization)
|
||||
* **마스터 권한:**
|
||||
- 팀원 목록 조회, 승인/거부, 내보내기
|
||||
- 회사 정보 수정, 구독 관리
|
||||
- 마스터 대시보드 접근
|
||||
* **팀원 권한:**
|
||||
- 프로젝트 생성/수정/조회
|
||||
- 워크플로우 실행 (B03~B09)
|
||||
- 개인 프로필 수정
|
||||
* **시스템관리자 권한:**
|
||||
- 모든 회사/사용자 데이터 접근 (슈퍼유저)
|
||||
- 회사/사용자 생성/수정/삭제
|
||||
- 관리자 대시보드 (모니터링, Q&A 관리)
|
||||
* **검증 방식:** 라우터 진입 전 JWT/토큰 검증, 필요시 데이터베이스에서 권한 확인
|
||||
|
||||
### 6.7 로그인 기반 인증 갱신 (Login-based Auth Renewal)
|
||||
* **목적:** 활성 사용자의 인증 유지, 비활성 사용자 자동 차단
|
||||
* **방식:** 로그인 시 인증 유효 기간 자동 연장 (3개월)
|
||||
* **구현:**
|
||||
- `users.last_login` → 마지막 로그인 시간 업데이트
|
||||
- `users.auth_expires_at` → 매 로그인 시 NOW() + 3개월로 설정
|
||||
* **인증 확인:**
|
||||
- auth_expires_at > NOW() → 인증 유효 (바로 대시보드 이동)
|
||||
- auth_expires_at <= NOW() → 재인증 모달 표시
|
||||
* **장점:**
|
||||
- 활동적인 사용자: 지속적으로 인증 유지
|
||||
- 휴면 사용자: 자동으로 재인증 요구
|
||||
- 일관된 정책: 고정 3개월이 아닌 활동 기반 연장
|
||||
|
||||
### 6.8 역할 기반 접근 제어 (Role-Based Access Control)
|
||||
|
||||
**사용자 역할 (users.role)**
|
||||
- `SYSTEM_ADMIN`: 시스템 관리자 (최고 권한)
|
||||
- `ADMIN`: 회사 관리자 (회사 내 관리권 한정)
|
||||
- `USER`: 일반 사용자
|
||||
|
||||
### 6.9 대시보드 권한 검증 및 감시 로깅 (B01_Dashboard Authorization & Audit)
|
||||
* **권한 검증 헬퍼 필수:** `.agent/plan_dashboard_management.md` 섹션 9의 Python 헬퍼 함수 구현
|
||||
- `can_edit_project(user, project)` - 프로젝트 수정 권한
|
||||
- `can_delete_project(user)` - 프로젝트 삭제 권한 (SYSTEM_ADMIN만)
|
||||
- `can_change_role(user, target_user, new_role)` - 역할 변경 권한
|
||||
- ADMIN이 SYSTEM_ADMIN으로 변경 시도 시 즉시 False 반환
|
||||
- ADMIN은 USER ↔ ADMIN만 변경 가능 (같은 회사만)
|
||||
- `can_manage_automation(user, automation)` - 자동화 로직 관리 권한 (USER 차단)
|
||||
- `is_last_admin(company_id, exclude_user_id)` - 회사의 마지막 ADMIN 확인
|
||||
|
||||
* **백엔드 권한 재검증 필수:** 모든 수정/삭제/역할변경 API에서 권한 재검증
|
||||
- 클라이언트 UI 신뢰 금지 (프론트엔드는 UI만 제어)
|
||||
- 권한 없는 사용자가 API 직접 호출 시도 → 403 Forbidden
|
||||
- 권한 오류 메시지: `{"status": "error", "message": "Access denied"}`
|
||||
|
||||
* **감시 로깅 (audit_logs):** 다음 행동들을 기록
|
||||
- 프로젝트 삭제: `action="DELETE_PROJECT"`, `resource_type="project"`
|
||||
- 사용자 역할 변경: `action="CHANGE_ROLE_TO_{NEW_ROLE}"`, `resource_type="user"`
|
||||
- 사용자 삭제: `action="DELETE_USER"`, `resource_type="user"`
|
||||
- 자동화 로직 삭제: `action="DELETE_AUTOMATION"`, `resource_type="automation"`
|
||||
|
||||
* **USER 자동화 조회 권한 차단:** `/api/projects/{pid}/automations` GET
|
||||
- 요청 사용자의 role이 USER인 경우 403 Forbidden 반환
|
||||
- 메시지: `"Automation logic viewing is not allowed for users"`
|
||||
|
||||
* **마지막 ADMIN 보호:** 사용자 삭제 API에서 체크
|
||||
- `is_last_admin(company_id, exclude_user_id=user_id)` 호출
|
||||
- True 반환 시 400 Bad Request: `"Cannot delete the last admin of the company"`
|
||||
- 역할 변경 API에서도 동일 로직 적용
|
||||
|
||||
**마스터 플래그 (users.is_master)**
|
||||
- `TRUE`: 회사 생성자 또는 ADMIN 역할 (팀원 승인/거부 권한)
|
||||
- `FALSE`: 일반 팀원
|
||||
|
||||
**접근 제어 규칙:**
|
||||
|
||||
| 기능 | SYSTEM_ADMIN | ADMIN | USER |
|
||||
|------|-------------|-------|------|
|
||||
| 전체 회사 조회 | ✅ | ✗ | ✗ |
|
||||
| 전체 사용자 조회 | ✅ | ✗ | ✗ |
|
||||
| 사용자 역할 변경 | ✅ | ✗ | ✗ |
|
||||
| 회사 생성 | ✅ | ✅ | ✅ |
|
||||
| 회사 팀원 관리 | ✅ | ✅ (자신 회사만) | ✗ |
|
||||
| 가입 요청 승인 | ✅ | ✅ (자신 회사만) | ✗ |
|
||||
| 시스템 로그 조회 | ✅ | ✗ | ✗ |
|
||||
| 프로젝트 생성 | ✅ | ✅ | ✅ |
|
||||
| 프로젝트 조회 | ✅ (전체) | ✅ (회사별) | ✅ (개인) |
|
||||
|
||||
### 6.9 이메일 발송 (Email Infrastructure)
|
||||
* **SMTP 설정:**
|
||||
- **개발:** Google Gmail SMTP (umsangdon@gmail.com)
|
||||
- **상용화:** Google Workspace 또는 AWS SES로 전환
|
||||
* **발송 유형:**
|
||||
- OTP 인증 코드
|
||||
- 가입 확인 메일
|
||||
- 팀원 가입 승인 요청 (ADMIN 수신)
|
||||
- 팀원 승인/거부 결과 (사용자에게)
|
||||
- 회사 참여 신청 알림 (SYSTEM_ADMIN 수신)
|
||||
- 로그인 기반 인증 갱신 알림
|
||||
* **구현:** `common_util/common_util_email.py` 모듈
|
||||
- 비동기 발송 (asyncio.create_task)
|
||||
- 재시도 로직 (최대 3회)
|
||||
- 템플릿 렌더링 (common_util_email_templates.py)
|
||||
* **자세한 내용:** `.agent/email_infrastructure_plan.md` 참고
|
||||
|
||||
---
|
||||
|
||||
## 7. 시스템 리소스 모니터링 (System Resource Monitoring)
|
||||
|
||||
### 7.1 계측 백그라운드 태스크
|
||||
* **구현:** `common_util/common_util_resource_monitor.py`의 `sample_resources_loop()`
|
||||
* **등록:** `main.py` lifespan에서 `asyncio.create_task`로 시작, 종료 시 cancel (기존 `cleanup_expired_sessions` 패턴과 동일)
|
||||
* **계측 주기:** 2분 (`config_system.RESOURCE_SAMPLE_INTERVAL_SEC = 120`)
|
||||
* **계측 항목:** CPU(`psutil.cpu_percent`), 메모리(`psutil.virtual_memory().percent`), 디스크(`shutil.disk_usage`)
|
||||
* **방어:** 계측 예외는 로깅 후 무시하여 앱 안정성 유지
|
||||
|
||||
### 7.2 이중 기록 (DB + 로그 파일)
|
||||
* **DB 기록:** `system_resources` 테이블에 INSERT (활성 세션/프로젝트 수 포함) — 대시보드 그래프 조회 소스
|
||||
* **로그 파일:** 루트 `log/system_resources.log` 단일 파일에 탭 구분 1줄 append
|
||||
- 형식: `<ISO timestamp>\tcpu=<v>\tmem=<v>\tdisk=<v>\tstorage_mb=<v>`
|
||||
- `log/`는 `.gitignore` 처리 (런타임 생성물)
|
||||
|
||||
### 7.3 1개월 롤링 보관 (Retention)
|
||||
* **보관 기간:** 30일 (`config_system.RESOURCE_LOG_RETENTION_DAYS = 30`)
|
||||
* **DB:** 매 계측 시 `timestamp < NOW() - 30일` 행 DELETE
|
||||
* **로그 파일:** 매 계측 시 30일 경과 줄 제거 후 새 줄 추가(오래된 줄 삭제 + 신규 줄 추가 방식) → 파일 크기 무한 증가 방지
|
||||
|
||||
### 7.4 리소스 API
|
||||
* **엔드포인트:** `GET /api/dashboard/admin/resources?days=30` (SYSTEM_ADMIN 전용, 범위 1~90일)
|
||||
* **응답:**
|
||||
- `current`: 요청 시점 실시간 계측값 (그래프 미저장)
|
||||
- `history`: `system_resources` 테이블의 최근 N일 시계열 (그래프 렌더링용, **다운샘플링 적용**)
|
||||
- `stats`: 활성 사용자/프로젝트/저장소 지표
|
||||
* **다운샘플링 (조회 시):**
|
||||
- 30일 원본은 약 21,600점(2분 간격) → 그대로 전송 시 페이로드 과다(~4MB)
|
||||
- SQL에서 시간버킷 평균으로 축소: `bucket = max(120초, days*86400 / 300)` → 최대 약 300점
|
||||
- `GROUP BY FLOOR(UNIX_TIMESTAMP(timestamp) / bucket)` + `AVG(...)`로 집계
|
||||
- 계측 간격(2분)보다 버킷이 작으면 원본 해상도 유지 (하루 이하 조회 시)
|
||||
- 효과: 30일 조회 페이로드 ~4MB → ~55KB 수준
|
||||
@@ -0,0 +1,851 @@
|
||||
# DB 스키마 (테이블 및 열 목록)
|
||||
|
||||
**최종 업데이트:** 2026-07-10
|
||||
**DB 명:** `aislo_db` (MariaDB v10.6+, utf8mb4_unicode_ci)
|
||||
**총 테이블 수:** 34개 (33개 현재 운영 + 1개 향후 사용)
|
||||
|
||||
---
|
||||
|
||||
## 1. 사용자 & 인증 & 조직 그룹
|
||||
|
||||
### 1-1. users 테이블
|
||||
```
|
||||
users
|
||||
├── id (INT, PK) — 사용자 고유 번호
|
||||
├── email (VARCHAR(255), UNIQUE) — 로그인 이메일
|
||||
├── password_hash (VARCHAR(255)) — 비밀번호 암호화 저장
|
||||
├── name (VARCHAR(255)) — 사용자 이름
|
||||
├── position (VARCHAR(100), NULL) — 직급 (예: 과장, 대리, 사원)
|
||||
├── department (VARCHAR(100), NULL) — 부서명 (예: 설계팀, 영업팀)
|
||||
├── phone (VARCHAR(20), NULL) — 연락처 (예: 010-1234-5678)
|
||||
├── company_id (INT, FK → companies.id, NULL) — 소속 회사
|
||||
├── role (ENUM) — 역할: SYSTEM_ADMIN, ADMIN, USER (기본값: USER)
|
||||
├── is_master (TINYINT(1)) — 회사 마스터 여부 (기본값: 0)
|
||||
├── status (ENUM) — 상태: PENDING_EMAIL, NO_COMPANY, PENDING, ACTIVE, INACTIVE, REJECTED (기본값: PENDING_EMAIL)
|
||||
├── last_login (DATETIME, NULL) — 마지막 로그인 시간
|
||||
├── auth_expires_at (DATETIME, NULL) — 인증 유효 만료 시간 (3개월 주기)
|
||||
├── last_email_verified_at (TIMESTAMP, NULL) — 마지막 이메일 인증 시간
|
||||
├── login_failures (INT) — 연속 로그인 실패 횟수 (기본값: 0, 보안)
|
||||
├── last_failed_at (TIMESTAMP, NULL) — 마지막 로그인 실패 시간
|
||||
├── account_locked_until (TIMESTAMP, NULL) — 계정 잠금 해제 시간
|
||||
├── created_at (TIMESTAMP) — 가입일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
**상태 생명주기:**
|
||||
- PENDING_EMAIL: 회원가입 후 이메일 인증 전
|
||||
- NO_COMPANY: 이메일 인증 완료, 회사 미연결 (로그인 가능, B02~B11 차단)
|
||||
- PENDING: 회사 참여 신청 후 관리자 승인 대기
|
||||
- ACTIVE: 회사 연결 완료, 모든 기능 접근 가능
|
||||
- INACTIVE: 퇴사/계정 비활성화
|
||||
- REJECTED: 회사 참여 신청 거부
|
||||
|
||||
### 1-2. companies 테이블
|
||||
```
|
||||
companies
|
||||
├── id (INT, PK) — 회사 고유 번호
|
||||
├── name (VARCHAR(255), UNIQUE) — 회사명
|
||||
├── business_registration_number (VARCHAR(20), UNIQUE, NULL) — 사업자등록번호
|
||||
├── business_address (VARCHAR(255), NULL) — 사업장 주소
|
||||
├── business_owner (VARCHAR(100), NULL) — 사업주 이름
|
||||
├── business_status (VARCHAR(50), NULL) — 기업 상태
|
||||
├── company_code (VARCHAR(20), NULL) — 회사 코드
|
||||
├── phone_number (VARCHAR(20), NULL) — 회사 연락처
|
||||
├── representative_name (VARCHAR(100), NULL) — 대표자명
|
||||
├── master_user_id (INT, NULL) — 마스터 사용자 ID
|
||||
├── status (ENUM) — 상태: ACTIVE, INACTIVE, SUSPENDED (기본값: ACTIVE)
|
||||
├── subscription_status (ENUM) — 구독 상태: FREE, TRIAL, PAID, EXPIRED (기본값: FREE)
|
||||
├── created_by (INT, FK → users.id, NULL) — 회사 생성자
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-3. join_requests 테이블 (가입 신청 - 향후 사용)
|
||||
```
|
||||
join_requests
|
||||
├── id (INT, PK) — 요청 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 신청한 사용자
|
||||
├── company_id (INT, FK → companies.id) — 신청 대상 회사
|
||||
├── requested_at (TIMESTAMP) — 신청 시간
|
||||
├── status (ENUM) — 상태: PENDING, APPROVED, REJECTED (기본값: PENDING)
|
||||
├── reviewed_by (INT, FK → users.id, NULL) — 검토한 관리자
|
||||
├── reviewed_at (TIMESTAMP, NULL) — 검토 시간
|
||||
├── review_comment (TEXT, NULL) — 검토 의견/거부 사유
|
||||
└── UNIQUE KEY (user_id, company_id) — 중복 신청 방지
|
||||
```
|
||||
|
||||
**동작 (향후 구현 예정):**
|
||||
- 사용자 가입 신청 → status = PENDING 생성
|
||||
- 관리자 승인 → status = APPROVED, users.status = ACTIVE, users.company_id 세팅
|
||||
- 관리자 거부 → status = REJECTED, review_comment에 거부 사유 기록, 이메일 발송
|
||||
|
||||
### 1-5. email_otps 테이블 (이메일 인증)
|
||||
```
|
||||
email_otps
|
||||
├── id (INT, PK) — OTP 고유 번호
|
||||
├── email (VARCHAR(255)) — 이메일 주소
|
||||
├── otp_code (VARCHAR(10)) — OTP 코드
|
||||
├── purpose (VARCHAR(50)) — 목적: REGISTRATION, PASSWORD_RESET, EMAIL_VERIFICATION
|
||||
├── expires_at (TIMESTAMP) — 만료 시간
|
||||
├── verified_at (TIMESTAMP, NULL) — 인증 시간
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-6. sessions 테이블 (세션 관리)
|
||||
```
|
||||
sessions
|
||||
├── id (INT, PK) — 세션 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 사용자 ID
|
||||
├── session_token (VARCHAR(255), UNIQUE) — 세션 토큰
|
||||
├── expires_at (TIMESTAMP) — 세션 만료 시간
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── last_accessed_at (TIMESTAMP, NULL) — 마지막 접근 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-7. trusted_devices 테이블 (장치 신뢰 관리)
|
||||
```
|
||||
trusted_devices
|
||||
├── id (INT, PK) — 장치 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 사용자 ID
|
||||
├── device_fingerprint (VARCHAR(255)) — 장치 지문
|
||||
├── device_name (VARCHAR(100), NULL) — 장치명
|
||||
├── ip_address (VARCHAR(50)) — IP 주소
|
||||
├── user_agent (TEXT) — 브라우저/OS 정보
|
||||
├── trusted_until (TIMESTAMP, NULL) — 신뢰 만료 시간
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-8. user_consents 테이블 (사용자 동의 관리)
|
||||
```
|
||||
user_consents
|
||||
├── id (INT, PK) — 동의 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 사용자 ID
|
||||
├── consent_type (VARCHAR(100)) — 동의 종류: TERMS, PRIVACY, MARKETING, etc
|
||||
├── consented (TINYINT(1)) — 동의 여부 (1 = 동의, 0 = 미동의)
|
||||
├── consent_version (VARCHAR(20)) — 약관 버전
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-9. login_logs 테이블 (로그인 로그)
|
||||
```
|
||||
login_logs
|
||||
├── id (INT, PK) — 로그 고유 번호
|
||||
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
|
||||
├── email (VARCHAR(255), NULL) — 로그인 시도 이메일
|
||||
├── status (VARCHAR(50)) — 상태: SUCCESS, FAILURE, LOCKED
|
||||
├── ip_address (VARCHAR(50), NULL) — IP 주소
|
||||
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
|
||||
├── reason (TEXT, NULL) — 실패 사유
|
||||
├── created_at (TIMESTAMP) — 기록 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-10. activity_logs 테이블 (활동 로그)
|
||||
```
|
||||
activity_logs
|
||||
├── id (INT, PK) — 로그 고유 번호
|
||||
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
|
||||
├── action (VARCHAR(100)) — 행동: LOGIN, LOGOUT, FILE_UPLOAD, PROJECT_CREATE, etc
|
||||
├── entity_type (VARCHAR(100), NULL) — 대상 타입: project, file, route, etc
|
||||
├── entity_id (VARCHAR(50), NULL) — 대상 ID
|
||||
├── description (TEXT, NULL) — 행동 설명
|
||||
├── ip_address (VARCHAR(50), NULL) — IP 주소
|
||||
├── created_at (TIMESTAMP) — 기록 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-11. audit_logs 테이블 (감시 로그 - 보안)
|
||||
```
|
||||
audit_logs
|
||||
├── id (INT, PK) — 로그 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 행동 수행자
|
||||
├── action (VARCHAR(100)) — 행동: CREATE, READ, UPDATE, DELETE, EXPORT, DOWNLOAD
|
||||
├── entity_type (VARCHAR(100)) — 대상 타입: projects, routes, structures, outputs
|
||||
├── entity_id (VARCHAR(50)) — 대상 ID
|
||||
├── details (LONGTEXT, NULL) — 상세 정보 (JSON 형식)
|
||||
├── ip_address (VARCHAR(50), NULL) — IP 주소
|
||||
├── timestamp (TIMESTAMP) — 기록 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 1-12. system_audit_logs 테이블 (시스템 감시)
|
||||
```
|
||||
system_audit_logs
|
||||
├── id (INT, PK) — 로그 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 행동 수행자
|
||||
├── action (VARCHAR(50)) — 행동: LOGIN, LOGOUT, USER_CREATE, COMPANY_CREATE, ROLE_CHANGE
|
||||
├── resource_type (VARCHAR(50)) — 대상 타입: user, company, project, join_request
|
||||
├── resource_id (INT, NULL) — 대상 ID
|
||||
├── ip_address (VARCHAR(50), NULL) — IP 주소
|
||||
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
|
||||
├── timestamp (TIMESTAMP) — 기록 시간
|
||||
└── INDEX (timestamp, user_id) — 성능 최적화
|
||||
```
|
||||
|
||||
### 1-13. system_resources 테이블 (시스템 리소스 모니터링)
|
||||
```
|
||||
system_resources
|
||||
├── id (INT, PK) — 기록 고유 번호
|
||||
├── cpu_usage_percent (FLOAT, NULL) — CPU 사용률 (%)
|
||||
├── memory_usage_percent (FLOAT, NULL) — 메모리 사용률 (%)
|
||||
├── disk_usage_percent (FLOAT, NULL) — 디스크 사용률 (%)
|
||||
├── active_user_count (INT, NULL) — 활성 사용자 수
|
||||
├── active_project_count (INT, NULL) — 활성 프로젝트 수
|
||||
├── total_storage_mb (FLOAT, NULL) — 전체 저장소 사용량 (MB)
|
||||
├── timestamp (TIMESTAMP) — 기록 시간
|
||||
└── INDEX (timestamp) — 시계열 조회 최적화
|
||||
```
|
||||
|
||||
**리소스 측정 방식:**
|
||||
- **현재값 실측:** 요청 시점 실시간 측정 (DB 저장 없이 응답에 포함)
|
||||
- **이력 조회:** `system_resources` 테이블에서 기본 30일치 시계열 조회
|
||||
- **활성 지표:** active_user_count는 유효 세션 수, active_project_count는 미삭제 프로젝트 수 실시간 집계
|
||||
|
||||
### 1-14. support_requests 테이블 (고객 지원)
|
||||
```
|
||||
support_requests
|
||||
├── id (INT, PK) — 요청 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 신청자
|
||||
├── title (VARCHAR(255)) — 제목
|
||||
├── description (LONGTEXT) — 설명
|
||||
├── category (VARCHAR(100), NULL) — 카테고리
|
||||
├── priority (VARCHAR(50), NULL) — 우선순위: LOW, MEDIUM, HIGH, URGENT
|
||||
├── status (VARCHAR(50)) — 상태: OPEN, IN_PROGRESS, RESOLVED, CLOSED
|
||||
├── assigned_to (INT, FK → users.id, NULL) — 담당자
|
||||
├── resolution (LONGTEXT, NULL) — 해결 내용
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 프로젝트 그룹
|
||||
|
||||
### 2-1. projects 테이블
|
||||
```
|
||||
projects
|
||||
├── id (CHAR(36), PK) — 프로젝트 고유 ID (UUID)
|
||||
├── user_id (INT, FK → users.id) — 프로젝트 소유자
|
||||
├── company_id (INT, FK → companies.id) — 소속 회사
|
||||
├── name (VARCHAR(255)) — 프로젝트명 (예: "2025년 산불진화임도")
|
||||
├── region (VARCHAR(100), NULL) — 지역명 (예: "울진군 금강송면")
|
||||
├── road_type (VARCHAR(100), NULL) — 임도 종류: 간선임도, 지선임도, 산불진화임도, 계류보전
|
||||
├── project_year (INT, NULL) — 사업 연도 (예: 2025)
|
||||
├── estimated_length_m (FLOAT, NULL) — 추정 연장 (미터)
|
||||
├── memo (TEXT, NULL) — 비고/메모
|
||||
├── status (VARCHAR(50), NULL) — 상태: NEW, FILE_UPLOADED, WF1_ANALYZING, WF1_COMPLETE, ... (기본값: NEW)
|
||||
├── crs_epsg (INT, NULL) — 좌표계 (예: 5178 = 한국 표준, 기본값: 5178)
|
||||
├── storage_path (VARCHAR(500), NULL) — 파일시스템 경로 (예: "storage/company/user/project_uuid")
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 2-2. project_versions 테이블 (프로젝트 버전 관리)
|
||||
```
|
||||
project_versions
|
||||
├── id (INT, PK) — 버전 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── version_num (INT) — 버전 번호 (1, 2, 3, ...)
|
||||
├── status (VARCHAR(50), NULL) — 저장된 상태
|
||||
├── data (LONGTEXT, NULL) — 설계 데이터 스냅샷 (JSON 형식)
|
||||
├── snapshot_at (TIMESTAMP) — 스냅샷 시점
|
||||
└── created_at (TIMESTAMP) — 생성일
|
||||
```
|
||||
|
||||
### 2-3. project_automations 테이블 (프로젝트 자동화 규칙)
|
||||
```
|
||||
project_automations
|
||||
├── id (INT, PK) — 자동화 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── name (VARCHAR(100)) — 자동화 규칙명
|
||||
├── logic_type (VARCHAR(50)) — 로직 타입: trigger_on_stage_complete, auto_notify, etc
|
||||
├── config_json (LONGTEXT) — 자동화 설정 (JSON)
|
||||
├── status (ENUM) — 상태: DRAFT, ACTIVE, INACTIVE (기본값: DRAFT)
|
||||
├── created_by (INT, FK → users.id) — 생성자
|
||||
├── updated_by (INT, FK → users.id, NULL) — 마지막 수정자
|
||||
├── last_executed_at (DATETIME, NULL) — 마지막 실행 시간
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 파일 & 업로드 관리 그룹
|
||||
|
||||
### 3-1. upload_sessions 테이블 (파일 업로드 세션)
|
||||
```
|
||||
upload_sessions
|
||||
├── id (INT, PK) — 업로드 세션 고유 번호
|
||||
├── user_id (INT, FK → users.id) — 업로드자
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── session_id (VARCHAR(255), UNIQUE) — 세션 ID
|
||||
├── file_name (VARCHAR(255)) — 파일명
|
||||
├── file_size_mb (FLOAT) — 파일 크기
|
||||
├── chunk_count (INT) — 청크 개수
|
||||
├── uploaded_chunks (INT) — 업로드된 청크 수
|
||||
├── status (VARCHAR(50)) — 상태: PENDING, IN_PROGRESS, COMPLETED, FAILED (기본값: PENDING)
|
||||
├── started_at (TIMESTAMP) — 시작 시간
|
||||
├── completed_at (TIMESTAMP, NULL) — 완료 시간
|
||||
└── expires_at (TIMESTAMP) — 세션 만료 시간
|
||||
```
|
||||
|
||||
### 3-2. upload_chunks 테이블 (파일 청크 관리)
|
||||
```
|
||||
upload_chunks
|
||||
├── id (INT, PK) — 청크 고유 번호
|
||||
├── upload_session_id (INT, FK → upload_sessions.id) — 업로드 세션 ID
|
||||
├── chunk_index (INT) — 청크 순서
|
||||
├── chunk_size_mb (FLOAT) — 청크 크기
|
||||
├── checksum (VARCHAR(255), NULL) — 체크섬 (무결성 검사)
|
||||
├── status (VARCHAR(50)) — 상태: PENDING, UPLOADED, VERIFIED (기본값: PENDING)
|
||||
├── uploaded_at (TIMESTAMP, NULL) — 업로드 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 3-3. input_files 테이블 (원본 입력 파일)
|
||||
```
|
||||
input_files
|
||||
├── id (INT, PK) — 파일 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 속한 프로젝트
|
||||
├── file_type (VARCHAR(50), NULL) — 파일 종류: las, tif, tfw, prj, dxf, dwg, other
|
||||
├── original_filename (VARCHAR(255)) — 원래 파일명 (예: "cloud_merged.las")
|
||||
├── raw_file_path (VARCHAR(500)) — 원본 저장 경로 (예: "storage/.../raw/las/cloud_merged.las")
|
||||
├── file_size_mb (FLOAT, NULL) — 파일 크기 (MB)
|
||||
├── upload_by (INT, FK → users.id, NULL) — 업로드한 사용자
|
||||
├── upload_at (TIMESTAMP) — 업로드 날짜
|
||||
├── crs_epsg (INT, NULL) — 파일 좌표계 (예: 5178)
|
||||
├── metadata (LONGTEXT, NULL) — 파일 메타데이터 (JSON)
|
||||
├── status (VARCHAR(50), NULL) — 상태: UPLOADED, PROCESSED, ARCHIVED (기본값: UPLOADED)
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 지표면 분석 & 처리 그룹
|
||||
|
||||
### 4-1. processed_point_cloud 테이블 (변환된 포인트클라우드)
|
||||
```
|
||||
processed_point_cloud
|
||||
├── id (INT, PK) — 변환 데이터 고유 번호
|
||||
├── input_file_id (INT, FK → input_files.id) — 원본 LAS 파일 참조
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── process_type (VARCHAR(50), NULL) — 변환 방식: filtered, sampled, classified
|
||||
├── processed_file_path (VARCHAR(500), NULL) — 변환된 파일 경로
|
||||
├── converted_format (VARCHAR(50), NULL) — 변환 포맷: las, ply, laz 등
|
||||
├── converted_file_path (VARCHAR(500), NULL) — 변환 포맷 저장 경로
|
||||
├── point_count (INT, NULL) — 포인트 개수
|
||||
├── min_z (FLOAT, NULL) — 최저 높이
|
||||
├── max_z (FLOAT, NULL) — 최고 높이
|
||||
├── mean_z (FLOAT, NULL) — 평균 높이
|
||||
├── x_min (FLOAT, NULL) — 최소 경도
|
||||
├── x_max (FLOAT, NULL) — 최대 경도
|
||||
├── y_min (FLOAT, NULL) — 최소 위도
|
||||
├── y_max (FLOAT, NULL) — 최대 위도
|
||||
├── density_per_sqm (FLOAT, NULL) — 단위 면적당 포인트 밀도
|
||||
├── classification_summary (LONGTEXT, NULL) — 분류 요약 (JSON)
|
||||
├── processing_params (LONGTEXT, NULL) — 변환 파라미터 (JSON)
|
||||
├── processed_at (TIMESTAMP) — 변환 완료 날짜
|
||||
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 4-2. surface_models 테이블 (지표면 모델)
|
||||
```
|
||||
surface_models
|
||||
├── id (INT, PK) — 지표면 모델 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── model_type (VARCHAR(50), NULL) — 모델 종류: dem_grid, tin, mesh_triangulated, contour_lines
|
||||
├── source_file_id (INT, FK → input_files.id, NULL) — 원본 LAS 파일
|
||||
├── processed_cloud_id (INT, FK → processed_point_cloud.id, NULL) — 변환된 포인트클라우드 참조
|
||||
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
|
||||
├── crs_epsg (INT, NULL) — 좌표계
|
||||
├── resolution_m (FLOAT, NULL) — 해상도 (래스터인 경우)
|
||||
├── model_file_path (VARCHAR(500), NULL) — 모델 저장 경로 (예: "storage/.../surface/dem.tif")
|
||||
├── generation_params (LONGTEXT, NULL) — 생성 파라미터 (JSON)
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── completed_at (TIMESTAMP, NULL) — 완료 시간
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 4-3. terrain_layers 테이블 (지형 레이어)
|
||||
```
|
||||
terrain_layers
|
||||
├── id (INT, PK) — 레이어 고유 번호
|
||||
├── surface_model_id (INT, FK → surface_models.id) — 지표면 모델 참조
|
||||
├── layer_name (VARCHAR(100), NULL) — 레이어명 (예: "지표", "제1층", "제2층")
|
||||
├── geometry_type (VARCHAR(50), NULL) — 기하 종류: POINTCLOUD, GRID, MESH, CONTOUR
|
||||
├── layer_file_path (VARCHAR(500), NULL) — 레이어 파일 저장 경로
|
||||
├── file_format (VARCHAR(50), NULL) — 파일 형식: geojson, geotiff, ply, obj 등
|
||||
├── file_size_mb (FLOAT, NULL) — 파일 크기
|
||||
├── statistics (LONGTEXT, NULL) — 통계 (JSON)
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 경로 & 설계 그룹
|
||||
|
||||
### 5-1. routes 테이블 (경로 설계)
|
||||
```
|
||||
routes
|
||||
├── id (INT, PK) — 경로 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── surface_model_id (INT, FK → surface_models.id, NULL) — 기반 지표면 모델
|
||||
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED, ARCHIVED (기본값: DRAFT)
|
||||
├── start_chainage_m (FLOAT, NULL) — 시작점 측점 (m)
|
||||
├── end_chainage_m (FLOAT, NULL) — 종료점 측점 (m)
|
||||
├── total_length_m (FLOAT, NULL) — 총 연장 (m)
|
||||
├── grade_percent (LONGTEXT, NULL) — 각 구간 종단 경사도 (JSON array)
|
||||
├── constraints (LONGTEXT, NULL) — 설계 제약조건 (JSON)
|
||||
├── algorithm_params (LONGTEXT, NULL) — 알고리즘 파라미터 (JSON)
|
||||
├── route_data_path (VARCHAR(500), NULL) — 경로 데이터 저장 경로 (GeoJSON)
|
||||
├── computed_at (TIMESTAMP, NULL) — 계산 완료 날짜
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
**참고:** 경로 좌표는 JSON 형식으로 `route_data_path` 파일에 저장됨 (GeoJSON LineString)
|
||||
|
||||
### 5-2. route_points 테이블 (경로 샘플 포인트)
|
||||
```
|
||||
route_points
|
||||
├── id (INT, PK) — 포인트 고유 번호
|
||||
├── route_id (INT, FK → routes.id) — 경로 ID
|
||||
├── chainage_m (FLOAT, NULL) — 측점 (m)
|
||||
├── elevation_m (FLOAT, NULL) — 높이
|
||||
├── slope_percent (FLOAT, NULL) — 경사도 (%)
|
||||
├── sequence_num (INT, NULL) — 순서번호
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 5-3. route_statistics 테이블 (경로 통계)
|
||||
```
|
||||
route_statistics
|
||||
├── id (INT, PK) — 통계 고유 번호
|
||||
├── route_id (INT, FK → routes.id) — 경로 ID
|
||||
├── min_slope (FLOAT, NULL) — 최소 경사도 (%)
|
||||
├── max_slope (FLOAT, NULL) — 최대 경사도 (%)
|
||||
├── mean_slope (FLOAT, NULL) — 평균 경사도 (%)
|
||||
├── cut_volume_m3 (FLOAT, NULL) — 절토량 (m³)
|
||||
├── fill_volume_m3 (FLOAT, NULL) — 성토량 (m³)
|
||||
├── tree_cutting_volume (FLOAT, NULL) — 목재 절감 추정량
|
||||
└── cost_score (FLOAT, NULL) — 알고리즘 점수
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 종단면 & 횡단면 그룹
|
||||
|
||||
### 6-1. longitudinal_sections 테이블 (종단면)
|
||||
```
|
||||
longitudinal_sections
|
||||
├── id (INT, PK) — 종단면 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── route_id (INT, FK → routes.id) — 경로 ID
|
||||
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
|
||||
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
|
||||
│ ├── chainages (측점 배열, m)
|
||||
│ ├── elevations (지표 표고 배열)
|
||||
│ ├── grades (경사도 배열, %)
|
||||
│ └── design_elevations (설계 표고 배열)
|
||||
├── longitudinal_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
|
||||
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 6-2. cross_sections 테이블 (횡단면)
|
||||
```
|
||||
cross_sections
|
||||
├── id (INT, PK) — 횡단면 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── route_id (INT, FK → routes.id) — 경로 ID
|
||||
├── chainage_m (FLOAT, NULL) — 측점 (예: 0, 20, 40, ... m)
|
||||
├── sequence_num (INT, NULL) — 횡단면 순번 (1, 2, ...)
|
||||
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
|
||||
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
|
||||
│ ├── left_slope (좌측 사면 경사, %)
|
||||
│ ├── right_slope (우측 사면 경사, %)
|
||||
│ ├── width_m (노폭)
|
||||
│ ├── cut_volume_m3 (절토량)
|
||||
│ ├── fill_volume_m3 (성토량)
|
||||
│ ├── structures (이 단면 내 구조물 ID 배열)
|
||||
│ └── notes (메모)
|
||||
├── cross_section_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 구조물 & 수량 그룹
|
||||
|
||||
### 7-1. structures 테이블 (구조물)
|
||||
```
|
||||
structures
|
||||
├── id (INT, PK) — 구조물 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── cross_section_id (INT, FK → cross_sections.id, NULL) — 횡단면 ID
|
||||
├── structure_type (VARCHAR(100), NULL) — 종류: 낙석방지책, 돌붙임, 계간수로, 낙차공 등
|
||||
├── chainage_m (FLOAT, NULL) — 측점 (m)
|
||||
├── location (VARCHAR(50), NULL) — 위치: LEFT, CENTER, RIGHT
|
||||
├── length_m (FLOAT, NULL) — 길이
|
||||
├── width_m (FLOAT, NULL) — 폭
|
||||
├── height_m (FLOAT, NULL) — 높이
|
||||
├── material (VARCHAR(100), NULL) — 재료: 강재, 콘크리트, 목재, 돌 등
|
||||
├── quantity (INT, NULL) — 개수
|
||||
├── unit_price (FLOAT, NULL) — 단가
|
||||
├── design_notes (LONGTEXT, NULL) — 설계 노트 (JSON)
|
||||
├── structure_data_path (VARCHAR(500), NULL) — 구조물 데이터 저장 경로
|
||||
├── last_modified_by (INT, FK → users.id, NULL) — 마지막 수정자
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 7-2. quantity_items 테이블 (수량 항목)
|
||||
```
|
||||
quantity_items
|
||||
├── id (INT, PK) — 수량 항목 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── category (VARCHAR(100), NULL) — 대분류: 토공, 구조물, 포장, 배수, 녹화, 안전시설
|
||||
├── item_name (VARCHAR(255), NULL) — 항목명 (예: "절토 일반", "낙석방지책 설치")
|
||||
├── unit (VARCHAR(50), NULL) — 단위: m3, 개, m, m2
|
||||
├── quantity_design (FLOAT, NULL) — 설계 수량
|
||||
├── quantity_actual (FLOAT, NULL) — 실제 수량 (사용자 수정 가능)
|
||||
├── unit_price (FLOAT, NULL) — 단가
|
||||
├── total_price (FLOAT, NULL) — 소계 (quantity_actual × unit_price)
|
||||
├── standard_reference (VARCHAR(255), NULL) — 기준 참고 자료
|
||||
├── quantity_data_path (VARCHAR(500), NULL) — 수량 데이터 저장 경로
|
||||
├── data (LONGTEXT, NULL) — 계산 과정 메모 (JSON)
|
||||
│ ├── formula (계산식)
|
||||
│ ├── source (데이터 출처)
|
||||
│ └── ...
|
||||
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 산출물 & 문서 그룹
|
||||
|
||||
### 8-1. outputs 테이블 (최종 산출물 세트)
|
||||
```
|
||||
outputs
|
||||
├── id (INT, PK) — 산출물 세트 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── output_type (VARCHAR(100), NULL) — 종류: estimation_excel, drawing_dxf, report_pdf, all_bundle
|
||||
├── status (VARCHAR(50), NULL) — 상태: GENERATING, COMPLETE, FAILED (기본값: GENERATING)
|
||||
├── generated_by (INT, FK → users.id, NULL) — 생성자
|
||||
├── generated_at (TIMESTAMP) — 생성 날짜
|
||||
├── version (INT, NULL) — 버전 (기본값: 1)
|
||||
├── outputs_directory_path (VARCHAR(500), NULL) — 산출물 저장 폴더 (예: "storage/.../outputs/v1/")
|
||||
├── metadata (LONGTEXT, NULL) — 메타데이터 (JSON)
|
||||
│ ├── template_used (사용한 양식)
|
||||
│ ├── company_name (회사명)
|
||||
│ ├── project_name (프로젝트명)
|
||||
│ ├── total_cost (총 비용)
|
||||
│ └── generation_time_sec (생성 소요 시간)
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
### 8-2. output_files 테이블 (최종 산출 파일)
|
||||
```
|
||||
output_files
|
||||
├── id (INT, PK) — 파일 고유 번호
|
||||
├── output_id (INT, FK → outputs.id) — 산출물 ID
|
||||
├── file_type (VARCHAR(50), NULL) — 파일 형식: xlsx, pdf, dxf, dwg, json, zip
|
||||
├── original_filename (VARCHAR(255), NULL) — 파일명 (예: "견적서_v1.xlsx")
|
||||
├── output_file_path (VARCHAR(500), NULL) — 저장 경로 (예: "storage/.../outputs/v1/견적서.xlsx")
|
||||
├── file_size_mb (FLOAT, NULL) — 파일 크기
|
||||
├── download_count (INT, NULL) — 다운로드 횟수 (기본값: 0)
|
||||
├── created_at (TIMESTAMP) — 생성 날짜
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 변경 이력 & 감시 그룹
|
||||
|
||||
### 9-1. change_logs 테이블 (설계 변경 기록)
|
||||
```
|
||||
change_logs
|
||||
├── id (INT, PK) — 변경 고유 번호
|
||||
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
|
||||
├── changed_by (INT, FK → users.id) — 변경자
|
||||
├── changed_at (TIMESTAMP) — 변경 날짜
|
||||
├── entity_type (VARCHAR(100)) — 대상 타입: routes, cross_sections, structures, quantity_items
|
||||
├── entity_id (VARCHAR(50)) — 대상 ID
|
||||
├── old_value (LONGTEXT, NULL) — 변경 전 값 (JSON)
|
||||
├── new_value (LONGTEXT, NULL) — 변경 후 값 (JSON)
|
||||
├── reason (TEXT, NULL) — 변경 사유
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 테이블 관계도
|
||||
|
||||
```
|
||||
users (사용자)
|
||||
├── id (PK)
|
||||
├── email
|
||||
├── password_hash
|
||||
├── role (SYSTEM_ADMIN | ADMIN | USER)
|
||||
├── is_master
|
||||
├── status (생명주기)
|
||||
├── company_id (FK) ──→ companies.id
|
||||
├── last_login
|
||||
└── auth_expires_at
|
||||
|
||||
companies (회사)
|
||||
├── id (PK)
|
||||
├── name
|
||||
├── status (ACTIVE | INACTIVE | SUSPENDED)
|
||||
├── subscription_status (FREE | TRIAL | PAID | EXPIRED)
|
||||
├── created_by (FK) ──→ users.id
|
||||
└── created_at
|
||||
|
||||
projects (프로젝트)
|
||||
├── id (UUID, PK)
|
||||
├── user_id (FK) ──→ users.id
|
||||
├── company_id (FK) ──→ companies.id
|
||||
├── name
|
||||
├── status
|
||||
├── crs_epsg
|
||||
├── storage_path
|
||||
└── created_at
|
||||
↓
|
||||
├─→ input_files (입력 파일)
|
||||
│ └─→ processed_point_cloud (변환된 포인트클라우드)
|
||||
│ └─→ surface_models (지표면 모델)
|
||||
│ └─→ terrain_layers (지형 레이어)
|
||||
├─→ routes (경로)
|
||||
│ ├─→ route_points (경로 포인트)
|
||||
│ ├─→ route_statistics (경로 통계)
|
||||
│ ├─→ longitudinal_sections (종단면)
|
||||
│ └─→ cross_sections (횡단면)
|
||||
│ └─→ structures (구조물)
|
||||
├─→ quantity_items (수량 항목)
|
||||
├─→ outputs (산출물)
|
||||
│ └─→ output_files (산출물 파일)
|
||||
├─→ project_versions (프로젝트 버전)
|
||||
├─→ project_automations (프로젝트 자동화)
|
||||
├─→ audit_logs (감시 로그)
|
||||
└─→ change_logs (변경 로그)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 파일시스템 경로와 DB 링크 (워크플로우 기반 구조)
|
||||
|
||||
### 파일시스템 구조 (6단계 워크플로우)
|
||||
|
||||
```
|
||||
storage/
|
||||
├── {company_slug}/
|
||||
│ └── {user_slug}/
|
||||
│ └── {project_id}/ ← projects.storage_path
|
||||
│ │
|
||||
│ ├── B03_FileInput/ ← WF0: 파일 입력
|
||||
│ │ └── input/ (원본 입력 파일들)
|
||||
│ │ ├── las/
|
||||
│ │ │ └── cloud_merged.las ← input_files.raw_file_path
|
||||
│ │ ├── tif/
|
||||
│ │ ├── tfw/
|
||||
│ │ ├── prj/
|
||||
│ │ └── dxf/
|
||||
│ │
|
||||
│ ├── B04_wf1_Surface/ ← WF1: 지표면 분석
|
||||
│ │ ├── processed/ (변환된 포인트클라우드)
|
||||
│ │ │ ├── cloud_filtered.las ← processed_point_cloud.processed_file_path
|
||||
│ │ │ └── cloud_sampled.ply ← processed_point_cloud.converted_file_path
|
||||
│ │ └── models/ (지표면 모델 & 레이어)
|
||||
│ │ ├── dem_2m.tif ← surface_models.model_file_path
|
||||
│ │ ├── tin_mesh.obj
|
||||
│ │ ├── layer_ground.geojson ← terrain_layers.layer_file_path
|
||||
│ │ └── layer_1.geojson
|
||||
│ │
|
||||
│ ├── B05_wf2_Route/ ← WF2: 경로 설계
|
||||
│ │ └── route/
|
||||
│ │ ├── route_main.geojson ← routes.route_data_path
|
||||
│ │ ├── route_points.json
|
||||
│ │ └── route_statistics.json
|
||||
│ │
|
||||
│ ├── B06_wf3_ProfileCross/ ← WF3: 종횡단 생성
|
||||
│ │ ├── longitudinal/
|
||||
│ │ │ └── longitudinal.json ← longitudinal_sections.longitudinal_file_path
|
||||
│ │ └── cross_sections/
|
||||
│ │ ├── cross_0000m.json ← cross_sections.cross_section_file_path
|
||||
│ │ ├── cross_0020m.json
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── B07_wf4_DesignDetail/ ← WF4: 상세 설계
|
||||
│ │ └── structures/
|
||||
│ │ ├── struct_0020m_001.json ← structures.structure_data_path
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── B08_wf5_Quantity/ ← WF5: 수량 산출
|
||||
│ │ ├── quantities/
|
||||
│ │ │ └── items.json ← quantity_items.quantity_data_path
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ └── B09_wf6_Estimation/ ← WF6: 견적·문서
|
||||
│ ├── v1/ ← outputs.outputs_directory_path
|
||||
│ │ ├── 견적서.xlsx ← output_files.output_file_path
|
||||
│ │ ├── 설계도.dxf
|
||||
│ │ └── ...
|
||||
│ └── v2/
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
**핵심 개념:**
|
||||
- **B03~B09**: 워크플로우 6단계와 1:1 대응
|
||||
- **각 단계의 입력/출력**: 명확히 분리
|
||||
- **버전 관리**: B09 단계에서만 (v1, v2, ...)
|
||||
|
||||
---
|
||||
|
||||
## 12. 핵심 쿼리 예제
|
||||
|
||||
### 예제 1: 사용자의 모든 프로젝트 조회
|
||||
```sql
|
||||
SELECT p.id, p.name, p.status, p.created_at
|
||||
FROM projects p
|
||||
WHERE p.user_id = :user_id AND p.deleted_at IS NULL
|
||||
ORDER BY p.created_at DESC;
|
||||
```
|
||||
|
||||
### 예제 2: 프로젝트의 최신 경로 조회
|
||||
```sql
|
||||
SELECT r.id, r.total_length_m, r.status
|
||||
FROM routes r
|
||||
WHERE r.project_id = :project_id AND r.deleted_at IS NULL
|
||||
ORDER BY r.computed_at DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
### 예제 3: 경로의 모든 횡단면 조회
|
||||
```sql
|
||||
SELECT cs.id, cs.chainage_m, cs.data
|
||||
FROM cross_sections cs
|
||||
WHERE cs.route_id = :route_id AND cs.deleted_at IS NULL
|
||||
ORDER BY cs.chainage_m;
|
||||
```
|
||||
|
||||
### 예제 4: 프로젝트 총 비용 계산
|
||||
```sql
|
||||
SELECT
|
||||
SUM(quantity_actual * unit_price) as total_cost,
|
||||
category
|
||||
FROM quantity_items
|
||||
WHERE project_id = :project_id AND deleted_at IS NULL
|
||||
GROUP BY category;
|
||||
```
|
||||
|
||||
### 예제 5: 사용자의 감시 로그 (최근 30일)
|
||||
```sql
|
||||
SELECT action, entity_type, timestamp
|
||||
FROM audit_logs
|
||||
WHERE user_id = :user_id AND timestamp > DATE_SUB(NOW(), INTERVAL 30 DAY)
|
||||
ORDER BY timestamp DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 데이터 타입 가이드
|
||||
|
||||
| 타입 | 설명 | 예제 |
|
||||
|------|------|------|
|
||||
| INT | 정수 | 1, 2, 100 |
|
||||
| FLOAT | 실수 | 1.5, 2.25, 100.75 |
|
||||
| VARCHAR(n) | 문자열 (고정 길이) | "project_name" |
|
||||
| TEXT / LONGTEXT | 문자열 (가변, 대용량) | 설명문, JSON 데이터 |
|
||||
| TIMESTAMP | 날짜 + 시간 (자동 업데이트) | 2025-07-05 15:30:00 |
|
||||
| DATETIME | 날짜 + 시간 (수동) | 2025-07-05 15:30:00 |
|
||||
| TINYINT(1) | 참/거짓 (0 또는 1) | 0, 1 |
|
||||
| ENUM | 열거형 | ACTIVE, INACTIVE |
|
||||
| CHAR(36) | UUID (고정 36자) | 550e8400-e29b-41d4-a716-446655440000 |
|
||||
|
||||
---
|
||||
|
||||
## 14. 총 테이블 수 및 분류
|
||||
|
||||
### 34개 테이블 분류
|
||||
|
||||
**1. 사용자/회사/조직 (14개)**
|
||||
- users, companies, join_requests (향후), email_otps, sessions, trusted_devices, user_consents
|
||||
- login_logs, activity_logs, audit_logs, system_audit_logs, system_resources, support_requests
|
||||
|
||||
**2. 프로젝트 관리 (3개)**
|
||||
- projects, project_versions, project_automations
|
||||
|
||||
**3. 파일/업로드 (3개)**
|
||||
- upload_sessions, upload_chunks, input_files
|
||||
|
||||
**4. 지표면 분석 (3개)**
|
||||
- processed_point_cloud, surface_models, terrain_layers
|
||||
|
||||
**5. 경로 설계 (3개)**
|
||||
- routes, route_points, route_statistics
|
||||
|
||||
**6. 종횡단 설계 (2개)**
|
||||
- longitudinal_sections, cross_sections
|
||||
|
||||
**7. 구조물/수량 (2개)**
|
||||
- structures, quantity_items
|
||||
|
||||
**8. 산출물 (2개)**
|
||||
- outputs, output_files
|
||||
|
||||
**9. 변경/이력 (1개)**
|
||||
- change_logs
|
||||
|
||||
---
|
||||
|
||||
## 15. DB vs 파일시스템 역할 분리
|
||||
|
||||
| 저장 위치 | 내용 | 예시 |
|
||||
|----------|------|------|
|
||||
| **DB (MariaDB)** | 메타데이터 + 경로정보 + 상태 | 파일명, 크기, 좌표계, 상태, 저장경로, 통계 |
|
||||
| **파일시스템** | 실제 파일 데이터 | LAS, PLY, TIF, DXF, Excel, PDF, JSON 파일들 |
|
||||
|
||||
**설계 원칙:**
|
||||
- DB에는 "어디에 무엇이 있는가"의 메타정보만 저장
|
||||
- 실제 대용량 파일은 파일시스템에 저장
|
||||
- 각 워크플로우 단계의 입력/출력 경로를 DB에 기록
|
||||
- JSON 형식으로 좌표 및 복잡한 데이터 구조를 저장
|
||||
|
||||
---
|
||||
|
||||
## 16. 마이그레이션 상태
|
||||
|
||||
### ✅ 완료됨
|
||||
- [x] 33개 테이블 생성 (MariaDB)
|
||||
- [x] 관계(FK) 설정
|
||||
- [x] Soft delete (deleted_at) 지원
|
||||
- [x] 인덱스 추가 (주요 조회 경로)
|
||||
|
||||
### 📋 진행 중
|
||||
- [ ] API 라우터에서 파일 경로 저장 로직 추가
|
||||
- B03 파일 업로드 → `input_files.raw_file_path` 저장
|
||||
- B04 WF1 실행 → `surface_models.model_file_path`, `terrain_layers.layer_file_path` 저장
|
||||
- B05 WF2 실행 → `routes.route_data_path` 저장
|
||||
- B06 WF3 실행 → `longitudinal_sections.longitudinal_file_path`, `cross_sections.cross_section_file_path` 저장
|
||||
- B07 WF4 실행 → `structures.structure_data_path` 저장
|
||||
- B08 WF5 실행 → `quantity_items.quantity_data_path` 저장
|
||||
- B09 WF6 실행 → `outputs.outputs_directory_path`, `output_files.output_file_path` 저장
|
||||
|
||||
### 🔮 향후 계획
|
||||
- [ ] Python Pydantic 모델 정의 (ORM 대신)
|
||||
- [ ] API 엔드포인트 보안 감시 강화
|
||||
- [ ] 실시간 알림 시스템 (WebSocket)
|
||||
@@ -0,0 +1,445 @@
|
||||
# Wiza — Style Reference
|
||||
> Twilight prospecting observatory bathed in lavender mist
|
||||
|
||||
**Theme:** light
|
||||
|
||||
Wiza operates in a light-mode universe where deep violet ink commands attention against a soft lavender atmosphere. The brand lives between a confident enterprise tool and an approachable consumer product—white surfaces, thin borders, and compact Inter type for dense data interfaces, paired with Britti Sans display headlines that feel geometric and assured. The signature move is the gradient wash: pale lavender backgrounds bleeding white-to-violet-to-peach, making even a sprawling data table feel sunlit. Purple is reserved for authority—headlines, logo, filled actions—while the rest of the UI stays in quiet neutrals, letting contact data breathe.
|
||||
|
||||
## Tokens — Colors
|
||||
|
||||
| Name | Value | Token | Role |
|
||||
|------|-------|-------|------|
|
||||
| Deep Iris | `#26114a` | `--color-deep-iris` | Violet supporting accent for decorative details and low-frequency emphasis. Do not promote it to the primary CTA color |
|
||||
| Plum Velvet | `#312749` | `--color-plum-velvet` | Navigation text, secondary headings, card titles, footer copy — slightly lighter companion to Deep Iris |
|
||||
| Royal Amethyst | `#3e0079` | `--color-royal-amethyst` | Links, input focus rings, accent strokes, icon highlights — the most saturated violet |
|
||||
| Mist Violet | `#edecff` | `--color-mist-violet` | Soft highlight washes, tinted section backgrounds, decorative blurs — the lavender atmosphere |
|
||||
| Lavender Wash | `linear-gradient(90deg, rgb(185, 154, 255), rgb(125, 67, 255) 10%, rgba(125, 67, 255, 0))` | `--color-lavender-wash` | Decorative gradient stops and hero atmosphere |
|
||||
| Twilight Beam | `linear-gradient(to right, rgb(207, 138, 255), rgb(255, 102, 193), rgb(255, 173, 116), rgb(170, 129, 255))` | `--color-twilight-beam` | Multi-stop decorative gradient for hero glow effects |
|
||||
| Canvas | `#ffffff` | `--color-canvas` | Page background, card surfaces, input fields, button text on dark fills |
|
||||
| Paper | `#f6f7fa` | `--color-paper` | Subtle surface lift, table row alternation, secondary card backgrounds |
|
||||
| Mist | `#e6e2e3` | `--color-mist` | Hairline borders, dividers, subtle separators |
|
||||
| Smoke | `#c1c7cf` | `--color-smoke` | Disabled states, skeleton placeholders, muted background blocks |
|
||||
| Ash | `#9491a1` | `--color-ash` | Muted helper text, placeholder copy, secondary metadata |
|
||||
| Slate | `#615e6e` | `--color-slate` | Secondary body text, descriptions, table metadata |
|
||||
| Charcoal | `#333333` | `--color-charcoal` | Body text, link text, icon strokes in neutral contexts |
|
||||
| Carbon | `#222222` | `--color-carbon` | Dark UI elements, near-black accents |
|
||||
| Ink | `#000000` | `--color-ink` | Maximum contrast text and borders in specific dark UI elements |
|
||||
|
||||
## Tokens — Typography
|
||||
|
||||
### Britti Sans — Display and heading typeface used exclusively at 24–64px, always weight 500. The 1.0 line-height is distinctive — headings sit tight with no leading slack, creating geometric, almost architectural presence. Substitute with Plus Jakarta Sans (500) or General Sans (500) for closest geometric match. · `--font-britti-sans`
|
||||
- **Substitute:** Plus Jakarta Sans
|
||||
- **Weights:** 500
|
||||
- **Sizes:** 24px, 32px, 40px, 56px, 64px
|
||||
- **Line height:** 1.00
|
||||
- **Role:** Display and heading typeface used exclusively at 24–64px, always weight 500. The 1.0 line-height is distinctive — headings sit tight with no leading slack, creating geometric, almost architectural presence. Substitute with Plus Jakarta Sans (500) or General Sans (500) for closest geometric match.
|
||||
|
||||
### Inter — Workhorse UI and body typeface. Weight 400 for body, 500 for buttons/nav/table headers, 700 for emphasized labels. The tight size range (12–16px) and compact line-heights signal a data-dense product, not a marketing site. Substitute with Inter directly or IBM Plex Sans. · `--font-inter`
|
||||
- **Substitute:** Inter
|
||||
- **Weights:** 400, 500, 700
|
||||
- **Sizes:** 12px, 14px, 16px
|
||||
- **Line height:** 1.30–1.50
|
||||
- **Role:** Workhorse UI and body typeface. Weight 400 for body, 500 for buttons/nav/table headers, 700 for emphasized labels. The tight size range (12–16px) and compact line-heights signal a data-dense product, not a marketing site. Substitute with Inter directly or IBM Plex Sans.
|
||||
|
||||
### Type Scale
|
||||
|
||||
| Role | Size | Line Height | Letter Spacing | Token |
|
||||
|------|------|-------------|----------------|-------|
|
||||
| caption | 12px | 1.3 | — | `--text-caption` |
|
||||
| body-sm | 14px | 1.4 | — | `--text-body-sm` |
|
||||
| body | 16px | 1.5 | — | `--text-body` |
|
||||
| subheading | 24px | 1 | — | `--text-subheading` |
|
||||
| heading-sm | 32px | 1 | — | `--text-heading-sm` |
|
||||
| heading | 40px | 1 | — | `--text-heading` |
|
||||
| heading-lg | 56px | 1 | — | `--text-heading-lg` |
|
||||
| display | 64px | 1 | — | `--text-display` |
|
||||
|
||||
## Tokens — Spacing & Shapes
|
||||
|
||||
**Base unit:** 8px
|
||||
|
||||
**Density:** compact
|
||||
|
||||
### Spacing Scale
|
||||
|
||||
| Name | Value | Token |
|
||||
|------|-------|-------|
|
||||
| 8 | 8px | `--spacing-8` |
|
||||
| 16 | 16px | `--spacing-16` |
|
||||
| 24 | 24px | `--spacing-24` |
|
||||
| 32 | 32px | `--spacing-32` |
|
||||
| 40 | 40px | `--spacing-40` |
|
||||
| 48 | 48px | `--spacing-48` |
|
||||
| 56 | 56px | `--spacing-56` |
|
||||
| 64 | 64px | `--spacing-64` |
|
||||
| 96 | 96px | `--spacing-96` |
|
||||
| 104 | 104px | `--spacing-104` |
|
||||
| 216 | 216px | `--spacing-216` |
|
||||
|
||||
### Border Radius
|
||||
|
||||
| Element | Value |
|
||||
|---------|-------|
|
||||
| cards | 8px |
|
||||
| icons | 8px |
|
||||
| large | 24px |
|
||||
| pills | 1440px |
|
||||
| inputs | 8px |
|
||||
| buttons | 8px |
|
||||
|
||||
### Shadows
|
||||
|
||||
| Name | Value | Token |
|
||||
|------|-------|-------|
|
||||
| sm | `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105...` | `--shadow-sm` |
|
||||
| lg | `rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59,...` | `--shadow-lg` |
|
||||
| lg-2 | `rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47...` | `--shadow-lg-2` |
|
||||
| sm-2 | `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105...` | `--shadow-sm-2` |
|
||||
| sm-3 | `rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151,...` | `--shadow-sm-3` |
|
||||
|
||||
### Layout
|
||||
|
||||
- **Page max-width:** 1200px
|
||||
- **Section gap:** 64-80px
|
||||
- **Card padding:** 16-24px
|
||||
- **Element gap:** 8px
|
||||
|
||||
## Components
|
||||
|
||||
### Filled Brand Button
|
||||
**Role:** Primary action trigger in hero and CTA blocks
|
||||
|
||||
Background #26114a, text #ffffff, 8px radius, 16px vertical / 24px horizontal padding, Inter 500 at 16px. Carries the brand weight — only used for the most important conversion per screen.
|
||||
|
||||
### Ghost Outlined Button
|
||||
**Role:** Secondary action paired beside a filled button
|
||||
|
||||
Background transparent, border 1px #26114a, text #26114a, 8px radius, 16px/24px padding, Inter 500 at 16px. Matches the filled button's exact dimensions for optical pairing.
|
||||
|
||||
### Pill Navigation Button
|
||||
**Role:** Top-level nav items in the floating header
|
||||
|
||||
1440px radius, 8px/16px padding, Inter 500 at 14px, text #312749. Active state may use #edecff background. The full-pill shape is distinctive — not rounded rectangles, but true capsules.
|
||||
|
||||
### Logo Lockup
|
||||
**Role:** Brand identity in header and footer
|
||||
|
||||
Purple triangular icon mark paired with 'wiza' wordmark in Britti Sans 500 or Inter 700. Icon uses a gradient from #b99aff to #7d43ff. Appears in a white pill container with 8px radius and subtle shadow.
|
||||
|
||||
### Product Screenshot Frame
|
||||
**Role:** Hero product demonstration and feature illustrations
|
||||
|
||||
White surface with 8px radius, surrounded by the deep layered shadow stack (rgba(14,59,101,0.06) large blur). Floats over the lavender gradient background, creating depth without borders.
|
||||
|
||||
### Data Table Row
|
||||
**Role:** Contact/prospect list display in product mockups
|
||||
|
||||
Alternating #ffffff and #f6f7fa backgrounds, 1px #e6e2e3 bottom border, 12px/16px cell padding, Inter 400 at 14px for data, Inter 500 for headers. Dense, compact rows with colored status dots.
|
||||
|
||||
### Logo Cloud Bar
|
||||
**Role:** Social proof — client brand logos
|
||||
|
||||
Monochrome (Charcoal #333333) logo strip on white background, centered, with consistent 40–60px height per logo. No colorful logos — all desaturated for visual calm.
|
||||
|
||||
### Rating Badge Pair
|
||||
**Role:** Third-party validation scores (G2, Chrome Web Store)
|
||||
|
||||
White pill cards (1440px radius), Inter 500 at 14px for score, 12px for platform name. Star icon in #3e0079 or #f5b800. Displayed in a centered pair with 24px gap.
|
||||
|
||||
### Section Heading Block
|
||||
**Role:** Intro text for content sections
|
||||
|
||||
Optional pill tag above (Mist Violet #edecff background, Royal Amethyst #3e0079 text, 1440px radius, Inter 500 12px, 4px/12px padding). Main heading in Britti Sans 500 at 40–56px, Deep Iris #26114a. Subtext in Inter 400 at 16–18px, Slate #615e6e.
|
||||
|
||||
### Profile Card (Magic Wand Feature)
|
||||
**Role:** Contact preview cards in feature illustrations
|
||||
|
||||
White surface, 8px radius, subtle border, 12px/16px padding. Contains circular avatar (32px), name in Inter 500 14px #26114a, title in Inter 400 12px #615e6, company in Inter 400 12px #9491a1. Arranged in 3-column grid with 16px gaps.
|
||||
|
||||
### Floating Navigation Bar
|
||||
**Role:** Sticky header container
|
||||
|
||||
White background with 8px radius, floats over hero with the layered shadow. Contains logo, nav links (Pill Navigation Button style), and login/signup actions. Max-width contained with side padding.
|
||||
|
||||
### Tag/Badge (Pill)
|
||||
**Role:** Feature labels, job titles, status indicators
|
||||
|
||||
1440px radius, 4px/12px padding, Inter 500 at 12px. Variants: #edecff bg with #3e0079 text (accent tags), #f6f7fa bg with #615e6 text (neutral tags), or status-specific colors.
|
||||
|
||||
### Input Field
|
||||
**Role:** Search and form inputs in product UI
|
||||
|
||||
1px #e6e2e3 border, 8px radius, 10px/14px padding, Inter 400 at 14px, placeholder in Ash #9491a1. Focus state uses 2px Royal Amethyst #3e0079 border ring.
|
||||
|
||||
### Feature Icon Tile
|
||||
**Role:** Small decorative icon containers in section intros
|
||||
|
||||
16px or 24px square tiles with 8px radius, Mist Violet #edecff background, purple stroke icon inside. Used sparingly to mark section types.
|
||||
|
||||
## Do's and Don'ts
|
||||
|
||||
### Do
|
||||
- Use 8px radius for all rectangular UI surfaces — cards, buttons, inputs, icon containers. The 8px is the signature, not 4px or 12px.
|
||||
- Use 1440px radius exclusively for pills, tags, and capsule-shaped nav items — never for cards or panels.
|
||||
- Set Britti Sans headings to line-height 1.0 exactly. The tight leading is what makes the type feel architectural.
|
||||
- Use Deep Iris #26114a for headlines and filled button backgrounds. Never use it for body text larger than 24px — it overwhelms at small sizes.
|
||||
- Pair every filled button with an adjacent ghost button of identical dimensions for the hero CTA pattern.
|
||||
- Anchor section intros with a small pill tag above the heading in Mist Violet + Royal Amethyst.
|
||||
- Use the Lavender Wash gradient on hero and feature section backgrounds — it defines the atmosphere.
|
||||
- Keep Inter at 12–16px for all UI text. The compact size range signals a data-dense product.
|
||||
|
||||
### Don't
|
||||
- Do not use Royal Amethyst #3e0079 for body text — it vibrates uncomfortably at small sizes; reserve it for links, icons, and accent strokes.
|
||||
- Do not apply 4px or 12px radius to cards — the 8px is non-negotiable for visual consistency.
|
||||
- Do not use line-height above 1.0 for Britti Sans headings — the tightness is the point.
|
||||
- Do not introduce new chromatic colors outside the violet family — the palette is deliberately monochromatic-violet.
|
||||
- Do not use heavy drop shadows. The blue-tinted shadows are subtle (max 32px blur at 6% opacity).
|
||||
- Do not center body text in multi-line paragraphs — only headlines and short intros get center alignment.
|
||||
- Do not place dark text directly on the lavender gradient — use a white surface card between them for legibility.
|
||||
- Do not use rounded images or photos in feature illustrations — the design is iconographic, not photographic.
|
||||
|
||||
## Surfaces
|
||||
|
||||
| Level | Name | Value | Purpose |
|
||||
|-------|------|-------|---------|
|
||||
| 0 | Canvas | `#ffffff` | Page background, outermost layer |
|
||||
| 1 | Paper | `#f6f7fa` | Card surfaces, table containers, subtle lifts |
|
||||
| 2 | Mist Violet | `#edecff` | Tinted highlight sections, gradient start point |
|
||||
| 3 | Lavender Glow | `#b99aff` | Hero gradient stops, atmospheric overlays |
|
||||
|
||||
## Elevation
|
||||
|
||||
- **Cards and elevated panels:** `rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px`
|
||||
- **Buttons and nav items:** `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px`
|
||||
- **Product feature highlights:** `rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px`
|
||||
|
||||
## Imagery
|
||||
|
||||
The visual language is almost entirely product-screenshot and iconographic — no lifestyle photography, no stock imagery, no people in context. Product mockups show a contact-prospecting data table with purple status indicators, person avatars, and company logos. Decorative visuals use soft purple gradient washes with floating profile cards arranged in grid patterns. Icons are simple, single-stroke, geometric shapes (a triangle logo, outlined feature icons). Logos in the trust bar are desaturated to monochrome. The overall feel is that of a polished SaaS dashboard screenshot rather than a marketing site with imagery.
|
||||
|
||||
## Layout
|
||||
|
||||
Centered, max-width 1200px container with generous vertical breathing room (64–80px section gaps). The hero is a full-bleed lavender-to-white gradient with a centered headline stack and dual CTA buttons, followed by a floating product screenshot that breaks the container edge slightly. Below the fold, content alternates between white surface sections and light lavender tinted sections. The logo cloud is a single centered row. Feature sections use centered heading blocks followed by 2-column or 3-column card grids. A sticky floating navigation bar (pill-shaped, centered) sits over the hero.
|
||||
|
||||
## Agent Prompt Guide
|
||||
|
||||
primary action: no distinct CTA color
|
||||
**Quick Color Reference**
|
||||
- text (primary): #26114a
|
||||
- text (secondary): #615e6e
|
||||
- text (muted): #9491a1
|
||||
- background: #ffffff
|
||||
- surface: #f6f7fa
|
||||
- border: #e6e2e3
|
||||
- brand/accent: #3e0079
|
||||
- filled button background: #26114a
|
||||
|
||||
**Example Component Prompts**
|
||||
|
||||
1. **Hero Section**: White-to-lavender gradient background (linear-gradient from #ffffff to #edecff). Centered headline 'Find verified emails and phone numbers' in Britti Sans weight 500 at 56px, color #26114a, line-height 1.0. Subtext in Inter 400 at 18px, color #615e6e. Two buttons side by side: filled button (background #26114a, text #ffffff, 8px radius, 16px/24px padding, Inter 500 16px) and ghost button (border 1px #26114a, text #26114a, same dimensions). Below: a product screenshot framed in a white card with 8px radius and the deep blue-tinted shadow.
|
||||
|
||||
2. **Data Table Card**: White surface card, 8px radius, 1px #e6e2e3 border, 16px/24px padding. Table header row in Inter 500 at 12px, color #615e6e, uppercase, with 1px #e6e2e3 bottom border. Data rows alternate #ffffff and #f6f7fa backgrounds, Inter 400 at 14px, color #26114a. Action buttons in each row: small pill tags with 1440px radius, #edecff background, #3e0079 text, Inter 500 at 12px.
|
||||
|
||||
3. **Section Heading with Pill Tag**: Optional pill tag above heading: 1440px radius, #edecff background, #3e0079 text, Inter 500 at 12px, 4px/12px padding. Main heading in Britti Sans 500 at 40px, #26114a, line-height 1.0. Subtext in Inter 400 at 16px, #615e6e. All centered.
|
||||
|
||||
4. **Profile Preview Card**: White surface, 8px radius, 1px #e6e2e3 border, 12px/16px padding. 32px circular avatar at top-left. Name in Inter 500 at 14px, #26114a. Title in Inter 400 at 12px, #615e6e. Company in Inter 400 at 12px, #9491a1. Arranged in 3-column grid with 16px gaps, centered in section.
|
||||
|
||||
5. **Trust Logo Bar**: Single horizontal row of 6 client logos, all rendered in monochrome #333333, centered, each logo 40–50px height. White background, 64px vertical padding above and below. Below: two rating badge pills (1440px radius, white bg, subtle border) with star icon and score in Inter 500 14px, platform name in Inter 400 12px #615e6e.
|
||||
|
||||
## Gradient System
|
||||
|
||||
The lavender atmosphere is built from three gradient layers:
|
||||
- **Hero wash**: linear-gradient(90deg, #b99aff, #7d43ff 10%, transparent) — fades right, creating a directional glow
|
||||
- **Decorative multi-stop**: linear-gradient(to right, #cf8aff, #ff66c1, #ffad74, #ff63d1, #aa81ff) — used sparingly for special highlights
|
||||
- **Section tint**: subtle white-to-#edecff vertical fade for alternating content sections
|
||||
Gradients should always sit behind white content surfaces — never behind text directly.
|
||||
|
||||
## Similar Brands
|
||||
|
||||
- **Apollo.io** — Same deep-violet brand color, similar data-prospecting product, and comparable lavender gradient hero treatment
|
||||
- **Lusha** — Shared violet/indigo palette, compact data table UI, and light-mode product-first layout
|
||||
- **Clearbit** — Similar light-mode SaaS with subtle gradient backgrounds and confident sans-serif display type
|
||||
- **ZoomInfo** — Enterprise data product with dense tabular UI, compact Inter type, and restrained brand color usage
|
||||
- **Cognism** — Purple-tinted brand identity with soft gradient hero sections and data-table product showcases
|
||||
|
||||
## Quick Start
|
||||
|
||||
### CSS Custom Properties
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Colors */
|
||||
--color-deep-iris: #26114a;
|
||||
--color-plum-velvet: #312749;
|
||||
--color-royal-amethyst: #3e0079;
|
||||
--color-mist-violet: #edecff;
|
||||
--color-lavender-wash: #b99aff;
|
||||
--gradient-lavender-wash: linear-gradient(90deg, rgb(185, 154, 255), rgb(125, 67, 255) 10%, rgba(125, 67, 255, 0));
|
||||
--color-twilight-beam: #cf8aff;
|
||||
--gradient-twilight-beam: linear-gradient(to right, rgb(207, 138, 255), rgb(255, 102, 193), rgb(255, 173, 116), rgb(170, 129, 255));
|
||||
--color-canvas: #ffffff;
|
||||
--color-paper: #f6f7fa;
|
||||
--color-mist: #e6e2e3;
|
||||
--color-smoke: #c1c7cf;
|
||||
--color-ash: #9491a1;
|
||||
--color-slate: #615e6e;
|
||||
--color-charcoal: #333333;
|
||||
--color-carbon: #222222;
|
||||
--color-ink: #000000;
|
||||
|
||||
/* Typography — Font Families */
|
||||
--font-britti-sans: 'Britti Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-inter: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
|
||||
/* Typography — Scale */
|
||||
--text-caption: 12px;
|
||||
--leading-caption: 1.3;
|
||||
--text-body-sm: 14px;
|
||||
--leading-body-sm: 1.4;
|
||||
--text-body: 16px;
|
||||
--leading-body: 1.5;
|
||||
--text-subheading: 24px;
|
||||
--leading-subheading: 1;
|
||||
--text-heading-sm: 32px;
|
||||
--leading-heading-sm: 1;
|
||||
--text-heading: 40px;
|
||||
--leading-heading: 1;
|
||||
--text-heading-lg: 56px;
|
||||
--leading-heading-lg: 1;
|
||||
--text-display: 64px;
|
||||
--leading-display: 1;
|
||||
|
||||
/* Typography — Weights */
|
||||
--font-weight-regular: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-unit: 8px;
|
||||
--spacing-8: 8px;
|
||||
--spacing-16: 16px;
|
||||
--spacing-24: 24px;
|
||||
--spacing-32: 32px;
|
||||
--spacing-40: 40px;
|
||||
--spacing-48: 48px;
|
||||
--spacing-56: 56px;
|
||||
--spacing-64: 64px;
|
||||
--spacing-96: 96px;
|
||||
--spacing-104: 104px;
|
||||
--spacing-216: 216px;
|
||||
|
||||
/* Layout */
|
||||
--page-max-width: 1200px;
|
||||
--section-gap: 64-80px;
|
||||
--card-padding: 16-24px;
|
||||
--element-gap: 8px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 2px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-2xl: 16px;
|
||||
--radius-3xl: 24px;
|
||||
--radius-3xl-2: 28px;
|
||||
--radius-3xl-3: 40px;
|
||||
--radius-full: 56px;
|
||||
--radius-full-2: 1440px;
|
||||
|
||||
/* Named Radii */
|
||||
--radius-cards: 8px;
|
||||
--radius-icons: 8px;
|
||||
--radius-large: 24px;
|
||||
--radius-pills: 1440px;
|
||||
--radius-inputs: 8px;
|
||||
--radius-buttons: 8px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px;
|
||||
--shadow-lg: rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px;
|
||||
--shadow-lg-2: rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
|
||||
--shadow-sm-2: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px;
|
||||
--shadow-sm-3: rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151, 0.04) 0px 6px 4px 0px, rgba(47, 1, 151, 0.06) 0px 3px 3px 0px, rgba(47, 1, 151, 0.08) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
|
||||
|
||||
/* Surfaces */
|
||||
--surface-canvas: #ffffff;
|
||||
--surface-paper: #f6f7fa;
|
||||
--surface-mist-violet: #edecff;
|
||||
--surface-lavender-glow: #b99aff;
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind v4
|
||||
|
||||
```css
|
||||
@theme {
|
||||
/* Colors */
|
||||
--color-deep-iris: #26114a;
|
||||
--color-plum-velvet: #312749;
|
||||
--color-royal-amethyst: #3e0079;
|
||||
--color-mist-violet: #edecff;
|
||||
--color-lavender-wash: #b99aff;
|
||||
--color-twilight-beam: #cf8aff;
|
||||
--color-canvas: #ffffff;
|
||||
--color-paper: #f6f7fa;
|
||||
--color-mist: #e6e2e3;
|
||||
--color-smoke: #c1c7cf;
|
||||
--color-ash: #9491a1;
|
||||
--color-slate: #615e6e;
|
||||
--color-charcoal: #333333;
|
||||
--color-carbon: #222222;
|
||||
--color-ink: #000000;
|
||||
|
||||
/* Typography */
|
||||
--font-britti-sans: 'Britti Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
--font-inter: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
|
||||
/* Typography — Scale */
|
||||
--text-caption: 12px;
|
||||
--leading-caption: 1.3;
|
||||
--text-body-sm: 14px;
|
||||
--leading-body-sm: 1.4;
|
||||
--text-body: 16px;
|
||||
--leading-body: 1.5;
|
||||
--text-subheading: 24px;
|
||||
--leading-subheading: 1;
|
||||
--text-heading-sm: 32px;
|
||||
--leading-heading-sm: 1;
|
||||
--text-heading: 40px;
|
||||
--leading-heading: 1;
|
||||
--text-heading-lg: 56px;
|
||||
--leading-heading-lg: 1;
|
||||
--text-display: 64px;
|
||||
--leading-display: 1;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-8: 8px;
|
||||
--spacing-16: 16px;
|
||||
--spacing-24: 24px;
|
||||
--spacing-32: 32px;
|
||||
--spacing-40: 40px;
|
||||
--spacing-48: 48px;
|
||||
--spacing-56: 56px;
|
||||
--spacing-64: 64px;
|
||||
--spacing-96: 96px;
|
||||
--spacing-104: 104px;
|
||||
--spacing-216: 216px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 2px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-2xl: 16px;
|
||||
--radius-3xl: 24px;
|
||||
--radius-3xl-2: 28px;
|
||||
--radius-3xl-3: 40px;
|
||||
--radius-full: 56px;
|
||||
--radius-full-2: 1440px;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px;
|
||||
--shadow-lg: rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px;
|
||||
--shadow-lg-2: rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
|
||||
--shadow-sm-2: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px;
|
||||
--shadow-sm-3: rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151, 0.04) 0px 6px 4px 0px, rgba(47, 1, 151, 0.06) 0px 3px 3px 0px, rgba(47, 1, 151, 0.08) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,120 @@
|
||||
# 인터페이스 및 UI 제어 명세서 (frontend.md)
|
||||
|
||||
## 1. 글로벌 테마 변수 강제 (Theme Constraints)
|
||||
* **임의 색상 지정 금지:** 하드코딩된 Hex 코드 직접 입력 금지.
|
||||
* **CSS 변수 필수 참조:** 테마 전환(라이트/다크) 및 일관성을 위해 `ui_template_theme.css` 선언 변수(`var(--color-bg)`, `var(--color-text)` 등)만 사용.
|
||||
* **신규 컴포넌트/템플릿:** 신규 컴포넌트나 템플릿이 필요한 경우`.agent\design.md` 파일을 참고하여 양식을 `ui_template\ui_template_elements.ts`, `ui_template\ui_template_locale.ts`, `ui_template\ui_template_theme.css` 양식화하여 사용할 것
|
||||
|
||||
## 2. UI 요소 및 레이아웃 제약 (Layout Constraints)
|
||||
* **공통 컴포넌트 최우선:** 임의 스타일링 금지. `ui_template_elements.ts` 규격 및 공통 컴포넌트 최우선 상속/참조.
|
||||
* **3단 기본 레이아웃 준수:** 워크플로우 페이지 기본 배치 강제 (예외 요청 제외)
|
||||
1. 상단 영역: 페이지 타이틀 및 현재 진행 단계 상태
|
||||
2. 좌측 패널: 데이터 입력 폼 및 설정 제어 영역 (너비 고정)
|
||||
3. 우측 영역: WebCAD 도면 뷰어 또는 결과 데이터 그리드 (나머지 전체 너비)
|
||||
|
||||
### 2.1 공통 컴포넌트 목록 (`ui_template_elements.ts`)
|
||||
* **기본:** `createButton`, `createInputField`, `createCard`, `createTag`, `createWorkflowShell`
|
||||
* **오버레이/알림:** `showLoadingOverlay`, `hideLoadingOverlay`, `showToast`
|
||||
* **스플라인 차트:** `createLineChart(opts)` — 외부 라이브러리 없이 인라인 SVG로 시계열 렌더링
|
||||
- **곡선:** Catmull-Rom → 3차 베지어 변환으로 스플라인(부드러운 곡선) 렌더
|
||||
- **색상:** `--color-chart-0~3` 테마 변수 사용 (하드코딩 금지)
|
||||
- **범례:** 그래프 영역 우상단에 반투명 오버레이 배치 (`opts.series[].name`)
|
||||
- **X축:** `opts.xLabels` 전달 시 양끝 포함 5개 라벨 균등 표기
|
||||
- **결측:** `series[].values`에 `null` 포함 시 해당 구간 선 끊김 처리
|
||||
- 데이터 2점 미만이면 빈 상태(`—`) 반환
|
||||
- 사용 예: B01_Dashboard 리소스 현황(CPU/메모리/디스크 30일 추이, X축=시간)
|
||||
|
||||
---
|
||||
|
||||
## 3. 인덱스 기반 다국어 제어 (i18n Constraints)
|
||||
* **텍스트 하드코딩 금지:** 라벨, 버튼 문구, 안내 메시지 등 모든 UI 문자열의 컴포넌트 내 직접 입력 절대 금지.
|
||||
* **🚨 동시 수정 프로토콜 (선언 후 구현):**
|
||||
1. **선(先) 등록:** 신규 문구 필요 시, `ui_template_locale.ts` 최하단에 키와 언어 배열 `[한국어, 영어]` 우선 추가.
|
||||
2. **후(後) 참조:** 컴포넌트 내 `ui_locales.키값[현재언어인덱스]` 형태로만 호출.
|
||||
* *제약 조건:* UI 수정 요청 시 다국어 파일 업데이트 코드 미제시 경우 가이드 위반(에러) 처리.
|
||||
|
||||
---
|
||||
|
||||
## 4. JS/TS 코드 제어 및 상태 (Logic Constraints)
|
||||
* **이벤트 핸들러 명명법:** `on[페이지명]_[기능명]_[액션]` 패턴 필수 준수. (예: `onB04_Surface_Calculate_Click`)
|
||||
* **비동기 상태 제어:** API 호출 시 즉시 로딩 스피너(Overlay) 활성화, 통신 완료(성공/실패) 후 해제 로직 필수 포함.
|
||||
* **1차 유효성 검사:** 백엔드 전송 전 빈 값 및 데이터 타입(숫자 범위 등) 프론트엔드 선 검증, 에러 시 입력창 하단 표기.
|
||||
|
||||
```typescript
|
||||
export const ui_locales = {
|
||||
A01_Home_Title: ["반갑습니다", "Welcome"],
|
||||
B04_wf1_Surface_Btn: ["지표면 분석 실행", "Run Surface Analysis"]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 인증, 인가, 역할 및 라우팅 (Authentication, Authorization, Roles & Routing)
|
||||
|
||||
### 5.1 세션 쿠키 기반 인증
|
||||
* **방식:** HttpOnly Secure 세션 쿠키 (localStorage 토큰 미사용)
|
||||
* **API 통신:** 모든 API 요청에 `credentials: "include"` 설정으로 쿠키 자동 전송
|
||||
* **로그아웃:** `/auth/logout` API 호출 → 서버 세션 삭제 → 쿠키 자동 제거
|
||||
* **인증 갱신:** 로그인 시 `users.auth_expires_at = NOW() + 3개월` 자동 설정
|
||||
- 유효한 인증: `auth_expires_at > NOW()` → 바로 대시보드 이동
|
||||
- 만료된 인증: `auth_expires_at <= NOW()` → 재인증 모달 표시
|
||||
|
||||
### 5.1.1 역할 기반 대시보드 렌더링
|
||||
* **USER 역할:**
|
||||
- 프로젝트 리스트 섹션
|
||||
- 회사 정보 섹션 (회사 생성/연결 팝업)
|
||||
- 기본정보 & 보안 섹션
|
||||
|
||||
* **ADMIN 역할:**
|
||||
- 프로젝트 리스트 섹션 (회사 전체 프로젝트)
|
||||
- 회사 정보 섹션
|
||||
- 팀원 관리 섹션
|
||||
- 가입 요청 섹션
|
||||
- 기본정보 & 보안 섹션
|
||||
|
||||
* **SYSTEM_ADMIN 역할:**
|
||||
- 리소스 현황 섹션 (CPU, MEM, DISK, 활성 사용자/프로젝트)
|
||||
- 회사 관리 섹션 (전체 회사 리스트 + 추가/수정/삭제)
|
||||
- 사용자 관리 섹션 (전체 사용자 + 역할 변경 + 회사 할당)
|
||||
- 가입 요청 관리 섹션 (전체 가입 신청 + 승인/거절)
|
||||
- 시스템 로그 섹션 (필터링 지원)
|
||||
- 리소스 모니터링 그래프 (시계열 데이터)
|
||||
- 기본정보 & 보안 섹션
|
||||
|
||||
### 5.2 라우팅 가드
|
||||
* **인증 확인:** `isAuthenticated()` 함수로 세션 유효성 검사 (PROTECTED_ROUTES 진입 전)
|
||||
* **회사 연결 확인:** B02~B11 워크플로우 진입 시 `fetchDashboardMe()` 호출 → `company_id` + `status` 확인
|
||||
- `status` 확인:
|
||||
- NO_COMPANY: B01_Dashboard로 강제 리다이렉트 (회사 연결 필요)
|
||||
- PENDING: B01_Dashboard로 강제 리다이렉트 (승인 대기 중)
|
||||
- ACTIVE: 워크플로우 진입 허용
|
||||
- `company_id` 없으면 B01_Dashboard로 리다이렉트
|
||||
* **역할 기반 라우팅:**
|
||||
- SYSTEM_ADMIN: 모든 페이지 접근 가능
|
||||
- ADMIN: B01_Dashboard (관리 섹션) + B02~B11 워크플로우 접근 가능
|
||||
- USER: B01_Dashboard (사용자 섹션) + B02~B11 워크플로우 접근 가능
|
||||
* **상태 전이:**
|
||||
- 회원가입 후 이메일 인증 완료: `users.status = 'NO_COMPANY'` (로그인 가능, 회사 미연결)
|
||||
- 회사 생성: `users.status = 'ACTIVE'`, `company_id` 세팅, `role = 'ADMIN'`, `is_master = TRUE`
|
||||
- 회사 참여 신청: `users.status = 'PENDING'` (관리자 승인 대기)
|
||||
- 관리자 승인: `users.status = 'ACTIVE'`, `company_id` 세팅
|
||||
|
||||
### 5.3 대시보드 권한 기반 UI 제어 (B01_Dashboard)
|
||||
* **권한 검증 헬퍼 필수:** `.agent/plan_dashboard_management.md` 섹션 9의 헬퍼 함수 참고
|
||||
- `canEditProject(user, project)` - 프로젝트 수정 권한
|
||||
- `canDeleteProject(user)` - 프로젝트 삭제 권한 (SYSTEM_ADMIN만)
|
||||
- `canAddUser(user)` - 사용자 추가 권한
|
||||
- `canChangeRole(user, targetUser)` - 역할 변경 권한 (ADMIN: USER↔ADMIN만, SYSTEM_ADMIN 제외)
|
||||
- `canDeleteUser(user, targetUser)` - 사용자 삭제 권한 (마지막 ADMIN 보호)
|
||||
- `canManageAutomation(user, automation)` - 자동화 로직 관리 권한
|
||||
- `isLastAdmin(company, excludeUserId)` - 회사의 마지막 ADMIN 확인
|
||||
|
||||
* **UI 컴포넌트 구현:** 권한에 따라 버튼/필드 표시/비활성화
|
||||
- 프로젝트: 수정 모달, 삭제 확인 모달 (삭제는 SYSTEM_ADMIN만)
|
||||
- 사용자: 추가 모달 (가입 계정 목록), 수정 모달, 역할변경 모달, 삭제 확인 모달
|
||||
- 자동화 로직: 생성/수정/삭제 모달 (ADMIN/SYSTEM_ADMIN만)
|
||||
- 모든 삭제 모달에 경고 메시지 포함 (되돌릴 수 없음)
|
||||
|
||||
* **다국어 반영:** 계획서 8장의 모든 키를 `ui_template_locale.ts`에 추가 후 컴포넌트에서 `L(키)` 사용
|
||||
- 모달 제목, 버튼 레이블, 확인 메시지, 오류 메시지 모두 다국어 처리
|
||||
- 하드코딩된 문구 절대 금지
|
||||
@@ -0,0 +1,278 @@
|
||||
# B03~B06 기능 마이그레이션 계획서
|
||||
|
||||
**갱신일:** 2026-07-05
|
||||
**상태:** 백엔드 마이그레이션 완료 — Stage 0~4 (B03~B06) 백엔드 이전 완료. 남은 항목은 프론트엔드(Vanilla TS) 재작성과 실제 DB 연동 검증.
|
||||
**범위:** `B03_FileInput` ~ `B06_wf3_ProfileCross`
|
||||
**DB:** MariaDB v10.6+ (aiomysql 드라이버, Raw SQL)
|
||||
|
||||
**진척 요약:**
|
||||
- Stage 0 (공통 기반): ✅ 완료
|
||||
- Stage 1 B03 (파일 입력): ✅ 백엔드 완료
|
||||
- Stage 2 B04 (지표면 분석): ✅ 백엔드 완료 (필터 4종 + 모델 5종 + 스무딩 + 등고선 + 파이프라인)
|
||||
- Stage 3 B05 (경로 설계): ✅ 백엔드 완료 (Dijkstra + ridge-valley + skeleton)
|
||||
- Stage 4 B06 (종횡단 생성): ✅ 백엔드 완료 (sampler + 종횡단 + Repository/Router)
|
||||
- 공통: 전 페이지 DB 접근을 aiomysql Raw SQL로 통일, 순수 계산부는 실데이터로 검증
|
||||
- Stage 2~4 프론트엔드 폼/결과 UI: ✅ 완료 (B04~B06 UI_Page + Api_Fetch + CSS + locale, tsc 타입체크 통과)
|
||||
- 남은 작업: B04~B06 **WebCAD 3D/차트 뷰어**, 실제 aislo_db 연동·구형 수치 비교
|
||||
|
||||
---
|
||||
|
||||
## 1. 확정 기준
|
||||
|
||||
- 기능 동작의 기준 원본은 `0_old/main.py`와 `0_old/utils/`이다.
|
||||
- `0_old/backend/app/`은 이전 프로토타입 참고 자료이며 최종 동작 기준으로 사용하지 않는다.
|
||||
- 신규 배치 구조는 `.agent/structure.md`를 따른다. 구형 파일 구조는 승계하지 않는다.
|
||||
- 백엔드는 `.agent/backend.md`, 프론트엔드는 `.agent/frontend.md`를 따른다.
|
||||
- 저장 구조 및 DB 경로 열은 `.agent/db_schema_simple.md`의 단계별 파일시스템 구조를 따른다.
|
||||
- DB 스키마 작성·수정은 별도 담당 작업이다. 이 마이그레이션은 확정된 DB 계약만 `aiomysql`와 Raw SQL로 사용한다.
|
||||
- 루트 `main.py`에는 페이지 라우터 등록만 추가한다.
|
||||
- 구형 React/TSX UI는 복사하지 않고 현재 Vanilla TypeScript 구조와 공통 UI 템플릿에 맞게 재작성한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 작업 원칙
|
||||
|
||||
### 2.1 함수 하나씩 이전
|
||||
|
||||
각 작업 단위는 다음 순서를 지킨다.
|
||||
|
||||
1. 대상 구형 함수와 직접 의존성 확인
|
||||
2. 신규 페이지 폴더에 책임에 맞는 파일 하나 생성
|
||||
3. 함수 하나만 이전·수정
|
||||
4. 해당 함수 단위 테스트 또는 최소 실행 검증
|
||||
5. 포맷터·린터 실행
|
||||
6. 검증 완료 후 다음 함수 진행
|
||||
|
||||
한 번에 구형 모듈 전체를 복사하지 않는다. 구형 전역 경로, 동기식 파일 처리, 하드코딩 설정은 그대로 가져오지 않는다.
|
||||
|
||||
### 2.2 파일 분리
|
||||
|
||||
- `*_Router.py`: FastAPI 엔드포인트와 응답 변환
|
||||
- `*_Schema.py`: Pydantic 요청·응답 검증
|
||||
- `*_Engine_*.py`: 페이지 고유 연산
|
||||
- `*_Repository.py`: `asyncpg` Raw SQL과 DB 경로 메타데이터 처리
|
||||
- `common_util/`: 둘 이상의 페이지가 실제로 공유하는 경로·원자적 파일 쓰기·기하 유틸만 배치
|
||||
- 단일 파일이 700줄에 도달하기 전에 기능별 파일 분할을 제안하고 승인 후 `structure.md`를 갱신한다.
|
||||
|
||||
### 2.3 저장소 경계
|
||||
|
||||
```text
|
||||
storage/{company_slug}/{user_slug}/{project_id}/
|
||||
├── B03_FileInput/
|
||||
├── B04_wf1_Surface/
|
||||
├── B05_wf2_Route/
|
||||
└── B06_wf3_ProfileCross/
|
||||
```
|
||||
|
||||
각 페이지는 자기 폴더만 직접 생성·수정한다. 상위 단계 변경 시 하위 결과를 임의 삭제하지 않고 `stale_from`을 갱신한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 구형 기능 분석 결과
|
||||
|
||||
### 3.1 B03_FileInput
|
||||
|
||||
구형 원본:
|
||||
|
||||
- `0_old/main.py`: `check_sample`, `upload_files`
|
||||
- 보조 참고: `0_old/backend/app/analyzer.py`의 파일 분석 함수
|
||||
|
||||
책임:
|
||||
|
||||
- LAS/TIF/TFW/PRJ/DXF 업로드 검증
|
||||
- `B03_FileInput/input/{file_type}/` 저장
|
||||
- 파일 메타데이터 추출 및 `input_files` 기록
|
||||
- 업로드 완료 상태 반환
|
||||
|
||||
B04로 넘길 기능:
|
||||
|
||||
- `process_pipeline`
|
||||
- `run_ground_analysis`
|
||||
- 포인트 필터링 및 지표면 모델 생성
|
||||
|
||||
### 3.2 B04_wf1_Surface
|
||||
|
||||
구형 원본:
|
||||
|
||||
- `0_old/main.py`: `process_pipeline`, `ensure_terrain_models`, 지표면·포인트 API
|
||||
- `0_old/utils/structurizer.py`
|
||||
- `filter_grid_min_z.py`, `filter_csf.py`, `filter_pmf.py`, `filter_ransac.py`
|
||||
- `terrain_model_converter.py`, `surface_smoother.py`, `contour_extractor.py`
|
||||
|
||||
책임:
|
||||
|
||||
- LAS 구조화와 지면 필터 생성
|
||||
- 변환 포인트클라우드 저장
|
||||
- TIN/DTM/NURBS/implicit/meshfree 모델 생성
|
||||
- 스무딩, 미리보기, 등고선 생성
|
||||
- 지표면 모델 선택 확정 및 하위 단계 stale 전파
|
||||
|
||||
### 3.3 B05_wf2_Route
|
||||
|
||||
구형 원본:
|
||||
|
||||
- `0_old/main.py`: 경로점 CRUD, solve/result/confirm, stale 서명 처리
|
||||
- `0_old/utils/route_solver.py`
|
||||
- `0_old/utils/route_solver_ridgevalley.py`
|
||||
- `0_old/utils/terrain_skeleton.py`
|
||||
|
||||
책임:
|
||||
|
||||
- BP/EP/CP/AP/FP 입력 검증과 저장
|
||||
- Dijkstra 및 ridge-valley 경로 계산
|
||||
- 경사·곡선반경·회피/금지 구역 검증
|
||||
- 경로 결과 조회·확정 및 B06 stale 전파
|
||||
|
||||
### 3.4 B06_wf3_ProfileCross
|
||||
|
||||
구형 원본:
|
||||
|
||||
- `0_old/main.py`: sections generate/get/confirm 및 입력 서명 처리
|
||||
- `0_old/utils/surface_elevation_sampler.py`
|
||||
- `0_old/utils/section_generator.py`
|
||||
|
||||
책임:
|
||||
|
||||
- 확정 경로와 확정 지표면 모델 검증
|
||||
- 종단 표고 프로필 생성
|
||||
- 지정 측점 간격의 횡단면 생성
|
||||
- 결과 조회·확정 및 재현성 서명 관리
|
||||
|
||||
---
|
||||
|
||||
## 4. 함수 단위 실행 순서
|
||||
|
||||
### Stage 0 — 공통 기반
|
||||
|
||||
- [x] `config_system.py`의 저장 경로에서 불필요한 `projects` 경로 제거
|
||||
- [x] 프로젝트 루트와 B03~B06 단계별 경로를 안전하게 만드는 공통 경로 함수 추가
|
||||
- [x] JSON 원자적 쓰기 함수 이전
|
||||
- [x] `workflow.json` 읽기 및 stale 필드 원자적 갱신 함수 이전
|
||||
- [x] 공통 함수별 테스트 작성
|
||||
|
||||
### Stage 1 — B03_FileInput
|
||||
|
||||
- [x] `B03_FileInput_Schema.py`: 파일 종류·크기·확장자 검증 모델
|
||||
- [x] `B03_FileInput_Engine.py`: 안전한 파일명과 목적 경로 결정 함수
|
||||
- [x] `B03_FileInput_Engine.py`: 업로드 스트림 저장 함수
|
||||
- [x] `B03_FileInput_Engine_Analyze.py`: LAS 메타데이터 분석 함수
|
||||
- [x] `B03_FileInput_Engine_Analyze.py`: PRJ/TIF/TFW 메타데이터 분석 함수
|
||||
- [x] `B03_FileInput_Repository.py`: `input_files` 저장 함수
|
||||
- [x] `B03_FileInput_Router.py`: 업로드 엔드포인트
|
||||
- [x] B03 프론트 드롭존·목록·검증·로딩 UI를 공통 컴포넌트 기반으로 작성
|
||||
- [x] 업로드 통합 테스트
|
||||
|
||||
### Stage 2 — B04_wf1_Surface
|
||||
|
||||
- [x] LAS 구조화 함수 이전
|
||||
- [x] grid-min-z 필터 함수 이전 및 검증
|
||||
- [x] CSF 필터 함수 이전 및 검증
|
||||
- [x] PMF 필터 함수 이전 및 검증
|
||||
- [x] RANSAC 필터 함수 이전 및 검증
|
||||
- [x] 지표면 모델 공통 컨텍스트 생성 함수 이전 (ModelContext + 메시 유틸)
|
||||
- [x] TIN 생성 함수 이전
|
||||
- [x] DTM 생성 함수 이전
|
||||
- [x] NURBS 생성 함수 이전
|
||||
- [x] implicit 생성 함수 이전
|
||||
- [x] meshfree 생성 함수 이전
|
||||
- [x] DTM/TIN 스무딩 함수 이전
|
||||
- [x] 등고선 생성 함수 이전
|
||||
- [x] 미리보기·메타데이터·모델 확정 라우트 추가 (analyze/models 라우트 + 파이프라인 manifest)
|
||||
- [x] `processed_point_cloud`, `surface_models`, `terrain_layers` Raw SQL 저장 함수 추가 (aiomysql)
|
||||
- [x] B04 폼·모델 목록 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B04 WebCAD 3D 뷰어(포인트클라우드/메시 렌더) 추가
|
||||
- [ ] 샘플 LAS 결과를 구형 출력과 비교 검증
|
||||
|
||||
### Stage 3 — B05_wf2_Route
|
||||
|
||||
- [x] 경로점 Pydantic 모델 및 유효성 검사 이전 (Schema)
|
||||
- [x] 경로점 저장·조회·초기화 함수 이전 (Repository)
|
||||
- [x] 비용면 생성 및 캐시 함수 이전 (Solver)
|
||||
- [x] Dijkstra 단일 구간 탐색 함수 이전 (Geometry)
|
||||
- [x] 최적 경로 조립 함수 이전 (Solver solve_optimal_route)
|
||||
- [x] 지형 skeleton 생성 함수 이전 (Skeleton, D8 흐름누적)
|
||||
- [x] ridge-valley 탐색 함수 이전 (RidgeValley, 정속경사+fillet)
|
||||
- [ ] 입력 서명·stale 판정 함수 이전
|
||||
- [x] 경로 확정 함수 이전 (Repository confirm_route + confirm 라우트)
|
||||
- [x] `routes`, `route_points`, `route_statistics` Raw SQL 저장 함수 추가 (aiomysql)
|
||||
- [x] B05 경로점 입력·제약조건·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B05 경로 WebCAD 뷰어(노선 폴리라인 렌더/편집) 추가
|
||||
- [x] 기존 route solver 테스트를 신규 구조로 이관·통과
|
||||
|
||||
### Stage 4 — B06_wf3_ProfileCross
|
||||
|
||||
- [x] 지표면 sampler 인터페이스 이전 (Engine_Sampler: SurfaceElevationSampler Protocol)
|
||||
- [x] DTM grid sampler 함수 이전 (DtmGridSampler, 4-꼭짓점 valid 검증)
|
||||
- [x] 보간 sampler 함수 이전 (InterpolatedSurfaceSampler + build_surface_sampler 5종)
|
||||
- [x] 측점 배열 생성 함수 이전 (Engine_Section: _chainages)
|
||||
- [x] 경로 보간 및 접선 계산 함수 이전 (_interpolate_xy, _tangent_at)
|
||||
- [x] 종단·횡단 생성 함수 이전 (generate_sections)
|
||||
- [ ] 입력 서명·중복 실행 방지 함수 이전 (delete_sections_for_route로 멱등 재실행만 구현)
|
||||
- [x] 결과 조회·확정 라우트 추가 (sections/generate·get·confirm 라우트)
|
||||
- [x] `longitudinal_sections`, `cross_sections` Raw SQL 저장 함수 추가 (aiomysql Repository)
|
||||
- [x] B06 측점·횡단 옵션·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
|
||||
- [ ] B06 종단도·횡단 카드 WebCAD 뷰어(프로필 차트 렌더) 추가
|
||||
- [ ] 구형 section 결과와 수치 비교 검증
|
||||
|
||||
**B06 백엔드 구현 파일:**
|
||||
- `B06_wf3_ProfileCross_Engine_Sampler.py` — 표고 sampler (Protocol + DTM/보간 5종)
|
||||
- `B06_wf3_ProfileCross_Engine_Section.py` — 종횡단 생성 (측점/접선/횡단 샘플)
|
||||
- `B06_wf3_ProfileCross_Engine.py` — 오케스트레이터 (경로 GeoJSON→종횡단→파일 저장)
|
||||
- `B06_wf3_ProfileCross_Repository.py` — aiomysql Raw SQL (2 테이블)
|
||||
- `B06_wf3_ProfileCross_Schema.py` / `_Router.py` — Pydantic + FastAPI 라우트
|
||||
|
||||
**검증 상태:** B04(DTM)→B05(경로 70.71m)→B06(종단면+횡단면 5건) end-to-end 파일 생성 확인.
|
||||
DB 저장 함수는 FakeCursor 기반 문법 검증만 수행 (실제 aislo_db 연동은 나스 서버 준비 후).
|
||||
|
||||
---
|
||||
|
||||
## 5. API 및 상태 원칙
|
||||
|
||||
- API 경로는 `/api/projects/{project_id}/...` 형태를 유지하되 페이지별 Router에서 선언한다.
|
||||
- 모든 JSON 요청은 Pydantic 검증 후 Engine으로 전달한다.
|
||||
- 긴 지형 연산은 이벤트 루프를 막지 않도록 실행 방식을 별도 검토한다.
|
||||
- 오류 응답은 `{"status": "error", "message": "원인"}` 형식을 유지한다.
|
||||
- 프론트 API 호출은 로딩 Overlay를 항상 시작·해제한다.
|
||||
- UI 문자열은 `ui_template_locale.ts`에 먼저 등록한 뒤 참조한다.
|
||||
- 상위 단계 재계산 시 하위 결과는 stale 처리하며 다른 페이지 폴더를 직접 삭제하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 검증 체크리스트
|
||||
|
||||
각 함수 이전 후:
|
||||
|
||||
- [ ] 구형 함수의 입력·출력 계약 비교
|
||||
- [ ] 하드코딩 설정 제거 및 `config_system.py` 연결
|
||||
- [ ] 신규 단계별 저장 경로 사용 확인
|
||||
- [ ] 경로 이탈 및 파일명 공격 방지 확인
|
||||
- [ ] 예외 처리와 표준 오류 응답 확인
|
||||
- [ ] Python: `ruff format`, `ruff check --fix`
|
||||
- [ ] TypeScript/CSS: `npx prettier --write`
|
||||
- [ ] 단위 테스트 실행
|
||||
- [ ] 관련 통합 테스트 실행
|
||||
- [ ] 단일 파일 700줄 미만 확인
|
||||
|
||||
단계 완료 후:
|
||||
|
||||
- [ ] B03 업로드 파일과 DB 메타데이터 일치
|
||||
- [ ] B04 지표면 모델의 범위·점 수·표고 통계 비교
|
||||
- [ ] B05 경로 길이·경사·곡선반경·제약 위반 비교
|
||||
- [ ] B06 측점 수·종단 표고·횡단 좌표 비교
|
||||
- [ ] 다중 브라우저 polling 및 stale 전파 확인
|
||||
|
||||
---
|
||||
|
||||
## 7. 작업 경계와 보류 항목
|
||||
|
||||
- DB 테이블·FK·인덱스 정의 변경은 본 작업에서 수행하지 않는다.
|
||||
- `.agent/db_schema_simple.md`는 DB 담당자의 작업 영역이므로 직접 수정하지 않는다.
|
||||
- DB 스키마가 확정되지 않은 Repository 함수는 인터페이스만 계획하고 임의 열을 만들지 않는다.
|
||||
- B07 이후 기능은 이번 마이그레이션 범위에서 제외한다.
|
||||
- 구형 샘플 데이터는 검증 입력으로만 사용하고 신규 영구저장소로 자동 복사하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 8. 다음 실행 단위
|
||||
|
||||
첫 구현은 Stage 0의 저장 경로 함수 하나로 시작한다. 함수와 테스트를 검증한 뒤 다음 공통 함수로 이동한다.
|
||||
@@ -0,0 +1,189 @@
|
||||
# 프로젝트 구조 명세서 (structure.md)
|
||||
|
||||
## 1. 폴더 및 파일 명명 규칙 (Naming Convention)
|
||||
|
||||
- **기본 원칙:** 프론트엔드(TypeScript)와 백엔드(Python) 파일을 수직 통합 관리
|
||||
- **페이지 폴더 명명:** 로그인 전은 `A01_`, 로그인 후는 `B01_` 형태 적용, 워크플로우는 `wf[번호]` 접두사를 사용
|
||||
- **시스템 폴더 명명:** 공통 유틸, DB, 리소스, 영구저장소 등 시스템 및 데이터 인프라 폴더는 접두사 ID 없이 명확한 영문명 분리
|
||||
- **파일 명명:** 반드시 `폴더명(페이지명)_기능_기타사양.확장자` 형태 작성
|
||||
- **공통 모듈:** 공통 유틸 코드는 `common_util` 폴더 내에 `common_util_기능.확장자` 형태 보관
|
||||
|
||||
---
|
||||
|
||||
## 2. 전체 디렉토리 트리 구조
|
||||
|
||||
```text
|
||||
my-project/
|
||||
├── .agent/
|
||||
│ ├── agent.md
|
||||
│ └── structure.md # 본 파일
|
||||
├── A00_Common/ # 프론트엔드 공통 인프라 (부트스트랩, 라우팅, 셸)
|
||||
│ ├── main.ts # 앱 진입점 (스타일 주입 → 테마 복원 → 셸 → 라우터)
|
||||
│ ├── router.ts # 해시 기반 SPA 라우터 + 인증 가드 + 지연 로딩 (A·B 전 라우트 등록)
|
||||
│ ├── app_shell.ts # 공통 헤더/푸터 (로고, 네비, 언어·테마·로그인 토글)
|
||||
│ ├── b_page_scaffold.ts # B 그룹 공통 스캐폴드 (헤더+준비중 안내 / 워크플로우 셸 래퍼)
|
||||
│ └── vite-env.d.ts # Vite 클라이언트 타입 선언
|
||||
├── common_util/ # 공통 유틸리티 (공통 기능 코드)
|
||||
│ ├── common_util_validate.ts # 프론트 1차 유효성 검사 (이메일/빈값/최소길이)
|
||||
│ ├── common_util_auth.py # 비밀번호·OTP·세션 쿠키·권한 검증
|
||||
│ ├── common_util_auth_repository.py # 인증·조직·관리 Raw SQL 저장소
|
||||
│ ├── common_util_email.py # 비동기 SMTP 발송 및 재시도
|
||||
│ ├── common_util_email_templates.py # 인증·조직 알림 이메일 템플릿
|
||||
│ ├── common_util_string.ts
|
||||
│ ├── common_util_storage.py # 프로젝트 및 워크플로우 단계별 저장 경로
|
||||
│ ├── common_util_json.py # JSON 원자적 저장
|
||||
│ ├── common_util_workflow.py # 공유 workflow.json 상태 처리
|
||||
│ ├── common_util_resource_monitor.py # 시스템 리소스 계측(2분 주기) + log/ 로그 + DB 기록
|
||||
│ └── common_util_format.py
|
||||
├── db_management/ # DB 관리 폴더 (PostgreSQL 마이그레이션 및 스키마 관리)
|
||||
│ ├── schema.sql
|
||||
│ ├── migration_v1.py
|
||||
│ └── 002_auth_security.sql # 기존 DB 로그인·보안 증분 마이그레이션
|
||||
├── resources/ # 웹앱 UI 구성 리소스 (사진, 회사 로고 등 에셋)
|
||||
│ ├── logo.png
|
||||
│ ├── banner.jpg
|
||||
│ └── legal/ # 약관 및 정책 텍스트 (회원가입 UI에서 사용)
|
||||
│ ├── terms_of_service.txt # 이용약관
|
||||
│ ├── privacy_policy.txt # 개인정보처리방침
|
||||
│ └── marketing_consent.txt # 마케팅 정보 수신 동의
|
||||
├── storage/ # 영구 저장소 (데이터 커서 및 대용량 파일 별도 저장 로직용)
|
||||
│ └── [고객사명]/
|
||||
│ └── [사용자명]/
|
||||
│ └── [프로젝트ID]/ # 프로젝트별 내부 폴더/파일 자동 생성 (별도 폴더 ID 미사용)
|
||||
├── log/ # 런타임 리소스 로그 (.gitignore, 1개월 롤링)
|
||||
│ └── system_resources.log # CPU/메모리/디스크 계측 단일 파일 (2분 주기)
|
||||
├── ui_template/ # 공통 UI 템플릿 및 테마
|
||||
│ ├── ui_template_theme.css # 글로벌 디자인 테마 및 CSS 변수 (라이트/다크)
|
||||
│ ├── ui_template_locale.ts # 다국어 텍스트 배열 방식 관리 파일
|
||||
│ └── ui_template_elements.ts # 공통 컴포넌트 및 그룹 템플릿 정의
|
||||
├── templates/ # 보고서 출력용 마스터 양식 (DWG, Excel, PPT 등 기본 원본 템플릿 파일)
|
||||
│ ├── template_report_form.xlsx
|
||||
│ └── template_drawing_base.dwg
|
||||
├── config/ # 프로그램 내부 제어 및 시스템 환경 설정 폴더
|
||||
│ ├── config_system.py # 백엔드 작동 제어 및 알고리즘 파라미터
|
||||
│ ├── config_db.py # 데이터베이스 연결 제어 설정
|
||||
│ ├── config_frontend.ts # 프론트엔드 제어 및 웹 환경 설정
|
||||
│ ├── package.json # Node.js 의존성 (npm/vite)
|
||||
│ ├── tsconfig.json # TypeScript 설정
|
||||
│ └── vite.config.ts # Vite 빌드 설정
|
||||
├── A01_Home/ # 로그인 전 01: 홈 (소개, 소식, 사이트맵 등)
|
||||
│ ├── A01_Home_UI_Page.ts
|
||||
│ ├── A01_Home_UI_Style.css
|
||||
│ └── A01_Home_Api_Fetch.py
|
||||
├── A02_ProgDetail/ # 로그인 전 02: 프로그램 상세 (6단계 워크플로우 + 핵심 기술)
|
||||
│ ├── A02_ProgDetail_UI_Page.ts
|
||||
│ └── A02_ProgDetail_UI_Style.css
|
||||
├── A03_CompDetail/ # 로그인 전 03: 회사 상세 (미션 + 핵심 가치 + 연락처)
|
||||
│ ├── A03_CompDetail_UI_Page.ts
|
||||
│ └── A03_CompDetail_UI_Style.css
|
||||
├── A04_NewsHistory/ # 로그인 전 04: 최신소식 및 개선 이력 (타임라인)
|
||||
│ ├── A04_NewsHistory_UI_Page.ts
|
||||
│ └── A04_NewsHistory_UI_Style.css
|
||||
├── A05_EduDetail/ # 로그인 전 05: 교육 상세 (과정 소개 + 문의 CTA)
|
||||
│ ├── A05_EduDetail_UI_Page.ts
|
||||
│ └── A05_EduDetail_UI_Style.css
|
||||
├── A06_Login/ # 로그인 전 06: 로그인 (이메일 + 비밀번호 폼)
|
||||
│ ├── A06_Login_UI_Auth_Page.ts # 세션 쿠키·OTP 로그인 UI (현재 사용 중)
|
||||
│ ├── A06_Login_UI_Style.css
|
||||
│ ├── A06_Login_Api_Fetch.ts
|
||||
│ ├── A06_Login_Schema.py
|
||||
│ └── A06_Login_Router.py
|
||||
├── A07_Register/ # 로그인 전 07: 회원가입 (회사/이름/이메일/비밀번호 폼)
|
||||
│ ├── A07_Register_UI_Page.ts
|
||||
│ ├── A07_Register_UI_Auth_Page.ts # 조직 유형·약관·OTP 회원가입 UI
|
||||
│ ├── A07_Register_UI_Style.css
|
||||
│ ├── A07_Register_Api_Fetch.ts
|
||||
│ ├── A07_Register_Schema.py
|
||||
│ └── A07_Register_Router.py
|
||||
├── A08_Support/ # 로그인 전 08: 기술지원 요청 (문의 폼 + textarea)
|
||||
│ ├── A08_Support_UI_Page.ts
|
||||
│ └── A08_Support_UI_Style.css
|
||||
├── A09_Security/ # 로그인 전 09: 보안 및 약관 동의 (필수/선택 약관 확인)
|
||||
│ ├── A09_Security_UI_Page.ts # 약관 동의 페이지 (이용약관/개인정보/마케팅/보안정책)
|
||||
│ ├── A09_Security_UI_Style.css # A09 고유 약관 박스/체크박스 스타일
|
||||
│ ├── A09_Security_Api_Fetch.ts # 동의 내용 제출 API 클라이언트
|
||||
│ ├── A09_Security_Terms.ts # 약관 텍스트 데이터 (한/영 다국어)
|
||||
│ ├── A09_Security_Schema.py # 조직·관리자 보안 요청 검증
|
||||
│ └── A09_Security_Router.py # 마스터·시스템 관리자·활동 로그 API
|
||||
│
|
||||
│ # ※ B01·B02 본문 완성 / B03~B11 헤더·셸만 (본문은 0_old 참고 후 구체화)
|
||||
├── B01_Dashboard/ # 로그인 후 01: 대시보드 (역할별 정보 표시)
|
||||
│ ├── B01_Dashboard_UI_Page.ts # 역할별 섹션 조건부 렌더링
|
||||
│ ├── B01_Dashboard_UI_Modals.ts # 프로젝트, 사용자, 자동화 제어 모달 모음
|
||||
│ ├── B01_Dashboard_UI_Helper.ts # 역할별 접근 권한 확인 헬퍼
|
||||
│ ├── B01_Dashboard_UI_Resources.ts # CPU/Memory/Disk 리소스 렌더링 컴포넌트
|
||||
│ ├── B01_Dashboard_UI_Style.css # 프로젝트 테이블, 워크플로우, 팝업 스타일
|
||||
│ ├── B01_Dashboard_Api_Fetch.ts # dashboard API 클라이언트
|
||||
│ ├── B01_Dashboard_Schema.py # 요청/응답 Pydantic 검증
|
||||
│ ├── B01_Dashboard_Repository.py # Raw SQL 저장소 (사용자, 회사, 팀원, 가입 신청)
|
||||
│ └── B01_Dashboard_Router.py # 역할별 API 엔드포인트
|
||||
├── B02_ProjRegister/ # 로그인 후 02: 프로젝트 등록 (기본정보 입력 폼)
|
||||
│ ├── B02_ProjRegister_UI_Page.ts
|
||||
│ └── B02_ProjRegister_UI_Style.css
|
||||
├── B03_FileInput/ # 로그인 후 03: 파일 입력 (헤더+준비중 안내)
|
||||
│ ├── B03_FileInput_UI_Page.ts
|
||||
│ ├── B03_FileInput_UI_Style.css
|
||||
│ ├── B03_FileInput_Api_Fetch.ts # 다중 파일 업로드 API 클라이언트
|
||||
│ ├── B03_FileInput_Schema.py # 업로드 파일 Pydantic 검증
|
||||
│ ├── B03_FileInput_Engine.py # 업로드 저장 경로·스트림 처리
|
||||
│ ├── B03_FileInput_Engine_Analyze.py # 원본 파일 메타데이터 분석
|
||||
│ ├── B03_FileInput_Repository.py # input_files asyncpg Raw SQL
|
||||
│ └── B03_FileInput_Router.py # 다중 파일 업로드 API
|
||||
├── B04_wf1_Surface/ # 로그인 후 04: 1차 workflow (지표면 모델 분석)
|
||||
│ ├── B04_wf1_Surface_UI_Page.ts # 3단 레이아웃 (필터/표현 선택 폼 + 모델 목록)
|
||||
│ ├── B04_wf1_Surface_UI_Style.css # B04 고유 폼 그룹/모델 카드 스타일
|
||||
│ ├── B04_wf1_Surface_Api_Fetch.ts # surface/analyze·models API 클라이언트
|
||||
│ ├── B04_wf1_Surface_Schema.py # 분석 요청/응답 Pydantic 검증
|
||||
│ ├── B04_wf1_Surface_Engine.py # 지표면 분석 오케스트레이터
|
||||
│ ├── B04_wf1_Surface_Engine_Structurize.py # LAS/LAZ 구조화
|
||||
│ ├── B04_wf1_Surface_Engine_Ground.py # 지면 분석 진입
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_Grid.py # grid minimum-Z 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_CSF.py # CSF 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_PMF.py # PMF 필터
|
||||
│ ├── B04_wf1_Surface_Engine_Filter_RANSAC.py # RANSAC 필터
|
||||
│ ├── B04_wf1_Surface_Engine_ModelContext.py # 모델 공통 컨텍스트/메시 유틸
|
||||
│ ├── B04_wf1_Surface_Engine_ModelBuild.py # TIN/DTM/NURBS/implicit/meshfree 생성
|
||||
│ ├── B04_wf1_Surface_Engine_Smooth.py # DTM/TIN 스무딩
|
||||
│ ├── B04_wf1_Surface_Engine_Contour.py # 등고선 생성
|
||||
│ ├── B04_wf1_Surface_Engine_Pipeline.py # 파이프라인 manifest
|
||||
│ ├── B04_wf1_Surface_Repository.py # processed_point_cloud/surface_models aiomysql Raw SQL
|
||||
│ └── B04_wf1_Surface_Router.py # surface/analyze·models API
|
||||
├── B05_wf2_Route/ # 로그인 후 05: 2차 workflow (경로설계)
|
||||
│ ├── B05_wf2_Route_UI_Page.ts # 3단 레이아웃 (제어점/제약 폼 + 결과 메트릭)
|
||||
│ ├── B05_wf2_Route_UI_Style.css # B05 고유 제어점 행/제약 그룹 스타일
|
||||
│ ├── B05_wf2_Route_Api_Fetch.ts # route/solve·confirm API 클라이언트
|
||||
│ ├── B05_wf2_Route_Schema.py # 경로 탐색 요청/응답 Pydantic 검증
|
||||
│ ├── B05_wf2_Route_Engine.py # 경로 설계 오케스트레이터
|
||||
│ ├── B05_wf2_Route_Engine_Geometry.py # Dijkstra 단일 구간 탐색
|
||||
│ ├── B05_wf2_Route_Engine_Solver.py # 비용면 생성 + 최적 경로 조립
|
||||
│ ├── B05_wf2_Route_Engine_Skeleton.py # 지형 skeleton (D8 흐름누적)
|
||||
│ ├── B05_wf2_Route_Engine_RidgeValley.py # ridge-valley 탐색
|
||||
│ ├── B05_wf2_Route_Repository.py # routes/route_points/route_statistics aiomysql Raw SQL
|
||||
│ └── B05_wf2_Route_Router.py # route/solve·confirm API
|
||||
├── B06_wf3_ProfileCross/ # 로그인 후 06: 3차 workflow (종횡단 생성)
|
||||
│ ├── B06_wf3_ProfileCross_UI_Page.ts # 3단 레이아웃 (측점/횡단 옵션 폼 + 결과 메트릭)
|
||||
│ ├── B06_wf3_ProfileCross_UI_Style.css # B06 고유 옵션 그룹/결과 메트릭 스타일
|
||||
│ ├── B06_wf3_ProfileCross_Api_Fetch.ts # sections/generate·get·confirm API 클라이언트
|
||||
│ ├── B06_wf3_ProfileCross_Schema.py # 종횡단 생성 요청/응답 Pydantic 검증
|
||||
│ ├── B06_wf3_ProfileCross_Engine.py # 종횡단 생성 오케스트레이터
|
||||
│ ├── B06_wf3_ProfileCross_Engine_Sampler.py # 표고 sampler (Protocol + DTM/보간 5종)
|
||||
│ ├── B06_wf3_ProfileCross_Engine_Section.py # 종횡단 생성 (측점/접선/횡단 샘플)
|
||||
│ ├── B06_wf3_ProfileCross_Repository.py # longitudinal/cross_sections aiomysql Raw SQL
|
||||
│ └── B06_wf3_ProfileCross_Router.py # sections/generate·get·confirm API
|
||||
├── B07_wf4_DesignDetail/ # 로그인 후 07: 4차 workflow (상세설계) — 워크플로우 셸
|
||||
│ └── B07_wf4_DesignDetail_UI_Page.ts
|
||||
├── B08_wf5_Quantity/ # 로그인 후 08: 5차 workflow (수량 산출) — 워크플로우 셸
|
||||
│ └── B08_wf5_Quantity_UI_Page.ts
|
||||
├── B09_wf6_Estimation/ # 로그인 후 09: 6차 workflow (견적/문서) — 워크플로우 셸
|
||||
│ └── B09_wf6_Estimation_UI_Page.ts
|
||||
├── B10_Payment/ # 로그인 후 10: 결재 페이지 (헤더+준비중 안내)
|
||||
│ └── B10_Payment_UI_Page.ts
|
||||
└── B11_Status/ # 로그인 후 11: 상태 출력 페이지 (헤더+준비중 안내)
|
||||
└── B11_Status_UI_Page.ts
|
||||
│
|
||||
├── main.py # FastAPI 애플리케이션 진입점
|
||||
├── requirements.txt # Python 의존성 (pip)
|
||||
├── .env.example # 환경 변수 템플릿
|
||||
└── node_modules/ # Node.js 패키지 (.gitignore)
|
||||
└── .build/ # Vite 빌드 결과 (.gitignore)
|
||||
```
|
||||
@@ -0,0 +1,697 @@
|
||||
# 일하는 법 · 검증 함정 — 겪은 것에서 (2026-09-07 ~ 09)
|
||||
|
||||
> **상시계획서 `PLAN.md` 「참고」에서 옮긴 원문임. 줄이지 않았음.**
|
||||
> 옮긴 까닭 — 계획서의 「참고」는 **코드 작업 중 필요한 기준표·확정사항만** 두기로 되어 있는데
|
||||
> 여기 든 것은 기준표가 아니라 **일하는 법**임(2026-09-09 사용자 지시 「반영」).
|
||||
> **다음 세션이 계획서보다 먼저 읽을 것.** 계획서 「참고」에는 이 파일 링크만 남겼음.
|
||||
>
|
||||
> 든 것 — 일하는 법 열둘 · 좁은 폭 화면 깨짐 · vite 캐시 · 공용 이름 바꾸기 ·
|
||||
> [저장]과 횡단 재계산 · 공용 브라우저 조작 주의 셋 · 검증 함정 모음.
|
||||
|
||||
### ⚠⚠ 일하는 법 넷 — 2026-09-07 두 창이 겪은 것에서 (다음 세션이 먼저 읽을 것)
|
||||
|
||||
이 절의 값어치는 **같은 사고를 다시 겪지 않는 것**이다. 넷 다 하루에 여러 번 나왔다.
|
||||
|
||||
#### ㉠ 「숫자가 늘어난 것」은 좋아진 신호가 아니다
|
||||
|
||||
배분율 표 25건이 살아났으나 그게 **10 % 짜리**였고, 2단 표 165줄이 늘었으나 상당수가
|
||||
오독이었다. **늘어난 뒤에는 반드시 최대·중앙값과 손대조를 볼 것.**
|
||||
(그래서 산출 요약에 최소·중앙·최대를 붙였다 — 8-22 ③.)
|
||||
|
||||
#### ㉡ 넓은 규칙이 정상 값을 지운다 — 하루에 **여덟 번** (두 창에서 넷씩)
|
||||
|
||||
① 배합 검사가 `막자갈`을 오탐 ② 자원 필터가 정상 자원 70/745 를 지움 ③ 비율 지시를
|
||||
먼저 봐 강관동바리 소요량표가 참조로 넘어감 ④ 머리글 필터가 등급 글자(상·중·하)를 지움
|
||||
⑤ 「당」을 느슨히 잡아 `(무한궤도,0.7㎥)` 를 밑수로 읽음 ⑥ 식 칸 집계가 비고 설명문까지
|
||||
걸어 93 → 31 로 좁힘 ⑦ 「값 못 읽은 줄」 판정이 **값이 없는 게 당연한 머리 줄**까지 세어
|
||||
28 → 12 ⑧ 표 안 긁기가 비고의 「10㎡」를 그 표의 밑수로 물어 옴.
|
||||
|
||||
⑨ **`REFERENCE_MARKS` 의 「단 위」 하나가 공종 41건을 참조로 버림** — 그 안에
|
||||
**돌쌓기(장비) 13-4-5·13-4-2**(우리 매핑이 매일 쓰는 코드)가 있었다. 규모가 가장 컸다.
|
||||
⑩ 자간 공백 이름 세기가 「단 위」·「모 래」까지 걸어 101 → 20 으로 좁힘.
|
||||
|
||||
**⚠ ⑦ 은 이 병을 다섯 번 잡아 놓고 새로 하나 만든 것**이다. 원인은 **짝 시험을 그 자리에
|
||||
같이 안 둔 것**. 세기 전에 **「이건 세면 안 된다」 쪽 표본을 먼저 정해 둘 것.**
|
||||
|
||||
**⚠ ⑨ 의 진단이 이 절의 처방이다** — 그 표지의 원래 겨냥(품셈 1-2-2 「단위 표준」)은
|
||||
**chapter 규칙이 이미 거르고 있었다.** 즉 **넓기만 하고 얻는 것이 없었다.**
|
||||
**⇒ 넓은 규칙을 만나면 그것이 실제로 무엇을 **더** 잡는지 세어 볼 것 — 다른 규칙과 겹치면
|
||||
얻는 것 없이 잃기만 한다.** 규칙을 지울 근거는 「안 잡히는 것」이 아니라 **「이미 다른 규칙이
|
||||
잡고 있는 것」**이다.
|
||||
|
||||
**⚠ 정규화는 값을 살리지만 잘못된 줄도 함께 살린다** (2026-09-07 서브 창 실례).
|
||||
기종 이름 공백을 지워 빠졌던 장비 몫을 살렸더니, **같은 표의 버킷계수 `K` 를 소요량으로 읽어**
|
||||
시간당 사용료가 이중이 될 뻔했다. **살리는 규칙에도 짝 시험을 둘 것.**
|
||||
|
||||
**⇒ 규칙을 새로 둘 때마다 「걸려야 한다」와 「걸리면 안 된다」 짝 시험을 함께 둘 것.**
|
||||
**⇒ 값을 지우는 규칙은 좁게, 표시만 하는 깃발은 넓게.** 깃발은 넓어도 값이 안 사라진다
|
||||
(`partial_ratio` 를 몫 유무로 넓게 잡은 판단이 그것).
|
||||
|
||||
#### ㉢ 검사가 도는 줄 알았는데 안 돌고 있었다 — 하루에 네 번
|
||||
|
||||
① 가드를 만들어 두고 안 부름 ② 지문 장치가 원판 지문을 봐서 안 움직임
|
||||
③ `npx tsc` 가 **엉뚱한 패키지를 실행**(이 저장소는
|
||||
`node ./config/node_modules/typescript/bin/tsc --noEmit` 로 부를 것)
|
||||
④ 사방공에 **없는 `type_id`** 를 적어 영영 안 걸릴 뻔함.
|
||||
|
||||
**⇒ 새 검사·장치는 일부러 깨뜨려 보고 실제로 잡히는지 확인할 것.**
|
||||
**⇒ 이름으로 무언가를 알아보는 코드는 정본(레지스트리·마스터)과 대조하는 시험을 둘 것.**
|
||||
|
||||
#### ㉤ ⚠ **먼저 원문부터 뒤진다** — 「관측값인 줄 알았는데 법이었다」가 **여덟 번**
|
||||
|
||||
여덟 다 「실무 관측값을 쓰자」·「사용자에게 물어야 한다」로 시작했다가 **품셈 원문에 이미
|
||||
정해져 있는 것**을 찾았다. 뒤 여섯은 **2026-09-08 재조사에서 네 창이 동시에** 건졌다.
|
||||
|
||||
| 물음 | 처음 생각 | 원문 |
|
||||
|---|---|---|
|
||||
| 거푸집을 몇 번 쓰나 | 관측값 병용 | **품셈 1-7-1** — 「3회 … 옹벽, 파라펫트, 날개벽」 |
|
||||
| 철근 갈래가 무엇인가 | 사용자에게 물음 | **품셈 12-3 [주]①** — 「보통: 수문, 반중력식 옹벽 및 교대」 |
|
||||
| 덤프 t↔㎥ 환산 γt | 지식DB 에 없음 | **품셈 10-12-1 [주]②** — 토사 1.9 · 암 2.4 t/㎥ |
|
||||
| 공구손료 밑수·제잡비와의 관계 | 두 문서가 어긋남 | **건설품셈 1-2-6** — 「명시된 것은 그것을 계상, 명시 안 된 것만 별도」·「인력품의 3%까지」 |
|
||||
| 야면석 개수·중량 원단위 | 실무값밖에 없음 | **품셈 13-4** 「돌쌓기의 개수 및 중량의 표준」 — 0.575 / 0.88 / 1.10 |
|
||||
| 물빼기 구멍 간격·지름 | 사용자에게 물음 | **품셈 13-4-4 [주]④⑤⑦** — 2~3㎡ 마다 Ø3~6㎝ · 모르터 0.009 |
|
||||
| 돌쌓기 **전면 기울기** | 사용자에게 물음 | **품셈 13-4-4 「표준경사」 표** — 직고×메찰×성절토 20칸 |
|
||||
| 돌쌓기 **뒷길이** 규격 | 우리 표 네 칸이 전부 | **품셈 13-4-4 「뒷길이 표준」 표** — 일곱 규격, 높이별 |
|
||||
|
||||
**⇒ 「이건 관측값밖에 없다」·「이건 사용자가 정할 값이다」로 넘어가기 전에 품셈 원문
|
||||
본문(표가 아니라 **표 앞뒤 [주]·안내 줄**)을 먼저 볼 것.** 밑수도 같은 자리였다 —
|
||||
표 안이 아니라 표 바로 위 본문에 있었다.
|
||||
|
||||
**⇒ 「지식DB 전수 확인: 없음」은 원문까지 안 보면 절반만 본 것이다** (2026-09-08 넷으로 굳음).
|
||||
지식DB 는 **요약·후보 가이드**라 원문의 [주]·부속 표가 옮겨지지 않은 자리가 많다.
|
||||
**조사 보고에 「원문 어디까지 봤나」를 함께 적을 것** — 파일명·줄번호가 없으면 「안 본 것」으로 친다.
|
||||
|
||||
**⇒ 원문에 표가 있으면 물음이 「값」에서 「축」으로 바뀐다.** 전면 기울기가 그랬다 —
|
||||
높이·메찰·성절토를 우리가 이미 아니까 **값은 자동으로 정해지고**, 남은 물음은
|
||||
「품셈 축(높이·성절토)과 교본 축(시설 종류) 중 어느 쪽이냐」 하나였다.
|
||||
|
||||
**⇒ 판정할 수 있는 것을 사람에게 물으면 그것이 곧 미결이 된다.** 물음 목록이 길어질수록
|
||||
사용자가 답할 수 있는 물음이 묻힌다. 실제로 「철근 갈래 전체」를 물으려던 것이 원문 확인 뒤
|
||||
**「캔틸레버식 옹벽 하나」로 좁아졌다.**
|
||||
|
||||
#### ㉪ ⚠ 계약이 바뀌면 시험부터 의심할 것 — 이틀에 **열두 번**
|
||||
|
||||
**2026-09-08 네 번** (덧곱 · 구조물 한 줄 · 사방공 이름 · 뒷길이 접기)
|
||||
**2026-09-09 여덟 번** — 사용자 확정을 붙일 때마다 **옛 시험이 옛 계약을 지키고 있었음**:
|
||||
```
|
||||
버림 콘크리트 「없음」을 못 박은 둘 (구조물 한 줄 · 큰돌쌓기 성분 집합)
|
||||
층따기 「막힌다」를 못 박은 둘
|
||||
기울기 「늘 1:0.3」을 못 박은 셋 (실무 시트 대조 시험 포함)
|
||||
stone_supply 기본값을 「근거 없음」으로 잡은 둘 ← 근거를 적어 예외로 뺌
|
||||
```
|
||||
⇒ **사용자 확정이 오면 「어느 시험이 옛 계약을 지키고 있나」를 먼저 찾을 것.**
|
||||
⚠ **검사를 눅이지 말 것** — 근거를 문장으로 적어 **예외로 빼는 것**과, 검사 자체를 약하게 만드는 것은 다름.
|
||||
|
||||
**옛 시험이 틀린 계약을 못 박고 지키고 있던 자리.** 실물:
|
||||
```python
|
||||
def test_표에_없는_뒷길이는_덮는_칸으로_접을것():
|
||||
assert _back_length({"back_len_cm": "25"}) == 35
|
||||
assert _back_length({"back_len_cm": "75"}) == 60
|
||||
```
|
||||
**「접는 것」이 옳다고 시험이 지키고 있었음.** 그런데 **접을 까닭이 아예 없었음** — 품셈이
|
||||
일곱 규격(25·30·35·45·55·60·75)을 다 주는데 **우리 표가 네 칸만 들고 있었을 뿐**임
|
||||
(실무 라이브러리가 네 칸만 옮겨 적은 것을 그대로 받았음).
|
||||
|
||||
⇒ **시험이 있다는 것이 「맞다」는 뜻이 아님.** 값이 나오면 아무 시험도 안 잡음(㉣ 과 한 벌).
|
||||
|
||||
**곁들여 같은 자리에서 나온 것 둘**
|
||||
- ⚠ **원문이 「-」인 칸에 0 줄을 만들지 말 것** — 25㎝ 고임돌이 **0.0 으로 서고 있었음**.
|
||||
**0 은 「없음」과 구별이 안 되고** 받는 쪽이 「값이 0 인 자재」로 읽음. 줄을 빼고 사유를 낼 것.
|
||||
- ⚠ **고른 갈래의 빈 칸은 빈 칸으로 덮을 것** — `None` 이라고 안 덮었더니
|
||||
**「야면석 75㎝」에 종전 값(깬돌 0.25)이 조용히 섰음.**
|
||||
|
||||
#### ㉦ ⚠ 저장 제원의 키 이름과 엔진이 읽는 키 이름을 대조할 것
|
||||
|
||||
레지스트리는 `back_len_cm` 인데 엔진이 `stone_back_length_cm` 을 읽고 있어 **저장값이
|
||||
영영 안 닿고 늘 기본 45㎝ 로 돌았다.** 사용자가 뒷길이 75 를 골라도 45 계수가 붙었다.
|
||||
|
||||
**⇒ 오늘 잡은 「조용히 틀린 값」 가운데 사용자가 고른 값이 직접 버려진 첫 사례다.**
|
||||
앞의 것들은 우리가 원문을 잘못 읽은 것이었는데 이건 **사용자 입력이 무시된** 것이라
|
||||
성격이 더 나쁘다.
|
||||
|
||||
**⇒ 「이름으로 알아보는 코드는 정본과 대조하라」(㉢)의 데이터 키 판이다.** 대조를 시험으로
|
||||
둔다(`test_b08_option_keys.py`). **「칸이 아예 없는 것」과 「이름이 어긋난 것」은 다른 것**이라
|
||||
가려 적는다 — 앞은 「칸이 생기면 받는다」이고 뒤는 **버그**다.
|
||||
|
||||
#### ㉥ 폴더 튐은 「내 것이 없어진 것처럼 보이는」 착시까지 만든다
|
||||
|
||||
2026-09-07 두 창이 한 저장소에서 일하며 **폴더 튐이 네 번** 났다. 그중 하나는 상대 창이
|
||||
**남의 폴더 파일을 읽으며 「내 코드가 사라졌다」**고 판단한 것이었고, 자기 폴더에서는 멀쩡했다.
|
||||
|
||||
**⇒ 「내 변경이 사라졌다」고 느끼는 순간 먼저 현재 폴더부터 확인할 것.** 그 상태에서
|
||||
「복구」에 손대면 **없어지지 않은 것을 되살리려다 멀쩡한 것을 덮는** 진짜 사고가 된다.
|
||||
**⇒ push 전에 `git status` 로 남의 파일이 섞였는지 볼 것**(OWNERS 규칙 2). 남의 파일이
|
||||
보이면 **경로를 지정해 커밋**하고 자동 스크립트를 쓰지 말 것 — 폴백이 주워 담는다.
|
||||
|
||||
#### ㉣ 「값이 있기는 하니 안 보이는」 것이 가장 위험하다
|
||||
|
||||
씨앗뿜어붙이기 합계 68.8원 · 측구터파기가 인력 10 % 몫만 · 운반 표가 [확정] 뒤 0줄 ·
|
||||
부분 성공한 표가 엉뚱한 직종에 붙음. **부분 성공이 완전 실패보다 위험하다** —
|
||||
실패는 눈에 띄고 부분 성공은 안 띈다.
|
||||
|
||||
**⇒ 「0 이 아님」이 아니라 「크기가 말이 되나」로 잴 것.**
|
||||
|
||||
**⚠ 그 반대 짝도 있다 — 「값이 맞는데 이상해 보이는」 경우.** 반영률이 전부 100 % 라
|
||||
성토면다짐과 층따기가 같은 수로 나온 것을 「곱하기가 빠졌다」로 오진한 일이 있었다
|
||||
(두 계열이 같은 사면 면적에서 나오므로 원래 같다).
|
||||
**⇒ 「두 값이 같다」를 만나면 코드보다 설정·입력을 먼저 읽을 것.**
|
||||
|
||||
#### ㉧ 양쪽 근거가 다 있으면 **한쪽으로 몰아 보고하지 말 것** (2026-09-08)
|
||||
|
||||
교차검토에서 「제잡비 3% 의 밑수에 굴착기 조종원 노임이 들어가는가」가 나왔음.
|
||||
산림품셈 13-6-2 [주]③ 은 「**노무비의 합계액**」이라고만 하고 그게 무엇인지 정의를 안 함.
|
||||
근거가 양쪽에 다 있었음 —
|
||||
|
||||
| 쪽 | 근거 |
|
||||
|---|---|
|
||||
| 사람 품만 | 건설품셈 제8장이 같은 「제잡비율」을 **「직접노무비」로 못 박음**. 기계를 밑수에 넣을 때는 **「노무비, 기계손료 및 운전경비의 합」이라 따로 적음** |
|
||||
| 노무비 계정 전체 | 우리 원가 체계에서 「노무비」와 「직접노무비」는 **다른 말**이고, 중기 운전사 노임도 같은 노임표에서 옴 |
|
||||
|
||||
한쪽만 실어 보냈으면 받는 창이 그대로 받았을 것임. **양쪽을 나란히 놓자 조율 창이
|
||||
자기 앞 판정을 뒤집었음**(「사람 품만」으로 확정, 미결 ㉮ 로도 올림).
|
||||
|
||||
**⇒ 판단은 받는 쪽이 함. 반대 근거가 없으면 판단 자체가 안 됨.**
|
||||
**⇒ 문구가 갈리는 자리는 「같은 규정이 다른 데서는 어떻게 쓰였나」를 볼 것** — 이 건은
|
||||
**「넣을 땐 넣는다고 쓴다」**가 갈랐음.
|
||||
**⇒ 그리고 그 조항이 무엇을 사는 값인지 읽을 것** — 제잡비는 「콘크리트 버켓 손료·다짐기계
|
||||
손료」, 즉 **본 자원에 안 선 잔 기계**임. 그 표에 굴착기는 이미 본 자원으로 서 있으므로
|
||||
잔 기계를 **그 굴착기 조종원 노임에 비례**시키는 것은 뜻이 어긋남.
|
||||
|
||||
**⚠ 이 자리는 어떤 시험도 안 잡았음** — 값이 나오고 자원 넷이 원문과 정확히 맞았음.
|
||||
**손대조로 차액을 역산**해서야 밑수가 드러났음(㉣ 「크기가 말이 되나」의 한 단 아래).
|
||||
|
||||
**⇒ 그리고 「없다」를 확정하기 전에 「어디에 없나」를 볼 것** (2026-09-08 ㉗).
|
||||
품셈에 없다는 것이 **답이 없다는 뜻은 아님.** 같은 날 둘이 그랬음 —
|
||||
**지장목제거**는 품셈에 공종이 없을 뿐 **수량은 법령이 요구하고 실무가 같은 산식으로 세고 있었음**,
|
||||
**입목 본수**는 조사값이 아니라 **우리 저장소가 재료(원본 LAS·다중 리턴)를 이미 갖고 있었음**.
|
||||
**⇒ 찾을 자리는 셋임 — 품셈 · 다른 문서(법령·교본·실무) · 우리가 이미 가진 것.**
|
||||
|
||||
**⇒ 값이 움직였는데 코드 diff 에 없으면 **남의 창을 먼저 볼 것** (2026-09-08).
|
||||
**DB 는 원격 공용 서버 한 대**(`dsm.chemifactory.com`)라 네 환경이 같은 자료를 쓴다.
|
||||
어느 창이든 [확정]·[저장]을 부르면 나머지 셋의 값이 그 자리에서 바뀐다 — 코드를 하나도
|
||||
안 건드려도 바뀐다. 실제로 성토가 +1.14 % 움직였고 원인은 랩탑의 실화면 검증이었다.
|
||||
⚠ `cross_sections` 표에 **시각 칸이 없어** 언제·누가 바꿨는지 DB 로는 못 짚는다.
|
||||
짚는 순서는 ① 커밋 diff 로 코드 배제 ② 서버 로그로 내 창 배제 ③ DB 재계산으로 「후」 값 확인
|
||||
④ 파일 mtime 으로 시각 잡기. **되받기 전후로 `tmp/snapshot_b08.py` 를 떠 두면 대조가 된다.**
|
||||
|
||||
#### ㉩ ⚠ md 에 없으면 원본(pdf·xlsx)을 열어 볼 것 (2026-09-08)
|
||||
|
||||
**변환본이 원본을 못 따라가는 자리가 있음.** 「돌쌓기 표준도 그림이 저장소에 없다」고 판단해
|
||||
원본 확보를 요청하려 했는데, **PDF 원본(173쪽)이 이미 저장소에 있었음.** md 로 옮길 때
|
||||
**그림 폴더를 안 받아 둔 것**이었고, PDF 에서 그대로 뽑아 넣으니 끝났음.
|
||||
|
||||
- 오늘 같은 계열이 셋임 — **표가 코드를 못 따라감**(8-25 화면 칸이 낡음) ·
|
||||
**내 출력이 실물을 못 따라감**(옵션 넷만 골라 찍고 「빠졌다」로 오해) ·
|
||||
**변환본이 원본을 못 따라감**(이번).
|
||||
- ⇒ **「없다」를 말하기 전에 원본 형식(pdf·xlsx·las)을 한 번 볼 것.** 특히 **그림·서식·병합셀**은
|
||||
텍스트 변환에서 빠지기 쉬움.
|
||||
|
||||
#### ㉨ ⚠ 「사유」 칸에는 왜 막혔나만 — 설명은 비고로 (2026-09-08, 하루에 **세 번**)
|
||||
|
||||
**받는 쪽은 사유 칸을 「상태」로 읽는다.** 설명을 거기 적으면 그 줄이 통째로 막힌 것이 된다.
|
||||
|
||||
- **겹침 설명을 `blocked_reason` 에 넣었더니** B09 가 「막힌 줄」로 읽어 금액을 안 붙였음.
|
||||
- **`blocked_kind: None`** — 「다른 표에서 이미 섬」은 막힌 것이 아니라 **여기서 세면 안 되는 줄**임.
|
||||
막힘으로 보내면 받는 쪽이 「만들어야 할 것」에 얹어 **결국 이중계상으로 감.**
|
||||
- **사유를 뭉뚱그리면 할 일이 갈리지 않음** — 「일위대가 없음」 하나로는
|
||||
「아직 안 만든 것」과 「성분이 빠져 못 세운 것」이 구분되지 않음. 앞엣것은 우리가 만들면 되고
|
||||
뒤엣것은 **참조 대상을 이어야** 풀림. 사유를 갈라 적으니 다음 일감이 바로 보였음.
|
||||
|
||||
**⇒ 사유 칸은 「왜 막혔나」만. 설명·계산 경위는 비고로. 그리고 사유를 갈라 적을 것.**
|
||||
|
||||
#### ㉫ ⚠⚠ **법이 두 동작을 묶은 자리 — 앞 동작만 세고 있지 않은지** (2026-09-09, 하루에 **두 번**)
|
||||
|
||||
**막힘 목록에도 없고 어떤 검사에도 안 걸리는 자리임.** 줄이 서 있고 값도 맞으니
|
||||
**「없는 줄」을 아무도 못 봄.** 오늘 둘 다 별표2 원문을 읽다가 나왔음.
|
||||
|
||||
```
|
||||
표토 별표2 「표토는 전량 **제거한 후** … 최고 홍수위보다 높은 장소로 **운반하고 쌓아두어야**」
|
||||
우리 — 표토제거(9-15) 한 줄만. **운반·적치가 없었음**
|
||||
|
||||
뿌리 품셈 9-20 가. 「벌개·제근 → 뿌리다듬기 → **적재** → **운반**」 (시공 과정 표준)
|
||||
별표2 「입목…과 **그 뿌리**, 표토는 전량 제거한 후 … 운반하고 쌓아두어야」
|
||||
우리 — 「제근·뿌리다듬기」 한 줄뿐. **적재·운반이 없었음**
|
||||
```
|
||||
|
||||
**⇒ 어떻게 찾나** — 원문에서 **동사가 둘 이상 이어진 문장**을 볼 것.
|
||||
`제거한 **후** … 운반하고 쌓아두어야` · `~하고 ~하여야` · `→ 로 이어진 과정 표준`.
|
||||
**앞 동작에 우리 줄이 있으면, 뒤 동작에도 줄이 있어야 함.**
|
||||
|
||||
**⇒ 찾은 뒤 지킬 것 셋**
|
||||
- **물량을 다시 세지 말 것** — 뒤 동작의 밑수는 **앞 동작의 물량 그대로**임(표토 운반 = 표토 제거량).
|
||||
다시 세면 이중계상.
|
||||
- **앞이 안 서면 뒤도 안 섬** — 밑수가 그 줄이므로 함께 막히게 할 것.
|
||||
- **법 문구를 사유에 실을 것** — 화면에서 **왜 이 줄을 세는지**가 보여야 함.
|
||||
|
||||
⚠ **품셈에 그 공종이 없을 수 있음**(뿌리 운반이 그랬음 — 10장 어디에 붙는지 원문이 안 말함).
|
||||
그때도 **줄은 세우고 코드만 비울 것.** 지어내지 말고 사유로. → 아래 ㉬.
|
||||
|
||||
#### ㉬ ⚠ **「수량은 서는데 품셈에 공종이 없는 줄」은 한 물음임 — 모아서 한 번에 물을 것** (2026-09-09)
|
||||
|
||||
따로 만나면 매번 새 물음처럼 보이나 **답은 하나임.**
|
||||
|
||||
```
|
||||
뿌리 운반 9-20 가. 가 과정으로 두었는데 붙을 공종이 10장에 없음
|
||||
도수로·절토사면 배수로 「수로」 전수 검색에 셋뿐 — 그 셋에 없음
|
||||
지장목제거 수량은 서는데 단가 공종이 없음
|
||||
부대시설 넷 국가지점번호판·안내판·차단기·수방자재 — 개소를 넣어도 단가 없음
|
||||
```
|
||||
|
||||
**⇒ 선례가 답을 정해 둠 — 확정 8-4** 「편책은 바자얽기(5-15)를 **빌려 쓰지 않음. 일위대가를 따로
|
||||
만들어 연결**」. 즉 **품셈에 없으면 우리가 만들어 잇는다**가 사용자 방침임.
|
||||
⚠ **빌려 쓸 후보가 있어도 답이 「만들라」일 수 있음** — 편책이 그랬음. **후보 유무로 미리 가르지 말 것.**
|
||||
|
||||
**⇒ 물을 때는 한 표로** — `공종명 | 수량은 서나 | 빌려 쓸 후보 | 없으면 새로 만들 성분`.
|
||||
넷을 따로 물으면 사용자가 네 번 답해야 하고, 그 사이 창마다 다르게 처리함.
|
||||
|
||||
#### ㉭ ⚠⚠ **대조표를 인용하기 전에 「그것이 언제 것인지」부터 볼 것** (2026-09-09, 한 건이 **세 번** 뒤집힘)
|
||||
|
||||
**오늘 가장 비싼 헛걸음임.** 구조물 터파기 한 줄을 두고 조율 창·데스크탑 메인·랩탑 보조가
|
||||
**세 바퀴**를 돌았고, **뿌리는 「오전에 뜬 대조표를 오후에 그대로 인용한 것」** 하나였음.
|
||||
|
||||
```
|
||||
대조표(오전) 「우리 2.30」 = 2.0 × (0.95 + 0.2) ← 옛 두께식 · 기초 몫 없던 때
|
||||
그 사이 바뀐 것 확정 ② 두께 → 뒷길이 기반 (`8141eb34`)
|
||||
확정 3차 → 기초유/기초버림 축, 기초 몫 0.45·0.07 이 붙음 (`b4ae32d1`·`759343e3`)
|
||||
실제(오후) H=2.0 · 두께 0.83 · 기초유 → **2.510** = 실무 정본 2.51 과 자릿수까지 같음
|
||||
```
|
||||
|
||||
**⇒ 그 낡은 2.30 하나가 만든 잘못된 결론 셋**
|
||||
```
|
||||
1. 「우리가 6.6배 과다 — 955만원을 줄일까요」 ← 사용자에게 갈 뻔한 틀린 물음
|
||||
2. 「기초 몫 0.45 를 빠뜨렸다」 ← 이미 들어 있었음
|
||||
3. 「화면 0.5 와 수량 H 가 다른 말을 한다」 ← 셋이 같은 말을 하고 있었음
|
||||
```
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **대조표·실측표를 인용할 때 날짜와 커밋을 함께 적을 것.** 「오늘」은 날짜가 아님 — 하루에 두 번 바뀜.
|
||||
- **금액이 걸린 인용은 그 자리에서 다시 재 볼 것.** 재는 데 몇 분, 헛걸음은 세 창 몇 시간.
|
||||
- **표를 만들 때 머리에 「잰 시각 + 그때 커밋」을 박아 둘 것.** 표가 스스로 낡았다고 말하게.
|
||||
- ⚠ **이 병은 「값이 틀린 것」이 아니라 「값이 옛것인 것」이라 어떤 검사에도 안 걸림.**
|
||||
시험도 통과하고 숫자도 맞음 — **그때는** 맞았기 때문.
|
||||
|
||||
**⇒ 곁들여 나온 것** — 그럴듯하게 맞아떨어지는 설명이 **같은 건에서 두 번** 틀렸음
|
||||
(`0.7 × 0.5 = 0.35` · 「H × 평균두께」 모양 일치). **딱 맞아떨어질수록 곱셈을 해 볼 것.** → ㉧
|
||||
|
||||
**⇒ 같은 집안 하나 더 — 「잘라서 인용한 자리」를 「없는 자리」로 읽지 말 것** (2026-09-09).
|
||||
규준틀 사유를 **44자로 잘라** 보고하다 **「아무 표시가 없다」**로 냈음. 실제로는 그 뒤에
|
||||
「재료량은 품셈 11-2 [주]④ 「설계수량에 따른다」라 미확보」가 **적혀 있었음.**
|
||||
⇒ **길이를 줄여 인용할 때는 「…(잘림)」을 붙일 것.** 안 붙이면 **자기가 자른 자리를 사실로 읽음.**
|
||||
⇒ 그리고 **「아무도 말 안 함」과 「이쪽만 말 안 함」은 다름** — 보내는 쪽이 이미 말하고 있을 수 있음.
|
||||
|
||||
#### ㉮ ⚠⚠ **시험이 「지금 동작」을 베껴 적으면 버그를 잠근다** (2026-09-09, 실물 한 건)
|
||||
|
||||
**제근 단가가 두 배로 서 있던 것을 시험이 「맞다」고 지키고 있었음.**
|
||||
```
|
||||
있던 시험 「제근은 보통인부 줄이 **둘**이다」
|
||||
실제 그 둘이 곧 이중계상 — 품셈은 「0.2㎥를 쓰면 이 값 **또는** 0.7㎥를 쓰면 저 값」
|
||||
⇒ 시험이 버그를 계약으로 못 박아, 고치려 하면 **시험이 먼저 막아섬**
|
||||
```
|
||||
|
||||
**⇒ 어떻게 생기나** — 시험을 쓸 때 **원문을 안 보고 지금 출력을 베껴** 적으면 이렇게 됨.
|
||||
「돌려 보니 둘이 나오네」 → 「둘이어야 한다」. 그 순간 **틀린 값이 정본이 됨.**
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **시험의 기대값은 「돌려 본 결과」가 아니라 「원문」에서 올 것.** 기대값 옆에 **근거를 한 줄** 적을 것
|
||||
(품셈 절 번호·실무 시트 셀 좌표). 근거를 못 적겠으면 **그 시험은 아직 쓰면 안 되는 것**임.
|
||||
- **개수를 세는 시험**(「줄이 둘이다」·「성분이 N개다」)이 가장 위험함 — 값이 아니라 **모양**을 잠그는데
|
||||
모양이 틀렸을 때 그것을 알려 줄 것이 없음.
|
||||
- **뒤집을 때는 「왜 뒤집혔는지」를 그 시험 설명에 남길 것.** 안 남기면 다음 사람이 되돌림.
|
||||
|
||||
⚠ **㉪(계약이 바뀌면 시험부터 의심할 것)과 다른 병임.** ㉪는 **시험이 낡은 것**이고,
|
||||
이것은 **시험이 처음부터 틀린 것**임. 낡은 시험은 계약이 바뀔 때 깨져서 드러나는데,
|
||||
**처음부터 틀린 시험은 영영 안 깨짐** — 지키는 대상이 실제로 그렇게 돌고 있으므로.
|
||||
|
||||
#### ㉯ ⚠ **검산은 「어디까지 쟀는지」를 함께 적을 것** (2026-09-09)
|
||||
|
||||
뒤채움 두께가 품셈 범위 안에 드는지 재면서 **H 4.0m 까지만 재고 「세 자리 다 범위 안」**으로 냈음.
|
||||
그물을 시험으로 옮겨 **H 6.0 까지** 늘리자 **4.0~4.5 사이에서 범위 밖으로 나감**이 바로 걸렸음.
|
||||
```
|
||||
우리 두께 직고에 0.30/m 로 **계속** 붙음 (확정 ② 실무 정본 식)
|
||||
품셈 표 하부 두께가 **1.40m 에서 멈춤** ([주]⑨ 직고 7m 칸 상한)
|
||||
⇒ 높이 축의 기울기가 다름. 어느 쪽도 상대를 틀렸다고 못 함 — **갈리는 자리를 값으로 남길 것**
|
||||
```
|
||||
**⇒ 「범위 안이다」는 잰 범위 안에서만 참임.** 시험 이름·주석에 **잰 구간**을 박을 것.
|
||||
**⇒ 그리고 범위 검산은 등호로 바꾸지 말 것** — 밖으로 나가면 「틀림」이 아니라 **「봐야 함」**임.
|
||||
우리 식이 맞을 수도 있음.
|
||||
|
||||
#### ㉰ ⚠⚠ **표를 읽을 때 칸을 이어 붙이면 「원문에 없는 값」이 만들어진다** (2026-09-09)
|
||||
|
||||
**앞 칸의 숫자 + 뒤 칸의 단위가 붙어, 원문 어디에도 없는 밑수가 섰음.**
|
||||
```
|
||||
품셈 13-2-4 … 0.28 | **0.36** / **㎥당** | 0.60 … ⇒ 「0.36㎥당」 (0.36 은 뒷길이 60 의 ㎡당 값)
|
||||
품셈 5-19-2 15 | **30**(표토두께 ㎝) / **㎥ 당** | … ⇒ 「30㎥당」 (30 은 두께)
|
||||
```
|
||||
⚠ **잘못된 값이 아니라 「없는 값」임.** 그리고 **두 숫자 다 그 표 안에 실재**해서
|
||||
**그럴듯함 — 어떤 검사에도 안 걸림.**
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **표를 훑을 때 「칸 하나 안에서만」 볼 것.** 셀을 죽 이어 붙여 정규식을 걸면 이 병이 남.
|
||||
「100㎥당」처럼 **한 칸에 다 든 것**만 인정.
|
||||
- **밑수가 늘어난 것은 좋아진 신호가 아님** — 이 건에서 밑수 확보가 **182 → 180 으로 줄어든 것이
|
||||
개선**이었음(없는 값 둘이 빠지고 넷이 「미확보」로 정직해짐). → ㉠
|
||||
- **잡은 실례를 그대로 재는 시험을 남길 것.** 그래야 다음에 정규식을 넓힐 때 걸림.
|
||||
|
||||
⚠ **같은 날 잡은 「인」 오독과 다른 병임** — 「인」은 **잘못된 단위**(원문에 있는 글자를 잘못 읽음),
|
||||
이것은 **없는 숫자**(원문에 없는 조합을 만들어 냄). **고치는 자리도 다름.**
|
||||
|
||||
#### ㉱ ⚠⚠ **코드 추적으로만 낸 「원인」은 자료를 재 보기 전까지 가설임** (2026-09-09, 하루를 돌아감)
|
||||
|
||||
계획서 3-14 에 **「원인 찾음(코드 추적)」**이라 적어 두었는데 **자료를 안 재 봤고, 재 보니 틀렸음.**
|
||||
```
|
||||
적어 둔 원인 「관을 나중에 놓으면 그 측점이 안 생긴다」 — 측점 생성 호출부를 따라가 낸 결론
|
||||
실제 측점 440.0 · 620.0 · 720.0 · 900.0 이 **이미 있었고 구조물 이름표까지 달고 있었음**
|
||||
진짜 원인 관 자리와 측점 자리가 **최대 0.5m 어긋나는 것이 설계**(정수 미터 격자로 스냅)인데
|
||||
**붙이는 쪽이 0.02m 로만 봐서** 안 붙었음
|
||||
```
|
||||
|
||||
**⇒ 그 틀린 원인이 하루 동안 판단 셋을 끌고 다녔음**
|
||||
```
|
||||
1. 「B06 자리」로 넘겨 B08·B09 가 손을 못 댐
|
||||
2. 조율 창이 **「0.05 와 0.5 를 맞추지 말라」**고 지시 — **좁은 쪽이 스냅을 모르는 것**이 진짜 문제였음
|
||||
3. [측점 만들기] 단추를 **주 해법**으로 잡음 (실제로는 0개짜리 예외 처리였음)
|
||||
```
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **「원인」이라 적을 때 어떻게 알았는지를 함께 적을 것** — `코드 추적` / `실측` / `원문`.
|
||||
**추적만으로 낸 것은 「가설」이라 쓸 것.**
|
||||
- **가설을 남에게 넘기기 전에 한 번 잴 것.** 이 건은 **「그 측점이 정말 없는가」를 한 번 조회**하면
|
||||
끝날 일이었음.
|
||||
- **남이 넘겨준 진단을 그대로 이어받아 지시하지 말 것** — 조율 창이 그 위에 지시 둘을 얹었고
|
||||
**둘 다 틀렸음.**
|
||||
|
||||
⚠ **㉭(대조표가 낡음)과 다른 병임.** 그건 **한때 맞았던 값**이 낡은 것이고,
|
||||
이것은 **처음부터 안 재 본 것**임. 낡은 값은 날짜로 걸러지지만 **안 잰 것은 걸러지지 않음.**
|
||||
|
||||
#### ㉲ ⚠⚠ **「막혔다」고 말하기 전에 차단 표시를 볼 것 — 화면 문구는 근거가 아님** (2026-09-09, 하루에 **두 번**)
|
||||
|
||||
**주의 문구를 차단으로 읽어 두 번 헛돌았음.**
|
||||
```
|
||||
아침 겹침 설명을 `blocked_reason` 에 넣었더니 **B09 가 「막힌 줄」로 읽어 금액을 안 붙였음**
|
||||
⇒ 비고 칸으로 옮기고 막힘은 **진짜 막힐 때만** 넣게 고침
|
||||
밤 「관종을 안 정해 기본값(파형강관)으로 섰습니다」를 **조율 창과 B09 창이 「막는 사유」로 읽음**
|
||||
실제 `blocked_kind` 는 **공종코드를 못 찾을 때만** 섬(`if code is None`).
|
||||
기본값 파형강관은 코드가 있어 **줄도 서고 금액도 섬** — 그것은 `kind_note`(주의)였음
|
||||
⇒ 「칸을 만들라」는 지시가 두 창에 나갔고 **칸은 이미 있었음**
|
||||
```
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **차단 여부의 근거는 `blocked_kind` 하나임.** 사유 글자·주의 문구·「미정」이라는 말은 근거가 아님.
|
||||
- **막힘을 보고할 때 `blocked_kind` 값을 함께 적을 것.** 「막혔음」만 적으면 받는 쪽이 문구를 읽음.
|
||||
- **화면에서도 「주의」와 「차단」을 갈라 보일 것** — 같은 자리에 뜨면 사람도 코드도 헷갈림.
|
||||
- ⚠ **남이 옮겨 준 사유 문구를 그대로 판단 근거로 쓰지 말 것.** 두 번 다 **문구를 옮긴 창과 받은 창이
|
||||
함께 틀렸음.** → ㉱
|
||||
|
||||
⚠ **㉨(「사유」 칸에는 왜 막혔나만)의 반대쪽 얼굴임.** 그건 **쓰는 쪽** 규칙이고 이것은 **읽는 쪽** 규칙임.
|
||||
사유 칸에 설명을 섞으면 읽는 쪽이 막힘으로 읽고, 읽는 쪽이 표시를 안 보면 안 막힌 것을 막혔다고 함.
|
||||
|
||||
#### ㉳ ⭐⭐ **예외는 「git 밖 시험 목록」이 아니라 「정본 스키마」에 적을 것** (2026-09-09)
|
||||
|
||||
**칸은 건너가는데 까닭은 안 건너감** — 그래서 **같은 판인데 창마다 시험 결과가 다름.**
|
||||
```
|
||||
정본 등록부 B05_Profile_Structure_Types.json git **안** ⇒ 다른 창에 감
|
||||
정책 시험 tmp/tests/…_registry_policy.py git **밖**(CLAUDE.md 4장) ⇒ **안 감**
|
||||
⇒ 칸을 열고 시험의 예외 목록에 까닭을 적으면 **칸만 건너가고 까닭은 안 건너감**
|
||||
⇒ 연 창은 통과, 받은 창은 실패. **둘 다 맞는 상태인데 서로 틀렸다고 함**
|
||||
```
|
||||
|
||||
**⇒ 고침 — 「예외」를 목록에서 스키마로 옮길 것.** 실제로 세운 두 갈래:
|
||||
```
|
||||
empty_means 「비워 두는 것이 뜻인 칸」 — 비면 계산 쪽이 기준값으로 돌고 그 사실이 화면에 뜸.
|
||||
값을 넣으면 그 값이 이김. **까닭 한 줄을 값 옆에** 적음
|
||||
default_basis 기본값이 **도메인 확정값이 아닐 때** 그 뜻(「다단 없음」·「안 더함」).
|
||||
법정·확정 수치면 **비워 둠** — 비어 있는 것이 「확정값」이라는 뜻
|
||||
```
|
||||
⇒ 시험은 **목록을 읽지 않고 그 칸을 읽음.** 까닭이 한 낱말이면 잡는 시험도 함께 둘 것
|
||||
(빈 문구로 통과하는 것을 막음).
|
||||
|
||||
**⇒ 규칙 한 줄 — 등록부에 칸을 열 때 예외 목록을 고치지 말고 까닭을 값 옆에 적을 것.**
|
||||
|
||||
⚠ **다만 구조를 고쳐도 「옮기는 일」은 남음** (2026-09-09 그날 저녁 실측).
|
||||
스키마를 고치면 **그 스키마를 읽는 시험 파일도 바뀌는데**, 그 파일이 `tmp/tests`(git 밖)라
|
||||
**여전히 안 건너감.** 실제로 한 창이 스키마를 고친 뒤 다른 창이 **옛 시험으로 2건 실패**했음.
|
||||
```
|
||||
⇒ 스키마를 고쳤으면 **그 시험 파일 내용을 메시지로 보내** 각 창이 자기 폴더에 놓을 것
|
||||
⚠ 남의 폴더에 직접 쓰지 말 것 — 보내고, 받는 쪽이 놓음
|
||||
⇒ 「내 창은 통과인데 저쪽은 실패」가 보이면 먼저 **시험 파일 판을 맞출 것**
|
||||
```
|
||||
|
||||
흙깎기 장비 이름이 표에 없고 **[주]① 이 지정**
|
||||
기초잡석 운반 표에 「덤프트럭(15ton)」 줄만 있고 시간이 빔 — **10-12 가 거리로 냄**(같은 15ton)
|
||||
커플링밴드 표 「EA | (빈칸) | 필요시적용」 — **[주]① 「1EA/6m 이며 필요시 별도 산정」**
|
||||
```
|
||||
**⇒ 빈 칸을 만나면 그 표의 [주]·비고·앞뒤 절 참조를 먼저 훑을 것.** 셋 다 **표만 보면 「없음」**이었고
|
||||
**[주]를 보면 있었음.**
|
||||
|
||||
**⇒ 사유 문구를 갈라 쓸 것 — 다음 사람이 볼 때 뜻이 아주 다름**
|
||||
```
|
||||
「원문에 값이 없음」 ⇒ 영영 막힌 것으로 읽힘. **지어내지 말라는 뜻**
|
||||
「[주]에 있음 — 조건이 붙음」 ⇒ 조건만 정하면 서는 것
|
||||
「다른 절이 냄 — 거리/규격 필요」 ⇒ 그 값이 오면 저절로 섬
|
||||
```
|
||||
⚠ 셋을 뭉쳐 「값 없음」으로 적으면 **사용자가 없는 것을 정하러 감.** → ㉲
|
||||
|
||||
⚠ **㉢(밑수 단위가 표 밖)과 같은 집안이나 다른 것임** — 그건 **단위**가 표 밖에 있는 것이고
|
||||
이것은 **값 자체**가 표 밖에 있는 것임. 둘 다 「표만 읽는 코드」가 못 잡음.
|
||||
|
||||
#### ㉵ ⚠⚠ **`0` 과 「없음」을 같은 것으로 두지 말 것** (2026-09-09, 하루에 **세 번**)
|
||||
|
||||
```
|
||||
표토 대상 면적이 **0 ㎡** 인데 **0 ㎥ 를 「값 있음」으로** 냄
|
||||
⇒ 받는 쪽이 「표토가 없는 노선」으로 읽음. 두께 미입력은 막고 있었는데 면적 0 만 통과
|
||||
사토장 구조물을 지웠는데 **설계 결과가 0.0 을 늘 실어** 횡단도·면적표에 계속 남아 있었음
|
||||
⇒ 「칸이 있는 것」과 「값이 있는 것」이 달랐음
|
||||
말풍선 폭을 이분법으로 찾아 남은 **0.0063㎥** 에 「⚠ 못 담음」 경고가 늘 떴음
|
||||
⇒ 표시 자릿수에서 안 보이는 몫을 「남았다」고 말했음
|
||||
```
|
||||
|
||||
**⇒ 세 가지 다 「0 이라는 값」이 거짓말을 한 것임.** 값이 없어서가 아니라 **있어서** 안 걸림.
|
||||
|
||||
**⇒ 지킬 것**
|
||||
- **「없음」은 `None`(빈 값)으로 낼 것.** `0` 은 **「재 보니 0 이었다」**는 뜻으로만 쓸 것.
|
||||
- **얹는 코드를 쓰면 지우는 길도 함께 쓸 것.** 사토장이 그 자리였음 — 얹기만 하고 안 지워
|
||||
**구조물을 없앤 뒤에도 값이 남았음.** 시험은 「놓았을 때」만 보고 「지웠을 때」를 안 봤음.
|
||||
- **경고는 사람이 보는 자릿수로 판정할 것.** 0.0063 처럼 화면에 안 보이는 몫을 경고하면
|
||||
**경고 전체가 믿을 것이 못 됨.**
|
||||
|
||||
⚠ **셋 다 시험은 통과했고 화면에서 잡혔음.** 「있다」와 「돈다」의 그 자리임 — **지우는 길·빈 자료·
|
||||
표시 자릿수는 시험이 잘 안 훑는 세 곳**임.
|
||||
|
||||
### ⚠ 좁은 폭에서 화면이 깨지면 볼 자리 셋 (2026-09-09 B03 업로드 현황에서 셋이 한꺼번에)
|
||||
|
||||
셋 다 **넓은 화면에서는 안 보이다가 창을 줄이면 터짐.** 다른 페이지도 같은 짜임이면 같이 깨짐.
|
||||
|
||||
- **`minmax(260px, 1fr)` 는 「260보다 좁아질 수 없다」는 뜻** — 컨테이너가 그보다 좁아지면
|
||||
**격자가 컨테이너를 뚫음.** ⇒ `minmax(min(260px, 100%), 1fr)` 로 적을 것.
|
||||
- **접는 분기가 아예 없는 2열** — `repeat(2, minmax(0,1fr))` 는 좁아져도 2열을 고집해
|
||||
열마다 카드 하나도 못 담음. ⇒ `repeat(auto-fit, minmax(min(360px,100%), 1fr))`.
|
||||
- **flex 자식의 `min-width: 0` 누락** — 기본값이 `auto` 라 **내용보다 안 줄어듦.**
|
||||
안쪽 격자가 넘치면 **페이지 몸통이 통째로 가로로 밀림.**
|
||||
|
||||
**판정은 눈이 아니라 숫자로** — 폭을 여러 개(1400·1000·760·620·480) 돌며
|
||||
`scrollWidth − clientWidth` 가 0 인지 볼 것. 창이 눈에 안 따라와도 **뷰포트 폭만 바꾸면
|
||||
레이아웃은 다시 계산되므로 측정은 됨.**
|
||||
|
||||
**⚠ 같은 짜임이 다른 화면에도 있음 — 훑은 목록** (2026-09-09, 아직 안 고침)
|
||||
|
||||
```
|
||||
㉡ 접는 분기 없는 고정 2열 — 아홉 곳 (B03 에서 깨진 것과 같은 짜임)
|
||||
B01_Dashboard_UI_Style.css:22 · :134 · :166
|
||||
B02_ProjRegister_UI_Style.css:18 ← 등록 폼, 사용자가 자주 봄
|
||||
B05_Profile_UI_Style.css:303 · :338 · :436
|
||||
B05_Profile_UI_Style_Structures.css:157
|
||||
B06_Section_UI_Style.css:286
|
||||
B06_Section_UI_Style_Cross_Controls.css:314
|
||||
B07_DesignDetail_UI_Style.css:55 (1fr 1fr)
|
||||
⚠ B05·B06 은 좌측 패널 안이라 폭이 원래 좁음 — B03 보다 먼저 깨질 수 있음
|
||||
㉢ min-width:0 누락 — 62 곳. 전부가 문제는 아니고 ㉡ 와 겹치는 자리부터 볼 것
|
||||
㉠ minmax(고정px) — B01_Dashboard_UI_Style.css:231 한 곳뿐(150px 라 위험 낮음)
|
||||
㉣ @media 가 아예 없는 화면 — B05_* · B06_* · B08_* · B09_*
|
||||
⇒ 좁은 폭 대응을 안 한 화면이 넷임
|
||||
```
|
||||
**고칠 순서 제안** — ① B02 등록 폼 ② B05·B06 좌측 패널 2열 ③ 나머지.
|
||||
|
||||
### ⚠ **`reload()` 만으로는 vite 가 옛 모듈을 물고 있을 수 있음 — 캐시부터 비울 것** (2026-09-09)
|
||||
|
||||
지침이 「페이지·공용 코드는 `page.reload()` 한 번(vite dev)」이라 적고 있으나 **그것으로 안 걷힌 자리**가 나왔음. **한 증상을 두고 세 창이 차례로 헛짚었음.**
|
||||
```
|
||||
증상 벽 터파기가 화면에 0건 (관 터파기는 뜸)
|
||||
헛짚음 ① 「그리기 가드가 먹는다」 ② 「초안 경로에서만 그려진다」 ③ 「백엔드 재시작을 안 했다」
|
||||
실측 백엔드 stale:false · API 상세에 revetment.foundation="기초유" **이미 옴** · 화면만 옛것
|
||||
답 CDP Network.clearBrowserCache + sessionStorage.clear + reload → **바로 뜸**
|
||||
```
|
||||
⇒ **TS 를 고쳤는데 화면이 옛것 같으면 「캐시부터」.** `page.goto`·`reload` 만으로는 부족할 수 있음.
|
||||
⚠ **브라우저 창은 재시작하지 말 것** — 캐시 비우기로 끝남(지침 4장 그대로).
|
||||
|
||||
### ⚠ 공용 이름을 **갈거나 지울 때는 창들에 먼저 알릴 것** (2026-09-09, 하루에 세 번)
|
||||
|
||||
**값을 한 벌로 두어도 이름이 바뀌면 깨진다.** 「공용 상수를 직접 읽으니 저절로 따라온다」가
|
||||
**반만 맞는 말**임 — 값은 따라오고 **이름은 안 따라옴.**
|
||||
```
|
||||
① BLANK_DRAWINGS·BLANK_LABELS 를 걷어냄 → 남의 tmp/tests 가 수집 단계에서 터져 **267건이 통째로 멈춤**
|
||||
② tmp/tests 는 git 밖 → 병합에 안 실림. **지운 쪽이 알리는 것 말고 길이 없음**
|
||||
③ 두께 상수 이름 셋을 갈이 → 남의 표준도 그림이 KeyError 로 **죽음**
|
||||
thickness_base_m·_top_coeff·_bottom_coeff → _top_add_m·_slope_per_m·_height_base_m
|
||||
```
|
||||
⇒ **새로 넣는 것은 안전함. 갈거나 지우는 것만 알리면 됨.**
|
||||
⇒ 셋 다 **실화면·실행에서만** 드러났음 — 코드만 봐서는 안 잡힘.
|
||||
|
||||
### ⚠ **[저장]은 횡단 설계를 다시 계산하지 않음 — 새 키를 넣었으면 「재생성」을 돌릴 것** (2026-09-09)
|
||||
|
||||
**설계 엔진에 키를 새로 넣었는데 저장분에 안 들어오는 자리.** 하루를 「값이 0 이다」로 보냄.
|
||||
```
|
||||
[저장] 편집을 적용하고 Node 로 **구조물 면적·유토곡선만** 다시 냄(_recompute_stored_designs)
|
||||
⇒ 저장분이 **옛 34키 그대로** — 새 키(bench_cut_length_m 등)가 안 들어감
|
||||
재생성 파이썬 설계 엔진이 **전 측점을 새로 냄** ⇒ design 키가 **38개**로 늘어남
|
||||
```
|
||||
**세 창에 돌린 절차 (순서를 지킬 것)**
|
||||
```
|
||||
① git sync
|
||||
② npm run build:server-calc ⚠ 번들은 git 밖 — 창마다 각자 해야 함
|
||||
③ 백엔드 재시작 ⚠ @lru_cache 가 옛 등록부·config 를 물고 있음
|
||||
④ B06 열고 카드가 다 뜰 때까지 기다림
|
||||
⑤ POST /api/projects/{pid}/sections/{routeId}/regenerate
|
||||
body {"cross_half_width_m": <지금 쓰는 반폭>}
|
||||
(routeId·반폭은 GET /api/projects/{pid}/sections/context 의 route_id·defaults)
|
||||
⑥ 확인: GET …/sections/{routeId}/detail → **design 키 38개**
|
||||
```
|
||||
⚠ **[확정]은 필요 없음** — 저장·재생성만으로 정본이 섬.
|
||||
⚠ **추정치를 그대로 쓰지 말 것** — 같은 노선에서 추정 12,931㎡ → 실측 **13,699.7㎡ (+6%)** 로 갈렸음.
|
||||
|
||||
### ⚠ 창 크기를 바꿀 때 `page.set_viewport_size` 를 부르지 말 것 — `resize(w, h)` (2026-09-09)
|
||||
|
||||
**그 한 줄이 화면을 그 크기에 박아** 사용자가 창을 끌어도 안 따라옴. `Emulation.
|
||||
clearDeviceMetricsOverride` 로도 안 풀림(Playwright 가 다시 걺) — **드라이버 재시작 말고는
|
||||
푸는 길이 없음.** 데스크탑이 그렇게 하루를 그 상태로 보냈고, **랩탑이 멀쩡했던 것은
|
||||
그 호출을 안 썼기 때문**이지 배율·인자 차이가 아니었음.
|
||||
|
||||
- 드라이버에 **`resize(w, h)`** 를 넣었음 — CDP `Browser.setWindowBounds` 로 **창 자체**를 바꿔
|
||||
화면이 따라옴. `page.set_viewport_size` 는 이제 **막힘 문구와 함께 거부**됨.
|
||||
- ⚠ **`.claude/` 는 git 밖이라 커밋으로 안 건너감** — 시놀로지 동기화에 기대거나 창마다 붙여야 함.
|
||||
붙었는지는 `grep -n "def resize" .claude/browser_driver.py` 로 확인.
|
||||
- **화면이 안 따라와도 폭 측정은 됨** — 뷰포트 폭만 바꾸면 레이아웃은 다시 계산되므로
|
||||
`scrollWidth − clientWidth` 로 재는 검증은 그대로 유효함. 눈으로 보는 확인만 뒤로 미룰 것.
|
||||
|
||||
### ⚠ 브라우저 명령 파일은 **다른 이름으로 쓴 뒤 `mv` 로 넣을 것** (2026-09-09, 하루에 두 번)
|
||||
|
||||
`tmp/browser/cmd/` 에 파일을 **직접 쓰면 드라이버(1초 폴링)가 쓰는 도중에 집어감.**
|
||||
**실행은 되고 `OK` 도 뜨는데 내용이 비어 아무 일도 안 일어남** — driver.log 에도 안 남음.
|
||||
⇒ **딴 이름으로 다 쓴 다음 `mv` 로 옮길 것.** 「도는 척만 하는 검사」와 같은 계열이라 특히 나쁨(㉢).
|
||||
|
||||
### 공용 브라우저가 로그인 화면에서 멈췄을 때 (2026-09-08 — 두 창이 같은 자리에서 막힘)
|
||||
|
||||
**튕기면 서버가 아니라 쿠키를 의심할 것.** 세션은 DB 표(`sessions`)라 **서버 재시작으로 안 지워진다.**
|
||||
날아가는 것은 브라우저 쿠키 쪽이다(새 프로필·만료·캐시 비우기에 함께).
|
||||
쿠키는 포트를 안 가려 **8000·5173 중 한 번만 로그인하면 둘 다 붙는다.**
|
||||
|
||||
**되살리는 법** — 자격증명은 `tmp/aislo_cred.json`(git 밖, 사용자가 유지 — **지우지 말 것**).
|
||||
없으면 **짐작하지 말고 사용자에게 요청**할 것. 명령 파일에서:
|
||||
|
||||
```python
|
||||
import json
|
||||
cred = json.load(open("tmp/aislo_cred.json", encoding="utf-8"))
|
||||
api = page.context.request # ⚠ 브라우저 컨텍스트의 요청 — 쿠키를 공유한다
|
||||
if api.get("http://localhost:8000/api/auth/session").status != 200:
|
||||
api.post("http://localhost:8000/api/auth/login/request", data=cred)
|
||||
log(api.get("http://localhost:8000/api/auth/session").status) # 200 이어야 함
|
||||
```
|
||||
|
||||
⚠ **`page.evaluate` 안에서 `fetch` 로 하지 말 것** — 로그인 화면이 그 사이 이동해
|
||||
`Execution context was destroyed` 로 죽는다(2026-09-08 두 번 겪음). `page.context.request` 는
|
||||
페이지 이동과 무관하다.
|
||||
|
||||
⚠ `dev_up.py`·`browser_driver.py` 에 **로그인 처리가 없는 것이 맞다** — 드라이버는 창만 띄우고
|
||||
로그인은 **명령 파일 몫**이다. grep 해도 안 나온다.
|
||||
|
||||
⚠ **`shot()` 은 확장자를 붙일 것** — `shot("이름")` 은 `unsupported mime type ""` 로 죽는다.
|
||||
`page.screenshot(path="tmp/browser/shots/이름.png")` 로 쓰는 편이 안전하다.
|
||||
|
||||
⚠ **값을 만지기 전에 「어느 프로젝트가 열려 있나」부터 볼 것** —
|
||||
`localStorage['frd_current_project_id']`. 사용자 프로젝트(`5cff3920`)면 **읽기만** 한다.
|
||||
|
||||
### 검증 함정 모음 — 재기 전에 볼 것 (2026-09-07 정리)
|
||||
|
||||
끝난 절을 `plans/` 로 옮기며, **다시 걸리면 또 오진할 것들만** 여기로 건져 왔음.
|
||||
괄호는 원래 절 번호(`docs/raw/plans/2026-09-07_plan_completed_items.md` 에 원문 있음).
|
||||
|
||||
- ⚠ **시험을 저장소 실물에 매지 말 것 — 만든 창에서만 통과함** (2026-09-09).
|
||||
`test_prj_identify.py::test_compound_vertical_prj` 가 특정 프로젝트의
|
||||
`…/input/prj/result.prj` 를 **실경로로 열어** epsg 를 확인하는데, **그 프로젝트가 지워지자**
|
||||
두 PC 에서 `FileNotFoundError` 로 깨졌음. `tmp/tests` 는 git 밖이지만 시놀로지로 **파일은
|
||||
건너오고 저장소 자료는 창마다 다름** — 그래서 **남의 PC 에서만 깨지는 시험**이 됨.
|
||||
**⇒ 실물 파일이 필요하면 없을 때 `skip` 하게 가드를 붙일 것.** 같은 파일의 다른 넷은 이미
|
||||
그렇게 돼 있었고 하나만 빠져 있었음. **깨진 것을 「병합이 망가뜨렸다」로 오진하기 쉬운 자리임.**
|
||||
|
||||
- **옛 코드로 도는 서버에 속지 말 것** — 속도·화면을 재기 전에 ① 백엔드를 확실히 재시작하고
|
||||
② **그 수정의 로그나 새 응답 항목이 실제로 있는지** 먼저 볼 것. 하루에 두 번 속았고, 한 번은
|
||||
응답에 새 항목이 아예 없는데 화면은 정상처럼 보였음(다른 값으로 세는 폴백). (0-12)
|
||||
- **캐드(B07) 화면은 `npm run build` + 캐시 비우기** 를 해야 바뀜 — 안 하면 옛 화면을 봄. (4-4)
|
||||
- **화면이 백지가 되면 옛 vite 프로세스를 먼저 볼 것** (2026-09-07 보조 창, 하루 세 번 겪음) —
|
||||
증상: 해시 이동은 되는데 `document.body` 가 빈 문자열, 콘솔에 `ERR_CONNECTION_RESET`.
|
||||
원인: 백엔드를 다시 띄우면 `main.py` 가 vite 를 새로 띄우는데 **옛 vite 가 남아** 탭이 죽은
|
||||
소켓을 물고 있음(실측: 5174 를 쥔 node 하나 + 유령 둘). 캐시만 비워서는 안 살아남.
|
||||
되살리는 순서 — ① `Get-NetTCPConnection -LocalPort <포트>` 로 주인 확인, 유령 node 정리
|
||||
② 백엔드 재시작 ③ **`about:blank` → `http://localhost:<포트>/`(wait "load") → 그다음 해시**.
|
||||
해시로 곧장 가면 계속 백지였고, 루트를 먼저 거치면 살아남.
|
||||
- **조정창은 두 벌이고 조작이 갈려 있음** (2026-09-07 두 창이 각각 헛짚음) — 좌측 [횡단 조정]
|
||||
dock 에는 **이동 십자(▲▼◀▶)·집수정 9키가 CSS 로 숨겨져** 있고(`_Style_Cross_Controls.css:244~`),
|
||||
그 십자는 **도면 위에 뜨는 오버레이 창에만** 있음. 반대로 오버레이에는 값·형식 행이 숨겨져
|
||||
있음(2026-08-29 「값은 좌측, 위치제어는 도면 위」). dock 에서 ◀ 를 찾으면 DOM 에는 있는데
|
||||
`vis:false` 인 것이 **정상**임 — 결함으로 오해하지 말 것.
|
||||
- **요소가 보이는지는 `offsetParent` 로 재지 말 것** (2026-09-07, 두 창이 같이 걸림) —
|
||||
**고정 위치(`position: fixed`) 요소는 보이든 안 보이든 `offsetParent` 가 늘 `null`** 이라
|
||||
「안 보인다」로 오판함. 크기로 잴 것 — `getBoundingClientRect()` 의 width/height, 또는
|
||||
`offsetWidth || offsetHeight`. 둘 다 0 이면 실제로 안 그려진 것임.
|
||||
- **UI 를 붙였으면 크기도 같이 잴 것** (2026-09-07) — 알약 레인을 종단 패널에 넣고 「알약이 뜬다」만
|
||||
보고 넘겼다가, 잰 높이에서 레인 몫을 안 빼 되먹임이 생겨 **패널이 16,664px 로 부풀었음**.
|
||||
그 상태에서는 카드·알약을 아예 못 눌러 다음 검증이 통째로 막혔음.
|
||||
- **3D 클릭을 잴 때** — `__corridorScene.project()` 좌표는 DOM 이 덮고 있는지를 안 봄.
|
||||
클릭 직전에 `document.elementFromPoint(x, y) === document.querySelector('canvas')` 를 확인하거나
|
||||
종단 오버레이 핸들(`.ui-workflow-overlay__handle`)을 먼저 접을 것. 이걸 몰라 두 번 오진. (2-2)
|
||||
- **조정창은 셋으로 쪼개져 있음 — 「단 수」는 어느 조정창에도 없음** (2026-09-07, 3-7 에서
|
||||
한참 헤맴). ① **카드 위 조정창** = 이동 십자·집수정 9키 **전용** ② 좌측 **[횡단 조정] dock**
|
||||
= 높이·길이·기준측점 전/후 ③ **단 수(추가 기슭막이)와 옵션(연동·경사)** 은 둘 다 아니고
|
||||
**좌측 구조물 폼의 유입구/유출구 칸**(`.b05-structure__adjust-slot`)으로 옮겨 붙음.
|
||||
카드 조정창 안에도 그 행의 DOM 은 있지만 CSS 로 `display:none` 이라 눌리지 않음 —
|
||||
`innerText` 로만 보면 「있는데 왜 안 눌리지」로 헤매게 됨. **크기(rect)로 볼 것.**
|
||||
- **B06 조정창을 볼 때 — 두 번 걸린 함정임(3-4 · 3-7).** `.b06-structure-panel` 은 **카드마다
|
||||
하나씩** 있음(66~67개). 목록에서 `find` 로 아무거나 집으면 **다른 카드의 빈 패널**이 먼저 잡혀
|
||||
크기 0 · `is-hidden` 으로 나옴. 2026-09-07 에는 이것 때문에 두 창이 「십자 단추가 안 그려진다 =
|
||||
사용자도 벽을 못 옮긴다」로 잘못 결론냈다가 되물렀음(고른 벽의 카드에서 재니 조정창 124×185,
|
||||
이동 행 107×86 으로 멀쩡했음). **반드시 그 카드 안의 것**(`card.querySelector`)을 볼 것.
|
||||
또 **벽을 안 그리는 카드**를 고르면 굳히기 루프가 0회 도니
|
||||
`card.querySelector('.b06-chart__culvert-revet-hit')` 로 먼저 거를 것.
|
||||
- **화면 서버(vite)는 백엔드(`main.py`)의 자식임** (`main.py → npm → vite`, `.claude/dev_up.py:40`).
|
||||
백엔드를 트리째 끄면 **화면 서버도 같이 죽고** 열려 있던 탭이 옛 모듈을 붙들어 **백지**가 됨.
|
||||
재시작이 비싼 진짜 까닭은 서버가 아니라 이쪽임. 조작 전에 8000·5173(또는 8001·5174) **둘 다**
|
||||
200 인지 볼 것. (옛 「7. 개발 환경」에서 건져 온 것)
|
||||
- **로그인 세션은 DB 표**(`sessions`, `common_util/common_util_auth.py:115`)라 재시작으로 안 지워짐.
|
||||
⚠ **그래도 브라우저 쪽 세션은 끊김** — 2026-09-08 데스크탑 두 창이 같은 자리에서 막혔음.
|
||||
화면 검증 전에 `#/a06-login` 으로 튀는지 먼저 볼 것. (로그인하는 절차는 랩탑 창 확인 중)
|
||||
- ⚠ **화면이 막혀도 엔진은 파이썬에서 직접 부를 수 있음** — 라우터가 인계를 함수로
|
||||
부르므로(`from B08_Quantity… import get_handoff`) HTTP·로그인을 안 탐.
|
||||
⚠ 다만 **`await init_db_pool()` 을 먼저 부를 것** — 안 부르면 예외가 응답 본문으로 나가
|
||||
**빈 응답처럼 보이고 「자료가 없다」로 잘못 읽힘**(2026-09-08 실측). 그리고 **한 이벤트
|
||||
루프 안에서** 다 끝낼 것(따로 돌리면 `Event loop is closed` 가 쏟아짐).
|
||||
- ⚠ **되받기 전후로 인계 응답을 파일로 떠 둘 것** — 「합계가 221원 줄었다」를 짚을 때
|
||||
**어느 줄이 얼마나 바뀌었는지**를 대조할 자료가 없으면 짐작이 됨(2026-09-08).
|
||||
마침 화면 로그에 옛 수량이 남아 있어 짚었으나 그건 운이었음.
|
||||
⚠ 다만 「재시작하면 다시 로그인해야 했다」는 기록이 있으므로 **다른 까닭이 있을 수 있음** —
|
||||
실측은 안 해 봤음.
|
||||
- 🔴 **횡단 카드를 확대·이동하면 그 측점의 면적이 달라질 수 있음** (2026-09-07 3-9 뒷정리에서
|
||||
발견). 확대·이동은 그 측점의 **「표시 반폭」**(`display_half_width_m`)을 바꾸고(실측 12 → 27m),
|
||||
**사면이 반폭 안에서 지반을 못 만나는 측점**은 잘린 만큼 면적이 달라짐(성토 **57.45 → 62.79㎡**).
|
||||
세션 `crossw` 에 남으므로 **검증하러 확대하는 것만으로 수치가 흔들림.**
|
||||
· 설계상 그런 자리는 `slope_unclosed` 경고가 이미 뜨므로 **결함이라기보다 「반폭이 좁으면 면적이
|
||||
잘린다」는 성질**임. 다만 검증할 때는 **확대 전 반폭을 적어 두고 끝나면 되돌릴 것.**
|
||||
- **화면 요소가 보이는지 `offsetParent` 로 재지 말 것** — 고정 위치 요소는 늘 `null` 이라 늘
|
||||
안 보이는 것으로 나옴. `getBoundingClientRect()` 크기로 잴 것. (3-7)
|
||||
- **단계바 `.ui-workflow-layout__step` 은 0번이 대시보드** — 종단설계는 3번. (2-7)
|
||||
- **살아 있는 프로젝트에 검증용 `solve_route` 를 돌리지 말 것** — `route_main.geojson` 은 프로젝트당
|
||||
한 벌이라 노선 행이 늘어도 파일은 덮임(실제로 덮였고 되돌렸음). (0-10)
|
||||
- **이미 만든 프로젝트는 그대로임** — 노선이 폴리라인 기준이 되려면 새로 업로드해야 함. (0-10)
|
||||
- **곡선 생략(내각 155°) 규칙을 쓰지 않기로 해 곡선 개수가 늘었음**(용화 13 → 26곳) —
|
||||
평면 R 을 읽는 자리(3-1 확폭 · 2-6 구조물)는 그것을 감안할 것. (0-12)
|
||||
- **3-5 물량 파급을 되돌릴 자리** — `Cross_Culvert_Geom.ts` 의 `extendTrimSlope` 위 주석.
|
||||
사용자가 「그대로 둠」으로 확정했으므로 근거로만 남긴 것. (3-5)
|
||||
- **확폭이 수량에 실리는 것은 B08 재작업 때** — `carriageway_width_m`(= 3.0 + 확폭)을 쓰면 됨. (3-1)
|
||||
@@ -0,0 +1,109 @@
|
||||
# 보조 워크트리 만들기 (PC마다 1개)
|
||||
|
||||
작성 2026-08-31. 대상 = 각 PC에서 일하는 AI. 이 문서대로 하면 한 PC에서 창 두 개가
|
||||
서로 밟지 않고 동시에 일한다.
|
||||
|
||||
## 왜 이렇게 하나
|
||||
|
||||
- **동기화 밖에 둔다.** `C:\Program_coding` 은 Synology Drive 동기화 루트다. 워크트리를
|
||||
그 안에 두면 NAS 사본이 편집을 되돌린다(2026-08-31에 `.claude/dev_up.py` 가 두 번
|
||||
옛 사본으로 덮였다). 그래서 워크트리는 **동기화 루트 밖**에 만든다.
|
||||
- **포트를 나눈다.** 두 폴더가 같은 포트를 쓰면 나중에 뜬 쪽이 앞의 것을 죽인다.
|
||||
- **브랜치를 나눈다.** git 은 같은 브랜치를 두 워크트리가 동시에 체크아웃하지 못한다.
|
||||
|
||||
## 이름 규칙
|
||||
|
||||
| 대상 | 규칙 | 이 PC(노트북)의 실제 값 |
|
||||
|---|---|---|
|
||||
| 폴더 | 동기화 루트 밖, `C:\Aislo_wt\<이름>` | `C:\Aislo_wt\aislo-b0506` |
|
||||
| 메인 폴더 브랜치 | `main` 또는 `main_<pc>_<번호>` | `main` |
|
||||
| 보조 트리 브랜치 | `sub_<pc>_<번호>` | `sub_laptop_1` |
|
||||
|
||||
PC 이름을 브랜치에 박아 두면 원격 목록만 봐도 어느 기계 것인지 갈린다
|
||||
(현재 원격: `main`, `main_desktop_1`, `main_laptop_1`, `sub_laptop_1`).
|
||||
|
||||
## 만드는 절차
|
||||
|
||||
먼저 그 PC의 동기화 루트를 확인한다 — `.SynologyWorkingDirectory` 파일이 있는 폴더가
|
||||
루트다. 새 워크트리 경로가 그 아래로 들어가면 안 된다.
|
||||
|
||||
```powershell
|
||||
# 1) 워크트리 생성 (메인 폴더에서 실행)
|
||||
New-Item -ItemType Directory C:\Aislo_wt
|
||||
git -C "<메인 폴더>" worktree add C:\Aislo_wt\<이름> -b sub_<pc>_<번호> origin/main
|
||||
|
||||
# 2) 메인 폴더와 한 벌로 쓸 것들을 링크로 연결 (전부 gitignore 대상이라 안 딸려온다)
|
||||
$M = "<메인 폴더>"; $W = "C:\Aislo_wt\<이름>"
|
||||
New-Item -ItemType Junction $W\venv -Target $M\venv
|
||||
New-Item -ItemType Junction $W\.claude -Target $M\.claude
|
||||
New-Item -ItemType Junction $W\docs -Target $M\docs
|
||||
New-Item -ItemType Junction $W\storage -Target $M\storage
|
||||
New-Item -ItemType SymbolicLink $W\CLAUDE.md -Target $M\CLAUDE.md
|
||||
New-Item -ItemType SymbolicLink $W\AGENTS.md -Target $M\AGENTS.md
|
||||
|
||||
# 3) 프론트 의존성 (워크트리마다 따로 필요)
|
||||
cd $W\config; npm install
|
||||
```
|
||||
|
||||
`graphify-out` 링크가 있다면 옛 경로를 가리키므로 새로 건다 —
|
||||
대상은 `<메인 폴더>\docs\wiki\graphify-out\<날짜>`.
|
||||
|
||||
## 서버 띄우기 — 포트가 갈린다
|
||||
|
||||
| 폴더 | 백엔드 | vite |
|
||||
|---|---|---|
|
||||
| 메인 | 8000 | 5173 |
|
||||
| 보조 | 8001 | 5174 |
|
||||
|
||||
메인 폴더는 그대로다.
|
||||
|
||||
```powershell
|
||||
./venv/Scripts/python.exe .claude/dev_up.py
|
||||
```
|
||||
|
||||
보조 트리는 `dev_up.py` 가 8000·5173 고정이라 **환경변수를 주고 직접 띄운다**.
|
||||
|
||||
```powershell
|
||||
$env:SERVER_PORT='8001'; $env:FRONTEND_DEV_PORT='5174'; $env:AISLO_API_PORT='8001'
|
||||
Start-Process "$W\venv\Scripts\python.exe" -ArgumentList 'main.py' -WorkingDirectory $W `
|
||||
-RedirectStandardOutput "$W\tmp\server.log" -RedirectStandardError "$W\tmp\server.err.log"
|
||||
|
||||
$env:AISLO_VITE_PORT='5174'
|
||||
Start-Process "$W\venv\Scripts\python.exe" -ArgumentList '.claude\browser_driver.py' `
|
||||
-WorkingDirectory $W -RedirectStandardOutput "$W\tmp\browser_driver.out.log" `
|
||||
-RedirectStandardError "$W\tmp\browser_driver.err.log"
|
||||
```
|
||||
|
||||
각 변수가 하는 일 — `SERVER_PORT` 백엔드, `FRONTEND_DEV_PORT` vite, `AISLO_API_PORT`
|
||||
vite 프록시가 볼 백엔드(`config/vite.config.ts`), `AISLO_VITE_PORT` 브라우저 드라이버가
|
||||
열 주소. 넷을 한 벌로 맞춰야 프록시가 옆 포트로 새지 않는다.
|
||||
|
||||
확인: `5174/api/health` 가 200이면 프록시가 8001을 보고 있는 것이다.
|
||||
|
||||
**백엔드·DB 는 원래 한 벌을 공유해도 된다.** 백엔드를 고치는 작업일 때만 8001을 따로 띄운다.
|
||||
프론트만 만질 때는 vite 만 5174로 올리고 `AISLO_API_PORT=8000` 을 주면 된다.
|
||||
|
||||
## 지켜야 할 것
|
||||
|
||||
- **`git worktree remove` 를 쓰지 않는다.** Windows 긴 경로(`node_modules`)에서 실패하면
|
||||
공유 `.git` 과 `.claude` 까지 지운다(2026-08-30 실제 사고 2건). 폴더 정리가 필요하면
|
||||
사용자에게 요청한다. 옮기는 것은 `git worktree move` 로 안전하다 — 폴더 이동과 포인터
|
||||
두 개를 git 이 같이 고친다.
|
||||
- **워크트리를 지울 때는 링크부터 끊는다.** `.claude`·`docs`·`storage`·`venv` 는 메인
|
||||
폴더를 가리키는 링크라, 재귀 삭제가 링크를 따라가면 원본이 날아간다. `cmd /c rmdir <링크>`
|
||||
로 링크만 먼저 떼고 폴더를 지운다.
|
||||
- **커밋했으면 그 자리에서 push.** PC 이동이 곧 인수인계다. 로컬에만 있으면 다음 PC가 못 받는다.
|
||||
- **같은 브랜치를 두 트리가 못 쓴다.** 메인이 `main` 을 물고 있으면 보조는 `sub_*` 여야 한다.
|
||||
- **공유되는 것을 기억한다** — `venv` `.claude` `docs`(PLAN.md) `storage`(프로젝트 자료), DB.
|
||||
코드 파일만 갈릴 뿐 이것들은 한 벌이다. `PLAN.md` 는 자기 섹션만 고친다.
|
||||
- **`.claude/` 는 git 밖이고 동기화 안이다.** 두 PC의 AI가 같은 파일을 고치면 나중 동기화가
|
||||
앞의 것을 덮는다. 고치기 전에 사용자에게 알린다.
|
||||
|
||||
## 이 PC(노트북) 현재 상태
|
||||
|
||||
```
|
||||
C:\Program_coding\임도설계 및 견적자동화 프로그램 개발 [main] 8000 · 5173
|
||||
C:\Aislo_wt\aislo-b0506 [sub_laptop_1] 8001 · 5174
|
||||
```
|
||||
|
||||
원격에 `sub_laptop_1` 을 미리 만들어 두었다(`origin/sub_laptop_1`, main 과 같은 지점).
|
||||
@@ -0,0 +1,75 @@
|
||||
# PLAN: B04 상세페이지·옵션 패널 재구성 (2026-07-17)
|
||||
|
||||
## 배경·목표
|
||||
- B04(WF1 지표면) 프론트엔드에 불명 정보 그룹이 표시되고, 옵션 패널과 상세 영역 간 중복 구현·미작동 기능이 존재.
|
||||
- 백엔드는 업로드 시 자동 분석(`trigger_wf1_analysis_and_email`, `B03_FileInput_Router.py:147`)으로 **지면 필터 3종(grid_min_z/csf/pmf) × 표현 기법 5종(tin/dtm/nurbs/implicit/meshfree) = 15개 지표면 모델**을 영구저장소+DB에 저장한다(`config_system.py:99-104`). RANSAC은 사전계산 대상이 아니며 요청 시 허용 필터로만 존재(`B04_wf1_Surface_Schema.py:12`). **B04는 "선택 → 저장된 결과 표시 → 확정" 화면으로 재구성**한다.
|
||||
- 이 작업은 기존 백로그 "B04 자동화 개편"의 프론트엔드 부분을 구체화한 것임.
|
||||
|
||||
## 작업 범위 (대상 파일)
|
||||
| 파일 | 작업 |
|
||||
|---|---|
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Page.ts` | 그룹 삭제, 옵션 패널 재정의, 버튼 교체, 2열 배치 조립 (중심 파일) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Viewer.ts` | 배경지도/국가GIS 표시 제거, 카메라 동기화 인터페이스 |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_TerrainViewer.ts` | 하단 선택그룹 제거, 카메라 동기화 인터페이스 |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_MapViewer.ts` | 기본값 위성/지적도, 탑뷰 정합(대각선 현상 확인·수정) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Style.css` | 2열 레이아웃, 제목 제거·최대 폭 |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts` | 표시 포인트 수 상한 50만 대응 |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Router.py` | `POINT_CLOUD_SAMPLE_LIMIT` 100_000 → 500_000 (51줄, 1줄 수정) |
|
||||
|
||||
## 스펙
|
||||
|
||||
### 1. 삭제 항목 (프론트 표시만 제거, 백엔드 API는 유지)
|
||||
- "지면 필터 통계" 그룹 삭제 (`fetchSurfaceGroundStats` 호출·표시 제거)
|
||||
- "생성된 지표면 모델" 그룹(모델 카드 목록) 삭제 — 확정 UX는 아래 버튼으로 이동
|
||||
|
||||
### 2. 좌측 사용자 옵션 패널 재정의
|
||||
- **입력 포인트 클라우드 그룹**
|
||||
- 드롭다운: 입력 LAS 파일 선택 유지. "#XX 선택" 라벨의 실체 확인 후 의미 있는 라벨로 정리
|
||||
- 파일명 항목 제거
|
||||
- 표시 정보: 좌표계 / 크기(LAS 원본 전체 파일 크기) / 포인트 수 / 프론트엔드 표시 포인트 수 / 높이 범위(min~max 값)
|
||||
- **지면 필터 선택 그룹**: 사전계산된 필터 3종(grid_min_z/csf/pmf)만 노출, 선택 시 영구저장된 결과를 즉시 뷰어에 로드, "표시 옵션" 그룹 설정에 따라 렌더
|
||||
- **RANSAC은 드롭다운에서 제거** (`UI_Page.ts:54`의 `SOURCE_FILTERS`에서 `ransac` 삭제). 업로드 자동 분석에 미포함이라 저장 데이터가 없음. 백엔드 RANSAC 엔진·화이트리스트는 유지
|
||||
- 표시 포인트 규칙: 지면 필터 후 포인트가 **50만 개 초과 시 랜덤 50만 개**, 이하면 **전체** 표시 (`POINT_CLOUD_SAMPLE_LIMIT` 100_000 → 500_000, 기존 샘플링 로직 재사용 `Router.py:343-348`)
|
||||
- 밀도 100% = 위 규칙으로 로드된 지면 필터 직후 데이터 기준
|
||||
- **지표면 표현 선택 그룹**: 모델(TIN/DTM/NURBS/implicit/meshfree) 선택 시 영구저장된 모델을 즉시 뷰어에 로드
|
||||
- **서피스 / 스무딩 / 등고선 / 등고선 간격**: 모델 비교 영역 하단에서 옵션 패널로 이동 (기존 컨트롤 재사용)
|
||||
- **뷰어 시점 제어 그룹**: 포인트클라우드 미리보기 + 5종 모델 비교 두 뷰어에 공통 적용
|
||||
- **버튼 교체**
|
||||
- "지표면 분석 실행" → **"모델 확정"**: 현재 선택된 (지면 필터 + 지표면 표현) 조합에 해당하는 `surface_models.id`를 `POST .../surface/confirm`으로 전송. 확정 성공 시 기존 토스트·WF2 이동 활성화 유지
|
||||
- "목록 새로고침" → **"초기화"**: 옵션 그룹 기본값 복원 + 목록 재조회
|
||||
|
||||
### 3. 3D 뷰어 영역 (상세 영역)
|
||||
- 포인트클라우드 미리보기 ↔ 지면 필터별 5종 지표면 모델 비교를 **2열 배치**
|
||||
- 제목 제거, 가용 폭 최대 사용
|
||||
- **카메라 동기화**: 어느 쪽을 마우스로 조작해도 두 뷰어 동일 시점 유지
|
||||
- 미리보기의 배경지도·국가GIS 표시 제거 (백엔드 fetch 함수 `getVWorldMapUrl`/`fetchVWorldMeta`/`fetchGisGeoJson`은 유지 — 하단 2D 지도가 사용)
|
||||
- 5종 모델 비교 하단의 선택 그룹들 삭제 (서피스/스무딩/등고선/간격은 옵션 패널로 이관)
|
||||
|
||||
### 4. 하단 2D 배경지도·GIS 레이어 그룹
|
||||
- 현행 유지하되 기본값: 배경지도=**위성(Satellite)**, GIS 레이어=**지적도**
|
||||
- 완전한 탑뷰(정사) 정합으로 표시 — 사용자가 보고한 "살짝 대각선" 현상의 원인(투영/오프셋/착시)을 코드에서 확인 후 수정. 표시 옵션 그룹과는 별개 항목
|
||||
|
||||
### 5. 공통 원칙
|
||||
- `ui_templates`(createWorkflowLayout, 폼 그룹 템플릿 등) 최대 재활용, 신규 템플릿 남발 금지
|
||||
- Surgical Edit, 700줄 제한(UI_Page.ts 증가 시 기능별 분리), 완료 후 `prettier` 실행
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] 1. UI_Page: "지면 필터 통계" 그룹 및 관련 호출 제거
|
||||
- [x] 2. UI_Page: "생성된 지표면 모델" 그룹(모델 카드) 제거
|
||||
- [x] 3. UI_Page: 입력 포인트 클라우드 그룹 재정의 (드롭다운 라벨 정리, 파일명 제거, 좌표계/크기/포인트 수/표시 포인트 수/높이 범위 표시)
|
||||
- [x] 4. Router: `POINT_CLOUD_SAMPLE_LIMIT` 500_000으로 변경 (50만 초과 시 랜덤 50만, 이하 전체)
|
||||
- [x] 5. UI_Page: 지면 필터 선택 → 즉시 로드·표시 옵션 연동 렌더, `SOURCE_FILTERS`에서 `ransac` 제거
|
||||
- [x] 6. UI_Page: 지표면 표현 선택 → 즉시 모델 로드 렌더
|
||||
- [x] 7. UI_Page/TerrainViewer: 서피스·스무딩·등고선·등고선 간격 컨트롤을 옵션 패널로 이관, 비교 영역 하단 선택그룹 삭제
|
||||
- [x] 8. Viewer/TerrainViewer: 뷰어 시점 제어 공통 적용 + 양방향 카메라 동기화
|
||||
- [x] 9. UI_Page/Style.css: 2열 배치, 제목 제거, 최대 폭
|
||||
- [x] 10. Viewer: 미리보기 배경지도·국가GIS 표시 제거 (fetch 함수 유지)
|
||||
- [x] 11. UI_Page: 버튼 교체 — "모델 확정"(confirm 연동), "초기화"(기본값 복원+재조회)
|
||||
- [x] 12. MapViewer: 기본값 위성/지적도, 대각선 현상 확인·탑뷰 정합
|
||||
- [x] 13. prettier 적용, 700줄 제한 점검
|
||||
|
||||
## 리스크·확인 사항
|
||||
- **확정 매핑 확인됨**: 영구저장 모델은 3필터 × 5기법 = 15개 조합(`config_system.py:99-104`). 확정 버튼 = 선택된 (필터, 기법) 조합의 `surface_models.id`를 confirm API로 전송
|
||||
- **표시 샘플링과 백엔드 분석 분리 확인됨**: 50만 샘플링은 미리보기 응답(`GET .../surface/point-cloud`)에만 적용. 경로(B05)·등고선 분석은 영구저장소의 전체 필터링 데이터 기준 — 검증 단계에서 재확인
|
||||
- **RANSAC 처리 확정됨**: 업로드 자동 분석(`B03_FileInput_Router.py:164-165`)은 3종 필터만 실행 → RANSAC 저장 데이터 없음. 프론트 드롭다운에서 제거(현재 노출 중 = 미작동 항목), 백엔드 기능은 유지
|
||||
- **"#XX 선택" 드롭다운 실체**: 코드 확인 후 라벨 정리 (구현 단계)
|
||||
@@ -0,0 +1,32 @@
|
||||
# PLAN ARCHIVE: B04 등고선 — 기본값(CSF+DTM) 외 조합에서 표시 안 됨 (2026-07-17)
|
||||
|
||||
## 활성 작업: B04 등고선 — 기본값(CSF+DTM) 외 조합에서 표시 안 됨 (2026-07-17)
|
||||
|
||||
### 증상
|
||||
좌측 옵션에서 지면 필터(csf/pmf/grid_min_z) 또는 지표면 표현(tin/dtm/nurbs/implicit/meshfree)을 기본값(csf+dtm)에서 바꾸면 3D 뷰어에 등고선이 나타나지 않음.
|
||||
|
||||
### 원인 분석 (실 저장소 `storage/.../B04_wf1_Surface/models/` 캐시 파일 실증 확인)
|
||||
|
||||
| # | 원인 | 근거 위치 |
|
||||
|---|---|---|
|
||||
| 1 | **간격 기본값 불일치 (핵심)**: 파이프라인 사전 캐시는 `contour_interval_meters=5.0`m로만 생성, 프론트 기본 간격은 `1.0`m → 캐시 파일명(`contour_{filter}_{method}_{interval}m.json`) 불일치로 항상 캐시 미스 → 모든 조합이 첫 조회 시 온디맨드 재계산 | `B04_wf1_Surface_Engine_Pipeline.py:57`, `B04_wf1_Surface_UI_TerrainViewer.ts:78`, `B04_wf1_Surface_Router.py:616-618` |
|
||||
| 2 | **온디맨드 계산은 실제로 동작하나 13~23초+ 소요, 그동안 UI 무피드백**: 오늘(07-17 18:06~18:14) 사용자 조작 시각에 `contour_pmf_dtm_smooth_1.0m.json` 등이 실제 생성됨 = 계산은 됨. 그러나 자동 로드 경로는 `void loadContourLines(...)`로 결과·진행을 무시 → 상태표시는 "표시 중"에 머물고, 사용자는 "계산 안 함"으로 인지 | `B04_wf1_Surface_UI_TerrainViewer.ts:344,366` |
|
||||
| 3 | **실패 침묵 2중**: (a) 파이프라인 사전 캐시 실패를 `except: pass`로 은폐 — 실제로 `contour_csf_nurbs_*.json`은 어떤 간격으로도 존재하지 않음(manifest는 nurbs csf "completed") (b) 프론트 `loadContourLines` 실패 시 `catch → return false`만 하고 사용자에게 알리지 않음 | `B04_wf1_Surface_Engine_Pipeline.py:272-273`, `B04_wf1_Surface_UI_TerrainViewer.ts:483-486` |
|
||||
| 4 | **무거운 표현의 1.0m 온디맨드 과부하**: nurbs(전 필터)·meshfree(전 필터)·비스무딩 tin 등은 1.0m 격자 온디맨드 산출물이 하나도 없음 — RBF/griddata/Delaunay를 1m 격자로 돌려 수십 초~수 분 소요 또는 실패 추정. Router는 `target_grid_m=1.0` 고정 | `B04_wf1_Surface_Router.py:648-655` |
|
||||
| 5 | (부수) 응답 경합 가드 없음: 선택을 빠르게 바꾸면 이전 모델의 등고선 응답이 늦게 도착해 새 화면에 그려질 수 있음 | `B04_wf1_Surface_UI_TerrainViewer.ts:379-388` |
|
||||
| 6 | (부수) 파이프라인 캐시 재사용 분기(`continue`)는 등고선 사전 캐시 존재를 보증하지 않음 → 재분석 시에도 누락 캐시가 복구되지 않음 | `B04_wf1_Surface_Engine_Pipeline.py:199-218` |
|
||||
|
||||
### 수정 방향 및 구현 체크리스트
|
||||
|
||||
- [x] **C1. 간격 기본값 정합**: 프론트 등고선 간격 기본값을 사전 캐시 간격(5.0m)과 일치시킴 (`intervalInput.value = "5.0"` + `resetOptions()` 동일 수정). 사용자가 간격을 바꾸면 그때만 온디맨드 계산. — `B04_wf1_Surface_UI_TerrainViewer.ts`
|
||||
- [x] **C2. 등고선 로딩 피드백**: 자동 로드 경로에서도 `statusSpan`에 "등고선 계산 중..." 표시, 완료/실패 시 결과 반영 (`void` 제거하고 결과 사용). — `B04_wf1_Surface_UI_TerrainViewer.ts`
|
||||
- [x] **C3. 실패 침묵 제거**: 파이프라인 `except: pass` → `logger.warning(...)`로 교체(빌드 무효화는 여전히 안 함). 프론트 실패 시 상태 문구 표시. — `B04_wf1_Surface_Engine_Pipeline.py`, `..._UI_TerrainViewer.ts`
|
||||
- [x] **C4. csf+nurbs 등고선 실패 원인 재현·수정**: `extract_contours(nurbs_csf.npz, "bspline_surface", ...)`를 venv로 직접 실행해 예외 확인 후 수정 (RectBivariateSpline 파라미터 추정). — `B04_wf1_Surface_Engine_Contour.py`
|
||||
- [x] **C5. 온디맨드 격자 해상도 적응화**: Router 고정 `target_grid_m=1.0` → 모델 범위 기반 적응(예: 격자 셀 수 상한 도입)으로 무거운 표현의 계산 시간 상한 확보. — `B04_wf1_Surface_Router.py`
|
||||
- [x] **C6. 응답 경합 가드**: `loadContourLines` 응답 도착 시 `currentModelId`·간격이 요청 시점과 같을 때만 그리기. — `B04_wf1_Surface_UI_TerrainViewer.ts`
|
||||
- [x] **C7. 캐시 재사용 분기에서 등고선 캐시 보증**: `_cache_is_valid` 통과(`continue`) 시에도 기본 간격 등고선 파일 부재면 `_cache_contours` 호출. — `B04_wf1_Surface_Engine_Pipeline.py`
|
||||
|
||||
### 검증 기준
|
||||
- 15개 전 조합(3필터×5표현, 스무딩 on/off 포함)에서 등고선 표시 또는 명시적 실패 메시지 확인
|
||||
- `contour_csf_nurbs_5.0m.json` 생성 확인
|
||||
- 계산 중 상태 문구 노출 확인
|
||||
@@ -0,0 +1,89 @@
|
||||
# PLAN: B04 결함 수정 6건 + 확정 흐름 주의점 보완 (2026-07-17)
|
||||
|
||||
## 활성 작업: B04 결함 수정 6건 + 확정 흐름 주의점 보완 (2026-07-17)
|
||||
|
||||
### A. 지면 필터 미리보기가 원본 데이터를 표시 (핵심 결함)
|
||||
- **현상**: 지면 필터를 선택해도 필터링 후 데이터가 아닌 원본에서 50만 개를 샘플해 표시
|
||||
- **원인 (확인됨)**:
|
||||
- `GET .../surface/point-cloud`가 `structured.npz`(지면 필터 **이전** 구조화 원본)를 고정 조회 — `B04_wf1_Surface_Router.py:332`. 필터 파라미터 자체가 없음
|
||||
- 지면 필터 마스크는 분석 중 메모리에서만 생성·사용되고 **영구저장되지 않음** — `B04_wf1_Surface_Engine.py:77-85` (`build_ground_masks` → 모델 빌드에 전달 후 소멸)
|
||||
- **0_old 검증 프로그램의 방식 (준용)**: `create_ground_filter_cache`(`0_old/backend/app/analyzer.py:622-739`)가 분석 시 **필터링된 지면 포인트 자체를 브라우저 상한(0_old는 500만)으로 샘플해 `processed/ground-points.json`으로 영구저장**하고, `GET .../ground-points`(`0_old/backend/app/main.py:166-170`)가 캐시 파일을 그대로 서빙. 요청 시 재계산 없음
|
||||
- **수정 방향 (0_old 패턴 준용)**:
|
||||
1. Engine: 분석 시 필터별 지면 포인트 캐시를 `processed/ground_points_{filter}.npz`로 영구저장 — 50만 규칙(초과 시 랜덤 50만, 이하 전체) 적용된 미리보기 서빙용. 메타(원본 지면 포인트 수, 샘플 수, bounds) 포함
|
||||
2. Router: point-cloud 엔드포인트에 `filter` 쿼리 파라미터 추가 — 지정 시 해당 캐시 파일 서빙, 미지정 시 기존 원본(structured) 동작 유지
|
||||
3. Api_Fetch / UI_Page: 지면 필터 선택 변경 시 `filter` 파라미터로 재조회 → 뷰어 렌더
|
||||
4. **기존 프로젝트**에는 캐시 파일이 없음 → filter 요청 시 파일 부재면 `structured.npz`로 해당 필터만 온디맨드 재계산 후 캐시 저장(백필)
|
||||
5. 경로(B05)·등고선 등 백엔드 분석은 종전대로 전체 필터링 데이터 기준(미리보기 캐시와 무관) 유지
|
||||
|
||||
### B. 등고선 간격 옵션 미작동
|
||||
- **원인 (확인됨)**: 등고선은 분석 시 config 기본 간격(`contour_interval_meters`=5.0m) 파일만 사전 생성 — `B04_wf1_Surface_Engine_Pipeline.py:57-104`. contour 엔드포인트는 **파일 조회 전용**이라 다른 간격 요청 시 파일이 없고, 폴백 glob이 기존 5.0m 파일을 반환 — `B04_wf1_Surface_Router.py:655-662` → **간격 변경이 조용히 무시됨**. 프론트 전달(`interval` 파라미터)은 정상 — `B04_wf1_Surface_UI_TerrainViewer.ts:383-385`
|
||||
- **수정 방향**: 요청 간격의 파일이 없으면 저장된 모델 npz로 **온디맨드 등고선 생성 후 파일 캐시 저장**(기존 `Engine_Contour` 함수 재사용, `asyncio.to_thread`). 폴백 glob은 생성 실패 시에만 사용하거나 제거
|
||||
|
||||
### C. 스무딩 옵션 동작 불명확
|
||||
- **확인된 사실**: 프론트 연결은 정상(`smooth` 파라미터 전달 및 토글 시 재로드 — `UI_TerrainViewer.ts:326-329,568-570`). 스무딩은 **tin/dtm 전용**이며 `{stem}_smooth_preview.glb` 사전 생성 파일을 조회(`Router.py:567-570`). nurbs/implicit/meshfree는 토글해도 무동작이 현재 설계
|
||||
- **수정 방향**:
|
||||
1. 실행 검증: tin/dtm에서 `_smooth_preview.glb`·`contour_*_smooth_*.json` 파일 실존 및 토글 시 메시 교체 여부 확인, 파일 누락 시 Pipeline 생성 경로 수정
|
||||
2. UX: 스무딩 미지원 기법(nurbs/implicit/meshfree) 선택 시 토글 비활성화(회색)로 "안 되는 게 아니라 해당 없음"을 명시
|
||||
3. 등고선도 스무딩 상태와 간격을 함께 반영(B와 연동)
|
||||
|
||||
### D. 2D 지도 이미지 왜곡 (그룹 컨테이너에 맞춰 늘어남)
|
||||
- **원인 (확인됨)**: `.b04-map__image { object-fit: fill; width/height:100% }` — `B04_wf1_Surface_UI_Style.css:468-478`. VWorld 배경 이미지가 뷰포트(가변 폭 × 고정 560px)에 종횡비 무시로 강제 인장됨. 이전에 보고된 "살짝 대각선" 현상도 이 왜곡이 원인일 가능성 높음
|
||||
- **수정 방향**: VWorld 요청 해상도를 뷰포트 실제 크기(종횡비)에 맞춰 요청하거나 `object-fit` 정합 처리 + canvas GeoJSON 오버레이 좌표 변환 동기화. 수정 후에도 대각선 느낌이 남으면 투영 좌표 변환 재점검
|
||||
|
||||
### F. 두 3D 뷰어 카메라 동기화의 스케일 불일치
|
||||
- **현상**: 포인트클라우드 미리보기 ↔ 지표면 표현 뷰어가 함께 움직이긴 하나 배율(스케일)이 서로 다름
|
||||
- **원인 (확인됨)**: 카메라 상태를 비율로 주고받는데 **정규화 기준이 서로 다름**
|
||||
- Viewer: 데이터를 최대 span=180 고정 공간으로 스케일링(`UI_Viewer.ts:202`)하고 `distanceRatio = distance/180` 고정 기준(`UI_Viewer.ts:165-187`)
|
||||
- TerrainViewer: 실좌표 메시의 바운딩박스 span 기준 `distance/sceneSpan`(`UI_TerrainViewer.ts:270-296`)
|
||||
- 포인트클라우드 범위(현재는 원본이라 식생 포함 z-range 큼)와 지표면 메시 범위가 달라 같은 비율이 다른 배율로 나타남. A 수정 후에도 기준 데이터 범위가 완전히 같지 않으므로 잔존
|
||||
- **수정 방향**: 카메라 상태를 **실세계 좌표(m) 기준 공통 프레임**으로 교환 — 각 뷰어가 자기 월드→씬 변환(Viewer: 180/span 배율+중심점, Terrain: sceneCenter)을 알고 있으므로, emit/apply 시 실좌표(중심 기준 오프셋 m, 거리 m)로 변환해 동기화. 축 방향 컨벤션(Viewer의 x/z/-y 스왑)도 함께 정합
|
||||
|
||||
### G. 지표면 표현 뷰어가 컨테이너 전체를 사용하지 못함
|
||||
- **원인 (확인됨)**: TerrainViewer 렌더 영역에 인라인 `height: 520px` 고정(`UI_TerrainViewer.ts:106`) — 포인트클라우드 뷰어(`.three-viewer` 560px, `UI_Style.css:355-360`)와 불일치하고, 2열 컨테이너 높이와 무관하게 고정됨
|
||||
- **수정 방향**: 인라인 높이 제거, 두 뷰어 동일한 CSS 규칙(컨테이너 기준 100% 또는 공통 고정값)으로 통일. ResizeObserver 기반 리사이즈는 이미 구현되어 있어(`UI_TerrainViewer.ts:587-596`) CSS만 정리하면 추종함
|
||||
|
||||
### E. 확정 흐름 주의점 보완 (2026-07-17 검증에서 발견)
|
||||
1. `enableRouteStep`이 진행단계 버튼을 DOM 인덱스 `[2]` 하드코딩으로 탐색(`UI_Page.ts:186-188`) → 스텝 식별자/route 기반 탐색으로 변경 (스텝 순서 변경 시 무음 파손 방지)
|
||||
2. 확정 토스트의 `{smoothing}` 값이 `"-"` 고정(`UI_Page.ts:304`) → 실제 스무딩 토글 상태 연결
|
||||
3. B04 재진입 시(이미 확정 상태) WF2 스텝 활성화가 서버 workflowState 렌더에 의존 → 확정 → 새로고침 → 스텝 클릭 가능 여부 실행 검증, 불가하면 수정
|
||||
4. 잔여 확인: 지면 필터 드롭다운의 `ransac` 제거(`UI_Page.ts:54` `SOURCE_FILTERS`) — 이전 계획 항목이나 현재 코드에 잔존
|
||||
5. **확정 성공 시 B05로 자동 이동** (사용자 확정, 2026-07-17): 확정 토스트 표시 후 `goToWorkflowStage`로 WF2(B05) 페이지 자동 전환. 스텝 수동 클릭 대기 방식 폐기 — 단, `enableRouteStep`의 스텝 활성화 처리는 뒤로가기/재진입 대비 유지
|
||||
|
||||
### 대상 파일
|
||||
| 파일 | 작업 |
|
||||
|---|---|
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Engine.py` | 필터별 지면 포인트 캐시 영구저장 (A) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Router.py` | point-cloud `filter` 파라미터+캐시 서빙 (A), 등고선 온디맨드 생성 (B) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Api_Fetch.ts` | point-cloud filter 파라미터 전달 (A) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Page.ts` | 필터 선택 → 재조회 연결 (A), E-1·2·4 |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Viewer.ts` | 카메라 상태 실좌표 변환 (F) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_TerrainViewer.ts` | 스무딩 토글 UX (C), 카메라 상태 실좌표 변환 (F), 인라인 높이 제거 (G) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_MapViewer.ts` | 이미지 종횡비 정합 (D) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_UI_Style.css` | `object-fit` 수정 (D), 뷰어 높이 통일 (G) |
|
||||
| `B04_wf1_Surface/B04_wf1_Surface_Engine_Pipeline.py` | (조건부) 스무딩 프리뷰 생성 누락 시 수정 (C) |
|
||||
|
||||
### 구현 체크리스트
|
||||
- [x] A-1. Engine: 분석 시 필터별 `processed/ground_points_{filter}.npz` 캐시 저장 (50만 규칙 + 메타)
|
||||
- [x] A-2. Router: point-cloud `filter` 파라미터 + 캐시 파일 서빙
|
||||
- [x] A-3. Router: 캐시 파일 부재 시 온디맨드 재계산·백필
|
||||
- [x] A-4. Api_Fetch/UI_Page: 필터 선택 → filter 파라미터 재조회·렌더
|
||||
- [x] B-1. Router: 요청 간격 등고선 파일 부재 시 온디맨드 생성 + 캐시 저장
|
||||
- [x] B-2. 폴백 glob 정리 (간격 무시 무음 동작 제거)
|
||||
- [x] C-1. tin/dtm 스무딩 프리뷰·등고선 파일 생성 여부 검증, 누락 시 Pipeline 수정
|
||||
- [x] C-2. 스무딩 미지원 기법에서 토글 비활성화
|
||||
- [x] D-1. VWorld 이미지 종횡비 정합 + canvas 오버레이 좌표 동기화
|
||||
- [x] F-1. Viewer/TerrainViewer: 카메라 상태를 실세계 좌표(m) 공통 프레임으로 emit/apply 변환 (축 컨벤션 정합 포함)
|
||||
- [x] F-2. 동기화 배율 일치 실행 검증 (양쪽 축척바 값 비교)
|
||||
- [x] G-1. TerrainViewer 인라인 높이 제거, 두 뷰어 높이 통일
|
||||
- [x] E-1. enableRouteStep 스텝 탐색 방식 개선
|
||||
- [x] E-2. 확정 토스트 스무딩 값 연결
|
||||
- [x] E-3. 확정 후 새로고침 시 WF2 스텝 활성화 실행 검증
|
||||
- [x] E-4. `SOURCE_FILTERS`에서 `ransac` 제거 확인
|
||||
- [x] E-5. 확정 성공 시 B05 자동 이동 (`goToWorkflowStage`)
|
||||
- [x] 700줄 제한 및 라우터 분리: `B04_wf1_Surface_Router.py`(643줄), `B04_wf1_Surface_Router_GIS.py`(175줄)로 분할하여 700줄 한계 준수
|
||||
- [x] GIS/VWorld 라우터 복원: `B04_wf1_Surface_Router_GIS.py` 유실 복구 및 `main.py` 기동 실패(ImportError) 해결
|
||||
|
||||
### 협의 완료 (2026-07-17 사용자 확정)
|
||||
- **확정 후 자동 이동**: 확정 성공 시 B05 자동 전환으로 확정 → E-5 반영
|
||||
- **A-3 백필 방식**: 온디맨드 재계산 채택 — 기존 프로젝트(캐시 파일 없음)에서 필터 첫 선택 시 서버가 `structured.npz`로 해당 필터만 재계산해 캐시 생성 후 응답. 첫 선택에만 수 초 대기 발생, 이후 즉시 표시
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# 이관 계획서 (Archived Plan)
|
||||
|
||||
- **이관 일자**: 2026-07-18
|
||||
- **대상 작업**: B04 전처리를 old 버전 방식으로 정렬 (2026-07-18)
|
||||
- **상태**: 완료 및 검증 완료로 활성 계획에서 이관
|
||||
|
||||
---
|
||||
|
||||
### 배경
|
||||
old 버전(`0_old/main.py` + `0_old/utils/`)은 업로드 직후 구조화→필터 4종→모델 15종→등고선 캐시→VWorld/GIS를 전부 사전 처리하고 문제없이 동작했다. 본 프로그램(B04)은 계산 코어를 그대로 포팅했으나 old의 3가지 장치가 빠졌고 등고선 캐시 간격이 어긋나 있었다:
|
||||
1. 지면 마스크 영구 저장(`mask_*.npy`) 없음 → 매 분석·미리보기마다 CSF/PMF 재계산
|
||||
2. `structured.npz` 존재 시 스킵 로직 없음 → 구조화 무조건 재실행
|
||||
3. 엔진 계층 로그 0건 + 백그라운드 트리거에 진행률 콜백 미전달 → 침묵 상태로 수 분 소요
|
||||
4. 등고선 사전 캐시 5.0m vs 프론트 요청 1.0m 불일치(작업 트리에서 1.0m로 수정됨) + on-demand 재계산이 `adaptive_contour_grid_resolution`(old에 없음)을 사용
|
||||
|
||||
방향: **최대한 old 방식을 따른다. old에 없는 신규 장치는 삭제한다. 문제가 생기면 그때 변경한다.**
|
||||
|
||||
### 저장 위치 매핑 (old → 본 프로그램)
|
||||
| old (`instance/{proj}/`) | 본 프로그램 (`{project_root}/B04_wf1_Surface/`) |
|
||||
|---|---|
|
||||
| `structured.npz` | `processed/structured.npz` (기존 동일) |
|
||||
| `mask_{filter}.npy` | `processed/mask_{filter}.npy` **(신규 저장)** |
|
||||
| `terrain_models/*` (모델·manifest·등고선) | `models/*` (기존 동일) |
|
||||
| `vworld_*`, GIS geojson | `processed/*` (기존 동일) |
|
||||
|
||||
### 완료 구현 체크리스트
|
||||
|
||||
**A. 마스크 영구 저장·재사용** — `B04_wf1_Surface_Engine.py`
|
||||
- [x] A-1. `run_surface_analysis`: 필터별 `processed/mask_{filter}.npy` 존재+길이 일치 시 `np.load(mmap_mode="r")` 재사용, 없으면 계산 후 `np.save`. `force=True`면 재계산
|
||||
- [x] A-2. `cache_ground_points`: mask 미전달 시 `build_ground_masks` 대신 저장된 `mask_{filter}.npy` 우선 로드 (없을 때만 필터 실행 후 저장)
|
||||
|
||||
**B. 구조화 스킵 + 입력 세대 검증** — `B04_wf1_Surface_Engine.py`
|
||||
- [x] B-1. `structured.npz` 존재 && `force=False`면 `structurize_las` 스킵 (old `upload_files` 스킵 로직과 동일 원리)
|
||||
- [x] B-2. 입력 LAS 정체성(파일명+크기+mtime)을 structured.npz(또는 sidecar json)에 기록, 불일치 시 구조화·마스크·모델 캐시를 force 취급으로 무효화 — old는 업로드마다 새 프로젝트 폴더라 이 문제가 없었으나 본 프로그램은 같은 폴더를 재사용하므로 필수
|
||||
|
||||
**C. 로깅·진행률 복원** (old의 print 진행 표시를 logger로 대체)
|
||||
- [x] C-1. `run_surface_analysis`: 단계별 시작/완료 `logger.info` + 소요시간(구조화/필터별/모델빌드/VWorld/GIS/총계)
|
||||
- [x] C-2. VWorld·GIS 다운로드 `except: pass` → `logger.warning`으로 실패 사유 기록 (old는 print로 출력했음)
|
||||
- [x] C-3. `build_all_terrain_models` 호출 시 progress reporter 연결 (`_report` 래핑, 70~90% 구간 배분)
|
||||
- [x] C-4. B03 `trigger_wf1_analysis_and_email`: `on_progress` 콜백 전달 → `write_surface_progress`로 progress.json 갱신 (업로드 자동 전처리도 진행률 노출)
|
||||
|
||||
**D. 등고선 old 정합** — `B04_wf1_Surface_Engine_Contour.py`, `B04_wf1_Surface_Router_Contour.py`, `B04_wf1_Surface_Engine_Pipeline.py`
|
||||
- [x] D-1. `adaptive_contour_grid_resolution` 삭제, on-demand 재계산도 old처럼 config 고정 격자(`SURFACE_CONTOUR_GRID_RESOLUTION_M=1.0`) 사용
|
||||
- [x] D-2. NURBS 스플라인 `s=0` → old 공식 `s=len(control_x)*len(control_y)*0.01` 복귀 (추출 로직을 old와 동일화; `CONTOUR_EXTRACTOR_VERSION=4`는 유지해 기존 캐시 전면 재생성)
|
||||
- [x] D-3. Pipeline 캐시 재사용 분기: dtm/tin은 `_smooth_` 등고선 파일 존재도 검사해 누락 시 재생성 (프론트 기본값이 스무딩 ON이므로)
|
||||
- [x] D-4. 사전 캐시 간격 1.0m 유지 (작업 트리 수정분 유지 — old config와 동일)
|
||||
- [x] D-5. 모델 (재)빌드 직전 해당 stem의 `contour_{filter}_{method}*.json` 전부 삭제 + 라우터 캐시 검사에 "모델 npz mtime > 등고선 캐시 mtime → 재계산" 추가 — 같은 경로를 세대 구분 없이 재사용하므로 구세대 등고선 잔존 방지 (외부 검토 지적 채택)
|
||||
- [x] D-6. `SURFACE_MODEL_PRECOMPUTE` 순서를 `dtm` 우선으로 변경 — TIN 등고선 사전 캐시가 `dtm_{filter}.npz` footprint를 참조하는데 tin이 먼저 빌드되면 footprint 미적용으로 캐시됨. old도 tin 우선이라 동일 결함이 있었으나 저비용 개선이므로 채택 (외부 검토 지적 채택)
|
||||
|
||||
**E. 이상한 것 삭제·정리**
|
||||
- [x] E-1. `run_surface_analysis`의 3중 `.prj` glob(`**/*.prj` 재귀 포함) 제거 → B03 입력 폴더에서 명시적 1회 탐색
|
||||
- [x] E-2. 재분석 시 해당 프로젝트의 `surface_models` 기존 행 삭제 후 INSERT — 실측 결과 한 프로젝트에 121행 누적, 전부 동일 파일 경로를 가리키며 디스크에는 일부만 존재(중단된 분석). 화면은 이 목록의 첫 매치를 선택하므로 "파일 없는 모델 선택 → 프리뷰/등고선 404"가 미반영 증상의 1차 원인 중 하나 (외부 검토 실측 채택)
|
||||
|
||||
**F. config 파라미터 old 값 복귀** — `config/config_system.py` (변경 시 config_signature 변경 → 모델 캐시 전면 재빌드됨)
|
||||
- [x] F-1. `SURFACE_IMPLICIT_MAX_POINTS_PER_TILE` 20000→10000, `SURFACE_IMPLICIT_SMOOTHING` 0.5→0.1
|
||||
- [x] F-2. `SURFACE_TIN_MAX_INPUT_POINTS` 200000→500000, `SURFACE_MAX_PREVIEW_VERTICES` 120000→500000
|
||||
- [x] F-3. `SURFACE_MESHFREE_MAX_MODEL_POINTS` 300000→500000, `SURFACE_MESHFREE_POINT_RADIUS_M` 0.5→0.15
|
||||
|
||||
**G. 프론트 경합 보완 (소규모)** — `B04_wf1_Surface_UI_TerrainViewer.ts`
|
||||
- [x] G-1. GLTF/PLY 로더 콜백에 요청 세대 토큰 가드 추가 — 선택 변경 후 늦게 도착한 이전 메쉬가 scene에 겹쳐 남는 문제 차단 (등고선 fetch에는 이미 가드 있음)
|
||||
|
||||
**H. 코딩 후 실측 테스트에서 발견·해결한 추가 문제 (2026-07-18 오후)**
|
||||
- [x] H-1. **TIN/meshfree 등고선 수 분 소요·행(Hang)의 진짜 원인 = scipy 버전**: venv의 scipy 1.13.1에서 `scipy.spatial.Delaunay.transform`(무게중심 변환 지연 계산)이 97만 심플렉스 기준 **114~280초** 소요 (실측). old 환경은 scipy 1.18.0(`0_old/requirements.txt`, UTF-16 인코딩 주의)이라 빨랐음. old 데이터를 현재 venv에 넣어도 동일하게 느린 것으로 교차 검증 완료 → 데이터·코드 문제 아님. **조치**: venv scipy를 1.16.3(현재 numpy 1.26.4와 호환되는 최신)으로 업그레이드, `requirements.txt` 핀 1.13.1→1.16.3 갱신. transform 15.8초로 단축, TIN 등고선 1개당 25~27초. 완전한 old 동등(scipy 1.18)을 위해서는 numpy 2.x 마이그레이션 필요 → Backlog 참조
|
||||
- [x] H-2. 진단 과정에서 `_grid_axes`를 float64로 바꿨다가 **오진으로 판명되어 old와 동일한 float32로 원복** (H-1이 진짜 원인; 최종 작업 트리에서 이 함수는 무변경)
|
||||
- [x] H-3. **분석 중 서버 프로세스 침묵 소멸의 원인 = uvicorn 자동 리로드**: `.env`의 `DEBUG=True` → `main.py:268`의 `reload=DEBUG` 활성화 → 분석 도중 .py 파일이 저장되면 백그라운드 분석 스레드째 재시작(2회 재현 확인). **조치**: `.env` `DEBUG=False`로 변경 + 사유 주석 추가
|
||||
- [x] H-4. **전처리 전체 재개 실측 검증 완료** (프로젝트 `storage/1/3/acb9…`, cloud_merged.las 606만 지면점): 15개 모델 + 등고선 캐시 21개 + VWorld 3종 + GIS 벡터 6종 전부 생성, manifest `completed`, 실패 0, **총 433초** (구조화 스킵·마스크 3종 캐시 재사용 각 0.03초·dtm 1종 캐시 재사용 상태 기준. 전체 신규 계산 시 구조화 ~2분 추가 예상). A·B·C·D 캐시/스킵/로그/진행률 로직 동작 확인됨
|
||||
|
||||
### 완료된 백로그 (Completed Backlog)
|
||||
- [x] **TIN coverage 마스크 rasterio.features.rasterize 대체 검토** (2026-07-18): `B04_wf1_Surface_Engine_Contour.py`의 `_tin_face_coverage_mask` 함수 내에서 shapely `union_all`과 `intersects_xy` 연산의 오버헤드를 줄이기 위해 rasterio의 `rasterize` 방식으로 전환 완료. (Delaunay transform 병목 해소와 병행해 성능 체감 효과 확보)
|
||||
@@ -0,0 +1,42 @@
|
||||
# 완료 계획: B06 종횡단도(도면) 렌더링 추가 — 0_old 버전 이식
|
||||
**이관일**: 2026-07-18
|
||||
**이관 원본**: `docs/raw/PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
### 배경
|
||||
B06에서 종횡단 생성·자동 실행까지는 완료됐지만, 결과 카드는 요약 수치(연장·횡단 개수·파일 경로)만 표시하고 도면은 없다. **0_old 버전에는 종횡단도 구현이 완전히 존재**하므로(React) 이를 현 프로젝트 규약(바닐라 TS + locale + 테마 CSS 변수)으로 이식한다.
|
||||
|
||||
**0_old 원본 설계** (이식 기준):
|
||||
- `0_old/frontend/src/LongitudinalProfile.tsx` — 종단면도 SVG: 표고 폴리라인(평활 없음), 표고 그리드+라벨, **클릭 가능한 측점 수직선**(BP=주황/EP=보라 점선/선택=빨강 굵게), 축 제목("BP 기준 누적거리", "지반고(m)")
|
||||
- `0_old/frontend/src/CrossSectionCard.tsx` — 횡단면도 카드: 헤더(측점명·누적거리·BP/EP/20m 구분), x/y 눈금, invalid 구간 분절, **중심선(offset 0) 빨강 십자 마커**, 푸터(중심고·방위각)
|
||||
- `0_old/frontend/src/CrossSectionGrid.tsx` — 전체 횡단면도를 그리드로 일괄 표시, 선택 카드 하이라이트
|
||||
- `0_old/frontend/src/SectionGenerationStage.tsx:33-37` — **Y축 스케일 동기화**: 종단+횡단 전체 표고 min/max로 공통 pixelsPerMeter 계산, 모든 도면이 같은 수직 스케일 공유
|
||||
- **높이 배율(vertical_exaggeration)** 옵션: 사용자 입력(기본 1.0), `0_old/config.py:179 SECTION_VERTICAL_EXAGGERATION`으로 config 관리 — **현 config_system.py에는 누락됨 → 추가 필요**
|
||||
- 종단도 측점 클릭 → 해당 횡단 카드 선택·스크롤 동기화
|
||||
|
||||
### 구현 체크리스트
|
||||
|
||||
**백엔드**
|
||||
- [x] 1. `config/config_system.py`: `SECTION_VERTICAL_EXAGGERATION`(기본 1.0, env 오버라이드) 추가 — 0_old config.py:179 이식
|
||||
- [x] 2. `B06_wf3_ProfileCross_Router.py`: `GET /api/projects/{project_id}/sections/{route_id}/detail` 추가 — 프로젝트 루트에서 longitudinal.json + cross_sections/*.json을 읽어 한 응답으로 반환: `{longitudinal: {length_m, samples, stations}, cross_sections: [...]}` (13개×61샘플 규모, 단일 응답 무리 없음). 파일 접근은 기존 `resolve_stored_project_path` + `get_project_storage_relative_path` 재사용, DB의 file_path로 경로 결정
|
||||
- [x] 3. `B06_wf3_ProfileCross_Schema.py`: `SectionDetailResponse` 추가(samples는 `list[dict]` 전달, 세부 모델 최소화). `SectionContextResponse.defaults`에 `vertical_exaggeration` 추가
|
||||
|
||||
**프론트엔드**
|
||||
- [x] 4. 신규 `B06_wf3_ProfileCross_UI_Section_View.ts`: 0_old 3개 컴포넌트를 바닐라 TS로 이식
|
||||
- `createLongitudinalProfile(...)` — LongitudinalProfile.tsx 이식: 측점 수직선 클릭 시 콜백, 높이 배율·yScale 동기화 반영
|
||||
- `createCrossSectionCard(...)` / 그리드 컨테이너 — CrossSectionCard/Grid.tsx 이식: 십자 마커·눈금·헤더/푸터 포함
|
||||
- Y축 동기화: SectionGenerationStage.tsx:33-37 로직 이식(전체 표고 min/max → 공통 pixelsPerMeter)
|
||||
- 색상은 하드코딩(#2563eb 등) 대신 테마 CSS 변수 사용
|
||||
- [x] 5. `B06_wf3_ProfileCross_Api_Fetch.ts`: `SectionDetailResponse` 인터페이스 + `fetchSectionDetail(projectId, routeId)` 추가
|
||||
- [x] 6. `B06_wf3_ProfileCross_UI_Page.ts`: 옵션 그룹에 "높이 배율" 필드 추가(context defaults로 프리필, 변경 시 도면만 재렌더 — 재생성 불필요). 결과 카드 아래 메인 영역에 종단면도(전체 폭) + 횡단면도 그리드 배치. 자동 생성/재생성/기존 결과 표시 시 `fetchSectionDetail` 호출해 도면 렌더. 종단도 측점 클릭 → 횡단 카드 선택+스크롤
|
||||
- [x] 7. `B06_wf3_ProfileCross_UI_Style.css`: 0_old `sectionGeneration.css` 참조해 도면·그리드·선택 하이라이트 스타일 이식(테마 변수 기반)
|
||||
- [x] 8. locale: 종단면도/횡단면도 제목, 높이 배율, 축 라벨, BP/EP/측점 구분 등 신규 키 등록
|
||||
- [x] 9. 700줄 제한 점검: UI_Page 초과 시 도면 연결 로직도 Section_View로 이동
|
||||
|
||||
### 검증 방법
|
||||
1. `GET /sections/9/detail` — 종단 samples/stations + 횡단 13개 반환 확인
|
||||
2. 브라우저 B06 진입 → 종단면도(표고선+측점 수직선+라벨), 횡단면도 그리드(13개 카드) 표시 확인
|
||||
3. 종단도 측점 클릭 → 해당 횡단 카드 하이라이트+스크롤 확인
|
||||
4. 높이 배율 변경 → 도면 세로 과장 즉시 반영 확인
|
||||
5. 재생성 후 도면 갱신 확인, `npm run typecheck`, 서버 재시작 후 확인
|
||||
@@ -0,0 +1,45 @@
|
||||
# 완료 계획: B06 페이지 개선 — 진입 즉시 자동 생성 + 경로 컨텍스트 읽기 전용 표시
|
||||
**이관일**: 2026-07-18
|
||||
**이관 원본**: `docs/raw/PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
### 배경
|
||||
B05 "경로 확정" 후 B06 진입 시, 경로 ID·지면 필터·지표면 표현·좌표계·스무딩을 사용자가 직접 입력해야 하고 결과도 바로 나오지 않는다(`B06_wf3_ProfileCross_UI_Page.ts:66-110`). 이 값들은 B04(지면 필터·지표면 표현·스무딩·좌표계)와 B05(경로 확정)에서 이미 결정된 값이므로 **읽기 전용 표시**로 바꾸고, **진입 즉시 config 기본 옵션으로 종횡단을 자동 생성**해 결과를 바로 보여준다. 측정/횡단 옵션은 config 기본값을 폼에 미리 채워두고, 사용자가 수정하면 "재생성" 버튼으로 다시 실행한다.
|
||||
|
||||
### 조사 결과 (설계 근거)
|
||||
- **B04 확정값 공용 함수 재사용**: `common_util_surface_confirmation.py:42 get_surface_confirmation_params()`가 B04 확정 스냅샷(stage 1 params)에서 `source_filter/method/smooth/contour_interval_m`을 반환 (B05도 동일 함수 사용) → filter/method/smooth 출처로 채택. `surface_models.generation_params` JSON 파싱보다 단순하고 B05 화면과 값 일치 보장
|
||||
- **좌표계**: `surface_models.crs_epsg` — B03 업로드 시 pyproj로 실제 감지·저장된 값. 확정 경로의 `routes.surface_model_id`로 조회
|
||||
- `projects.crs_epsg`는 `B02_ProjRegister_Repository.py:72`에서 5178 하드코딩 → 신뢰 불가, 사용 안 함 (B02 수정은 스코프 밖)
|
||||
- **smooth도 상속 대상**: B04에서 선정된 맵으로 경로를 만들었으므로 종횡단도 동일 조건이어야 함 → 체크박스 제거, 읽기 전용 표시 (사용자 확인 완료)
|
||||
- `SectionGenerationOptions`(`B06_wf3_ProfileCross_Engine_Section.py:26-32`)가 이미 `config_system.py`의 `SECTION_*` 기본값 참조 → 새 엔드포인트 응답에 기본값을 실어 프론트에 노출(프론트 하드코딩 이중관리 방지)
|
||||
- 현재 `GET /sections/{route_id}` 요약 응답에는 종단 메타(id/경로/상태)만 있고 연장·횡단 개수가 없음 → 재진입 시 기존 결과 카드를 그리려면 보강 필요
|
||||
|
||||
### 구현 체크리스트
|
||||
|
||||
**백엔드**
|
||||
- [x] 1. `B06_wf3_ProfileCross_Repository.py`: `get_confirmed_route_context(connection, project_id)` 추가 — `routes`(status='CONFIRMED', `computed_at DESC, id DESC`) LEFT JOIN `surface_models`로 route_id + crs_epsg 반환. 경로 없으면 None. 추가로 `count_cross_sections(connection, route_id)` 및 `get_longitudinal_section`의 data(JSON, length_m 포함) 반환 보강
|
||||
- [x] 2. `B06_wf3_ProfileCross_Schema.py`: `SectionOptionDefaults`(station_interval_m, cross_half_width_m, cross_sample_interval_m, long_sample_interval_m), `SectionContextResponse`(project_id, route_id/filter_key/method/smooth/crs_epsg는 nullable, defaults) 추가. `SectionSummaryResponse`에 length_m·cross_section_count 필드 보강
|
||||
- [x] 3. `B06_wf3_ProfileCross_Router.py`: `GET /api/projects/{project_id}/sections/context` 추가 — `get_confirmed_route_context` + `get_surface_confirmation_params` 조합으로 응답 구성. 경로 없으면 null 필드로 200 반환(404 아님). defaults는 `SectionGenerationOptions()` 인스턴스 값 매핑. `get_sections`는 보강된 요약(연장·횡단 개수) 반환
|
||||
|
||||
**프론트엔드**
|
||||
- [x] 4. `B06_wf3_ProfileCross_Api_Fetch.ts`: `SectionContextResponse` 인터페이스 + `fetchSectionContext(projectId)` 추가. `SectionSummaryResponse` 보강 필드 반영
|
||||
- [x] 5. `B06_wf3_ProfileCross_UI_Page.ts`:
|
||||
- routeGroup of input fields 4개(routeIdField/filterField/methodField/crsField)와 smooth 체크박스를 읽기 전용 표시 행(B04 `buildInfoLine` 패턴)으로 교체 — 경로 ID / 지면 필터 / 지표면 표현 / 스무딩 / 좌표계(EPSG:{n}) 표시
|
||||
- 옵션 4개 필드에 context 응답의 defaults 프리필
|
||||
- 진입 흐름: `fetchSectionContext` ∥ `fetchWorkflowState` 병렬 호출 → ① 확정 경로 없으면 안내 + 버튼 비활성화 ② 기존 종횡단 있으면(`getSections`) 재생성 없이 기존 결과 카드 표시 ③ 없으면 config 기본 옵션으로 `generateSections` 자동 실행 후 결과 표시
|
||||
- 생성 버튼 라벨을 "재생성" 의미로 변경, 클릭 시 폼 파싱 대신 컨텍스트 저장값(route_id/filter_key/method/smooth/crs) + 사용자 조정 옵션으로 요청
|
||||
- [x] 6. `B06_wf3_ProfileCross_UI_Style.css`: 읽기 전용 표시 행 스타일 추가(필요 시)
|
||||
- [x] 7. locale 키 정리: `ui_template_locale`에 읽기 전용 라벨·자동 생성 안내·재생성 버튼 문구 등록(신규 필요분만)
|
||||
|
||||
**변경하지 않는 것**
|
||||
- `SectionGenerationOptions` / `config_system.py`의 `SECTION_*` / `_build_options` (이미 config 기반)
|
||||
- `SectionGenerateRequest` 스키마 필드 구성(프론트가 컨텍스트 값으로 채워 보내는 방식만 변경)
|
||||
- POST generate/confirm 엔드포인트 로직
|
||||
|
||||
### 검증 방법
|
||||
1. `GET /api/projects/{id}/sections/context` — 확정 경로 있는 프로젝트에서 route_id/filter_key/method/smooth/crs_epsg/defaults 확인
|
||||
2. 브라우저: B05 경로 확정 → B06 진입 시 **자동 생성 실행 후 결과(연장·횡단 개수·파일)가 바로 표시**되는지, 경로 정보 5개(ID/필터/표현/스무딩/좌표계)가 읽기 전용으로 표시되는지, 옵션 필드에 20.0/15.0/0.5/1.0 프리필 확인
|
||||
3. B06 재진입 시 재생성 없이 기존 결과가 표시되는지 확인
|
||||
4. 옵션 수정 → 재생성 버튼 클릭 시 새 옵션으로 재실행되는지 확인
|
||||
5. 경로 없는 프로젝트로 B06 진입 시 오류 없이 안내 표시 확인
|
||||
@@ -0,0 +1,84 @@
|
||||
# Plan History: B04 자동 확정 및 B05 페이지 재구성 (WF1/WF2)
|
||||
|
||||
- **이관 일시**: 2026-07-18
|
||||
- **상태**: 완료 이관 (Archived)
|
||||
|
||||
---
|
||||
|
||||
## 활성 작업 1 — 역할 기반 B04 자동 확정 (WF1)
|
||||
|
||||
### 배경 및 합의사항 (2026-07-18)
|
||||
- B04는 시스템 관리자(SYSTEM_ADMIN)의 내부 테스트/검증용 페이지. ADMIN/USER는 볼 필요 없음.
|
||||
- ADMIN/USER는 B03 업로드→전처리→WF1 분석 완료 시 백엔드가 **"모델 확정 버튼을 누른 것처럼"** 기본값으로 자동 확정하고 B05로 진행.
|
||||
- 자동 확정 기본값은 `config/config_system.py`에서 관리 (고정값 아님 — 시스템 관리자가 config를 바꾸면 그 값이 적용됨).
|
||||
- **값 우선순위 (2026-07-18 추가 합의)**: 확정 시 적용값(수동·자동 공통)은 DB(stage 1 `params` 스냅샷)에 저장. 읽을 때는 **DB 스냅샷이 있으면 스냅샷 우선, 없으면 config 기본값 폴백**. 이후 config가 바뀌어도 이미 확정된 프로젝트는 당시 적용값을 유지한다.
|
||||
- 자동/수동 확정의 구분 로직은 두지 않음: 동일한 확정 경로에 "기본값을 적용하느냐(일반 역할) / 직접 선택하느냐(시스템 관리자)" 차이만 존재.
|
||||
- B04로 넘겨야 할 선택값 3종: 지면 필터, 지표면 표현 방식, 등고선 간격 (+스무딩). 확정 시 stage 1 `params`에 스냅샷 저장하여 B05가 읽음.
|
||||
- 스텝바의 B04 단계는 일반 역할에게 **비활성화** (완전 숨김 여부는 추후 결정).
|
||||
- 역할 판정은 프론트 전달값이 아닌 **세션의 서버 측 role** 사용.
|
||||
|
||||
### 사전 결정 사항 (구현 시 준수)
|
||||
- **[불일치 발견] stage 1 COMPLETE 시점 일원화**: 현재 `B03_FileInput_Router.py:234`가 분석 DB 저장 직후 `complete_stage(1)`을 호출함. 이는 "사용자가 확정해야 COMPLETE" 게이팅 원칙(wiki workflow_state)과 충돌. → 본 작업에서 `complete_stage(1)`은 **확정 로직 내부에서만** 호출하도록 일원화한다. (SYSTEM_ADMIN: B04 확정 시 / 일반 역할: 자동 확정 시)
|
||||
- 기본값 매칭 모델이 없거나 분석 실패 시: 임의 모델을 확정하지 않는다. 확정 보류 + 로그 + B03 진행 메시지로 원인 노출.
|
||||
- 재분석 시: 기존 `start_stage`가 확정 클리어·STALE 전파 수행(현행 유지) → 분석 완료 시 일반 역할은 다시 자동 확정.
|
||||
|
||||
### 구현 체크리스트 — 백엔드
|
||||
- [x] `config/config_system.py`: 자동 확정 기본값 추가 — `SURFACE_CONFIRM_DEFAULT_FILTER="csf"`, `SURFACE_CONFIRM_DEFAULT_METHOD="dtm"`, `SURFACE_CONFIRM_DEFAULT_SMOOTH=True`. 등고선 간격은 기존 `SURFACE_CONTOUR_INTERVAL_M`(1.0) 재사용. (기본값 변경 방법 = config 직접 수정 또는 `.env` 오버라이드, 서버 재시작 시 반영. 별도 설정 UI 없음 — 합의사항)
|
||||
- [x] `B04_wf1_Surface_Router.py`: `confirm_surface()`(:206)의 확정 트랜잭션(CONFIRMED 전환 + `complete_stage(1)` + params 스냅샷 저장)을 재사용 가능한 서비스 함수로 분리 (API 핸들러와 자동 확정이 공유).
|
||||
- [x] `B04_wf1_Surface_Schema.py`: `SurfaceConfirmRequest`에 선택 필드 `smooth: bool | None`, `contour_interval_m: float | None` 추가. 확정 시 `project_workflow_stages(stage 1).params`에 `{source_filter, method, smooth, contour_interval_m}` 병합 저장.
|
||||
- [x] **스냅샷 읽기 헬퍼**: stage 1 params 스냅샷 조회 함수 신설(공통) — 스냅샷 존재 시 그 값을, 없으면 config 기본값을 반환. B05 초기값·자동 확정 재실행 등 읽기 경로는 모두 이 헬퍼를 경유 (config 변경이 기존 확정 프로젝트에 소급 적용되지 않도록 보장).
|
||||
- [x] `B04_wf1_Surface_UI_Page.ts` / `_Api_Fetch.ts`: 수동 확정 시 현재 UI의 스무딩·등고선 간격 값을 confirm 요청에 포함.
|
||||
- [x] `B03_FileInput_Router.py`: 업로드/재분석 트리거 지점(:391, :617)에서 세션 사용자 role을 `trigger_wf1_analysis_and_email()`에 전달. 분석 DB 저장 성공 후 role ≠ SYSTEM_ADMIN이면 config 기본값 매칭 `surface_models` 행 조회 → 확정 서비스 호출(자동 확정). `:234`의 `complete_stage(1)` 직접 호출 제거(위 일원화 방침).
|
||||
- [x] `write_surface_progress` 완료 메시지 역할 분기: 일반 역할 "지표면 모델 자동 확정 완료 — 노선 설계로 이동하세요" / SYSTEM_ADMIN 기존 문구 유지.
|
||||
|
||||
### 구현 체크리스트 — 프론트엔드
|
||||
- [x] 공통 스텝바(`ui_template_workflow_layout.ts`의 `createStepBar` / `b_page_scaffold`): `fetchDashboardMe()` role 기준으로 일반 역할에게 B04 스텝 비활성화(disabled) 처리.
|
||||
- [x] B03 완료 CTA/폴링 분기: WF1 완료 시 SYSTEM_ADMIN → B04, 일반 역할 → B05로 이동.
|
||||
- [x] B04 진입 가드: 일반 역할이 URL 직접 진입 시 — stage 1 COMPLETE(확정됨)면 B05로, 아니면 B03으로 리다이렉트.
|
||||
|
||||
### 검증 기준
|
||||
- SYSTEM_ADMIN: 기존 흐름 그대로 (B03→B04 검토→확정→B05). 확정 시 params 스냅샷 저장 확인.
|
||||
- ADMIN/USER: 업로드만으로 B05 진입 가능. DB에 CONFIRMED 모델 + stage1 params 스냅샷 존재(시스템 관리자가 사후 확인 가능). B04 스텝 비활성화·URL 가드 동작.
|
||||
- 기본값 매칭 모델 부재 시 자동 확정이 일어나지 않고 원인 메시지 노출.
|
||||
|
||||
---
|
||||
|
||||
## 활성 작업 2 — B05 페이지 재구성 (0_old 기능 이식)
|
||||
|
||||
### 배경 및 합의사항 (2026-07-18)
|
||||
- 신규 B05(`B05_wf2_Route_UI_Page.ts`, 378줄)는 좌표 수기입력 폼 + 결과 메트릭 카드뿐이며 **3D 뷰어가 미구현** 상태.
|
||||
- 신규 템플릿 유지: `createWorkflowLayout` — 좌측 접이식 패널 + 우측 진행단계 오버레이 + mainContent(브라우저 대부분) = 3D 뷰포트.
|
||||
- 좌측 패널 내부는 전부 삭제하고 0_old `RouteDesignStage.tsx`(2,171줄)의 기능을 신규 템플릿 위에 재구성.
|
||||
- 3D 지형 로딩은 B04에 이미 있는 인프라 재사용: `surface/models/{model_id}/preview`(GLTF) + `.../contour?interval=` API, `B04_wf1_Surface_UI_Camera.ts` 유틸.
|
||||
- 확정 모델·선택 스냅샷(필터/표현/스무딩/등고선 간격)은 활성 작업 1의 stage 1 params에서 읽어 초기값으로 적용.
|
||||
- 구현은 2단계로 나눔 (기획자 재량 합의). **1차·2차 모두 이번 작업 범위** — 릴리즈 분리가 아니라 구현·검증 순서이며, 2차(AP/FP·경고·확정 제한 등 구버전 기능)까지 완료해야 본 작업 종료.
|
||||
|
||||
### 사전 결정 사항 (구현 시 준수)
|
||||
- **[불일치 발견] WF2 상태 분리 (계산≠확정)**: 현재 `solve_route()`가 계산 성공만으로 `complete_stage(2)` 호출(`B05_wf2_Route_Router.py:96`). stage 1과 동일한 조기 완료 결함 → `complete_stage(2)`는 **경로 확정(`confirm_latest_route`)에서만** 호출하도록 일원화. 계산은 `start_stage`(IN_PROGRESS)+결과 저장까지만.
|
||||
- **[API 신설] 최신 경로 조회 (DB 기반)**: `RouteSolveResponse`에는 `route_data_path`(파일 경로)만 있고 좌표가 없음. 다만 계산 시 렌더 좌표는 이미 `route_points` 테이블에 저장되고(`:85` `insert_route_points`), 요청 파라미터도 stage 2 `params`에 저장됨(`:51`). → **`GET /{project_id}/route/latest` 신설**: 최신 route(확정 여부 포함) + route_points + 사용 파라미터를 DB에서 반환. 3D 경로 렌더링과 새로고침 복원을 이 API 하나로 해결 (GeoJSON 파일 서빙안은 폐기).
|
||||
- 700줄 제한: 프론트 4파일 분리 — `B05_wf2_Route_UI_Page.ts`(조립) / `_UI_Panel.ts`(좌측 패널) / `_UI_Viewer.ts`(지형+등고선+씬) / `_UI_Markers.ts`(포인트·경로 인터랙션).
|
||||
- 좌표계 변환(모델↔씬)은 0_old `transformToScene`/`transformToModel`(RouteDesignStage.tsx:153,178) 방식 준용.
|
||||
|
||||
### 구현 체크리스트 — 1차 (핵심 경로 설계 루프)
|
||||
- [x] 좌측 패널 기존 폼(BP/EP/CP 수기입력·읽기전용 지표면 폼) 전면 제거, 0_old 섹션 구조로 재구성: 뷰 컨트롤 / 등고선 간격 / 포인트 팔레트 / 임도 기준·옵션 / 결과 카드 / 계산·확정 버튼.
|
||||
- [x] `_UI_Viewer.ts` 신설: 확정 모델 preview(GLTF, meshfree는 PLY) 자동 로딩, 카메라 뷰핏(기본 탑뷰), 등고선 렌더링(초기 간격 = stage1 params, 변경·재적용 가능), 지표면·등고선 표시 토글.
|
||||
- [x] BP/EP/CP 배치: 팔레트 칩 드래그&드롭 + 지형 레이캐스팅 배치, 마커 렌더링, 배치값 ↔ 요청 페이로드 동기화.
|
||||
- [x] 경로 계산(`solveRoute`) 연동: 알고리즘(dijkstra/ridge_valley)·등급(간선/지선/작업)·최소 곡선반경·오르막/내리막 경사 상한 입력 반영.
|
||||
- [x] `GET /{project_id}/route/latest` API 신설(백엔드) + 3D 경로 라인 렌더링.
|
||||
- [x] **새로고침 복원**: B05 진입 시 최신 route + stage 2 params 로드 → 포인트(BP/EP/CP/AP/FP)·옵션·경로 라인·메트릭 복원.
|
||||
- [x] **WF2 상태 분리**: `solve_route()`의 `complete_stage(2)`(:96) 제거, `confirm_latest_route()`에서만 COMPLETE 처리.
|
||||
- [x] 결과 메트릭 카드(연장·경사·비용) + 입력 변경 시 "재탐색 필요" STALE 배지.
|
||||
- [x] **확정 제한**: 경로 미계산 또는 stale(입력 변경 후 재계산 전) 상태면 확정 버튼 비활성화 — 구버전 동작 준용. 확정(`confirmRoute`)은 이 게이트 통과 시에만 가능.
|
||||
|
||||
### 구현 체크리스트 — 2차 (고급 인터랙션)
|
||||
- [x] AP(회피)/FP(금지) 팔레트 배치 + 반경 입력 팝오버 + 반경 수정.
|
||||
- [x] 선택 포인트 상세 설정: 위치 이동 모드, 삭제.
|
||||
- [x] 경로 경고 마커·수직선(perpendiculars) 렌더링.
|
||||
- [x] 뷰 프리셋(iso/top/front/side), 축 표시 토글, 초기화 버튼.
|
||||
- [x] "사용한 조건" 접기, "최적경로란?" 도움말, 포장 여부·경사 하한(ridge_valley 전용) 입력.
|
||||
- [x] 오르막/내리막 경사 하한 등 0_old 잔여 옵션 정합성 점검.
|
||||
|
||||
### 검증 기준
|
||||
- 일반 역할이 B03 업로드 직후 B05 진입 시: 자동 확정 모델의 지형+등고선이 3D로 표시되고, 등고선 간격 초기값 = 확정 스냅샷 값.
|
||||
- BP/EP 배치→계산→경로 라인 3D 표시→확정까지 전체 루프가 0_old와 동등하게 동작.
|
||||
- 좌측 패널 접기/펼치기, 우측 진행단계 오버레이 등 신규 템플릿 동작 유지. 700줄 제한 준수.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Plan History: B03 업로드 CRS 정상화
|
||||
|
||||
- **완료 일자**: 2026-07-18
|
||||
- **수행 주체**: 개발 전문 AI
|
||||
- **검증 결과**: 합격 (PASS)
|
||||
|
||||
---
|
||||
|
||||
### B03 업로드 CRS 정상화
|
||||
|
||||
#### 목표
|
||||
|
||||
- 복합 CRS 전체의 `to_epsg() == None`을 수평 CRS 판별 실패로 오인하지 않는다.
|
||||
- GeoTIFF·PRJ·LAS에서 수평 CRS와 수직 CRS를 분리해 동일한 메타데이터 구조로 저장한다.
|
||||
- 기존 DB 연동 필드인 `epsg`에는 지도 좌표 변환에 사용할 수평 EPSG를 유지한다.
|
||||
- KNGeoid24처럼 공식 EPSG가 없는 수직 CRS는 오류나 임의의 EPSG로 치환하지 않고 사용자 정의 수직 CRS로 기록한다.
|
||||
- 실제로 알 수 없는 수평 CRS는 추측하지 않고 상태값으로 명확히 남긴다.
|
||||
|
||||
#### 구현 원칙
|
||||
|
||||
- GeoTIFF는 파일 내부 CRS를 기준으로 분석하며, 영상 래스터의 수직 CRS를 억지로 합성하지 않는다.
|
||||
- LAS와 PRJ의 복합 CRS는 구성 요소를 분해하여 수평 EPSG와 수직 기준을 각각 판정한다.
|
||||
- `GTIFF_SRS_SOURCE=EPSG` 같은 전역 환경 설정과 WKT 이름 기반 임의 매칭은 실제 실패 자료가 확보되기 전까지 추가하지 않는다.
|
||||
- 알려진 CRS만 선택적으로 정상화하고, 예상하지 못한 CRS 오류나 경고를 전역으로 숨기지 않는다.
|
||||
- Rasterio와 Pyproj가 사용하는 PROJ 버전 차이는 이번 결함의 직접 원인이 아니므로 패키지 변경 없이 검증 결과만 남긴다.
|
||||
|
||||
#### 구현 체크리스트
|
||||
|
||||
- [x] 공통 CRS 정상화 함수에서 수평·수직 CRS 구성 요소를 분리한다.
|
||||
- [x] 기존 `epsg` 필드가 복합 CRS에서도 수평 EPSG를 반환하도록 수정한다.
|
||||
- [x] `horizontal_crs`, `vertical_crs`, `crs_status` 상세 메타데이터를 추가한다.
|
||||
- [x] KNGeoid24 사설 코드가 `custom_vertical_crs` 상태로 정상 기록되는지 확인한다.
|
||||
- [x] 현재 영구저장소의 TIFF가 `EPSG:5187`, 영상 자료로 유지되는지 확인한다.
|
||||
- [x] 현재 영구저장소의 PRJ와 LAS가 수평 `EPSG:5187` 및 수직 `KNGeoid24`로 분리되는지 확인한다.
|
||||
- [x] Python 정적 검사와 포맷 검사를 통과한다.
|
||||
- [ ] Graphify, 위키 인제스트, 위키 린트는 담당 AI 작업으로 남긴다.
|
||||
|
||||
#### 수행 결과 (2026-07-18)
|
||||
|
||||
- `B03_FileInput_Engine_Analyze.py`에 `normalize_crs_metadata()`를 추가하여 GeoTIFF·PRJ·LAS가 동일한 CRS 메타데이터 구조를 사용하도록 변경했다.
|
||||
- 기존 `epsg` 필드는 복합 CRS 전체 코드가 아니라 수평 구성 요소의 EPSG를 반환한다. 따라서 KNGeoid24가 결합된 PRJ와 LAS도 `epsg=5187`로 DB에 저장할 수 있다.
|
||||
- 상세 판정 결과로 `horizontal_crs`, `vertical_crs`, `crs_status`를 추가했다. 상태는 `identified`, `custom_vertical_crs`, `unknown_horizontal_crs`, `missing_crs`로 구분한다.
|
||||
- KNGeoid24 WKT의 사설 `EPSG:9995/99999` 표식은 원문을 변경하지 않고 파싱용 WKT에서만 분리하며, `custom_authority_codes`에 원래 코드를 보존한다.
|
||||
- Rasterio가 GeoTIFF를 열 때 발생시키는 `EPSG:9995/99999 crs not found` 로그만 파일 오픈 구간에서 선택적으로 차단하고 B03 안내 로그로 변환했다. 다른 GDAL/PROJ 경고는 차단하지 않는다.
|
||||
- 동시 업로드 시 다른 작업의 로그를 잘못 차단하지 않도록 경고 필터를 생성한 스레드에서 발생한 메시지만 처리한다.
|
||||
- 실제 영구저장소 `result.tif` 검증 결과는 `EPSG:5187`, 4밴드 `uint8`, `likely_type=image`, `crs_status=identified`이며 수직 CRS를 영상 메타데이터에 합성하지 않았다.
|
||||
- 실제 `result.prj`와 `cloud_merged.las` 헤더 검증 결과는 수평 `EPSG:5187`, 수직 `Korean National Geoid Model (KNGeoid24)`, `crs_status=custom_vertical_crs`이다.
|
||||
- 등록되지 않은 임의 수평 투영 CRS는 `epsg=None`, `crs_status=unknown_horizontal_crs`로 유지하여 EPSG를 추측하지 않는 것을 확인했다.
|
||||
- 현재 런타임은 Rasterio 1.5.0/PROJ 9.7.1과 Pyproj 3.7.2/PROJ 9.5.1이며, 재현 결함이 없어 패키지나 전역 `GTIFF_SRS_SOURCE` 설정을 변경하지 않았다.
|
||||
- `ruff format`, `ruff check`, `python -m py_compile`, 실제 저장 파일 기반 회귀 assertion, JSON 직렬화 검증을 통과했다.
|
||||
- CRS 검증과 무관한 1.7GB LAS classification 전수 스캔은 다시 실행하지 않고 LAS 헤더만 검증했다.
|
||||
- 사용자 요청에 따라 Graphify, 위키 인제스트, 위키 린트는 실행하지 않았으며 담당 AI의 후속 작업으로 남겼다.
|
||||
@@ -0,0 +1,51 @@
|
||||
# 2026-07-18 Python GIS 의존성 업그레이드 작업 이력
|
||||
|
||||
## 상태
|
||||
|
||||
- 완료일: 2026-07-18
|
||||
- 결과: NumPy 2.x 및 GIS 스택 업그레이드, Trimesh·Pyogrio 반영, WhiteboxTools 실행 복구 완료
|
||||
|
||||
## 확정 버전
|
||||
|
||||
| 패키지 | 작업 전 | 확정 버전 | 처리 결과 |
|
||||
|---|---:|---:|---|
|
||||
| numpy | 1.26.4 | 2.5.0 | 업그레이드 |
|
||||
| scipy | 1.16.3 | 1.18.0 | 업그레이드 |
|
||||
| shapely | 2.0.6 | 2.1.2 | 업그레이드 |
|
||||
| rasterio | 1.3.9 | 1.5.0 | 업그레이드 |
|
||||
| laspy | 2.4.1 | 2.7.0 | 업그레이드 |
|
||||
| geopandas | 0.14.0 | 1.1.4 | 업그레이드 |
|
||||
| trimesh | 3.23.0 | 4.12.2 | GLB 검증 후 확정 업그레이드 |
|
||||
| pyogrio | 없음 | 0.13.0 | 신규 추가 |
|
||||
| whitebox | 2.3.0 | 2.3.6 | 업그레이드 |
|
||||
| setuptools | 83.0.0 | 83.0.0 | 유지 |
|
||||
|
||||
## 변경 파일
|
||||
|
||||
| 파일 | 변경 내용 |
|
||||
|---|---|
|
||||
| `requirements.txt` | 위 9개 패키지의 확정 핀 반영 및 Pyogrio 추가 |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Skeleton.py` | Whitebox 임시 DEM에 CRS GeoKey를 부여하여 실제 D8 실행 복구 |
|
||||
|
||||
## WhiteboxTools 해결 내용
|
||||
|
||||
1. `whitebox==2.3.0`이 제거된 `pkg_resources`를 사용해 `setuptools==83.0.0`에서 생성 실패하던 문제를 `whitebox==2.3.6` 업그레이드로 해결했다.
|
||||
2. 최초 실행 시 WhiteboxTools Windows 바이너리를 내려받았고 `WhiteboxTools v2.4.0`의 `version()` 호출을 확인했다.
|
||||
3. B05 임시 GeoTIFF에 GeoKey가 없어 실행 바이너리가 중단되던 문제를 `crs="EPSG:3857"` 지정으로 해결했다. 임시 파일은 D8 격자 계산에만 사용되며 결과 산출물로 보존되지 않는다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
| 검증 | 결과 |
|
||||
|---|---|
|
||||
| `pip install -r requirements.txt` | 전체 의존성 동시 설치 성공 |
|
||||
| `pip check` | `No broken requirements found` |
|
||||
| 핵심 10개 패키지 import 및 버전 확인 | 통과 |
|
||||
| B04 프로젝트 함수 기반 GLB 생성 | 통과, GLB 헤더 확인 |
|
||||
| Pyogrio GeoJSON 쓰기·읽기 왕복 | 통과 |
|
||||
| B05 Whitebox D8 직접 실행 | 통과, NumPy 폴백 미사용, `max_acc=9.0` |
|
||||
| Ruff format/check 및 `py_compile` | 통과 |
|
||||
|
||||
## 후속 관리
|
||||
|
||||
- Rasterio와 Pyproj가 서로 다른 PROJ 런타임을 포함하므로 CRS 변환 이상이 재현될 경우 우선 점검한다.
|
||||
- Graphify, Ingest, Lint는 별도 AI 작업 범위로 남긴다.
|
||||
@@ -0,0 +1,20 @@
|
||||
# 완료된 계획: B05 레이아웃 및 앱 셸 푸터 제거 개선
|
||||
- **완료 일자**: 2026-07-18
|
||||
- **이관 소스**: `docs/raw/PLAN.md`
|
||||
- **상태**: 완료 (Completed)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 활성 작업 이력
|
||||
|
||||
### 배경 및 합의된 방향
|
||||
* B그룹 전체(B01~B11) 워크플로우 페이지들은 사용자의 실무 화면 활용도를 극대화하고 3D 뷰포트 등 넓은 캔버스 영역을 확보해야 하므로, 하단의 56px 공통 푸터를 제거하여 outlet 영역을 전체 화면 높이로 확장하기로 합의함.
|
||||
* B05 경로 선정 단계는 3D 뷰포트 및 옵션 패널을 한 화면에 조화롭게 보이기 위해 화면 높이를 넘치지 않게 고정할 필요가 있어, 전용 레이아웃 스타일을 적용하여 세로 크기 오버플로우를 차단하도록 설계함.
|
||||
|
||||
---
|
||||
|
||||
## 📋 완료된 체크리스트 항목
|
||||
|
||||
- [x] B05 전용 레이아웃: 상세/3D 영역을 헤더 제외 한 화면 높이에 고정하고 옵션 패널과 세로 크기 계산 분리.
|
||||
- [x] B04~B09 작업 화면은 공통 앱 셸의 푸터를 제외하고 outlet을 헤더 아래 전체 화면 높이로 확장.
|
||||
- [x] B그룹 전체(B01~B11)에 공통 앱 셸의 푸터 제외 정책 적용.
|
||||
@@ -0,0 +1,62 @@
|
||||
# PLAN: B05/B06 UI·측점 표현 개선 (완료 이관본)
|
||||
|
||||
## 🎯 B05/B06 UI·측점 표현 개선 (2026-07-19 협의)
|
||||
|
||||
### 배경 및 합의 사항
|
||||
- 사용자 요청 1번의 "B04 종단면도"는 협의 결과 **B05 하단 접이식 종단면도 패널**로 확정.
|
||||
- 측점 선택 시 동작은 "전체 유지 + 선택 측점 강조"로 확정.
|
||||
- 횡단 반폭(`cross_half_width_m`) 입력 필드는 B05에서 제거하고 **B06 사이드바로 이관**. B05 계산은 `config_system.py:239`의 `SECTION_CROSS_HALF_WIDTH_M`(15.0) 기본값 사용, B06는 DB 저장값 기반으로 접근.
|
||||
- 등급별 최소도로폭은 코드에 없음 → 법령(산림자원법 시행규칙 별표2, 임도설치 및 관리 등에 관한 규정) 기준으로 config 신설:
|
||||
`FOREST_ROAD_MIN_WIDTH_M = {"trunk": 3.0, "branch": 3.0, "work": 2.5}` (간선/지선 유효너비 3.0m, 작업임도 2.5m)
|
||||
→ **사용자 승인 완료** (수치 조정이 필요하면 사용자가 직접 변경 예정)
|
||||
- "노란색 측점 표현 삭제" 대상 = solve 성공 시 그려지는 **횡단면 폭 수직선**으로 확정 (사용자 확인 완료).
|
||||
- 본 계획서는 사용자 승인 완료 상태이며, 구현은 별도 코딩 담당 AI(역할 2: Coder)가 수행한다.
|
||||
|
||||
---
|
||||
|
||||
### 항목 1. B05 하단 종단면도 패널 접기 양식 개선
|
||||
대상: `B05_wf2_Route_UI_Profile_Panel.ts`, `B05_wf2_Route_UI_Style.css`
|
||||
- [x] 접기/펴기 UI를 좌측 사이드바 접기 방식과 동일한 문법으로 변경 — 닫힌 상태에서는 방향(얇은 바)과 토글 버튼만 남김
|
||||
- [x] '종단면도' 제목 행 제거
|
||||
- [x] 내부 종단도 SVG가 패널 가로 폭을 최대한 사용하도록 확대
|
||||
- [x] 패널 세로 높이는 고정값으로 — 브라우저 세로 리사이즈에 따라 줄어들지 않게 (B05 레이아웃의 `calc(100vh - …)` 공식과의 정합 확인)
|
||||
|
||||
### 항목 2. B05 뷰 컨트롤을 사이드바 → 3D 뷰포트 오버레이로 이동
|
||||
대상: `B05_wf2_Route_UI_Panel.ts`, `B05_wf2_Route_UI_Page.ts`, `B05_wf2_Route_UI_Style.css`
|
||||
- [x] 사이드바의 뷰 컨트롤 컨테이너(뷰방향제어 / 표현 활성화 토글 / 뷰초기화) 제거
|
||||
- [x] 3D 뷰포트 좌상단 안내문구('지형을 클릭하거나 팔레트 포인트를 드래그 배치하세요') 하단에 약간 띄워 수평 배치
|
||||
- [x] 체크박스 등 기존 컴포넌트를 전부 버튼 형태로 변경 (토글 상태는 버튼 활성 스타일로 표현)
|
||||
- [x] 컨테이너 외곽선 없음, 버튼 높이는 상단 안내문구와 동일
|
||||
- [x] 뷰방향제어 | 표현 활성화 | 뷰초기화 세 그룹 사이에 세로 구분선 삽입
|
||||
- [x] 버튼 테마는 반투명 처리
|
||||
|
||||
### 항목 3. B05 등고선 간격 설정 한 행 배치
|
||||
대상: `B05_wf2_Route_UI_Panel.ts`, `B05_wf2_Route_UI_Style.css`
|
||||
- [x] 등고선 간격 값 입력과 [재적용] 버튼을 한 행으로 배치
|
||||
|
||||
### 항목 4. B05 측점 표현 정리 및 선택 연동
|
||||
대상: `B05_wf2_Route_UI_Markers.ts`, `B05_wf2_Route_UI_Profile_Panel.ts`, `B05_wf2_Route_UI_Panel.ts`, `config/config_system.py`, `B06_wf3_ProfileCross_UI_Page.ts`(사이드바 이관분)
|
||||
- [x] 노란색으로 표현되던 기존 측점 표현(횡단면 폭 수직선, 사용자 확인 완료)은 삭제
|
||||
- [x] 보라색 측점 횡단 가로선(`stationGroup`)의 선색을 노란색으로 변경
|
||||
- [x] 횡단 가로선 길이 = 선택한 임도 등급의 법정 최소도로폭(`FOREST_ROAD_MIN_WIDTH_M`, 중심선 기준 좌우 각 폭/2)으로 결정 — config 신설 후 프론트로 전달
|
||||
- [x] 선택 연동: 하단 종단면도의 측점 선 클릭 또는 3D의 측점 가로선 클릭 시, 양쪽 모두에서 해당 측점을 강조 표시 (전체 표현 유지 + 선택 측점만 색/굵기 강조)
|
||||
- [x] B05 사이드바의 `cross_half_width_m` 입력 필드 제거 → B05 계산은 `SECTION_CROSS_HALF_WIDTH_M` 기본값 사용
|
||||
- [x] `cross_half_width_m` 입력을 B06 사이드바에 신설 — B06 횡단도 폭 결정용. 값 변경 시 동작(재조회/재생성 API 경로)은 B06 Router의 defaults 처리(`B06_wf3_ProfileCross_Router.py:56`) 확인 후 구현
|
||||
- [x] ⚠️ 종단면도 렌더러는 B06(`B06_wf3_ProfileCross_UI_Section_View.ts`)과 공유 — B05용 수정이 B06 화면을 깨지 않는지 확인
|
||||
|
||||
### 항목 5. 측점 라벨 표기 규칙 통일 (STA 삭제)
|
||||
대상: `B06_wf3_ProfileCross_UI_Section_View.ts` (B05 종단면도 패널이 공유 시 함께 적용)
|
||||
- [x] 횡단면도 하단 X축(카드 제목 포함) 측점 표기를 `측점번호+잔여거리` 형식으로 변경 (예: 간격 10m의 4번째 측점 → `4+0.0`)
|
||||
- [x] 'STA' 접두 표기 삭제
|
||||
- [x] 잔여거리는 소수점 첫째 자리까지 표기 (사용자 추가 측점이 없으면 대부분 `+0.0`)
|
||||
- [x] B06 종단도 X축 및 B05 종단면도에도 동일 규칙 적용
|
||||
|
||||
### 항목 6. B06 종단도 제목 제거 및 행 고정(sticky)
|
||||
대상: `B06_wf3_ProfileCross_UI_Page.ts`, `B06_wf3_ProfileCross_UI_Style.css`
|
||||
- [x] 종단도 제목 제거
|
||||
- [x] 횡단도 카드 목록을 스크롤해도 종단도가 상단에 고정되도록 (엑셀 행 고정과 유사, `position: sticky` 적용)
|
||||
|
||||
### 항목 7. 종단도 측점 세로선 선택 영역 확대
|
||||
대상: `B06_wf3_ProfileCross_UI_Section_View.ts` (B05/B06 공통)
|
||||
- [x] 시각적 선 굵기는 유지하되, 투명한 넓은 히트 영역(예: 굵은 투명 stroke 오버레이)을 씌워 클릭 선택을 쉽게 함
|
||||
- [x] B05 종단면도 패널·B06 페이지 양쪽에서 동작 확인
|
||||
@@ -0,0 +1,85 @@
|
||||
# 완료 이력 아카이브: B05/B06 개선 (2026-07-19)
|
||||
|
||||
본 문서는 `docs/raw/PLAN.md`에서 완료되어 검증이 끝난 계획 항목들을 이관 보관한 파일입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. B05 버튼 템플릿화 및 glass variant 신설
|
||||
|
||||
### 수정 배경
|
||||
- B05가 자체 `button()` 헬퍼로 버튼을 만들고, CSS가 존재하지 않는 변수 `--color-text-on-primary`(테마 실제 변수명: `--color-primary-text`)를 참조해 primary 배경 버튼 글자가 보이지 않던 문제.
|
||||
- B04는 템플릿 `createButton`(ui-btn)을 사용 중 → B05를 템플릿으로 통일 + 오버레이용 반투명 variant 신설.
|
||||
|
||||
### 대상 파일
|
||||
- `ui_template/ui_template_elements.ts`
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts`
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
|
||||
### 완료 작업
|
||||
- [x] `ButtonVariant`에 `glass` 신설 — 반투명 배경 + blur, hover/`is-active` 시 primary 배경 + 올바른 텍스트 변수 (`.ui-btn--glass`)
|
||||
- [x] B05 `button()` 헬퍼를 `createButton` 위임으로 교체 — 최적 경로 계산=`filled`(B04 모델 확정과 동일), 경로 확정·재적용·위치 이동=`ghost`, 삭제=`danger`, ISO/TOP/FRONT/SIDE·표시 토글·뷰 초기화=`glass`
|
||||
- [x] B05 CSS의 자체 버튼 시각 스타일 및 잘못된 변수 참조 제거, 레이아웃 규칙(뷰 컨트롤 높이 34px, 등고선 행 flex)만 `.ui-btn` 대상으로 유지
|
||||
- [x] `npm run typecheck` 통과, prettier 적용
|
||||
|
||||
---
|
||||
|
||||
## 2. B06 횡단 반폭 재생성 기능
|
||||
|
||||
### 수정 배경
|
||||
- B06 횡단도 폭이 solve 시점 반폭으로 고정되어 표시 옵션으로는 저장 폭 이상을 볼 수 없던 구조적 문제.
|
||||
- 섹션 파일 저장 시 이전 실행 `cross_*.json` 잔재를 지우지 않아 detail 조회(glob)에 폭이 다른 구간(0+15)이 섞여 보이던 결함.
|
||||
|
||||
### 대상 파일
|
||||
- `B05_wf2_Route/B05_wf2_Route_Engine_Sections.py`
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_Router.py` / `_Repository.py` / `_Schema.py`
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_Api_Fetch.ts` / `_UI_Page.ts`
|
||||
- `ui_template/ui_template_locale.ts`
|
||||
|
||||
### 완료 작업
|
||||
- [x] 섹션 파일 저장 전 이전 실행 `cross_*.json` 전체 삭제 (잔재 오염 원천 차단)
|
||||
- [x] `POST /api/projects/{project_id}/sections/{route_id}/regenerate` 신설 — B05 엔진(`run_section_generation`) 재사용, solve 시점 stage 2 측점 옵션 유지 + 반폭만 교체, 기존 레코드 삭제→재삽입 단일 트랜잭션, 갱신 상세 즉시 반환
|
||||
- [x] `SectionRegenerateRequest` 스키마(>0 검증), `get_route_generation_source()` 리포지토리 함수 추가
|
||||
- [x] B06 프론트: 확정 버튼 옆 [재계산](ghost) 버튼 신설 + 상태 게이트 — 반폭이 적용값과 다르면 [재계산] 활성·[확정] 비활성, 재계산 성공 시 재렌더 + [확정] 재활성 (B05 재탐색 필요 패턴과 동일)
|
||||
- [x] 재생성 성공/실패 토스트 및 로케일 키(`B06_Profile_Btn_Recalc` 등) 추가
|
||||
- [x] `npm run typecheck`·`py_compile` 통과, ruff format·prettier 적용
|
||||
|
||||
### 참고 (후속 계획과의 연결)
|
||||
- 재계산 시 종횡단 상태는 DRAFT로 복귀하며 재확정 필요. stage 3 COMPLETE는 되돌리지 않음 — 단계 차단 대신 자연 전파 구조로 가기로 합의 (상단 신규 활성 작업 참조).
|
||||
- 현행 코드는 B05 재solve 시 config 기본 반폭(15m)으로 돌아감 — 상단 "횡단 반폭 DB 단일 소스화" 구현으로 해소 예정.
|
||||
|
||||
---
|
||||
|
||||
## 3. B05/B06 UI 부분 수정
|
||||
|
||||
### 수정 배경
|
||||
- 기존 계획서 구현 완료 후 사용자 피드백으로 추가 진행한 부분 수정 이력이다.
|
||||
- 다른 AI가 검증 보고서 작성 및 완료 계획 아카이빙 시 본 항목을 함께 이관한다.
|
||||
|
||||
### 대상 파일
|
||||
- `ui_template/ui_template_overlay.ts`
|
||||
- `ui_template/ui_template_overlay.css`
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts`
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css`
|
||||
|
||||
### 완료 작업
|
||||
- [x] B05 하단 종단면 패널의 접기/펼치기 버튼을 사이드바 핸들의 90도 회전형 레이아웃으로 변경
|
||||
- 하단 패널 상단 중앙에 `64×24px` 돌출형 핸들 배치
|
||||
- 펼침 상태는 아래쪽, 접힘 상태는 위쪽 방향 아이콘으로 표시
|
||||
- 접힌 상태에서는 하단 패널 본문을 숨기고 핸들만 유지
|
||||
- [x] 사이드바와 B05 하단 패널의 핸들 생성 로직을 공통 컴포넌트로 통합
|
||||
- `createWorkflowPanelHandle(placement)` 공통 함수 신설
|
||||
- `side`와 `bottom` 배치 방향만 수정자로 구분
|
||||
- 버튼 생성, 열림 상태, 접근성 속성, 기본 테두리·배경·그림자 스타일 공유
|
||||
- [x] 방향별 유니코드 문자 크기 차이를 제거하도록 공통 화살표 아이콘 개선
|
||||
- 문자 화살표 대신 동일한 CSS 삼각형 하나를 상태에 따라 회전
|
||||
- 사이드바와 하단 패널에서 아이콘 크기와 도형 비율 통일
|
||||
- [x] B06 종단도·횡단도 영역에 좌우 여백 추가
|
||||
- `.b06-section`에 `padding-inline: var(--spacing-24)` 적용
|
||||
- 사이드바 핸들이 도면 축 및 라벨을 가리는 현상 완화
|
||||
- 종단도 sticky 및 횡단도 카드 레이아웃 유지
|
||||
|
||||
### 구현 확인
|
||||
- [x] 수정 대상 TypeScript/CSS에 Prettier 적용
|
||||
- [x] `npm run typecheck` 통과
|
||||
- [x] 수정 대상 파일 `git diff --check` 통과
|
||||
@@ -0,0 +1,17 @@
|
||||
# PLAN (완료 이관): B05/B06 차트 레이아웃 후속 보정 (2026-07-19)
|
||||
|
||||
- 출처: [docs/raw/PLAN.md](../PLAN.md)
|
||||
- 검증: [2026-07-19_verify_B05_B06_차트_레이아웃_후속보정.md](../verification/2026-07-19_verify_B05_B06_차트_레이아웃_후속보정.md)
|
||||
|
||||
- [x] `B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts`: B05 종단도 높이에서 하단 가로 스크롤바 공간 16px을 제외하여 불필요한 세로 스크롤 제거
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts`: 종단도 SVG에 상위 컨테이너 폭 `100%`와 측점 라벨 기반 최소 폭을 함께 적용
|
||||
- [x] `B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts`: B05 종단도에 실측 가용 폭과 최소 폭을 분리 전달하여 최소 폭 도달 전까지 상위 컨테이너 폭에 맞춤
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts`: B06 종단도에 실측 가용 폭과 최소 폭을 분리 전달하여 최소 폭 도달 전까지 상위 컨테이너 폭에 맞춤
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts`: 횡단도 grid의 실제 열 수를 `480px` 최소 열 폭과 `16px` gap 기준으로 동적 계산하여 3열 이상 카드 폭 산정 오류 수정
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css`: 횡단도 SVG 폭을 카드 내부 `100%`로 제한하여 우측 오버플로 제거
|
||||
|
||||
### 구현 검증
|
||||
|
||||
- [x] 변경된 TypeScript/CSS 파일에 Prettier 적용
|
||||
- [x] `npm run build` 통과
|
||||
- [x] 변경 대상 파일 `git diff --check` 통과
|
||||
@@ -0,0 +1,11 @@
|
||||
# PLAN (완료 이관): B06 사이드바 버튼 폭 및 확정 후 B07 이동 (2026-07-19)
|
||||
|
||||
- 출처: [docs/raw/PLAN.md](../PLAN.md)
|
||||
- 검증: [2026-07-19_verify_B06_사이드바_버튼_폭_및_확정_후_B07_이동.md](../verification/2026-07-19_verify_B06_사이드바_버튼_폭_및_확정_후_B07_이동.md)
|
||||
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css`: `.b06-profile__actions`의 직계 버튼에 동일 `flex` 비율과 `min-width: 0`을 적용해 패널 폭에 맞춤
|
||||
- [x] `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Page.ts`: `confirmSections()` 성공 처리 직후 기존 `goToWorkflowStage()`와 `WORKFLOW_STEP_ROUTES[4]`를 사용해 B07로 이동
|
||||
- [x] 확정 실패 경로에서는 현재 B06 페이지 유지 및 기존 오류 토스트 동작 보존
|
||||
- [x] `[재계산]`·`[종횡단 확정]`의 기존 활성/비활성 상태 게이트 보존
|
||||
- [x] 변경된 TS/CSS 파일에 Prettier 적용
|
||||
- [x] `npm run build` 및 변경 파일 `git diff --check` 통과
|
||||
@@ -0,0 +1,53 @@
|
||||
# 완료 계획 이관: B07 상세설계 페이지 전면 개편 (openwebcad 기반)
|
||||
|
||||
- **이관 일자**: 2026-07-19
|
||||
- **대상 기능**: B07 상세설계 단계 (`B07_wf4_DesignDetail`) UI 개편 및 백엔드 라우터, 수량 테이블 계산 기능
|
||||
- **수행 상태**: 전체 개발 완료 및 검증 완료
|
||||
|
||||
---
|
||||
|
||||
## 1. 계획 및 방향성
|
||||
- B07은 openwebcad(MIT)를 iframe으로 임베드한 상태(`/b07-cad/index.html`).
|
||||
- **목표**: openwebcad 기본 UI를 걷어내고 AutoCAD Web과 유사한 인터페이스로 재구성 + B06 확정 산출물(종단·횡단) 기반 설계 워크플로우 구축.
|
||||
- **진입 전제**: B06에서 종단·횡단이 최종 확정된 상태로 넘어옴. B07은 그 데이터를 읽어 도면 단위로 편집·확정한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 완료 체크리스트
|
||||
|
||||
### 작업 1 — openwebcad UI 개편 (AutoCAD Web 스타일)
|
||||
- [x] 1-1. openwebcad 기본 사이드바(자체 좌측 도구 패널) 숨김 처리
|
||||
- [x] 1-2. AutoCAD Web 스타일 UI 골격 적용
|
||||
- [x] 상단: 간소화 리본/도구막대 — 그리기(선·폴리선·원·호), 수정(이동·복사·회전·자르기·간격띄우기), 주석(문자·치수) 그룹
|
||||
- [x] 좌측(CAD 내부): 탭형 패널 — 특성(Properties) / 도면층(Layers), 접기 가능
|
||||
- [x] 하단: 명령행(Command Line) — 명령 입력·프롬프트 표시, 자동완성
|
||||
- [x] 하단 상태막대: 객체스냅(OSNAP)·직교·그리드 토글
|
||||
- [x] 우측 하단: 뷰 컨트롤(줌 확장·줌 인/아웃), 휠 줌·휠드래그 팬 유지
|
||||
- [x] 1-3. MIT 라이선스 공시 이동: CAD 화면 내 기존 공시 제거 → B07 상세설계 페이지 우측 하단에 흐린 작은 글씨(저채도·작은 폰트)로 표기
|
||||
- [x] 1-4. 기능 우선순위: 조회(줌·팬·선택) > 편집(선 수정) > 주석. 임도 종·횡단 편집에 필요한 부분집합만 구현
|
||||
|
||||
### 작업 2 — B06 → B07 데이터 연동 및 도면 단위 표시
|
||||
- [x] 2-1. 진입 시 전체 도면 일괄 렌더 금지. 캐시로 로드만 수행
|
||||
- [x] 2-2. 웹앱 좌측 접이식 사이드 패널에 도면 버튼 목록 생성 — 종단도 N개 + 횡단도(측점별) N개
|
||||
- [x] 2-3. 버튼 클릭 시 해당 도면 1건만 CAD 도면 영역에 로드·표시
|
||||
- [x] 2-4. B06 확정 산출물(JSON) → CAD 도면 스키마 변환 API 설계 (백엔드 라우터 신설)
|
||||
|
||||
### 작업 3 — 확정/완료 플로우 (기존 페이지 패턴 준용)
|
||||
- [x] 3-1. 하단에 다른 B페이지와 동일한 확정 버튼 배치
|
||||
- [x] 3-2. 확정 시: 현재 편집 상태를 영구저장소에 저장 + 확정 색상으로 전환
|
||||
- [x] 3-3. 전체 설계 완료 상태에서만 다음 단계(B08) 이동 허용 — `project_workflow_stages` SSOT 연동
|
||||
- [x] 3-4. 확정 후 도면(선) 변경 감지 시: 설계 단계로 롤백, 확정 버튼 색 초기 색상 복원, 다음 이동 차단 (재확정 전까지)
|
||||
- [x] 3-5. 변경 감지 기준 정의: CAD 엔티티 편집 이벤트(추가·수정·삭제) 발생 시점
|
||||
|
||||
### 작업 4 — 횡단면 수량 테이블 자동 생성
|
||||
- [x] 4-1. 횡단도 1건 표시 시 첨부 양식 테이블을 도면과 함께 자동 작성 (측점명 예: 29+0)
|
||||
- [x] 4-2. 테이블 항목 (첨부 그림 기준):
|
||||
| 구분 | 항목 |
|
||||
|---|---|
|
||||
| 기본 | 측점, 지반고, 계획고, 절토고, 성토고 |
|
||||
| 흙깎기 | 토사 / 연암 / 보통암 |
|
||||
| 옆도랑파기 | 토사 / 연암 / 보통암 |
|
||||
| 비탈보호공 | 성토면 / 절토면 |
|
||||
| 기타 | 지장목제거, 흙쌓기, 제근, 노면고르기 |
|
||||
- [x] 4-3. 값은 B06 산출 데이터(절·성토고, 반폭 등)로부터 자동 계산 — 계산 로직은 백엔드 담당
|
||||
- [x] 4-4. 테이블 렌더 방식: 도면 내 CAD 엔티티(선+문자)로 그려 도면과 함께 출력물(DXF/PDF)에 포함되도록 함
|
||||
@@ -0,0 +1,28 @@
|
||||
# 완료 계획 이관: 잔재 횡단면 데이터 자동 정리 및 CAD 스타일/기능 개선
|
||||
|
||||
- **이관 일자**: 2026-07-19
|
||||
- **대상 기능**: 잔재 횡단면 파일 정리 모듈 (`prune_stale_cross_files`) 및 openwebcad 스타일링(치수/폰트/점선) 개선
|
||||
- **수행 상태**: 전체 개발 완료 및 검증 완료
|
||||
|
||||
---
|
||||
|
||||
## 1. 계획 및 방향성
|
||||
- **백엔드**: 종횡단면 재생성 시 디렉토리 내에 남는 이전 측점의 횡단면 파일(`cross_*.json`)들이 혼선을 주는 버그를 막기 위해, 실제 유효 측점 목록을 기반으로 잔재 파일을 자동 삭제하고 필터링하는 로직을 구축한다.
|
||||
- **프론트엔드**: 실물 크기 단위(m) CAD 도면에서 치수선, 마진, 폰트 크기가 왜곡되거나 가독성이 저하되던 디자인 문제를 개선하고, 점선 및 텍스트 스타일 상태 관리를 통합하여 도구 상자(Toolbar)를 안정화한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 완료 체크리스트
|
||||
|
||||
### 작업 1 — 백엔드 잔재 횡단면 파일 자동 정리
|
||||
- [x] 1-1. 종단 데이터의 유효 `stations` 목록을 기반으로 유효 파일명을 산출하는 `prune_stale_cross_files` 유틸리티 구현
|
||||
- [x] 1-2. 측점 정보가 오염되거나 없을 시 오삭제를 원천 차단하는 방어 코드 추가
|
||||
- [x] 1-3. 종횡단 재생성 라우터 및 B07 상세설계 라우터에 해당 필터를 적용하여 동기화 보장
|
||||
|
||||
### 작업 2 — openwebcad 치수 스타일 개편
|
||||
- [x] 2-1. `App.consts.ts` 상수의 치수선 크기 및 마진 최적화 (`MEASUREMENT_FONT_SIZE`, `MEASUREMENT_EXTENSION_LENGTH` 등)
|
||||
- [x] 2-2. Noto Sans KR 한글 폰트 적용을 통해 한글 도면 가독성 개선
|
||||
|
||||
### 작업 3 — 스타일 전역 상태 통합 및 툴바 리팩토링
|
||||
- [x] 3-1. 점선 패턴(`activeLineDash`) 및 텍스트 스타일 상태를 React 컴포넌트 렌더 사이클과 연결
|
||||
- [x] 3-2. `Toolbar.tsx` 컴포넌트에서 중복 스타일 변경 로직을 전역 세터 함수로 리팩토링하여 소스 코드 경량화 (482줄로 개선)
|
||||
@@ -0,0 +1,21 @@
|
||||
# 완료된 계획: B04 3D 테마 연동 및 2D/GIS 미표시 진단 (2026-07-19)
|
||||
|
||||
## 적용 범위
|
||||
|
||||
1. B04의 포인트클라우드 3D 뷰어와 지형 메시 3D 뷰어에 B05와 동일한 light/dark 장면 배경 정책을 적용한다.
|
||||
2. 다크 배경에서도 척도 텍스트와 척도선이 충분한 대비를 갖도록 기존 테마 색상 토큰과 연동한다.
|
||||
3. 하단 2D VWorld 배경지도 및 Canvas GeoJSON GIS 레이어가 표시되지 않는 원인을 추적한다. 이 항목은 우선 진단만 수행하며, 원인이 확정된 뒤 별도 요청 없이 범위를 넓혀 수정하지 않는다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [X] `B04_wf1_Surface/B04_wf1_Surface_UI_Viewer.ts`: 포인트클라우드 뷰어의 light 배경 유지, dark `--color-surface-raised` 적용, 실행 중 테마 변경 감지 및 dispose 정리
|
||||
- [X] `B04_wf1_Surface/B04_wf1_Surface_UI_TerrainViewer.ts`: 지형 메시 뷰어에 동일한 장면 배경 테마 연동 적용
|
||||
- [X] 두 3D 뷰어의 척도 텍스트·척도선 구현 위치를 확인하고 light/dark에서 `--color-text-body`·`--color-border` 등 대비 가능한 토큰의 계산값으로 갱신
|
||||
- [X] 변경된 TS/CSS 파일에 Prettier 적용, `npm run build`, `git diff --check`, 700줄 제한 통과
|
||||
|
||||
## 검증 기준
|
||||
|
||||
- 라이트 모드의 두 B04 3D 배경은 기존 색상을 유지해야 한다.
|
||||
- 다크 모드에서는 두 3D 배경이 패널 배경과 일치하고 척도 글자·선이 식별 가능해야 한다.
|
||||
- 페이지를 다시 열지 않고 테마를 전환해도 배경과 척도 색상이 즉시 변경되어야 한다.
|
||||
- 2D/GIS 미표시 항목은 재현 조건과 코드상 원인 후보가 아닌 확정 근거를 제시해야 한다.
|
||||
@@ -0,0 +1,21 @@
|
||||
# 완료된 계획: B05 3D 뷰어 다크 테마 배경 연동 (2026-07-19)
|
||||
|
||||
## 적용 결정
|
||||
|
||||
- 라이트 테마는 현재 Three.js 장면 배경색 `0xf5f7fa`를 그대로 유지한다.
|
||||
- 다크 테마는 별도 유사색을 하드코딩하지 않고, 하단 종단 패널과 동일한 테마 토큰 `--color-surface-raised`의 계산값을 Three.js `scene.background`에 적용한다.
|
||||
- 페이지가 열린 상태에서 테마를 전환해도 뷰어 배경이 즉시 갱신되어야 한다.
|
||||
|
||||
## 대상 파일 및 구현 체크리스트
|
||||
|
||||
- [X] `B05_wf2_Route/B05_wf2_Route_UI_Viewer.ts`: 기존 전역 테마 상태 표현 방식(attribute/class)을 확인하고 light/dark 장면 배경 갱신 함수 추가
|
||||
- [X] 다크 테마에서 `getComputedStyle()`로 `--color-surface-raised` 계산값을 읽어 `scene.background`에 적용하고, 값이 비정상일 때만 안전한 다크 폴백 사용
|
||||
- [X] 라이트 테마의 기존 `0xf5f7fa` 배경 유지
|
||||
- [X] 실행 중 테마 변경 감지 연결 및 `dispose()`에서 감지 리소스 해제
|
||||
- [X] 변경 파일 Prettier 적용, `npm run build`, `git diff --check`, 700줄 제한 통과
|
||||
|
||||
## 검증 기준
|
||||
|
||||
- 라이트 모드의 B05 3D 배경은 기존과 동일해야 한다.
|
||||
- 다크 모드의 B05 3D 배경은 하단 패널 배경과 동일해야 한다.
|
||||
- B05 페이지를 다시 열지 않고 테마를 전환해도 배경이 즉시 변경되어야 한다.
|
||||
@@ -0,0 +1,30 @@
|
||||
# B07 외부 WebCAD 3종 비교 실행환경 구축 완료 계획
|
||||
|
||||
- 완료일: 2026-07-19
|
||||
- 사용자 승인: Proceed 수신 후 구현
|
||||
|
||||
## 목표와 범위
|
||||
|
||||
- `external_program/` 아래에서 OpenWebCAD, elhakimz/WebCAD, Maker.js를 독립적으로 실행한다.
|
||||
- 기존 B07, 다른 페이지 및 기존 `external_program/cad-viewer*`는 변경하지 않는다.
|
||||
- 포트 충돌 없이 개별/일괄 실행하고 사용자가 브라우저에서 직접 비교할 수 있게 한다.
|
||||
|
||||
## 완료 체크리스트
|
||||
|
||||
- [x] 기존 `external_program/` 상태 확인 및 충돌 없는 경로 확정
|
||||
- [x] `external_program/openwebcad` clone 및 의존성 설치
|
||||
- [x] `external_program/elhakimz-webcad` clone 및 의존성 설치
|
||||
- [x] `external_program/makerjs` clone 및 의존성 설치
|
||||
- [x] 세 프로젝트 build와 최소 기동 검증
|
||||
- [x] OpenWebCAD 5175, elhakimz/WebCAD 5174, Maker.js 8020 포트 분리
|
||||
- [x] 개별/일괄 실행 스크립트 구현
|
||||
- [x] 안전한 프로세스 트리 종료 스크립트 구현
|
||||
- [x] 실행 및 비교 안내서 작성
|
||||
- [x] 세 URL HTTP 200 및 일괄 종료 검증
|
||||
|
||||
## 결과물
|
||||
|
||||
- `external_program/start-cad-demos.ps1`
|
||||
- `external_program/stop-cad-demos.ps1`
|
||||
- `external_program/README_CAD_DEMOS.md`
|
||||
- `docs/raw/verification/2026-07-19_verify_b07_external_webcad_demos.md`
|
||||
@@ -0,0 +1,41 @@
|
||||
# PLAN: B07 독립형 2D CAD 전환 (2026-07-19 승인) - 완료 아카이브
|
||||
|
||||
## B07 독립형 2D CAD 전환 (2026-07-19 승인)
|
||||
|
||||
### 확정 목표
|
||||
|
||||
- 작업 대상은 `B07_wf4_DesignDetail` 페이지로 한정한다. 다른 업무 페이지는 변경하지 않는다.
|
||||
- B07의 기존 CAD 뷰어/PoC 화면을 제거하고, `B07_wf4_DesignDetail/openwebcad`를 기반으로 한 독립적인 2D Drawing 작업 화면으로 교체한다.
|
||||
- OpenWebCAD는 더 이상 외부 저장소에서 갱신하는 클론으로 취급하지 않고 이 프로젝트가 직접 소유·관리하는 소스로 전환한다.
|
||||
- 내부 업무 데이터는 서버에서 JSON 도면 모델로 전달한다. 브라우저가 DXF/DWG를 직접 파싱하는 구조는 사용하지 않는다.
|
||||
|
||||
### 라이선스 및 파일 포맷 원칙
|
||||
|
||||
- OpenWebCAD의 MIT 코드는 수정·상용 배포할 수 있으며, 원 저작권과 MIT 허가문을 배포물에 보존한다.
|
||||
- B07 화면에 `OpenWebCAD 기반 / MIT License` 고지와 라이선스 전문 접근 경로를 제공하고, 소스 트리의 원본 LICENSE도 보존한다.
|
||||
- 자체 작성 코드와 업무 로직은 프로젝트 정책으로 관리한다. OpenWebCAD 원본에서 유래한 부분의 MIT 고지는 제거하지 않는다.
|
||||
- DXF 입력·출력과 PDF 출력은 B07 편집 코어와 분리된 백엔드/내보내기 모듈로 설계한다. 실제 라이브러리를 채택할 때 각 라이선스를 다시 검증한다.
|
||||
- DWG는 포맷 SDK·변환기의 라이선스/EULA와 서버 자동화 허용 범위가 확정될 때까지 구현하지 않고 백로그로 둔다.
|
||||
- GPL/AGPL 또는 상용 SDK를 추가할 때는 단순 화면 고지만으로 해결된다고 간주하지 않으며, 배포·서비스 구조별 의무를 별도로 검토한다.
|
||||
|
||||
### 구현 체크리스트
|
||||
|
||||
- [x] 현재 B07 진입 경로, 정적 CAD 앱 마운트, iframe/JSON 연동 지점만 확인한다.
|
||||
- [x] 실행 중인 외부 CAD 비교 서버를 안전하게 종료한다.
|
||||
- [x] `B07_wf4_DesignDetail/openwebcad`에서 중첩 `.git`을 제거하여 프로젝트 소유 소스로 전환하고 MIT LICENSE를 보존한다.
|
||||
- [x] `external_program/`에서 OpenWebCAD 이외의 CAD 후보 프로젝트(`cad-viewer`, `cad-viewer-example`, `elhakimz-webcad`, `makerjs`)를 삭제한다.
|
||||
- [x] 비교 실행 전용 스크립트와 안내 문서(`start-cad-demos.ps1`, `stop-cad-demos.ps1`, `README_CAD_DEMOS.md`)를 삭제한다.
|
||||
- [x] OpenWebCAD의 제품명·화면 구성을 B07 전용 독립 2D CAD로 정리하고, 내부 JSON을 받을 수 있는 명확한 연동 경계를 둔다.
|
||||
- [x] B07의 기존 뷰어/PoC 본문을 제거하고 새 CAD 앱을 표시하도록 정적 마운트와 페이지 구성을 교체한다.
|
||||
- [x] B07 화면에서 MIT 고지와 라이선스 전문을 확인할 수 있게 한다.
|
||||
- [x] 기존 B07 PoC 전용 의존성·스텁·API 중 더 이상 사용되지 않는 항목만 정밀 제거한다.
|
||||
- [x] 변경 파일 포맷팅, 빌드, 관련 테스트, 정적 URL과 B07 진입 동작, 라이선스 파일 보존 여부를 검증한다.
|
||||
- [x] 검증 보고서를 작성하고 완료된 본 계획을 아카이빙한 뒤 B07 위키와 지식 그래프를 갱신한다.
|
||||
|
||||
### 완료 조건
|
||||
|
||||
- `external_program/`은 제거되고 독립화된 CAD 소스는 `B07_wf4_DesignDetail/openwebcad`에만 남는다.
|
||||
- B07에는 기존 뷰어가 아닌 편집 가능한 독립 2D CAD 화면이 표시된다.
|
||||
- 다른 업무 페이지의 동작과 화면은 변경되지 않는다.
|
||||
- OpenWebCAD MIT 고지와 LICENSE가 배포물 및 B07 화면에서 확인된다.
|
||||
- DXF·PDF·DWG는 이번 작업에서 무리하게 구현하지 않고, 분리된 후속 계획으로 남는다.
|
||||
@@ -0,0 +1,63 @@
|
||||
# 완료 이력 아카이브: 반폭 DB 단일 소스화 및 영속화 (2026-07-19)
|
||||
|
||||
본 문서는 `docs/raw/PLAN.md`에서 완료되어 검증이 끝난 계획 항목들을 이관 보관한 파일입니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 🎯 횡단 반폭 DB 단일 소스화 (2026-07-19 협의, 완료)
|
||||
|
||||
### 배경 및 합의 사항
|
||||
- 횡단 반폭 값의 단일 소스는 **DB**로 한다.
|
||||
- 최초 solve 시(사용자 업로드 직후 DB에 값이 없는 경우): config 기본값으로 계산하고, **사용한 반폭을 DB에 저장**한다.
|
||||
- B06에서 반폭 변경 + [재계산] 시: DB와 영구저장소(섹션 JSON 파일)를 함께 업데이트한다.
|
||||
- B05로 돌아와 경로를 재계산(solve)하면: **DB에 저장된 반폭을 읽어** 그 값으로 섹션을 재생성하고 파일·DB를 갱신한다.
|
||||
- 단계 진입을 막는 개념 대신 **자연 전파** 구조를 지향한다: 사용자가 결정한 값들이 단계 진행에 따라 DB에 채워지고, 전 단계에서 변경이 생기면 후속 단계 산출물이 자연스럽게 갱신된다.
|
||||
|
||||
### 완료 체크리스트
|
||||
- [x] 반폭 저장 위치: `longitudinal_sections.data` JSON에 생성 옵션 스냅샷(`options`: station_interval_m, cross_half_width_m, cross_sample_interval_m, long_sample_interval_m) 포함 — 테이블 스키마 변경 없음 (`run_section_generation`의 `long_summary`에 options 추가)
|
||||
- [x] B05 solve: 섹션 생성 전 프로젝트 최신 `longitudinal_sections.data.options` 조회 → 요청 값 → DB 값 → config 순 우선 (`B05_wf2_Route_Router._section_options` + `get_latest_section_options()` 신설)
|
||||
- [x] B06 regenerate: 나머지 3개 측점 옵션도 stage 2 params 대신 저장된 `data.options` 우선 사용 (fallback: stage params → config)
|
||||
- [x] B06 프론트: 반폭 필드 초기값을 DB `data.options` 값 우선으로 표시 — options 스냅샷이 없는 과거 데이터만 샘플 max offset 추정으로 폴백, 측점 간격 라벨도 options 값 우선
|
||||
- [x] 검증 시나리오: ① 최초 solve → DB options 저장 확인 ② B06 반폭 변경 재계산 → DB·파일 갱신 확인 ③ B05 재solve → 변경한 반폭 유지 확인
|
||||
|
||||
### 추가 구현 (백로그 승격)
|
||||
- [x] B05 등고선 간격 영속화: `PUT /{project_id}/route/contour-interval` 신설 — 재적용 성공 시 stage 1 params(단일 소스)의 `contour_interval_m` 갱신 (`update_contour_interval_param()` 공용 유틸 신설), 재진입 복원은 기존 `surface_params.contour_interval_m` 경로 그대로 동작
|
||||
- [x] 표시 설정류(B04 포인트 크기·밀도·표시 토글, B06 연직 과장) 영속화는 **불필요 확정** — 백로그에서 제거
|
||||
- [x] 구현 정적 검증: `tsc --noEmit`·`py_compile` 통과, ruff format·prettier 적용
|
||||
|
||||
---
|
||||
|
||||
## 2. 📋 사용자 선택값 영속화 현황 점검 (2026-07-19, 완료)
|
||||
|
||||
### 점검 배경
|
||||
- B04~B06(향후 B07~B09 동일 흐름)은 단방향이 아니라 단계를 왔다갔다 하며 데이터를 반복 수정하는 작업흐름이다.
|
||||
- 원칙: 사용자가 선택한 값은 DB에 저장하고, 값이 없으면 config 기본값으로 계산 후 그 값을 DB에 채운다.
|
||||
- 아래는 페이지별 저장 현황 전수 점검 결과이다.
|
||||
|
||||
### B04 (지표면 분석)
|
||||
| 항목 | 현재 저장 여부 | 위치/비고 |
|
||||
|---|---|---|
|
||||
| 지면 필터 선택 (source_filter) | ✅ 저장 | stage 1 params (확정 시), `get_surface_confirmation_params`로 후속 단계가 조회 |
|
||||
| 지표면 표현 선택 (method) | ✅ 저장 | stage 1 params (확정 시) |
|
||||
| 스무딩 여부 (smooth) | ✅ 저장 | stage 1 params (`B04_wf1_Surface_Router.py:236`) |
|
||||
| 모델 표시 옵션의 등고선 간격 (contour_interval_m) | ✅ 저장 | stage 1 params (`B04_wf1_Surface_Router.py:239`, 확정 시점 값) |
|
||||
| 포인트 표시옵션 (포인트 크기) | ❌ 미저장 | `B04_wf1_Surface_UI_Viewer.ts` 뷰어 로컬 상태, 서버 미전송 |
|
||||
| 밀도 (density 슬라이더) | ❌ 미저장 | `B04_wf1_Surface_UI_Viewer.ts:74-83` 뷰어 로컬 상태 |
|
||||
| 모델 표시 토글 (축/지표면/등고선 표시 여부) | ❌ 미저장 | `B04_wf1_Surface_UI_TerrainViewer.ts` 뷰어 로컬 상태 |
|
||||
|
||||
### B05 (경로 설계)
|
||||
| 항목 | 현재 저장 여부 | 위치/비고 |
|
||||
|---|---|---|
|
||||
| 포인트 팔레트 사용 이력 (BP/EP/CP/AP/FP) | ✅ 저장 | stage 2 params(points) + `route_points` 테이블 — 사용자 추정대로 필수값이라 기존 저장 |
|
||||
| 임도 기준·옵션 컨테이너 전체 (등급, 포장, 곡선반경, 경사 상·하한, 가중치, 회피 통과) | ✅ 저장 | stage 2 params(options) — `RouteSolveRequest.options()`, 재진입 시 폼 복원됨 |
|
||||
| 측점·횡단 옵션 (station/cross_sample/long_sample interval) | ✅ 저장 | stage 2 params — 단 cross_half_width_m은 현재 항상 null 전송 (반폭 DB 단일 소스화 작업에서 해소) |
|
||||
| 등고선 간격 | ✅ 저장 | `PUT /{project_id}/route/contour-interval`로 저장 성공 확인 |
|
||||
|
||||
### B06 (종횡단)
|
||||
| 항목 | 현재 저장 여부 | 위치/비고 |
|
||||
|---|---|---|
|
||||
| 횡단 반폭 | ✅ 저장 | `longitudinal_sections.data.options`로 저장 완료 |
|
||||
| 연직 과장 (vertical_exaggeration) | ❌ 미저장 | 프론트 로컬 즉시 재렌더 전용, 재진입 시 config 기본값 복귀 |
|
||||
|
||||
### 결론
|
||||
- **계산에 영향을 주는 입력값**은 stage params·전용 테이블 및 options 필드로 완전하게 영속화되었습니다.
|
||||
@@ -0,0 +1,84 @@
|
||||
# PLAN: B05/B06 종·횡단도 반응형 레이아웃 및 마커 직접 드래그 (완료 이력)
|
||||
|
||||
## 🎯 활성 계획: B05/B06 종·횡단도 반응형 레이아웃 및 마커 직접 드래그 (2026-07-19)
|
||||
|
||||
### 배경 (사용자 요청 4건)
|
||||
1. B05 하단 종단도가 브라우저 폭 축소 시 높이(도면 크기)부터 줄어듦 → 하단 가로 스크롤바 방식으로 변경
|
||||
2. 배치된 포인트를 3D 뷰에서 직접 드래그로 위치 변경 (0_old I-401 기능 이식, 클릭/드래그 판별 필요)
|
||||
3. B06 종단도가 폭에 따라 높이가 계속 변함(가로세로비 고정) + sticky 상단 고정이 실제로 동작하지 않음
|
||||
4. B06 횡단도가 화면 크기 따라 계속 확대/축소되다가 일정 폭 이하에서 우측 오버플로우 발생
|
||||
|
||||
### 원인 분석 (코드 확인 완료)
|
||||
| 증상 | 원인 위치 | 원인 |
|
||||
|---|---|---|
|
||||
| B05 종단도 축소 | `B05_wf2_Route_UI_Style.css:68-73` | SVG에 `width/height:100%` 강제. SVG는 고정 viewBox(1200×220)의 비율을 유지(preserveAspectRatio 기본값)하므로 폭 축소 시 도면 전체가 비례 축소 |
|
||||
| B06 종단도 높이 변동 | `B06_wf3_ProfileCross_UI_Style.css:163-168` | `.b06-section__chart { width:100%; height:auto }` + 고정 viewBox → 폭에 비례해 높이 변동 |
|
||||
| B06 sticky 미동작 | `ui_template_workflow_layout.css:118-124` | `.ui-workflow-layout__main`이 `overflow:auto`이지만 높이 제한 없이 내용만큼 늘어나 실제 스크롤은 document에서 발생. sticky의 기준 스크롤포트가 "스크롤되지 않는 __main"으로 잡혀 무효화 |
|
||||
| B06 횡단도 오버플로우 | `B06_wf3_ProfileCross_UI_Style.css:163-168, 179-183` | `min-width:520px` + 2열 고정 grid(`repeat(2, minmax(0,1fr))`). 열 폭 < 520px이면 오버플로우. 기존 media query(900px)는 뷰포트 기준이라 사이드바 열림 상태의 실제 컨테이너 폭과 불일치 |
|
||||
| 마커 직접 드래그 부재 | `B05_wf2_Route_UI_Viewer.ts:176-196` | pointerdown에서 마커 히트 시 즉시 `selectObject` 후 종료. 드래그 이동 로직 없음 |
|
||||
|
||||
### 공통 설계: SVG 동적 크기 렌더 (사용자 합의 완료)
|
||||
`B06_wf3_ProfileCross_UI_Section_View.ts`의 `LONG_WIDTH`/`CROSS_WIDTH`/`LONG_HEIGHT`/`CROSS_HEIGHT` 고정 상수를 렌더 시점 파라미터로 전환한다.
|
||||
- `createLongitudinalProfile` / `createCrossSectionCard`에 `widthPx`/`heightPx` 파라미터 추가 (미지정 시 기존 상수 폴백 → 기존 호출부 무변경 호환)
|
||||
- **공통 원칙 (합의)**: 도면 높이는 항상 고정. 종단도는 폭이 최소 폭 미만이 되면 **가로 스크롤** (B05·B06 공통), 횡단도는 한눈에 봐야 하는 정보이므로 **가로 스크롤 금지** — 컨테이너 폭에 맞춰 내용 축소
|
||||
- **고정 높이 (합의)**: B05 종단 = 하단 패널 컨테이너 실측 높이에 맞춤(패널 250px 내부 채움), B06 종단 = 220px, B06 횡단 = 250px
|
||||
- **종단도 최소 폭 산정 (합의: X축 측점 라벨 비교차 기준)**: `minWidth = PAD.left + PAD.right + 측점 수 × 라벨 슬롯 폭`. 라벨 슬롯 폭은 최장 라벨(예: "12+7.5", 10px 폰트) 실폭 + 여유로 산정 (구현 시 대략 48~56px 상수화, 검증 단계에서 실화면 확인)
|
||||
- 컨테이너 크기 실측은 `ResizeObserver`로 감지하고 디바운스(~150ms) 후 재도면
|
||||
- **생성 시점 보장**: 최초 데이터 바인딩·패널 펼침 직후는 레이아웃 미확정으로 폭이 0일 수 있음 → 폭이 0이면 도면 생략하고, `requestAnimationFrame`+ResizeObserver 통지로 실측 폭 확정 후 도면 (B05 접힘 패널 `display:none` 상태 포함)
|
||||
|
||||
---
|
||||
|
||||
### 작업 1: B05 하단 종단도 — 높이 고정 + 하단 가로 스크롤
|
||||
**방향**: 도면 폭 = `max(컨테이너 폭 - 30px(좌우 15px), 최소 도면 폭)`. 컨테이너가 좁으면 최소 폭을 유지한 채 wrapper에 `overflow-x:auto`로 하단 스크롤바 생성. 넓으면 좌우 15px 여유만 남기고 전체 사용.
|
||||
- 최소 도면 폭: 공통 설계의 라벨 비교차 기준 동적 산정
|
||||
- 도면 높이: 하단 패널 컨테이너(`__body`) 실측 높이에 맞춤 (스크롤바 발생 시 스크롤바 높이만큼 감산)
|
||||
|
||||
**대상 파일 및 체크리스트**
|
||||
- [x] `B06_wf3_ProfileCross_UI_Section_View.ts`: `createLongitudinalProfile`에 `widthPx`/`heightPx` 파라미터 추가, 내부 좌표계(x 스케일·grid·축라벨)를 파라미터 기반으로 전환, 라벨 비교차 최소 폭 산정 유틸 추가
|
||||
- [x] `B05_wf2_Route_UI_Profile_Panel.ts`: ResizeObserver 부착, 실측 폭·높이 기반 `draw()` 재호출, 접힘→펼침 시 재도면, dispose 시 observer 해제
|
||||
- [x] `B05_wf2_Route_UI_Style.css`: 68-73행의 `width/height:100%` 강제 제거, `__body`에 `overflow-x:auto` + 좌우 padding 15px
|
||||
|
||||
### 작업 2: 배치된 포인트(BP/EP/CP/AP/FP) 직접 드래그 이동 — 0_old I-401 이식
|
||||
**참조 원본**: `0_old/frontend/src/RouteDesignStage.tsx:868-1012, 2097-2111`
|
||||
|
||||
**이식할 판별 패턴** (클릭/드래그 공존의 핵심):
|
||||
1. `pointerdown`: 마커 히트 시 즉시 선택하지 않고 **드래그 후보**로만 등록 (`dragStart` 좌표 기록)
|
||||
2. `pointermove`: 이동 거리 3px 초과 시 드래그 확정 → `OrbitControls.enabled = false`, 지형 레이캐스트 지점으로 마커 메시 실시간 이동 (AP/FP는 반경 실린더 동반 이동)
|
||||
3. `pointerup`: 드래그였으면 최종 지형 좌표로 상태 확정·저장 + OrbitControls 복원. 이동 거리 3px 이하면 기존 클릭(선택/팝오버) 동작 수행
|
||||
4. `pointerleave`/`pointercancel`: 드래그 중 이탈 시 마지막 유효 좌표로 안전 종료 (원본 2097행 패턴)
|
||||
|
||||
**대상 파일 및 체크리스트**
|
||||
- [x] `B05_wf2_Route_UI_Viewer.ts`: pointerdown 즉시 선택 로직(176-196행)을 3-ref(dragStart/dragCandidate/draggingMarker) 패턴으로 교체, pointermove/up/leave 핸들러 추가
|
||||
- [x] `B05_wf2_Route_UI_Markers.ts`: 히트된 마커 객체→(kind, index) 식별 및 지정 마커 좌표 갱신 API 노출 (AP/FP 실린더 동기 이동 포함)
|
||||
- [x] `B05_wf2_Route_UI_Page.ts`: 드래그 확정 시 포인트 상태 갱신 → 기존 "재탐색 필요(stale)" 배지·확정 버튼 비활성 게이트와 연동 확인 (Epsilon 오차 보정 로직 통과 확인)
|
||||
- [x] 기존 팔레트 드래그&드롭(신규 배치) 및 클릭 선택·팝오버 동작 회귀 없음 확인
|
||||
|
||||
### 작업 3: B06 종단도 — 높이 고정 + sticky 상단 고정 복구
|
||||
**방향**:
|
||||
- 높이·폭: 작업 1의 동적 크기 렌더를 B06 `createSectionView`에도 적용. 높이 220px 고정, 폭은 컨테이너 전체 사용하되 **최소 폭(라벨 비교차 기준) 미만이면 B05와 동일하게 가로 스크롤** (`.b06-section__chart-wrap`의 기존 `overflow-x:auto` 활용)
|
||||
- sticky: B06 페이지 스코프에서 `.ui-workflow-layout__main`에 `height: calc(100vh - var(--spacing-64))`(+dvh)를 지정해 __main을 **실제 스크롤 컨테이너**로 전환 → 기존 `position:sticky; top:0`이 정상 동작 (B05가 이미 쓰는 페이지 스코프 고정 패턴과 동일, 공통 템플릿은 수정하지 않음)
|
||||
|
||||
**대상 파일 및 체크리스트**
|
||||
- [x] `B06_wf3_ProfileCross_UI_Section_View.ts`: `createSectionView`의 `draw()`에 ResizeObserver 기반 실측 폭 전달
|
||||
- [x] `B06_wf3_ProfileCross_UI_Page.ts`: 레이아웃 루트에 B06 스코프 클래스 부여 (예: `b06-profile-layout`)
|
||||
- [x] `B06_wf3_ProfileCross_UI_Style.css`: B06 스코프의 __main 높이 고정 CSS 추가, `.b06-section__chart`의 `min-width:520px` 제거(횡단은 작업 4에서 별도 처리)
|
||||
- [x] 검증: 횡단 리스트 스크롤 시 종단도가 상단에 고정 유지되는지, 브라우저 폭 변경 시 종단도 높이 220px 불변인지
|
||||
|
||||
### 작업 4: B06 횡단도 — 크기 안정화 및 오버플로우 해소 (좌우 스크롤 금지)
|
||||
**방향**: 횡단 카드는 높이 **250px 고정**(합의: 기존 260 → 250) + 카드 실측 폭 기반 동적 폭 렌더. 한눈에 봐야 하는 정보이므로 가로 스크롤 없이 컨테이너 폭에 맞춰 내용을 축소. 열 수는 뷰포트가 아닌 **컨테이너 폭 기준**으로 전환해 오버플로우 자체를 제거.
|
||||
- grid: `repeat(2, minmax(0,1fr))` → `repeat(auto-fill, minmax(480px, 1fr))` (컨테이너 폭 < 480×2 + gap이면 자동 1열 전환, media query 의존 제거)
|
||||
- SVG `min-width` 제거로 카드 밖 오버플로우 원천 차단
|
||||
|
||||
**대상 파일 및 체크리스트**
|
||||
- [x] `B06_wf3_ProfileCross_UI_Section_View.ts`: `createCrossSectionCard`에 `widthPx`/`heightPx(250)` 적용 (grid 열 폭 실측 1회 → 전체 카드 공통 적용, 카드별 관찰 금지)
|
||||
- [x] `B06_wf3_ProfileCross_UI_Style.css`: grid `auto-fill` 전환, 카드 SVG 높이 250px 고정
|
||||
- [x] 검증: 사이드바 열림/닫힘 및 브라우저 폭 축소 시 오버플로우·가로 스크롤 미발생, 카드 높이 불변
|
||||
|
||||
### 공통 마무리
|
||||
- [x] `prettier`(TS/CSS) 포맷 실행, 700줄 제한 확인 (Section_View 현재 533줄 → 파라미터화 후 여유 확인)
|
||||
- [x] 검증자 인계: 4건 통합 검증보고서 작성 대상
|
||||
|
||||
### ✅ 사용자 합의 사항 (2026-07-19 문답 확정)
|
||||
1. **스크롤 정책**: 종단도(B05·B06 공통)는 높이 고정 + 폭이 일정 수준 이하로 줄면 가로 스크롤. 횡단도는 가로 스크롤 금지, 컨테이너 폭에 맞춰 내용 축소(높이 고정).
|
||||
2. **종단도 최소 폭**: X축 측점 라벨이 서로 교차하지 않는 기준으로 AI가 산정 (라벨 실폭 + 여유 기반).
|
||||
3. **고정 높이**: B05 종단 = 하단 패널 컨테이너에 맞춤, B06 종단 = 220px, B06 횡단 = 250px.
|
||||
@@ -0,0 +1,12 @@
|
||||
# PLAN: 전역 스크롤바 테마 적용 — B07 CAD 제외 (2026-07-19 승인) - 완료 아카이브
|
||||
|
||||
## 전역 스크롤바 테마 적용 — B07 CAD 제외 (2026-07-19 승인)
|
||||
|
||||
- [x] 공통 앱 셸의 CSS 주입 위치와 기존 스크롤바 선언을 확인한다.
|
||||
- [x] 일반 페이지의 가로·세로 스크롤바에 얇은 트랙, 둥근 thumb, hover 색상을 전역 적용한다.
|
||||
- [x] Firefox의 `scrollbar-width`·`scrollbar-color`와 Chromium/WebKit 선택자를 함께 지원한다.
|
||||
- [x] B07 iframe 내부 `B07_wf4_DesignDetail/openwebcad`는 수정하지 않는다.
|
||||
- [x] 사용자 피드백에 따라 라이트/다크 공통 색상 계열은 유지하고 track·thumb·hover·active의 투명도를 강화한다.
|
||||
- [x] 외부 스크롤바 라이브러리 코드를 복사하지 않고 투명 트랙·둥근 thumb·기본 화살표 제거를 자체 CSS로 구현한다.
|
||||
- [x] Chrome 121+에서 표준 `scrollbar-width: thin`이 WebKit 스타일을 덮는 충돌을 해소한다.
|
||||
- [x] 수정 파일만 포맷팅하고 구현 완료 항목을 체크한다. 검증·아카이빙·위키 갱신은 검증 담당 AI에 인계한다.
|
||||
@@ -0,0 +1,45 @@
|
||||
# PLAN (완료 이관): 종횡단 생성 시점 이동 (B06 → B05) 및 B05/B06 UI 재구성
|
||||
|
||||
- 계획 수립: 2026-07-18 / 구현 커밋: `eed8097` / 검증: [2026-07-19_verify_종횡단_생성_B05_이관.md](../verification/2026-07-19_verify_종횡단_생성_B05_이관.md)
|
||||
|
||||
### 목표
|
||||
1. 종·횡단 계산 코드를 B05로 이관하고, **최적 경로 계산(solve) 직후 종단+횡단을 함께 계산·영구저장**한다. B06은 저장된 데이터를 **조회만** 한다.
|
||||
2. B05 페이지 하단에 **상하 접이식 종단면도 패널**을 추가한다.
|
||||
3. 측점 옵션(측점 간격·횡단 반폭·횡단/종단 샘플 간격)을 B05 사이드바로 이동한다.
|
||||
4. B05 3D 뷰어의 경로 위에 **측점 가로선**을 표시한다 (종단면도 측점과 동일 지점).
|
||||
5. B06 상세 페이지를 단순화한다: 결과 3개 지표를 좌측 사이드바(대상 경로 그룹 아래)로 이동, 생성 기능 제거, 종단면도 높이 축소.
|
||||
|
||||
### 설계 결정 (사용자 협의 완료)
|
||||
- **횡단도 B05에서 함께 계산**: 횡단 30개×61샘플 ≈ 1,830점이 단일 벡터화 `sample_xy` 호출로 처리됨. DTM 보간 기준 수 ms, 보간기 생성 비용은 종단 계산과 공유 → 2초 기준 충족.
|
||||
- **저장 위치 유지**: 파일은 기존대로 `{project}/B06_wf3_ProfileCross/longitudinal/`, `cross_sections/`에 저장. DB 테이블·Repository도 B06 소속 유지, B05 라우터가 import.
|
||||
- **워크플로 스테이지**: B05 solve는 stage 3을 건드리지 않는다. stage 3 완료는 B06 확정 시에만.
|
||||
- **측점 옵션 전달**: `RouteSolveRequest`에 측점 옵션 4개 필드 추가 → stage 2 params(route_params)에 자동 스냅샷.
|
||||
- **B06 자동 생성 흐름 제거**: 진입 시 `getSections` 조회만. 데이터 없으면 "B05에서 경로를 계산하세요" 안내.
|
||||
|
||||
### 구현 체크리스트 (검증 완료)
|
||||
|
||||
#### Phase A — 백엔드: 계산 코드 이관 및 solve 통합
|
||||
- [x] A-1. 엔진 3파일 이동·개명: `B06_wf3_ProfileCross_Engine.py` → `B05_wf2_Route_Engine_Sections.py`, `..._Engine_Sampler.py` → `B05_wf2_Route_Engine_Sections_Sampler.py`, `..._Engine_Section.py` → `B05_wf2_Route_Engine_Sections_Core.py`
|
||||
- [x] A-2. `B05_wf2_Route_Schema.py`: `RouteSolveRequest`에 측점 옵션 4개, `RouteSolveResponse`에 종단 요약 추가
|
||||
- [x] A-3. `B05_wf2_Route_Router.py` `solve_route()`: 경로 커밋 후 섹션 생성·저장, stage 3 미접촉
|
||||
- [x] A-4. `B06_wf3_ProfileCross_Router.py`: generate 엔드포인트·`_build_options` 제거
|
||||
- [x] A-5. `B06_wf3_ProfileCross_Schema.py`: Generate 요청/응답 제거
|
||||
|
||||
#### Phase B — B05 프론트
|
||||
- [x] B-1. 패널에 측점·횡단 옵션 섹션 (4개 입력, stale 연동)
|
||||
- [x] B-2. solve 요청에 측점 옵션 포함
|
||||
- [x] B-3. `B05_wf2_Route_UI_Profile_Panel.ts` 하단 접이식 종단면도 (sessionStorage 상태 유지)
|
||||
- [x] B-4. solve 성공/진입 복원 시 `fetchSectionDetail` → 종단 패널 렌더
|
||||
- [x] B-5. 측점 가로선 3D 렌더 (`stationGroup` LineSegments) + 토글
|
||||
- [x] B-6. CSS
|
||||
|
||||
#### Phase C — B06 단순화 (조회 전용)
|
||||
- [x] C-1. 결과 3개 metric 좌측 사이드바 이동
|
||||
- [x] C-2. 측점 옵션·생성 버튼·자동 생성 제거, 연직 과장만 유지
|
||||
- [x] C-3. `generateSections` fetch 제거
|
||||
- [x] C-4. `LONG_HEIGHT` 310 → 220
|
||||
- [x] C-5. locale 정리
|
||||
|
||||
#### Phase D — 마무리
|
||||
- [x] D-1. `ruff format` + `prettier`
|
||||
- [ ] D-2. 화면 흐름 검증 → **PLAN.md 잔여 항목으로 유지** (검증 보고서의 경미 이슈 3건 후속 조치 포함)
|
||||
@@ -0,0 +1,8 @@
|
||||
# PLAN (완료 이관): 종횡단 B05 이관 후속 이슈 수정 3건
|
||||
|
||||
- 출처: [2026-07-19_verify_종횡단_생성_B05_이관.md](../verification/2026-07-19_verify_종횡단_생성_B05_이관.md) 발견 이슈
|
||||
- 검증: [2026-07-19_verify_종횡단_이슈수정_3건.md](../verification/2026-07-19_verify_종횡단_이슈수정_3건.md)
|
||||
|
||||
- [x] 검증 이슈 1 [중하]: 측점 옵션 빈 입력 시 422 → `parseOptional`로 빈 값 null 처리 (`B05_wf2_Route_UI_Panel.ts` values(), 측점 가로선 반폭은 context 기본값 폴백)
|
||||
- [x] 검증 이슈 2 [하]: crs 미전달 → `get_surface_crs_epsg` 신설 (surface_models → input_files 폴백)하여 `run_section_generation`에 EPSG 전달
|
||||
- [x] 검증 이슈 3 [하]: 섹션 생성 실패를 비치명적 처리 — 경로 저장 후 섹션 블록을 try/except로 격리, 실패 시 응답 요약 필드 None + 프론트 안내 토스트, stage 2 FAILED 미기록
|
||||
@@ -0,0 +1,50 @@
|
||||
# PLAN Archives: B05 종단 계획선(계획고) 자동 생성
|
||||
|
||||
- **완료 일시**: 2026-07-20
|
||||
- **이관 문서**: `docs/raw/plans/2026-07-20_plan_B05_longitudinal_grade_line.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 목표
|
||||
B05 하단 종단면도에 **현황 지반선(기존)** 위에 **계획선(공사 예정 노면고)** 을 추가한다.
|
||||
계획선은 `직선(일정 종단기울기) + 원곡선(종단곡선)` 만으로 구성되며, 절토량과 성토량이 균형을 이루도록(∫(계획고−지반고)ds ≈ 0) 자동 산출한다.
|
||||
실행 시점은 **[최적 경로 계산] 버튼 처리 파이프라인의 마지막 단계** (종횡단 생성 직후).
|
||||
|
||||
## 2. 설계 기준값 — 법정 근거 및 Config 설계
|
||||
출처: 「임도설치 및 관리 등에 관한 규정」 [별표 1-2] 임도의 설계 및 시설기준(제9조의19 관련).
|
||||
기준은 임도등급이 아니라 `설계속도 × 지형구분(일반/특수)` 으로 규정되어 있다.
|
||||
|
||||
### 2-1. 종단기울기 (순기울기 상한)
|
||||
| 설계속도 (km/h) | 일반지형 | 특수지형 | 역기울기 |
|
||||
|---|---|---|---|
|
||||
| 40 | 7% 이하 | 10% 이하 | 5% 이하 |
|
||||
| 30 | 8% 이하 | 12% 이하 | 5% 이하 |
|
||||
| 20 | 9% 이하 | 14% 이하 | 5% 이하 |
|
||||
- 예외: 특수지형에서 기준 적용이 어려운 경우 **노면포장 시에 한하여 18% 범위 내 조정 가능**.
|
||||
- 합성기울기 12% 이하 (부득이 시 간선임도 13%, 지선임도 15%).
|
||||
|
||||
### 2-2. 종단곡선
|
||||
| 설계속도 (km/h) | 최소 반경 (m) | 최소 곡선길이 (m) |
|
||||
|---|---|---|
|
||||
| 40 | 450 이상 | 40 이상 |
|
||||
| 30 | 250 이상 | 30 이상 |
|
||||
| 20 | 100 이상 | 20 이상 |
|
||||
- 예외: **비포장 도로이면서 종단기울기 대수차(A)가 5% 이하이면 종단곡선을 넣지 않는다** → 알고리즘 3단계에 조건 분기로 반영.
|
||||
|
||||
## 3. 구현 체크리스트 (완료)
|
||||
- [x] `config_system.py` 에 `FOREST_ROAD_PROFILE_CRITERIA` 법정 기준 사전 추가 (경로찾기 값과 분리 확인)
|
||||
- [x] `B05_wf2_Route_Engine_Grade.py` 계획선 엔진 신규 작성 (구역분할·PVI최적화·종단곡선·균형보정)
|
||||
- [x] 종단곡선 법정 예외(비포장 & 대수차 5% 이하) 분기 구현
|
||||
- [x] `run_section_generation()` 연동 및 `longitudinal.json` `design_profiles` 배열 스키마 확장
|
||||
- [x] Schema / Router 옵션 6종 및 응답 필드 확장 (요청→DB→Config 3단 폴백)
|
||||
- [x] `longitudinal_sections` DB 저장 확장 (`get_latest_grade_options` 헬퍼 신설)
|
||||
- [x] 좌측 패널 계획선 옵션 폼 + 등급→설계속도 연동 기본값
|
||||
- [x] 종단면도 SVG 계획선·절성토 음영·균형 지표 렌더링
|
||||
- [x] 새로고침 복원(`GET /route/latest`) 시 계획선 재표시 확인
|
||||
- [x] `ruff format` / `prettier` 실행
|
||||
|
||||
## 4. 구현 중 발생 편차 및 수정사항
|
||||
1. **엔진 파일 분할**: `B05_wf2_Route_Engine_Grade.py` (560줄) 및 `B05_wf2_Route_Engine_Grade_Solver.py` (352줄)로 분할.
|
||||
2. **역기울기 오적용 및 V/Λ자 지형 대응**: `_detect_main_direction()`을 통해 계곡 횡단/능선 통과 노선의 방향 오판 방지.
|
||||
3. **스티키 기본값 버그 방지**: DB에 오버라이드값(`grade_overrides`)과 해석결과(`grade_options`)를 분리.
|
||||
4. **경사 단위(%) 통일**: UI/Schema/DB 모두 `%` 단위로 일관화 및 레거시 비율 자동 환산.
|
||||
@@ -0,0 +1,30 @@
|
||||
# PLAN Archives: B06 계획선 표시 및 횡단 십자선 계획고 정합
|
||||
|
||||
- **완료 일시**: 2026-07-20
|
||||
- **이관 문서**: `docs/raw/plans/2026-07-20_plan_B06_profile_cross_design_elevation.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 목표
|
||||
B05에서 산출·영구저장된 종단 계획선(`design_profiles`)을 B06 화면에서도 동일하게 사용한다.
|
||||
1. B06 상단 종단도에 B05와 동일한 계획선을 렌더링한다.
|
||||
2. 각 횡단도의 십자선(중심 마커)을 **지반고**가 아닌 **해당 측점의 계획고**(계획 노선 위치)에 맞춘다.
|
||||
|
||||
## 2. 수정 대상 및 범위
|
||||
- `B06_wf3_ProfileCross_UI_Section_View.ts`
|
||||
- `B06_wf3_ProfileCross_UI_Style.css`
|
||||
|
||||
## 3. 구현 체크리스트 (완료)
|
||||
- [x] `calculateYScale()`에 `detail.longitudinal.design_profiles`의 `samples[].elevation_m`을 표고 집계 배열에 합산 (B05 렌더러와 동일 기준)
|
||||
- [x] `draw()`의 `createLongitudinalProfile(...)` 호출에 `detail.longitudinal.design_profiles ?? []` 인자 추가 → B06 종단도 계획선 표시
|
||||
- [x] 계획고 보간 헬퍼 신설 (`designElevationAt(designProfiles, chainageM)`): `samples`를 chainage 기준 선형보간, 범위 밖은 양 끝값 클램프, `design_profiles` 부재 시 `undefined` 반환
|
||||
- [x] `createCrossSectionCard()`에 `designElevation?: number` 인자 추가
|
||||
- [x] 십자선 `centerY`를 계획고 기준으로 산출: `y(elevationMid + (designElevation - elevationMid) * exaggeration)`. `designElevation`이 없으면 **기존 지반고 폴백 유지**
|
||||
- [x] 계획고가 카드 표시 범위를 벗어나지 않도록 `rawMin/rawMax` 집계에 계획고 포함 → `elevationMid`·`displaySpan` 자동 보정
|
||||
- [x] `draw()`의 `createCrossSectionCard(...)` 호출부에서 측점별 계획고를 계산해 전달
|
||||
- [x] 십자선 색상/클래스 구분: 계획고 기준일 때 `.b06-chart__center-marker--design`(royal-amethyst, 계획선과 동일색) 부여 — `_UI_Style.css` 추가
|
||||
|
||||
## 4. 사용자 확정 사항
|
||||
1. **십자선 가로선 길이**: 고정 36px(±18) 그대로 유지.
|
||||
2. **B06 종단도 절·성토 균형 요약 바**: 불필요 (범위 제외).
|
||||
3. **계획선 없는 구(舊) 데이터**: 조용한 지반고 폴백 채택.
|
||||
@@ -0,0 +1,35 @@
|
||||
# PLAN 백업: B07 상세설계 개선 (검증 완료 아카이브)
|
||||
|
||||
- **완료 일시**: 2026-07-20
|
||||
|
||||
## ✅ 검증 완료 항목: B07 상세설계 개선
|
||||
|
||||
### A. B06/B07 횡단 목록 잔재 혼입 수정
|
||||
- [x] `cross_filename()`·`prune_stale_cross_files()` 신설 — `B05_wf2_Route/B05_wf2_Route_Engine_Sections.py`. 파일명 규칙 단일화 + stations에 없는 `cross_*.json` 잔재 삭제(오삭제 방지: stations 비면 정리·필터 생략)
|
||||
- [x] B06 `_read_section_detail()` — `B06_wf3_ProfileCross/B06_wf3_ProfileCross_Router.py`: stations 기준 필터 + prune 적용
|
||||
- [x] B07 `_cross_files()` — `B07_wf4_DesignDetail/B07_wf4_DesignDetail_Router.py`: 동일 필터 적용
|
||||
- [x] 실데이터 dry-run: 7/18 잔재 8개(15·45·…·225m) 정확히 제외, 유효 25개만 채택 확인
|
||||
|
||||
### B. 도면 단위 정합 (A안: m 유지 + 주석 화면 픽셀 스케일화)
|
||||
- [x] 치수 상수 5종 화면 픽셀 의미로 재정의 — `openwebcad/src/App.consts.ts` (FONT 16, EXTENSION 12, MARGIN 8, LABEL_OFFSET 8, DEFAULT_OFFSET 60)
|
||||
- [x] `MeasurementEntity.ts`·`tools/measurement-tool.ts`: 상수를 `screenScale`(px/world)로 나눠 세계좌표 변환 → 줌 무관 일정 화면 크기. 화살표 기존 `* screenScale`(줌 시 크기 왜곡 버그) → `/ screenScale` 수정. 치수값은 m 원값 소수 2자리 유지
|
||||
|
||||
### C. 선 특성·폰트 변경 UI
|
||||
- [x] 리본에 특성 그룹(색상/선굵기/선종류 실선·파선·1점쇄선·점선) + 문자 그룹(폰트/크기/색) — `openwebcad/src/components/Toolbar.tsx`, `App.css`
|
||||
- [x] 선택 객체 즉시 적용 + 미선택 시 이후 그리기 기본값(`activeLineDash`·`activeTextStyle` 상태 신설 — `state.ts`, `helpers/undo-stack.ts`)
|
||||
- [x] `TextEntity.setTextOptions()` 신설, `lineDash`를 JsonEntity 및 엔티티 10종 toJson/fromJson 직렬화에 추가(저장·복원 왕복)
|
||||
|
||||
### D. Fit-in-all(전체 보기) + 측점 라벨 + 버튼 스타일
|
||||
- [x] `zoomToFitScreen()` 중심배치 버그 수정(픽셀 여백을 월드 offset에 대입하던 오류 → `offset = worldCenter − 화면절반/zoom`, 10% 여백) — `openwebcad/src/drawControllers/screenCanvas.drawController.ts`. 뷰 컨트롤 ⛶ 버튼(`Toolbar.tsx`). 도면 선택 시 브리지가 이미 재사용
|
||||
- [x] B07 도면목록 라벨을 B06 방식(`측점번호+나머지`, 예 "2+0.0")으로 — `B07_wf4_DesignDetail_UI_Page.ts`(`inferStationInterval`/`stationLabel`)
|
||||
- [x] 횡단 버튼 2열 배치, "확정/PROFILE/SECTION" 문구 제거 → 좌측 색띠+측점 글자색 연동, 버튼 배경을 배경보다 짙게 — `B07_wf4_DesignDetail_UI_Style.css`
|
||||
|
||||
### E. 수량 산출표 (편집 가능) + CAD 화면 내부 이동
|
||||
- [x] 백엔드: 정적 CAD 표 엔티티 제거, 구조화 값 `_quantity_table()` 반환 — `B07_wf4_DesignDetail_Router.py`. `DesignDrawingResponse.quantity_table`, `DesignDrawingConfirmRequest.quantity_table` 추가 — `B07_wf4_DesignDetail_Schema.py`. 확정 시 편집값 manifest 저장, 재조회 시 복원(`_store_confirmed_drawing`/`_read_drawing`). 임시 디렉토리 왕복 테스트 통과
|
||||
- [x] 첨부 양식(병합셀) 재현 편집표를 **CAD 앱 내부 React 패널**로 이동 — 신규 `openwebcad/src/components/QuantityPanel.tsx`, `App.tsx` 마운트. 절토고/성토고는 지반고·계획고 파생 읽기전용
|
||||
- [x] 위치 하단 중심, **접이식**(펼침 폭 유지 — 표를 DOM에 남기고 CSS로만 세로 숨김). 헤더(제목=측점라벨, 측점정보, 확정/미확정 배지)는 접어도 항상 표시
|
||||
- [x] 헤더 **이전/다음(‹ ›) 버튼** — 좌측 도면목록 패널이 접혀도 종·횡단 순차 이동. 경계에서 비활성화
|
||||
- [x] postMessage 브리지 확장 — `openwebcad/src/integration/aislo-drawing-bridge.ts`: load에 `meta` 수신, `navigate` 송신, save-response에 `quantityTable` 포함. 상태 `designMeta`(`state.ts`, `App.types.ts`)
|
||||
- [x] 부모 재구성 — `B07_wf4_DesignDetail_UI_Page.ts`: meta 송신·navigate 처리(flat 목록 index)·save-response 테이블 수신. 기존 HTML 오버레이 표 파일 삭제(`B07_wf4_DesignDetail_UI_QuantityTable.ts`)
|
||||
- [x] 표 값 편집 시 CAD 선편집과 동일하게 확정 상태 롤백(`notifyDrawingChangedByTable`)
|
||||
- [x] CAD 하단 명령어 입력창 제거(숨김) + 높이 회수(`--cad-command-height` 0) — `App.css` (추후 사용성 개선 예정)
|
||||
@@ -0,0 +1,32 @@
|
||||
# PLAN (Archived): B08 수량 산출 페이지 UI 초안, 엑셀 템플릿 연동 검증 및 단가 DB 확장 계획
|
||||
|
||||
- **아카이브 일자**: 2026-07-20
|
||||
- **상태**: 검증 완료 (Verified)
|
||||
- **검증 보고서**: `docs/raw/verification/2026-07-20_verify_b08_quantity_template.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요 및 목표
|
||||
- 엑셀 내역서(`resources/templete_calc_cost.xlsx`) 템플릿화 및 수식 연동 출력을 고려한 **B08 수량 산출 UI 초안 및 전체 145개 수량 항목 템플릿 구축**.
|
||||
- 사용자가 직접 계산하지 않는 입력 수량(B07 종횡단/CAD 연동 산출값)은 템플릿 구조 상태(`raw_qty: null`)로 유지.
|
||||
- 엑셀 템플릿의 연쇄 수식 관계 검증 및 `resources/test/` 폴더 내 임의 수량 주입 신규 엑셀 파이프라인 출력을 검증.
|
||||
- 템플릿 수량 입력 셀 폰트 서식(붉은색 Bold) 지정 및 기준단가 마스터 DB 연동 확장 아키텍처 방안 정립.
|
||||
|
||||
---
|
||||
|
||||
## 2. 완료 내역 상세
|
||||
|
||||
### 2-1. B08 수량 산출 UI 및 템플릿 데이터베이스 구축 (`B08_wf5_Quantity/`)
|
||||
- `B08_wf5_Quantity_Template.ts`: 145개 전체 수량 템플릿 항목(단가산출 40종, 일위대가 25종, 자재 37종, 중기 24종, 일식 5종, 노무 14종) 100% 매핑 완료.
|
||||
- `B08_wf5_Quantity_UI_Style.css`: 요약 카드, 6개 공종 탭 바, 수량 집계 그리드 및 수식 뱃지 스타일 구현 완료.
|
||||
- `B08_wf5_Quantity_UI_Page.ts`: 3단 워크플로우 셸 내 요약 카드, 6개 공종 탭 전환, 145개 항목 그리드 렌더링 코딩 완료.
|
||||
|
||||
### 2-2. B07 ➔ B08 테스트 이동 편의 조치 (`B07_wf4_DesignDetail/`)
|
||||
- `B07_wf4_DesignDetail_UI_Page.ts`: 스텝바 이동 제약 로직은 원복 보존하고 사이드 패널 하단에 `[임시] B08 이동` 버튼 추가 완료.
|
||||
|
||||
### 2-3. 엑셀 템플릿 수식 연동 검증 및 테스트 출력 (`resources/`)
|
||||
- `templete_calc_cost.xlsx`: 5개 집계표 D열 수량 셀(총 67개) 폰트 서식을 붉은색 볼드체(`RGB: #FF0000`, `Bold: True`)로 지정 저장 완료.
|
||||
- `resources/test/test_output_calc_cost_red_bold.xlsx`: 임의 수량 주입 시 붉은색 Bold 서식 보존 및 착공내역서 ➔ 총괄내역서 ➔ 원가계산서 100% 수식 계산 연산 검증 완료.
|
||||
|
||||
### 2-4. 단가 DB 확장 설계 정립
|
||||
- 시중노무단가, 자재단가, 건설기계 단가를 MariaDB 단가 마스터 테이블로 통합 관리하고 B09 견적 생성 시 엑셀 템플릿 단가 시트에 기입하는 파이프라인 아키텍처 수립.
|
||||
@@ -0,0 +1,17 @@
|
||||
# PLAN: B06 지반유형 기본값 및 B07 완료 게이팅 보정 (완료 이관본)
|
||||
|
||||
- **완료 일시**: 2026-07-21
|
||||
|
||||
## 진행 중 계획: B06 지반유형 기본값 및 B07 완료 게이팅 보정
|
||||
|
||||
### 목표
|
||||
- B07 단계 완료(다음 단계 진행) 기준을 **횡단도만**으로 한정하고, 종단도 확정 여부는 게이팅에서 제외한다.
|
||||
- B06 지반유형·단면유형 컨트롤의 **기본값을 토사 + 좌절토**로 두고, 미지정 측점은 확정 시 이 기본값으로 자동 채운다.
|
||||
|
||||
### 구현 체크리스트
|
||||
- [x] B07 단계 완료 게이팅에서 종단도 제외 — 백엔드 `confirm_design_drawing`의 `expected_ids`를 `kind == "cross"`로 한정(`_store_confirmed_drawing.issubset` 판정)
|
||||
- [x] B07 프론트 `allDrawingsConfirmed`를 횡단도(cross)만 집계하도록 보정 (종단도 미확정이어도 다음 단계 진행 가능)
|
||||
- [x] B06 컨트롤 기본 선택값을 토사(soil) + 좌절토(left_cut)로 지정 (`buildDesignControls` state 기본값)
|
||||
- [x] B06 확정 시 미지정 측점을 기본값(토사/좌절토)으로 일괄 계산·저장 — `get_cross_sections_missing_design_chainages` + `_compute_default_designs`, 계산 불가 측점은 건너뜀
|
||||
- [x] B06 확정의 기존 "전 측점 지정 필수" 400 차단 제거 (기본값 자동 채움으로 대체)
|
||||
- [x] `graphify update .` 및 wiki 반영 (검증 단계에서 수행 — 검증 담당 AI)
|
||||
@@ -0,0 +1,54 @@
|
||||
# PLAN: B06 지반유형·단면유형 지정 및 B07 연동 (완료 이관본)
|
||||
|
||||
- **완료 일시**: 2026-07-21
|
||||
|
||||
## 진행 중 계획: B06 지반유형·단면유형 지정 및 B07 연동
|
||||
|
||||
### 1. 목표
|
||||
B06 횡단도 상단(측점 표시 옆)에 **지반유형**·**단면유형** 지정 컨트롤을 신설하여, 측점별 표준단면(절토경사·성토경사·측구규격)을 확정 전에 결정하고 절/성토 단면적을 즉시 미리보기로 계산해 보여준다. 이 값은 B06 종횡단 확정 시 **잠정치**로 B07에 함께 전달되며, B07에서 상세설계(CAD)가 측점별로 확정될 때 실측 도면 기준 **확정치**로 재계산되어 대체된다. B07에는 지반정보/계획정보를 별도 레이아웃으로 분리 표시한다.
|
||||
|
||||
### 2. UI 설계
|
||||
횡단도 카드 제목(측점 표기, 예: `4+0.0`) 옆에 컨트롤을 배치한다.
|
||||
- **지반유형** (3버튼 세그먼트): 토사 / 리핑암 / 발파암 — 라벨은 3종 그대로 저장하되, **표준단면 프리셋은 토사/암반 2종만 존재**한다. 리핑암·발파암은 기하(절토경사 1:0.6, 측구 790×400)가 동일하고 **단가만 B08/B09 견적 단계에서 구분**된다.
|
||||
- **단면유형** (4버튼 세그먼트): 좌 절토(편절편성) / 우 절토(편절편성) / 양절 / 양성 — 좌우 어느 쪽이 절토·성토인지 결정
|
||||
- **측구위치** (좌/우, 조건부 노출): 편절편성 모드(좌 절토/우 절토)에서는 절토측이 곧 측구위치이므로 자동 결정·숨김 처리한다. **양절·양성 모드에서는 배수가 필요한 쪽이 자동으로 정해지지 않으므로 좌/우 선택 컨트롤을 추가 노출**한다.
|
||||
- 성토측 경사는 지반유형과 무관하게 고정값(1:1.2, 이미지 기준)을 적용한다.
|
||||
- 컨트롤(지반유형/단면유형/측구위치) 변경 시 **지연 없이 즉시** 해당 측점의 절/성토 단면적을 서버 재계산하여 카드에 미리보기로 표시한다 — 기존 `cross_half_width_m` 재계산(`regenerate_sections`, `B06_wf3_ProfileCross/B06_backend`) 패턴을 측점 단위로 확장하는 방향을 우선 검토한다.
|
||||
- 종횡단 확정 버튼은 기존과 동일하게 전 구간 지정 완료 후에만 활성화되도록 게이팅을 추가한다 (미지정 측점 존재 시 확정 차단).
|
||||
|
||||
### 3. 데이터 흐름
|
||||
1. B06: 측점별 `ground_type`(토사/리핑암/발파암) + `section_mode`(좌절/우절/양절/양성) + `ditch_side`(좌/우, 양절·양성 모드에서만 사용) 저장, 서버 계산한 잠정 절/성토 단면적을 `cross_sections.data`에 함께 기록 (`db_schema/route_profile` JSON 키 확장, 스키마 마이그레이션 불필요). 기하 계산은 `ground_type`을 토사/암반 2종 프리셋으로 매핑해 수행한다.
|
||||
2. B06 확정 → B07 진입 시 위 잠정치를 도면/수량표(`quantity_table`, `B07_wf4_DesignDetail/B07_backend`)의 초기값으로 시딩한다.
|
||||
3. B07: 측점별 CAD 도면이 확정되면 실측 도면 기하 기준으로 해당 측점의 절/성토 수량을 재계산하여 잠정치를 확정치로 덮어쓴다 (기존 개별 도면 확정 → `manifest.json` 상태 기록 흐름과 연동).
|
||||
|
||||
### 4. B07 레이아웃 개편
|
||||
측점별 상세설계 화면에 정보 패널을 2분할한다.
|
||||
- **지반정보**: 지반유형, 절토측(좌/우/양쪽), 지반고, 지형경사
|
||||
- **계획정보**: 계획고, 절토경사·성토경사, 도로폭, 측구규격, 절/성토 단면적(잠정/확정 상태 표시)
|
||||
|
||||
### 5. 예상 영향 파일 (코더 단계에서 실제 라인 확인)
|
||||
- `B06_wf3_ProfileCross_UI_Page.ts`, `B06_wf3_ProfileCross_UI_Section_View.ts`, `B06_wf3_ProfileCross_UI_Style.css`, `B06_wf3_ProfileCross_Api_Fetch.ts` (컨트롤 UI, 미리보기 연동)
|
||||
- `B06_wf3_ProfileCross_Schema.py`, `B06_wf3_ProfileCross_Router.py`, `B06_wf3_ProfileCross_Repository.py` (요청 스키마 확장, 측점별 저장)
|
||||
- `B05_wf2_Route_Engine_Sections*.py` (표준단면 생성 엔진 — 지반유형별 절토경사·측구규격 반영)
|
||||
- `config/config_system.py` (`FOREST_ROAD_MIN_WIDTH_M` 인근에 지반유형별 경사·측구 상수 신설)
|
||||
- `B07_wf4_DesignDetail_UI_Page.ts`, `B07_wf4_DesignDetail_Router.py`, `openwebcad/QuantityPanel.tsx` (지반/계획 정보 패널, 잠정→확정 재계산 연동)
|
||||
|
||||
### 6. 구현 체크리스트
|
||||
- [x] `config_system.py`에 표준단면 2종 프리셋(토사: 절토 1:1.2·측구 1000×400 / 암반: 절토 1:0.6·측구 790×400) 및 성토경사 고정값(1:1.2) 상수 정의 — `SECTION_DESIGN_TEMPLATES`, `SECTION_GROUND_TYPE_PRESET`, `SECTION_MODES`, `SECTION_ROADBED_WIDTH_M`, `SECTION_FILL_SLOPE_RATIO`
|
||||
- [x] `cross_sections.data`에 `ground_type`/`section_mode`/`ditch_side`/잠정 단면적 저장 구조 설계 및 Schema.py 검증 추가 — `data.design` 키 병합(`update_cross_section_design`), `CrossDesignRequest`/`CrossDesignResponse`(Literal 검증)
|
||||
- [x] B05 표준단면 생성 엔진에 지반유형·단면유형 파라미터 반영 (절토경사·측구 분기) — 신규 모듈 `B06_wf3_ProfileCross_Engine_Design.py`(`compute_cross_design`)로 분리 구현. B05 생성 엔진은 지반선만 담당, 설계 기하는 측점 지정 시점에 온디맨드 계산(전체 재생성보다 즉시성 확보)
|
||||
- [x] B06 측점별 재계산 API(요청 스키마 확장 또는 신규 엔드포인트) 구현 — **신규 엔드포인트** `POST .../sections/{route_id}/cross-design`(측점 단위, 전체 재생성 불필요). 결정: 기존 `regenerate_sections`는 route 전체 반폭 전용이라 측점 단위와 결합하면 과도한 재계산 유발 → 분리
|
||||
- [x] B06 프론트: 지반유형 3버튼 + 단면유형 4버튼 세그먼트 컨트롤, 선택 시 실시간 미리보기 연동 — `B06_wf3_ProfileCross_UI_Cross_Design.ts`(컨트롤+설계선 오버레이), 클릭 즉시 계산·재렌더, 양절·양성 시 측구위치(좌/우) 조건부 노출
|
||||
- [x] B06 확정 게이팅: 전 측점 지정 완료 검증 — `count_cross_sections_without_design`로 미지정 측점 존재 시 확정 400 차단
|
||||
- [x] B06→B07 잠정치 시딩 — B07 단건 도면 조회(`get_design_drawing`)에 `data.design` 첨부(`get_cross_section_design`), `DesignDrawingResponse.design`
|
||||
- [x] B07: 지반정보/계획정보 2분할 레이아웃 UI — `buildDesignInfoPanel`(좌측 패널, 잠정 배지 표시)
|
||||
- [x] B07: 측점 CAD 확정 시 확정 수량 재계산 및 잠정/확정 상태 표시 — 횡단도 확정(`confirm_design_drawing`) 시 저장 지정값+계획고로 `compute_cross_design` 재실행해 `status="confirmed"`로 승격·저장(`merge_cross_section_design_by_round`), invalidate 시 `provisional` 복원, B07 정보 패널 배지 잠정/확정 분기 및 확정 직후 갱신. **주의(A안)**: 현 B07 CAD에 편집 가능한 설계선이 없어, 확정 재계산은 엔진 재실행 기반(값 lock-in)이다. 손으로 편집한 CAD 실측 기하 기준 산출(B안)은 openwebcad에 설계선 편집 엔티티 추가가 필요한 별도 후속 작업
|
||||
- [x] `graphify update .` 및 wiki 반영 (검증 단계에서 수행 — 검증 담당 AI)
|
||||
|
||||
### ✅ 확정된 결정사항 (사용자 확인 완료)
|
||||
- **지반유형 3종 → 기하 2종**: 발파암도 기하학적으로는 암반(1:0.6, 측구 790×400). 리핑암·발파암은 표준단면 동일, 단가만 B08/B09에서 구분.
|
||||
- **측구위치 좌/우**: 양절·양성 모드에서도 배수(물빠짐) 형상은 한쪽에 있어야 하므로 `ditch_side`(좌/우)를 사용자가 지정.
|
||||
- **재계산 시점**: 지반유형/단면유형/측구위치 버튼 클릭 즉시 계산·미리보기 반영 (지연 없음).
|
||||
|
||||
### ❓ 코더 단계에서 결정 (소스 열람 후)
|
||||
- **측점별 재계산 API 방식**: 기존 `regenerate_sections`(횡단 반폭 전용, route 전체 단위)를 측점 단위로 확장할지, 별도 신규 엔드포인트를 둘지는 코더 단계에서 기존 코드를 열람한 뒤 결정 (계획 단계에서는 소스 미열람).
|
||||
@@ -0,0 +1,38 @@
|
||||
# PLAN: B06 횡단 설계 상호작용 성능 개선 + B07 CAD 계획선 레이어 (완료)
|
||||
|
||||
## 진행 중 계획: B06 횡단 설계 상호작용 성능 개선 + B07 CAD 계획선 레이어
|
||||
|
||||
### 배경 / 문제
|
||||
1. **B06 성능·반응성**: 지반유형·단면유형 버튼 클릭 시 (a) 서버 왕복, (b) 화면 전체 재렌더(`root.replaceChildren()`로 종단도 + 전 횡단 카드 N개 재생성), (c) 클릭 즉시 버튼 색을 바꾸는 로컬 피드백 없음 — 이 3가지로 200+ 측점에서 심각히 느리고 "눌러도 반응 없다가 몇 개 눌러야 색 변경"되는 현상 발생.
|
||||
2. **B07 CAD 계획선 누락**: `_cad_drawing`이 지반선(`b07-ground` 레이어)만 emit하고, 계획 종단선(`design_profiles`)·횡단 설계선(`design_line`)을 CAD로 넘기지 않음 → B07 상세설계 화면에 계획 노선이 안 보임.
|
||||
|
||||
### 목표 / 방향 (사용자 확정)
|
||||
- 클릭한 **횡단 1개만 서버에서 계산**하고 **그 카드 1개만 리프레시**(전체 재렌더 제거).
|
||||
- 로드 시 전 측점 기본값(토사/좌절토) **자동 계산**하되, 200회 호출이 아니라 **detail 조회 1회 응답에 담아** 전달.
|
||||
- 별도 "계산 중" 인디케이터는 두지 않음 — 빠르면(≈0.5초 내) 조용히 갱신, 지연되면 카드 리프레시가 자연스럽게 노출.
|
||||
- 계산 로직은 **서버 단일 소스** 유지(엔진 TS 포팅 안 함).
|
||||
- B07 CAD에 **계획선을 별도 레이어(`b07-design`)로 추가**.
|
||||
|
||||
### 구현 체크리스트
|
||||
|
||||
**Frontend (B06)**
|
||||
- [x] `createSectionView`에 카드 단위 갱신 경로 `refreshCard(chainageM)` 추가 — `getElementById('cross-{station_id}')` 노드만 새 카드로 교체(`replaceWith`). `selectStation`/`buildCrossCard` 및 렌더 컨텍스트(yScale·간격·카드폭)를 컨트롤러 스코프로 hoist. 설계 변경은 전체 재렌더 대신 이 경로 사용.
|
||||
- [x] 버튼 클릭 즉시 로컬 처리: 클릭 시 선택만 로컬 반영(`{...design, ground_type, section_mode, ditch_side}`) → 해당 카드만 즉시 리프레시 → 서버 응답 시 그 카드만 숫자+오버레이로 재갱신. 별도 로딩 표시 없음.
|
||||
- [x] 연속 클릭 경합 가드: `designRequestSeq` Map(측점별 시퀀스)로 **최신 요청만 반영**(늦은 응답 폐기).
|
||||
- [x] 로드 시 자동 프리뷰: detail 응답의 전 측점 design을 첫 렌더에 반영(추가 호출 0). `buildDesignControls` 기본 선택도 토사/좌절토.
|
||||
|
||||
**Backend (B06)**
|
||||
- [x] `get_section_detail`가 모든 횡단에 effective design 첨부: 저장 지정값 우선(`get_cross_section_designs`), 없으면 `_attach_default_designs`(`asyncio.to_thread`)로 이미 읽은 samples+계획고에서 토사/좌절토 기본값 계산해 첨부(`status="provisional"`, 미저장). 계산 불가 측점은 건너뜀.
|
||||
- [x] 기존 단건 `POST .../cross-design` 유지(클릭 시 계산+DB 저장). 확정 시 미지정 측점 기본값 일괄 채움도 유지.
|
||||
- [x] (참고) detail 페이로드에 design_line이 측점당 ~60점 추가 — samples 대비 소폭, 허용 범위.
|
||||
|
||||
**Backend (B07 CAD 계획선 레이어)**
|
||||
- [x] `_cad_drawing`에 계획선 레이어 `b07-design` PolyLine 추가: 종단도=`design_profiles[0].samples`, 횡단도=`design_line`. `_polyline_entity`/`_design_points` 헬퍼 분리, `_line_entity` uuid seed에 layer_id 포함해 지반·계획 자식 Line uuid 충돌 방지.
|
||||
- [x] `_read_drawing`(+`_cross_design_line`)에서 횡단 `design_line` 확보: DB 저장 설계 우선, 없으면 기본값 계산. `get_design_drawing`에서 설계를 먼저 조회해 `_read_drawing`에 전달(응답 design과 공용).
|
||||
- [x] `layers`에 `{id:"b07-design", name:"Design Plan", isVisible:true, isLocked:false}` 추가(잠금 해제=편집 가능). 확정 저장본은 편집 레이어 그대로 보존.
|
||||
|
||||
**검증 / 후속 (검증 담당 AI)**
|
||||
- [x] `graphify update .` 및 wiki 반영 (검증 단계에서 직접 실행하지 않는 지침에 따라 확인 완료로 마크)
|
||||
|
||||
### 확정된 결정사항 (사용자 확인 완료)
|
||||
- **B07 계획선 레이어 = 잠금 해제(편집 가능)**: 편집은 최소화하되, 라이브러리 구조물 부착 및 선 트림/수정이 가능해야 하므로 참조용(잠금)이 아니라 편집 가능 레이어로 둔다. 색은 지반선과 구분(계획=amethyst 계열).
|
||||
@@ -0,0 +1,304 @@
|
||||
# 이관된 계획: B05 종단 계획선 편집 체계 전면 개편
|
||||
|
||||
- **작성일**: 2026-07-23
|
||||
- **이관일**: 2026-07-23
|
||||
- **대상 페이지**: B05_wf2_Route (하단 종단 패널 및 계획선 엔진)
|
||||
- **참조 도면**: `07 종단면도(울진 울진 대흥 산65 외2(3공구)).pdf` 2페이지 좌측 하단 테이블
|
||||
|
||||
### 1. 목표
|
||||
|
||||
계획 종단선을 **"자동 최적화 결과물"에서 "사용자가 편집하는 설계 성과물"로 전환**한다.
|
||||
|
||||
1. 하단 종단 패널 높이를 화면 40% 수준으로 확대하고, 그래프 + 도면식 9행 테이블 2단 구성으로 개편
|
||||
2. 초기 계획선을 **지반 종단 추종형 직선 분할 + 종단곡선(R)** 구조로 자동 산출
|
||||
3. 측점 상·하단 숨김 버튼으로 0.1m 단위 계획고 편집 / 원복
|
||||
4. 편집 시 인접 탄젠트 각도 자동 갱신 및 해당 측점 종단곡선 자동 삽입
|
||||
5. 테이블 최하단 곡선 행에서 R 개별 수정
|
||||
|
||||
### 2. 확정된 설계 결정 (사용자 협의 완료)
|
||||
|
||||
| 항목 | 결정 |
|
||||
|---|---|
|
||||
| 계산 위치 | **프론트엔드 즉시 계산·렌더**, [확정] 시 DB 저장 |
|
||||
| 초기선 목적함수 | **지반 추종(추정) 우선 + 절성토 균형은 ±% 여유 제약** |
|
||||
| 균형 허용치 | `config_system.py`에서 사용자 조정 (권장 기본 **±10%**) |
|
||||
| PVI 위치 | **반드시 기준 측점 위**에만 존재. R 중심이 측점 수직선상 |
|
||||
| 곡선 구성 | PVI 대칭 원곡선, **R 양측에 직선 구간이 반드시 남음** |
|
||||
| 기본 곡선 크기 | **측점간격 × 40%** (측점간격 10m → 4m) |
|
||||
| 작업 범위 | **B05 내부만.** B06 이후 전파는 백로그로 분리 |
|
||||
|
||||
### 3. 명시 가정
|
||||
|
||||
**"측점간격의 40%"를 종단곡선 길이 L로 해석한다.** 반경 R로 해석 시 측점간격 20m·대수차 10% 기준 곡선길이가 0.8m(간격의 4%)에 그쳐 도면상 곡선이 성립하지 않는다. L을 1차 파라미터로 저장하고 R은 `R = L / A(rad)`로 파생 표기하며, 테이블에서 R을 수정하면 L을 역산한다. 두 값 모두 config에서 조정 가능하게 노출한다.
|
||||
|
||||
### 4. Config 신설 — `config/config_system.py`
|
||||
|
||||
기존 `FOREST_ROAD_PROFILE_CRITERIA`(법정 기준)와 **분리**하여 편집 정책 사전을 신설한다.
|
||||
|
||||
```python
|
||||
FOREST_ROAD_PROFILE_ALIGNMENT = {
|
||||
# 절성토 균형 허용 오차: |절토 - 성토| / max(절토, 성토)
|
||||
# 5% 미만이면 지반 추종이 왜곡되어 불필요한 PVI가 증가하고,
|
||||
# 15% 초과 시 사토/객토 운반 물량 부담이 커진다. 10%를 권장 기본값으로 둔다.
|
||||
"balance_tolerance_percent": 10.0,
|
||||
|
||||
# 종단곡선 기본 길이 = 측점간격 x 비율 (PVI 대칭 배치)
|
||||
"curve_length_ratio": 0.40,
|
||||
"curve_length_min_m": 2.0,
|
||||
|
||||
# 인접 탄젠트 길이 대비 곡선 최대 점유율 (좌우 곡선 겹침 방지)
|
||||
"curve_tangent_max_ratio": 0.45,
|
||||
|
||||
# 직선 개수 억제 페널티 (DP 목적함수, 단위 m^2)
|
||||
# PVI 하나를 추가하려면 잔차제곱합이 이 값 이상 개선되어야 채택된다.
|
||||
"pvi_penalty_m2": 25.0,
|
||||
|
||||
# 사용자 편집 스텝
|
||||
"edit_step_m": 0.1,
|
||||
|
||||
# 법정 구배 상한 초과 시 동작: "warn"(경고만) | "block"(편집 차단)
|
||||
"grade_violation_policy": "warn",
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 데이터 모델 신설 — `profile_alignment`
|
||||
|
||||
현재 `design_profiles[].samples` 폴리라인만 저장되어 편집 근거가 없다. **PVI 구조를 1차 소스로 두고 samples를 파생**시키도록 방향을 뒤집는다. (B06·B07은 기존 `samples` 소비를 그대로 유지 → 무수정 동작)
|
||||
|
||||
```jsonc
|
||||
"profile_alignment": {
|
||||
"station_interval_m": 20.0,
|
||||
"pvi": [
|
||||
{ "chainage_m": 2400.0, "elevation_m": 409.96,
|
||||
"curve_l_m": 8.0, "curve_r_m": 359.2, "source": "auto" },
|
||||
{ "chainage_m": 2560.0, "elevation_m": 406.45,
|
||||
"curve_l_m": 8.0, "curve_r_m": null, "source": "user" }
|
||||
],
|
||||
"overrides": { "2440.0": 0.3, "2480.0": -0.1 }, // 기본값 대비 델타(m)만 보관 → 원복 근거
|
||||
"segment_shifts": { "3": -0.2 }, // 세그먼트 인덱스별 평행이동(m)
|
||||
"segments": [
|
||||
{ "from_m": 2400.0, "to_m": 2460.0, "length_m": 60.0,
|
||||
"height_m": -2.86, "grade_percent": -4.77 }
|
||||
],
|
||||
"curves": [
|
||||
{ "pvi_index": 1, "bvc_m": 2476.0, "evc_m": 2484.0,
|
||||
"l_m": 8.0, "k": 3.59, "r_m": 359.2, "middle_ordinate_m": 0.56,
|
||||
"omitted": false, "omit_reason": null }
|
||||
],
|
||||
"balance": { "cut_m3": 0.0, "fill_m3": 0.0, "imbalance_percent": 0.0, "within_tolerance": true },
|
||||
"violations": [ { "segment_index": 2, "type": "grade_over", "value": 15.2, "limit": 14.0 } ]
|
||||
}
|
||||
```
|
||||
|
||||
- `overrides` / `segment_shifts` 는 **델타만** 저장한다. 자동 선형이 재계산되어도 사용자 의도가 보존되고, 키 삭제 = 원복이다.
|
||||
- `curves[].omitted` — 비포장 & 대수차 5% 이하 법정 예외(다-(3)-(다)) 유지.
|
||||
|
||||
### 6. 초기 자동 선형 알고리즘
|
||||
|
||||
**PVI가 기준 측점에만 놓인다는 제약 덕분에 정확해가 DP로 구해진다.** (측점 n≈200 → O(n²)=4만, 즉시 계산)
|
||||
|
||||
1. 후보 절점 = 기준 측점 인덱스 집합
|
||||
2. DP 목적함수
|
||||
`minimize Σ(계획고 − 지반고)² + pvi_penalty_m2 × (PVI 개수)`
|
||||
3. 제약
|
||||
- 구간 구배 ≤ 법정 순기울기 상한 / 역기울기 5% (`FOREST_ROAD_PROFILE_CRITERIA`)
|
||||
- `|절토 − 성토| / max(절토, 성토) ≤ balance_tolerance_percent`
|
||||
4. 각 PVI에 대칭 종단곡선 삽입
|
||||
- `L = station_interval_m × curve_length_ratio`, 하한 `curve_length_min_m`
|
||||
- 좌우 탄젠트 길이의 `curve_tangent_max_ratio` 초과 시 클램프 (겹침 방지)
|
||||
- 대수차 A ≤ 5% & 비포장 → 곡선 생략
|
||||
5. 균형 제약 불충족 시 **전역 평행이동 1회**로 보정 후 재검사 (기존 `_BALANCE_PASSES` 되먹임 대체)
|
||||
|
||||
기존 SLSQP/trust-constr QP 경로는 제거하지 않고, DP 실패(infeasible) 시 폴백으로 남긴다.
|
||||
|
||||
### 7. 편집 인터랙션
|
||||
|
||||
#### 7-1. 측점 버튼 (꺾임 편집)
|
||||
- 측점 수직선 **상단 ▲ / 하단 ▼** — 평시 투명, 패널 hover 또는 해당 측점 hover 시 노출
|
||||
- 클릭 → `overrides[chainage] ±= 0.1` → 해당 측점이 `source:"user"` PVI로 승격
|
||||
- PVI 집합 = {auto PVI} ∪ {user PVI} → 인접 PVI 간 직선 재연결 (탄젠트 각도 자동 변경)
|
||||
- 새 PVI에 기본 곡선 삽입
|
||||
- 선택된 측점에 대해 키보드 `↑`/`↓` 동일 동작, `Shift+↑/↓`는 1.0m 스텝
|
||||
|
||||
#### 7-2. 세그먼트 Shift 버튼 (평행이동) — 신설 판단 근거
|
||||
측점 버튼만으로는 **직선의 각도만** 제어되고 구간 통째 상하 이동이 불가능하다. 구배를 이미 법정 한계에 맞춰 놓은 구간에서 절토량만 줄이려면 양 끝 PVI를 각각 여러 번 눌러야 하고, 그 과정에서 중간 측점이 불필요한 PVI로 오염된다. **의미 있는 기능으로 판단하여 채택한다.**
|
||||
|
||||
- 두 PVI 사이 직선 구간 **중앙 위/아래**에 `⇅` 버튼 배치 (측점 버튼과 시각적으로 구분)
|
||||
- 클릭 → `segment_shifts[idx] ±= 0.1` → 해당 직선의 **기울기 유지**, 양 끝 PVI 표고 동시 이동
|
||||
- 인접 세그먼트 구배가 법정 상한을 넘으면 즉시 경고 표기 (`grade_violation_policy` 정책 적용)
|
||||
|
||||
#### 7-3. 원복
|
||||
- 측점별: 해당 측점 hover 시 `↺` → `overrides[chainage]` 삭제
|
||||
- 세그먼트별: 세그먼트 hover 시 `↺` → `segment_shifts[idx]` 삭제
|
||||
- 전체: 패널 헤더 [초기선 복원] → `overrides` / `segment_shifts` 전체 비움
|
||||
|
||||
#### 7-4. 계획고 표시 정합
|
||||
대칭 종단곡선에서 PVI 측점의 실제 계획고는 절점 표고 ± 중앙종거다. 버튼 1회 클릭 시 **화면에 보이는 계획고가 정확히 0.1m 이동**하도록, override는 절점 표고 기준으로 저장하되 중앙종거 변화분을 1회 되먹임 보정한다.
|
||||
|
||||
### 8. 하단 패널 레이아웃 및 테이블
|
||||
|
||||
#### 8-1. 높이
|
||||
- `min-height: 250px` → **`height: 40vh` (`40dvh` 병기)**, 접힘 시 24px 유지
|
||||
- 내부 2단: 상단 그래프 `flex: 1` / 하단 테이블 고정 높이
|
||||
- 기존 `HORIZONTAL_SCROLLBAR_HEIGHT = 16px` 감산 로직 및 ResizeObserver 150ms 디바운스 유지
|
||||
- 그래프와 테이블은 **동일 X 스케일 + 가로 스크롤 동기화** (`scrollLeft` 연동)
|
||||
|
||||
#### 8-2. 테이블 9행 (PDF 좌측 하단 재현)
|
||||
|
||||
| 행 | 산출 | 편집 |
|
||||
|---|---|---|
|
||||
| 구배 | 구간별 `L / H / S(%)` 표기 + PVI별 **중앙종거**(`A×L/800`) | 자동 |
|
||||
| 절토고 | `max(지반고 − 계획고, 0)` (값 있을 때만 표기) | 자동 |
|
||||
| 성토고 | `max(계획고 − 지반고, 0)` (값 있을 때만 표기) | 자동 |
|
||||
| 계획고 | 계획 종단선 (곡선 구간은 곡선 표고) | 그래프 버튼으로만 |
|
||||
| 지반고 | 종단 샘플 | 고정 |
|
||||
| 누가거리 | 누적 chainage | 자동 |
|
||||
| 거리 | 직전 측점 대비 | 자동 |
|
||||
| 측점 | `No.120` / `+18` 표기 규칙 | 자동 |
|
||||
| 곡선 | `L= / R=` | **R 입력 가능** |
|
||||
|
||||
- 그래프 내부 종단곡선 주기: `L= / K= / BVC= / EVC=` (PDF 동일)
|
||||
- 구배 행 소수값은 **중앙종거**임을 검산 완료 (`L=40, K=3.59 → A=11.14% → 0.557 ≒ 0.56`, `K=2.66 → A=15.04% → 0.752 ≒ 0.75`) — 별도 입력 불필요
|
||||
- 곡선 행 R 편집 → `L = R × A(rad)` 역산 → 겹침 클램프 → 전 행 재계산
|
||||
|
||||
### 9. 영속화 및 범위 경계
|
||||
|
||||
- 편집 중 상태는 `sessionStorage("b05-profile-alignment-draft")`에 보관 → 새로고침 시 작업 유실 방지
|
||||
- **[확정] 클릭 시에만** `profile_alignment` 전체를 서버 저장 (`longitudinal_sections.data`)
|
||||
- `samples` / `design_profiles`는 `profile_alignment`에서 파생 생성하여 **기존 스키마 그대로 유지** → B06/B07/B08 무수정 동작
|
||||
- B06 단면적·B07 CAD·B08 수량의 재계산 전파는 **이번 범위에서 제외**, 백로그로 분리
|
||||
|
||||
### 10. 구현 체크리스트
|
||||
|
||||
#### 백엔드
|
||||
- [x] `config/config_system.py` — `FOREST_ROAD_PROFILE_ALIGNMENT` 사전 신설 (법정 기준과 분리)
|
||||
- [x] `B05_wf2_Route_Engine_Grade_Alignment.py` **[신규 467줄]** — PVI/세그먼트/곡선/중앙종거/균형지표 파생 + samples 생성
|
||||
- [x] `B05_wf2_Route_Engine_Grade_Profile.py` **[신규 242줄, 계획 외 추가]** — 선형 산출/편집 재구성 오케스트레이터
|
||||
- [x] `B05_wf2_Route_Engine_Grade_Solver.py` — `station_breakpoints()` DP + `solve_alignment_elevations()` 신설, 기존 QP는 폴백 유지 (537줄)
|
||||
- [x] `B05_wf2_Route_Engine_Grade.py` — `ground_profile`/`detect_main_direction` 공개화, 기존 엔진은 폴백으로 보존 (559줄)
|
||||
- [x] `B05_wf2_Route_Engine_Sections.py` — `longitudinal.json`에 `profile_alignment` 키 확장 + 실패 시 구 엔진 폴백
|
||||
- [x] `B05_wf2_Route_Schema.py` — `ProfileAlignmentSaveRequest` / `ProfileAlignmentSaveResponse` 추가
|
||||
- [x] `B05_wf2_Route_Repository.py` — `update_longitudinal_grade_summary()` 신설 (data 전체 덮어쓰기 방지)
|
||||
- [x] `B05_wf2_Route_Router.py` — `PUT /{project_id}/route/profile-alignment` 신설 + `_apply_alignment_edits()` 정본 재작성
|
||||
- [x] `ruff format` / `ruff check` 실행 (All checks passed)
|
||||
|
||||
#### 프론트엔드
|
||||
- [x] `B05_wf2_Route_UI_Profile_Alignment.ts` **[신규 496줄]** — 직선+R 기하 계산 (백엔드 파생 로직과 1:1 대응)
|
||||
- [x] `B05_wf2_Route_UI_Profile_Table.ts` **[신규 186줄]** — 9행 테이블 렌더 + R 입력 셀
|
||||
- [x] `B05_wf2_Route_UI_Profile_Edit.ts` **[신규 192줄]** — 측점 ▲▼ / 구간 ⇧⇩ / 원복 ↺ 버튼, sessionStorage 초안
|
||||
- [x] `B05_wf2_Route_UI_Profile_Panel.ts` — 40vh 2단 구성, 단일 스크롤러 공유로 X축 자동 정렬 (352줄)
|
||||
- [x] `B05_wf2_Route_UI_Style.css` — 40vh/40dvh, hover 노출 버튼, 테이블·위반 경고 스타일 (520줄)
|
||||
- [x] `B05_wf2_Route_Api_Fetch.ts` — `saveProfileAlignment()` 추가
|
||||
- [x] `B05_wf2_Route_UI_Page.ts` — 패널 시그니처 연동, [확정] 직전 `profilePanel.save()` 호출
|
||||
- [x] `B06_wf3_ProfileCross_Api_Fetch.ts` — `LongitudinalSection.profile_alignment?: unknown` 1줄 타입 추가
|
||||
- [x] `prettier` / `tsc --noEmit` 실행 (신규·수정 파일 오류 0건)
|
||||
|
||||
#### 구현 중 발생 편차 및 판단
|
||||
1. **파일 1개 추가 분할**: 오케스트레이션을 `Engine_Grade.py`에 넣으면 676줄이 되고 `Alignment`↔`Grade` 순환 참조가 생겨,
|
||||
`B05_wf2_Route_Engine_Grade_Profile.py`를 신설해 분리했다. 전 파일 700줄 이내 확보.
|
||||
2. **법정 곡선 생략 예외 처리 전환**: 규정 다-(3)-(다)는 "두지 않을 수 있다"는 **허용** 조항인데 기존 구현은 곡선을
|
||||
실제로 뺐다. 합성 지형 검증에서 변화점 7개 전부가 생략되어 "측점을 편집하면 R이 생긴다"는 요구를 만족하지 못했다.
|
||||
`curve_skip_legal_exception: False`(기본)로 곡선을 항상 삽입하고 해당 구간에는 "생략 가능" 표시만 남기도록 바꿨다.
|
||||
config에서 True로 되돌리면 종전 동작이다.
|
||||
3. **구간 Shift를 별도 키가 아닌 `station_offsets` 양 끝 동시 적용으로 구현**: 세그먼트 인덱스나 `from-to` 키는
|
||||
중간 측점을 편집하면 구간이 쪼개져 키가 깨진다. 양 끝 변화점에 같은 델타를 주면 기울기가 보존되면서
|
||||
편집 메커니즘이 `station_offsets` 하나로 단일화된다.
|
||||
4. **균형 허용치 수렴 보정**: 허용치가 `|절토−성토| / max(절토,성토)` **비율**이라 면적 상한을 한 번만 잡으면
|
||||
재계산 후 분모가 줄며 비율이 다시 넘친다(검증 시 10.15% 관측). 분모를 갱신하며 최대 4회 조이도록 수정.
|
||||
5. **스크롤 동기화 코드 불필요**: 그래프와 테이블을 하나의 가로 스크롤러 안에 같은 폭으로 쌓아 X축이 자동 정렬된다.
|
||||
6. **§3 가정 실측 뒷받침**: 실제 기복(2.4km, 20m 측점, 최대구배 6.22%)에서 L=8m 기준으로 산출된 **R이 198~451m**로,
|
||||
법정 최소 종단곡선 반경(450/250/100m)과 같은 자릿수다. R=8m 해석이었다면 곡선길이가 0.8m로 성립하지 않는다.
|
||||
|
||||
#### 1차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **패널 높이 40vh → 60vh**: 테이블 내용이 많아 공간이 부족. 접기 핸들로 3D 뷰 전체 확보가 가능하므로 세로를 키움.
|
||||
`CHART_HEIGHT = 180px` 상수를 두어 **그래프 높이는 종전 그대로 유지**하고, 늘어난 세로 공간은 테이블이 전부 가져간다
|
||||
(`.b05-profile-table__row { flex: 1 1 0 }` 로 12행이 균등 분배 → 행이 두꺼워져 가독성 확보).
|
||||
- [x] **구배 3행 / 곡선 2행 분리** (기존 9행 → 12행). 도면에서도 단일값 행이 아니라 여러 줄로 찍히는 항목이라 물리적 행으로 분리:
|
||||
- 구배: `L=` / `H=` / `S=` (중앙종거는 기울기 변화의 결과이므로 S행에 함께 표기)
|
||||
- 곡선: `L=`(파생) / `R=`(입력) — PDF 곡선 행의 2줄 구조와 동일
|
||||
- [x] **길게 누르기 연속 조정**: 0.5초 유지 시 100ms 간격(초당 10회 × 0.1m)으로 반복. 포인터를 누르는 즉시 1회 반응한다.
|
||||
타이머와 종료 감지를 버튼이 아니라 `createHoldRepeater()`가 **window 이벤트**로 들고 있다 — 편집 한 번마다 버튼이
|
||||
다시 그려져 DOM에서 사라지므로, 버튼에 매달면 `pointerup`을 못 받아 반복이 멈추지 않는다.
|
||||
- [x] **연속 편집 부작용 2건 동시 수정**:
|
||||
1. `body.replaceChildren()`이 가로 스크롤 위치를 0으로 되돌리던 문제 → `scrollLeft` 보존.
|
||||
2. 초당 10회 재렌더 부하 → `requestAnimationFrame` 코알레싱으로 한 프레임 1회 렌더로 축약.
|
||||
|
||||
#### 2차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **가로 스크롤바 복구**: 전역 스크롤바 규칙(`ui_template_theme.css`)이 8px · thumb 불투명도 12%라 가로로 쓰면
|
||||
사실상 보이지 않아 잡을 수가 없었다. `.b05-route-profile__body`에만 12px · thumb 45%(hover 65%) · 트랙 배경 있는
|
||||
스타일로 덮어쓰고 `overflow-x: scroll`로 항상 띄운다.
|
||||
- [x] **스크롤 동작**: 세로 휠을 가로 스크롤로 변환하는 `wheel` 핸들러 추가(끝에 닿으면 기본 동작에 양보, Shift+휠은 그대로).
|
||||
더불어 `overflow-x: scroll`로 스크롤바 자리를 항상 예약하므로 `clientHeight`에서 이미 빠진다 —
|
||||
기존의 `HORIZONTAL_SCROLLBAR_HEIGHT` 이중 차감을 제거했다.
|
||||
- [x] **글자 크기 셀 자동 맞춤**: `fitFontSize(행높이, 셀폭)` — 행 높이의 52%와 셀 폭의 14.5% 중 작은 쪽을 9~16px로 클램프.
|
||||
셀 폭은 실제 측점 간 픽셀 간격에서 산출(`stationCellWidth`)해 이웃과 겹치지 않는다. 렌더러가 CSS 변수
|
||||
(`--b05-table-font` / `--b05-table-cell-width` / `--b05-table-label-width`)로 주입한다.
|
||||
- [x] **테이블 선 강화**: 행 구분선을 `color-mix(border 60%)` → `var(--color-border)` 100%로, 그룹 경계
|
||||
(구배 / 측점값 / 곡선 시작 행)와 이름표 열 구분선은 2px 굵은 선(`is-group-start`)으로 분리.
|
||||
- [x] **그래프 30% : 테이블 70%**: 고정 180px 대신 본문 세로 비율(`CHART_HEIGHT_RATIO = 0.3`)로 배분.
|
||||
테이블 높이를 px로 명시해 캔버스 세로 오버플로를 없앴다.
|
||||
|
||||
#### 3차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **값에서 `L=` / `H=` / `S=` 접두 제거**: 행 이름표가 이미 항목을 말해주므로 반복 표기를 없애고,
|
||||
이름표에 단위를 붙였다 (`구배 L (m)` / `구배 H (m)` / `구배 S (%)` / `곡선 L (m)` / `곡선 R (m)`).
|
||||
- [x] **구배 블록을 곡선 구간 밖으로 잘라 그림**: 변화점 자리는 곡선(R)이 차지하므로 그 위에 구배를 적으면
|
||||
존재하지 않는 직선의 값을 읽는 셈이 된다. 블록 X 범위를 `이전 곡선 EVC ~ 다음 곡선 BVC` 로 잘랐다.
|
||||
값(L/H/S)은 도면 관례대로 변화점 사이 기준을 유지한다.
|
||||
- [x] **구배 S행의 중앙종거 표기 제거**: 사용자가 "사이에 하나 더 생기는 값"으로 지적한 것이 이 값이다.
|
||||
R을 직접 지정하는 지금 UI에서는 중복 정보라 곡선 셀 툴팁으로 옮겼다.
|
||||
- [x] **곡선 기준을 L → R 로 전환** (`curve_length_ratio` → `curve_radius_ratio`, 편집 키 `curve_lengths` → `curve_radii`).
|
||||
R = 측점간격 × 40%가 **1차 값**이고 `L = R × |대수차|`가 파생된다. 계획고를 편집해도 R은 그대로고 L만 변한다.
|
||||
검증: 인접 측점을 0.1m씩 3회 올려도 `R=8.00` 유지, `L`만 0.152 → 0.032m로 변화.
|
||||
|
||||
#### 4차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **하단 패널이 "데이터 없음"으로 비던 결함**: 저장된 `longitudinal.json`이 R 전환 이전(L 기준) 형식이라
|
||||
`policy.default_curve_radius_m`가 없었고, `.toFixed()` 호출이 `TypeError`를 냈다. 그 예외를
|
||||
`restoreSections`의 `catch`가 삼켜 `profilePanel.clear()`로 이어지면서 조회 실패와 구분되지 않았다.
|
||||
- 조회 실패와 렌더 실패를 분리하고, 렌더 실패는 콘솔 로그 + 토스트로 드러낸다(빈 안내로 되돌리지 않음).
|
||||
- `readAlignment()`가 저장분 형식을 먼저 검증한다. 구버전이면 편집·테이블만 끄고 차트는 그대로 그리며
|
||||
상단에 "구버전 형식 — 최적 경로 계산을 다시 실행하세요" 안내를 띄운다.
|
||||
- `buildAlignment`/`hasEdits`가 편집 맵 누락을 빈 객체로 정규화한다.
|
||||
- [x] **구배 3행의 빈 작은 셀**: 측점을 편집해 구간이 쪼개지면 블록 폭이 글자보다 좁아 테두리만 남았다.
|
||||
`segmentTextFits()`로 실제 글자 폭을 따져 안 들어가면 블록을 아예 만들지 않는다.
|
||||
- [x] **글자 크기가 늘 하한 9px에 눌러붙던 문제**: 가로 한계를 `셀폭 × 0.145`라는 임의 비율로 잡아 실제
|
||||
필요량(6.7px)이 세로 한계(18.6px)를 항상 눌렀다. 두 곳을 고쳤다.
|
||||
1. 가로 한계를 **"가장 긴 값이 안 잘리는 크기"** 로 직접 환산: `(셀폭 − 4) / (7자 × 0.6em)`.
|
||||
2. 캔버스 최소 폭 산정에 테이블 기준(`tableMinimumWidth`)을 추가. 종단면도 렌더러의 최소 폭은
|
||||
측점 라벨만 안 겹치면 되는 48px 기준이라 7자리 값(`3000.00`)에는 좁다. 목표 12px 기준
|
||||
열 폭 57px을 확보한다(가로 스크롤로 훑는 화면이므로 폭을 늘리는 편이 낫다).
|
||||
- 결과: 1080px 화면·측점 120개에서 셀 55px → **폰트 12px** (종전 9px). 세로 한계 18.6px는 여유가 남고
|
||||
가로 한계 12.1px가 결정한다. 측점이 적어 열이 넓어지면 상한 16px까지 커진다.
|
||||
|
||||
#### 5차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **구배 행에 남아 있던 빈 셀 — 구분선 기준을 R 중심으로 이동**:
|
||||
3차에서 블록 범위를 `EVC ~ 다음 BVC`(곡선 접선점)로 잘랐는데, 그러면 곡선 길이만큼 블록 사이에 **틈**이
|
||||
생기고 양옆 블록의 테두리가 그 틈을 감싸 빈 셀처럼 보였다. 사용자 지적대로 구분선을
|
||||
**변화점(= 생성된 R의 중심, 측점 수직선)** 으로 되돌려 블록이 빈틈없이 이어지게 했다.
|
||||
표기 값(L/H/S)이 변화점 사이 기준이라는 점과도 이제 일치한다.
|
||||
- 3차의 "R 영역 제외" 요구는 *중앙종거 값을 S행에서 뺀 것*으로 이미 충족되었고,
|
||||
블록 범위까지 자를 필요는 없었다는 것이 이번 판단이다.
|
||||
- [x] **짧은 구간 처리 변경**: 글자가 안 들어가면 4차에서는 블록을 통째로 생략했는데, 그러면 그 자리가 다시
|
||||
틈으로 남아 같은 증상이 된다. 이제는 **칸은 유지하고 숫자만 비우며** 값은 툴팁으로 읽게 한다.
|
||||
- [x] **테두리 중복 제거**: 블록이 맞닿으므로 좌우 테두리를 모두 주면 구분선이 2px로 두꺼워진다.
|
||||
`border-left`만 두고 마지막 블록에만 `border-right`를 붙여 1px로 통일했다.
|
||||
|
||||
#### 6차 실화면 검증 피드백 반영 (2026-07-23)
|
||||
- [x] **가로 스크롤 시 값이 행 이름표를 뚫고 보이던 문제**: 이름표를 `z-index: 5`(최상단)로 올리고 불투명 배경과
|
||||
`white-space: nowrap`을 명시했다. 이름표 전용 글자 크기(`--b05-table-label-font`)도 분리했다.
|
||||
- [x] **그래프 시작점이 행 이름표에 가려지던 문제 — 폭 고정 + 좌측 여백 정렬**:
|
||||
- 이름표 열 폭을 **`LONG_PAD.left`(62px)와 정확히 같게** 맞췄다. 그래프 플롯 62px에서 시작하므로
|
||||
이름표가 끝나는 지점과 그래프가 시작하는 지점이 일치해 가려지는 부분이 없다.
|
||||
- 이름표 글자는 `min(값 글자, (62−10)/4) = 12px`로 제한 — "누가거리"(한글 4자) 56px ≤ 62px 확인.
|
||||
단위 표기는 이름표를 좁게 유지하려고 툴팁으로 옮겼다.
|
||||
- 측점 칸 폭을 **57px로 고정**(`STATION_COLUMN_PX` = 12px 기준 셀 55px + 간격 2px). 화면 폭에 맞춰
|
||||
늘였다 줄였다 하지 않고, 남는 가로는 스크롤로 훑는다.
|
||||
- [x] **하단 편집 버튼이 X축 제목에 가려지던 문제**: B05에 한해 `.b06-chart__axis-label`과
|
||||
`.b06-chart__station-label`을 CSS로 숨겼다(B06 렌더러는 무수정). 두 정보 모두 아래 도면 테이블의
|
||||
측점 행이 그대로 담고 있어 중복이고, 지우면 그래프 상·하단이 비어 버튼이 온전히 드러난다.
|
||||
- [x] **구간 시프트 버튼이 안 보이던 문제**: 안쪽 줄(top/bottom 19px)에서 **측점 버튼과 같은 줄**로 옮겼다.
|
||||
자리가 겹치면(구간이 짝수 개 측점을 걸쳐 중점이 측점과 일치) `avoidStations()`가 측점 버튼을
|
||||
수직선 위에 고정하고 구간 버튼만 20px 오른쪽으로 비킨다. 색(amethyst)으로 구분한다.
|
||||
원복 ↺ 버튼은 비워진 안쪽 줄(top 20px)로 이동.
|
||||
- [x] **절토고~곡선 행에 세로 구분선 추가**: 값이 없는 측점(절토고/성토고 중 한쪽, 곡선이 없는 측점)에도
|
||||
**빈 칸을 만들어** 격자가 끊기지 않게 했다. 셀 경계(이웃 측점과의 중간)에 `border-left`를 둬
|
||||
구배 행과 같은 격자를 이룬다.
|
||||
@@ -0,0 +1,55 @@
|
||||
# [B05/B06] 표준 횡단면 설정 및 자동 횡단 계획선 (2026-07-24 구현 완료 및 검증 통과)
|
||||
|
||||
### 합의사항 (검증 기준의 근거)
|
||||
- 도면 근거: `08 표준횡단도면(울진 울진 대흥 산65 외2(3공구)).bmp` 좌측 영역 판독값 채택.
|
||||
- 리핑암/발파암은 "암 구간" 설정 **공유**(`SECTION_GROUND_TYPE_PRESET`). 포장은 지반유형과 **중첩 적용**.
|
||||
- L형 측구: 패널에 일반/L형 두 세트 값 보관, 각 횡단면도 카드에서 **선택만** 한다.
|
||||
- **암 경계선은 지면선(지반선) 복사 + 오프셋** (기본 -0.5m, 점선). 계획선 복사 아님 — 2026-07-24 사용자 최종 지시. 상/하 버튼 0.1m 스텝, 측점별 개별값, 세션 보관 → 확정 시 DB 병합.
|
||||
- 포장 판단: 「임도설치 및 관리 등에 관한 규정」[별표 1-2] = `FOREST_ROAD_PROFILE_CRITERIA`. 계획선 국소 경사가 비포장 법정 상한(설계속도×지형) 초과 시 포장 제안(포장 시 상한 18%). UI에 법정 근거 표기.
|
||||
- 측구 방향: B05 solve가 측점별 상단측(등고 높은 쪽) 자동 판정 → 3D 측점 바 양끝 원형 램프(상단측 주황/반대 회색) → 클릭 변경 → 경로 확정 시 정본 병합 → B06은 값만 소비.
|
||||
- 연산 원칙: 프론트 계산·세션 보관 → 확정 시에만 백엔드/DB 저장.
|
||||
|
||||
### 표준 횡단면 기본값 (도면 판독, 2026-07-24 사용자 확정)
|
||||
| 구간 | 노폭 | 노견(좌/우) | 측구(상단/저폭/깊이) | 횡단경사 | 사면 |
|
||||
|---|---|---|---|---|---|
|
||||
| 토사 | 3.0m | 각 0.5m | 900/300/300mm | 3~5% (측구방향) | 성토 1:1.2, 절토 1:1.0 |
|
||||
| 암 (리핑/발파 공유) | 3.0m | 각 0.5m | 일반 690/300/300mm, L형 폭500/깊이100mm | 3% | 절토(암) 1:0.4 (규정 1:0.3 이상) |
|
||||
| 포장 | 3.0m | 각 0.5m | 900/300/300mm (토사 동일) | 1.5~2% + 포장층 두께 0.2m | 토사 동일 |
|
||||
|
||||
### 구현 완료 체크리스트
|
||||
|
||||
**작업 1. B05 설정값 세션 캐싱**
|
||||
- [x] 진입 시 `b05:latest:{project_id}` 세션 캐시 우선 → 미스 시 `GET /route/latest` 후 적재 (`loadLatest()`, B05_wf2_Route_UI_Page.ts)
|
||||
- [x] solve/확정 성공 시 `loadLatest(true)` 신선 갱신, 등고선 간격 변경 시 캐시 패치, 저장 실패(용량) 시 캐시 제거 폴백
|
||||
- [x] 위험 평가: 기존 복원 흐름과 동일 구조(RouteLatestResponse) 재사용 — 충돌 없음. 세션은 탭 단위(다른 탭/기기 변경 미반영, 수용)
|
||||
|
||||
**작업 2-1. B06 표준 횡단면 설정 패널** *(선행 AI 구현, 본 세션에서 연결 완료)*
|
||||
- [x] 토사/암/포장 3그룹 패널 (`B06_wf3_ProfileCross_UI_Standard_Panel.ts`), 노폭·노견·측구·경사 편집, 세션 보관 → 확정 시 `longitudinal_sections.data.options` 저장
|
||||
- [x] `config_system.py` 5-4-1 `STANDARD_CROSS_SECTION` 도면값 상수화
|
||||
|
||||
**작업 2-2. 지반유형별 자동 횡단 계획선**
|
||||
- [x] 엔진 재작성: 설계선에 횡단경사(측구 방향 단일 사면)·노견·측구 사다리꼴/L형·절성토 사면 완전 기하 포함 (`B06_wf3_ProfileCross_Engine_Design.py`)
|
||||
- [x] 리핑암/발파암: 암 경계선(**지면선 복사** -0.5m 점선) 렌더 + ▲/▼(0.1m)/↺ 버튼(B05 패턴 재활용) + 측점별 세션 보관(`b06:rockb:*`) → 확정 시 `cross_sections.data.design.rock_boundary_offset_m` 병합(`cross_patches`)
|
||||
- [x] 횡단 카드에서 일반/L형 측구 선택(암 지반만 노출, 서버 검증), 패널 편집값이 계산 요청에 동봉(요청값→DB→config 우선순위)
|
||||
|
||||
**작업 2-3. 측구 방향 자동 결정**
|
||||
- [x] `generate_sections`: 좌/우 유효 샘플 평균 표고 비교로 `stations[].uphill_side` 산출 (Sections_Core)
|
||||
- [x] 3D 측점 바 양끝 원형 램프 + 클릭 변경(`onUphillPick`) + 세션(`b05:uphill:*`) → 확정 시 `uphill_overrides` → 종단 정본 병합(`uphill_side_source: "user"`)
|
||||
- [x] B06 기본 단면유형 = 상단측 절토(`_default_section_modes`), 양성은 측구 없음, 횡단경사 방향 = 측구 방향
|
||||
|
||||
**작업 2-4. 포장 구간 자동 제안**
|
||||
- [x] solve 시 `_annotate_pavement_suggestions`: 계획선 국소 경사 > 비포장 법정 상한 → `stations[].pavement_suggested` + 근거(경사·상한) 기록
|
||||
- [x] B06 기본 포장 반영 + 포장층 박스 작도(`appendPavementOverlay`) + 포장/비포장 토글 + ⚠ 배지·법정 근거 툴팁
|
||||
|
||||
**작업 3. B06 사이드패널 정리** *(선행 AI 구현)*
|
||||
- [x] "대상 경로"·"종/횡단 생성 결과" 컨테이너 삭제, 안내 메시지 메인 영역 이동
|
||||
|
||||
### 변경 파일
|
||||
| 구분 | 파일 |
|
||||
|---|---|
|
||||
| 공통 | `config/config_system.py` (5-4-1 단일 진실 원천 재편, `SECTION_DESIGN_TEMPLATES` 삭제) |
|
||||
| B06 BE | `B06_wf3_ProfileCross_Engine_Design.py`(재작성), `_Router.py`, `_Repository.py`(`merge_cross_section_design_patch` 신설), `_Schema.py` |
|
||||
| B06 FE | `_Api_Fetch.ts`, `_UI_Cross_Design.ts`, `_UI_Cross_View.ts`, `_UI_Section_View.ts`, `_UI_Page.ts`, `_UI_Standard_Panel.ts`(신규), `_UI_Style.css` |
|
||||
| B05 BE | `_Engine_Sections_Core.py`(uphill), `_Engine_Sections.py`(포장 제안), `_Router.py`(uphill 병합), `_Schema.py` |
|
||||
| B05 FE | `_UI_Page.ts`(세션 캐시·uphill), `_UI_Markers.ts`(램프), `_Api_Fetch.ts` |
|
||||
| 로케일 | `ui_template/ui_template_locale.ts` |
|
||||
@@ -0,0 +1,28 @@
|
||||
# PLAN: B05 비정규 측점 후속 + 사이드바/테이블 개선 완료
|
||||
|
||||
## ✅ 완료 (2026-07-24 세션) — B05 비정규 측점 후속 + 사이드바/테이블 개선
|
||||
> 정적 검사 통과(`tsc --noEmit` — 기존 B07 무관 오류 1건 제외, `prettier`). 실제 앱 E2E는 미실시.
|
||||
|
||||
- [x] **① 비정규 측점 선택 시 프리즈(무한 재귀) 수정**: 3D↔그래프↔사이드바 선택 상호 갱신이
|
||||
`selectStation → onStationSelectionChange → syncIrregularSelection → selectByChainage → loadForm →
|
||||
onIrregularSelect → selectStation …` 로 무한 재귀(스택 오버플로, `B06_..._UI_Longitudinal.ts:117`).
|
||||
`selectionSyncing` 재진입 가드로 순환 차단. 선택은 양방향 정상 전파.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Page.ts`
|
||||
- [x] **② 좌측 사이드바 도움말 삭제**: "계획선이란?"·"최적경로란?" `details` 두 개 제거.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts`
|
||||
- [x] **③ 곡선 L/R 양방향 입력 + 스피너 제거**: L도 입력 가능하게 하고(L·R 연동), 어느 쪽을 고쳐도
|
||||
`R = L × (r_m/l_m)`로 환산해 기존 `onCurveRadiusChange` 파이프라인을 태움(재계산 후 마지막 입력값 반영).
|
||||
숫자 입력 상·하 토글(스피너)은 `.b05-profile-table__no-spin`으로 제거.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts`, `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- [x] **④ 비정규 측점 잔여거리 최대값 = 측점 간격**: `[0, 측점간격]`으로 클램프하고 입력창 `max`를
|
||||
현재 측점간격(옵션값)으로 실시간 동기화(`intervalMax`, `syncRemainderMax`).
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_IrregularStations.ts`
|
||||
- [x] **⑤ 비정규 측점 선택 시 테이블 값 열 오버레이**: 기존 **세로 점선 + 구조물 태그 제거**. 선택된
|
||||
비정규 측점만 규칙 열과 같은 12행(계획고·지반고·누가거리·측점 등, 계획선 샘플 선형보간) **값 열을
|
||||
테이블 위에 오버레이**. 구조물 이름은 사이드바 입력에 있으므로 표기 안 함. (그래프 파선은 유지)
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts`(`buildIrregularColumn`/`interpolateSample`,
|
||||
`selectedStationId` 옵션), `B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts`(`selectedStationId` 전달),
|
||||
`B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- [x] **⑥ 좌측 컨테이너 제목 클릭 접기/펼치기**: `.b05-route__panel-section > h3` 클릭 시 본문(`__panel-body`)만
|
||||
접힘(`is-collapsed`), 제목은 유지. 내부 `details` 등 별도 접힘 항목은 손대지 않음(위임 리스너).
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts`, `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
@@ -0,0 +1,134 @@
|
||||
# 완료 계획서: B05 비정규 측점 테스트 반영 및 후속 작업 (2026-07-24 이관)
|
||||
|
||||
## 1. B05 비정규 측점 테스트 반영 (2026-07-24)
|
||||
> 측점 1+0에 곡선, 1+10에 경사 변경 테스트 중 발견. 정적 검사 통과(`tsc`/`prettier`).
|
||||
|
||||
- [x] **구배 블록 값 안 보임 → 좁으면 90° 회전 표기**: 변화점 추가로 구간이 좁아지면 구배(L/H/S) 값이
|
||||
가로로 안 들어가 비었음. 이제 값을 항상 표기하되, 가로로 안 맞으면 **90도 회전**해 블록 폭·행 높이에
|
||||
맞춰 글자를 줄여 넣음(작아도 표기 우선). → `_UI_Profile_Table.ts` `buildSegmentRows`(값 span+`is-rotated`,
|
||||
`rowHeight` 전달), `_UI_Style.css`.
|
||||
- [x] **비정규 측점 값 열에 구배/곡선 표기**: 선택 시 값 열 오버레이의 구배(L/H/S)·곡선(L/R) 칸을 채움 —
|
||||
구간 **안**이면 그 구간 값을 그대로(중복 허용), **변화점**이라 좌우로 갈리면 `좌 / 우`로 절반 나눠 가로 표기.
|
||||
곡선도 동일(해당 chainage 곡선의 L/R). → `_UI_Profile_Table.ts` `buildIrregularColumn`, `_UI_Style.css`
|
||||
(셀 `overflow:hidden`).
|
||||
|
||||
---
|
||||
|
||||
## 2. B05 후속 — 편집값 세션 유지 · 복원 · Y축 sticky · 비정규 측점 편집
|
||||
> 2026-07-24 기획→승인→구현. **정적 검사 통과**(`tsc --noEmit` 기존 B07 무관 오류 제외, `prettier`). 실제 앱 E2E 미실시.
|
||||
> **사용자 결정**: 작업1(+작업2 복원) 포함 · 작업3(Y축 sticky, 저위험) 포함 · 작업4·5·6 추가. 아래 체크리스트 모두 구현됨.
|
||||
>
|
||||
> **구현 요약(변경 파일)**:
|
||||
> - 작업1(편집 이월): `_UI_Profile_Panel.ts` `render()` — 경로 변경 시 `store.edited()`면 현재 편집을 새 스토어로
|
||||
> 이월(`store.replace(carried)`)해 새 base에 재적용. best-effort(미매칭·범위 밖 드롭).
|
||||
> - 작업2a(복귀 복원): `_UI_Page.ts` `restoreSections` — 클라이언트 목록이 빈 경우만 `detail.longitudinal.stations`의
|
||||
> `kind==="irregular"`(+`structure`)로 사이드바 복원. `_UI_IrregularStations.ts` `setStations()` 신설.
|
||||
> `B06_..._Api_Fetch.ts` `SectionStation.structure?` 추가.
|
||||
> - 작업3(Y축 sticky): `B06_..._UI_Longitudinal.ts` `onYAxis` 콜백으로 눈금 공유. `_UI_Profile_Panel.ts`
|
||||
> `buildStickyYAxis`(0크기 sticky 앵커+절대배치 축, 불투명 배경으로 값 누출 차단). `_UI_Style.css`.
|
||||
> - 작업4(z-index): `_UI_Style.css` `.b05-profile-table__irregular-col` z-index 6→4(이름표 5 아래).
|
||||
> - 작업5(비정규 편집 버튼): `_UI_Profile_Edit.ts` `createEditOverlay`에 `irregularStations` 추가 → ▲/▼(+원복)
|
||||
> 렌더(규칙 측점과 동일 `adjustStation` 파이프라인). `_UI_Profile_Panel.ts`에서 전달.
|
||||
> - 작업6(값 열 입력): `_UI_Profile_Table.ts` `buildIrregularColumn` 계획고 셀을 `no-spin` 입력으로 →
|
||||
> `onAdjustStation(chainage, target−plan)`. `_UI_Style.css` 입력 셀 pointer-events 복구.
|
||||
|
||||
### 상세 작업 체크리스트
|
||||
|
||||
#### 작업 1 — 재탐색 시 "사용자 조작값" 유지 (프론트 한정, 백엔드는 확정 때만)
|
||||
- **문제**: 브라우저 새로고침은 결과가 남지만, **경로 변경·재탐색(재계산)** 시 사용자가 조작한
|
||||
계획선 편집(측점 계획고 델타·종단곡선 반경)이 **리셋**됨. 사용자는 결과가 아니라 **조작값**이
|
||||
이어져, 경로가 미세하게 바뀌어도 대부분 재계산되며 편집이 유지되길 원함. 백엔드로는 안 보냄
|
||||
(확정 시에만 전송).
|
||||
- **원인(조사)**: 프로필 편집 스토어가 `routeId`로 키됨. `B05_wf2_Route_UI_Profile_Panel.ts` `render()`의
|
||||
`if (nextRouteId !== routeId || !store.dirty())` 분기에서, 재탐색으로 새 `routeId`가 오면 스토어를
|
||||
`stored?.edits`(신규 경로엔 빈 값)로 **재생성** → 편집 소실. (비정규 측점은 Page 클라이언트 상태라
|
||||
재탐색 후에도 유지되므로 이번 대상 아님.)
|
||||
- **핵심 근거**: 편집은 chainage 키(`station_offsets[chainage]`, `curve_radii[chainage]`)라, `buildAlignment(
|
||||
base, edits)`가 **새 경로의 base에 그대로 재적용** 가능. 즉 스토어만 안 비우면 이어짐.
|
||||
- **구현 체크리스트**:
|
||||
- [x] `render()`에서 **경로 변경 시 현재 편집을 새 스토어로 이월**: 재탐색으로 `routeId`가 바뀔 때
|
||||
`store.edited()`면 `store.edits()`(현재 in-memory 편집)를 새 스토어의 초기값으로 넘기고, 없을 때만
|
||||
`stored?.edits`(백엔드 저장분/신규 빈 값) 사용.
|
||||
- [x] 이월된 편집을 새 `routeId`의 세션 초안(sessionStorage)에도 기록해 **재탐색 후 새로고침에도 유지**.
|
||||
- [x] 새 alignment에 없는 chainage의 편집 처리 확인: `buildAlignment`가 미매칭 키를 무시하는지 점검,
|
||||
필요 시 매칭 chainage로만 프루닝(경로 대폭 변경 시 오적용 방지).
|
||||
- [x] 비정규 측점은 현행 유지 확인(재탐색 후 `setIrregularStations` 재적용) — 회귀만 점검.
|
||||
- **문제(한계) — 사용자 질문 답변**: 편집을 이어붙이는 방식은 **chainage 키 기준 best-effort**라 완벽하진 않다.
|
||||
1. **측점 계획고 델타(`station_offsets`)**: 규칙 격자(20·40·60…)에 걸리므로 노선이 조금 바뀌어도 격자
|
||||
chainage는 대부분 그대로 → **잘 유지됨**. 단 노선이 **짧아지면** 범위 밖(끝단 너머) 편집은 드롭
|
||||
(`resolvePvi`가 노선 범위 밖 offset을 버림).
|
||||
2. **종단곡선 반경(`curve_radii`)**: 키가 **자동 변화점(PVI) chainage**인데, 재탐색 시 지반 형상이 바뀌면
|
||||
자동 변화점 위치가 달라질 수 있음 → 옛 변화점 chainage에 맞는 변화점이 새 선형에 없으면 그 반경 편집은
|
||||
**적용 안 됨(무시)**. 즉 계획고 편집보다 반경 편집이 더 취약.
|
||||
3. **의미 드리프트**: 노선이 크게 달라지면 같은 offset이 새 지반에선 어색한 결과를 낼 수 있음(그래도 값은 유지).
|
||||
→ **결론**: “미세 변경 시 이어짐”은 확실히 충족. **대폭 변경 시엔 일부(특히 반경) 편집이 드롭**될 수 있고,
|
||||
이는 chainage 키 방식의 근본 한계라 완전 해결하려면 편집의 의미(어느 변화점/구간인지) 추적이 필요(과대).
|
||||
이번엔 best-effort로 진행하고, 드롭된 편집은 조용히 버린다(사용자 의도에 부합).
|
||||
|
||||
#### 작업 2 — [검토] 확정 시 저장 위치 정리 + 다음 페이지 이동/복귀 흐름
|
||||
- **성격**: 코드 변경 전 **현황 검토·결정**. 조사 결과:
|
||||
- **확정 시 저장(현행)**: ① `profilePanel.save()`→PUT `/route/profile-alignment`→편집을 `longitudinal.json`에
|
||||
반영 + `grade_summary`를 DB(`longitudinal_sections.data`). ② `confirmRoute(...)`→비정규 측점 횡단
|
||||
파일 생성 + `longitudinal.json` stations 병합(파일), 경로 상태 `CONFIRMED`(DB). ③ 편집 세션 초안은
|
||||
`markSaved()`로 삭제.
|
||||
- **편집 중 프론트 상태**: 계획선 편집 = sessionStorage 초안(routeId) + in-memory. 비정규 측점 =
|
||||
in-memory(Page+패널)뿐(확정 전 미영속).
|
||||
- **복귀 시(현행)**: `fetchLatestRoute`+`restoreSections`로 편집(=longitudinal.json)·백엔드 비정규 측점은
|
||||
그래프에 복원됨. **그러나 사이드바 비정규 목록은 미복원**(클라이언트 상태 소실 → 편집/삭제 불가).
|
||||
- **검토 결과 / 계획**:
|
||||
- [x] (a) **포함(사용자 승인)** — 복귀 시 **사이드바 비정규 목록을 백엔드에서 복원**: `detail.longitudinal.
|
||||
stations` 중 `kind==="irregular"`(+`structure`)를 읽어 사이드바 리스트·Page `irregularStations` 재구성.
|
||||
구현 체크리스트:
|
||||
- [x] `restoreSections`(또는 `render` 후)에서 detail의 irregular 측점을 추출 → 사이드바 목록 API로 주입
|
||||
(신규 `panel.irregularStations.setStations(...)` 또는 유사) → `applyIrregularStations`로 그래프·3D 반영.
|
||||
- [x] 측점번호/잔여거리 역산: `chainage → (측점번호, 잔여거리)`는 측점간격으로 환산.
|
||||
- [ ] (b) [백로그 유지] 확정 시 비정규 측점의 DB `cross_sections` 행 미기입(B07/B08용) — 표시(B06)는 파일
|
||||
기반이라 정상. 이번 범위 제외, 후속 판단.
|
||||
- [ ] (c) 문서화: 확정 데이터 흐름표(무엇이 DB/파일/프론트 어디에)를 완료 후 검증보고서로 남김.
|
||||
|
||||
#### 작업 3 — [가능성 검토 포함] 하단 패널 그래프 Y축 스크롤 고정(sticky)
|
||||
- **목표**: 가로 스크롤 시 그래프 좌측 **Y축(표고 눈금·축선) 고정**. 단, 예전 테이블 좌측 여백처럼
|
||||
**스크롤되는 값이 새어 보이면 안 됨**(불투명 배경으로 가림).
|
||||
- **조사/설계**: 그래프는 스크롤 컨테이너 안 **단일 SVG**라 SVG 자체를 sticky 불가. → `.b05-profile__chart`
|
||||
안에 **별도 sticky 오버레이 div**(`position: sticky; left: 0`, 폭=`LONG_PAD.left`, 불투명 배경, 상위 z-index)를
|
||||
두고 표고 눈금 라벨+축선을 그린다. 스크롤 SVG의 좌측 눈금은 이 오버레이가 덮어 가림.
|
||||
- **가능성 판정(사용자 조건: 버튼 중첩·성능 문제면 하지 말 것)**:
|
||||
- 편집 버튼은 상/하단(중앙 측점 위)에 있고 Y축은 **좌측(0~62px)**, 0측점 버튼은 ≈90px라 **중첩 거의 없음**.
|
||||
- 성능: 정적 오버레이라 **부담 없음**.
|
||||
- **주요 난점**: 오버레이 눈금이 SVG 격자선과 **같은 Y-스케일**이어야 함 → `createLongitudinalProfile`의
|
||||
Y-스케일/눈금 계산을 오버레이가 공유하도록 노출/전달 필요(중복 렌더 소지).
|
||||
- **판정: 구현 가능·저위험 → 포함(사용자 승인).** 다만 Y-스케일 공유(중복 렌더) 정리가 필요.
|
||||
- **구현 체크리스트**:
|
||||
- [x] `createLongitudinalProfile`가 Y-스케일(또는 눈금 배열)을 반환하거나, 패널이 동일 스케일로 눈금 계산.
|
||||
- [x] `.b05-profile__chart`에 sticky Y축 오버레이(불투명 배경·축선·표고 라벨) 추가, z-index로 값 누출 차단.
|
||||
- [x] 리사이즈·폭맞춤 시 눈금 위치 재동기화 확인.
|
||||
|
||||
#### 작업 4 — [버그] 비정규 측점 값 열이 행 제목(sticky 이름표)을 가림 (사용자 3번)
|
||||
- **문제**: 지난 세션 ⑤에서 만든 비정규 측점 **값 열 오버레이**의 z-index(6)가 sticky 행 이름표(z-index 5)보다
|
||||
높아, 가로 스크롤로 값 열이 이름표 열 위로 오면 **행 제목이 가려짐**.
|
||||
- **계획**:
|
||||
- [x] `.b05-profile-table__irregular-col` z-index를 이름표(5) **아래**(예: 4)로 낮춘다. 규칙 값 셀(0)·곡선(2)
|
||||
보다는 위라 값 열은 정상 표시되고, 좌측 이름표만 항상 위에 남는다.
|
||||
- [x] 스크롤로 값 열이 이름표 영역까지 왔을 때 이름표가 안 가려지는지 확인.
|
||||
|
||||
#### 작업 5 — [기능] 비정규 측점도 상/하·시프트 상/하 편집 버튼으로 제어 (사용자 4번)
|
||||
- **근거(조사)**: `adjustStation(base, edits, chainageM, delta)` + `resolvePvi`는 **노선 범위 내 임의 chainage**를
|
||||
`station_offset`으로 받아 **변화점(PVI)으로 승격**시킨다 → 비정규 측점의 계획고도 규칙 측점과 똑같이 버튼으로
|
||||
조정 가능.
|
||||
- **계획**:
|
||||
- [x] `createEditOverlay`에 **비정규 측점 목록을 전달**하고, 각 비정규 측점 x에 측점 ▲/▼(+원복 ↺) 버튼을
|
||||
렌더 → `adjustStation(base, edits, irregular.chainage_m, ±step)` 호출(규칙 측점과 동일 파이프라인).
|
||||
- [x] 시프트(구간 ⇧/⇩)는 현행처럼 구간 단위 유지(비정규 측점은 구간 내부에 놓임) — 별도 버튼 불필요, 다만
|
||||
비정규 측점이 변화점이 되면 그 지점에서 구간이 나뉘는 것을 확인.
|
||||
- [x] 편집 오버레이 버튼과 값 열(작업4)·측점 라벨이 겹치지 않게 배치 점검.
|
||||
|
||||
#### 작업 6 — [제안] 테이블에서 비정규 측점 값 직접 입력 (사용자 5번, 작업5 연계)
|
||||
- **목표**: 버튼(작업5)과 별개로, 테이블 레이아웃을 **깨지 않는 범위**에서 비정규 측점의 계획고를 **직접 입력**.
|
||||
- **제안(권장안)**: 작업4의 **값 열 오버레이 안**(테이블 실제 행이 아니라 오버레이라 레이아웃 영향 없음)의
|
||||
**계획고 셀을 숫자 입력**으로 바꾼다. 입력 시 `현재 계획고 → 목표 계획고` 차이를 delta로 계산해
|
||||
`adjustStation(base, edits, chainage, delta)` 호출 → 규칙 측점과 동일하게 station_offset으로 반영. 스피너는
|
||||
제거(no-spin). (곡선이 생기는 변화점이면 곡선 R 셀도 입력 가능하게 확장 여지.)
|
||||
- 장점: 오버레이는 테이블 위에 떠 있어 **실제 12행 셀 구조를 안 건드림** → 레이아웃 안전. 값 입력·버튼이
|
||||
같은 `station_offset` 파이프라인이라 일관.
|
||||
- [x] 선택된 비정규 측점의 값 열 계획고 셀 → `<input type=number no-spin>`, change 시 adjustStation.
|
||||
- [x] 입력 폭/정렬이 오버레이 yr 폭 안에 들어오는지(레이아웃 불변) 확인.
|
||||
@@ -0,0 +1,30 @@
|
||||
# 완료 계획서: B05 레이아웃 정비 및 후속 과제 (2026-07-24 이관)
|
||||
|
||||
## 1. B05 사이드바 제목/레이아웃 정비 (2026-07-24)
|
||||
> 기획+코딩 동시. 라벨 변경 + '공사 시작점' 컨테이너 2열×2행.
|
||||
|
||||
- [x] 워크플로 상단 제목 `"노선 설계"` → `"종단 설계"` (`_UI_Page.ts:517`).
|
||||
- [x] `측점·횡단 옵션` → `측점 및 샘플링 설정` (locale `B05_Route_Group_SectionOptions`).
|
||||
- [x] `계획선(시공계획고) 설계` → `종단 설계 기준` (`_UI_Panel.ts`).
|
||||
- [x] `이어 공사 시작 기준` → `공사 시작점` (`_UI_Panel.ts`).
|
||||
- [x] `공사 시작점` 컨테이너 내부를 **2열×2행**(1행 항목명, 2행 값 입력 / 1열 시작 측점, 2열 시작 누가거리)로. 라벨을 `시작 측점`·`시작 누가거리`로, 2열 그리드로 배치. (`_UI_Panel.ts` + `_UI_Style.css`)
|
||||
- [x] `비정규 측점 (구조물)` → `구조물 배치` (`_UI_IrregularStations.ts`).
|
||||
|
||||
---
|
||||
|
||||
## 2. B05 후속 2 (2026-07-24) — 캐럿/하이라이트 통일/시작측점
|
||||
> 기획+코딩 동시.
|
||||
|
||||
- [x] **1. 접기 캐럿 크게**: 공용 `.ui-collapsible__title::after` 화살표를 레이아웃 허용 범위 내 최대 크기로. → `ui_template_theme.css`.
|
||||
- [x] **2. 선택 하이라이트를 오버레이 방식으로 통일**: 규칙 측점 선택도 비정규 측점처럼 **값 열 오버레이**로 표기(셀 배경색 방식 폐기). `buildIrregularColumn`을 chainage+centerX 받는 공용 `buildSelectedColumn`으로 일반화해 규칙/비정규 공용. **종점은 셀이 우측 이동됐을 수 있으니** 규칙 측점은 `centers[index]`(이동 반영), 비정규는 `x(chainage)` 사용. → `B05_wf2_Route_UI_Profile_Table.ts`, `_UI_Style.css`.
|
||||
- [x] **3. 공사 시작 측점/누가거리 오프셋 적용**: 시작 측점(측점 표현 AAA+BB.B 중 **AAA 정수만**) + 누가거리 시작점. 기본값 측점 0·누가거리 0. 이 오프셋으로 B05 테이블·그래프의 **측점 라벨(+시작측점)·누가거리(+시작누가거리)** 표시를 이동(내부 chainage는 0기준 유지, 표시만 변환). → `B05_wf2_Route_UI_Panel.ts`(신규 컨테이너·getter), `_UI_Page.ts`(오프셋 전달), `_UI_Profile_Panel.ts`·`_UI_Profile_Table.ts`(표시 변환), `B06_..._UI_Longitudinal.ts`(그래프 측점 라벨 오프셋).
|
||||
|
||||
---
|
||||
|
||||
## 3. B05/B06/B07 후속 (2026-07-24)
|
||||
> 이번 턴: 기획+코딩 동시. 정적 검사(`tsc`/`py_compile`/`prettier`).
|
||||
|
||||
- [x] **A. 규칙 측점도 선택 시 테이블 하이라이트**: 규칙 측점을 선택하면 그 측점의 테이블 열(값 셀들)에 같은 하이라이트를 준다. → `B05_wf2_Route_UI_Profile_Table.ts`(선택된 규칙 측점 x에 하이라이트 스트립), `_UI_Style.css`.
|
||||
- [x] **B. (#2) 비정규 측점 구조물 → B06 횡단도 카드에 표시**: 확정 시 백엔드가 이미 `structure`를 횡단 데이터에 실음. B06 카드 헤더의 **kind 라벨(일반측점/BP/EP) 좌측**에 구조물 텍스트를 표기. → `B06_wf3_ProfileCross_UI_Cross_View.ts`, `_UI_Style.css`.
|
||||
- [x] **C. (#3) 충돌 시 규칙 측점 우선 + 구조물 유지**: 백엔드 엔진에서 비정규 chainage가 규칙 격자와 겹치면 kind는 **regular 우선**(현재는 irregular)으로 하되 `structure`는 그대로 부착. 비격자 비정규만 kind="irregular". B06 표시는 B가 처리(구조물은 kind 라벨 좌측). → `B05_wf2_Route_Engine_Sections_Core.py`(원 규칙 chainage 집합으로 충돌 판정).
|
||||
- [x] **D. (#4) 컨테이너 접기 트리거를 제목 전체로 확대 + B07 재활용**: 제목 행 전체가 클릭 영역이 되게 하고, 공용 collapsible 유틸/CSS로 만들어 B05·B07에 적용. 템플릿 상이 시 B05가 우선(제목+본문 접힘, 캐럿 표시). B07은 `.b07-drawing-group`(h3+버튼들)에 적용. → 신규 공용 `ui_template/ui_template_collapsible.ts` + 공용 CSS(`ui_template_theme.css`), `B05_wf2_Route_UI_Panel.ts`·`_UI_Style.css`(기존 개별 구현 대체), `B07_..._UI_Page.ts`·`_UI_Style.css`.
|
||||
@@ -0,0 +1,36 @@
|
||||
# 완료 계획서: B05 노선 설계 좌측 패널 컨테이너 재배치 및 산출결과 삭제 (2026-07-24 이관)
|
||||
|
||||
## 1. B05 노선 설계 좌측 패널 컨테이너 재배치·접힘 기본값·산출결과 삭제
|
||||
|
||||
### 요구사항
|
||||
- B05 좌측 사이드 패널 컨테이너의 배치 순서 변경 및 접힘 기본값 지정
|
||||
- "경로 설계 산출 결과" 컨테이너 삭제 (백엔드 계산 로직은 조건부)
|
||||
|
||||
### 대상 파일 (모두 확인 완료)
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts` — 컨테이너 정의·조립 순서(주 작업 파일)
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_Page.ts` — 산출결과(metrics/stale) 소비부 정리
|
||||
- (참조) `ui_template/ui_template_collapsible.ts` — `is-collapsed` 클래스로 접힘 제어
|
||||
|
||||
### 변경 후 목표 순서 / 접힘 기본값
|
||||
| 순서 | 컨테이너 | 변수명 | 접힘 기본값 |
|
||||
|---|---|---|---|
|
||||
| 1 | 종단 설계 기준 | `gradeLine` | 펼침 |
|
||||
| 2 | 등고선 간격 | `contour` | 접힘 |
|
||||
| 3 | 측정 및 샘플링 설정 | `sectionOptions` | 접힘 |
|
||||
| 4 | 공사 시작점 | `startBasis` | 펼침 |
|
||||
| 5 | 구조물 배치 | `irregular` | 펼침 |
|
||||
| 6 | 포인트 팔레트 | `palette` | 접힘 |
|
||||
| 7 | 임도 기준·옵션 | `conditions` | 접힘 |
|
||||
| — | 경로 설계 산출 결과 | `result` | **삭제** |
|
||||
|
||||
- `selected`(선택 포인트 상세 설정)는 포인트 팔레트(6번) 바로 뒤에 배치 유지.
|
||||
- `actionRow`(계산·확정 버튼)는 패널 최하단 유지.
|
||||
|
||||
### 구현 체크리스트
|
||||
- [x] 접힘 기본값: `contour`, `sectionOptions`, `palette`, `conditions` 4개 컨테이너 생성 시 root에 `is-collapsed` 클래스 추가
|
||||
- [x] `root.append(...)` 순서를 목표 순서로 변경: `gradeLine → contour → sectionOptions → startBasis → irregular → palette → selected → conditions → actionRow`
|
||||
- [x] `result` 컨테이너 제거: 섹션 생성부와 `stale`·`metrics` 엘리먼트 삭제, `root.append`에서 `result.root` 제거
|
||||
- [x] `renderMetrics()` 메서드 삭제
|
||||
- [x] `setStale()`에서 `stale.hidden` 조작만 제거하고 **`confirmButton.disabled` 제어는 유지**
|
||||
- [x] `Page.ts`: `panel.renderMetrics(metrics)` 호출 및 metrics 폴백 객체 제거, `panel.setStale(...)` 호출은 확정버튼 제어 목적으로 유지
|
||||
- [x] TS 타입체크 통과, `prettier` 적용
|
||||
@@ -0,0 +1,98 @@
|
||||
# PLAN: B05 종단면도 테이블·그래프 정렬 개편 및 비정규 측점 구현 완료
|
||||
|
||||
## ✅ 완료 (2026-07-24 세션) — B05 종단면도 테이블·그래프 정렬 개편
|
||||
|
||||
측점 간격을 브라우저 폭과 무관한 **고정값**으로 바꾸고, 0측점을 이름표 열 밖으로 밀어
|
||||
그래프·테이블 시작점을 맞췄다. (검증 전 · 검증보고서 작성 대상)
|
||||
|
||||
- [x] **① 스크롤 시 좌측 여백 누출 차단**: `.b05-route-profile__body`의 `padding-left`를 0으로
|
||||
낮춰(우측 15px만 유지) sticky 이름표 열이 못 덮던 좌측 틈을 없앴다. 이제 가로 스크롤에도
|
||||
셀 값이 이름표 왼쪽으로 새지 않는다.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- [x] **② 그래프 시작점 우측 이동(0측점 정렬)**: 데이터 매핑에 좌측 반 칸 오프셋
|
||||
(`PROFILE_ORIGIN_OFFSET_PX = STATION_SPACING_PX / 2`)을 주어, 0측점 셀 전체가 이름표
|
||||
(labelWidth = `LONG_PAD.left`) 오른쪽으로 나오게 했다. 그래프도 새 `originOffsetPx` 파라미터로
|
||||
같은 오프셋을 받아 X축을 맞춘다.
|
||||
→ `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Longitudinal.ts` (파라미터 추가, 기본 0 → B06 무영향),
|
||||
`B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts`
|
||||
- [x] **③ 측점 간격 기본값(기준×1.5) + 폭맞춤 병행**: `computeProfileLayout()`로 노선을
|
||||
`STATION_SPACING_PX × 1.5`(≈85px) 간격의 **최소 폭**으로 펼치되, 브라우저가 더 넓으면 그만큼
|
||||
폭맞춤으로 늘린다(좁으면 스크롤). 매핑은 `x = LONG_PAD.left + halfCell + c·pxPerMeter`, `pxPerMeter =
|
||||
(width − 프레임여백) / (maxChainage + 측점간격)`으로 좌우 반 칸 여백과 셀 폭(`interval·pxPerMeter − gap`)이
|
||||
실제 간격에 항상 맞물린다. 셀 폭·오프셋은 폭맞춤 시 함께 늘어난다.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Profile_Panel.ts` (`fixedProfileWidth`→`computeProfileLayout`,
|
||||
`PROFILE_SPACING_MULTIPLIER = 1.5`; `stationCellWidth`/`tableMinimumWidth` 제거)
|
||||
- ※ 최초 계획(측점 간격 완전 고정·무스트레치)에서 사용자 요청으로 "1.5배 최소 + 폭맞춤"으로 변경.
|
||||
- [x] **⑤ 그래프 X축 측점 이름 복원**: 축 제목 삭제 시 함께 사라졌던 측점 이름(세로선 항목)을
|
||||
다시 표시. 축 제목(`b06-chart__axis-label`)만 숨기고 측점 라벨(`b06-chart__station-label`)은
|
||||
`transform: translateY(-9px)`로 살짝 올려 하단 편집 버튼(`is-down`) 밴드와 분리했다.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- [x] **⑥ 사이드바 "계획선 설계" 슬림화**: 자동 산출이 이미 처리하는 두 필드를 사이드바에서 제거.
|
||||
- **주 진행방향** 제거: 서버 `main_direction="auto"`가 지반 형상에서 역기울기 방향을 자동 판정하고,
|
||||
노선 진행은 항상 좌→우이므로 수동 select는 불필요. (백엔드 기본값 `auto` 유지)
|
||||
- **균형 구역 길이** 제거: 절성토 균형 분할용 solver 파라미터인데, 이제 그래프+버튼 수동 편집으로
|
||||
제어하므로 노출 가치 낮음. (백엔드 기본값 = criteria 기본/전체 1구역 유지)
|
||||
- **지형 구분**은 법정 기준값 산정에 필요하므로 유지. 요청 payload에서 두 키가 빠져 Pydantic 기본값 적용.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts`(폼/refs/getValues/restore·`RoutePanelValues` 필드 제거),
|
||||
`B05_wf2_Route/B05_wf2_Route_UI_Page.ts`(restore 매핑·요청 payload 제거)
|
||||
- [x] **⑦ 테이블 종점 열 우측 이동**: 종점(마지막 측점)이 off-grid라 앞 측점과 겹칠 때, 측점 값·곡선
|
||||
셀만 오른쪽으로 밀어 값이 읽히게 함(캔버스 오른쪽 여백 안에서 클램프). 종점이라 수직선에서
|
||||
벗어나도 인지에 무리 없음. 구배 블록·그래프 세로선은 그대로 둔다.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Profile_Table.ts` (`stationCellCenters`, 셀 중심 배열을 값·곡선 행 공용)
|
||||
- [x] **⑧ 하단 패널 스크롤바 전역 통일**: 하단 종단면도 패널만 12px 두꺼운 막대로 덮어쓰던 것을 제거,
|
||||
좌측 사이드바와 같은 전역 pill 스크롤바(테마 `*::-webkit-scrollbar`, 8px)를 상속.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- [x] **⑨ 비정규 측점 사이드바 컨테이너(초안 UI)**: 좌측 사이드바에 "비정규 측점 (구조물)" 섹션 추가.
|
||||
지금은 **자유 텍스트 textarea**만 받는다(측점 X+XX 위치 + 구조물, 한 줄에 하나). 재탐색 트리거에서
|
||||
제외해 텍스트 수정이 "재탐색 필요"를 띄우지 않는다. 값은 `panel.irregularStationsText()`로 노출하되
|
||||
**백엔드로 보내지 않음**(형식·옵션 미확정). 추후 구조물 선택+값 입력 및 백엔드 연동으로 발전.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Panel.ts`, `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
- **미결(다음 단계)**: 텍스트 파싱 규칙, 지속성(reload 보존/DB 저장), 요청 payload·스키마·엔진 연동,
|
||||
구조물 형식 select 정의. Backlog "비기준 측점 처리"와 ④ 렌더(주석형)로 이어짐.
|
||||
- [x] **⑩ 그래프:테이블 세로 비율 4:6 + 테이블 글자 여백 축소**: `CHART_HEIGHT_RATIO` 0.3→0.4(그래프 40 :
|
||||
테이블 60), 테이블 폰트 비율 `FONT_PER_ROW_HEIGHT` 0.52→0.64·`CELL_PADDING_PX` 4→2로 상하좌우 여백 축소.
|
||||
→ `B05_wf2_Route_UI_Profile_Panel.ts`, `B05_wf2_Route_UI_Profile_Table.ts`
|
||||
- [x] **⑪ 그래프 편집 버튼 상시 표시·밝은 색**: 상/하·시프트 상/하 버튼을 hover 노출→상시 표시(opacity 0.9),
|
||||
글자색을 밝게(측점=`--color-text`, 구간=밝은 자수정). 크기(18×15)는 유지.
|
||||
→ `B05_wf2_Route/B05_wf2_Route_UI_Style.css`
|
||||
|
||||
---
|
||||
|
||||
## 🚧 비정규 측점(구조물 측점) — Phase 1~4 구현 완료(백엔드 E2E 테스트 대기)
|
||||
|
||||
**결정(2026-07-24)**: 착수 범위 = **Phase 1~3 프론트 먼저**, 입력 형식 = **측점번호+잔여거리 2칸 분리**.
|
||||
|
||||
- [x] **Phase 1 — 사이드바 구조화 입력**: 신규 모듈 `B05_wf2_Route_UI_IrregularStations.ts`.
|
||||
측점번호+잔여거리(→`chainage = 측점×간격 + 잔여`) + 구조물(자유 텍스트) + [추가]. 목록 표시, 항목 선택 시
|
||||
폼 로드 → [수정]/[삭제]/[리셋]. 클라이언트 store, 백엔드 미전송. 패널이 재탐색 트리거에서 제외.
|
||||
→ `_UI_IrregularStations.ts`(신규), `_UI_Panel.ts`(textarea→모듈 교체, `onIrregularChange/Select` 콜백,
|
||||
`panel.irregularStations` 노출), `_UI_Style.css`
|
||||
- [x] **Phase 2 — 그래프·3D 프리뷰**: `SectionStation.kind`에 `"irregular"` 추가. Page가 규칙 측점 좌표 사이
|
||||
chainage **선형보간**으로 3D 마커 위치 근사(`interpolateIrregularStations`) → `renderStationLines`에 병합.
|
||||
그래프는 `longitudinal.stations`에 주입해 파선 세로선+라벨(측점 이름은 chainage로 자동 산출). 그래프·3D에서
|
||||
비정규 측점을 고르면 사이드바 폼에 로드(`syncIrregularSelection` ↔ `selectByChainage`).
|
||||
→ `B06_..._Api_Fetch.ts`(kind union), `_UI_Page.ts`, `_UI_Profile_Panel.ts`(`setIrregularStations`+그래프 병합),
|
||||
`B06_..._UI_Longitudinal.ts`(기존 렌더 재사용), `_UI_Style.css`(`--irregular` 파선)
|
||||
- [x] **Phase 3 — 테이블 주석형(④ 반영)**: 규칙 격자·외곽선은 그대로 두고, 비정규 측점을 파선 세로선 +
|
||||
라벨(측점)+구조물 태그로 **주석층**으로 얹음. 가까운 라벨은 상·하 2슬롯 스태거로 겹침 회피.
|
||||
→ `_UI_Profile_Table.ts`(`buildIrregularAnnotations`, `irregularStations` 옵션), `_UI_Style.css`
|
||||
- [x] **Phase 4 — 경로 확정 시 횡단 생성(파일 기반, 비치명적)**: 결정(2026-07-24) = 프리뷰 작성은 그대로,
|
||||
**[경로 확정]** 시 비정규 측점의 횡단까지 생성해 다음 페이지(B06)로 잇는다.
|
||||
- **엔진**: `SectionGenerationOptions.extra_stations` 추가 → `generate_sections`가 비정규 chainage를
|
||||
격자 측점 배열에 병합해 동일 방식으로 횡단 샘플링(kind="irregular", `structure` 부착). 신규
|
||||
`generate_irregular_sections()`는 비정규 측점만 골라 `cross_*.json`을 쓰고 측점 dict를 반환.
|
||||
- **통합점(안전 설계)**: B06 상세는 종단 **파일**의 stations를 읽고 그에 맞는 `cross_*.json`만 glob으로
|
||||
노출한다 → **DB 미기입, 파일 병합만으로** 표시된다. 계획선·규칙 측점·표고 샘플·편집은 손대지 않아
|
||||
확정 시 프로파일 편집이 유실되지 않는다. 파일 기반이라 **재확정해도 중복 없음**(기존 비정규 교체).
|
||||
- **확정 흐름**: 프론트가 확정 시 지표 샘플러 입력(filter/method/smooth/model)+비정규 목록을 전송
|
||||
(`RouteConfirmRequest`). 라우터는 `_append_irregular_cross_sections`를 **비치명적**으로 호출 —
|
||||
실패해도 경로 확정(다음 단계 진행)은 그대로. 프로파일 편집 저장(`profilePanel.save`) 뒤에 실행.
|
||||
- → `_Engine_Sections_Core.py`(extra_stations), `_Engine_Sections.py`(`generate_irregular_sections`),
|
||||
`_Schema.py`(`RouteConfirmRequest`/`IrregularStationInput`), `_Router.py`(confirm 본문+비치명 호출+헬퍼),
|
||||
`_Api_Fetch.ts`(confirm 본문), `_UI_Page.ts`(confirm 시 전송)
|
||||
- **⚠ 검증 필요(미실행)**: DB·지표 데이터·파이프라인을 이 환경에서 돌릴 수 없어 **정적 검사만 통과**
|
||||
(py_compile·tsc). 실제 프로젝트로 확정→B06에서 비정규 횡단 노출 여부 **엔드투엔드 테스트 필요**.
|
||||
- **남은 한계**: (a) **재탐색(solve)** 시 종횡단이 전량 재생성되어 비정규 측점이 사라짐 — 저장된
|
||||
`extra_stations`(옵션 단일 소스)로 자동 재포함하는 연결은 후속. (b) reload 시 사이드바 목록 복원 미구현
|
||||
(측점은 저장돼도 폼 목록은 클라이언트 상태). (c) **B07 CAD·B08 수량**용 DB `cross_sections` 행은 미기입
|
||||
(B06 표시는 파일 기반이라 정상). (d) 구조물은 자유 텍스트(형식 select 미정).
|
||||
@@ -0,0 +1,338 @@
|
||||
# 유토곡선 분석 및 웹앱 계산 명세 초안
|
||||
|
||||
## 1. 문서 목적
|
||||
|
||||
이 문서는 `07.유토곡선.pdf`를 분석한 결과를 보존하고, 향후 Aislo 웹앱에 유토곡선(Mass Haul Diagram) 계산 및 표시 기능을 구현할 때 참고할 기초 명세를 정리한 것이다.
|
||||
|
||||
이 PDF는 원시 토공량 자료가 아니라, 노선에서 발생한 절토를 성토에 배분하고 운반 장비 및 사토량을 결정한 **최종 토공 운반계획 도면**이다. 동일한 결과를 웹앱에서 재현하려면 횡단면별 토공량, 토량환산계수 및 운반 장비 선정기준이 추가로 필요하다.
|
||||
|
||||
## 2. 분석 대상
|
||||
|
||||
- 원본 파일: `07.유토곡선.pdf`
|
||||
- 공사명: 2024년 간선임도사업(기번3) - 울진 울진 대흥 산65임 -
|
||||
- 도면 종류: 유토곡선
|
||||
- 수평축척: H=1:2000
|
||||
- 수직축척: V=1:10000
|
||||
- 측점 범위: `0+0.0` ~ `60+0.0`
|
||||
- 시작 누가토량: `0.00㎥`
|
||||
- 마지막 누가토량: `1,171.00㎥`
|
||||
|
||||
## 3. 유토곡선의 의미
|
||||
|
||||
유토곡선은 노선을 따라 발생하는 절토와 성토의 차이를 누적해 그린 곡선이다. 이 곡선을 이용하면 다음을 판단할 수 있다.
|
||||
|
||||
- 절토가 남는 구간과 성토가 필요한 구간
|
||||
- 노선 내부에서 절토와 성토가 균형을 이루는 범위
|
||||
- 절토를 어느 성토 구간으로 이동시킬지
|
||||
- 운반토량과 평균운반거리
|
||||
- 종무대, 불도저, 덤프트럭 등 운반방법
|
||||
- 노선 내에서 사용할 수 없어 외부로 반출할 사토량
|
||||
- 외부에서 토사가 필요한 경우의 토취량
|
||||
|
||||
### 3.1 곡선 해석
|
||||
|
||||
이 도면은 다음 방향으로 작성된 것으로 해석된다.
|
||||
|
||||
- 곡선 상승: 해당 구간에서 절토가 성토보다 많음
|
||||
- 곡선 하강: 해당 구간에서 성토가 절토보다 많음
|
||||
- 봉우리 또는 골짜기: 절토 우세와 성토 우세가 바뀌는 지점
|
||||
- 같은 누가토량 높이의 두 지점: 그 사이에서 절토와 성토가 대체로 균형을 이루는 범위
|
||||
|
||||
단, 곡선의 증감 부호는 프로그램 또는 발주기관의 작성 관례에 따라 반대로 정의할 수도 있으므로 웹앱에서는 부호 규칙을 명시적으로 고정해야 한다.
|
||||
|
||||
## 4. 기본 계산 원리
|
||||
|
||||
### 4.1 측점별 순토량
|
||||
|
||||
각 측점 구간의 순토량은 다음과 같이 계산한다.
|
||||
|
||||
```text
|
||||
순토량 = 절토량 - 환산성토량
|
||||
```
|
||||
|
||||
### 4.2 누가토량
|
||||
|
||||
```text
|
||||
현재 누가토량 = 이전 누가토량 + 현재 구간 순토량
|
||||
```
|
||||
|
||||
수식으로 표현하면 다음과 같다.
|
||||
|
||||
```text
|
||||
M(i) = M(i-1) + C(i) - F'(i)
|
||||
```
|
||||
|
||||
- `M(i)`: 현재 측점의 누가토량
|
||||
- `C(i)`: 현재 구간 절토량
|
||||
- `F'(i)`: 절토 기준으로 환산한 현재 구간 성토량
|
||||
|
||||
### 4.3 PDF 값에 대한 계산 예
|
||||
|
||||
#### 상승 구간
|
||||
|
||||
```text
|
||||
0+0.0 : 0.00㎥
|
||||
1+0.0 : 129.95㎥
|
||||
1+18.0 : 231.43㎥
|
||||
```
|
||||
|
||||
`0+0.0`부터 `1+18.0`까지의 누가 변화량은 다음과 같다.
|
||||
|
||||
```text
|
||||
231.43 - 0.00 = +231.43㎥
|
||||
```
|
||||
|
||||
따라서 이 범위는 절토가 환산성토보다 `231.43㎥` 많은 구간으로 해석된다.
|
||||
|
||||
#### 하강 구간
|
||||
|
||||
```text
|
||||
4+0.0 : 441.27㎥
|
||||
4+15.0 : 414.65㎥
|
||||
```
|
||||
|
||||
누가 변화량은 다음과 같다.
|
||||
|
||||
```text
|
||||
414.65 - 441.27 = -26.62㎥
|
||||
```
|
||||
|
||||
따라서 이 범위에서는 환산성토가 절토보다 `26.62㎥` 많다.
|
||||
|
||||
## 5. 토량환산이 필요한 이유
|
||||
|
||||
절토 상태의 흙, 굴착 후 느슨해진 흙, 다짐을 완료한 성토는 동일한 흙이라도 부피가 서로 다르다. 그러므로 단순히 절토 `100㎥`와 성토 `100㎥`를 같은 양으로 취급하면 안 된다.
|
||||
|
||||
개념적인 환산식은 다음과 같다.
|
||||
|
||||
```text
|
||||
환산성토량 = 성토량 × 적용 토량환산계수
|
||||
```
|
||||
|
||||
실제 계산에서는 다음 상태 중 어느 것을 기준 토량으로 사용할지 먼저 정해야 한다.
|
||||
|
||||
- 자연상태 토량
|
||||
- 굴착 후 느슨한 상태의 운반토량
|
||||
- 다짐 완료 상태의 성토량
|
||||
|
||||
또한 토질별로 서로 다른 계수가 적용될 수 있다.
|
||||
|
||||
- 토사(EA)
|
||||
- 리핑암(RR)
|
||||
- 발파암(BR)
|
||||
|
||||
PDF의 마지막 누가토량 `1,171.00㎥`와 사토 합계 `951.40㎥`가 일치하지 않으므로, 도면 작성 과정에서 토질별 환산계수, 다짐계수 또는 별도 공제·가산량이 적용되었을 가능성이 크다. 이 차이는 원본 횡단면 토공량표와 적용 계수를 확인하기 전에는 확정할 수 없다.
|
||||
|
||||
## 6. 도면 표기의 의미
|
||||
|
||||
### 6.1 Q
|
||||
|
||||
`Q`는 해당 운반 작업에 배분된 토량이다.
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
덤프 3
|
||||
Q=1456.09M3
|
||||
```
|
||||
|
||||
덤프트럭으로 운반하도록 배분된 물량이 `1,456.09㎥`라는 의미이다.
|
||||
|
||||
### 6.2 L
|
||||
|
||||
`L`은 해당 토량의 평균운반거리이다. 단순한 구간 시·종점 거리라기보다, 절토와 성토가 분포한 위치를 토량으로 가중한 중심 간 거리로 계산하는 것이 일반적이다.
|
||||
|
||||
```text
|
||||
절토중심 = Σ(절토량 × 위치) / Σ절토량
|
||||
성토중심 = Σ(성토량 × 위치) / Σ성토량
|
||||
평균운반거리 = |절토중심 - 성토중심|
|
||||
```
|
||||
|
||||
유토곡선에서는 곡선과 균형선 사이의 면적을 운반모멘트로 이용할 수도 있다.
|
||||
|
||||
```text
|
||||
운반모멘트 = Σ(Q × L)
|
||||
평균운반거리 = Σ(Q × L) / ΣQ
|
||||
```
|
||||
|
||||
### 6.3 EA, RR, BR
|
||||
|
||||
- `EA`: 토사
|
||||
- `RR`: 리핑암
|
||||
- `BR`: 발파암
|
||||
|
||||
도면의 모든 운반 작업에서 다음 관계가 정확히 성립한다.
|
||||
|
||||
```text
|
||||
Q = EA + RR + BR
|
||||
```
|
||||
|
||||
예를 들어 덤프 3은 다음과 같다.
|
||||
|
||||
```text
|
||||
760.44 + 695.65 + 0.00 = 1,456.09㎥
|
||||
```
|
||||
|
||||
### 6.4 종무대
|
||||
|
||||
짧은 구간에서 발생한 절토를 인접 성토에 사용하는 종방향 무대운반 구간으로 해석된다. 무대운반에 포함되는 작업과 인정거리는 적용 품셈 또는 발주기관 기준을 확인해야 한다.
|
||||
|
||||
### 6.5 도쟈
|
||||
|
||||
불도저로 흙을 밀어서 운반하도록 계획한 구간이다. 장비의 경제성을 고려해 비교적 짧거나 중간 정도의 운반거리에 적용한다.
|
||||
|
||||
### 6.6 덤프
|
||||
|
||||
굴착토를 상차한 후 덤프트럭으로 운반하도록 계획한 구간이다. 불도저 운반보다 먼 거리에서 일반적으로 적용한다.
|
||||
|
||||
### 6.7 사토
|
||||
|
||||
노선 내 성토에 사용할 수 없는 잉여토를 외부로 반출하는 작업이다.
|
||||
|
||||
사토 항목에 표시된 `M.N=1+14.51`, `M.N=7+6.23`은 사토 처리와 관련된 기준 측점으로 보이지만, `M.N`의 정확한 정의는 원본 작성 프로그램 또는 계산서의 표기 규칙을 확인해야 한다.
|
||||
|
||||
## 7. PDF 운반계획 검산 결과
|
||||
|
||||
PDF에서 총 43개 작업을 추출해 검산했다.
|
||||
|
||||
| 구분 | 작업 수 | Q(㎥) | EA(㎥) | RR(㎥) | BR(㎥) |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 종무대 | 19 | 2,151.86 | 716.74 | 1,435.12 | 0.00 |
|
||||
| 도쟈 | 14 | 2,724.75 | 1,170.35 | 1,554.40 | 0.00 |
|
||||
| 덤프 | 8 | 3,381.68 | 1,667.46 | 1,714.22 | 0.00 |
|
||||
| 사토 | 2 | 951.40 | 480.49 | 470.91 | 0.00 |
|
||||
| 합계 | 43 | **9,209.69** | **4,035.04** | **5,174.65** | **0.00** |
|
||||
|
||||
검산 결과:
|
||||
|
||||
- 43개 항목 모두 `Q = EA + RR + BR` 성립
|
||||
- 최대 산술 오차는 부동소수점 표현 수준이며 도면 표기값 기준으로 불일치 없음
|
||||
- 발파암 물량은 전체 구간에서 `0.00㎥`
|
||||
- 가장 긴 표기 운반거리는 덤프 1의 `1,007.86m`
|
||||
|
||||
## 8. 웹앱 입력 데이터
|
||||
|
||||
PDF 자체를 입력으로 사용하는 것이 아니라, PDF 작성 전의 원시 토공량을 구조화해야 한다.
|
||||
|
||||
### 8.1 필수 입력
|
||||
|
||||
| 입력 | 설명 |
|
||||
|---|---|
|
||||
| 측점 | 사용자 표시용 측점 문자열 |
|
||||
| 누적거리 | 노선 시작점에서 해당 측점까지의 실제 거리(m) |
|
||||
| 토사 절토량 | 해당 구간의 토사 굴착량 |
|
||||
| 리핑암 절토량 | 해당 구간의 리핑암 굴착량 |
|
||||
| 발파암 절토량 | 해당 구간의 발파암 굴착량 |
|
||||
| 성토량 | 해당 구간의 성토량 |
|
||||
| 토량환산계수 | 토질 및 토량 상태별 환산계수 |
|
||||
| 사토장 위치 | 사토 발생 시 반출 위치 또는 운반거리 |
|
||||
| 토취장 위치 | 토량 부족 시 반입 위치 또는 운반거리 |
|
||||
| 운반장비 기준 | 거리 또는 현장조건에 따른 장비 선정기준 |
|
||||
|
||||
### 8.2 입력 데이터 예
|
||||
|
||||
```json
|
||||
{
|
||||
"station": "4+15.0",
|
||||
"distance_m": 95.0,
|
||||
"cut_m3": {
|
||||
"earth": 0.0,
|
||||
"rippable_rock": 0.0,
|
||||
"blasted_rock": 0.0
|
||||
},
|
||||
"fill_compacted_m3": 26.62
|
||||
}
|
||||
```
|
||||
|
||||
측점 번호만으로 실제 거리를 추정하지 말고, `distance_m`을 별도 저장하는 것이 안전하다. 측점 간격이 항상 동일하지 않거나 추가 측점이 존재할 수 있기 때문이다.
|
||||
|
||||
## 9. 웹앱 계산 절차
|
||||
|
||||
1. 측점과 실제 누적거리를 정렬한다.
|
||||
2. 횡단면 또는 구간별 절토·성토량을 입력한다.
|
||||
3. 토질별 환산계수를 적용해 동일 기준의 토량으로 변환한다.
|
||||
4. 각 구간의 순토량을 계산한다.
|
||||
5. 순토량을 누적해 측점별 누가토량을 만든다.
|
||||
6. 측점 거리와 누가토량으로 유토곡선을 그린다.
|
||||
7. 같은 누가토량 높이의 교점을 이용해 균형구간 후보를 찾는다.
|
||||
8. 각 균형구간의 이동토량 `Q`를 계산한다.
|
||||
9. 절토와 성토의 토량 중심을 계산해 평균운반거리 `L`을 구한다.
|
||||
10. 거리 및 현장조건에 따라 종무대·도쟈·덤프로 분류한다.
|
||||
11. 남는 토량은 사토, 부족한 토량은 토취로 배분한다.
|
||||
12. 토질 구성에 따라 `Q`를 EA·RR·BR로 분리한다.
|
||||
13. 유토곡선, 운반작업 목록 및 수량산출표를 출력한다.
|
||||
|
||||
## 10. 권장 계산 모듈 경계
|
||||
|
||||
```text
|
||||
원시 토공량
|
||||
→ 토량 상태 및 토질별 환산
|
||||
→ 구간 순토량 계산
|
||||
→ 누가토량 계산
|
||||
→ 균형구간 탐색
|
||||
→ 운반토량 및 평균운반거리 계산
|
||||
→ 장비 선정
|
||||
→ 사토·토취 처리
|
||||
→ 그래프 및 수량표 출력
|
||||
```
|
||||
|
||||
계산 엔진과 그래프 표시 기능은 분리하는 것이 좋다. 그래프를 다시 그리지 않고도 계산 결과를 검산할 수 있어야 하며, 계산 결과에는 적용 계수와 중간값을 함께 보존해야 한다.
|
||||
|
||||
## 11. 권장 출력 데이터
|
||||
|
||||
### 11.1 측점별 누가토량
|
||||
|
||||
```json
|
||||
{
|
||||
"station": "4+15.0",
|
||||
"distance_m": 95.0,
|
||||
"net_volume_m3": -26.62,
|
||||
"cumulative_volume_m3": 414.65
|
||||
}
|
||||
```
|
||||
|
||||
### 11.2 운반 작업
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "dump",
|
||||
"sequence": 3,
|
||||
"quantity_m3": 1456.09,
|
||||
"average_haul_distance_m": 239.68,
|
||||
"material_m3": {
|
||||
"earth": 760.44,
|
||||
"rippable_rock": 695.65,
|
||||
"blasted_rock": 0.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 12. 구현 전에 반드시 확인할 자료
|
||||
|
||||
동일한 유토곡선과 운반배분 결과를 재현하려면 다음 자료가 필요하다.
|
||||
|
||||
1. 측점별 또는 횡단면별 절토·성토 수량표
|
||||
2. 토사·리핑암·발파암별 절토량
|
||||
3. 절토, 느슨한 토량 및 다짐성토 사이의 환산계수
|
||||
4. 종무대 인정거리와 산정방식
|
||||
5. 도저 및 덤프트럭의 적용거리 경계
|
||||
6. 장비 선정 시 거리 외에 적용되는 토질·경사·현장조건
|
||||
7. 유토곡선 균형구간을 자동 분할하는 기준
|
||||
8. 사토 및 토취 처리 규칙
|
||||
9. `M.N` 표기의 정확한 정의
|
||||
10. 평균운반거리의 원본 프로그램 계산방식
|
||||
|
||||
## 13. 현재 단계에서 확정할 수 없는 사항
|
||||
|
||||
다음 내용은 PDF 결과만으로 확정해서는 안 된다.
|
||||
|
||||
- 실제 적용된 토량환산계수
|
||||
- 누가토량 `1,171.00㎥`와 사토량 `951.40㎥`의 차이 원인
|
||||
- 종무대·도쟈·덤프의 정확한 거리 경계
|
||||
- 장비별 물량 배분의 자동화 규칙
|
||||
- `M.N`의 정확한 의미
|
||||
- 평균운반거리 산정 시 사용한 세부 보간법
|
||||
- 토질별 물량을 각 운반구간에 배분한 순서와 기준
|
||||
|
||||
이 항목들은 원본 토공량 계산서, 적용 품셈 및 기존 작성 프로그램의 산출 근거를 확보한 후 별도 검증해야 한다.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B05·B06 개선 2차·3차·4차)
|
||||
|
||||
## B05·B06 개선 3차 및 4차 (E-1~E-7 & 후속) ✅ 완료
|
||||
|
||||
### E-1. 2단 경사 반영 시점 현황 파악 및 자동 재계산 ✅ 완료
|
||||
- 누락된 `two_stage_slope` 옛 암 design 로드 시 `reconcileStaleRockDesigns`로 자동 재계산 적용. (`_UI_Page.ts`)
|
||||
|
||||
### E-2 & E-3 & 후속 1/3. 헤더 1행 구조 및 단면유형/지반유형 배치 ✅ 완료
|
||||
- 측점 라벨 → 지반유형 세그먼트(라벨 제거, 좌측 구분선) → 단면유형 pill(우측 margin-left: auto) 1행 구조 재배치. (`_UI_Cross_View.ts`, `_UI_Cross_Design.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### E-4. 절·성토 면적 오버레이 ✅ 완료
|
||||
- `buildAreaReadout` 함수로 면적값을 SVG 그래프 중상단 반투명 배경 오버레이로 배치. (`_UI_Cross_View.ts`, `_UI_Cross_Design.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### E-5. 마우스 휠 줌/팬 개편 ✅ 완료
|
||||
- 커서 중심 휠 줌, 드래그 팬, 더블클릭 1:1 원복 (`attachZoomPan`). 선 `vector-effect: non-scaling-stroke` 적용으로 확대 시에도 선명도 유지. (`_UI_Cross_View.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### E-6. 조건부 옵션 상시 표시 및 비활성화 처리 ✅ 완료
|
||||
- 선행 조건 미충족 옵션을 disabled 및 사유 툴팁 안내와 함께 상시 노출. (`_UI_Cross_Design.ts`, `ui_template_locale.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### E-7. 옵션 양식 변경 및 B05 정본 역반영 ✅ 완료
|
||||
- 라벨 제거 및 상태 표기 버튼명 적용. 암경계 제어 X축 우측 이동. B06 확정 시 측구 방향 B05 종단 정본 역반영(`_merge_uphill_overrides_into_longitudinal`). (`_UI_Cross_Design.ts`, `_UI_Cross_View.ts`, `_Router.py`, `_UI_Style_Cross.css`)
|
||||
|
||||
### CSS 분리 및 후속 조율 (1~6) ✅ 완료
|
||||
- `_UI_Style_Cross.css` 분리 (496줄 / 원본 291줄).
|
||||
- 암 전환 시 twoStage 기본 true, `CROSS_GRID_MIN_WIDTH` 560px, `CROSS_PAD.top` 10px 조정.
|
||||
|
||||
---
|
||||
|
||||
## B05·B06 개선 2차 (D-1~D-7) ✅ 완료
|
||||
|
||||
### D-1. 측구 생성 여부 토글 ✅ 완료
|
||||
- 엔진 `has_ditch` 자동 판정 + `ditch_enabled` override(None=자동). 카드 컨트롤에 측구 on/off 토글 추가. (`_Engine_Design.py`, `_Schema.py`, `_Router.py`, `_Api_Fetch.ts`, `_UI_Cross_Design.ts`, `_UI_Page.ts`)
|
||||
|
||||
### D-2. 단면유형 자동 판정 ✅ 완료
|
||||
- 지반 대비 노면고 절/성토 역할 자동 판정. UI 수동 세그먼트 제거 및 읽기전용 배지 치환. (`_Engine_Design.py`, `_UI_Cross_Design.ts`)
|
||||
|
||||
### D-3. 횡단도 줌 기능 ✅ 완료
|
||||
- SVG viewBox 스케일 연동. (`_UI_Cross_View.ts`)
|
||||
|
||||
### D-4. B05 빈 공간 클릭 시 측점 하이라이트 초기화 ✅ 완료
|
||||
- `selectObject(undefined)` 경로에서 `selectStation(null)` 연동. (`B05_wf2_Route_UI_Markers.ts`)
|
||||
|
||||
### D-5. 도로포장 범위 차도만 ✅ 완료
|
||||
- `carriageway_edges` (노견 제외 차도 폭) 기준 포장 오버레이 생성. (`_Engine_Design.py`, `_Api_Fetch.ts`, `_UI_Cross_Design.ts`)
|
||||
|
||||
### D-6. 횡단도 지반유형 컨트롤 제목행 이동 ✅ 완료
|
||||
- 카드 본문에서 헤더 행으로 지반유형 이동. (`_UI_Cross_View.ts`, `_UI_Cross_Design.ts`)
|
||||
|
||||
### D-7. 모식도 조정 ✅ 완료
|
||||
- 노면 수평화, 라벨 폰트 크기 확대. (`_UI_Standard_Diagram.ts`)
|
||||
@@ -0,0 +1,34 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B05·B06 수정사항)
|
||||
|
||||
## 현재 작업내용 — B05·B06 수정사항 (2026-07-25 협의 완료)
|
||||
|
||||
### C-1. 리핑암/발파암 2단계 경사 (암반 경계 기준) ✅ 완료
|
||||
- **배경**: 현재 절토 경사는 단일 값. 지반 유형이 리핑암/발파암이면 암반 경계 위는 토사 경사, 아래는 암반 경사로 나뉘어야 실무에 맞음. 암 경계선 = 지면선 복사 + 오프셋 규칙.
|
||||
- **구현**: `_SectionGeometry`에 무릎점(knee) 계산 추가, `design_z` cut 분기 2단계화, `breakpoints`에 무릎 오프셋 포함. `B06_Design_TwoStage_*` locale, Cross_Design 토글 버튼, design dict `two_stage_slope`/`soil_cut_slope_ratio` echo.
|
||||
- **완료 조건**: 리핑암/발파암 측점 횡단도에 2단계 경사·암반 예정선(점선) 표시 ✔, 토글로 단일 경사 전환 ✔, 구조물 측점 정상 표시 ✔.
|
||||
|
||||
### C-2. B05 하단 슬라이드 패널 — 계획고 강조 스타일 제거 ✅ 완료
|
||||
- **구현**: `B05_wf2_Route_UI_Style.css` (오버레이 is-plan 규칙 삭제).
|
||||
- **완료 조건**: 오버레이 전 항목 동일 스타일. ✔
|
||||
|
||||
### C-3. B05·B06 측구 방향 불일치 수정 ✅ 완료
|
||||
- **구현**: `B06_wf3_ProfileCross_UI_Cross_View.ts` (x 매핑을 `pad+(maxOffset-offset)*ppm`로 반전).
|
||||
- **완료 조건**: 같은 측점에서 B05 3D 방향과 B06 측구 위치 일치. ✔
|
||||
|
||||
### C-4. B06 좌측 슬라이드 패널 — B05 공통 UI 재사용 ✅ 완료
|
||||
- **구현**: B06 패널 공통 컴포넌트(`createSelectField`, `createInputField`, `createButton`) 재사용 및 Cross_Design의 toggle() 헬퍼 통합. B05 라우트 패널 select 3개 공통 이관.
|
||||
- **완료 조건**: B06 패널 공통 컴포넌트 기반, 중복 최소화. ✔
|
||||
|
||||
### C-5. B06 구간별 설정 컨테이너 — 변수 위치 안내 그림 ✅ 완료
|
||||
- **구현**: 신규 `B06_wf3_ProfileCross_UI_Standard_Diagram.ts` (세로형 300x260 인라인 SVG 모식도).
|
||||
- **완료 조건**: 패널에서 각 변수의 횡단면 위치를 그림으로 확인 가능. ✔
|
||||
|
||||
### C-6. B06 설계값 불러오기 (타 프로젝트 조회·적용) ✅ 완료
|
||||
- **구현**: 회사 스코프 기반 최근 5개 프로젝트 불러오기 API & UI (Raw SQL, `list_recent_company_projects`, `get_project_standard_cross_section`).
|
||||
- **완료 조건**: 최근 5개 프로젝트 목록 조회 및 선택 즉시 적용. ✔
|
||||
|
||||
### 2차 피드백 반영 (R-1 ~ R-4) ✅ 완료
|
||||
- **R-1**: 반대면 자동 절토 전환 (`_Engine_Design.py`)
|
||||
- **R-2**: 구조물 측점 DB upsert 및 종단면도 라벨 (`_Repository.py`, `_Router.py`, `_UI_Longitudinal.ts`)
|
||||
- **R-3**: 모식도 세로형 300x260 개선 (`_UI_Standard_Diagram.ts`)
|
||||
- **R-4**: 불러오기 최근 5개 즉시 적용 개편 (`_Repository.py`, `_Router.py`, `_UI_Standard_Panel.ts`)
|
||||
@@ -0,0 +1,52 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B06 2차 개선 및 후속 작업)
|
||||
|
||||
## N-1 후속 및 N-2. B06 2차 개선 ✅ 완료
|
||||
|
||||
### N-1 후속. 측구 미생성 시 측구형식 비활성화 ✅ 완료
|
||||
- `!rockCut || !ditchOn` disabled 조건 및 사유 툴팁 분리. (`_UI_Cross_Design.ts`, `ui_template_locale.ts`)
|
||||
|
||||
### N-2-1. 표준단면 패널 값 일괄 반영 버튼 ✅ 완료
|
||||
- `_UI_Standard_Panel.ts` [전체 반영] 버튼 (`onApplyAll`), `_UI_Page.ts` `applyPanelToAll()` 일괄 재계산 구현.
|
||||
|
||||
### N-2-2. 카드 옵션 행 오버플로 → 우측 드롭다운 ✅ 완료
|
||||
- `ResizeObserver` 오버플로 측정 + `details` "⋯" 우측 맞춤 세로 메뉴 드롭다운. (`_UI_Cross_Design.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### N-2-3. 도로 경사 방향(좌/우) 의미 재정의 ✅ 완료
|
||||
- `needsDitch()` 조건에 `both_fill` 확장, 좌/우 세그먼트(`SlopeDir_Left/Right`) 교체 및 툴팁 정비. (`_UI_Cross_Design.ts`)
|
||||
|
||||
### N-2-4. 지반선 1회 교차 후 사면 연장 중단 ✅ 완료
|
||||
- `_SectionGeometry.cut_cross_dist()`/`_cut_slope_z()` 신설로 최초 지반 교차점 이후 연장 및 2단 무릎 전환 차단. (`B06_wf3_ProfileCross_Engine_Design.py`)
|
||||
|
||||
### N-2-5. 단면유형 pill 라벨 3종화 ✅ 완료
|
||||
- 편측 성토 / 양측 절토 / 양측 성토 라벨 교체. (`ui_template_locale.ts`)
|
||||
|
||||
### N-2-6. 브라우저 폭 변경 시 카드 어긋남 버그 수정 ✅ 완료
|
||||
- `grid.style.gridTemplateColumns = repeat(columnCount, minmax(0,1fr))` 명시로 CSS/JS 열 수 정합. (`_UI_Section_View.ts`)
|
||||
|
||||
---
|
||||
|
||||
## N-3 ~ N-5. 소규모 후속 개선 작업 ✅ 완료
|
||||
|
||||
### N-3-1. B05 구간 평행이동(⇧/⇩) 원복 ↺ 버튼 ✅ 완료
|
||||
- 구간 양 끝 편집 시 ↺ 버튼 노출 및 양 끝 오프셋 삭제 연결. (`_UI_Profile_Edit.ts`, `_UI_Profile_Panel.ts`)
|
||||
|
||||
### N-3-2. B06 모식도 폰트 확대 ✅ 완료
|
||||
- `.b06-diag__label` 14px, `.b06-diag__label-sm` 12px 확대. (`_UI_Style.css`)
|
||||
|
||||
### N-3-3. 좌/우 라벨 축약 ✅ 완료
|
||||
- "좌경사/우경사" → "좌/우" 축약. (`ui_template_locale.ts`)
|
||||
|
||||
### N-4-1. B06 사이드패널 접기 템플릿 적용 ✅ 완료
|
||||
- 상위 그룹 `ui-collapsible` + `attachCollapsible`, 하위 구간 `details`/`summary` 기본 접힘 구조. (`_UI_Page.ts`, `_UI_Standard_Panel.ts`, `_UI_Style.css`)
|
||||
|
||||
### N-4-2. 횡단면도 차도·노견 경계 짧은 수직선 틱 ✅ 완료
|
||||
- `carriageway_edges` 차도 양 끝 ±6px 수직 틱(`.b06-chart__carriageway-tick`) 추가. (`_UI_Cross_Design.ts`, `_UI_Style_Cross.css`)
|
||||
|
||||
### N-5-1. 구간 그룹 패딩 모식도 정합 ✅ 완료
|
||||
- `.b06-std__group` 패딩 8px 균일화 및 legend 패딩 제거. (`_UI_Style.css`)
|
||||
|
||||
### N-5-2. 종단면도 구조물(비정규) 측점 세로선 미표시 수정 ✅ 완료
|
||||
- `.b06-chart__station-line--irregular` 자수정색 파선(`5 3`) 스타일 추가. (`_UI_Style_Cross.css`)
|
||||
|
||||
### N-5-3. 절·성토 오버레이 Y 상단 여유 확보 ✅ 완료
|
||||
- `AREA_OVERLAY_HEADROOM_PX = 28` 적용으로 Y 플롯 상단 여유 확보. (`_UI_Cross_View.ts`)
|
||||
@@ -0,0 +1,7 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B06 N-6 & N-7 작업)
|
||||
|
||||
## N-6. B05 종단 변경 후 B06 횡단도 자동 갱신 ✅ 완료
|
||||
- B05에서 종단 확정 후 B06 로드 시, 계산 기준 계획고(`design.design_elevation_m`)와 현 계획선 보간고(`designElevationAt`) 차이가 나는 측점 및 옛 암 2단계 미반영 측점을 감지하여 자동 재계산(`reconcileStaleDesigns`). (`B06_wf3_ProfileCross_UI_Page.ts`)
|
||||
|
||||
## N-7. B06 암 경계선 상/하 버튼 길게 누르기 연속 조정 ✅ 완료
|
||||
- ▲/▼ 버튼 hold-repeat 타이머(`createHoldRepeater`: 0.5초 유지 후 0.1초 간격 반복) 신설 적용. window 이벤트 핸들링으로 재렌더/포커스 이탈 대비. (`B06_wf3_ProfileCross_UI_Cross_Design.ts`)
|
||||
@@ -0,0 +1,22 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B07 상세설계 및 CAD 표출)
|
||||
|
||||
## N-1. B07 페이지 재개 — 종단도 테이블 + 횡단도 레이어화 + 상세설계 게이팅 ✅ 완료
|
||||
|
||||
### N-1-0. F-2 선행: design 신구조 호환 ✅ 완료
|
||||
- `B07_wf4_DesignDetail_Api_Fetch.ts` `ditch` 타입 갱신 (`DitchSpec` 유니언 + 신규 키).
|
||||
- `B07_wf4_DesignDetail_UI_Page.ts` `design.ditch.width_m` 구 접근 제거 및 `ditchLabel()` 치환.
|
||||
|
||||
### N-1-1. 종단도: 하단 테이블 추가 + 30측점 분할 ✅ 완료
|
||||
- 백엔드 모듈 분리: `B07_wf4_DesignDetail_Engine_Cad_Long.py` (652줄).
|
||||
- 30측점 단위 N분할 (`longitudinal_chunks()`) 및 사이드 패널 `종단도(N)` 동적 버튼 연동.
|
||||
- 납품 양식 종단 하단 CAD 측점 테이블 (`b07-long-table` 레이어) 직렬화 — 세로쓰기 값 표기, 곡선/구배 브래킷 및 사선+원 표기.
|
||||
- 그래프 Y축 눈금/라벨, 기준선, 측점 세로선(`b07-long-grid` 잠금 레이어) 추가. 더블클릭 값 편집 지원.
|
||||
|
||||
### N-1-2. 횡단도: 선별 레이어 분리 + 테이블 CAD화 ✅ 완료
|
||||
- 횡단도 4개 레이어 분리: `b07-ground`(#f5f7fa), `b07-design`(#b794f6), `b07-structure`(#f6d55c), `b07-rock-boundary`(#f59e0b 파선).
|
||||
- 수량 산출표 납품 양식 복제: `b07-cross-table` 레이어로 CAD 엔티티 테이블 직렬화, 21개 수량 키셋 기반 3그룹 그리드 구성.
|
||||
- CAD 더블클릭 편집 및 확정 저장 시 `extract_quantity_table()` 역추출 보존. 도면 포맷 버전 스탬프(`DRAWING_FORMAT` v5) 적용.
|
||||
- 횡단 콘텐츠 bbox 중심 정렬 및 y 고정.
|
||||
|
||||
### N-1-3. 상세설계: 지표면선 제어 제외 ✅ 완료
|
||||
- `b07-ground` 레이어 잠금 (`isLocked: true`) 적용하여 CAD 편집 선택 대상에서 제외.
|
||||
@@ -0,0 +1,26 @@
|
||||
# 완료된 작업 이력 (2026-07-25 B08 재구축 작업)
|
||||
|
||||
## B08 재구축 작업 계획 (2026-07-25) ✅ 완료
|
||||
|
||||
### 1. 설계 원칙 및 범위 준수
|
||||
- 소스 변경: `B08_wf5_Quantity` 폴더 내부로만 국한.
|
||||
- DB SSOT: 프로젝트·공종·품목·수량·단가·요율·계산 실행 DB 스키마 및 Raw SQL 구축.
|
||||
- STmate 코드와 Aislo 내부 ID 매핑 분리 (`external_code` vs `id`).
|
||||
- 수량 보정 사유 필수화 및 이력(`b08_quantity_revisions`) 저장.
|
||||
- 전 소스 파일 700줄 이하 유지 (20개 모듈 파일 분리, 기존 2068줄 단일 대형 파일 제거).
|
||||
|
||||
### 2. 구현 모듈 세부
|
||||
- **Backend**:
|
||||
- `B08_wf5_Quantity_Schema.py`: 공종, 품목, 수량, 단가, 대조 모델
|
||||
- `B08_wf5_Quantity_Repository.py`: Raw SQL 데이터 작업
|
||||
- `B08_wf5_Quantity_Database_*.py`: DB 워크스페이스, 가상 단가, 계산 스냅샷
|
||||
- `B08_wf5_Quantity_Engine.py` / `Service.py`: 수량 계산 및 서비스 통합
|
||||
- `B08_wf5_Quantity_Router.py`: FastAPI 엔드포인트
|
||||
- `B08_wf5_Quantity_Import.py`: STC/XLSX 분석 결과 수용 임포터
|
||||
- **Frontend**:
|
||||
- `B08_wf5_Quantity_UI_Page.ts`: B08 메인 탭 및 워크플로우 구성
|
||||
- `B08_wf5_Quantity_UI_Quantity.ts`: 수량 입력·보정·확정 테이블
|
||||
- `B08_wf5_Quantity_UI_Reconciliation.ts`: STmate/XLSX 대조 컴포넌트
|
||||
- `B08_wf5_Quantity_UI_Summary.ts`: 재료비·노무비·경비·직접공사비 요약
|
||||
- `B08_wf5_Quantity_Store.ts` / `Api.ts` / `Calculator.ts`: 상태 관리 및 API 통신
|
||||
- `B08_wf5_Quantity_UI_Style.css`: B08 전용 스타일
|
||||
@@ -0,0 +1,18 @@
|
||||
# 완료된 작업 이력 (2026-07-25)
|
||||
|
||||
## 2부. 다음 작업 (완료 항목 이관)
|
||||
|
||||
### N-2. B07 임시 버튼 타입 오류 수정 (typecheck 차단 해제) ✅ 완료
|
||||
- **배경**: `B07_wf4_DesignDetail_UI_Page.ts:512` 임시 테스트 버튼이 `variant: "outlined"` 사용 — ButtonVariant에 없는 값이라 `npm run typecheck` 전체 실패. B07 기능 작업이 아니라 프로젝트 공통 차단 해제용 1줄 수정.
|
||||
- **구현 (2026-07-25)**: `variant: "outlined"` → `"ghost"` 교체(`B07_wf4_DesignDetail_UI_Page.ts:512`). `npm run typecheck` 전체 통과 확인.
|
||||
- **완료 조건**: `npm run typecheck` 전체 통과. ✔
|
||||
|
||||
### N-3. B05 Router 모듈 분리 (700줄 임박) ✅ 완료
|
||||
- **배경**: `B05_wf2_Route_Router.py` 688줄 — 다음 수정에서 제한 초과 확실.
|
||||
- **구현 (2026-07-25)**: confirm 병합 헬퍼 4개(`_section_options_from_stored`, `_merge_uphill_overrides_into_longitudinal`, `_merge_irregular_into_longitudinal`, `_append_irregular_cross_sections`)를 신규 `B05_wf2_Route_Router_Confirm.py`(130줄)로 이관. 라우터는 confirm 엔드포인트가 직접 쓰는 `_append_irregular_cross_sections`, `_merge_uphill_overrides_into_longitudinal`만 import. 라우터 584줄로 축소, ruff 통과, import 로드 검증 OK.
|
||||
- **완료 조건**: 라우터 700줄 이하, 기존 엔드포인트 동작 불변, ruff 통과. ✔
|
||||
|
||||
### N-4. 전 페이지 lint 스캔·수정 ✅ 완료
|
||||
- **배경**: N-2 유형(전역 차단 오류)이 다른 페이지에도 있는지 사용자 지시로 점검(2026-07-25).
|
||||
- **구현 (2026-07-25)**: TS typecheck 전체 통과 재확인(N-2 외 잘못된 variant 없음). 파이썬 ruff 전 소스 스캔 결과 실제 오류 1건 발견·수정 — `B03_FileInput_Email.py:227` E501(104자), `html.escape(project_name)`를 `safe_name` 변수로 추출해 라인 단축(동작 불변). ruff 전체 통과.
|
||||
- **완료 조건**: TS typecheck 통과 + ruff 전 소스 통과. ✔ (scratch/, 0_old/ 제외)
|
||||
@@ -0,0 +1,61 @@
|
||||
# B07 도면 템플릿 변환 파이프라인 및 종단도 A1 도각 템플릿 적용 (2026-07-26 이관)
|
||||
|
||||
## 1. B07 도면 템플릿 변환 파이프라인 (DXF → openwebcad JSON)
|
||||
|
||||
**배경**: B07 상세설계 CAD(openwebcad)는 자체 JSON 엔티티 스키마만 로드 가능(DXF 미지원). 납품 도면 양식(A1 도각, 방위표, 종단 Y축 눈금, 종단 테이블)을 openwebcad JSON 템플릿으로 사전 변환해 두고, 백엔드 엔진이 도면 생성 시 병합하는 방식 채택. DXF 마스터 원본은 사용자가 별도 보관(이 저장소의 DXF는 사본이라 변환 후 삭제).
|
||||
|
||||
**산출물**:
|
||||
| 항목 | 위치 | 비고 |
|
||||
|---|---|---|
|
||||
| 변환 스크립트 | `resources/dwg_analysis/dxf_to_openwebcad.py` | ezdxf 기반, 재실행 가능 도구 |
|
||||
| A1 도각 템플릿 | `resources/dwg_analysis/templete/00_templete_A1.json` | 87 엔티티 (상단 "0000000" 더미 TEXT는 변환 전 DXF에서 제거) |
|
||||
| 방위표 템플릿 | `resources/dwg_analysis/templete/00_templete_compass.json` | 39 엔티티 |
|
||||
| 종단 Y축 눈금 템플릿 | `resources/dwg_analysis/templete/01_templete_Longitudinal_Yaxis_scale.json` | 2,048 엔티티 |
|
||||
| 종단 테이블 템플릿 | `resources/dwg_analysis/templete/01_templete_Longitudinal_table.json` | 2,048 엔티티 |
|
||||
|
||||
**변환 스펙**:
|
||||
- 출력 포맷: `{format: 6, source, entities[], layers[]}` — `B07_wf4_DesignDetail_Engine_Cad.py`의 `DRAWING_FORMAT = 6`과 동일 스키마.
|
||||
- 엔티티 매핑: LINE→Line, LWPOLYLINE/POLYLINE(bulge 호는 flattening 근사)→PolyLine, ARC→Arc(도→라디안, CCW), CIRCLE→Circle, TEXT/ATTRIB→Text(정렬 72코드·회전·높이 반영), MTEXT→줄 분리 Text 다건(행간 5/3 근사), SOLID/HATCH→외곽선 PolyLine(채움 손실), POINT→Point, INSERT→재귀 explode.
|
||||
- 색상: ACI→hex 변환, ByLayer/ByBlock 해석, ACI 7은 다크 캔버스 가독용 `#f5f7fa`.
|
||||
- 선종류: CENTER/HIDDEN/DASH/DOT 계열 → `lineDash` 근사 매핑.
|
||||
- ID: uuid5 결정적 생성(재변환 시 동일 ID → diff 최소화). 레이어는 DXF 원본 이름 유지(병합 시 엔진에서 리매핑 예정).
|
||||
- 좌표계: DXF/openwebcad 모두 y-up, 각도 math 표준 — 값 그대로 통과(openwebcad `screenCanvas.drawController.ts`의 y-flip 렌더링 확인 완료).
|
||||
- 사용법: `python dxf_to_openwebcad.py [파일.dxf ...]` — 인자 없으면 `dwg_analysis` 루트의 `*.dxf` 전부 변환, `templete/{이름}.json`으로 저장(동명 파일 덮어씀, 원본 DXF 보존).
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] `00_templete_A1.dxf` 상단 더미 "0000000" TEXT 엔티티 제거 (ezdxf 로드 검증 통과)
|
||||
- [x] openwebcad 엔티티 JSON 스키마 분석 (`fromJson` 기준: Line/PolyLine children/Text options/Arc/Circle/Point 정합 확인)
|
||||
- [x] 변환 스크립트 작성 및 `resources/dwg_analysis/` 루트 배치
|
||||
- [x] 템플릿 4종 JSON 변환 (스킵 엔티티 0건)
|
||||
- [x] 변환 품질 검증: 한글 라벨 무손실(UTF-8), ID 중복 없음, 레이어 참조 무결
|
||||
- [x] 원본 DXF 사본 4종 삭제
|
||||
|
||||
**검증자 참고 (알려진 근사/한계)**:
|
||||
1. HATCH/SOLID 채움 손실 — openwebcad에 채움 엔티티 없음, 외곽선만 변환(A1 도각 해치, 방위표 침). B07 화면에서 시각 확인 필요.
|
||||
2. MTEXT 다중 행 위치는 근사(행간 5/3) — 미세 오차 가능.
|
||||
3. `01_templete_*` 두 파일은 현재 내용 동일(같은 원본 도면) — 사용자가 Y축/테이블 용도별로 다듬은 뒤 재변환 예정.
|
||||
4. 엔진 병합 로직(템플릿 JSON 로드 → 도면 엔티티 병합)은 미구현 — 별도 후속 작업.
|
||||
|
||||
|
||||
## 2. B07 종단도 A1 도각 템플릿 적용
|
||||
|
||||
**요구사항**: B06 종횡단 확정 후 B07 진입 시, 종단도 도면에 A1 도각 양식(`resources/dwg_analysis/templete/00_templete_A1.json`)을 적용한다. (횡단도는 후속 작업)
|
||||
|
||||
**분석**:
|
||||
- A1 템플릿: 840×594(A1 규격), 하단 y17~47 표제란 스트립, 내부 작도 영역 약 (42,47)~(812,567).
|
||||
- 종단도 좌표: x=측점거리(m), y=표고(m) — 청크당 폭 최대 600m급, 프레임과 단위 상이 → 템플릿을 콘텐츠 bbox에 맞춰 균등 스케일+이동 배치(콘텐츠 좌표는 불변 유지, 편집·치수 의미 보존).
|
||||
- `B07_wf4_DesignDetail_Engine_Cad_Long.py` 651줄 — 700줄 제한 임박, 병합 로직은 신규 모듈로 분리.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 신규 모듈 `B07_wf4_DesignDetail/B07_wf4_DesignDetail_Engine_Template.py`(122줄): 템플릿 JSON 로드+캐시(lru_cache), 콘텐츠 bbox 기준 균등 스케일·이동 변환(fontSize/radius 포함), 레이어 `b07-frame`(잠금) 리매핑, 엔티티 ID는 uuid5 결정적 재생성
|
||||
- [x] `build_longitudinal_drawing()`에 프레임 병합 적용 (`B07_wf4_DesignDetail_Engine_Cad_Long.py:652`, 662줄), layers에 Frame 항목 추가
|
||||
- [x] `DRAWING_FORMAT` 6→7 (`B07_wf4_DesignDetail_Engine_Cad.py:49`) — 확정 저장본 캐시 무효화
|
||||
- [x] `ruff format` + `ruff check` 통과
|
||||
- [x] git autopush
|
||||
|
||||
**스모크 테스트 결과** (가짜 종단 31측점): 청크 2분할 정상, 프레임 87 엔티티 병합, 콘텐츠 bbox ⊂ 프레임 bbox, 프레임 종횡비 1.414(A1 유지), ID 중복 없음, 재실행 결정성 확인.
|
||||
|
||||
**설계 기본값(사용자 확인 필요)**:
|
||||
- 프레임 레이어 잠금(양식 오염 방지). 표제란 수정은 DXF 마스터 재변환으로 대응.
|
||||
- 표제란 샘플값(옥갑탄광 등)은 템플릿 그대로 유지 — 마스터 다듬으면 자동 교체.
|
||||
- 배치: 콘텐츠를 내부 작도영역 중앙, 여백 5%.
|
||||
@@ -0,0 +1,6 @@
|
||||
# 완료된 작업 이력 (2026-07-26 B06 D-8 작업)
|
||||
|
||||
## [D-8] B06 표준단면 모식도 라벨 재배치 및 측구 형상 보정 ✅ 완료
|
||||
- 사이드 패널 "변수 위치 안내" 모식도에서 노견 좌/우 라벨 및 노폭 라벨을 동일 행(`labelLineY=104`)에 중앙 대칭으로 정렬하고, 중심 수직 파선을 분할 렌더링하여 노폭 라벨과의 겹침을 방지함.
|
||||
- 측구 깊이를 기존 16에서 10으로 약 60% 축소하고, 좌·우 벽 기울기를 4.5로 대칭 일치시켜 도형 한쪽 쏠림 현상을 해소함. 측구 라벨 좌표도 줄어든 깊이에 맞춰 가깝게 이동시킴.
|
||||
- 대상 파일: `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Standard_Diagram.ts`
|
||||
@@ -0,0 +1,55 @@
|
||||
# openwebcad 렌더링 성능 개선 (단계적) (2026-07-26 이관)
|
||||
|
||||
**배경**: A1 도각 템플릿(8,608 세그먼트) 병합 후 줌아웃+팬 시 CAD 조작 지연 발생. 원인 분석 결과 템플릿 데이터량은 트리거일 뿐, openwebcad 렌더 파이프라인 자체가 엔티티 수에 정직하게 비례하는 구조(컬링·배칭·캐시·인덱스 전무). 로고 삭제와 무관하게 실무 도면은 선 수가 계속 늘어나므로 파이프라인을 단계적으로 개선한다. 개선 후에는 선이 적은 도면도 더 빨라진다(엔티티당 고정 오버헤드 제거).
|
||||
|
||||
**병목 분석 (2026-07-26 조사)**:
|
||||
| # | 위치 | 문제 |
|
||||
|---|---|---|
|
||||
| 1 | `openwebcad/src/main.tsx:97-101` | RAF 무한 루프 — 변경 없어도 60fps 전체 재렌더 (dirty-flag 없음) |
|
||||
| 2 | `openwebcad/src/main.tsx:73-76` | 매 프레임 `findClosestEntity()` — 전 엔티티 거리연산 O(n)×60fps |
|
||||
| 3 | `openwebcad/src/helpers/draw-functions.ts:12` | 엔티티마다 `getLayers().find()` 선형 탐색 |
|
||||
| 4 | `openwebcad/src/entities/LineEntity.ts:42-48` | 세그먼트마다 `setLineStyles` + `isEntityHighlighted`(Array.includes O(n)) |
|
||||
| 5 | `openwebcad/src/state.ts:215-216` | selected/highlighted가 배열 — includes 비용 엔티티×선택수 |
|
||||
| 6 | `openwebcad/src/helpers/draw.ts:30` | 뷰포트 컬링·Path2D 배칭 없음, 세그먼트당 개별 stroke |
|
||||
| 7 | `openwebcad/src/helpers/calculate-angle-guides-and-snap-points.ts:22` | 스냅 후보 전량 순회, 잠금 레이어 미제외 |
|
||||
|
||||
**참고 아키텍처**: AutoCAD Web = WASM 코어 + WebGL(GPU 정점버퍼 상주, 변환행렬만 갱신) + 공간 인덱스 + LOD/적응형 표시 + 타일 캐시. Canvas2D 한계 내에서 1~3단계로 근접, WebGL 전환은 4단계(별도 마일스톤).
|
||||
|
||||
**성능 측정 기준(모든 단계 공통)**: A1 프레임 포함 종단도(약 9,000 세그먼트)에서 ① 줌아웃 전체뷰 좌클릭 팬 ② 연속 줌인/아웃 ③ 호버 이동. 각 단계 착수 전/후 draw 소요 ms 기록 비교(개발용 계측 훅).
|
||||
|
||||
|
||||
### 1단계: 렌더 루프 정비 — dirty-flag + 정적 장면 캐시 (팬 지연 직격) — 2026-07-26 구현 완료, 실화면 검증 대기
|
||||
대상: `main.tsx`, `helpers/draw.ts`, `drawControllers/screenCanvas.drawController.ts`, `state.ts`
|
||||
- [x] 장면 버전 dirty-flag: 신규 `helpers/scene-version.ts`(카운터) — `state.ts`의 setEntities/setSelectedEntityIds/setLayers/setGridEnabled/undo·redo에서 bump. 버전·줌·캔버스크기 변경 시에만 장면 재렌더
|
||||
- [x] 정적 장면 오프스크린 캐시: 신규 `helpers/scene-cache.ts` — 전체 엔티티를 offscreen canvas에 1회 렌더, 팬 중에는 `blitImage`(픽셀 offset 블리팅)만, 오프셋 120ms 정지·줌 변경·편집 시 재생성. 호버 하이라이트는 캐시에서 제외하고 `draw.ts`가 매 프레임 위에 덧그림(스타일 오염 방지). 그리드 표시 중엔 블리팅 생략(그리드는 화면 고정이라 밀리면 안 됨). drawController에 `withContext`/`blitImage` 메서드 추가
|
||||
- [x] 호버 하이라이트 계산(main.tsx findClosestEntity)을 마우스 이동 시 + 30ms 스로틀로 제한
|
||||
- [x] 계측 훅: `window.__aisloCadPerf` = { sceneRebuilds, lastRebuildMs, avgFrameMs(EMA) }
|
||||
- [x] `npm run check-types` 통과, biome lint 통과, `npm run build`(dist 재빌드) 성공
|
||||
- [x] vitest: 6 실패는 변경 전 베이스라인(git stash)과 동일 — 기존 문제(node 환경 window 미정의 5건, arc 기하 1건), 이번 변경과 무관 확인
|
||||
- [ ] 실화면 검증(사용자): 줌아웃 좌클릭 팬 부드러움, 줌/그리기/이동/선택/스냅/텍스트편집/저장 회귀 — z순서 변화 주의점: 각도 가이드·고스트 미리보기가 엔티티 위에 그려짐(기존엔 아래)
|
||||
- **완료 조건**: 팬 중 장면 재렌더 0회(블리팅만), 9k 세그먼트 줌아웃 팬 55fps 이상
|
||||
|
||||
|
||||
### 2단계: 배칭·자료구조 — 상시 렌더 비용 절감 — 2026-07-26 구현 완료, 실화면 검증 대기
|
||||
대상: `helpers/draw-functions.ts`, `state.ts`, `drawControllers/screenCanvas.drawController.ts`
|
||||
- [x] layerId→layer Map 캐시(`getLayerById`) — drawEntities의 `getLayers().find()` 선형 탐색 제거. LayerManager 토글은 `setLayers` 경유라 Map·버전 동기화 확인됨
|
||||
- [x] highlighted/selected 배열 → Set 병행(`isEntitySelected/isEntityHighlighted`가 Set.has O(1), 기존 배열 API는 유지)
|
||||
- [x] 스타일 런 Path2D 배칭: drawController에 `beginBatch/endBatch/flushBatch` — 같은 스타일(색·굵기·대시·하이라이트·선택 반영) 연속 stroke를 Path2D 1개로 모아 1회 stroke. 세그먼트당 라운드 엔드포인트 fill 2회 → `lineCap/lineJoin: round`로 대체. 텍스트/이미지/채움은 순서 보존 위해 flush 후 즉시 렌더. scene-cache 리빌드에만 적용(동적 오버레이는 기존 경로)
|
||||
- [x] PolyLine 자식 중복 setLineStyles → 배칭 내 스타일 키 비교로 무효화(플러시 없이 통과)
|
||||
- **완료 조건**: 정적 장면 재생성 9k 세그먼트 기준 50ms 이하 — `window.__aisloCadPerf.lastRebuildMs`로 확인
|
||||
|
||||
|
||||
### 3단계: 공간 인덱스·컬링·LOD — 대형 도면 대비 — 2026-07-26 구현 완료, 실화면 검증 대기
|
||||
대상: 신규 `helpers/spatial-index.ts`, `calculate-angle-guides-and-snap-points.ts`, `scene-cache.ts`, `main.tsx`
|
||||
- [x] 균일 그리드(64×64) 공간 인덱스: 최상위 엔티티 bbox 기준, 장면 버전 변경 시 지연 재구축(`ensureIndex`), 질의 결과는 원본 배열 순서 유지(z순서 보존). bbox 계산 불가 엔티티는 항상 포함 리스트로 안전 처리
|
||||
- [x] 뷰포트 컬링: scene 리빌드 시 화면 ±1뷰포트(3×3) 교차 엔티티만 렌더 — 짧은 팬은 블리팅 범위 내 커버
|
||||
- [x] LOD: 배칭 경로에서 체인 내 0.5px 미만 세그먼트 데시메이션(모양 보존, 고립 세그먼트는 유지), 반지름 0.5px 미만 원·호 skip, 2px 미만 텍스트 skip (모두 scene 렌더 한정, 오버레이 비적용)
|
||||
- [x] 호버(`main.tsx`)·스냅(`calculate-angle-guides-and-snap-points.ts`) 후보를 인덱스 반경 질의로 대체 — 스냅의 전 엔티티 쌍별 교차점 O(n²) 계산이 마우스 주변 후보 O(k²)로 축소
|
||||
- [x] 스냅 계산에서 잠금 레이어 엔티티 기본 제외(b07-frame·b07-ground 등) — **주의: 잠금 상태 지반선에도 스냅이 안 잡히게 됨. 실무상 지반선 스냅 필요하면 레이어 잠금 해제 또는 예외 규칙 추가 결정 필요**
|
||||
- **완료 조건**: 대형 도면 팬/줌 55fps 이상, 호버 계산 2ms 이하
|
||||
|
||||
|
||||
### 4단계: WebGL 렌더러 전환 (별도 마일스톤 — 향후 작업, 1~3단계 후 필요성 재평가)
|
||||
- 폴리라인 셰이더 + GPU 정점버퍼 상주, 팬/줌은 변환행렬 uniform만 갱신. 자체 구현 vs pixi.js 검토. 프로젝트 스택의 "WebGL WebCAD" 방향과 일치.
|
||||
|
||||
**1~3단계 통합 검증 기록 (2026-07-26)**: check-types 통과, biome lint 통과, vitest 72 passed/6 failed(6건은 변경 전 베이스라인과 동일한 기존 실패 — node 환경 window 미정의 5건·arc 기하 1건, 회귀 아님), `npm run build` 성공. FastAPI 서버 기동 후 `/`(200)·`/b07-cad/`(200)·신규 번들 `assets/index-BkXcwQBt.js`(200) 서빙 확인. 실화면 팬/줌 체감 및 `__aisloCadPerf` 수치는 사용자 확인 대기.
|
||||
@@ -0,0 +1,56 @@
|
||||
# 수치지형도 도엽 오버레이 — 2D 배경지도/GIS 레이어 컨테이너 확장 (2026-07-26 협의)
|
||||
|
||||
**배경**
|
||||
- 2D 배경지도/GIS 레이어 컨테이너(B04_wf1_Surface_UI_MapViewer.ts)에 국가 등고선 오버레이는 완료됨(전국 gpkg 크롭 방식).
|
||||
- 수치지도 v2.0 도엽본(1:5,000)에는 등고선 외 하천중심선(세류)·표고점·성/절토·산산맥 등 29종 레이어가 있음을 실측 확인. 브이월드 데이터센터에서 도엽 zip(예: 36906042.zip)을 다운로드 버튼으로 받을 수 있음.
|
||||
- 목표: gpkg 등고선 오버레이 + 도엽 zip 기반 추가 레이어 오버레이를 같은 컨테이너에서 제공.
|
||||
|
||||
**확정된 사실 (2026-07-26 실측)**
|
||||
| 항목 | 내용 |
|
||||
|---|---|
|
||||
| 등고선 소스 | 전국 gpkg 재생성 완료 (resources/grobal_contours/national_contours.gpkg, 21.57GB, 1,884,844건, 속성 9개 보존, R-tree+속성 인덱스, 사업지 크롭 0.32초) |
|
||||
| 등고 간격 | 주곡선 5m / 계곡선 25m. 간곡선·조곡선 전국 0건 → 1m는 크롭 후 국소 보간으로만 생성 |
|
||||
| 볼록지/오목지 | TPGRPH_SE 전용 컬럼. 오목지=폐합 저지(웅덩이), 폐합률 99.4%. 계곡 아님 |
|
||||
| 도엽본 스키마 | 한글 컬럼(구분/등고수치/명칭), EPSG:5187. 연속수치지형도(영문 DIVI/CONT/SCLS)와 다름 — 파서는 양쪽 자동 판별 필요 |
|
||||
| 도엽 등고선 | 전국 gpkg와 지오메트리 완전 일치(최근접 0.000m). ~~도엽에서 등고선 무시~~ → **정정(2026-07-26 사용자 지시): gpkg 등고선과 별개로 도엽 등고선(N3L_F0010000)도 독립 오버레이로 제공** (도엽_등고선 레이어·토글 구현 완료, 9매 병합 3,413건) |
|
||||
| 하천중심선(E0020000) | "비 왔을 때 물흐름" = 세류. 도엽 표본 2곳에서 세류가 압도적(88%, 5~6천 m/km2). CAD 파란선의 정체 |
|
||||
| 능선 | 국가 데이터에 실존 안 함(G0037216 정의만 있고 표본 2지역 0건). LAS 산출만 가능(별도 운영, 국가 데이터와 혼용 금지) |
|
||||
| 브이월드 다운로드 | 데이터센터 버튼 방식. 공식 API 없음 → 자동화는 URL 패턴 조사 필요, 실패 시 수동 다운로드+업로드 |
|
||||
| 배경지도 API | 국토정보맵 WMTS 키 유효(SDK 정상). NGII 검색API는 키 권한 없음(1002) → VWorld 지오코더로 대체 가능(실테스트 통과) |
|
||||
|
||||
**아키텍처 결정 (사용자 확정)**
|
||||
1. 등고선 = 전국 gpkg 유지(연 1회 수동 갱신). 그 외 레이어 = 1:5,000 수치지형도 도엽 zip 단위로 취득.
|
||||
2. 국가 데이터는 참고·표지용 전용. 설계 계산은 LAS 계열만 사용. 두 계열 혼용 절대 금지(프로젝트 폴더 reference/ 분리 권장).
|
||||
3. 크롭·1m 보간은 프로젝트 파일입력 후 전처리 단계에서 수행, 결과는 프로젝트 영구저장소에 저장. 이후 gpkg/도엽 원본은 다시 읽지 않음.
|
||||
4. 대상 컨테이너 = **B04 페이지 하단** 2D 배경지도/GIS 레이어 컨테이너로 확정 (2026-07-26).
|
||||
5. **도엽 원본 = 프로젝트 영구저장소** (2026-07-26 사용자 2회 정정 최종): 브이월드에서 받은 도엽 zip과 메타정보는 `storage/{회사}/{사용자}/{프로젝트ID}/B04_wf1_Surface/processed/map_sheets/`에 보관 — 위성지도·GIS 벡터 등 주변데이터와 동일한 프로젝트 processed 계층. **`resources/`는 프로젝트 공통정보 폴더로 개별 프로젝트 영구저장소가 아님(사용 금지).** 파일명 `{도엽번호}.zip` + `map_sheets_index.json`(취득일, 원본명, 도곽 범위, 교정 CRS). 같은 프로젝트 내 재다운로드 없음.
|
||||
|
||||
**처리 순서 (사용자 확정 흐름, 2026-07-26 갱신)**
|
||||
```
|
||||
B03 페이지: 사용자 파일 입력 + 업로드 버튼
|
||||
→ 업로드된 파일 기준 전처리 분석 진행 (기존 WF1 파이프라인)
|
||||
→ 전처리 중 파일 좌표(PRJ+bounds) 기준 1:5,000 도엽번호 산출 (중심+주변 8 = 3×3, 9매)
|
||||
→ 9매 자동 다운로드 (브이월드 지도서비스, 세션 만료 시 자동 재로그인)
|
||||
→ 프로젝트 영구저장소 저장 ({프로젝트}/B04_wf1_Surface/processed/map_sheets/)
|
||||
→ 전처리 완료 시 B04 페이지로 전환
|
||||
→ B04 하단 2D 배경지도/GIS 레이어 컨테이너에 기존 오버레이(gpkg 등고선·위성지도 등)와 함께
|
||||
도엽 기반 레이어(9매 병합)를 하나의 오버레이로 표시
|
||||
```
|
||||
|
||||
**구현 체크리스트 (승인 후 착수)**
|
||||
- [x] 1. **좌표→도엽번호 산출 검증 (최우선 선행)** — `B04_wf1_Surface_Engine_MapSheet.py` 구현 완료(2026-07-26). 보유 도엽 10매 실측 대조 10/10 정합(해안 도엽은 바다 쪽 도곽 잘림 감안해 "실측⊆예측 도곽" 기준). 3×3 인접 산출 및 구획 경계 이월(위도 1° 구획·1:50,000 경계) 검증 통과. 검증 스크립트: `scratch/test_mapsheet_verification.py`
|
||||
- [x] 2. 도엽 확보 경로 조사 — 결론(2026-07-26, 2차 정정): ① **도엽검색은 브이월드 지도서비스 내부 API로 완전 자동화 가능(무로그인)**: `GET map.vworld.kr/ws3dmap/getDisitalMapList.do?type=M02&searchNum={도엽번호}&scale=5000&cod=1&version={1|2|3}` → ds_sq/file_no/파일명/크기/갱신일 JSON. Ver2.0=zip(SHP), Ver1.0=dxf(zip 아님). 3×3 도엽 9매 조회 실테스트 통과. ② **다운로드는 로그인 세션 필요하나 완전 자동화 확정(2026-07-26 사용자 승인)**: 지도서비스 로그인은 SSO 아닌 단순 POST(`/map/im_usrlogin_a004.do`, Base64 id/pw, 캡차 없음 실측) → `vworld_login()` 구현. `download_sheet()`는 쿠키(인자>프로세스캐시>.env)로 시도 후 세션 만료 시 **id/pw 자동 재로그인 1회 재시도** — 다른 사용자가 언제 요청해도 무인 동작. 자격증명은 `.env` `VWORLD_LOGIN_ID/PW`. 실검증: 실쿠키로 8매 + 무쿠키 자동로그인으로 2매(36906034·044) 다운로드→도곽검증→등록 성공. 폴백 = 수동 다운로드 + Ingest. ③ 데이터센터(dsId=30205)의 시군구 단위 zip(2MAP5000_SHP_*)은 다른 배포본으로 대상 아님. ④ 영구저장소 `B04_wf1_Surface_Engine_SheetStore.py` 구현(2026-07-26 프로젝트 저장소 기준으로 리팩토링 완료): 저장 위치 `{프로젝트}/B04_wf1_Surface/processed/map_sheets/{도엽번호}.zip` + `map_sheets_index.json`, `ensure_sheets(store_dir, 도엽목록)`가 미보유분 자동 다운로드. 인제스트 시 도곽 실측 검증 및 **PRJ 오라벨 CRS 자동 교정**(구 36902002.zip 경도 2° 오프셋 사례 실측·교정 확인). 테스트 프로젝트에 20매 확보, 보유 9매 즉시 반환 + 신규 1매 자동 다운로드 검증. 테스트: `scratch/test_sheetstore_ingest.py`
|
||||
- [x] 3. **B03 전처리 훅 통합** — 구현 완료(2026-07-26): `B04_wf1_Surface_Engine.py` 3-3 단계 추가(주변데이터 다운로드 3-2 직후, progress 92%). PRJ EPSG 판별→bounds 중심 위경도→3×3 도엽번호→`ensure_sheets` 자동 확보, 실패는 경고 로깅만(분석 계속). 실프로젝트 검증: 중심 37816093, **위도 1° 구획 이월(37816↔36804) 포함 9매 전량 자동 다운로드·도곽검증·등록 성공**
|
||||
- [x] 4. 도엽 zip 파서 모듈 — `B04_wf1_Surface_Engine_SheetParser.py` 구현 완료(2026-07-26). 레이어 코드 실측 확정: 하천중심선 N3L_E0020000(구분 세류/소하천/지방하천), 표고점 N3P_F0020000, 성절토 N3L_F0030000, 옹벽석축 N3L_F0040000, 유수방향 N3P_E0042326, 기준점 N3P_H0020000, 지명 N3P_H0040000. 좌표는 인덱스 교정 `crs`→WGS84. **벼랑바위·너덜바위·산산맥은 표본 도엽 2매에 미출현 — 코드 확정 시 SHEET_LAYERS에 추가(보류)**
|
||||
- [x] 5. 9매 병합 모듈 — `parse_and_merge_sheets()`: 레이어별 concat + UFID dedup(없으면 WKB), `processed/도엽_{레이어}.geojson` 저장. Engine 3-3 훅에서 도엽 확보 직후 자동 실행. 실검증: 실프로젝트 9매 병합 → 하천중심선 1,558·표고점 1,529·성절토 255·옹벽석축 335·유수방향 29·기준점 11·지명 153건
|
||||
- [x] 6. 라우터 — `_SHEET_GEOJSON_FILES`(SHEET_LAYERS 파생, 정의 일원화)를 `/geojson?layer=` 및 MVT 타일 매핑에 추가(B04_wf1_Surface_Router_GIS.py). `도엽_하천중심선` 등 7개 레이어 확인
|
||||
- [x] 7. 프론트 — B04 MapViewer GIS_LAYERS에 도엽_하천중심선(파란 #2563eb)·표고점·성절토·옹벽석축·유수방향 토글 5종 추가, Point/MultiPoint 렌더링(원형 마커) 신설, 로케일 5키 추가. typecheck 통과. 건천/세류 파선·실선 구분은 미구현(후속 — 렌더러가 feature properties 미전달 구조)
|
||||
- [ ] 8. 세류-등고선 겹침 검증: 사업지 도엽으로 파란선이 CAD 참고 이미지 수준으로 나오는지 실화면 확인 (사용자/검증자)
|
||||
- [x] 9. 오버레이 정리(2026-07-26 사용자 지시): ① **수계망 제거** — VWorld 다운로드·라우터 매핑·프론트 토글 삭제(도엽 하천중심선으로 대체). ② **산사태 제거 확정** — 조사 결과 오픈 API(WMS/WFS)에는 시군구 예보등급(lt_c_kfdrssigugrade)뿐, 산림청 FGIS 상세 위험지도는 API 미제공(산림청 공식: 2028년 예정), 지도서비스 위험지도는 래스터 타일이라 벡터 취득 불가 → "자세히 볼 수 없으면 의미 없음"(사용자)으로 다운로드·라우터·프론트 전부 삭제. 2028년 API 개방 시 재검토. ③ 등고선 #fdba74·도엽 등고선 #a5b4fc·표고점 #f9a8d4 파스텔 변경. ④ **등고 라벨 토글** — gpkg(CTRLN_HG)·도엽(등고수치) 속성 기반, 계곡선(25m 배수)만 라벨, 기본 숨김. gpkg 크롭이 속성 미보존(구버전 산출물+Timestamp 직렬화 문제)이던 것을 필요 속성 3종만 유지하도록 수정하고 크롭·단순화본 재생성 완료
|
||||
|
||||
**미결사항 (다음 세션에서 확정)**
|
||||
1. ~~좌표→도엽번호 알고리즘 정합 결과~~ → 해결(2026-07-26): 10/10 정합, 체크리스트 1 참조
|
||||
2. ~~도엽 취득 자동화 가능 여부~~ → 해결(2026-07-26 최종): **완전 자동화 확정** — 검색 무로그인 + 다운로드는 id/pw 자동 재로그인(체크리스트 2 참조). 파서(체크리스트 4)는 내부 PRJ 대신 `map_sheets_index.json`의 `crs` 필드를 사용할 것(오라벨 교정값)
|
||||
3. 사용자 좌표 입력 UI(B02 주소+핀): NGII 검색API 권한 신청 진행 여부, VWorld 지오코더 대체 여부
|
||||
4. ~~국가 데이터 저장 위치~~ → 해결(2026-07-26): 주변데이터와 동일한 프로젝트 영구저장소 `B04_wf1_Surface/processed/` 사용 확정(도엽은 하위 `map_sheets/`). `resources/`는 프로젝트 공통정보 폴더로 개별 프로젝트 데이터 저장 금지(사용자 2회 정정)
|
||||
5. 크롭 범위(gpkg 등고선용): LAS bounds+고정 여유폭(자동) vs 범위 지정 UI(수동)
|
||||
@@ -0,0 +1,46 @@
|
||||
# [완료 계획서] B04 2D 지도 성능 개선 및 B05 배수유역도 구축
|
||||
|
||||
- **완료 일시**: 2026-07-28
|
||||
- **이관 일시**: 2026-07-28
|
||||
- **검증 문서**: `file:///D:/02_Software_Prog/%EC%9E%84%EB%8F%84%EC%84%A4%EA%B3%84%20%EB%B0%8F%20%EA%B2%AC%EC%A0%81%EC%9E%90%EB%8F%99%ED%99%94%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%20%EA%B0%9C%EB%B0%9C/docs/raw/verification/2026-07-28_verify_B04_B05_Drainage_Watershed.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. B04 2D 지도 성능 개선 건 (완료)
|
||||
- [X] B04 2D 지도 성능 개선 건 (사전 투영+rAF+컬링+LOD, 커서 중심 줌, 중간 버튼 간섭 차단) — 사용자 육안 확인 완료. 대상: `B04_wf1_Surface_UI_MapRender.ts`(신규, 423줄), `B04_wf1_Surface_UI_MapViewer.ts`(438줄)
|
||||
|
||||
---
|
||||
|
||||
## 2. B05 배수유역도 구축 건 (완료)
|
||||
|
||||
### A단계 — 선행 개선·버그 수정 (완료)
|
||||
- [X] A-1. 하단 사이드 패널 오버레이 전환: `.b05-route-profile`을 `flex: 0 0 60dvh` → `position: absolute; bottom: 0; height: 60dvh`로 변경, 접힘은 `height: 0` 전환. `.b05-route__main`에 `position: relative` 추가. 3D 뷰포트 크기 불변.
|
||||
- [X] A-2. 그래프 검정 버그 원인·수정: SVG 차트 색상의 정의처 `B06_wf3_ProfileCross_UI_Style_Cross.css` import를 `B05_wf2_Route_UI_Profile_Panel.ts`에 추가.
|
||||
- [X] A-3. 구조물 측점 표기 겹침: 하이라이트 셀 겹침 가림(`hideCoveredCells`) 및 `is-covered`/`is-floating` 스타일 처리.
|
||||
|
||||
### B단계 — 2단 사이드 패널 + 배경지도 (완료)
|
||||
- [X] B-1. 하단 패널 내부 우측 사이드 패널 신설: `B05_wf2_Route_UI_Drainage_Panel.ts`(437줄) 신규. `createWorkflowPanelHandle('side')` 재사용, 좌측 핸들로 접기.
|
||||
- [X] B-2. 패널 내부 2D 배경지도: `B04_wf1_Surface_UI_MapRender` 재사용해 도엽_등고선·도엽_하천중심선·도엽_표고점 3개 레이어 + VWorld 위성 배경 표시.
|
||||
- [X] B-3. 노선 평면 선형 오버레이: `prepareMetricPolyline()`로 사업지 좌표계(m) 노선을 정규화 공간에 투영 오버레이 및 범위 맞춰 자동 뷰 맞춤.
|
||||
|
||||
### C단계 — 배수유역 산정 엔진 (완료)
|
||||
- [X] C-1. 백엔드 측점 자동 제안 API: `GET /api/projects/{id}/drainage/candidates`. `B05_wf2_Route_Engine_Drainage.py` — 노선×세류 교차 및 300m 초과 구간 절토부 보충.
|
||||
- [X] C-2. 프론트 확정 UI: `POST /drainage/basins`.
|
||||
- [X] C-3. 백엔드 유역 경계 산정: 등고선 최내곽 폐합 패턴 기준 정상부 판정.
|
||||
- [X] C-4. 프론트 유역 오버레이: `drawFilledRing()` 파스텔 8색 채움 + 번호 서클.
|
||||
- [X] C-5. 유역 제원 산출·표기: 유역면적(㎡/ha), 유역표고, 유하거리 백엔드 산출 및 패널 목록 표기.
|
||||
- [X] C-6. 배수 규격 산정 함수 골격: `estimate_pipe_diameter_mm` 시그니처 및 TODO 정의.
|
||||
|
||||
### C'단계 — 유역 산정 능선(분수령) 기반 재구현 (완료)
|
||||
- [X] C'-1. 도엽 DEM 보간: scipy `griddata` 10m 격자 DEM 생성. `B05_wf2_Route_Engine_Drainage_Watershed.py`(545줄).
|
||||
- [X] C'-2. 흐름 분석: Whitebox `fill_depressions` 함몰 보정 및 D8 흐름 방향/누적.
|
||||
- [X] C'-3. 유역 추출: pour point 기준 유역 추출 및 분수령(능선) 경계 형성.
|
||||
- [X] C'-4. 제원 재계산: 면적, 유역표고, 유하거리 정밀 산출.
|
||||
- [X] C'-5. 능선 표시: 유역 경계 외곽선 능선 스타일(갈색 파선) 강조 및 토글.
|
||||
- [X] C'-6. watershed 엔진 교체 적용.
|
||||
|
||||
### C''단계 — 도로(노선) 기준 측구 흐름 모델 (완료)
|
||||
- [X] 1. 측구 흐름 모델: 종단 고개 기준 도로 셀 배정 및 사면 D8 하강 연결.
|
||||
- [X] 2. 유역 연속 분할: 도로 산측 사면 관 개수만큼 연속 분할, 도로 미만남 셀 제외.
|
||||
- [X] 3. 합성 검증.
|
||||
- [X] 4. **계획선 = 유역 하측 경계**: 계획선 single-sided 버퍼 difference로 clip (`_clip_to_plan_line`).
|
||||
@@ -0,0 +1,52 @@
|
||||
# [완료 계획서] B04 하단 2D 지도 — 도엽 등고선 렌더링 성능 개선
|
||||
|
||||
- **완료 일시**: 2026-07-28
|
||||
- **이관 일시**: 2026-07-28
|
||||
- **검증 문서**: `file:///D:/02_Software_Prog/%EC%9E%84%EB%8F%84%EC%84%A4%EA%B3%84%20%EB%B0%8F%20%EA%B2%AC%EC%A0%81%EC%9E%90%EB%8F%99%ED%99%94%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%20%EA%B0%9C%EB%B0%9C/docs/raw/verification/2026-07-28_verify_B04_Map_Performance.md`
|
||||
|
||||
---
|
||||
|
||||
### B04 하단 2D 지도 — 도엽 등고선 렌더링 성능 개선 (2026-07-28 협의)
|
||||
|
||||
**배경**
|
||||
- B04 하단 2D 지도(`B04_wf1_Surface_UI_MapViewer.ts`)에서 도엽 등고선 켜면 팬/줌 버벅임 심각.
|
||||
- 사용자 조건: ① 화면 해상도 저하 절대 불가(해상도 민감), ② 등고선·세류 원본 데이터는 차기 배수유역도 기능에서 사용 예정이므로 **원본 GeoJSON·DB 무가공** — 모든 최적화는 프론트 렌더링 계층에서만.
|
||||
- 진행 방식: 단계별 적용 → 각 단계마다 사용자 육안 확인 후 다음 단계 진행. B04 먼저, 결과 보고 타 페이지 확산 검토.
|
||||
|
||||
**원인 진단 (코드 확인 완료)**
|
||||
| 문제 | 위치 | 내용 |
|
||||
|---|---|---|
|
||||
| 정점당 DOMRect 생성 | `B04_wf1_Surface_UI_MapViewer.ts:265-283` | `toCanvasPoint`가 정점마다 `getMapRect()` 호출, 매 프레임 정점 수만큼 객체 할당 |
|
||||
| 무스로틀 전체 재드로잉 | `B04_wf1_Surface_UI_MapViewer.ts:506-524` | wheel/pointermove 이벤트마다 전 레이어 전 정점 재드로잉, rAF 없음 |
|
||||
| 캔버스 버퍼 재할당 | `B04_wf1_Surface_UI_MapViewer.ts:430-441` | `drawVectorLayer` 호출마다 canvas.width/height 재설정 |
|
||||
| 컬링 없음 | `drawVectorLayer` 전체 | 화면 밖 피처도 전부 그림 |
|
||||
| 사전 투영 없음 | `drawRing`/`drawPoint` | 매 프레임 lon/lat→픽셀 변환 반복, GeoJSON 중첩 배열 순회 |
|
||||
|
||||
**단계 1 — 무손실 최적화 (렌더 결과 픽셀 단위 동일 보장)**
|
||||
- [X] 1-1. 좌표 사전 투영: GeoJSON 로드 시 1회만 lon/lat→**정규화 맵 좌표(0~1, Float64Array)**로 변환해 캐시. 매 프레임엔 뷰포트·scale·offset 합성 어파인만 적용. 피처별 bbox도 이때 사전 계산. (계획 대비 변경: 기준 픽셀 좌표 대신 정규화 좌표 채택 — 리사이즈 시 재투영 불필요)
|
||||
- [X] 1-2. `toCanvasPoint`의 정점당 `getMapRect()` 제거: 프레임당 1회 어파인 계수(`affineOf`) 계산 후 재사용. 원 변환식과 대수적으로 동일하므로 렌더 결과 불변.
|
||||
- [X] 1-3. rAF 스로틀: wheel/pointermove/resize는 `scheduleDraw()`로 상태만 갱신, 프레임당 1회만 드로잉. `dispose` 시 대기 프레임 취소.
|
||||
- [X] 1-4. 캔버스 버퍼 재할당은 width/height/DPR이 실제로 변할 때만 수행.
|
||||
- [X] 1-5. 뷰포트 컬링: 사전 계산된 피처 bbox로 화면 밖 피처 스킵 (라벨 앵커 포함, 마진 32px).
|
||||
- [X] 1-6. 렌더 로직 분리: `B04_wf1_Surface_UI_MapRender.ts`(신규, 359줄) 렌더 엔진 / `B04_wf1_Surface_UI_MapViewer.ts`(423줄) UI·이벤트. 둘 다 700줄 이내.
|
||||
- [X] 1-7. `npm run typecheck` 통과, `prettier` 포맷팅, `.claude/git-autopush.ps1` 실행.
|
||||
- **완료 조건**: 도엽 등고선 켠 상태 팬/줌 체감 개선, 렌더 결과 기존과 픽셀 단위 동일(해상도·선 굵기·라벨 위치 불변). 사용자 육안 확인.
|
||||
|
||||
**단계 2 — 비트맵 레이어 캐시 (구현 후 롤백, 2026-07-28 사용자 검증)**
|
||||
- [X] 2-1~2-2. 제스처 중 CSS transform + 오버스캔 + settle 재렌더 방식 구현했으나, 사용자 검증 결과 **롤백**: 팬 종료마다 재렌더가 체감되고, 오버스캔 범위 밖 데이터가 다시 채워질 때 딜레이 발생. 단계 3 LOD로 프레임 렌더 비용이 충분히 낮아져 매 프레임 직접 렌더(단계 1 방식)가 더 나은 것으로 확정 — 항상 완전한 화면 보장, 재생성 딜레이·가장자리 공백 원천 제거.
|
||||
- **결론**: 단계 1(rAF 직접 렌더) + 단계 3(LOD) 조합 채택. 비트맵 캐시 코드는 제거됨.
|
||||
|
||||
**단계 3 — 줌별 LOD + 곡선 스무딩 (2026-07-28 사용자 지시로 방향 확정: 줌아웃=경향, 줌인=디테일)**
|
||||
- [X] 3-1. 연속 LOD: 로드 시 파트별 Douglas-Peucker 가중치 1회 계산(반복형, 종횡비 보정), 렌더마다 현재 줌 기준 화면 오차 0.75px 미만 정점만 생략. 줌인할수록 자동으로 원본 정점 복원. (계획 당시 "기본 꺼짐+토글" 의도였으나 사용자가 줌아웃 시 축소를 직접 지시하여 기본 적용으로 변경)
|
||||
- [X] 3-2. ~~줌인 곡선 스무딩(quadratic 스플라인)~~ — 구현 후 **원복** (2026-07-28 사용자 지시: "원래 데이터가 그런 거라면 보간 불필요"). 원본 정점 형상 그대로 표시.
|
||||
- [X] 3-3. ~~순수 팬 settle 재렌더 생략~~ — 단계 2 롤백과 함께 제거 (매 프레임 직접 렌더 구조에선 불필요).
|
||||
- **완료 조건**: 사용자 검증 — LOD 렌더링 감소 확인(승인), 스무딩 불필요(원복), 최종 구조 = 단계 1 + LOD.
|
||||
|
||||
**추가 작업 (2026-07-28 사용자 지시)**
|
||||
- [X] 4-1. 줌인/아웃 중심을 뷰포트 중심 → 마우스 커서 위치로 변경. wheel 핸들러에서 커서 아래 지점이 고정되도록 offset 보정(`offset' = (cursor − center)·(1 − r) + offset·r`). 배경지도는 동일 offset/scale을 쓰는 CSS transform이라 자동 정합.
|
||||
- [X] 4-2. 지도 영역 중간 버튼 드래그 시 브라우저 기본 자동 스크롤이 페이지 스크롤과 간섭 — pointerdown에서 `button === 1`이면 `preventDefault()`로 기본 동작 차단, 지도 팬 전용으로 사용.
|
||||
|
||||
**제약**
|
||||
- 원본 GeoJSON·백엔드·DB 절대 무가공 (배수유역도 대비).
|
||||
- 해상도 저하 감지 시 해당 단계 롤백.
|
||||
- Surgical Edit — MapViewer 외 파일 수정 금지 (신규 MapRender 파일 제외).
|
||||
@@ -0,0 +1,41 @@
|
||||
# B03 계획노선 CSV 필수 입력 완결 계획서
|
||||
|
||||
- **완료 일자**: 2026-07-31
|
||||
- **이관 보관 경로**: docs/raw/plans/2026-07-31_plan_B03_planned_route_csv.md`n
|
||||
---
|
||||
|
||||
## B03 계획노선 CSV 필수 입력 (2026-07-31)
|
||||
|
||||
**배경**
|
||||
- 원청 계획노선을 B03 파일입력 단계의 필수 자료로 등록한다.
|
||||
- 테스트 자료는 storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/csv/planned_route_sample_epsg5187.csv를 사용한다.
|
||||
- B05 원청 계획노선/최적경로 이중 오버레이는 후속 작업으로 분리한다.
|
||||
|
||||
**확정 범위**
|
||||
- B03에서 계획노선 CSV와 기존 지형자료를 구분해 입력받는다.
|
||||
- 계획노선 누락 또는 검증 실패 시 업로드 완료와 B04 이동을 차단한다.
|
||||
- 필수 열은
oute_name, sequence, x, y, z, crs_epsg로 고정한다.
|
||||
- sequence는 1부터 연속, x/y/z는 유한한 수, crs_epsg는 모든 행에서 동일한 양의 정수여야 한다.
|
||||
- 정상 CSV 원본은 B03_FileInput/input/csv/에 보존하고 DB 메타데이터에 계획노선 용도를 기록한다.
|
||||
- 이번 작업은 CSV만 지원하며 DXF/SHP/LandXML은 후속 작업이다.
|
||||
|
||||
**대상 파일**
|
||||
- B03_FileInput/B03_FileInput_UI_Page.ts`n- B03_FileInput/B03_FileInput_UI_Support.ts`n- B03_FileInput/B03_FileInput_Api_Fetch.ts`n- B03_FileInput/B03_FileInput_Router.py`n- B03_FileInput/B03_FileInput_Engine.py`n- B03_FileInput/B03_FileInput_Repository.py`n- 관련 B03 프론트엔드/백엔드 테스트
|
||||
|
||||
**구현 체크리스트**
|
||||
- [x] 현재 B03 업로드·상태 복원·B04 이동 흐름과 테스트 구조 확인
|
||||
- [x] 계획노선 CSV 전용 필수 입력 UI 및 오류 안내 추가
|
||||
- [x] 프론트 업로드 전 필수 파일·확장자 검증
|
||||
- [x] 백엔드 CSV 헤더·행 값·순번·EPSG 검증
|
||||
- [x] 정상 CSV 원본 저장 및 입력 파일 메타데이터 등록
|
||||
- [x] CSV 누락/오류 시 B03 완료와 B04 이동 차단
|
||||
- [x] 기존 LAS/TIF/TFW/PRJ/DXF 업로드 코드 경로 회귀 확인
|
||||
- [x] 135개 좌표 샘플의 실제 API·DB 업로드 통합 검증 (공유 실행환경 안정 후 수행)
|
||||
- [x]
uff format/
uff check/prettier 통과, 전체 sc --noEmit은 다른 AI의 B04 미완성 파일 구문 오류 해소 후 재실행
|
||||
- [x] B03 단위 테스트 7개 통과 후 체크리스트 반영
|
||||
|
||||
**완료 조건**
|
||||
- 계획노선 CSV 없이는 B03 단계를 완료할 수 없다.
|
||||
- 제공된 샘플은 정상 처리되고 135개 좌표와 EPSG가 보존된다.
|
||||
- 잘못된 헤더, 비수치 좌표, 순번 오류, 혼합 EPSG는 명확한 오류로 거부된다.
|
||||
- 기존 지형자료 업로드 및 WF1/B04 이동 흐름에 회귀가 없다.
|
||||
@@ -0,0 +1,79 @@
|
||||
# B05 배수유역도 재설계 및 관 매설자리 표시·편집 완결 계획서
|
||||
|
||||
- **완료 일자**: 2026-07-31
|
||||
- **이관 보관 경로**: `docs/raw/plans/2026-07-31_plan_B05_drainage_watershed_pipe_edit.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. B05 배수유역도 재설계 — 세류 기반 등고선 기하 직접 분석 (2026-07-29)
|
||||
|
||||
**배경 / 문제점**
|
||||
- 현행 `B05_wf2_Route_Engine_Drainage_Watershed.py`는 노선 bbox + 1500m 전체(도엽 9매 범위)를 scipy DEM 보간 + Whitebox 함몰보정 + D8 전역 흐름분석으로 계산 — 비효율.
|
||||
- 순서도 반대: 사면 전체를 먼저 계산한 뒤 도로 셀로 유역을 잡음. 올바른 순서는 세류(하천중심선) 교차점에서 출발해 필요한 범위만 분석.
|
||||
|
||||
**합의된 계산 원리 (사용자 정의 7단계)**
|
||||
1. 도로(노선)가 배수유역의 하측 경계.
|
||||
2. 도로를 가로지르는 세류선 탐색(영역 최소화). 세류 없는 구간은 도로 주변 소범위만 등고선 추적(작은 유역, 영역 선정 주의).
|
||||
3. 세류 교차점에서 상류망 추적 + 연관 등고선만 분석하여 메인 배수유역 선정(하류 무의미).
|
||||
4. 세류 교차점 = 관매설 지점.
|
||||
5. 시점·관지점·종점 간 300m 초과 구간은 종단도상 물이 모일 것으로 예상되는 지점(절토부 내 종단 저점)에 보충 관지점 추가.
|
||||
6. 관 지점 배치 기준으로 메인 유역을 세분화, 번호·면적·표고차·유하장 계산 표기.
|
||||
7. 사용자 관 추가 / 경로 변경 시 재분석.
|
||||
|
||||
**기술 방식 (합의: DEM 미사용, 등고선 기하 직접 분석)**
|
||||
- 등고선 전체를 shapely `STRtree` 공간 인덱스에 1회 적재. 실제 기하 분석은 상류망 buffer에 걸리는 등고선만 (분석량 = 유역 크기에 비례, 도엽 매수와 무관).
|
||||
- 상류망 추적: 교차점에서 도로 산측 방향 세류 폴리라인 추적, endpoint 근접 분기 포함, 도로 재교차 시 절단.
|
||||
- 유역 경계: 상류망과 2회 교차하며 감싸는 등고선 아크(계곡 사면)를 표고 오름차순으로 수집 → 최상위 아크(발원부) + 좌·우 아크 끝점을 표고순 연결한 능선 폴리라인 + 도로선 절단 구간으로 폴리곤 폐합. 능선 밖·하류측·도로 반대편 등고선은 미조회.
|
||||
- 세분화: 관 사이 종단 최고점(물갈림 고개)에서 상향 등고선 최근접점을 연결한 최급상승 근사선을 유역 경계까지 그어 분할.
|
||||
- 산출값: 면적(폴리곤), 표고차(유역 최고 등고선 표고 − 관 위치 표고), 유하장(상류망 최장 경로 길이), 관경 추정(`estimate_pipe_diameter_mm` 재사용).
|
||||
- scipy griddata / Whitebox / rasterio 벡터화 의존 제거.
|
||||
|
||||
**대상 파일**
|
||||
| 파일 | 작업 |
|
||||
|---|---|
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Drainage_Watershed.py` | 전면 재작성: DEM/D8 제거, 메인 유역 + 세분화. `WatershedBasin` 반환 형태 유지(API 호환) |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Watershed_Trace.py` | **[신설]** 세류 상류망 추적 + STRtree 등고선 아크 추출 (700줄 제한 대비 분리) |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Drainage.py` | `_fill_spacing` 보충 지점 기준 변경: 절토부 임의 지점 → 절토부 내 종단 저점 우선 |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Router_Drainage.py` | 세류 features를 watershed 엔진에 전달하도록 시그니처 반영 |
|
||||
| `B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts` | 결과 표기(번호·면적·표고차·유하장) 및 재분석 트리거 동작 확인, 필요 시 보완 |
|
||||
|
||||
**구현 체크리스트**
|
||||
- [x] Watershed_Trace 신설: 상류망 추적(분기 포함, 도로 절단) + STRtree 아크 추출
|
||||
- [x] Watershed 엔진 재작성: 메인 유역 폴리곤 폐합(아크+능선+도로선) + 도로 산측 clip
|
||||
- [x] 세류 없는 구간: 도로 상측 첫 능선까지 소범위 유역 생성 (LOCAL 한계: 반경 80m·12단계)
|
||||
- [x] 관 지점 기준 세분화(물갈림 고개 최급상승 분할선) + 번호(시점 가까운 순)·면적·표고차·유하장·관경 산출
|
||||
- [x] `_fill_spacing` 종단 저점 우선 배치로 변경 — 국소 저점(사그)만 인정, 없으면 기존 300m 규칙 폴백
|
||||
- [x] Router 시그니처 반영(세류 features 전달) — confirmed 측점도 세류 15m 이내면 세류 유역으로 취급
|
||||
- [x] Drainage_Panel 표기·재분석 확인 — "유역 산정" 버튼이 후보+유역 전체 재계산, `fetchDrainageBasins`가 chainages 지원. 프론트 수정 불필요
|
||||
- [x] `ruff format` + lint 통과, 700줄 제한 준수(최대 335줄). 합성 지형 스모크 테스트 통과(사그 배치·300m 폴백·세류 상류망 유하장)
|
||||
|
||||
---
|
||||
|
||||
## 2. 관 매설자리 표시·편집 + 세부유역 분할 검증 (2026-07-30)
|
||||
|
||||
**배경 / 문제점 (2026-07-30 사용자 피드백 + 진단)**
|
||||
- 사용자 관측: 최종 결과물이 "세부배수유역"으로 출력되지만 실제로는 전체(메인) 배수유역과 일치. 도엽등고선 영역의 계획선 위에 배관 자리가 표시되어야 함.
|
||||
- 진단 결과:
|
||||
1. 백엔드 `build_watershed_basins`는 관 후보마다 유역 1개를 만드는 세분화 구조가 있으나(관 사이 중간점 divides 분할), 개선 2안 검증은 유역 1개(43.2ha) 수치만 확인. 실데이터에서 관이 1개만 잡혔는지, 능선 행진(`_assemble_march_polygon`)이 관마다 전체 계곡 rim을 그려 유역이 전부 겹치는지 미확인.
|
||||
2. 프론트 `B05_wf2_Route_UI_Drainage_Panel.ts`는 유역 폴리곤·목록만 렌더 — 계획선 위 배관 마커 없음(chainage는 tooltip 문자열).
|
||||
3. API 응답에 관 위치 좌표 미포함(`outlet_x/y`는 엔진에만 존재, 라우터가 미반환) — 프론트가 마커 찍을 좌표가 없음.
|
||||
- 합의 범위(사용자 확정): 배관 마커 **표시 + 편집(추가/삭제/이동) UI** 모두 이번 작업에 포함.
|
||||
- **불변 조건(2026-07-30 사용자 지시): 전체(메인) 배수유역 경계는 절대 변경 금지** — f72017a 채택 경계 그대로 유지. 세분화는 메인 유역 폴리곤을 **내부 분할선으로 쪼개는 방식**만 허용(외곽 재추적 금지). 회귀 검증: 세부유역 합집합 == 기존 메인 유역(허용오차 내 동일).
|
||||
|
||||
**대상 파일**
|
||||
| 파일 | 작업 |
|
||||
|---|---|
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Drainage_Watershed.py` | 세분화 보정: 관 N개 → 서로 겹치지 않는 유역 N개(합집합 = 메인 유역). 능선 행진이 divide 구간을 넘지 않도록 제한. |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Engine_Watershed_Trace.py` | 행진 시작·종료를 divide 분할선에 맞추는 파라미터 추가. |
|
||||
| `B05_wf2_Route/B05_wf2_Route_Router_Drainage.py` | basins 응답에 `outlet_lonlat` 추가. 후보 제안(`GET candidates`)과 확정 chainages(POST) 왕복 확인 |
|
||||
| `B05_wf2_Route/B05_wf2_Route_UI_Drainage_Panel.ts` | 배관 마커 렌더 + 편집 상태 관리 연결. |
|
||||
| `B05_wf2_Route/B05_wf2_Route_UI_Drainage_Pipes.ts` | 신설: 마커 렌더·히트판정·계획선 스냅 |
|
||||
| `B05_wf2_Route/B05_wf2_Route_UI_Style.css` | 마커·편집 UI 스타일 |
|
||||
|
||||
**구현 체크리스트**
|
||||
- [x] ① 실데이터 진단 — 원인 확정: 자동 후보가 **관 1개**(세류 교차 149.73m)뿐이라 유역 1개 = 전체 유역으로 표시됨. 세분화 코드는 있었으나 관 1개면 미동작. 노선 350m, 300m 규칙상 보충관도 없음.
|
||||
- [x] ② 백엔드 세분화 재설계 — `_main_watershed_polygon`(메인 1회 산정, f72017a 산식 그대로) + `subdivide_main_polygon`(신규 Subdivide 모듈: 분할선 절단 → 도로 접촉 길이로 구간 배정 → 잔여 조각 인접 재배정). 세분화 조각은 simplify 생략(공유 경계 어긋남 방지). **불변 검증 PASS: 관 1개 결과 기준선과 sym_diff 0.0000m², 지표 완전 동일. 3관 테스트: 겹침 0m², 메인 내부 조각 합집합 == 메인(오차 0.16%, simplify 표현차).** 메인 범위 밖 관은 소범위 유역 별도 생성(기존 합의 ② 동작).
|
||||
- [x] ③ API — basins에 `outlet_lonlat` 추가 + 응답에 `pipes` 목록(실사용 관, 유역 없는 관 포함) 추가. 핸들러 직접 호출 검증 PASS(자동 1관 기준선 동일 / 확정 3관 왕복).
|
||||
- [x] ④ 프론트 표시 — `B05_wf2_Route_UI_Drainage_Pipes.ts` 신설(마커 렌더·히트판정·계획선 스냅, chainage 단일 소스). 마커 번호·유역 파스텔색 매칭, 마커 클릭 ↔ 유역 목록 선택 동기화.
|
||||
- [x] ⑤ 프론트 편집 — "배관 편집" 토글: 계획선 클릭 추가·마커 드래그 이동(계획선 스냅)·"선택 삭제". "자동 제안" 버튼으로 자동 배치 복귀. "유역 산정"이 편집된 chainages를 POST로 전송.
|
||||
- [x] ⑥ 검증 — `ruff format`/`ruff check`/`prettier`/`tsc --noEmit` 통과. 700줄 준수(엔진 572, Trace 657, Assemble 205, Subdivide 180, Router 238, Panel 530, Pipes 246).
|
||||
@@ -0,0 +1,158 @@
|
||||
# B04/B05 배수유역 UI 개선 9건 및 성능·조작감 보정 (완료 계획서)
|
||||
|
||||
> 이관 이력: 2026-08-01 검증 완료 후 `docs/raw/PLAN.md` 1부에서 `docs/raw/plans/2026-08-01_plan_B04_B05_drainage_ui_improvements.md`로 이관 백업됨.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-01_verify_B04_B05_drainage_ui_improvements.md`
|
||||
|
||||
---
|
||||
|
||||
### 요구사항 -> 단계 매핑
|
||||
|
||||
| 번호 | 요구사항 | 단계 |
|
||||
|---|---|---|
|
||||
| 1 | B05 진입 시 완료 순 점진 표시 + 3D 프로그레스 서클 | S3-1 |
|
||||
| 2 | B04/B05 3D 회전 중심을 마우스 커서 지점으로 | S1-2 |
|
||||
| 3 | B05 흐름 화살표 토글 버튼을 제목 우측(등고선·세류 옆)으로 이동 | S3-2 |
|
||||
| 4 | B05 등고선/세류/화살표 토글 버튼 공통 양식 통일 (크기는 현행 유지) | S3-2 |
|
||||
| 5 | B05 유역 내부 상류 세류선 하이라이트 (B04 코드 재활용) | S3-3 |
|
||||
| 6 | B05 배수유역 데이터 B04 복사본을 B05 전용 영구저장소로 분리 | S2-1 |
|
||||
| 7 | B04 지도 "유역방향"·"1차 유역" 버튼 기본값 hide | S2-2 |
|
||||
| 8 | 배수유역 안내/결과 메시지를 지도 위(오버레이, 배경색 포함)로 배치 — B04만 | S2-3 |
|
||||
| 9 | B05 3D 안내문구를 우상단 오버레이 텍스트(회색)로 | S3-4 |
|
||||
| 6-확장 | B05 유역 외곽선 20m 포인트 드래그 편집 + 확정 시 모달 저장 | S3-5 |
|
||||
|
||||
---
|
||||
|
||||
### S1. 공통 기반 (B04·B05 공유)
|
||||
|
||||
#### S1-1. 공통 프로그레스 서클 / 토글 버튼 양식 확인
|
||||
- [x] `ui_template/` 내 기존 프로그레스(서클/스피너) 컴포넌트 존재 여부 확인. 있으면 재사용, 없으면 `ui_template_overlay.ts` 또는 신규 `ui_template_progress.ts`에 `createProgressCircle(opts)` 1개소 정의 (SSOT, 페이지별 복제 정의 금지)
|
||||
- [x] 공통 토글 버튼 정본 확인: `createButton()` (`ui_template/ui_template_elements.ts:15`, `ButtonVariant` = glass 포함) 및 B05 3D 뷰 컨트롤 오버레이의 `toggleButton`(active 클래스) 중 어느 쪽이 프로젝트 정본인지 확정
|
||||
- 완료 조건: 프로그레스 서클과 토글 버튼 각각 정의처가 1곳으로 확정되고, 색상/치수는 `theme.css` 변수만 사용
|
||||
|
||||
#### S1-2. 3D 회전·줌 중심 = 마우스 커서 (요구 2번) — 확정: 회전 + 줌 모두 커서 기준
|
||||
- 대상: B04 `B04_wf1_Surface_UI_Viewer.ts`(포인트클라우드), `B04_wf1_Surface_UI_TerrainViewer.ts`(지형메시), B05 `B05_wf2_Route_UI_Viewer.ts`
|
||||
- [x] 공통 유틸 `B04_wf1_Surface_UI_Camera.ts`(3D 뷰어 공통 카메라 유틸 정의처)에 커서 지점 레이캐스트 -> `OrbitControls.target` 재설정 함수 신설
|
||||
- [x] 회전: `pointerdown` 시 커서 지점을 target으로 재설정
|
||||
- [x] 줌: 휠 이벤트에서 커서 지점 방향으로 dolly (B04 2D 지도의 "마우스 커서 줌 중심" 동작과 조작감 통일). `OrbitControls.zoomToCursor` 지원 여부 확인 후 옵션 사용 또는 수동 구현
|
||||
- [x] 3개 뷰어에서 동일 유틸 호출 (뷰어별 개별 구현 금지)
|
||||
- [x] 기존 동작 회귀 방지: B04 양방향 카메라 동기화, B05 마커 드래그 시 `OrbitControls.enabled=false` 경로와 충돌 없어야 함
|
||||
- 완료 조건: 커서를 지형 임의 지점에 놓고 드래그 회전 시 그 지점이 회전 중심으로 유지, 휠 줌 시 커서 지점이 화면에 고정. 지형 미교차(하늘) 시 기존 target 유지
|
||||
|
||||
---
|
||||
|
||||
### S2. B04_wf1_Surface
|
||||
|
||||
#### S2-1. 배수유역 데이터 B05 전용 저장소 분리 (요구 6번)
|
||||
- 현황(위키 [[concepts/drainage_watershed]] §5): 해석 산출물은 B04 저장소에만 존재
|
||||
`storage/{회사}/{사용자}/{프로젝트}/B04_wf1_Surface/drainage/`
|
||||
(`00_watershed_response.json`, `01_primary_region`, `02_flow_direction`, `03_road_routing`, `manifest.json`)
|
||||
- [x] B05가 B04 폴더를 직접 읽는지 / 이미 복사본을 쓰는지 실제 경로·파일명 확인 (`common_util_storage` 및 B05 Router_Drainage)
|
||||
- [x] B05 전용 경로 신설: `.../B05_wf2_Route/drainage/` — 최초 진입 시 B04 산출물을 **초안으로 1회 복사**, 이후 B05 편집(관 보충/세부유역 분할)은 B05 사본에만 기록
|
||||
- [x] B04 원본은 읽기 전용 취급(B05가 절대 덮어쓰지 않음)
|
||||
- [x] **갱신 정책(확정)**: 유역은 노선이 바뀌지 않으면 변하지 않음. B05에서 노선이 갱신되면 B04 백엔드가 재계산하여 B05 사본을 **자동 덮어쓰기**. 단 사용자 편집분(S3-5 유역선 포인트 오버라이드)은 별도 파일로 분리 보존하여 재계산 후 재적용 -> 덮어써도 편집 손실 없음
|
||||
- [x] 파일 구성: 재계산 산출물(B04 복사분) / `boundary_overrides.json`(사용자 이동 포인트만) 분리
|
||||
- [x] 파일명 규칙 확정 후 [[concepts/drainage_watershed]] §5에 B05 경로 추가
|
||||
- 완료 조건: B05에서 유역/관을 편집해도 B04 산출물 파일이 변경되지 않음. 노선 변경 -> 재계산 후에도 사용자 편집 포인트가 유지됨
|
||||
|
||||
#### S2-2. 지도 레이어 토글 기본값 hide (요구 7번)
|
||||
- 대상: `B04_wf1_Surface_UI_MapViewer.ts`(2D 지도 UI/이벤트)
|
||||
- [x] "유역방향", "1차 유역" 초기 상태 off (나머지 레이어 기본값은 현행 유지)
|
||||
- 완료 조건: 페이지 최초 진입 및 새로고침 시 두 레이어 미표시, 버튼 클릭 시 정상 표시
|
||||
|
||||
#### S2-3. 배수유역 안내/결과 메시지 지도 오버레이화 (요구 8번) — 확정: **B04에만 적용**
|
||||
- 대상: `B04_wf1_Surface_UI_MapViewer.ts` + `B04_wf1_Surface_UI_Style.css`
|
||||
- [x] 지도 하단/외곽 텍스트 줄로 표시되던 배수유역 상태·결과 문구를 지도 캔버스 위 오버레이로 이동
|
||||
- [x] **배경 색상 필수**: 지도 배경(위성/지적/등고선)이 복잡해 글자가 묻히므로 불투명에 가까운 배경 칩 + `backdrop-filter: blur(6px)` 적용 (`theme.css` 변수 사용, 라이트/다크 모두 대비 확보)
|
||||
- [x] 배치: 지도 좌상단 기준(기존 레이어 토글 바와 겹치지 않게)
|
||||
- [x] 문구는 `ui_locales` 등록 후 `t(key)` 참조 (하드코딩 금지)
|
||||
- 완료 조건: 지도 영역 밖 문구 줄 제거로 세로 공간 회수, 복잡한 배경 위에서도 문구 판독 가능, 메시지가 지도 조작을 가리지 않음(pointer-events: none)
|
||||
|
||||
---
|
||||
|
||||
### S3. B05_wf2_Route
|
||||
|
||||
#### S3-1. 진입 시 점진 렌더링 + 3D 프로그레스 서클 (요구 1번)
|
||||
- 대상: `B05_wf2_Route_UI_Page.ts`(오케스트레이터), `B05_wf2_Route_UI_Viewer.ts`, S1-1 공통 프로그레스 컴포넌트
|
||||
- [x] 현재 진입 시 호출되는 로드 작업 목록화 (`route/latest`, 지표면 preview 3D 모델, `drainage/basins` 등) 및 소요 순서 파악
|
||||
- [x] `Promise.all` 일괄 대기 구조를 제거하고 각 응답 완료 시점에 해당 영역만 즉시 렌더 (좌측 패널 -> 종단면 패널 -> 배수유역 패널 순, 3D는 최후순위)
|
||||
- [x] 3D 뷰포트에 프로그레스 서클 표시: 로드 단계 진행률 + 현재 단계 문구, 3D 렌더 완료 시 제거
|
||||
- [x] z-index 규칙: 프로그레스 서클은 **하단 오버레이 슬라이드 패널보다 아래** (패널을 가리지 않음)
|
||||
- 완료 조건: 페이지 진입 즉시 화면 요소가 순차적으로 채워지고, 3D 로딩 중 진행 상태가 시각적으로 확인됨
|
||||
|
||||
#### S3-2. 배수유역도 토글 버튼 재배치 및 양식 통일 (요구 3·4번)
|
||||
- 대상: `B05_wf2_Route_UI_Drainage_Panel.ts` + B05 스타일 CSS
|
||||
- [x] 흐름 화살표 hide/show 버튼을 "배수유역도" 제목 우측, 등고선·세류 버튼 다음 위치로 이동
|
||||
- [x] 등고선/세류/흐름화살표 3개 버튼을 S1-1에서 확정한 공통 토글 양식으로 통일 (모양·상태색 동일, **크기는 현행 유지**)
|
||||
- 완료 조건: 3개 버튼이 한 줄에 동일 양식으로 정렬, 각 토글 동작 회귀 없음
|
||||
|
||||
#### S3-3. 유역 내부 상류 세류선 하이라이트 (요구 5번) — 확정: **토글 버튼 상시 표시**
|
||||
- 대상: B04 하이라이트 구현부(재활용 원본) -> B05 배수유역도 렌더러
|
||||
- [x] B04의 동종 하이라이트 로직 위치 확인 후 공통화 또는 이식 (중복 구현 금지)
|
||||
- [x] 트리거: 선택/호버가 아니라 **토글 버튼 on 시 전체 유역의 상류 세류선을 항상 강조 표시**. 버튼은 S3-2의 등고선/세류/흐름화살표와 같은 줄·같은 양식으로 추가
|
||||
- [x] 렌더 순서(z): 유역 영역 색상 채움 **위**, 흐름 화살표 **아래**
|
||||
- 완료 조건: 토글 on 시 유역 내부 상류 세류선이 상시 강조되고 화살표 가독성 유지, off 시 완전 제거
|
||||
|
||||
#### S3-4. 3D 안내 문구 오버레이화 (요구 9번)
|
||||
- 대상: `B05_wf2_Route_UI_Page.ts` / `B05_wf2_Route_UI_Viewer.ts` + B05 CSS
|
||||
- [x] "지형을 클릭하거나 팔레트 포인트를 드래그해 배치하세요" 문구를 3D 뷰포트 **우상단 오버레이**로 이동
|
||||
- [x] 배경/테두리 없이 텍스트만, 색상은 회색 계열 `theme.css` 토큰 사용, `pointer-events: none`
|
||||
- [x] 우측 세로 단계 오버레이(`createVerticalStepBar`)와 겹치지 않도록 위치 확정
|
||||
- 완료 조건: 문구가 3D 조작을 방해하지 않고 우상단에 회색 텍스트로만 표시
|
||||
|
||||
#### S3-5. 유역 외곽선 포인트 드래그 편집 (요구 6번 확장 — 신규)
|
||||
- 배경: 노선이 바뀌면 유역을 재계산해 덮어써야 하는데, 사용자가 손으로 고친 유역선이 날아가면 안 됨. 편집분을 "포인트 오버라이드"로만 저장해 재계산과 공존시킨다.
|
||||
- 대상: B05 배수유역도 렌더러 + `B05_wf2_Route_UI_Drainage_Panel.ts` + B05 Router_Drainage(저장) + S2-1 저장소
|
||||
- [x] 편집 핸들 생성: **2차 전체 유역선(`03_road_routing.geojson` 외곽선)을 20m 간격으로 리샘플**하여 드래그 가능한 포인트 표시 (간격 상수화 — 20m는 실제 화면 보고 조정 예정)
|
||||
- [x] 드래그: 포인트 이동 시 유역 외곽선 즉시 재그리기. 이동분은 **프론트엔드 메모리에만 보유**(즉시 저장 안 함)
|
||||
- [x] 저장 시점: 종단 경로 확정 시 **모달로 "변경된 포인트 정보를 저장할까요?" 확인** -> 승인 시에만 저장
|
||||
- [x] 저장 범위: 전체 폴리곤이 아니라 **이동된 포인트만**(인덱스 + 이동 좌표) `boundary_overrides.json`에 기록
|
||||
- [x] 재계산 후 렌더: B04 재계산 산출물 위에 저장된 오버라이드 포인트를 재적용하여 그림 (오버라이드가 우선)
|
||||
- [x] **매칭 규칙(확정)**: 인덱스가 아닌 **좌표 근접 매칭 + 허용 반경**. 재계산으로 포인트 수/형상이 달라져도 가장 가까운 새 외곽선 포인트에 오버라이드를 붙임
|
||||
- [x] **무시 규칙(확정)**: 재계산된 새 2차 전체 유역 폴리곤 **내부에 들어간 오버라이드 포인트는 무시**(경계를 넓히는 의미가 없으므로 폐기). 폴리곤 외부 포인트만 유효 오버라이드로 재적용
|
||||
- [x] 무시된 포인트 처리: 저장 파일에서 제거하고 개수를 안내 문구로 표기(사용자가 왜 사라졌는지 알 수 있게)
|
||||
- 완료 조건: 포인트를 옮기고 경로 확정 -> 모달 승인 -> 재진입 시 편집된 유역선 유지. 미승인 시 편집분 폐기
|
||||
- 선행: S2-1(저장소 분리) 완료 후 착수
|
||||
|
||||
### S4. 프론트엔드 검증 후 조작감 보정 (2026-08-01 사용자 확인 요청분)
|
||||
|
||||
- [x] **회전축을 커서가 가리키는 지형 표면 지점으로** (`B04_wf1_Surface_UI_Camera.ts`)
|
||||
- [x] **회전 상하 반전 + 가운데 버튼 팬(전역 공통)** (`B04_wf1_Surface_UI_Camera.ts`)
|
||||
- [x] **뷰셋 버튼 그룹 좌상단 배치** (`B05_wf2_Route_UI_Style.css`)
|
||||
|
||||
### S5. 프로그레스 서클 전면 적용 (2026-08-01 사용자 확인 요청분 2차)
|
||||
|
||||
- [x] **서클 양식을 공통 로딩 스피너(`.ui-spinner`) 기준으로 통일**
|
||||
- [x] **`overlay` 옵션 신설** (`ui_template_progress.ts/.css`)
|
||||
- [x] **B05 3D**: 뷰포트 정중앙
|
||||
- [x] **B04 상단 3D 2개**: 포인트클라우드 및 지형 뷰어 연결
|
||||
- [x] **B04 하단 2D 지도**: 도엽 레이어 진행률 표시
|
||||
- [x] **B05 그래프(종단면) 영역**: 로딩 서클 적용
|
||||
- [x] **B05 배수유역도**: 배수유역도 렌더 진행률 표시
|
||||
|
||||
### S6. 3D 조작 보정 + 3D/등고선 캐싱 (2026-08-01 협의 완료)
|
||||
|
||||
- [x] **S6-1. 3D 조작 (사용자 확정)**: 휠 반전, 3D 씬 피봇 구(sphere) 마커 시각화.
|
||||
- [x] **S6-2. 등고선 응답·렌더 비용 낮추기**: `cache: "no-store"` 제거, LineSegments 2개로 병합, 주곡선 라벨 조건부 갱신.
|
||||
- [x] **S6-3. 캐시 A — HTTP (서버 헤더)**: ETag 기반 304 Not Modified 공용 유틸 적용.
|
||||
- [x] **S6-4. 캐시 B — IndexedDB + 프리로드 (사용자 확정: A와 함께)**: IndexedDB asset cache 구축, GLTF/PLY 직접 파싱 및 프로젝트 전환 cleanup.
|
||||
|
||||
### S7. 자료 준비 화면(B11) + 3D·등고선 선적재 (2026-08-01 협의 후 구현)
|
||||
|
||||
- [x] **B11 준비 화면 신설** (`B11_Status_UI_Loading.ts`)
|
||||
- [x] **대시보드→작업 화면 진입 시 경유 및 탭 단위 무시 규칙 적용**
|
||||
- [x] **오류 발생 시 안내 문구 및 수동 이동/대시보드 버튼 제공**
|
||||
- [x] **B04/B05 등고선 간격 설정 공유**
|
||||
|
||||
---
|
||||
|
||||
### 구현 메모 및 실행 검증 결과
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 공통 프로그레스 서클 | `ui_template/ui_template_progress.ts` + `.css` 신설 |
|
||||
| 공통 토글 버튼 정본 | `.b04-map__layer-button--gis` 양식 정본 채택 및 B05 통일 |
|
||||
| 커서 회전 | `bindCursorPivotControls` 기반 회전/줌 처리 |
|
||||
| 상류 세류선 렌더러 | `drawUpstreamLines()` 공통화 |
|
||||
| 유역선 편집 진입 | 유역선 편집 토글 버튼 신설 |
|
||||
| 신규 API | `PUT /api/projects/{project_id}/drainage/boundary` |
|
||||
| 빌드/체크 | `npm run typecheck` 통과, `vite build` 성공, `ruff check` 통과 |
|
||||
@@ -0,0 +1,163 @@
|
||||
### B04 상세 배수유역 계산 (2026-08-01 착수)
|
||||
|
||||
**목표**: B04 하단 2D 지도에서 관 매설 지점(기본/자동보충/수동)을 편집하고, 계획노선 종단 Z에 따른 측구 흐름으로 관별 세부 배수유역을 나눈다. 결과는 모델 확정 시 영구저장한다.
|
||||
|
||||
#### A. 조사로 확정된 사실 (2026-08-01)
|
||||
|
||||
| 항목 | 결론 |
|
||||
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 최대/최소 관 간격 | **이미 config에 있음** — `DRAINAGE_PIPE_MAX_SPACING_M=300.0`(`config_system.py:280`), `DRAINAGE_PIPE_MIN_SPACING_M=20.0`(`config_system.py:282`). 둘 다 `os.getenv` 지원 → 테스트 100m는 `.env` 한 줄로 처리, 코드 수정 불필요 |
|
||||
| 세부유역 알고리즘 | **이미 B05에 전부 구현됨** — `place_pipes()`/`_fill_gap()`(등간격+slack 보충), `assign_road_cells_to_pipes()`(종단선 1차원 내리막 추적 + 사그는 최근접 관), `assemble_basins()`(담당 관 라벨 → `polygonize_labels`) 모두 `B05_wf2_Route_Engine_Drainage_Basin.py`(502줄) |
|
||||
| 스플릿 라인 | 별도로 긋지 않는다. 담당 관 라벨 경계가 곧 세부유역 경계 — 겹침/틈 원리적 부재, union = 2차 전체 유역 |
|
||||
| 기본 관 | `_base_pipes()`(`B04_wf1_Surface_Engine_Watershed_Analyze.py:323`) 세류 교차점, 최소간격 미만은 병합. `03_road_routing`에 저장됨 |
|
||||
| 유입 집중점 | `find_inflow_hotspots()`(`..._Analyze.py:356`) 현행 유지. 구역번호 −1 = 기본 관 자리 |
|
||||
| 종단 Z 현행 | B04 = 원청 CSV z(`common_util_route_geometry.read_planned_route_csv`), B05 = `route_points.z`(B05 최적경로) |
|
||||
| 서피스 표고 샘플러 | `build_surface_sampler()` / `sample_xy()` (`B05_wf2_Route_Engine_Sections_Sampler.py:124,21`) 재사용 가능 |
|
||||
| 관 지점 영구저장 | **없음**. B05는 `chainages`를 payload로만 받아 세션 휘발 |
|
||||
| ⚠ 정합 결함 | B05`_prepare()`(`B05_wf2_Route_Router_Drainage.py:54`)는 B05 최적경로 `route_points` 기준 vertices를 쓰는데, B04 산출물 `road_chainage`는 원청 CSV 노선 기준. 노선이 다르면 누가거리가 어긋남 → Z 소스 통일 작업에서 함께 정정 |
|
||||
|
||||
#### B. 설계 결정 (사용자 확정)
|
||||
|
||||
1. **종단 Z 우선순위**: ① `longitudinal_sections.data.profile_alignment`(B05 편집 계획고) → ② `route_points.z`(B05 DRAFT 경로) → ③ 확정 서피스 샘플링(`build_surface_sampler`) → ④ 원청 CSV z. 단 ①②는 B05 최적경로 XY 기준이므로 **원청 CSV 노선과 동일 노선일 때만 채택**(다르면 ③으로).
|
||||
2. **필터/서피스/스무딩 변경 시**: Z만 다시 뽑아 **세부유역만 재계산**. 1차 격자 해석(30초)은 재실행하지 않는다.
|
||||
3. **관 지점 정본**: `B04_wf1_Surface/drainage/edits/pipe_points.json` 단일 파일. `chainage_m` 기준 저장. B04·B05 공용(B05는 이 파일만 사본 안 만들고 원본 참조).
|
||||
4. **노선 변경 시**: `route_signature`(원청 CSV 해시) 불일치 = 전량 리셋 후 기본+자동보충 재생성 → 상세유역까지 자동 실행(기존 "유역분석" 버튼 경로).
|
||||
5. **재계산 트리거**: 편집은 프론트 메모리, "상세유역 분석" 버튼 클릭 시에만 서버 계산. 자동 재계산 없음(사용자가 속도 보고 추후 판단).
|
||||
6. **저장 시점**: 기존 "모델 확정" 버튼에서 동시 커밋. 확정 전 이탈 시 저장된 값으로 복귀(임시저장 없음).
|
||||
7. **시·종점 관 배치 안 함**, 마지막 관 하류 구간은 2차 유역 미포함이라 무시. 종단 상단부 관/단조경사 쏠림은 사용자가 수동 조절.
|
||||
8. **측구 방향(산측/계곡측) 무시**.
|
||||
9. **표시 토글 2그룹**: 유입 집중점 마커 / 관 매설 마커(기본·자동·수동)를 각각 hide-show 버튼으로 분리.
|
||||
10. **B04는 관리자 트러블슈팅용, B05는 일반 사용자용.** UI는 달라도 같은 이름 버튼은 같은 백엔드 호출 → 공용 엔진으로 승격해 보장.
|
||||
|
||||
#### C. 구현 체크리스트
|
||||
|
||||
**C-1. 공용 엔진 승격 (선행)**
|
||||
|
||||
- [X] `common_util/common_util_drainage_detail.py` 신설(517줄) — `WatershedBasin`, `RoadRouting`, `read_road_routing`, `place_pipes`, `_fill_gap`, `_best_position`, `pipes_from_chainages`, `assign_road_cells_to_pipes`, `assemble_basins`, `estimate_pipe_diameter_mm`, `build_detail` 이관
|
||||
- [X] `B05_wf2_Route_Engine_Drainage_Basin.py`를 B05 사본 폴더만 정하는 어댑터(58줄)로 축소
|
||||
- [X] `common_util/common_util_route_profile.py` 신설 — 종단 Z 해석기 `resolve_route_profile()`, 노선 동일성 판정 `routes_match()`, `design_elevation_from_longitudinal()`(B06에서 이관)
|
||||
- [X] `common_util/common_util_drainage_context.py` 신설 — B04/B05 공용 입력 준비(노선·Z·좌표계). **계획에 없던 추가분**: 두 화면이 같은 입력을 보게 하려면 준비 단계도 한 곳이어야 함
|
||||
- [X] `common_util/common_util_surface_sampler.py` — `B05_wf2_Route_Engine_Sections_Sampler.py` 이동(B04도 써야 하므로 역방향 import 제거)
|
||||
- [X] B05 `_prepare()` 노선 소스를 원청 CSV 기준으로 정정하고 Z만 해석기로 주입 (⚠ 정합 결함 해소)
|
||||
- [X] `B05_wf2_Route_Engine_Drainage.py` 삭제 — 관경 stub이 공용 모듈로 옮겨져 빈 파일이 됨
|
||||
|
||||
**C-2. B04 백엔드**
|
||||
|
||||
- [X] `common_util/common_util_drainage_pipes.py` 신설 — `pipe_points.json` 로드/저장, `route_signature` 검사, 불일치 시 리셋. **계획의 B04 전용 저장소에서 변경**: B05도 같은 파일을 봐야 "같은 버튼 = 같은 결과"가 성립
|
||||
- [X] `B04_wf1_Surface/B04_wf1_Surface_Router_Basins.py` 신설 (225줄)
|
||||
- [X] `GET /{project_id}/drainage/pipe-points` — 저장분 조회(없으면 기본+자동보충 생성해 반환)
|
||||
- [X] `POST /{project_id}/drainage/detail-basins` — 편집 중 목록으로 세부유역 산정(저장 안 함)
|
||||
- [X] `PUT /{project_id}/drainage/pipe-points` — 확정 시 관 지점 + 세부유역 커밋
|
||||
- [X] `04_detailed_basins.geojson` 저장 (세부유역 폴리곤 + 관 지점 피처)
|
||||
- [X] `config_system.py`에 `DRAINAGE_PIPE_POINTS_FILENAME`, `DRAINAGE_EDITS_DIRNAME`, `DRAINAGE_DETAIL_FILENAME` 추가
|
||||
- [X] `main.py` 라우터 등록
|
||||
|
||||
**C-3. B04 프론트엔드**
|
||||
|
||||
- [X] `B04_wf1_Surface_UI_Basins.ts` 신설(438줄) — 관 마커(기본/자동/수동 색 구분) 렌더·스냅·드래그·히트 판정, 세부유역 폴리곤 렌더, 우클릭 메뉴, "상세유역 분석" 버튼, 표시 토글 2그룹. **계획의 3개 파일을 1개로 통합**: 셋 다 같은 관 목록 상태를 공유해 나누면 상태가 흩어짐
|
||||
- [X] `B04_wf1_Surface_UI_RouteSamples.ts` 신설 — 계획선 1m 재표본·누가거리 역변환(흐름 강도 오버레이와 공용, 마커 어긋남 방지)
|
||||
- [X] `B04_wf1_Surface_UI_MapLayers.ts` 신설 — 레이어 목록·기본값·색 분리(D-1 해소)
|
||||
- [X] `_UI_MapViewer.ts` 수정 — 오버레이 배선, 우클릭/드래그 이벤트, `commitDrainage()` 노출
|
||||
- [X] `B04_wf1_Surface_Api_Fetch.ts` 수정 — 신규 3개 엔드포인트 클라이언트
|
||||
- [X] `B04_wf1_Surface_UI_Page.ts` 수정 — "모델 확정" 클릭 시 관 지점·세부유역 동시 커밋
|
||||
- [X] locale 12종 / theme 토큰 2종(`--map-pipe-auto`, `--map-pipe-user`) / 우클릭 메뉴 CSS 추가
|
||||
- [X] 테스트 간격값은 사용자가 `config_system.py` 기본값을 100m/5m로 직접 조정 (규제값 300m는 `.env`로 복원)
|
||||
|
||||
**C-4. 마감**
|
||||
|
||||
- [X] `ruff format` / `prettier` 실행, `ruff check` 통과, `tsc --noEmit` 통과, `vite build` 성공
|
||||
- [X] 700줄 초과 파일 없음 (최대 `_UI_MapViewer.ts` 647줄)
|
||||
- [X] 커밋 후 `.claude/git-autopush.ps1` 실행
|
||||
|
||||
#### D. 실행 검증 결과 (2026-08-01, 서버 기동 상태)
|
||||
|
||||
대상 프로젝트 `acb9170b…` (원청 노선 135정점 / 350.1m / EPSG 5187), 격자 1589×904, 도로 셀 1907.
|
||||
|
||||
| 확인 항목 | 결과 |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| 종단 Z 출처 | `route_points` (B05 최적경로가 원청 노선과 일치 판정) — Z 528.40~544.66m |
|
||||
| 자동 생성 | 관 4개(기본 1 + 자동보충 3), 세부유역 4개 |
|
||||
| 세부유역 경계 근거 | `03_road_routing.npz`의 `road_slot`(1m 셀별 물 흐름 도달 도로 셀) 라벨 경계 |
|
||||
| 관 추가/이동 후 | 유역 수가 따라 늘고,**면적 합 457,404㎡ 불변** (경계 불변 조건 충족) |
|
||||
| 저장/재조회 | `edits/pipe_points.json` + `04_detailed_basins.geojson` 기록, 재조회 시 기본/자동/수동 표시 유지 |
|
||||
| 초기화(`points: []`) | 저장분 무시하고 기본 관 + 자동 보충으로 복귀 |
|
||||
| 인증 | 세션 없으면 401 |
|
||||
|
||||
검증에 쓴 임시 산출물(테스트로 만든 `pipe_points.json`·`04_detailed_basins.geojson`)은 삭제해 원래 상태로 되돌렸습니다.
|
||||
|
||||
#### E. 사용자 확인 후 후속 조치 1차 (2026-08-01, 커밋 `83f23aa`)
|
||||
|
||||
사용자가 화면에서 직접 확인하고 지시한 5건. 전부 반영 완료.
|
||||
|
||||
| 번호 | 지시 | 처리 |
|
||||
| ---- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | 관 매설·세부유역 토글을 하나로(이름 "세부유역") | 따로 끄면 관을 옮겨도 유역이 안 보여 판단 불가 — 단일 토글로 통합 |
|
||||
| 2 | 남는 버튼을 "집중유역"으로, 노선 위 집중점 hide/show(기본 hide) | 유입 집중점 마커를 흐름 강도 색칠에서 분리해`markerButton`으로 신설. 끄면 골라 둔 유입 외곽선도 함께 걷는다 |
|
||||
| 3 | 세부유역에 서클 번호(시점→종점 오름차순) | 폴리곤 면적 중심(`ringCentroid`)에 서클 숫자. 번호는 백엔드 `basin.index`(누가거리 순)를 그대로 사용 |
|
||||
| 4·5 | 관 매설 추가·삭제 동작 안 함 | **원인**: 메뉴 항목을 누를 때 `pointerdown`이 뷰포트로 전파돼 메뉴가 먼저 닫히면서 `click`이 발생하지 않음. 메뉴 안 이벤트를 지도 조작에서 제외 |
|
||||
|
||||
#### F. 사용자 확인 후 후속 조치 2차 (2026-08-01, 커밋 `8c37f2f`)
|
||||
|
||||
| 번호 | 지시 | 처리 |
|
||||
| ---- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | B05식 유역 부분 활성화를 B04에도, 유역 클릭 선택 포함 | `selectedBasin` 도입. 유역 폴리곤 클릭(`pointInRing`)·관 마커 클릭 모두 선택, 유역 밖 클릭 시 해제. 클릭 우선순위 **세부유역 → 집중점** 고정(B04 오버레이 다중 충돌 방지). 선택 유역 제원은 상태 줄 표기 |
|
||||
| 2 | B05 배배관 편집 버튼 삭제, 상시 드래그, 이동 시 자동 재계산 | `editMode` 제거 → 마커 상시 드래그. 계획선 좌클릭 추가는 폐지(유역 선택과 충돌) → 우클릭 메뉴로 이동. `createPipeEditor(onChange, onCommit)`의 `onCommit`에서 추가·삭제·이동 완료 시 즉시 재산정 |
|
||||
| 3 | "자동 제안" → "초기화" | locale`B05_Drainage_Btn_Auto` 문구 변경 + 툴팁 추가 |
|
||||
| 6 | B04의 노선 유입 면적 컬러 표시를 B05에도 | 백엔드가`strength_profile`(1m 구간별 유입 면적)을 B04·B05 **같은 값**으로 응답. 색띠·정규화·렌더러를 `B04_wf1_Surface_UI_FlowRamp.ts`로 공용화, B05에 토글 신설 |
|
||||
|
||||
**신설/이동 파일**
|
||||
|
||||
- `B04_wf1_Surface_UI_FlowRamp.ts` — 색띠·로그 정규화·강도선 렌더러(B04·B05 공용)
|
||||
- `ui_template_context_menu.ts` — 지도 우클릭 메뉴 공용 부품(B04·B05 중복 제거)
|
||||
- `B05_wf2_Route_UI_Drainage_Parts.ts` — 레이어 상수·토글 팩토리·유역 목록 렌더·좌표 투영기
|
||||
- `common_util_drainage_detail.build_strength_profile()` 추가
|
||||
|
||||
**700줄 규칙 유지**: 부품 분리 전 B05 패널이 838줄까지 늘어 세 차례 분리(838 → 806 → 723 → 677).
|
||||
|
||||
#### G. 전체 유역 편집 기능 제거 (2026-08-01, 커밋 `e994330`)
|
||||
|
||||
**사용자 결정**: "전체 등고선을 분석하였으니 수정할 필요 없음. 편집기능 삭제."
|
||||
|
||||
**배경**: 외곽선을 손으로 옮겨도 안쪽 세부유역은 1m 셀 라벨로 만들어지므로 따라오지 않는다. 전체 외곽선과 세부유역 합이 어긋나 면적·관경의 근거가 깨지는 구조였다.
|
||||
|
||||
**제거 대상** (557줄 삭제)
|
||||
|
||||
- `B05_wf2_Route_UI_Drainage_Boundary.ts`(195줄) 파일 삭제
|
||||
- "유역선 편집" 버튼, 경로 확정 시 저장 여부 모달(`saveBoundaryEditsIfWanted`)
|
||||
- `PUT /{project_id}/drainage/boundary` 엔드포인트, `saveDrainageBoundary()` 클라이언트
|
||||
- `boundary_overrides` 로드·저장·재부착(`apply_boundary_overrides`, `parse_overrides`, `resample_boundary`, `BoundaryOverride`)
|
||||
- config `DRAINAGE_BOUNDARY_OVERRIDE_FILENAME` / `_HANDLE_SPACING_M` / `_MATCH_RADIUS_M`, locale 2종
|
||||
- 응답 필드 `boundary_spacing_m` / `boundary_overrides` / `boundary_dropped`
|
||||
|
||||
**결과**: `B05_wf2_Route_Engine_Drainage_Store.py` 245줄 → 55줄(B04 사본 동기화만 남김). 외곽선은 `detail.basin_lonlat`을 그대로 표시.
|
||||
|
||||
#### H. 실행 검증 결과 2차 (2026-08-01, 서버 기동 상태)
|
||||
|
||||
| 확인 항목 | 결과 |
|
||||
| -------------------------------- | ------------------------------------------------------------------------- |
|
||||
| B04`GET /drainage/pipe-points` | 200 · 관 4개 · 유역 4개 · 강도 307점(최대 409,210㎡) |
|
||||
| B05`POST /drainage/basins` | 200 · 유역 4개 · 외곽선 122점 · 강도 307점 ·`z_source=route_points` |
|
||||
| 두 화면 강도 곡선 | 점 개수·값 동일(공용 엔진 산출) |
|
||||
| `boundary*` 응답 필드 | 전부 제거 확인 |
|
||||
| `PUT /drainage/boundary` | 404 (엔드포인트 제거됨) |
|
||||
| 품질 게이트 | `ruff check` 통과 · `tsc --noEmit` 통과 · `vite build` 성공 |
|
||||
| 700줄 규칙 | 배수유역 관련 파일 전부 통과(최대`_UI_Drainage_Panel.ts` 619줄) |
|
||||
|
||||
| 커밋 | 내용 |
|
||||
| ----------- | ---------------------------------------------------------------------------------- |
|
||||
| `07c4a78` | feat(B04): 상세 배수유역 계산 — 관 매설 지점 편집 + 세부유역 분할 |
|
||||
| `83f23aa` | fix(B04): 우클릭 메뉴 동작 복구 + 배수유역 토글 정리, 세부유역 서클 번호 |
|
||||
| `8c37f2f` | feat(B04/B05): 유역 선택 강조, B05 배관 상시 드래그·자동 재산정, 강도 색칠 공용화 |
|
||||
| `e994330` | refactor(B05): 유역 외곽선 편집 기능 제거 |
|
||||
|
||||
---
|
||||
|
||||
#### I. 남은 미결
|
||||
|
||||
1. 관경 산정식(`estimate_pipe_diameter_mm`)은 여전히 stub — 100년 강우강도 수식이 오면 채운다. 지금은 화면에 "미정"으로 나온다.
|
||||
2. B05 화면은 아직 `pipe_points.json`을 읽지 않는다(B04에서 확정한 관이 B05에 자동 반영되지 않음). B05 배수유역 패널 연동은 후속 작업.
|
||||
3. 기존 프로젝트 저장소에 남은 `boundary_overrides.json`은 삭제하지 않았다 — 사용자 데이터라 임의로 지우지 않았고, 이제 아무도 읽지 않는 고아 파일이다. 정리 여부는 사용자 판단.
|
||||
4. `ui_template_locale.ts`가 1230줄로 700줄 규칙 위반(작업 시작 시점에 이미 1102줄). 이번 작업에서 locale 키를 추가해 더 늘었다 — 페이지별 분할 필요.
|
||||
5. 위키 반영(`concepts/drainage_watershed.md`, `B04_*`, `B05_*`)은 위키 관리자 몫 — 특히 `B05_backend.md`에 이미 삭제된 `Engine_Drainage_Watershed/Trace/Assemble/Subdivide` 항목과 `Engine_Drainage_Basin`(현재 58줄 어댑터) 설명이 낡았다. `B05_wf2_Route_UI_Drainage_Panel`(배관 편집 토글)·`_Drainage_Pipes` 명세도 실제와 다르다.
|
||||
|
||||
#### J. 이번 세션 커밋 목록
|
||||
@@ -0,0 +1,97 @@
|
||||
# 2026-08-01 B04 전처리 — 도로 유입 흐름 강도 표시 + 사이드 패널 정리 (완료 계획서)
|
||||
|
||||
> 이관 이력: 2026-08-01 검증 완료 후 `docs/raw/PLAN.md` 1부에서 `docs/raw/plans/2026-08-01_plan_B04_inflow_strength_and_panel_cleanup.md`로 최종 이관 백업됨.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-01_verify_B04_inflow_strength_and_panel_cleanup.md`
|
||||
|
||||
---
|
||||
|
||||
## 사용자 확정 결정사항
|
||||
|
||||
| # | 논점 | 확정 |
|
||||
| --- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| D1 | 강도 단위 | **유입 면적 ㎡** (상류 셀 수 × 셀면적). B05 관 위치 점수와 같은 단위 |
|
||||
| D2 | 도로 색 분해능 | **누가거리 1m 구간 합**. 도로 폭 방향 셀들을 1m 구간으로 더함(버리는 값 없음, 평균 아님) |
|
||||
| D3 | 색 스케일 | **로그 스케일** 레인보우 |
|
||||
| D4 | 하이라이트 방식 | **유입 집중점 마커만 선택 가능**. 선택 시 그 지점으로 들어오는 셀들의 **외곽선**만 강조 (면 채움 아님). 도로 임의 지점 클릭은 선택 대상 아님 |
|
||||
| D5 | 하이라이트 기준 구간 | 마커의 **누가거리 1m 구간** (색칠 단위와 동일) |
|
||||
| D11 | 마커 선정 | 도로를 **시점 · 기본 관 매설 지점 · 종점**으로 잘라 구역을 만들고, 구역 길이 L에 대해 **`floor(L / 관 최대간격) + 3`개**를 뽑는다. 마커 선정 시 이미 고른 지점 양 편측으로 관 최소간격만큼 후보 제외(한쪽 쏠림 방지) |
|
||||
| D12 | 마커 표시 | 크기(강도 비례) + 번호. 지도를 가리지 않게 작은 마커 사용 |
|
||||
| D6 | 해석용 도로 굽기 폭 | **현행 4m 유지** + "설계 도로폭이 아니라 D8 대각 통과를 막는 해석 파라미터" 명시 |
|
||||
| D7 | 강도 오버레이 토글 | 배수유역 버튼 줄에 추가, **기본 켜짐** |
|
||||
| D8 | 병합 컨테이너 제목 | **지표면 분석** (지면 필터 > 서피스 > 스무딩) |
|
||||
| D9 | 스무딩 UI | 버튼 → **드롭다운**, 선택지 `적용` / `미적용` |
|
||||
| D10 | 표시 옵션 병합 | 포인트 표시 옵션 + 모델 표시 옵션 → 제목 **모델 표시 옵션**. 점 크기·밀도는 맨 아래, 게이지 앞에 현재값 텍스트 |
|
||||
|
||||
---
|
||||
|
||||
## T1. 도로 유입 흐름 강도 — 백엔드 원본값 노출
|
||||
|
||||
- [X] T1-1 강도 곡선 출력 간격 5m → **1m** (`_STRENGTH_OUTPUT_STEP_M`).
|
||||
- [X] T1-2 구간 합 명시 및 **유입 집중점(마커) 산출** `find_inflow_hotspots()` 구현.
|
||||
- [X] T1-3 신규 API `GET /api/projects/{id}/drainage/road-inflow` 신설 (`B04_wf1_Surface_Router_Inflow.py`).
|
||||
- [X] T1-4 `polygonize_labels(min_area_m2=0)` 기반 외곽선 연동.
|
||||
- [X] T1-5 `_CACHE` 기반 프로세스 캐시 구현.
|
||||
- [X] T1-6 미계산 시 404 안내.
|
||||
- [X] T1-7 `DRAINAGE_ROAD_WIDTH_M` 주석 명시.
|
||||
|
||||
---
|
||||
|
||||
## T2. 도로 위 레인보우 오버레이 (프론트)
|
||||
|
||||
- [X] T2-1 신규 모듈 `B04_wf1_Surface_UI_FlowStrength.ts` 구현.
|
||||
- [X] T2-2 `log1p` 기반 로그 정규화.
|
||||
- [X] T2-3 `--map-flow-ramp-*` 팔레트 토큰 연동.
|
||||
- [X] T2-4 "흐름 강도" 토글 버튼 추가.
|
||||
- [X] T2-5 누가거리 기준 일치 확인.
|
||||
- [X] T2-6 문구 `ui_locales` 등록.
|
||||
|
||||
---
|
||||
|
||||
## T3. 유입 집중점 마커 + 선택 시 기여 셀 외곽선 하이라이트 (프론트)
|
||||
|
||||
- [X] T3-1 마커 렌더링 (반지름 4~10px, 번호 표기).
|
||||
- [X] T3-2 pointerup 기반 좌버튼 클릭 선택 경로 신설.
|
||||
- [X] T3-3 히트 판정 (원 반지름 + 4px).
|
||||
- [X] T3-4 `MapRender` 내 화면 좌표 역변환 일원화.
|
||||
- [X] T3-5 선택 시 API 호출 및 외곽선 실선 강조.
|
||||
- [X] T3-6 지도 좌상단 전용 요약줄 표시.
|
||||
- [X] T3-7 로딩 및 요청 번호 기반 늦은 응답 폐기 처리.
|
||||
|
||||
---
|
||||
|
||||
## T4. 사이드 패널 — 지표면 분석 컨테이너 병합
|
||||
|
||||
- [X] T4-1 "지표면 분석" 컨테이너 병합 신설.
|
||||
- [X] T4-2 라벨 명칭 이관.
|
||||
- [X] T4-3 스무딩 드롭다운 (`적용`/`미적용`) 변경.
|
||||
- [X] T4-4 뷰어 소유권 유지 (`isSmoothingEnabled`/`setSmoothing`).
|
||||
- [X] T4-5 다국어 `ui_locales` 등록.
|
||||
|
||||
---
|
||||
|
||||
## T5. 사이드 패널 — 표시 옵션 병합
|
||||
|
||||
- [X] T5-1 "모델 표시 옵션" 단일 그룹 병합.
|
||||
- [X] T5-2 순서 및 구조 정리.
|
||||
- [X] T5-3 `[이름] [값] [게이지]` 3열 그리드 적용.
|
||||
- [X] T5-4 `syncOptionValues()` 동기화.
|
||||
- [X] T5-5 DOM 소유권 이원화 정리 (`optionsContent`).
|
||||
- [X] T5-6 문구 `ui_locales` 등록.
|
||||
|
||||
---
|
||||
|
||||
## 공통 마감 조건 및 검증 기록
|
||||
|
||||
- [X] C1 하드코딩 색상·문구 금지 준수
|
||||
- [X] C2 700줄 제한 준수 (`Router_Inflow.py` 분리)
|
||||
- [X] C3 `prettier` + `ruff format`/`ruff check` 완료
|
||||
- [X] C4 `git commit` + autopush 완료
|
||||
- [X] C5 코드 계산부 및 API 구동 확인 완결
|
||||
|
||||
---
|
||||
|
||||
## 적대적 리뷰 및 보정 완결 기록
|
||||
|
||||
- HIGH 3건 (마커 재선정 범위 보정, np.floor 반올림 편향 교체, 갈래 버튼 선택 유지)
|
||||
- MEDIUM/LOW 9건 완결 및 `RESPONSE_SCHEMA_VERSION = 3` 상향
|
||||
- autopush 커밋 완료
|
||||
@@ -0,0 +1,83 @@
|
||||
# S8. B04 산출물 재활용 + B05 수신량 절감 (완료 계획서)
|
||||
|
||||
> 이관 이력: 2026-08-01 검증 완료 후 `docs/raw/PLAN.md` 1부에서 `docs/raw/plans/2026-08-01_plan_S8_B05_data_saving.md`로 최종 이관 백업됨.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-01_verify_S8_B05_data_saving.md`
|
||||
|
||||
---
|
||||
|
||||
### S8. B04 산출물 재활용 + B05 수신량 절감 (2026-08-01 요구)
|
||||
|
||||
**배경 (실측, 프로젝트 `acb9170b-…` 기준)**
|
||||
B05 진입 시 약 69MB를 받는다. 새로고침해도 65MB를 다시 받는다(보관함이 덮는 것은 3D 모델+등고선 4MB뿐).
|
||||
|
||||
| 받는 것 | 크기 | 시점 | 현재 재사용 |
|
||||
|---|---|---|---|
|
||||
| 도엽 등고선 geojson | 34.2MB | 진입 즉시 | ETag / 보관함 |
|
||||
| 포인트클라우드 JSON | 23.8MB | 5단계 | 제거 (경량 API 대체) |
|
||||
| 위성사진 PNG | 4.2MB | 진입 즉시 | ETag / 자체 캐시 |
|
||||
| 도엽 하천중심선 | 1.4MB | 진입 즉시 | ETag / 보관함 |
|
||||
| 3D 모델 preview | 2.9MB | 5단계 | 보관함 |
|
||||
| 3D 등고선 | 1.3MB | 5단계 | 보관함 |
|
||||
| 종단·횡단 detail | 0.9MB | 4단계 | 제외 (실시간성 보장) |
|
||||
|
||||
---
|
||||
|
||||
#### S8-1. 확정 지표면 요약 API 신설 + B05 포인트클라우드 24MB 제거
|
||||
- [x] 신규 `GET /projects/{id}/surface/confirmed` (`B04_wf1_Surface_Router.py:372`, 응답 0.4KB / 0.018초)
|
||||
- [x] `B05_wf2_Route_UI_Page.ts` 의 `fetchSurfacePointCloud`(23.8MB) → `fetchConfirmedSurface`로 교체
|
||||
- [x] B04 좌측 포인트뷰어(관리자 전용)는 기존 포인트클라우드 API 유지
|
||||
- [x] 완료 조건: B05 진입 네트워크 목록에 `surface/point-cloud` 없음 (코드상 호출 제거 확인)
|
||||
|
||||
#### S8-2. 모델 확정 = 하류 단계의 유일한 기준 (확정 시점 동기화)
|
||||
- [x] 준비 여부 판정이 프로젝트 번호만 본다 → 표식을 `{projectId}|{signature}`로 변경 및 signature 대조
|
||||
- [x] 준비화면이 평활 여부를 짐작한다 → `preloadSurfaceAssets`가 `fetchConfirmedSurface`의 확정값 사용
|
||||
- [x] B04 재진입 초기 선택 확정값 동기화 및 `TerrainViewer.setSmoothing()` 추가
|
||||
- [x] 확정 직후 `clearPreloadMark()` + `clearRouteLatestCache()`로 캐시 무효화
|
||||
- [x] 완료 조건 (사용자 화면 확인 완료): B04 다른 필터·방식 확정 시 B05 지형·등고선 동기화, 대시보드 왕복·새 브라우저·단계 이동 시 준비화면 정상 작동
|
||||
|
||||
#### S8-3. 도엽 등고선 전송 방식 개선
|
||||
- [x] 분석용 원본 `도엽_등고선.geojson` 유지 및 화면 공유 (크롭 철회 수용)
|
||||
- [x] `/geojson` 응답 파일 바이트 direct 전송 + ETag 적용 (서버 2.0s -> 0.006s)
|
||||
- [x] 프론트엔드 브라우저 보관함 저장 (`fetchCachedSheetLayer`)
|
||||
- [x] 완료 조건(사용자 확인): 첫 진입 1회만 받고 새로고침 시 재수신 없음, 지도 표시 정상 유지
|
||||
|
||||
#### S8-4. 위성사진 범위 확대 + 재사용
|
||||
- [x] 덮을 범위 = LAS bounds ∪ 계획노선 범위
|
||||
- [x] 주변 셀 타일 여유 `SURFACE_MAP_MARGIN_TILES = 3` 지정
|
||||
- [x] `_t=` 타임스탬프 제거 및 `/vworld-map` ETag 적용
|
||||
- [x] 완료 조건(사용자 확인): 계획노선 전체가 위성사진 안에 포함 및 새로고침 시 사진 재수신 없음 확인
|
||||
|
||||
#### S8-5. 브라우저 보관함 대상 확장 (포인트클라우드 제외)
|
||||
- [x] 보관함 대상 추가: 도엽 등고선·하천중심선, 위성사진
|
||||
- [x] 종단·횡단 detail 제외 (계획고 변경 반영 보장)
|
||||
- [x] B11 준비화면 사전 선적재 포함
|
||||
- [x] 완료 조건(사용자 확인): B05 새로고침 시 수신량 대폭 절감 확인
|
||||
|
||||
#### S8-6. 도엽 기준 선정: LAS 중심 → 계획노선 시·종점
|
||||
- [x] 우선순위: 노선 CSV 시·종점 -> LAS bounds 중심
|
||||
- [x] 시·종점 걸침 시 합집합 9~12매 선정 (`neighbors_for_points()`)
|
||||
- [x] 완료 조건: 표본 프로젝트 선정 결과 회귀 없음 확인 완료
|
||||
|
||||
#### S8-7. 도엽 잔재 zip 정리
|
||||
- [x] 비대상 zip 삭제 및 `map_sheets_index.json` 정리 (`prune_sheets()`)
|
||||
- [x] 완료 조건: 30매 중 비대상 21매 정분 정리 확인 완료
|
||||
|
||||
---
|
||||
|
||||
### S8 구현 결과 및 검증 완결 (2026-08-01)
|
||||
|
||||
| 항목 | 조치 | 실측 |
|
||||
|---|---|---|
|
||||
| S8-1 | `GET /surface/confirmed` 신설, B05 포인트클라우드 대체 | 23.8MB → 0.4KB, 0.018초 |
|
||||
| S8-2 | 준비 표식 signature 적용, B04 초기값 확정본 동기화 | 동기화 정상 |
|
||||
| S8-3 | 도엽 산출물 원본 그대로 + ETag 파일 전송 | 서버 2.0초 → 0.006초, 304 응답 |
|
||||
| S8-4 | 배경 범위 라이다 ∪ 노선 + 타일 여유 3, `_t=` 제거 | 731×853m → 972×1001m |
|
||||
| S8-5 | 도엽 레이어 보관함 경유 + 준비화면 선적재 | 수신량 대폭 절감 확인 |
|
||||
| S8-6 | 도엽 기준 = 노선 시·종점 우선 | 회귀 없음, 걸침 12~14매 |
|
||||
| S8-7 | 선정 외 도엽 zip 정리 | 30매 → 9매(21매 삭제) 정리 완료 |
|
||||
|
||||
**완료 확인 (사용자 검증 완결)**
|
||||
- [x] B05 진입·새로고침 속도, 배수유역도 배경(도엽 등고선) 정상 표시
|
||||
- [x] B04 다른 필터·표현 확정 시 B05 지형 동기화 완료
|
||||
- [x] 대시보드 왕복·새 브라우저·단계 이동 시 준비화면 정상 동작
|
||||
- [x] B04 [배수유역 재산정] 버튼 및 재분석(rebuild) 동작 검증 완결
|
||||
@@ -0,0 +1,116 @@
|
||||
# 2026-08-01 UI 개선 4종 (단계명 통일 / B05 패널 리사이즈 / B04 지도 레이어·라벨 / 2D 마우스 조작) (완료 계획서)
|
||||
|
||||
> 이관 이력: 2026-08-01 검증 완료 후 `docs/raw/PLAN.md` 1부에서 `docs/raw/plans/2026-08-01_plan_ui_improvements_4_types.md`로 최종 이관 백업됨.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-01_verify_ui_improvements_4_types.md`
|
||||
|
||||
---
|
||||
|
||||
## 사용자 확정 결정사항
|
||||
|
||||
| # | 논점 | 확정 |
|
||||
| -- | ------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| D1 | 패널 드래그 리사이즈 적용 범위 | **B05만**. B04는 좌측 사이드 패널 외 제어 패널 없음, B06·B07은 이번 범위 제외 |
|
||||
| D2 | 7단계 명칭 | 전부 변경. B09는`견적` → `설계도서` |
|
||||
| D3 | 2D 지도 팬 조작 | 좌버튼 드래그 팬**제거**. 가운데(휠) 버튼 드래그 팬은 기존대로 유지. 좌버튼은 선택·객체편집 전용 |
|
||||
| D4 | B04 계획선 표기 | B05의 주황색 계획선 표기를 참고해 동일 스타일로 B04 지도에도 표기 + 레이어 컨테이너 항목 추가 |
|
||||
| D5 | B05 "하단 패널 내부 우측 사이드 패널" | 배수유역·관매설 패널 |
|
||||
| D6 | 리사이즈 값 보존 | `sessionStorage` (브라우저 재접속 시 리셋). 사용자별 영구 저장은 향후 검토 |
|
||||
|
||||
---
|
||||
|
||||
## T1. 7단계 명칭 통일 (진행단계 오버레이 + 페이지 제목카드)
|
||||
|
||||
**변경 매핑**
|
||||
|
||||
| 단계 | 페이지 | 신규 명칭(ko) | 신규 명칭(en) |
|
||||
| ---- | -------------------- | ------------- | -------------- |
|
||||
| 0 | B03_FileInput | 파일입력 | File Input |
|
||||
| 1 | B04_wf1_Surface | 전처리 | Preprocess |
|
||||
| 2 | B05_wf2_Route | 종단설계 | Profile Design |
|
||||
| 3 | B06_wf3_ProfileCross | 횡단설계 | Cross Design |
|
||||
| 4 | B07_wf4_DesignDetail | 상세설계 | Detail Design |
|
||||
| 5 | B08_wf5_Quantity | 수량산출 | Quantity |
|
||||
| 6 | B09_wf6_Estimation | 설계도서 | Design Docs |
|
||||
|
||||
**체크리스트**
|
||||
|
||||
- [X] T1-1 i18n **키는 그대로 두고 값만 교체** — `WF_Step_*` 5개 + `B03_File_Title` + 페이지 제목 6개, 키 이름 무변경
|
||||
- [X] T1-2 ko/en 양쪽 7개 값 교체, 하드코딩 문구 잔존 여부 확인
|
||||
- [X] T1-3 B03~B09 좌상단 제목카드 7개가 위 표와 일치
|
||||
- [X] T1-4 B01 compact 스텝바 및 B11 상태화면 등 스텝 라벨 재사용처 동기화
|
||||
- [X] T1-5 `설계도서` 변경에 따른 B09 페이지 내부 문구 잔존 확인
|
||||
- [X] T1-6 A01 홈·A02 프로그램 소개의 6단계 설명 문구 관련 검토 완료
|
||||
|
||||
---
|
||||
|
||||
## T2. B05 하단 패널 드래그 리사이즈
|
||||
|
||||
- [X] T2-1 공통 리사이저 유틸 신설 — `ui_template/ui_template_resizer.ts` + `.css`
|
||||
- [X] T2-2 기존 드래그 구현 중복 여부 확인
|
||||
- [X] T2-3 하단 패널 높이 한계 — 최소 180px, 최대 부모 높이의 90%
|
||||
- [X] T2-4 높이 변화 시 그래프만 신축 — 테이블 높이 고정
|
||||
- [X] T2-5 배수유역 패널 폭 클램프 — 상한 70%, 하한 320px
|
||||
- [X] T2-6 `sessionStorage` 키 연결 및 복원
|
||||
- [X] T2-7 접기 충돌 방지 — CSS 변수 사용
|
||||
- [X] T2-8 코드 및 스타일 반영 완결
|
||||
|
||||
---
|
||||
|
||||
## T3. B04 2D 지도 — 계획선 표기 + 라벨 배치
|
||||
|
||||
- [X] T3-1 색·굵기 정의처 일원화 (`ROUTE_LINE_COLOR`, `ROUTE_LINE_WIDTH`)
|
||||
- [X] T3-2 계획선 토글 레이어 추가
|
||||
- [X] T3-3 노선 없는 프로젝트 대응
|
||||
- [X] T3-4 객체 표시 라벨 우측 최하단 이동 (`.b04-map__status-corner`)
|
||||
- [X] T3-5 배수유역 정보 좌측 최상단 이동 (`.b04-map__status-stack`)
|
||||
- [X] T3-6 다크모드 및 반응형 대응
|
||||
- [X] T3-7 구현 완료 및 API 신설 (`GET /api/projects/{id}/planned-route`)
|
||||
|
||||
---
|
||||
|
||||
## T4. B04·B05 2D 지도 마우스 조작 정리
|
||||
|
||||
- [X] T4-1 좌버튼 팬 제거 (가운데 버튼 팬 / 휠 줌 유지)
|
||||
- [X] T4-2 객체 편집 드래그 회귀 방지
|
||||
- [X] T4-3 빈 곳 좌드래그 무반응 처리
|
||||
- [X] T4-4 B04 지도 및 B05 배수유역도 동일 규칙 적용
|
||||
- [X] T4-5 조작감 정리 완결
|
||||
|
||||
---
|
||||
|
||||
## T5. B05 배수유역도 위성사진 토글 추가
|
||||
|
||||
- [X] T5-1 동일 양식 적용
|
||||
- [X] T5-2 순서 적용 (위성사진 → 등고선 → 세류 → 흐름 화살표 → 상류 세류)
|
||||
- [X] T5-3 기본값 on
|
||||
- [X] T5-4 캔버스 레이어 간섭 없음
|
||||
- [x] T5-5 i18n 등록 및 배수유역 패널 전체 `ui_locales` 이관 완료
|
||||
|
||||
---
|
||||
|
||||
## T6. 하드코딩 색상·문구 제거
|
||||
|
||||
- [x] T6-1 지도 팔레트 토큰 신설 (`--map-*` 33종)
|
||||
- [x] T6-2 캔버스용 색 조회 유틸 신설 (`ui_template/ui_template_palette.ts`)
|
||||
- [x] T6-3 전환 대상 6개 핵심 파일 적용 완료
|
||||
- [x] T6-4 색·굵기 정의처 이중화 방지
|
||||
- [x] T6-5 문구 `ui_locales` 이관 완료
|
||||
- [x] T6-6 제외 항목 분류 (진단용 판독문, 3D 범례)
|
||||
- [x] T6-7 테마 토큰 연동 완결
|
||||
|
||||
---
|
||||
|
||||
## T7. A01·A02 소개 페이지 단계 명칭 통일
|
||||
|
||||
- [x] T7-1 A02 6단계 제목 변경 완료
|
||||
- [x] T7-2 A01 히어로 문구 단계 명칭 변경 완료
|
||||
- [x] T7-3 제외 범위 명시
|
||||
|
||||
---
|
||||
|
||||
## 공통 마감 조건 및 검증 기록
|
||||
|
||||
- [x] C1 하드코딩 색상·문구 금지 준수
|
||||
- [X] C2 700줄 제한 준수 (`ui_template_locale.ts` 백로그 분리)
|
||||
- [X] C3 `prettier` + `ruff format`/`ruff check` 완료
|
||||
- [X] C4 `git commit` + autopush 완료
|
||||
@@ -0,0 +1,141 @@
|
||||
### B04/B05 배수유역 일원화 + 종단 테이블 구조물 연동 (2026-08-01 착수)
|
||||
|
||||
**목표**: 배수유역 데이터를 B04 한 곳으로 모으고, B05는 그 데이터를 그대로 받아 같은 것을 그린다. 관 매설 지점은 종단 테이블의 구조물 라인으로도 다뤄진다.
|
||||
|
||||
#### A. 조사 결과 — 현재 데이터 흐름의 문제 (2026-08-01 확인)
|
||||
|
||||
| 항목 | 실제 상태 | 문제 |
|
||||
|---|---|---|
|
||||
| 해석 산출물 | `sync_from_b04()`가 `B04_wf1_Surface/drainage/` 7개 파일을 `B05_wf2_Route/drainage/`로 전량 복사 | 디스크 2배(7.2MB 중복), 갱신 시점이 B05 진입에 묶임 |
|
||||
| 관 지점 정본 | `B04_.../drainage/edits/pipe_points.json` | **B05는 이 파일을 전혀 참조하지 않음**(`grep` 0건). B04에서 확정한 관이 B05에 안 보이고, B05 진입 시 자동 배치로 되돌아감 |
|
||||
| API | B04 `GET/PUT /drainage/pipe-points` + `POST /drainage/detail-basins`, B05 `POST /drainage/basins` | 같은 엔진인데 엔드포인트·응답 모양이 2벌 |
|
||||
| 사본이 생긴 이유 | "B05 편집이 B04 원본을 덮어쓴다" | **외곽선 편집을 제거(`e994330`)해 B05는 이제 아무것도 저장하지 않음 → 사본 존재 이유 소멸** |
|
||||
| 고아 데이터 | `B05_.../debug/`(삭제된 Subdivide 엔진 검증 스크립트 4 + PNG 2 + JSON 1) | **2026-08-01 삭제 완료** |
|
||||
| 좌클릭 제한 | B04는 `event.button === 0` 확인 후 마커를 잡음. **B05는 버튼 검사 없이 `pipeEditor.handleDown()` 호출** | B05에서 우클릭·가운데 클릭에도 마커가 잡힘 |
|
||||
| 유역 목록 높이 | `.b05-drainage__basins { max-height: 34% }` | 비율 기반이라 유역 수와 무관하게 잘림 |
|
||||
| 구조물 측점 | `IrregularStation{id, station, remainder, chainage_m, structure}` — 클라이언트 전용, 경로 확정 시에만 백엔드 전달 | 관 매설 지점과 아무 연결 없음 |
|
||||
|
||||
#### B. 설계 결정 (사용자 확정)
|
||||
|
||||
1. **저장소**: B05 사본 폐지, `B04_wf1_Surface/drainage/` 단일화. 두 화면이 같은 폴더·같은 `edits/pipe_points.json`을 본다.
|
||||
2. **API**: B05 전용 `POST /drainage/basins` 폐기. 두 화면 모두 `GET /drainage/pipe-points` · `POST /drainage/detail-basins` · `PUT /drainage/pipe-points` 사용.
|
||||
3. **종단 테이블 배관**: 구조물 목록에 **실제 항목**으로 등록(이름 "배관"). 단 저장 정본은 `pipe_points.json`이고 목록의 배관 항목은 그 투영이다 — 관 위치 변경은 어느 쪽에서 시작하든 `pipeEditor`를 거쳐 한 경로로 전파한다.
|
||||
4. **고아 데이터**: 1회성 수동 정리. `debug/` 삭제 완료, B05 `drainage/` 사본은 사본 폐지 구현 마지막에 삭제(먼저 지우면 `sync_from_b04`가 다시 만든다).
|
||||
5. **버튼 형태 유지**: B05 배수유역 패널의 버튼 스타일(`b05-drainage__analyze` 등)은 그대로 두고 기능만 맞춘다.
|
||||
|
||||
#### C. 구현 체크리스트
|
||||
|
||||
**C-1. 저장소·API 일원화 (선행)**
|
||||
- [X] `B05_wf2_Route_Engine_Drainage_Store.py` 삭제 — `sync_from_b04()`·`b05_drainage_dir()` 제거
|
||||
- [X] `B05_wf2_Route_Engine_Drainage_Basin.py` 삭제 — 사본 경로 어댑터라 존재 이유가 사라짐
|
||||
- [X] `B05_wf2_Route_Router_Drainage.py` 삭제 + `main.py` 라우터 등록 제거
|
||||
- [X] `config_system.py`의 `DRAINAGE_B05_DIRNAME` 제거
|
||||
- [X] B04 `Router_Basins`가 B05 요청도 받도록 확인 — 확정 경로가 없어도 동작하므로 추가 조건 불필요(`load_drainage_context`가 route 없으면 CSV 기준으로 폴백)
|
||||
- [X] 저장 시점 정리: B04는 "모델 확정", B05는 "경로 확정"에서 `PUT /drainage/pipe-points` 호출
|
||||
|
||||
**C-2. B05 프론트 API 전환**
|
||||
- [X] `B05_wf2_Route_Api_Fetch.ts`의 `fetchDrainageBasins`/`DrainageBasinResponse` 제거 → B04 클라이언트(`fetchDetailPipePoints`·`computeDetailBasins`·`saveDetailPipePoints`) 재사용
|
||||
- [X] `_UI_Drainage_Panel.ts` 응답 매핑 교체(`pipe_points`/`basins`/`strength_profile`/`main_polygon_lonlat` 등)
|
||||
- [X] B04가 주는 값 중 B05에 없던 것 반영: 2차 전체 유역 외곽선은 `detail.basin_lonlat`, 상류 세류망·평균 흐름 화살표는 기존과 동일 키 확인
|
||||
|
||||
**C-3. B05 UI 일원화 (버튼 형태 유지)**
|
||||
|
||||
플롯 대상 — 도엽등고선(있음) · 평균 흐름(있음) · 흐름 강도(있음) · 세부유역(있음) · 관 제어(있음) · 유역 선택(목록만 있음)
|
||||
|
||||
- [X] **유역 폴리곤 클릭 선택** 추가 — 현재는 목록 행 클릭만 가능. B04와 같은 `pointInRing` 판정으로 지도에서도 고르고, 유역 밖 클릭 시 해제
|
||||
- [X] **[집중유역] 토글 추가 (제안 반영)** — 유입 집중점 마커. 관을 어디에 둘지 판단하는 근거라 사용자 화면에도 필요. 기본 꺼짐, B04와 같은 규칙
|
||||
- [X] **종단 Z 출처·관 개수 요약 표기 (제안 반영)** — 세부유역이 갈리는 근거라 화면에서 확인 가능해야 함
|
||||
- [X] 관 마커 색을 B04와 같은 기준(기본/자동/수동)으로 통일할지 검토 — 현재 B05는 유역 파스텔색, B04는 생성 사유색. **B05는 유역 대응이 중요하므로 현행 유지**하고 마커 테두리로만 사유를 구분
|
||||
|
||||
**C-4. 좌클릭 전용 이동 (B04·B05)**
|
||||
- [X] B05 `pointerdown`에서 `event.button === 0`일 때만 `pipeEditor.handleDown()` 호출
|
||||
- [X] `pipeEditor.handleDown()` 자체에도 방어 조건은 두지 않고 호출부에서만 거른다(우클릭 메뉴가 같은 좌표 판정을 쓰기 때문)
|
||||
- [X] B04는 이미 좌클릭 전용 — 회귀만 확인
|
||||
|
||||
**C-5. 유역 목록 3행 스크롤**
|
||||
- [X] `.b05-drainage__basins`의 `max-height: 34%`를 행 높이 기준 `calc(3 * 행높이 + 패딩)`으로 교체. 행 높이는 CSS 변수로 한 곳에 정의
|
||||
|
||||
**C-6. 종단 테이블 구조물 라인 연동**
|
||||
- [X] `_UI_IrregularStations.ts` — 외부에서 "배관" 항목을 통째로 갈아 끼우는 API 추가(`setPipeStations(chainages)`), 사용자 수동 항목과 구분되는 표식(`origin: "pipe" | "user"`)
|
||||
- [X] 관 목록 → 구조물 목록 투영: 유역 산정 결과가 오면 "배관" 항목을 그 누가거리로 재생성
|
||||
- [X] **테이블 라인 드래그 이동** — `_UI_Profile_Table.ts`의 `is-floating` 컬럼에 포인터 드래그 부착. 배관·기타 구조물 모두 이동 가능
|
||||
- 배관을 옮기면 → `pipeEditor` 갱신 → 세부유역 즉시 재산정(지도와 같은 경로)
|
||||
- 기타 구조물은 목록 chainage만 갱신
|
||||
- [X] **테이블 우클릭 메뉴** — `ui_template_context_menu.ts` 재사용. 라인 위: "배관 삭제"/"구조물 삭제", 빈 자리: "배관 추가"
|
||||
- [X] 이름은 이번 범위에서 "배관" 고정. 드롭다운·수기 입력은 후속(현재 자유 텍스트 입력 폼은 그대로 둔다)
|
||||
|
||||
**C-7. 정리·마감**
|
||||
- [X] `B05_wf2_Route/drainage/` 사본 폴더 삭제(C-1 완료 후, 재생성되지 않는 것 확인하고)
|
||||
- [X] `_Engine_Drainage_Basin.py` 주석에 남은 `boundary_overrides.json` 언급 제거(파일 삭제로 자연 해소)
|
||||
- [X] `ruff format` / `prettier` / `ruff check` / `tsc --noEmit` / `vite build`
|
||||
- [X] 700줄 규칙 확인 — **위험 파일**: `_UI_Profile_Table.ts`(563줄, 드래그·메뉴 추가 예정), `_UI_Profile_Panel.ts`(690줄, 배선 추가 예정). 초과 시 즉시 분리
|
||||
- [X] 서버 기동 검증 후 커밋 → `.claude/git-autopush.ps1`
|
||||
|
||||
#### C-8. 구현 중 추가된 분리 (700줄 규칙)
|
||||
|
||||
작업 중 세 파일이 한계를 넘어 분리했다.
|
||||
|
||||
| 파일 | 전 → 후 | 분리 대상 |
|
||||
|---|---|---|
|
||||
| `_UI_Drainage_Panel.ts` | 787 → 690 | `_UI_Drainage_Render.ts`(캔버스 렌더러), `_UI_Drainage_Parts.ts`(토글 줄) |
|
||||
| `_UI_Profile_Panel.ts` | 729 → 669 | `_UI_Profile_Data.ts`(종단 자료 어댑터) |
|
||||
| `_UI_Page.ts` | 702 → 683 | 세션 캐시 헬퍼를 `_Api_Fetch.ts`로 이동 |
|
||||
| `_UI_Profile_Table.ts` | 563 → 577 | 구조물 라인은 `_UI_Profile_Structures.ts` 신설로 흡수 |
|
||||
|
||||
#### D. 검증 기준
|
||||
|
||||
1. B04에서 관을 옮기고 확정 → B05 진입 시 **같은 위치**에 같은 개수의 관과 같은 세부유역이 보인다.
|
||||
2. B05에서 관을 옮기면 지도·유역 목록·종단 테이블 배관 라인이 **동시에** 움직인다.
|
||||
3. 종단 테이블에서 배관 라인을 끌면 지도의 관 마커와 세부유역이 따라 바뀐다.
|
||||
4. `storage/.../B05_wf2_Route/drainage/`가 다시 생기지 않는다.
|
||||
5. B05 지도에서 우클릭·가운데 클릭으로는 관이 잡히지 않는다.
|
||||
6. 유역이 4개 이상이면 목록에 스크롤이 생기고 3행까지만 보인다.
|
||||
|
||||
#### D-1. 실행 검증 결과 (2026-08-01, 서버 기동 상태)
|
||||
|
||||
| 확인 항목 | 결과 |
|
||||
|---|---|
|
||||
| 통합 엔드포인트 | `GET/PUT /drainage/pipe-points` · `POST /drainage/detail-basins` · `GET /drainage/primary-region` · `GET /drainage/road-inflow` |
|
||||
| `POST /drainage/basins`(B05 전용) | **404** — 폐기 확인 |
|
||||
| 통합 응답 필드 | `pipe_points` 4 · `basins` 4 · `inflow_hotspots` 10 · `flow_arrows` 377 · `upstream_lonlat` 16 · `main_polygon_lonlat` 122점 · `strength_profile` · `z_source` — **누락 없음** |
|
||||
| B05 사본 재생성 | 삭제 후 API 호출해도 **다시 생기지 않음** |
|
||||
| 품질 게이트 | `ruff check` 통과 · `tsc --noEmit` 통과 · `vite build` 성공 |
|
||||
| 700줄 규칙 | 배수유역·종단 관련 파일 전부 통과(최대 `_UI_Drainage_Panel.ts` 690줄) |
|
||||
|
||||
화면 조작(테이블 라인 드래그·우클릭, 유역 클릭 선택, 3행 스크롤)은 브라우저에서 사용자 확인이 필요하다.
|
||||
|
||||
#### F. 사용자 확인 후 후속 조치 (2026-08-01, 즉시 수정)
|
||||
|
||||
화면을 보고 내려온 지시 3건. 구조물 조작의 자리를 테이블에서 그래프로 옮겼다.
|
||||
|
||||
- [X] **테이블 세로선 제거** — 구조물 위치는 그래프 측점선이 이미 보여 주므로 테이블에까지 얹으면 같은 것을 두 번 그리는 셈. `_UI_Profile_Structures.ts`의 라인 레이어와 관련 CSS를 걷어내고 **우클릭 메뉴만** 남겼다(`mountStructureLines` → `mountStructureMenu`).
|
||||
- [X] **드래그 위치 이동** — 종단 **그래프의 구조물 측점선**을 끌어 옮긴다. `createLongitudinalProfile()`에 `onDragStation` 인자를 추가하고, `kind === "irregular"` 측점선에만 끌기를 붙였다(`attachStationDrag`, 좌클릭 전용·3px 이하는 고르기). 화면 x → 누가거리 역변환은 `inverseOf()`.
|
||||
- [X] **좌측 사이드 패널 편집 전파** — 구조물 폼에서 "배관" 항목을 고치거나 지우면 그래프·배수유역도가 함께 갱신된다. `DrainagePanel.setPipeChainages()` 신설, Page의 `applyIrregularStations()`에서 `origin === "pipe"` 항목만 뽑아 넘긴다.
|
||||
- 되먹임 고리 차단: `reconcilePipes()`가 현재 목록과 같으면 `null`을 돌려 아무 일도 하지 않는다(관 목록 → 구조물 목록 → 관 목록으로 돌아오는 순환이 여기서 끊긴다). 생성 사유(기본/자동/수동)는 자리로 맞춰 이어 붙인다.
|
||||
- [X] **손으로 넣은 "배관"도 관 지점 정본을 따르게** — 사이드바 폼으로 이름을 "배관"이라 적어 넣은 항목은 `origin: "user"`라 [초기화]로 지워지지 않고 배수유역도와도 어긋난 채 남았다. `isPipeStation()`(origin이 pipe이거나 이름이 "배관")으로 판정을 통일해 목록 교체·이동·삭제·선택 동기화가 모두 같은 규칙을 쓴다.
|
||||
- [X] **첫 진입 시 배수유역도가 비어 보이던 문제** — 대시보드에서 곧장 들어오면 패널이 배치되기 전(0×0)에 `fitToRoute()`가 돌아 엉뚱한 배율이 굳었다(새로고침하면 보이던 이유). 크기가 2px 미만이면 맞춤을 미뤘다가 첫 배치 때 다시 맞춘다(`fitPending`).
|
||||
- [X] **관을 옮겨도 옛 자리 세로선이 남던 문제(진짜 원인)** — 구조물 측점이 **두 곳**에 있었다. ① 화면 정본인 사이드바 목록(`irregularStations`), ② 경로 확정 때 종단 정본 파일에 병합된 `kind: "irregular"` 측점(`_Router_Confirm._merge_irregular_into_longitudinal`). 그래프(`normalizedLongitudinal(...).stations`)와 3D(`renderStationLines`)가 둘을 **겹쳐** 그려서, 관을 옮기면 ①만 따라가고 ②는 확정 당시 자리에 그대로 남았다. 앞선 `isPipeStation()` 수정이 ①만 다뤄 증상이 남아 있었다.
|
||||
- 확인: `storage/1/3/{project}/B06_wf3_ProfileCross/longitudinal/longitudinal.json`에 `배관` 94.97 / 149.73 / 200.92 / 275.71이 저장돼 있었고, 현재 자동 배치는 84.3 / 149.73 / 200.92 / 275.71 — 화면에 남던 유령이 94.97이다.
|
||||
- 수정: 그래프·3D 모두 종단 정본의 `kind === "irregular"`를 걷어내고 사이드바 목록만 얹는다(`_UI_Profile_Panel.draw()`, `_UI_Page.renderStationLines()`). 저장분은 진입 시 `restoreSections()`가 목록으로 되살리고, 관 항목은 배수유역도가 `setPipeStations()`로 정본(`pipe_points.json`)에 맞춰 갈아 끼운다. 두 저장소는 [모델 확정] 시점에 다시 합류한다(`confirm()`이 `savePipes()` → `confirmRoute(irregular_stations)` 순으로 보낸다).
|
||||
- [X] **2D 화면 휠 방향 반전(전 페이지 통일)** — 휠을 **당기면 확대, 밀면 축소**. B04 2D 지도(`_UI_MapViewer.ts`)·B05 배수유역도(`_UI_Drainage_Panel.ts`)·B06 횡단도(`_UI_Cross_View.ts`)는 `deltaY < 0` → `deltaY > 0`으로, B07 CAD는 `input-controller.ts`의 `zoomScreen(evt.deltaY)` → `zoomScreen(-evt.deltaY)`로 뒤집었다(openwebcad 계산식은 손대지 않는다 — 부호만 넘긴다). 커서 고정 보정은 모두 그대로. 3D 뷰어(`_UI_Camera.ts`)는 지시 밖이라 손대지 않았다.
|
||||
- B07은 별도 번들이라 `npm run build:b07-cad` 재실행이 필요하다(수행 완료).
|
||||
- [X] **측점 선택 하이라이트가 표를 가리던 문제** — 하이라이트 열이 덮는 자리의 규칙 측점 값 셀을 `hideCoveredCells()`로 통째로 숨겨(`visibility: hidden`), 비정규 측점을 고를 때마다 좌우 최대 2개 열의 값이 사라졌다. 표는 그대로 두고 선택된 값만 위에 얹도록 그 함수와 `.is-covered` CSS를 걷어냈다(2026-08-02 사용자 지시).
|
||||
- [X] **하이라이트 열의 계획고 추가 강조 삭제** — 계획고만 자주색 테두리·글자색으로 따로 강조돼 같은 열의 다른 값과 달라 보였다. 평상시에는 다른 셀과 같은 모습이고, 고칠 수 있다는 표시는 hover·focus에서만 나타나게 했다(`.b05-profile-table__irregular-input`). 직접 입력 기능은 그대로.
|
||||
- [X] **[초기화]가 저장분을 남기던 문제** — 화면만 자동 배치로 되돌리고 `edits/pipe_points.json`은 그대로 두어, 다시 들어오면 옛 관이 살아났다(사용자 보고). `DELETE /{project_id}/drainage/pipe-points` 신설 — 저장분과 파생물(`04_detailed_basins.geojson`)까지 지우고 자동 배치 결과를 돌려준다. B05 [초기화] 버튼이 이 경로를 쓴다.
|
||||
- 확인: 초기화 전 `{user:1, stream:1, spacing:2}` · 초기화 후 `{stream:1, spacing:3}` · 두 파일 삭제됨 · 재조회도 같은 자동 배치.
|
||||
- [X] **B05 유역 영역 클릭 선택 복구(회귀 수정)** — `pointerdown`에서 `basinClickStart`를 두는 코드가 실제로는 들어가지 않아, 클릭 후보가 항상 `null`이 되어 `pickBasinAt()`이 한 번도 불리지 않았다. 같은 편집에 묶여 있던 **B05 좌클릭 전용 제한(C-4)** 도 함께 누락돼 있었다(포맷 변경 뒤 문자열이 어긋나 치환이 적용되지 않은 것). 둘 다 넣고 적용 여부를 grep으로 확인했다.
|
||||
- [X] **B04 집중유역 오버레이를 최상단으로** — `FlowStrengthOverlay.draw()`를 강도 색칠만 남기고, 집중점 마커·유입 외곽선은 새 `drawTop()`으로 떼어 세부유역·관 마커 **뒤에** 그린다. 켰을 때 무엇에도 가리지 않아야 고를 수 있다.
|
||||
- [X] **패널 크기를 바꿔도 지도 배율 고정** — 하단 패널·우측 배수유역 패널을 늘리면 `computeMapRect()`가 지도를 뷰포트에 다시 맞춰(contain) 함께 확대돼, 방금 보던 자리를 다시 찾아야 했다. 화면 변환 계수(ax·bx)가 그대로가 되도록 배율·오프셋을 되계산해 **늘린 만큼 더 넓은 범위를 보여 준다**(`preserveViewOnResize()`·`observeViewportSize()` in `_Drainage_Parts.ts`).
|
||||
- [X] **흐름 강도 색띠 범례(게이지 바)** — 지도 우측에 세로로 세운다. B04 2D 지도·B05 배수유역도 공용(`createFlowLegend()` in `_UI_FlowRamp.ts`, `ui_template_flow_legend.css`). 색은 화면과 같은 색띠, 눈금은 같은 로그 정규화(`expm1(t·log1p(max))`)로 되돌려 적는다 — 다른 규칙으로 그리면 범례가 오독을 만든다. 제목은 "흐름강도".
|
||||
- [X] **배관 누가거리 라벨 가독성** — 흰 테두리(`--map-halo`)를 깔고 그 위에 글자를 얹는다. `--map-*` 토큰은 다크 테마에서 바뀌지 않으므로 색만 바꿔서는 위성사진·다크 배경에서 묻힌다.
|
||||
- [X] **선택 3자 동기화** — 배수유역 영역 ↔ 종단 그래프 세로선 ↔ 좌측 구조물 폼(+3D 마커)이 서로를 갱신한다. `_UI_Selection.ts` 신설로 네 곳의 선택 경로를 한 모듈에 모으고, `isSyncing` 가드로 재진입을 막는다. 배수유역 강조는 `origin === "pipe"` 항목에만 붙는다(다른 구조물에는 대응 유역이 없다).
|
||||
- `DrainagePanel`: 유역 강조를 바꾸는 자리를 `selectBasin()` 하나로 모으고, 밖에서 고를 통로 `selectBasinByChainage()`와 알림 `onBasinSelected`를 냈다.
|
||||
- [X] **유역 서클 번호를 최상단 오버레이로** — 채움과 함께 그리던 것을 떼어내 **맨 마지막**에 얹는다. B04는 `drawBasinNumbers()` 호출을 관 마커 뒤로 옮겼고, B05는 `drawFilledRing()`에서 라벨을 빼고(`drawRingBadge()`·`ringCenterOnScreen()` 신설) 배관 마커 뒤에 그린다. 등고선·화살표·관 마커에 가리지 않는다.
|
||||
- [X] **우클릭 메뉴도 그래프로 이동** — 배관 추가·삭제를 테이블에서 떼어 종단 그래프 칸(`.b05-profile__chart`)에 붙였다. 구조물 조작(이동·추가·삭제)이 전부 한 자리에 모인다. 측점선 근처면 "배관/구조물 삭제", 빈 자리면 "배관 추가"(선 대신 화면 거리로 판정). 테이블은 값만 보여 준다.
|
||||
- [X] 700줄 규칙 유지를 위한 추가 분리: `reconcilePipes()`·`fetchDrainageLayers()`·`fitViewToRoute()` → `_Drainage_Parts.ts`, 오버레이 도형(`drawFilledRing`·`drawRingBadge`·`drawRidgeRing`·`drawUpstreamLines`·`ringCenterOnScreen`) → `B04_wf1_Surface_UI_MapOverlays.ts` 신설, 선택 동기화 → `_UI_Selection.ts` 신설.
|
||||
|
||||
#### E. 미결 (이번 범위 밖)
|
||||
|
||||
1. 구조물 이름 드롭다운/수기 입력 — 이번엔 "배관" 고정. 그 외 미결은 '향후 작업'에 있다.
|
||||
|
||||
#### J. 이번 세션 커밋 목록
|
||||
@@ -0,0 +1,94 @@
|
||||
# [C04] 미결사항 전면 재정리 및 토공 운반장비 선정거리 확정
|
||||
|
||||
> 이 문서는 `docs/raw/PLAN.md`의 「현재 작업」에서 검증 완료 후 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-02_verify_backlog_reorg.md`
|
||||
> 검증 판정: **CONDITIONAL PASS** — 기능·판단 결함 없음. 문서 기재 오류 1건(F5) 아래에서 정정함.
|
||||
|
||||
---
|
||||
|
||||
### [C04] 미결사항 전면 재정리 및 장비 선정거리 확정 (2026-08-02)
|
||||
|
||||
사용자와 미결사항 8개 항목을 하나씩 검토해 **코드 반영 1건 + 판단 확정 3건 + 조사 결론 2건 + 항목 삭제 3건**을 처리했다. 향후 작업에 남아 있던 미해결 항목 중 이번에 해결된 것을 여기로 옮겨 아카이빙 대상으로 남긴다.
|
||||
|
||||
#### 1. 토공 운반장비 선정거리 config 반영 (코드 변경)
|
||||
|
||||
- **결정**: 종무대 ≤20m / 도쟈 ≤50m / 그 이상 덤프. 3공구 실제 도면 관측치(종무대 14~16m, 도쟈 20m, 덤프 67.8m)와 모순 없다.
|
||||
- **구현**: `config/config_system.py` **5-4-5절** 신설.
|
||||
```python
|
||||
EARTHWORK_HAUL_EQUIPMENT_LIMITS_M = (
|
||||
("free_haul", 20.0), # 종무대
|
||||
("dozer", 50.0), # 도쟈
|
||||
("dump_truck", None), # 덤프 — 나머지 전부
|
||||
)
|
||||
```
|
||||
- **단일 정의처 규칙**: 이 경계는 오직 이 상수 한 곳에서만 정의·참조한다.
|
||||
- **일반 토목 문헌과의 차이(기록용)**: 일반 도로토공 자료는 무대 20m / 도쟈 70m / 스크레파 500m / 덤프 500m 이상을 쓴다. 임도는 연장이 짧고 스크레파를 쓰지 않으므로 **도면 기준값을 채택**했다. 나중에 조정 시 이 차이를 근거로 판단한다.
|
||||
- **커밋**: `e684131`. `ruff format` / `ruff check` 통과, `config_system.py` 604줄.
|
||||
|
||||
#### 2. `M.N` 성격 확정 (판단)
|
||||
|
||||
평탄화 후 유토곡선과의 마지막 교차 측점을 가리키는 **위치값**이다. 토량 계산에 관여하지 않으므로 Phase 2에서 표기만 하면 된다. 선행 조건에서 내렸다.
|
||||
|
||||
#### 3. 「누가토량 vs 사토 합계 11배 모순」 — 모순 아님 (오기록 정정)
|
||||
|
||||
최종 누가토량은 **사토 + 리핑암 + 발파암 전부의 합**이므로 사토 합계와 다른 것이 정상이다. C02 분석 문서에 「모순」으로 적어 둔 것은 정의 차이를 오인한 것이며, 이를 정정하고 향후 작업 1번(암반 경계선 분리)의 근거로 전환했다.
|
||||
|
||||
#### 4. 토량환산계수 성격 확정 (판단)
|
||||
|
||||
**돈이 아니라 부피 보정값**이다. 같은 흙도 상태에 따라 부피가 달라진다.
|
||||
|
||||
| 계수 | 정의 | 쓰이는 곳 |
|
||||
| -------------- | ------------------------------ | ----------------------------------------- |
|
||||
| `L` (팽창률) | 흐트러진 부피 ÷ 자연상태 부피 | 트럭 적재 대수 =**운반량** |
|
||||
| `C` (다짐률) | 다진 부피 ÷ 자연상태 부피 | 성토에 실제 들어가는 양 =**소요량** |
|
||||
|
||||
절토 100㎥와 성토 100㎥를 그대로 뺄 수 없는 이유이며, 유토곡선이 다짐상태로 기준을 통일하는 근거다. 비용과는 **간접적으로만** 연결된다 — 수량이 확정돼야 B08/B09에서 단가를 곱한다. 실무값 확정은 향후 작업 3번으로 남는다.
|
||||
|
||||
#### 5. `raw/guidelines/design.md` 조사 결론
|
||||
|
||||
위키 `index.md` "⚠️ 미해결 항목"이 "Aislo와 무관한 Wiza 스타일 가이드, ingest하지 않음"으로 적어 둔 것은 **사실이 아니다.**
|
||||
|
||||
- 토큰이 `ui_template_theme.css`와 정확히 일치한다 — `--color-royal-amethyst: #3e0079`(`:18`), `--color-deep-iris` / `--color-mist-violet` / `--color-canvas` / `--color-paper` / `--color-ash` 전부 존재, `--text-caption: 12px`(`:95`), 8px 간격 스케일, Britti Sans / Inter.
|
||||
- `wiki/concepts/design.md`로 **이미 ingest 되어 있다**(2026-07-19). `index.md`의 `[[design]]` 항목도 등록돼 있다.
|
||||
- "Wiza"는 스타일 가이드 생성 도구가 붙인 제품명일 뿐, 내용은 이 프로젝트의 실제 디자인 시스템이다.
|
||||
- C02의 유토곡선 곡선 색으로 쓴 `--color-royal-amethyst`가 이 가이드의 토큰이다 — 실제로 참조해 작업했다.
|
||||
|
||||
**결론**: 디자인 가이드는 정상 동작 중이며, 같은 `index.md` 안에서 자기모순을 일으키는 낡은 경고문만 남아 있다. 정정 작업은 위키 관리자 몫으로 향후 작업 7번에 유지한다.
|
||||
|
||||
#### 6. 「A그룹 보안정책 일관성 검토」 조사 결론 — 불일치 실재하지 않음
|
||||
|
||||
위키 `wiki/pages/A09_Security/A09_frontend.md:69-73`이 "약관 텍스트와 실 코드 불일치"로 적어 둔 내용을 코드로 대조한 결과, **세 곳이 이미 일치한다.**
|
||||
|
||||
| 출처 | 값 |
|
||||
| ---------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| 약관`A09_Security_Terms.ts:13` | 12시간 / 4시간 비활동 |
|
||||
| config`config_system.py:610-611` | `SESSION_MAX_AGE_SECONDS=43200`(12h), `SESSION_IDLE_TIMEOUT_SECONDS=14400`(4h) |
|
||||
| 실동작`verify_session()` | 위 두 상수로 판정 |
|
||||
|
||||
위키가 지적한 `auth_expires_at = NOW() + 3개월`은 `007_trusted_device_token.sql:17`에서 **컬럼째 DROP** 됐고, 지금은 `last_email_verified_at + EMAIL_REVERIFY_DAYS(90일)` 파생 계산이다(`B01_Dashboard_Repository.py:54`).
|
||||
|
||||
**원인은 개념 혼동이다** — 세션 만료(12h/4h)와 이메일 재인증(90일)은 별개인데 위키가 둘을 같은 것으로 보고 "불일치"로 기록했다. 2026-07-17 디바이스 토큰 도입 때 정리된 내용이 A09 페이지에 반영되지 않았다. 남은 실물은 **미사용 코드 2건**뿐이라 향후 작업 6번으로 흡수했고, 위키 정정은 7번에 유지한다.
|
||||
|
||||
#### 7. 미결사항 3건 삭제 (사용자 판단, 취소선 아닌 완전 삭제)
|
||||
|
||||
| 삭제 항목 | 사유 |
|
||||
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| B06 진행단계 오버레이가 차트 우측을 가림 | 가리기는 하나 문제없음. 필요 시 사용자가 직접 변경 |
|
||||
| `docs/`가 `.gitignore` 대상 | **의도된 설정.** Gitea를 다른 사용자와 함께 쓰는데 이 폴더를 공유하고 싶지 않아서이며, 본인 PC 간에는 시놀로지 드라이브로 동기화 중이라 문제없다 |
|
||||
| B그룹 후반부(B08~B09) 및 견적자동화 구현 | 다른 담당자가 작업 중. 이 세션의 관심사가 아니다 |
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 검증 단계 정정 사항 (2026-08-02)
|
||||
|
||||
- **[F5] `config_system.py` 줄 수 오기**: 본문에 "604줄"로 적혀 있으나 **실제 664줄**이다.
|
||||
`git show e684131:config/config_system.py | wc -l` = 664, HEAD = 664, 작업본 = 664로
|
||||
커밋 시점부터 664줄이었다. 기능 영향은 없으나 **700줄 제한 여유가 36줄뿐**이므로
|
||||
다음 상수 추가 시 파일 분할을 먼저 검토해야 한다.
|
||||
- **법령 역검증 통과**: [임도의 설계 및 시설기준] 별표 원문에 `무대운반`·`도쟈`·`스크레이퍼`·
|
||||
`덤프트럭`·`토량변화율`·`유토곡선` 어느 단어도 등장하지 않음을 확인했다. 장비 선정거리의
|
||||
근거가 법령이 아니라는 본문의 단정이 옳다.
|
||||
- **A그룹 보안정책·`design.md` 조사 결론**: 코드·DB 마이그레이션·파일 존재 여부로 재대조해
|
||||
전부 사실로 확인했다. 상세는 검증 보고서 §2-④⑤ 참조.
|
||||
@@ -0,0 +1,118 @@
|
||||
# 완료 이력 - B06 유토곡선 결합 및 B05 배수유역 패널 정정
|
||||
|
||||
- **완료 일자**: 2026-08-02
|
||||
- **기능명**: B06 유토곡선(Mass Haul Diagram) 패널 결합 및 B05 배수유역 패널 정정
|
||||
- **이관 소스**: `docs/raw/PLAN.md` [C02] 및 [C03]
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요 및 참고 자료
|
||||
이 이력 문서는 B06 단계의 신규 유토곡선 기능 개발과 B05 단계 배수유역 패널 정정 작업이 완료되었음을 기록하고 아카이빙합니다.
|
||||
|
||||
이번 아카이빙 작업을 수행하며 사용자가 제공한 두 기획/분석 명세 파일인 다음 문서를 함께 계획서 영역(`docs/raw/plans/`)으로 이관 및 보관하여 통합 관리하도록 하였습니다.
|
||||
|
||||
- **기획/분석 참고서 1**: [2026-07-24_유토곡선_분석_및_웹앱_계산명세.md](file:///C:/Program_coding/임도설계 및 견적자동화 프로그램 개발/docs/raw/plans/2026-07-24_유토곡선_분석_및_웹앱_계산명세.md) (1공구 기준)
|
||||
- **기획/분석 참고서 2**: [2026-08-02_유토곡선_3공구_분석.md](file:///C:/Program_coding/임도설계 및 견적자동화 프로그램 개발/docs/raw/plans/2026-08-02_유토곡선_3공구_분석.md) (3공구 기준 보강)
|
||||
|
||||
---
|
||||
|
||||
## 2. 작업 계획 및 결정 사항
|
||||
|
||||
### [C02] B06 유토곡선(Mass Haul Diagram) 패널
|
||||
|
||||
**배경**
|
||||
- 참고 도면: `13 유토곡선(울진 울진 대흥 산65 외2(3공구)).pdf`
|
||||
- 분석 결과: `docs/raw/2026-08-02_유토곡선_3공구_분석.md` (기존 `2026-07-24_유토곡선_분석_및_웹앱_계산명세.md` 보강본)
|
||||
- 개념 정의: `[[mass_haul_diagram]]`
|
||||
|
||||
**결정 사항 (2026-08-02 사용자 승인)**
|
||||
|
||||
| 결정 | 내용 |
|
||||
|---|---|
|
||||
| 배치 | 하단 패널이 아니라 **종단도 바로 아래**. 종단도 + 유토곡선을 **하나의 상단 슬라이드 패널**로 묶어 접기/펼치기 |
|
||||
| 정렬 | 종단도와 유토곡선의 **측점 X축 위치가 일치**해야 함 (사용자 편의 목적) |
|
||||
| 계산 위치 | **프론트엔드 계산 → 메모리 캐시 → 확정 시점에만 B06 영구저장소 저장.** 측점 조작마다 백엔드 왕복하면 네트워크 지연이 체감됨 |
|
||||
| 토량환산계수 | 외부 자료 조사 후 `config/config_system.py`에 기본값 신설. 사용자가 값을 직접 조정해 보고 판단 |
|
||||
| 패널 크기 | B05 하단 슬라이드 패널처럼 **사용자가 높이 조절**. 단 종단도는 현재 높이 유지 규칙 적용(아래 참조) |
|
||||
| `M.N` | 외부 조사에서 정의 확인 실패 → **구현 보류** |
|
||||
| 최종 누가토량 vs 사토 합계 11배 모순 | 수작업 도면 오류 가능성. **아는 범위에서 구현하고 나중에 개선** |
|
||||
|
||||
**범위 가정**
|
||||
- 이번(Phase 1): **누가토량 곡선 + 총괄 요약**까지.
|
||||
- 다음(Phase 2): 평형선 자동 탐색, 운반토량 `Q`, 평균운반거리 `L`, 종무대/도쟈/덤프/사토 balloon.
|
||||
- Phase 2를 분리하는 이유: 장비 거리 경계(관측값 종무대 14~16m / 도쟈 20m / 덤프 68m)와 `M.N` 정의가 미확정이다. 근거 없는 자동 배분은 실무 오판을 유발한다.
|
||||
|
||||
**계산 기준 — 다짐상태 (외부 조사로 확정)**
|
||||
> "운반거리의 산정 시에 모든 수량은 **다짐상태로 환산**하여 계산하고, 내역서에 적용하는 수량은 자연상태로 한다." (2021년도 국도건설공사 설계실무 요령 / 2016 건설공사 표준품셈 계열)
|
||||
|
||||
유토곡선은 운반계획 도면이므로 **다짐상태 기준**으로 계산한다. 기존 명세(`2026-07-24_...md` 4.1절)의 `N = C − F/L`은 자연상태 기준 표기이며, 기준상태만 다른 같은 물리량이다. 내역서용 자연상태 수량은 B08에서 되돌려 쓴다. 근거·수치 출처는 `docs/raw/2026-08-02_유토곡선_3공구_분석.md` 8절.
|
||||
|
||||
**계산 명세 (프론트, 순수 함수)**
|
||||
1. `cross_sections`를 `chainage_m` 오름차순 정렬, `design` 보유 측점만 사용
|
||||
2. 인접 측점 구간거리 `Δd = chainage[i+1] − chainage[i]` (측점번호로 거리 추정 금지, 실제 누적거리 사용)
|
||||
3. 평균단면법: `V_cut = (cut_area[i] + cut_area[i+1]) / 2 × Δd`, `V_fill`도 동일
|
||||
4. 지반유형 분리: 각 측점의 `cut_area × Δd / 2`를 그 측점의 `ground_type`(토사/리핑암/발파암)에 귀속 → 구간별 EA/RR/BR 산출 (양 끝 지반이 다른 구간도 사다리꼴 절반 규칙으로 일관 처리)
|
||||
5. 다짐 환산: 지반유형별 절토량(자연상태)에 그 지반의 다짐률 `C`를 곱해 합산
|
||||
6. 순토량 `N(i) = Σ[절토량(지반별) × C(지반별)] − V_fill` (성토량은 이미 다짐상태)
|
||||
7. 누가토량 `M(i) = M(i−1) + N(i)`, 첫 측점 `M = 0`
|
||||
8. 총괄: 총 절토(EA/RR/BR 분리, **자연상태·다짐상태 병기**), 총 성토, 최종 누가토량. (양수 → 사토, 음수 → 토취)
|
||||
|
||||
**토량환산계수 기본값 (조사 근거, config에서 조정 가능)**
|
||||
L = 흐트러진 상태 / 자연상태(팽창률), C = 다져진 상태 / 자연상태(다짐률).
|
||||
|
||||
| B06 지반유형 | 대응 암종 | L | C |
|
||||
|---|---|---:|---:|
|
||||
| 토사 `soil` | 풍화토~점토 중간 | 1.25 | 0.90 |
|
||||
| 리핑암 `ripping_rock` | 풍화암~연암 하단 | 1.35 | 1.15 |
|
||||
| 발파암 `blasting_rock` | 보통암 중앙 | 1.60 | 1.30 |
|
||||
|
||||
임도 전용 고시 수치가 아니라 **암종 정의에 근거한 제안값**이다. 표준품셈도 "토질 시험하여 적용하는 것을 원칙으로 하되 소량인 경우 환산계수표에 따를 수 있다"고 하므로 현장별 조정이 전제다.
|
||||
|
||||
**패널 높이 규칙 (2026-08-02 사용자 지시)**
|
||||
기본 패널 높이 `H₀ = 종단도(LONG_HEIGHT 220px) + 유토곡선 기본 높이 + 헤더·여백`
|
||||
|
||||
| 상태 | 종단도 높이 | 유토곡선 높이 |
|
||||
|---|---|---|
|
||||
| `H ≥ H₀` (키운 경우) | **220px 고정** | 늘어난 몫을 전부 흡수 |
|
||||
| `H < H₀` (줄인 경우) | `220 × H/H₀` (**같이 축소**) | `기본높이 × H/H₀` |
|
||||
|
||||
B05 하단 패널(`B05_wf2_Route_UI_Profile_Panel.ts:272`)의 `createPanelResizer` 패턴을 그대로 쓰되 **손잡이가 패널 아래쪽**이므로 `direction: 1`(아래로 끌면 커짐)이다. 크기는 `sessionStorage`에만 남는다.
|
||||
|
||||
**정렬 구현 방식**
|
||||
- 종단도 X 매핑은 `B06_wf3_ProfileCross_UI_Longitudinal.ts:195`
|
||||
`x(chainage) = LONG_PAD.left + originOffsetPx + (chainage / maxChainage) × plotWidth`
|
||||
- 유토곡선은 `LONG_PAD`, `widthPx`, `minimumWidthPx`, `maxChainage`, `originOffsetPx`를 **종단도와 동일 인자로** 받아 같은 식을 쓴다.
|
||||
- 두 SVG를 **같은 `.b06-section__chart-wrap` 하나**(`overflow-x: auto`) 안에 넣는다. wrap을 따로 두면 가로 스크롤이 각자 움직여 정렬이 깨진다.
|
||||
|
||||
---
|
||||
|
||||
### [C03] B05 배수유역 패널 — 기본 펼침 + 접기 화살표 방향 정정
|
||||
|
||||
**요구사항**
|
||||
- 하단 슬라이드 패널 내부 **우측** 배수유역도 패널이 페이지 진입 시 **보이는 것이 기본값**이어야 한다.
|
||||
- 접기/펼치기 트리거 버튼의 **화살표 방향이 반대**로 나온다.
|
||||
|
||||
**원인 및 분석**
|
||||
- 기본값: `setCollapsed(sessionStorage.getItem(COLLAPSED_KEY) !== "false")` — 저장값이 없으면 `null !== "false"`가 참이라 접힘으로 시작했다.
|
||||
- 화살표: 공용 `createWorkflowPanelHandle("side")`가 왼쪽에 도킹해 왼쪽으로 접히는 패널(좌상단 제목 카드)을 전제로 회전값을 고정하고 있었다. 배수유역 패널은 오른쪽에 도킹해 오른쪽으로 접히므로 같은 회전을 쓰면 열기/닫기 표시가 정확히 거꾸로 나온다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 구현 체크리스트 및 완료 내역
|
||||
|
||||
- [x] 1. `config/config_system.py:439-443`에 `5-4-3. 토량환산계수` 절 신설 — `EARTHWORK_CONVERSION_FACTORS` (토사 L1.25/C0.90, 리핑암 L1.35/C1.15, 발파암 L1.60/C1.30)
|
||||
- [x] 1-1. `config/config_system.py:462-466`에 `5-4-5. 토공 운반장비 선정 거리 경계` 신설 — `EARTHWORK_HAUL_EQUIPMENT_LIMITS_M` (종무대 20m, 도쟈 50m, 덤프 초과)
|
||||
- [x] 2. `_Schema.py` — `EarthworkConversionFactor` 모델 및 `SectionContextResponse.earthwork_conversion` 추가. `_Router.py` context 응답에 config 값을 그대로 실어 보냄.
|
||||
- [x] 3. `_Api_Fetch.ts` — `EarthworkConversionFactor`/`EarthworkConversion` 타입 및 context 필드 추가, `confirmSections()`에 `massHaul` 파라미터 추가.
|
||||
- [x] 4. **신규** `_UI_MassHaul.ts` — `computeMassHaul()` / `massHaulPayload()`. DOM 의존 0.
|
||||
- [x] 5. **신규** `_UI_MassHaul_View.ts` — `createMassHaulChart()` / `createMassHaulSummary()`. 종단도와 동일 X 매핑, 0선, 누가토량 폴리라인, 0선 기준 음영, 측점선 클릭 선택, Y축 자동 스케일.
|
||||
- [x] 5-1. X축 정렬 보장 — `_UI_Section_Common.longitudinalMaxChainage()`를 신설해 종단 렌더러와 유토곡선이 같은 함수 하나를 보게 함. `calculateYScale()`에 종단도 실제 높이 파라미터를 추가해 패널 축소 시 표고가 잘리지 않게 조정.
|
||||
- [x] 6. `_UI_Section_View.ts` — 상단 패널을 `ui-collapsible`로 승격(헤더+캐럿), 본문에 종단도 + 유토곡선 세로 스택, 접힘 상태 `sessionStorage` 보존, `selectStation()` 3자 동기화.
|
||||
- [x] 6-1. **패널 높이 리사이저** — `createPanelResizer({ axis:"vertical", direction:1, cssVar:"--b06-profile-height" })`를 패널 아래 경계에 부착. `chartHeights()`가 「패널 높이 규칙」표대로 높이를 분배하고 120ms 디바운스 후 재렌더.
|
||||
- [x] 7. `_UI_Page.ts` — `context.earthwork_conversion`을 `sectionView.render()`로 전달, 확정 시 `computeMassHaul()` → `massHaulPayload()`를 `confirmSections()`에 실어 보냄.
|
||||
- [x] 8. `_Repository.py` — `_patch_latest_longitudinal_data()` 공용 힐퍼로 추출하고 `merge_longitudinal_section_data()` 신설. 확정 시 `longitudinal_sections.data.mass_haul`에 병합 저장.
|
||||
- [x] 9. `ui_template_locale_b2.ts` — `B06_MassHaul_*` 11개 키 등록(한/영). 하드코딩 0건.
|
||||
- [x] 10. `_UI_Style_Cross.css` — 유토곡선 클래스 추가. 패널 `height: var(--b06-profile-height, auto)` + `.is-collapsed { height: auto }`, `chart-wrap`을 flex 컬럼으로 바꿔 두 SVG가 같은 가로 스크롤을 공유.
|
||||
- [x] 11. `B05_wf2_Route_UI_Drainage_Panel.ts` — 기본값을 `=== "true"`로 바꿔 저장값이 없으면 펼침으로 시작하도록 수정. `createWorkflowPanelHandle("side", "right")`로 호출 방향 보정.
|
||||
- [x] 12. `ui_template/ui_template_overlay.ts` — `createWorkflowPanelHandle`에 `collapseToward: "left" | "right"` 인자 추가(기본 `"left"`).
|
||||
- [x] 13. 실화면 검증 완료 (프로젝트 `테스트1` 기준) — 기본 높이, 줌, 축소, 접기/펼치기, X축 정렬, 수식 및 요약 수치 정상 작동 확인.
|
||||
@@ -0,0 +1,72 @@
|
||||
# 완료 이력 - B06 유토곡선 토량 분배 및 기하/UI 세부 개선
|
||||
|
||||
- **완료 일자**: 2026-08-02
|
||||
- **기능명**: B06 유토곡선 토량 분배(평형선·운반 띠) 엔진, 자연방토 반영, 기하/UI 세부 오류 정정
|
||||
- **이관 소스**: `docs/raw/PLAN.md` 내 검증 완료된 B06 후속 개선 및 추가 피드백 전체
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요 및 참고 자료
|
||||
이 이력 문서는 B06 단계의 유토곡선 기능 고도화(토량 배분 평형선 엔진, 장비 띠 분할, 직선 보간 확정, 자연방토 모델링 등)와 조작성 및 시각적 레이아웃에 대한 세부 피드백 정정 작업이 완료되었음을 기록하고 아카이빙합니다.
|
||||
|
||||
이번 고도화 작업은 아래의 외부 기술자료 및 교과서 표준 작도법을 근거로 하여 수식 및 기하 문제를 해소하였습니다.
|
||||
- **참고 자료**: clouds-daily.tistory.com/249 (유토곡선의 성질 및 배분 3원칙)
|
||||
- **교차 검증 도면**: `07.유토곡선.pdf` (2024 간선임도 기번3, 울진 대흥 산65) - 측점 81점 및 balloon 43개 데이터 대조 검증용
|
||||
|
||||
---
|
||||
|
||||
## 2. 작업 계획 및 결정 사항
|
||||
|
||||
### [1] 상단 패널 접기 동작 및 자동 측점 선택 제거
|
||||
- **배경**: 상단 패널을 접었을 때 손잡이가 보이지 않는 현상이 발생함. 원인은 `.b06-section__panel`이 `.b06-cross-card`와 공용 스타일로 묶여 `overflow: hidden`을 상속받았기 때문임. 또한 진입 시 사용자가 선택하지 않은 0번 측점이 강제로 강조되는 불편이 있었음.
|
||||
- **결정 사항**: 패널을 스타일 그룹에서 분리하고 `overflow`를 제거하여 손잡이가 패널 경계 밖(`bottom: -24px`)으로 정상 노출되도록 함. 진입 시 자동 선택 로직을 제거하여 3자 연동(종·횡·유토)의 기동 상태를 정규화함.
|
||||
|
||||
### [2] 카드 줌/팬 유지 및 RR 밴드 누락 해결
|
||||
- **배경**: 측점을 클릭할 때 카드를 통째로 재생성하여 사용자가 맞춘 휠 줌(viewBox)이 날아가던 문제와, 구 데이터 폴백 시 RR 밴드가 누락되던 현상이 존재함.
|
||||
- **결정 사항**: 카드를 재생성하는 대신 `applySelection()` 핸들로 클래스 및 강조만 갱신하며, 면적 밴드는 평소 투명하게 상시 렌더링해 둠. `rockKindOf()` 폴백 맵을 신설하여 표, 밴드, 강조의 암종 판단 규칙을 단일화함.
|
||||
|
||||
### [3] 라이트 테마 가독성 보완
|
||||
- **배경**: RR(리핑암) 면적 밴드가 흰 배경 위에서 가독성이 떨어짐.
|
||||
- **결정 사항**: RR 색상을 `--color-slate`에서 `--color-success`로 보정하고, 암반 밴드에 `--cut_rr` / `--cut_br` 변형 클래스를 부여하여 가독성 및 표와의 색상 대응력을 일치시킴.
|
||||
|
||||
### [4] 토량 분배(평형선) 엔진 및 띠 분할
|
||||
- **배경**: 누가토량 단일 곡선만으로는 운반비 계산을 위한 장비 선정이 불가능함. 도면에 맞춘 평균운반거리 `L` 및 운반량 `Q`를 산출해야 함.
|
||||
- **결정 사항**:
|
||||
- 평형선 순차 탐색 알고리즘(`computeHaulPlan`)을 통해 사토/토취 지점에서 계단식으로 이동하는 평형선 구현.
|
||||
- 현의 길이가 장비 한계거리(20m, 70m)와 만나는 높이를 찾아 장비별 `HaulBand`(띠)로 분할.
|
||||
- `L`은 띠의 반높이 현 길이로 계산하고, `Q`는 띠의 가로 종거 차이로 집계.
|
||||
- 잔진동 제거 필터(`MIN_SWING_RATIO = 2%`)를 걸어 노이즈 극값 제거.
|
||||
|
||||
### [5] 2차 도면 정합성 및 기하 수정
|
||||
- **배경**: 직선 보간 시 포물선 대비 `L`이 과소평가되는 기하적 한계와, 독립된 절·성토 블록 간의 잉여/부족 미상쇄로 인한 수치 오류 발생.
|
||||
- **결정 사항**:
|
||||
- `PARABOLIC_MASS_CURVE = false` 설정을 통해 그리기와 재기를 직선 보간으로 완전 단일화하여 도면 모양을 맞추고, 수평선이 곡선에서 뜨는 시각적 결함을 베지에 2차 곡선 경로(`curvePath`) 보간으로 정교화하여 일치시킴.
|
||||
- `HaulTransfer`(장거리 상쇄)를 도입하여 거리 가까운 짝끼리 사토와 토취를 상쇄 처리.
|
||||
- `운반(띠) + 장거리 운반 + 토취 = 총 성토량` 항등식을 검산 칩으로 렌더링.
|
||||
|
||||
### [6] 자연방토 반영
|
||||
- **배경**: 성토 지반의 경사가 급한 곳은 흙이 쌓이지 않고 흘러내려 운반비를 기계적으로 산정하지 않는 임도 실무 관행 적용 필요.
|
||||
- **결정 사항**:
|
||||
- `config_system.NATURAL_SPOIL_MIN_GROUND_SLOPE = 1/1.5` 임계치 신설.
|
||||
- 성토측 지반 평균 경사가 이를 만족하고 양 끝 측점이 자연방토 가능할 시 사토 수량에서 분리하여 `natural_spoil_m3`로 계상하고 balloon에 `M.N=측점` 양식으로 기재.
|
||||
|
||||
### [7] 띠 외곽선 및 balloon 회피/이탈 방지, 임시 저장
|
||||
- **배경**: 띠 다각형의 자기교차 문제와 balloon이 스크롤이나 크기 축소 시 화면 밖으로 나가는 이탈 버그 제어 필요.
|
||||
- **결정 사항**:
|
||||
- 띠 면 영역(`bandOutline`)을 아래 현에서 위 현/곡선 사이로 클램프한 단순 다각형으로 재정의.
|
||||
- balloon 배치 시 `findSlot` 링 탐색과 저장 영역 clamping을 제공하고, 드래그 기능 및 더블클릭 복귀 제공.
|
||||
- 워크플로 전이 없이 현재 편집 데이터만 저장하는 `POST .../sections/{route_id}/save` 임시 저장 추가.
|
||||
- 700줄 제약 만족을 위해 라우터(`_Router_Confirm.py`), 벌룬(`_UI_MassHaul_Balloon.ts`), 면적 스타일(`_UI_Style_Cross_Areas.css`) 등 5개 파일 신규 분리.
|
||||
|
||||
---
|
||||
|
||||
## 3. 구현 체크리스트 및 완료 내역
|
||||
|
||||
- [x] **상단 패널/선택 개선**: `_UI_Section_View.ts` 내 overflow 그룹 분리, `draw()` 및 `render()` 내 접기/펴기 명시 제어 및 0측점 자동 선택 해제.
|
||||
- [x] **줌/밴드 정합**: `CrossCardElement` 신설을 통해 카드 재생성 없는 `applySelection()` 강조 처리, `rockKindOf()` 폴백 맵 단일화.
|
||||
- [x] **라이트 테마 보완**: `--color-success` 변경 및 `--cut_rr`/`--cut_br` 스타일 대응 (`_UI_Style_Cross_Areas.css`).
|
||||
- [x] **토량 배분 엔진**: `_UI_MassHaul_Balance.ts` 내 순차 평형선 탐색, 장비 띠(`HaulBand`) 분할, `L`/`Q` 계산, 잔진동 제거 필터(`MIN_SWING_RATIO` 2%).
|
||||
- [x] **기하/도면 정합**: `_UI_MassHaul_Curve.ts` 내 직선/포물선 보간 및 끝값 보정 처리, `_UI_MassHaul_Settle.ts` 내 장거리 상쇄(`HaulTransfer`), 검산 항등식 칩 연산.
|
||||
- [x] **자연방토 모델링**: `config_system.py` 상수 신설 및 백엔드 지반 경사 연산(`fill_ground_slope()`), 사토 내 자연방토 지분 분리 및 `M.N=측점` balloon 표기.
|
||||
- [x] **외곽선/벌룬/임시저장**: 다각형 자기교차 제거, balloon 회피 및 드래그 보존, `POST .../save` 라우터 구현 및 `_Router_Confirm.py` 파일 분리.
|
||||
- [x] **700줄 제한**: 분리를 통해 전 소스코드 파일 700줄 미만 여유 확보.
|
||||
@@ -0,0 +1,142 @@
|
||||
# [C05] 유토곡선 다중 곡선화 — 종단/횡단 기준 x 지반유형 처리 3종
|
||||
|
||||
> 이 문서는 `docs/raw/PLAN.md`의 「현재 작업」에서 검증 완료 후 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-02_verify_mass_haul_multi_curve.md`
|
||||
> 검증 판정: **CONDITIONAL PASS** — 기능 요구 전부 충족. 렌더 결함 1건(F1)은 PLAN.md 다음 작업으로 이관.
|
||||
|
||||
---
|
||||
|
||||
### [C05] 유토곡선 다중 곡선화 — 종단/횡단 기준 × 지반유형 처리 3종 (2026-08-02)
|
||||
|
||||
#### 배경
|
||||
|
||||
일반 토목 문헌의 유토곡선은 **종단면도에 시공기면을 그린 뒤 횡단도에서 구간 토량을 산출**하는 절차를 전제한다. 그러나 임도는 산복 사면을 따라가므로 편절·편성이 지배적이고, 종단상 절토 구간인데 횡단상으로는 성토가 더 큰 경우가 많다. 어느 기준을 정식으로 삼을지 **사용자가 그래프를 보고 결정**할 수 있도록 두 기준을 한 축에 겹쳐 그린다. 지반유형 처리도 아직 미결(향후 1번)이라 세 가지를 함께 낸다.
|
||||
|
||||
#### 법령 근거 — [임도의 설계 및 시설기준] 별표 (산림자원법 시행규칙)
|
||||
|
||||
이번 작업으로 원문을 확보해 대조했다. 아래 조항이 두 기준을 모두 내는 직접 근거다.
|
||||
|
||||
| 조항 | 원문 | 반영 |
|
||||
|---|---|---|
|
||||
| 2.다.(3)(나) | "시공계획고는 **절토량과 성토량이 균형을 이루게** 하되, 피해방지·경관유지를 감안하여 결정한다" | 종단 기준 곡선 = 법정 균형 판정선 |
|
||||
| 2.다.(4)(다) | "절토부분은 **토사·암반으로 구분**하되, 암반부분은 **추정선**으로 기입한다" | 향후 1번(암반 경계선 분리)의 법정 근거 |
|
||||
| 2.다.(4)(마) | "각 측점의 단면마다 지반고·계획고·절토고·성토고·**단면적** ... 을 기입한다" | 횡단 기준 곡선의 입력값 |
|
||||
| 2.가.(1)(가) | "측점 간격은 **20m**로 하고 ... 종·횡단의 변화가 심한 지점 ... 에는 **보조말뚝**을 설치한다" | `SECTION_STATION_INTERVAL_M = 20.0` + 불규칙 측점 허용 |
|
||||
| 2.라.(1) | 설계서 구성에 **토적표** 포함 | 유토곡선/토적표 산출의 법정 지위 |
|
||||
| 2.차.(2)(3) | 절토 암석지 1:0.4 / 토사 1:0.9 (±0.2), 성토 1:0.9 이하 (±0.2) | `STANDARD_CROSS_SECTION` 대조 근거 |
|
||||
| 2.차.(7) | "부족한 토사공급 또는 남는 토사의 처리가 필요한 경우에는 적정한 장소에 **사토장 또는 토취장**을 지정한다" | 사토/토취 표기의 법정 근거 |
|
||||
| 3.나.(1) | 유효너비 간선 4m 내외 / 지선 3m 내외 | 노반폭 기본값 대조 |
|
||||
|
||||
**중요**: 이 별표에는 **토공 운반장비 선정거리 기준이 없다.** 장비 경계값(`EARTHWORK_HAUL_EQUIPMENT_LIMITS_M`)의 근거는 법령이 아니라 3공구 실제 도면 실측치다. 다른 자료가 이를 법령 근거로 제시하면 오류다.
|
||||
|
||||
#### 참고 자료 (2026-08-02 사용자 제공: `clouds-daily.tistory.com/249`)
|
||||
|
||||
**유토곡선의 성질 7가지** — ① 상승=절토 / 하강=성토 ② 극대·극소점 = 절성토 경계 ③ 산모양 좌→우, 골모양 우→좌 운반 ④ 기선 위에서 끝나면 과잉, 아래면 부족 ⑤ 수평선 두 교점 사이는 절성토 균형(평형선) ⑥ **평균운반거리 = 구간 종거의 1/2 지점을 지나는 수평선** ⑦ 구간 전토량 = b지점 높이.
|
||||
|
||||
**토량 배분 원칙 3가지** — Phase 2 평형선·운반계획의 **명시적 제약**으로 고정한다.
|
||||
|
||||
| # | 원칙 | 반영 지점 |
|
||||
|---|---|---|
|
||||
| ① | 운반거리를 짧게 | 평형선 탐색의 목적함수 = 평균운반거리 최소화 |
|
||||
| ② | 높은 곳에서 낮은 곳으로 | 운반 방향은 성질 ③을 따르고, 종단 경사 역방향 운반은 후순위 |
|
||||
| ③ | 토량을 모아서 한 가지 방법으로 운반 | 장비 경계로 구간을 잘게 쪼개지 말고 통합 후 단일 장비 배정 |
|
||||
|
||||
#### 구현 결과
|
||||
|
||||
**곡선 6종 = 기준 2 × 지반유형 처리 3.** 색이 기준(횡단=브랜드 보라 / 종단=성공색 초록), 선모양이 환산 방식(실선=지반유형별 / 파선=토사 단일 / 점선=환산 없음)을 뜻한다.
|
||||
|
||||
| | 횡단 기준 | 종단 기준 |
|
||||
|---|---|---|
|
||||
| 소스 | `design.cut_area_m2` / `fill_area_m2` (엔진 실측) | `design_profiles[0].samples[].difference_m` × 노반폭 |
|
||||
| 부호 규약 | 엔진이 절·성토를 이미 분리 | `difference_m = 계획고 − 지반고`, 음수=절토 (`B05_wf2_Route_UI_Profile_Alignment.ts:338` 규약과 동일) |
|
||||
| 노반폭·지반유형 | 측점 자체 값 | **최근접 설계 측점**에서 상속 |
|
||||
| 표본 밀도 | 설계된 측점만 | 종단 샘플(측점보다 조밀) |
|
||||
|
||||
- **`_UI_MassHaul.ts`** (346줄) — 공통 적분 함수 `integrate(samples, conversion, groundMode)` 하나로 통일. 두 기준이 `AreaSample[]`까지 정규화된 뒤 **같은 코드**를 탄다(평균단면법·환산 로직 이중화 없음). `computeMassHaulSeries()` 신설. `computeMassHaul()`은 시그니처·결과 그대로 유지해 `_UI_Page.ts:385`의 확정 저장 경로가 바뀌지 않는다.
|
||||
- **`_UI_MassHaul_View.ts`** (318줄) — 다중 곡선 렌더 + 오버레이 범례. **Y 스케일은 표시 여부와 무관하게 항상 전체 곡선 기준**(`volumeRange(series[])`)이라 켜고 꺼도 축이 움직이지 않는다. 면(band)은 맨 앞 곡선 하나에만 칠한다(겹치면 못 읽음). 곡선은 범례 역순으로 그려 첫 곡선이 맨 위에 온다.
|
||||
- **`_UI_Section_View.ts`** (432줄) — 표시 집합을 `sessionStorage["b06:masshaul-visible"]`에 보존. 전부 끈 상태도 사용자의 선택으로 존중한다(기본값 복원 안 함). 요약줄은 켜 둔 첫 곡선의 수치를 쓰고, **어느 곡선인지 끝에 반드시 표기**한다.
|
||||
- **`_UI_Style_MassHaul.css`** (142줄, 신규) — `_UI_Style_Cross.css`가 742줄로 700줄 제한을 넘어 유토곡선 블록을 분리(분리 후 606줄). 범례는 `position: sticky; left: 0` + `height: 0`으로 가로 스크롤에도 왼쪽에 남으면서 곡선 위에 뜬다.
|
||||
- **`ui_template_locale_b2.ts`** — 곡선명·범례 문구 9개 키 추가. 하드코딩 없음.
|
||||
|
||||
#### 구현 체크리스트
|
||||
|
||||
- [x] 공통 적분 함수 분리 — 두 기준이 같은 코드를 탄다
|
||||
- [x] 종단 기준 곡선 계산 (`difference_m` × 노반폭, 최근접 측점 상속)
|
||||
- [x] 지반유형 처리 3종 (지반유형별 / 토사 단일 / 환산 없음)
|
||||
- [x] 다중 곡선 렌더 + **표시 무관 고정 Y 스케일**
|
||||
- [x] 그래프 상단 오버레이 hide/show 범례 버튼
|
||||
- [x] locale 키 등록, 색상은 `theme.css` 변수만 사용
|
||||
- [x] `prettier` + `tsc --noEmit` 통과, 전 파일 700줄 이하
|
||||
- [x] 계산 엔진 단위 테스트 18건 통과
|
||||
- [x] 서버 기동 후 실제 프로젝트(`테스트1`, route 48) 브라우저 검증
|
||||
|
||||
#### 검증 결과 (`테스트1` / route 48 / 24측점 중 23측점 설계)
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 범례 버튼 | 6개 생성, 기본 2개(횡단·종단의 지반유형별) 켜짐 |
|
||||
| 토글 | 2 → 6 → 0 → 2 정상 동작 |
|
||||
| **Y 스케일 고정** | 세 상태 모두 눈금 `60.5 / -46.3 / -153.0` **동일** |
|
||||
| **X축 정렬** | 종단도 측점선 `[62 … 1454] 24개` = 유토곡선 `[62 … 1454] 24개` |
|
||||
| 오버레이 | 범례 top 318px = SVG top 318px (겹침 확인) |
|
||||
| 세션 보존 | 새로고침 후 켜 둔 2개 그대로 복원 |
|
||||
| 전부 끈 상태 | 안내문 "표시할 곡선을 하나 이상 켜 주세요." 표시 |
|
||||
|
||||
**곡선별 수치** (자연상태 절토는 어느 곡선이든 같아야 하며 실제로 같다)
|
||||
|
||||
| 곡선 | 절토(자연) | 환산 후 | 성토 | 최종 |
|
||||
|---|---|---|---|---|
|
||||
| 횡단 · 지반유형별 | 216.1 | 244.8 | 321.4 | 토취 76.6 |
|
||||
| 횡단 · 토사 단일 | 216.1 | 194.5 | 321.4 | 토취 126.9 |
|
||||
| 횡단 · 환산 없음 | 216.1 | 216.1 | 321.4 | 토취 105.3 |
|
||||
| 종단 · 지반유형별 | 154.8 | 174.0 | 158.2 | **사토 15.8** |
|
||||
| 종단 · 토사 단일 | 154.8 | 139.3 | 158.2 | 토취 18.9 |
|
||||
| 종단 · 환산 없음 | 154.8 | 154.8 | 158.2 | **토취 3.4** |
|
||||
|
||||
검산 — `14.9×0.90 + 201.2×1.15 = 244.79` ✓ / `216.1×0.90 = 194.49` ✓ / `16.2×0.90 + 138.7×1.15 = 174.09` ✓. 각 행의 `환산 후 − 성토`가 최종값과 일치 ✓.
|
||||
|
||||
#### 사용자 판단 대기 사항
|
||||
|
||||
1. **정식 기준 채택** — 횡단 단독 / 종단 단독 / 병기 유지 중 택일.
|
||||
- 관측: **종단 기준은 환산 없이 −3.4㎥로 거의 완전 균형**이다. 법령 2.다.(3)(나)가 계획고를 절성토 균형으로 정하게 했고 B05가 그렇게 그었으니 당연한 결과다. 반면 **횡단 기준은 −105.3㎥**로 크게 부족하다. 이 간극이 곧 **임도 편절·편성이 종단 균형을 깨뜨리는 양**이며, 사용자가 제기한 "임도는 횡단 의존이 크다"를 수치로 확인해 준다.
|
||||
- 단서: 종단 기준은 **노반폭만 곱한 개략값**이라 비탈면 절·성토를 잡지 못한다. 절토 154.8 vs 216.1, 성토 158.2 vs 321.4로 양쪽 다 과소평가된다. 물량 산출에는 쓸 수 없고 **균형 판정 지표**로 봐야 한다.
|
||||
2. **지반유형 처리** — 향후 1번(암반 경계선 분리)이 서기 전까지는 세 곡선을 유지한다. 분리가 끝나면 "지반유형별"이 유일한 정답이 되므로 나머지 둘은 제거 후보다.
|
||||
|
||||
#### 미결사항 반영 내역 (향후 작업에서 이관)
|
||||
|
||||
- **향후 1번(암반 경계선 분리)**: 법정 근거 확보 — 2.다.(4)(다) "절토부분은 토사·암반으로 구분하되, 암반부분은 추정선으로 기입". 이제 이 항목은 설계 판단이 아니라 **법정 요구사항 이행**이다. 구현은 향후에 남긴다.
|
||||
- **향후 2번(Phase 2)의 선행 자료**: 배분원칙 3가지 확정, 평균운반거리 산정법(성질 ⑥) 확정, 장비 경제거리 문헌값 대조 완료. Phase 2 본체는 향후에 남긴다.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 검증 단계 정정·보완 사항 (2026-08-02)
|
||||
|
||||
- **[F1] 저장된 패널 높이가 첫 렌더에 미반영 (미해결, PLAN.md 다음 작업으로 이관)**:
|
||||
패널을 줄여 둔 세션에서 재진입하면 패널 높이는 296px로 복원되나 차트 배분이 기본값으로
|
||||
계산돼 **종단면도가 20px로 잘린다.** 창 크기를 바꾸면 정상(129/111)으로 잡힌다.
|
||||
원인은 `draw()`가 `root.replaceChildren(panel, ...)` 이전에 `drawPanel()`을 호출해
|
||||
첫 렌더 시 `panel`이 detached 상태이고, `getComputedStyle`이 빈 값을 주어
|
||||
`BASE_PANEL_HEIGHT`로 폴백하기 때문이다. 구조는 C02에서 유입됐으나 차트가 둘로 늘면서
|
||||
영향이 확대됐다.
|
||||
|
||||
- **[F2] 화면 요약값 != 확정 저장값 (설계 의도, 명시 필요)**:
|
||||
요약줄은 켜 둔 첫 곡선의 수치를 보여 주지만 확정 저장은 **항상 횡단 · 지반유형별**이다.
|
||||
요약줄 끝에 곡선명이 붙어 있어 오독 위험은 낮다. 정식 기준이 확정되면 해소된다.
|
||||
|
||||
- **[F3] 법령 인용 서술 정정**: 본문의 "유토곡선/토적표 산출의 법정 지위"는 과장이다.
|
||||
법령 2.라.(1)이 설계서 구성으로 요구하는 것은 **토적표**이며, **'유토곡선'이라는 단어는
|
||||
별표 전문에 등장하지 않는다.** 유토곡선은 토적표에서 파생되는 **실무 관행 도면**이다.
|
||||
나머지 인용 11건은 원문과 문자 그대로 일치함을 확인했다.
|
||||
|
||||
- **[F4] 좁은 화면(900px) 범례 (조치 불요)**: 범례 6개가 한 줄로 뻗어 가로 스크롤이 필요하나
|
||||
`position: sticky; left: 0`이라 스크롤해도 왼쪽에 남는다. 향후 1번 완료 시 곡선이 6->2로
|
||||
줄어 자연 해소된다.
|
||||
|
||||
- **검증 단계에서 추가한 테스트**: 엣지 8건(측점 범위 밖 종단 샘플, 노반폭 결측, difference_m=0,
|
||||
NaN 샘플, 미지 ground_type, 음수 단면적, 저장 payload 동일성, 합성 station_id 누출).
|
||||
기존 18건과 합쳐 **26건 전원 통과**.
|
||||
|
||||
- **미수행**: 확정 버튼 실동작은 프로젝트 상태를 WF3로 전이시키는 실제 데이터 변경이라
|
||||
실행하지 않았다. 확정 경로 무회귀는 정적 대조와 payload 동일성 테스트로 확인했다.
|
||||
@@ -0,0 +1,31 @@
|
||||
# 완료 이력 - B06 상단 패널 접기 손잡이 B05 하단 패널 스타일로 개선
|
||||
|
||||
- **완료 일자**: 2026-08-02
|
||||
- **기능명**: B06 상단 패널 접기 손잡이 개선 및 뷰 편의성 보완
|
||||
- **이관 소스**: `docs/raw/PLAN.md` B06 접기 손잡이 개선 건
|
||||
|
||||
---
|
||||
|
||||
## 1. 작업 계획 및 결정 사항
|
||||
- **배경**:
|
||||
- B06 상단 패널의 접기 손잡이를 리사이저가 덮어 손잡이를 마우스 오버하는 시점에 리사이저 hover 색상(보라색 바)이 비쳐 오작동하는 느낌을 주던 현상 해결.
|
||||
- B05 하단 패널과 동일한 배치 방식으로 개선하여 사용성 통일.
|
||||
- **결정 사항**:
|
||||
- 접기 손잡이를 패널 내부 흐름에서 제외하고, 패널 경계 바깥쪽 아래(`bottom: -24px`)로 오프셋하여 배치.
|
||||
- 리사이저는 원래의 경계 위치(`bottom: 0`)로 되돌려 z-index가 충돌하지 않도록 보정.
|
||||
- 손잡이가 패널 바깥으로 빠지면서 패널의 내부 높이 소모량 `PANEL_CHROME_PX`를 126px에서 102px로 축소.
|
||||
- 선택 카드 자동 스크롤 시 상단 패널과 겹쳐서 가리는 현상을 막기 위한 여백 마진 스케일을 `+32px`로 보정.
|
||||
- 횡단도 리스트 진입 시 첫 번째 측점(0+0.0)이 자동 선택되어 그래프 위 강조선과 램프가 무분별하게 화면을 가리는 현상을 방지하기 위해 진입 시 자동 측점 선택 로직 제거.
|
||||
- 불필요하게 영역을 차지하던 횡단면도 제목/개수 텍스트 영역을 제거하여 본문 스크롤 영역을 극대화.
|
||||
|
||||
---
|
||||
|
||||
## 2. 구현 내역
|
||||
- `ui_template/ui_template_overlay.ts`:
|
||||
- `createWorkflowPanelHandle` 에 `collapseToward` 파라미터 `"left" | "right" | "up" | "down"` 지원 추가.
|
||||
- `placement === "bottom"`일 때 위아래 펼침/접힘 방향에 맞추어 화살표(삼각형)의 회전 각도를 통일되게 보정 (`up` 이면 `-90`도, `down` 이면 `90`도 등으로 보정).
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View.ts`:
|
||||
- 상단 패널의 손잡이 배치 및 sticky 스크롤 마진(`.style.setProperty("--b06-cross-scroll-margin", Math.round(panelPx + 32)px)`) 반영.
|
||||
- `selectedStationId ??= ...` 라인을 제거하여 진입 시 자동 선택 차단.
|
||||
- `crossHeading` 제거를 통한 레이아웃 공간 확보 및 횡단 카드 외의 빈 영역 클릭 시 선택 상태 초기화 (`clearSelection`).
|
||||
- `tsc --noEmit` 검사 및 `prettier` 적용을 완료하여 코드 정합성 유지.
|
||||
@@ -0,0 +1,56 @@
|
||||
> 이 문서는 `docs/raw/PLAN.md`의 「다음 작업」에서 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-02_verify_panel_height_first_render.md`
|
||||
> 대상 커밋: bc02fcc
|
||||
|
||||
### [C06] B06 상단 패널 — 저장된 높이가 첫 렌더에 반영되지 않는 결함 수정
|
||||
|
||||
**출처**: C05 검증 보고서 [F1] (`docs/raw/verification/2026-08-02_verify_mass_haul_multi_curve.md`)
|
||||
|
||||
**증상**: 패널을 줄여 둔 세션에서 B06에 다시 들어오면 패널 높이는 복원되지만 차트 높이 배분이 기본값으로 계산돼 **종단면도가 20px로 잘려 사실상 사라진다.** 창 크기를 조금이라도 바꾸면 정상으로 잡힌다(자가 치유).
|
||||
|
||||
| 시점 | 패널 | 종단도 | 유토곡선 |
|
||||
| --- | --- | --- | --- |
|
||||
| 첫 렌더 직후 | 296px (정상 복원) | **20px** | 190px |
|
||||
| 재렌더 후 | 296px | 99px | 111px |
|
||||
|
||||
> C05 검증 보고서 [F1] 표에는 종단도 재렌더값이 `129px`로 적혀 있으나 이는 `chartHeights()`의
|
||||
> 계산값이고 **실측 렌더 높이는 99px**이다. 결함의 성격과 원인 진단에는 영향이 없다.
|
||||
|
||||
**원인**: `B06_wf3_ProfileCross_UI_Section_View.ts`의 `draw()`가 `root.replaceChildren(panel, …)` **이전에** `drawPanel()`을 호출한다. 첫 렌더 시점의 `panel`은 아직 `root`에 붙기 전(detached)이라 `getComputedStyle(panel).getPropertyValue("--b06-profile-height")`가 빈 문자열을 반환하고, `panelHeight()`가 `BASE_PANEL_HEIGHT`(506)로 폴백한다. 리사이저가 남긴 값은 **인라인 스타일에는 정상적으로 들어가 있다.**
|
||||
|
||||
구조 자체는 C02에서 유입됐으나, C05로 차트가 둘이 되면서 배분 실패가 종단면도 소멸로 확대됐다.
|
||||
|
||||
**수정 방향** — `panelHeight()`가 인라인 스타일을 먼저 읽고 없을 때만 computed로 폴백한다(detached 상태에서도 동작).
|
||||
|
||||
```ts
|
||||
const panelHeight = (): number => {
|
||||
// 첫 렌더 때 panel은 아직 root에 붙기 전이라 getComputedStyle이 빈 값을 준다.
|
||||
// 리사이저가 남긴 인라인 변수부터 읽어야 저장된 높이가 첫 화면부터 반영된다.
|
||||
const inline = Number.parseFloat(panel.style.getPropertyValue("--b06-profile-height"));
|
||||
if (Number.isFinite(inline) && inline > 0) return inline;
|
||||
const raw = Number.parseFloat(getComputedStyle(panel).getPropertyValue("--b06-profile-height"));
|
||||
return Number.isFinite(raw) && raw > 0 ? raw : BASE_PANEL_HEIGHT;
|
||||
};
|
||||
```
|
||||
|
||||
**체크리스트**
|
||||
|
||||
- [x] `panelHeight()` 수정 (위 방향) — `_UI_Section_View.ts:201-209`, 437줄
|
||||
- [x] `sessionStorage["b06:profile-panel-height"]`에 값이 있는 상태로 B06 첫 진입 시 종단도/유토곡선 높이가 재렌더 결과와 일치하는지 브라우저로 확인
|
||||
- [x] 저장값이 없는 기본 상태에서 기존 동작(220/190)이 그대로인지 회귀 확인
|
||||
- [x] `prettier` + `tsc --noEmit`
|
||||
|
||||
**완료 조건**: 패널 높이를 줄여 둔 뒤 페이지를 다시 열어도 창 크기를 건드리지 않고 두 차트가 올바른 비율로 나온다.
|
||||
|
||||
**자체 검증 결과 (프로그래머 수행)**
|
||||
|
||||
판정 기준은 **첫 렌더 = 재렌더**다. 결함이 있으면 첫 렌더만 어긋난다.
|
||||
|
||||
| 시나리오 | 첫 렌더 (패널/종단/유토) | 재렌더 | 판정 |
|
||||
| --- | --- | --- | --- |
|
||||
| 저장값 296px (기본 미만) | 296 / 99 / 111 | 296 / 99 / 111 | 일치 |
|
||||
| 저장값 없음 (기본) | 496 / 220 / 190 | 496 / 220 / 190 | 일치 (회귀 없음) |
|
||||
| 저장값 700px (기본 초과) | 700 / 220 / 384 | 700 / 220 / 384 | 일치 |
|
||||
|
||||
- 기본 초과 시 종단도는 220 고정, 늘어난 몫을 유토곡선이 흡수 — `chartHeights()`의 설계 의도대로다.
|
||||
- `prettier --write` 변경 없음, `tsc --noEmit` 오류 0건, 파일 437줄(700줄 이하).
|
||||
@@ -0,0 +1,52 @@
|
||||
# 완료 이력 - [C01] `ui_template_locale.ts` 분할
|
||||
|
||||
- **완료 일자**: 2026-08-02
|
||||
- **기능명**: `ui_template_locale.ts` 분할 — 700줄 제약 위반 해결
|
||||
- **이관 소스**: `docs/raw/PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
## 작업 계획 및 이력
|
||||
|
||||
**배경**
|
||||
- 2026-08-01 기준 `ui_template/ui_template_locale.ts` 가 1,230줄로 프로젝트 700줄 제한을 위반.
|
||||
- 이 파일은 `currentLanguageIndex`, `ui_locales`, `t(key)` 3개를 export 하며 A01~A09 / B01~B11 거의 전 페이지가 import 하는 최상위 공유 자원 (`[[ui_templates]]` concepts 참조).
|
||||
|
||||
**결정 사항 (2026-08-02 사용자 승인)**
|
||||
- **배럴(Barrel) 유지 방식** 채택. `ui_template_locale.ts` 는 얇은 재export 파일로 남기고 사전 데이터만 하위 파일로 분리한다.
|
||||
- 이유: 소비처 13개 이상 파일의 import 구문을 단 한 줄도 수정하지 않으므로 회귀 위험이 최소. 완전 분할은 얻는 것 대비 회귀 범위가 과도.
|
||||
|
||||
**리스크 (구현 시 주의)**
|
||||
- 키 하나가 누락돼도 런타임 에러가 나지 않고 화면 문구만 조용히 사라진다 → 분할 전후 **키 개수 및 키 목록 완전 일치**를 반드시 대조할 것.
|
||||
- 키 중복 시 스프레드 순서에 따라 뒤쪽이 앞쪽을 덮어쓴다 → 분할 파일 간 **중복 키 0건**을 확인할 것.
|
||||
|
||||
**목표 구조**
|
||||
```
|
||||
ui_template/
|
||||
├ ui_template_locale.ts (배럴 ~30줄: currentLanguageIndex, ui_locales 합성, t())
|
||||
├ ui_template_locale_common.ts (헤더/푸터/버튼/공통 문구)
|
||||
├ ui_template_locale_a.ts (A01~A09 로그인 전 페이지 문구)
|
||||
└ ui_template_locale_b.ts (B01~B11 워크플로우 페이지 문구)
|
||||
```
|
||||
- 분할 후 모든 파일이 700줄 이하여야 한다. B그룹이 700줄을 넘으면 `_b1`(B01~B05) / `_b2`(B06~B11) 로 한 번 더 쪼갠다.
|
||||
|
||||
**구현 체크리스트**
|
||||
- [x] 1. 원본 `ui_template_locale.ts` 의 `ui_locales` 키를 common / A / B 그룹으로 분류하고 분할 전 키 개수를 기록한다. → 714개, 중복 0건.
|
||||
- [x] 2. `ui_template_locale_common.ts` 생성 — 공통 문구 사전 export (60키 / 96줄). `LocaleEntry` 타입 정의처.
|
||||
- [x] 3. `ui_template_locale_a.ts` 생성 — A그룹 문구 사전 export (141키 / 263줄).
|
||||
- [x] 4. B그룹은 700줄 초과가 예상되어 처음부터 2분할 — `ui_template_locale_b1.ts`(B01~B04, 260키 / 430줄), `ui_template_locale_b2.ts`(B05~B11, 253키 / 408줄).
|
||||
- [x] 5. `ui_template_locale.ts` 를 배럴(51줄)로 축소 — 하위 사전 4종 스프레드 합성. `LANGUAGES`/`LanguageCode`/`currentLanguageIndex`/`setLanguage`/`t`/`ui_locales`/`LocaleKey` export 시그니처 동일 유지, 소비처 import 수정 0건.
|
||||
- [x] 6. 분할 전후 키 대조 — 714개 전부 키·값 문자열까지 완전 일치, 파일 간 중복 키 0건 확인.
|
||||
- [x] 7. `npm run typecheck` (tsc --noEmit) 오류 0건, `prettier --write` 5개 파일 모두 `unchanged`.
|
||||
- [x] 8. 전 파일 700줄 이하 확인(최대 430줄) 후 커밋 및 `.claude/git-autopush.ps1` 실행.
|
||||
|
||||
**구현 시 계획과 달라진 점**
|
||||
- 계획서의 `ui_template_locale_b.ts` 단일 파일 대신 `_b1`/`_b2` 2분할로 착수. B그룹 사전만 약 840줄이라 단일 파일로는 700줄 제약을 만족할 수 없음(계획서 재분할 조항 적용).
|
||||
- `LocaleEntry` 타입은 기존에 비공개였으나, 4개 사전 파일이 `satisfies`에 사용해야 하므로 `ui_template_locale_common.ts`에서 export 하도록 변경(배럴에서는 재export 하지 않아 외부 노출 표면은 불변).
|
||||
- 파일 말미에 흩어져 있던 `B01_Dashboard_*` 11개 키(프로젝트 관리/사용자 관리/확인 메시지)를 `_b1`의 B01 블록으로 이동. 키·값은 불변이며 사전 조회는 순서 무관.
|
||||
|
||||
---
|
||||
|
||||
## 미결사항 해소 이력
|
||||
- **해결된 미결사항**: 기존 향후 작업 내 `2. ui_template_locale.ts 분할 (700줄 제약 위반 해결)`
|
||||
- **상세**: `PLAN.md` 향후 작업의 미결사항(다음 세션에서 확정) 항목 중 2번에 존재하던 `ui_template_locale.ts` 분할 건이 이번 리팩토링 및 검증을 거쳐 최종 완료됨에 따라, `PLAN.md` 내 미결사항 목록에서 완전히 삭제 및 해소되었습니다.
|
||||
@@ -0,0 +1,215 @@
|
||||
# 유토곡선 3공구 도면 분석 (기존 명세 보강)
|
||||
|
||||
> 이 문서는 `2026-07-24_유토곡선_분석_및_웹앱_계산명세.md`(1공구 기준)를 **대체하지 않고 보강**한다.
|
||||
> 같은 사업(2024년 간선임도사업 기번3, 울진 울진 대흥 산65 외2)의 **다른 공구** 도면이다.
|
||||
|
||||
## 1. 분석 대상
|
||||
|
||||
- 원본 파일: `13 유토곡선(울진 울진 대흥 산65 외2(3공구)).pdf`
|
||||
- 설계사: 그루터기 ENG / 책임기술자: 안덕상 (산림공학기술자 기술고급)
|
||||
- 발주처: 남부지방산림청
|
||||
- 도면명: 유토곡선
|
||||
- 수평축척: H=1:2,000
|
||||
- 수직축척: **V=1:50,000**
|
||||
- 측점 범위: `120+0.0` ~ `176+10.0`
|
||||
- 시작 누가토량: `-0.00㎥` / 최종 누가토량: `12,888.98㎥`
|
||||
- Y축 눈금: `-5,000 / 0 / 5,000 / 10,000 / 15,000` (5,000㎥ 간격)
|
||||
|
||||
## 2. 기존 명세(1공구)와의 대조
|
||||
|
||||
| 항목 | 1공구 (`07.유토곡선.pdf`) | 3공구 (본 도면) |
|
||||
|---|---|---|
|
||||
| 측점 범위 | `0+0.0` ~ `60+0.0` | `120+0.0` ~ `176+10.0` |
|
||||
| 수평축척 | H=1:2,000 | H=1:2,000 (동일) |
|
||||
| 수직축척 | V=1:10,000 | **V=1:50,000** |
|
||||
| 최종 누가토량 | 1,171.00㎥ | **12,888.98㎥** |
|
||||
| 사토 작업 | 2건 / 951.40㎥ | **6건 / 1,144.46㎥** |
|
||||
| 발파암(BR) | 전 구간 0.00㎥ | **전 구간 0.00㎥ (동일)** |
|
||||
|
||||
## 3. 새로 확정된 사실
|
||||
|
||||
### 3.1 측점 간격 = 20m
|
||||
|
||||
비정규 측점의 잔여거리 최대값이 `18.0`(`120+18.0`), 그다음이 `17.0`(`138+17.0`)이고 `20.0` 이상은
|
||||
한 번도 나타나지 않는다. 따라서 `n+d` 표기의 누적거리는 `n × 20 + d`이며, 3공구 연장은
|
||||
`(176 − 120) × 20 + 10 = 1,130m`이다.
|
||||
|
||||
`config/config_system.py:320`의 `SECTION_STATION_INTERVAL_M = 20.0` 기본값과 일치한다.
|
||||
|
||||
### 3.2 Y축은 고정 축척이면 안 된다
|
||||
|
||||
같은 사업의 1공구는 V=1:10,000(최대 1,171㎥), 3공구는 V=1:50,000(최대 12,889㎥)로 **5배 차이**다.
|
||||
웹앱은 공구/노선별 데이터 범위 기반 자동 Y스케일을 써야 하며, 고정 축척을 상수로 박으면 안 된다.
|
||||
|
||||
### 3.3 `Q = EA + RR + BR` 재검증 통과 (10개 고유 작업)
|
||||
|
||||
| 작업 | Q(㎥) | EA 토사 | RR 리핑암 | BR 발파암 | L / M.N |
|
||||
|---|---:|---:|---:|---:|---|
|
||||
| 종무대12 | 13.50 | 6.99 | 6.51 | 0.00 | L=14.15m |
|
||||
| 종무대22 | 24.09 | 12.57 | 11.52 | 0.00 | L=15.67m |
|
||||
| 도쟈11 | 1.63 | 0.84 | 0.79 | 0.00 | L=20.44m |
|
||||
| 덤프4 | 162.41 | 68.24 | 94.17 | 0.00 | L=67.78m |
|
||||
| 사토1 | 611.98 | 246.66 | 365.32 | 0.00 | M.N=0+16.90 |
|
||||
| 사토2 | 141.23 | 48.67 | 92.56 | 0.00 | M.N=21+11.41 |
|
||||
| 사토3 | 17.54 | 7.11 | 10.42 | 0.00 | M.N=25+6.97 |
|
||||
| 사토4 | 17.81 | 7.18 | 10.63 | 0.00 | M.N=27+1.21 |
|
||||
| 사토5 | 217.03 | 110.03 | 107.00 | 0.00 | M.N=30+16.02 |
|
||||
| 사토6 | 138.87 | 98.95 | 39.92 | 0.00 | M.N=42+10.55 |
|
||||
| **사토 합계** | **1,144.46** | **518.60** | **625.85** | **0.00** | |
|
||||
|
||||
10건 전부 `Q = EA + RR + BR` 성립. 1공구 43건 검산 결과와 동일한 관계다.
|
||||
|
||||
### 3.4 EA:RR 비율은 작업마다 다르다
|
||||
|
||||
`34:66`(사토2) ~ `71:29`(사토6) 범위로 흩어진다. 전역 고정비가 아니라 **그 작업이 퍼온 절토
|
||||
구간의 지반유형 구성**이 그대로 반영된 값이다. 곧 B06의 측점별 지반유형(토사/리핑암/발파암)
|
||||
지정값이 EA/RR/BR 분리의 직접 근거가 된다.
|
||||
|
||||
### 3.5 `M.N` 표기 — 기존 명세 미확정 항목 9번에 대한 진전
|
||||
|
||||
6개 값이 `0+16.90 → 21+11.41 → 25+6.97 → 27+1.21 → 30+16.02 → 42+10.55`로 **단조 증가**하며,
|
||||
20m 환산 시 `16.9m ~ 850.55m`로 **3공구 연장 1,130m 안에 전부 들어온다**. 두 해석이 수치적으로
|
||||
같은 결과를 낸다.
|
||||
|
||||
- (a) 사토 발생 측점 — 단, **공구 시점(`120+0.0`)을 0으로 잡은 상대 측점**
|
||||
- (b) 사토장까지의 운반거리 — 단, **사토장이 공구 시점에 있을 때**
|
||||
|
||||
반례: 사토2의 위치 `141+11.41`은 누가토량 상승 구간 한복판이라, 사토를 극댓점에서 뽑는다는
|
||||
통념과 어긋난다. 이 때문에 (a) 단독 해석은 근거가 약하다. **실무 확인이 필요하며, 확정 전까지
|
||||
웹앱에서 `M.N`을 자동 산출하지 않는다.**
|
||||
|
||||
## 4. 곡선 형상 — 평형선 자동 탐색의 근거 자료
|
||||
|
||||
측점 74개 중 **성토 우세(하강) 구간은 8곳뿐**이다.
|
||||
|
||||
`120+0.0→122+0.0`, `125+0.0→126+0.0`, `135+0.0→137+0.0`, `138+17.0→140+0.0`,
|
||||
`154+0.0→154+6.0`, `155+0.0→156+0.0`, `166+0.0→167+0.0`, `170+0.0→171+0.0`
|
||||
|
||||
나머지는 전부 상승(절토 우세)이다. 산지 편절 임도의 전형이며, 최종 누가토량이 +12,889㎥로
|
||||
크게 남는 이유다.
|
||||
|
||||
주요 극점: 극소 `122+0.0`(−208.03) / 극대 `135+0.0`(2,764.84) / 극소 `137+0.0`(1,561.88) /
|
||||
극대 `170+0.0`(11,486.35) / 극소 `171+0.0`(10,839.61) / 종점 최대 `176+10.0`(12,888.98)
|
||||
|
||||
## 5. 미해결로 남는 모순 (기존 명세 13절과 동일 패턴, 규모는 더 큼)
|
||||
|
||||
최종 누가토량 **12,888.98㎥** vs 사토 합계 **1,144.46㎥** — **11배 차이**.
|
||||
(1공구는 1,171.00 vs 951.40으로 1.2배였다.)
|
||||
|
||||
즉 **곡선 Y축의 "누가토량"과 사토 balloon의 `Q`가 같은 기준 토량이 아니다.** 환산계수·다짐계수·
|
||||
유용토 공제가 중간에 개입했거나, 곡선은 자연상태 절토 기준이고 사토는 반출(느슨한 상태) 기준일
|
||||
가능성이 크다. 원본 토공량 계산서 없이는 확정할 수 없다.
|
||||
|
||||
## 6. PDF 텍스트 레이어의 한계 (분석 신뢰도)
|
||||
|
||||
도면 이미지에는 balloon이 40개 가까이 보이는데, 텍스트 추출본에서는 `종무대22`가 12회,
|
||||
`도쟈11`이 9회 **완전히 동일한 값으로 중복** 출력된다. 추출 아티팩트다.
|
||||
|
||||
따라서 3.3절 표는 **고유값 기준**이며, **작업별 전수 목록은 이 PDF 텍스트만으로 만들 수 없다.**
|
||||
1공구처럼 43건 전수 검산을 하려면 원본 DWG 또는 수량계산서가 필요하다.
|
||||
|
||||
## 7. B06 반영 관점 — 보유 자원과 결손
|
||||
|
||||
### 이미 있는 것
|
||||
|
||||
| 자원 | 위치 | 유토곡선에서의 쓰임 |
|
||||
|---|---|---|
|
||||
| `cut_area_m2` / `fill_area_m2` | `B06_wf3_ProfileCross_Api_Fetch.ts:225-226` (`CrossDesign`) | 평균단면법 구간 토량의 입력 |
|
||||
| `ground_type` (토사/리핑암/발파암) | `B06_wf3_ProfileCross_Api_Fetch.ts:181` | EA/RR/BR 분리 근거 |
|
||||
| `chainage_m` + 비정규 측점 | `SectionStation` / `stationLabel()` | X축 누적거리 (측점번호 추정 금지 원칙 충족) |
|
||||
| 측점 선택 동기화 | `createSectionView.selectStation()` | 종단도↔유토곡선↔횡단도 3자 연동 |
|
||||
| 종단 X 매핑 | `B06_wf3_ProfileCross_UI_Longitudinal.ts:195` | 동일 식 재사용 시 X축 정렬 보장 |
|
||||
| 접기 컨테이너 | `ui_template/ui_template_collapsible.ts:15` | 상단 패널 접기/펼치기 |
|
||||
|
||||
### 없는 것 (신규 필요)
|
||||
|
||||
- **토량환산계수** — `config_system.py`에 항목 자체가 없음
|
||||
- 평균단면법 구간 토량 → 순토량 → 누가토량 계산 로직
|
||||
- 유토곡선 SVG 렌더러
|
||||
- 확정 시 유토곡선 결과 영구 저장 경로
|
||||
|
||||
### 이번 범위에서 제외 (미확정 사항 의존)
|
||||
|
||||
- 평형선 자동 탐색, 운반토량 `Q`, 평균운반거리 `L`
|
||||
- 종무대/도쟈/덤프 장비 선정 (거리 경계 미확정: 관측값은 종무대 14~16m, 도쟈 20m, 덤프 68m)
|
||||
- 사토·토취 balloon 및 `M.N` 산출 (5절 모순과 3.5절 정의 미확정)
|
||||
|
||||
## 8. 외부 자료 조사 결과 (2026-08-02)
|
||||
|
||||
### 8.1 확인된 실무 관례 — 도면 해석 뒷받침
|
||||
|
||||
| 조사 결과 | 본 도면과의 대조 |
|
||||
|---|---|
|
||||
| "토적표상의 측점(**통상 20m**)이 횡으로, 누가토량은 종으로" | 3.1절 20m 추론과 일치 |
|
||||
| "**땅깎기 단면적은 토사·리핑암·발파암으로 구분하여 산출**" | 도면의 EA/RR/BR 3분할 및 B06 지반유형 3종과 정확히 일치 |
|
||||
| "**평형선 사이의 상하 간격은 순성토량 또는 사토량**을 표시" | 사토량은 곡선 절대값이 아니라 **평형선 기준 간격**. Phase 2 평형선 탐색의 직접 근거 |
|
||||
| "**운반거리 산정 시 모든 수량은 다짐상태로 환산**하여 계산하고, **내역서에 적용하는 수량은 자연상태**로 한다" | **계산 기준 확정** — 아래 8.3 참조 |
|
||||
| "굴착·적재·운반 중기 작업은 흐트러진 상태로 이루어지므로 자연상태 환산이 필요" | 장비 물량(Phase 2)과 곡선 물량의 기준이 다를 수 있음을 시사 |
|
||||
|
||||
### 8.2 토량환산계수 수치 (조사 결과)
|
||||
|
||||
L = 흐트러진 상태 / 자연상태 (팽창률), C = 다져진 상태 / 자연상태 (다짐률).
|
||||
|
||||
| 구분 | L값 | C값 |
|
||||
|---|---|---|
|
||||
| 경암 | 1.70 ~ 2.00 | 1.30 ~ 1.50 |
|
||||
| 보통암 | 1.55 ~ 1.70 | 1.20 ~ 1.40 |
|
||||
| 연암 | 1.30 ~ 1.50 | 1.00 ~ 1.30 |
|
||||
| 풍화암 | 1.30 ~ 1.35 | 1.00 ~ 1.15 |
|
||||
| 점토 | 1.20 ~ 1.35 | 0.75 ~ 0.90 |
|
||||
| 모래 | 1.10 ~ 1.20 | 0.80 ~ 0.95 |
|
||||
| 자갈 | 1.05 ~ 1.15 | 0.90 ~ 0.95 |
|
||||
| 풍화토 | 1.10 ~ 1.25 | 0.80 ~ 0.90 |
|
||||
|
||||
**B06 지반유형 3종으로의 대응 (제안)**
|
||||
|
||||
| B06 지반유형 | 대응 암종 | 채택 기본값 |
|
||||
|---|---|---|
|
||||
| 토사 `soil` | 풍화토~점토 중간 | L=1.25, C=0.90 |
|
||||
| 리핑암 `ripping_rock` | 풍화암~연암 하단 (리퍼 굴착 가능 지층) | L=1.35, C=1.15 |
|
||||
| 발파암 `blasting_rock` | 보통암 중앙 | L=1.60, C=1.30 |
|
||||
|
||||
이 대응은 **암종 정의(리핑암 = 리퍼로 굴착 가능한 풍화 진행 지층, 발파암 = 발파가 가장 효과적인
|
||||
지층)에 근거한 제안**이며, 임도 전용 고시 수치가 아니다. 표준품셈도 "토질 시험하여 적용하는 것을
|
||||
원칙으로 하되 소량인 경우 환산계수표에 따를 수 있다"고 하므로, **현장별 조정이 전제**다.
|
||||
따라서 `config/config_system.py`에 두어 사용자가 직접 조절할 수 있게 한다.
|
||||
|
||||
### 8.3 계산 기준 확정 — 다짐상태
|
||||
|
||||
8.1의 "운반거리 산정 시 모든 수량은 다짐상태로 환산" 규칙에 따라, 유토곡선(운반계획 목적)은
|
||||
**다짐상태 기준**으로 계산한다.
|
||||
|
||||
```text
|
||||
순토량 N(i) = Σ[지반유형별 절토량(자연상태) × C(지반유형)] − 성토량(다짐상태)
|
||||
누가토량 M(i) = M(i-1) + N(i)
|
||||
```
|
||||
|
||||
기존 명세(`2026-07-24_...md` 4.1절)의 `N = C − F/L`은 절토(자연상태) 기준 표기다. 두 표기는
|
||||
기준상태만 다를 뿐 같은 물리량이며, **운반계획 도면은 다짐상태 기준이 실무 관례**이므로 이쪽을 채택한다.
|
||||
내역서용 자연상태 수량은 B08에서 되돌려 쓴다.
|
||||
|
||||
### 8.4 `M.N` — 조사 실패, 구현 보류
|
||||
|
||||
`유토곡선 M.N 표기`, `사토 M.N 뜻 mass number 측점`, `유토곡선 balloon Q L EA RR BR` 등으로
|
||||
검색했으나 **정의를 확인할 수 있는 자료를 찾지 못했다.** 특정 작성 프로그램(예: 국내 토목 적산
|
||||
소프트웨어)의 자체 표기일 가능성이 높다.
|
||||
|
||||
→ **`M.N`은 구현하지 않는다** (2026-08-02 사용자 지시). 정의가 확인되면 그때 Phase 2에 포함한다.
|
||||
|
||||
### 8.5 5절 모순에 대한 사용자 판단
|
||||
|
||||
최종 누가토량(12,888.98㎥) vs 사토 합계(1,144.46㎥)의 11배 차이에 대해, **수작업 도면이라 오류가
|
||||
섞였을 수 있으므로 아는 범위에서 구현하고 나중에 개선한다**(2026-08-02 사용자 지시).
|
||||
|
||||
다만 8.1의 "평형선 사이 간격 = 사토량" 관례를 보면, **사토량은 곡선의 절대 최종값이 아니라 평형선
|
||||
기준 간격**이므로 두 수치가 애초에 같아야 할 이유가 없을 가능성도 있다. Phase 2에서 평형선을
|
||||
구현하면 자연히 판별된다.
|
||||
|
||||
**출처**
|
||||
- [토량환산계수 (시공자료)](https://m.cafe.daum.net/studype/8ZPF/18)
|
||||
- [토량환산계수 완벽 해설, L값·C값 개념부터 적용까지](https://marzanti.co.kr/entry/%ED%86%A0%EB%9F%89%ED%99%98%EC%82%B0%EA%B3%84%EC%88%98-%EC%99%84%EB%B2%BD-%ED%95%B4%EC%84%A4-L%EA%B0%92%C2%B7C%EA%B0%92-%EA%B0%9C%EB%85%90%EB%B6%80%ED%84%B0-%EC%A0%81%EC%9A%A9%EA%B9%8C%EC%A7%80)
|
||||
- [Mass Curve 토공량 산정과 유토곡선 토량배분 계획](https://damsplanet.com/%ED%86%A0%EA%B3%B5%EB%9F%89-%EC%82%B0%EC%A0%95%EA%B3%BC-%EC%9C%A0%ED%86%A0%EA%B3%A1%EC%84%A0-%ED%86%A0%EB%9F%89%EB%B0%B0%EB%B6%84-%EA%B3%84%ED%9A%8D/)
|
||||
- [2021년도 국도건설공사 설계실무 요령 — 수량산출요령(토공)](http://cyeng.iptime.org/xe/board_moct/9331)
|
||||
- [2016 건설공사 표준품셈](https://files-scs.pstatic.net/2024/11/28/CrMpUEke2d/2016%EA%B1%B4%EC%84%A4%EA%B3%B5%EC%82%AC%ED%91%9C%EC%A4%80%ED%92%88%EC%85%88.pdf)
|
||||
- [한국도로공사 2020 도로설계요령 제2권 5편 토공 — 흙 및 암의 분류](http://cyeng.iptime.org/xe/board_road04/10183)
|
||||
- [임도설치 및 관리 등에 관한 규정 (산림청)](https://www.forest.go.kr/newkfsweb/cmm/fms/BoardFileDown.do?atchFileId=FILE_000000000055871&fileSn=1&dwldHistYn=N&bbsId=BBSMSTR_1005)
|
||||
@@ -0,0 +1,107 @@
|
||||
# [C05] 계획 유토곡선 B05 이관 및 피드백 9차 반영 정리 (2026-08-03)
|
||||
|
||||
> 이 문서는 `docs/raw/PLAN.md`에서 검증 완료 후 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-03_verify_B05_mass_haul_transfer.md`
|
||||
> 검증 판정: **PASS** — 구현 완료, 검증 완료, 사용자 피드백 9차 반영 완료.
|
||||
|
||||
---
|
||||
|
||||
### 계획 유토곡선 B05 이관(분리안) + 사토장·토취장 자동 선정 (2026-08-03 협의)
|
||||
|
||||
#### 배경 및 결정사항
|
||||
* **설계기준 조사(2026-08-03)**: 유토곡선 정식 작성은 횡단 토적표(평균단면법) 기반이 국내 표준
|
||||
(도로설계요령 토공편, 국도건설공사 설계실무 요령). 종단면 기반 토량은 노선계획 단계의
|
||||
개산용으로만 쓰이며 별도 설계기준이 없다. → 종단 기준 곡선은 현재 단순식
|
||||
(높이차×노반폭, `longitudinalAreaSamples`) 유지, 정밀화하지 않는다.
|
||||
* **분리안 확정(사용자)**: 사토장·토취장 선정은 계획선을 바꾸며 계획선 편집 정본·배수유역·
|
||||
배관 데이터가 모두 B05에 있으므로, **B05 = 계획 도구**(종단 개략 유토곡선 + 4옵션 선정 +
|
||||
계획고 실시간 피드백), **B06 = 정식 검증·확정**(횡단 기준 곡선·확정 저장·B08 인계)으로 분리.
|
||||
B06 확정 저장 기준 논점은 소멸 — 횡단 정식만 저장.
|
||||
* **B05 배치(사용자)**: 종단면 하단 슬라이드 패널 **내부에 2차 하단 슬라이드 패널**을 신설,
|
||||
펼치면 12행 테이블 영역을 덮고 접으면 테이블 복귀. B06 유토곡선 기능 동등 제공(코드 재활용).
|
||||
* **저장·캐시 원칙**: 초기 전체 계산 → 영구저장, 페이지 진입 시 캐시 로드, 수정 → 확정 시
|
||||
영구저장 + 캐시 갱신 패턴 준수.
|
||||
* 기존 향후 작업 1(사토장 위치·거리 모델)은 본 작업에 흡수된다.
|
||||
|
||||
#### A. 유토곡선 엔진 공용화 (동작 무변경 리팩터링)
|
||||
- [x] B06 유토곡선 모듈 7종 + CSS를 `common_util/common_util_mass_haul*.ts|css`로 이동
|
||||
(`@util/*` 별칭 사용), B06 임포트 경로만 수정.
|
||||
- [x] `common_util_mass_haul_types.ts` 신설 — `GroundType`·`EarthworkConversion`·
|
||||
`HaulEquipmentLimit`·`BalloonOffsets` 정의처를 이곳 한 곳으로 모으고 B06 `Api_Fetch`가 재수출.
|
||||
엔진 입력은 페이지 API 타입 대신 구조적 부분집합(`MassHaulSection`/`MassHaulDesign`/
|
||||
`MassHaulLongitudinal`/`MassHaulStationSource`)으로 받아 B05가 B06을 임포트하지 않게 함.
|
||||
- [x] `common_util_svg.ts` 신설 — `svgElement`/`svgText`/`L`/`stationLabel`/`inferStationInterval`
|
||||
이관, B06 `_UI_Section_Common`은 재수출로 기존 경로 유지.
|
||||
- [x] `createMassHaulChart` X축 파라미터화(`MassHaulAxis`) — 종단 렌더러 상수(`LONG_PAD`,
|
||||
`longitudinalMaxChainage`) 의존을 걷어내고 호출한 쪽이 주입. B06은 기존과 동일 값 전달.
|
||||
- [x] 이동 후 무변경 확인: `npm run typecheck` 통과, B06 변경 41줄(임포트·인자 전달만).
|
||||
|
||||
#### B. B05 계획 유토곡선 패널
|
||||
- [x] `B05_wf2_Route_UI_Profile_MassHaul.ts` 신설 — 종단면 패널 안 2차 하단 슬라이드.
|
||||
펼치면 12행 테이블 자리를 곡선이 대신 차지하고, 펼침 상태는 sessionStorage 보존.
|
||||
- [x] 곡선 SVG를 종단 그래프와 **같은 가로 스크롤러(canvas)** 안 형제로 넣고
|
||||
`MassHaulAxis`에 `LONG_PAD + originOffset`을 넘겨 X축을 종단 `chainageMapper`와 일치시킴.
|
||||
- [x] 종단 개략 basis 엔진 신설(`computeLongitudinalMassHaul`) — 횡단 설계가 없는 B05용으로
|
||||
표준횡단 노반폭(`road_width_m + shoulder_left/right_m`)을 전 구간 공통으로 물린다.
|
||||
토량 분배(평형선·장비 띠)·balloon·잔량 정산·요약 메트릭은 공용 모듈 그대로 재사용.
|
||||
- [x] 계획고 편집 시 `draw()` 경로에서 곡선이 함께 다시 계산된다(순수 프론트, 백엔드 불필요).
|
||||
- [x] `setEarthworkContext()`로 B05 Page가 `fetchSectionContext()` 값을 주입(사본 금지).
|
||||
|
||||
#### C. 사토장·토취장 4옵션 자동 선정 (B05)
|
||||
UI (`B05_wf2_Route_UI_Profile_Disposal.ts` + `_UI_Style_Disposal.css`):
|
||||
- [x] 범례 줄의 [토량 분배]와 [도형 위치 초기화] 사이에 세로 구분기호(양측) + 토글 4개
|
||||
(`비용 | 지형안정성 | 계곡부 | 임내 공간`). 하나 선택 시 나머지 `disabled`(라디오).
|
||||
구분기호 삽입은 공용 `createMassHaulLegend`에 `extras` 인자를 더해 처리.
|
||||
- [x] 옵션 선택 시 부지 카드 표시: 위치(측점·좌우 오프셋), 배정/수용 용량, 운반거리, 지반경사 +
|
||||
옵션별 근거값(비용=낙차, 안정성=계곡 이격, 계곡부=계곡 축까지, 임내 공간=수고).
|
||||
- [x] 사용자 정의: 측점·용량 입력 수정 시 "사용자 정의" 배지 + 운반거리 재계산,
|
||||
[사용자 정의 삭제]로 자동값 복원.
|
||||
- [x] 후보 부족으로 배정되지 못한 토량을 경고 문구로 표시(조용히 빠지지 않게).
|
||||
|
||||
엔진 (`B05_wf2_Route_Engine_Disposal.py`, `POST /route/disposal-sites`):
|
||||
- [x] 노선 좌우 corridor(`DISPOSAL_SITE_CRITERIA`) DEM 격자 분석 — 경사면(cost surface 캐시
|
||||
재사용), 판정 반경 안 사용 가능 면적으로 용량 산출, 옵션별 0~1 점수로 정렬.
|
||||
- [x] 비용: 잉여/부족 구간 토량가중 중심까지 최단거리 + 하향 운반 가점.
|
||||
- [x] 지형안정성: 완경사 우선 + 계곡 축(`terrain_skeleton`) 버퍼 안쪽 제외.
|
||||
- [x] 계곡부: 계곡 축 버퍼 안쪽만 후보로 남김.
|
||||
- [x] 임내 공간: 라이다 `structured.npz` 격자별 수고로 저식생 평탄지만 후보로 남김.
|
||||
- [ ] **토취장 토질(발파암 배제) 미구현** — 지반유형은 B06 횡단 설계 산출물이라 B05 계획
|
||||
단계에는 없다. 지금은 경사 상한만 적용. B06 확정 지반유형이 붙은 뒤 별도 착수.
|
||||
- [ ] **계곡부 옵션의 암거 연장 산출 미구현** — 후보 위치와 계곡 축 거리까지만 낸다.
|
||||
필요 암거 규격·연장은 B04 관 지점 데이터와 묶어야 해서 후속 작업으로 분리.
|
||||
|
||||
저장:
|
||||
- [x] 확정 시 종단 정본(`longitudinal.json`)의 `disposal_sites`에 심는다
|
||||
(`_merge_disposal_sites_into_longitudinal`). 기준 미선택이면 저장분을 지운다.
|
||||
종단 정본은 B06 detail 응답에 통째로 실려 나가므로 별도 캐시 경로가 필요 없다.
|
||||
|
||||
#### D. B06 연동 정리
|
||||
- [x] B06 종단 기준 곡선은 범례 비교용으로 유지(전환 버튼 신설 불필요 — 분리안으로 소멸).
|
||||
- [x] `common_util_mass_haul_sites.ts` 신설 — B05가 심은 `disposal_sites`를 읽어 **정식 곡선의
|
||||
잔량 위치** 기준으로 운반거리를 다시 재고(`resolveDisposalSites`) 요약 줄에 표시.
|
||||
- [x] 확정 페이로드 `mass_haul.disposal_sites`에 재계산값으로 포함(B08 인계).
|
||||
- [x] 계획선 변경 감지는 기존 `reconcileStaleDesigns`가 `designElevationAt` 비교로 이미 잡는다
|
||||
(계획고가 바뀌면 측점 설계가 stale로 걸려 재계산) — 보강 불필요함을 확인.
|
||||
|
||||
---
|
||||
|
||||
### 2차 반영 — 사용자 피드백 9건 + 배관 자리 계획선 R (2026-08-03)
|
||||
|
||||
- [x] (5) 자동 선정 삭제
|
||||
- [x] (1·2) B05 유토곡선을 `computeMassHaulSeries`로 전환
|
||||
- [x] (3·6) 유토곡선 펼침 시 종단 그래프:곡선 세로 1:1 분할
|
||||
- [x] (4) 기본 설계 프리뷰·확정 기본값을 `ground_type="ripping_rock"` + `rock_boundary_offset_m=-0.5` 변경
|
||||
- [x] (7) EP(종점) 잔량 라벨 표기
|
||||
- [x] (9) 유토곡선 0선을 붉은 굵은 실선으로 변경
|
||||
- [x] (8) 3D 뷰 [측점 라벨] 토글 신설
|
||||
- [x] 배관 구조물 자리 계획선 R·선 연결 개선
|
||||
|
||||
### 3차~9차 반영 요약 (2026-08-03 ~ 2026-08-04)
|
||||
|
||||
- [x] UI 5건 + 계획선 1차 로직 개편 (도면 테이블 구배 블록 기본 숨김, Y축 이중 표시 제거, 리사이저, 라디오 버튼화)
|
||||
- [x] 페이지 간 캐시 공유 (`B06_wf3_ProfileCross_Section_Store.ts` 모듈 싱글턴 신설)
|
||||
- [x] 계획선 호 통과 보정 및 L 기준 역산 적용
|
||||
- [x] B05 계획선 편집 → 횡단 재계산 일괄 프리뷰 API (`POST /sections/{route_id}/cross-design/preview`)
|
||||
- [x] 프리뷰 응답 경량화 (1.09MB → 6KB)
|
||||
- [x] 유토곡선 고정 축 단위 `㎥` 표기 및 스티키 축 정돈
|
||||
- [x] 3D 측점 라벨 기본 켜짐 및 대상(BP/EP, 5측점 배수, 구조물 측점) 확대
|
||||
@@ -0,0 +1,34 @@
|
||||
# [ARCHIVE] 향후 작업 4개 항목 완료 아카이빙 (2026-08-03)
|
||||
|
||||
> 이 문서는 `docs/raw/PLAN.md`의 「향후 작업」 섹션에서 완료 처리되어 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-03_verify_향후작업_반영완료.md`
|
||||
> 검증 판정: **PASS** — 사용자의 반영 완료 확인 및 이관 아카이빙 완료.
|
||||
|
||||
---
|
||||
|
||||
### 이관된 향후 작업 항목 (총 4건)
|
||||
|
||||
#### 1. 사토장 위치·거리 모델
|
||||
|
||||
- **배경**: 자연방토로 빠지지 않은 사토는 사토장으로 실어 내야 하는데, 사토장 위치 데이터가 없어 운반거리 `L`과 장비를 못 매긴다. 지금은 balloon에 `사토장=X㎥` 수량만 적는다.
|
||||
- **대상**: `config_system.py`(기본 거리 후보), B06 입력 UI(사토장 좌표·거리 입력), `_UI_MassHaul_Settle.ts`(장거리 운반과 같은 방식으로 `L`·장비 부여).
|
||||
- **완료 조건**: ① 사토장 위치·거리 입력 수단 ② 사토 balloon에 `L=`·장비 표기 ③ B08 내역서에 사토 운반 항목으로 인계.
|
||||
- **상태**: 반영 완료 (사용자 확인).
|
||||
|
||||
#### 2. 자연방토 임계 경사 확정
|
||||
|
||||
- **배경**: `NATURAL_SPOIL_MIN_GROUND_SLOPE = 1/1.5`(≈33.7°)는 토사 안식각 상한이자 표준 성토 경사에서 잡은 초기값이다. 현장·발주처 기준이 다를 수 있다.
|
||||
- **완료 조건**: 실무 기준 확인 후 `config_system.py` 한 줄 수정. 값이 바뀌면 자연방토량과 사토장 운반량이 함께 바뀌므로 실측 노선으로 재확인.
|
||||
- **상태**: 반영 완료 (사용자 확인).
|
||||
|
||||
#### 3. 유토곡선 → B08 수량산출 인계
|
||||
|
||||
- **배경**: `mass_haul.haul_plan`에 블록·장비 띠·장거리 운반·잔량이 이미 직렬화돼 저장된다(`_UI_MassHaul_Settle.haulPlanPayload`). B08이 이 구조를 받아 운반 항목을 세우면 된다.
|
||||
- **완료 조건**: ① B08이 `haul_plan.blocks[].bands[]`를 장비별 운반 항목으로 전개 ② 자연방토는 운반비 미계상 ③ 내역서 수량은 자연상태(`cut_natural_m3`)로 환원.
|
||||
- **상태**: 반영 완료 (사용자 확인).
|
||||
|
||||
#### 4. 횡단도 목록 훑기 개선 (보류)
|
||||
|
||||
- **배경**: 휠 줌을 걷어내고 `+ / − / ⤢` 버튼으로 옮겼다. 카드가 수십 장일 때 원하는 측점으로 바로 가는 수단(측점 검색·점프)은 아직 없다.
|
||||
- **완료 조건**: 사용자 요청 시 착수. 후보 — 측점 입력 점프, 종단도 클릭 → 해당 카드로 스크롤.
|
||||
- **상태**: 반영 완료 (사용자 확인).
|
||||
@@ -0,0 +1,23 @@
|
||||
# [ARCHIVE] 향후 작업 공통 주의사항 아카이빙 및 실측 검증 (2026-08-03)
|
||||
|
||||
> 이 문서는 `docs/raw/PLAN.md` 하단의 「주의사항 (모든 후속 작업 공통)」 섹션에서 이관된 아카이브입니다.
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-03_verify_후속작업_주의사항.md`
|
||||
> 검증 판정: **PASS** — 주의사항 13개 항목 코드 실측 대조 및 검증 완료.
|
||||
|
||||
---
|
||||
|
||||
### 주의사항 및 소스코드 대조 검증 항목 (총 13건)
|
||||
|
||||
- **암 경계선 산출**: 암 경계선은 시공계획선이 아니라 **지면선(지반선) 복사 + 오프셋** 규칙을 고정 적용합니다 (`B06_wf3_ProfileCross_UI_Cross_View.ts:478`, `B07_wf4_DesignDetail_Engine_Cad.py:606`).
|
||||
- **표준단면 기하 정의**: 표준단면 기하의 정의는 오직 `STANDARD_CROSS_SECTION` 토큰 한 곳으로 한정하며(`config/config_system.py:340`), 타 파일에 중복 정의하는 것을 원천 금지합니다.
|
||||
- **B06 상세 조회 가이드**: 미지정 측점 프리뷰(`_attach_default_designs`)는 config 기본값으로 계산되며, 프론트 세션의 패널 편집값은 버튼 조작/확정 시점에 백엔드에 반영됩니다 (`B06_wf3_ProfileCross_Router_Confirm.py:73`).
|
||||
- **유토곡선 계산 기준**: 유토곡선(운반계획)의 모든 토량은 **다짐상태 기준**으로 통일해 누적하며, 내역서 작성용 자연상태 수량은 계산 엔진에서 추출된 `cut_natural_m3`를 가져와 활용합니다.
|
||||
- **장비 선정 거리 경계 정의**: 평균운반거리에 따른 운반장비 선정 경계는 오직 `config_system.py:464`의 `EARTHWORK_HAUL_EQUIPMENT_LIMITS_M` 상수 한 곳에서만 정의 및 참조해야 합니다.
|
||||
- **토량 배분 원칙**: 운반계획 로직은 ① 운반거리 최소화 ② 높은 곳에서 낮은 곳으로 ③ 토량을 모아 단일 장비로 운반, 이 3원칙을 위배하지 않아야 합니다 (`common_util_mass_haul_balance.ts`).
|
||||
- **`config_system.py` 잔여 용량**: 현재 **688줄**로 700줄 제한까지 **12줄**뿐입니다. 다음 상수 추가 시 **파일 분할을 먼저** 검토하십시오.
|
||||
- **유토곡선 곡선 형상은 직선**: `common_util_mass_haul_curve.ts:29`의 `PARABOLIC_MASS_CURVE = false`가 유일한 스위치입니다. 이 값 하나가 **그리기(`curvePath`)와 재기(`crossFrom`)를 함께** 바꿉니다.
|
||||
- **사면과 지반의 교차는 절·성토 모두 "첫 교차에서 끝"**: `cut_cross_dist` / `fill_cross_dist`가 같은 규칙입니다 (`B06_wf3_ProfileCross_Engine_Design.py:297, 348`).
|
||||
- **유토곡선 검산 항등식**: `운반(띠) + 장거리 운반 + 토취 = 총 성토량`. 화면 요약줄에 상시 표시되며, 차이는 극값 가지치기(`MIN_SWING_RATIO`) 손실입니다.
|
||||
- **장비 경계·자연방토 경사·환산계수의 정의처는 config 한 곳뿐**: `EARTHWORK_HAUL_EQUIPMENT_LIMITS_M`, `NATURAL_SPOIL_MIN_GROUND_SLOPE`, `EARTHWORK_CONVERSION_FACTORS`. 프론트는 context 응답으로 받아 쓰고 **사본을 두지 않습니다**.
|
||||
- **B06 저장 경로 두 갈래**: 임시 저장(`/save`)과 확정(`/confirm`)이 `_apply_section_edits()`를 공유합니다 (`B06_wf3_ProfileCross_Router_Confirm.py:8, 46`).
|
||||
- **법령 용어 주의**: [임도의 설계 및 시설기준] 별표가 설계서 구성으로 요구하는 것은 **토적표**이며, **'유토곡선'이라는 단어는 별표 전문에 등장하지 않습니다.** 유토곡선은 토적표에서 파생되는 실무 관행 도면이므로 "법정 요구 도면"으로 적지 마십시오.
|
||||
@@ -0,0 +1,117 @@
|
||||
# 개발 계획서 (완료 이관): B05/B06 종횡단 UI·유토곡선 오버레이·자동 계산 체인 연장 (33건)
|
||||
|
||||
- **이관 일시**: 2026-08-04
|
||||
- **이관 담당**: 코드 검증 AI (Verifier / QA)
|
||||
- **원본 계획**: `docs/raw/PLAN.md` 1부 (2026-08-04 구현 완료 항목 33건)
|
||||
|
||||
---
|
||||
|
||||
## 완료 및 아카이빙된 개발 작업 목록
|
||||
|
||||
### 1. B05 테이블 — 구조물 측점 기본값 미표기 — 완료
|
||||
- [x] 구조물 측점에서 항상 표기되는 셀 종류 특정 (측점/누가거리/거리/계획고/지반고/절토고/성토고/곡선)
|
||||
- [x] 해당 셀 기본 렌더 생략 (구배 블록과 같은 `is-structure` 규칙으로 통일)
|
||||
- [x] 선택 시 기존 값 열 오버레이로만 표시되는지 확인
|
||||
|
||||
### 2. B05 사이드 패널 — 구조물 목록 5개 이상이면 스크롤 — 완료
|
||||
- [x] 목록 컨테이너에 5개 높이 상한 + 내부 스크롤
|
||||
- [x] 항목 추가/삭제 시 스크롤 위치 유지(새 항목 추가 시 그 항목이 보이게)
|
||||
|
||||
### 3. B05 유토곡선 Y축 라벨이 표고(m)로 표기되는 버그 — 완료
|
||||
- [x] DOM 오버레이 중첩 실사 — 표고 오버레이의 높이/앵커가 유토곡선 영역을 침범하는지
|
||||
- [x] 유토곡선 오버레이가 `massYAxis`(㎥)를 실제로 받는지, 갱신 시점(rebuild) 문제인지
|
||||
- [x] 수정 후 가로 스크롤 시에도 좌측 고정 눈금이 ㎥로 유지되는지 확인
|
||||
|
||||
### 4. B05·B06 유토곡선 Y축 스케일 고정 (기본 −200 ~ +200㎥) — 완료
|
||||
- [x] `volumeRange`: 기본 ±200㎥, 초과 시에만 확장하는 규칙으로 교체
|
||||
- [x] B05·B06 양쪽에서 편집 반복 시 축이 안정적인지 확인
|
||||
|
||||
### 5. B05 종단 그래프 — ▼ 버튼과 X축 측점 라벨 오버랩 해소 — 완료
|
||||
- [x] 렌더러에 bottom 여백(또는 라벨 y 오프셋) 옵션 추가 — 기본값은 현행과 동일
|
||||
- [x] B05 하단 패널 호출부만 옵션 지정, 라벨·X축이 ▼ 버튼 위로 올라감
|
||||
- [x] 유토곡선과의 측점 세로선 정렬(left/right 여백)은 건드리지 않음 — X만 공유, 세로는 무관 확인
|
||||
- [x] B06 종단면도는 변화 없음 확인
|
||||
|
||||
### 6. B05 편집 버튼(▲▼·⇧⇩) 시인성 — 상승 빨강 / 하강 파랑 — 완료
|
||||
- [x] `.is-up` 빨강 계열, `.is-down` 파랑 계열 — 글자·테두리 색, hover 시 배경까지
|
||||
- [x] 라이트/다크 두 테마에서 대비 확인
|
||||
- [x] ↺(원복) 버튼은 현행 유지
|
||||
|
||||
### 7. B05 하단 패널 — 유토곡선을 오버레이 서브패널로 분리 — 완료
|
||||
- [x] 유토곡선 펼침이 종단도·테이블 높이에 영향 주지 않음 (1:1 분할 제거)
|
||||
- [x] 유토곡선 오버레이: 패널 바닥 고정, 위 경계 리사이저로 높이 조절(메인 패널과 동일 형태), 세션 보존
|
||||
- [x] 접기/펴기 손잡이 현행 유지, 접으면 오버레이 완전 제거
|
||||
- [x] 종단 그래프와 측점 세로선·가로 스크롤 동기화 확인
|
||||
- [x] 좌측 고정 Y축(㎥)·범례·요약 바가 오버레이 안에서 정상 동작 (3·4번과 연동 확인)
|
||||
- [x] `MASS_SPLIT_KEY`·`splitBar` 관련 코드 제거
|
||||
|
||||
### 8. B05→B06 진입 시 측점별 순차 재계산을 일괄 호출로 교체 — 완료
|
||||
- [x] stale 측점 재계산을 일괄 프리뷰 1회 호출로 교체, 카드 일괄 갱신
|
||||
- [x] 옛 암 2단계 마이그레이션 경로 처리 방식 확정
|
||||
- [x] B05 이탈 시 디바운스 중인 프리뷰 플러시(마지막 편집 반영)
|
||||
- [x] B05↔B06 왕복 시 진입 지연이 왕복 1회 수준인지 확인
|
||||
|
||||
### 9. 업로드 후 자동 계산 체인 연장 — B04 확정 → B05 기본 경로 → B06 기본 횡단 설계 — 완료
|
||||
- [x] B05 경로 계산을 서버 단독으로 실행하는 진입점 확인·정리(프론트 없이 호출 가능해야 함)
|
||||
- [x] WF1 확정 → B05 기본 경로 계산·저장 연결
|
||||
- [x] B05 저장 → B06 일괄 횡단 설계·저장 연결(기존 기본값 사용)
|
||||
- [x] 단계별 실패 격리 + workflow 상태 기록 + 실패 이메일
|
||||
- [x] 수동 확정 이력 있으면 자동 체인 건너뜀
|
||||
- [x] 완료 후 대시보드/B05 진입 시 저장본 로딩 확인(캐시 경로는 현행 유지)
|
||||
|
||||
### 10. B03 재접속 시 업로드 현황·완료 표시·재업로드 경고 — 완료
|
||||
- [x] 진입 시 서버 기준 업로드 현황 조회·표시(완료 파일 + 미완료 세션 진행률)
|
||||
- [x] 파일 재선택 전 이어올리기 안내 배너
|
||||
- [x] 전체 완료 상태 표시(업로드+분석)
|
||||
- [x] 완료 슬롯 재업로드 시 확인 모달, 승인 시에만 교체
|
||||
- [x] localStorage 상태를 서버 응답 보조로 정리
|
||||
|
||||
### 11. 진행단계 오버레이 최상단에 대시보드 버튼 추가 — 완료
|
||||
- [x] 세로 진행단계 목록 최상단에 대시보드 버튼(항상 활성)
|
||||
- [x] B04~B09 전 페이지에서 노출 확인
|
||||
|
||||
### 12. B04 재확정 시 B05·B06 재계산 체인 — 사용자 설정 이월 — 완료
|
||||
- [x] `run_redesign_chain` 신설(`B03_FileInput_Service_Chain.py`) — B04 확정 후 백그라운드 실행
|
||||
- [x] B05: stage 2 params(제어점·경사 옵션·측점 간격 등 사용자 입력) 유지, 지표면(filter/method/smooth/model id)만 새 확정값으로 교체해 재계산·재확정
|
||||
- [x] B06: 옛 경로의 측점별 설계(지반유형·단면유형·측구·암 경계)를 chainage 매칭으로 새 경로에 이월 + 표준단면 설정(data.options) 이월 후 확정(미지정 측점 기본값)
|
||||
- [x] 경로 없으면 신규 자동 체인으로 폴백, 실패는 단계별 격리·로그
|
||||
- [x] WF1 자동 확정 SYSTEM_ADMIN 예외 제거 — 역할 무관 B06까지 체인(7d7fd64)
|
||||
|
||||
### 13. B05 편집 버튼 — ⬆⬇ 채움 글리프·크기 확대 + 측점 버튼끼리 겹침 해소 — 완료
|
||||
- [x] 구간 이동 글리프 ⇧⇩ → 속 찬 ⬆︎⬇︎(텍스트 표기), 버튼 21×17·굵게
|
||||
- [x] 규칙·비정규 측점 버튼을 chainage 순 정렬 후 최소 간격(20px) 강제 — 근접 측점의 ▲▼가 서로 덮이지 않게 오른쪽 캐스케이드 배치(구간 버튼 회피 로직도 같은 좌표 사용)
|
||||
|
||||
### 14. B05 유토곡선 손잡이를 표준 패널 손잡이 양식으로 — 완료
|
||||
- [x] 풀폭 바 + 캡션 → 다른 패널과 같은 표준 삼각형 손잡이(64×24, 바닥 중앙, z-index 6)
|
||||
- [x] "유토곡선 펼치기/접기" 툴팁으로 대상 표기, 오버레이 bottom 0으로 조정
|
||||
|
||||
### 15. B05 유토곡선 영역 휠 가로 스크롤 — 완료
|
||||
- [x] 유토곡선 스크롤러에 종단 그래프와 같은 세로 휠 → 가로 이동 결선 (Shift+휠은 브라우저 기본 유지, scrollLeft 동기화로 종단 그래프도 함께 이동)
|
||||
|
||||
### 16~20. B05/B06 패널·버튼 시인성 정리 — 완료
|
||||
- [x] 16 편집 버튼 전체(측점 ▲▼·원복 ↺·구간 ⬆⬇) 21×17로 확대, 겹침 간격 23px로 재조정, X축 라벨 상향분 19px로 보정
|
||||
- [x] 18 유토곡선 손잡이가 오버레이 위 경계에 올라탐(펼치면 테이블 영역 침범 허용), 높이 조절 중에도 경계 추적
|
||||
- [x] 19 손잡이 테두리 강조색 `--panel-handle-border`(라이트 #2563eb 파랑 / 다크 #e0b64a 노랑) + 이름표: B05 메인 하단 "종단", 유토곡선 "유토곡선", 우측 패널 세로 90° "배수유역", B06 상단 "종단/유토곡선" (사이드바 제목 패널은 제외)
|
||||
- [x] 20 패널 리사이저(B05 3곳·B06 1곳) 상시 표시 — 손잡이와 같은 강조색 1px 줄, hover/드래그 시 3px·불투명
|
||||
|
||||
### 17. 초기선 복원 값·종단곡선 config 미반영 원인 확인 — 완료
|
||||
- [x] 원인 분석 및 현황 파악 완료 (base_pvi 스냅샷 우선 동작 구조 확인)
|
||||
|
||||
### 21~25. B05 유토곡선·배수유역·선택 UX·패널 시인성 — 완료
|
||||
- [x] 21 유토곡선 곡선 높이를 스크롤러 안높이(clientHeight) 기준으로 — 가로 스크롤바가 Y축 -200 라벨을 덮던 문제 해소, 스크롤바 상시 표시로 높이 안정화
|
||||
- [x] 22 절토·성토 요약 줄을 종단 열 안으로 이동 — 배수유역도 좌측 구분선 끊김(25px) 해소
|
||||
- [x] 23 선택 토글 해제 — 종단·유토곡선 측점 재클릭/빈 곳 클릭, 사이드바 구조물 재클릭 모두 해제(배수유역은 기존 토글 유지), 해제는 null로 전 화면 동기화
|
||||
- [x] 24 좌측 사이드 패널(제목 패널, B04~B07 공용) 우측 가장자리에 조절 패널과 같은 강조색 라인
|
||||
- [x] 25 닫힌 패널 표시(제안 적용) — 접힌 손잡이에 강조색 배경 틴트, 이름표는 유지
|
||||
|
||||
### 26~30. B05 접속 크래시·간격·sticky Y축·배관 곡선 — 완료
|
||||
- [x] 26 B05 접속 불가 원인 규명·수정 — 유토곡선 리사이저의 세션 높이 복원이 `open` 선언 전에 실행돼 TDZ ReferenceError. 상태 선언을 리사이저 생성 앞으로 이동. 리사이저 표시선 2px(hover 4px)로 상향
|
||||
- [x] 27 종단 하단 측점 라벨↔버튼 간격 70% 수준으로(bottomInset 19→15, gap 13→7px)
|
||||
- [x] 28 접힌 좌측 사이드 패널 손잡이가 본문 글자(Y축 라벨)를 덮지 않게 — 접힘 시 본문에 24px 거터(B04~B07 공용 레이아웃)
|
||||
- [x] 29 B06 종단·유토곡선에 가로 스크롤 고정 Y축 이식 — B05와 같은 buildStickyYAxis 재사용(CSS 정의처를 MassHaul css로 이동), 불투명 배경으로 지나간 눈금 노출 차단
|
||||
- [x] 30 배관 구조물 측점은 곡선 L·R 항상 표기(R 필수) — 구조물 기본값 숨김의 예외. 이웃 곡선 셀과 근접 시 배관 셀만 오른쪽으로 비킴, 선택 하이라이트 오버레이는 원위치 한 열 유지
|
||||
|
||||
### 31~33. 곡선 셀 회전·클릭 가로채기·배수유역 손잡이 — 완료
|
||||
- [x] 31 곡선 L·R 셀이 이웃과 겹치면 옆으로 비키는 대신 측점선 위 세로(회전) 표기 + 글자 축소(구배 행과 같은 방식)
|
||||
- [x] 32 곡선 L·R 수정 불가 원인 — 접힌 유토곡선 손잡이가 테이블 곡선 행 위에 떠서 입력 클릭을 가로챔. 테이블 아래 26px 손잡이 전용 띠 확보로 해소
|
||||
- [x] 33 배수유역 손잡이가 유토곡선 오버레이(z5) 아래 깔림 — z6으로 올려 항상 노출
|
||||
@@ -0,0 +1,40 @@
|
||||
# 개발 계획서 (완료 이관): 유토곡선 사토 balloon 표기 및 횡단 카드 스크롤 정렬
|
||||
|
||||
- **이관 일시**: 2026-08-04
|
||||
- **이관 담당**: 코드 검증 AI (Verifier / QA)
|
||||
- **원본 계획**: `docs/raw/PLAN.md` 1부 (현재 작업 / 검증 완료)
|
||||
|
||||
---
|
||||
|
||||
## 완료 및 아카이빙된 개발 작업 목록
|
||||
|
||||
### A. 사토 balloon에 운반거리·장비 표기 — 완료
|
||||
|
||||
- [x] 사토 balloon에 `L=`·장비 표기
|
||||
- [x] 부지가 둘 이상이면 잔량 구간별로 가까운 부지에 매칭
|
||||
- [x] 대상 파일: `common_util_mass_haul_balance_view.ts`(balloon 본문), `common_util_mass_haul_settle.ts`(잔량-부지 매칭 후 장비 부여)
|
||||
|
||||
### B. 자연방토 임계 경사 확정 — 완료 (값 유지)
|
||||
|
||||
배경: `NATURAL_SPOIL_MIN_GROUND_SLOPE = 1/1.5`(≈33.7°)는 사토를 흘려보내는 경사로의 경사를 반영한 초기값이었다.
|
||||
2026-08-04 사용자 판단: 현업에서는 사토 경사를 별도로 산정하지 않고 **도면에 사토했다는 사실만 남기는** 것이 관행이다. 따라서 값을 바꾸지 않고 `1/1.5`로 확정한다.
|
||||
|
||||
- [x] 코드 반영 확인 — 정의처 `config/config_system.py:481` 한 곳, `B06_wf3_ProfileCross_Router.py:105` → API context → `B06/B05_UI_Page.ts` → `common_util_mass_haul_balance_view.ts:510` balloon 표기까지 전 경로 연결됨
|
||||
- [x] 값 변경 없음 (`1.0 / 1.5` 유지)
|
||||
|
||||
### C. 유토곡선 → B08 수량산출 인계 — 취소
|
||||
|
||||
취소 사유: 사토장·토취장 **자동 선정** 기능이 취소됐고, 사토장은 향후 사용자가 B05에서 **구조물로 직접 작업**하는 방식으로 바뀐다. 임도 특성상 이 경우 사토 운반비가 계상되지 않으므로 B08으로 운반 항목을 인계할 대상 자체가 없다.
|
||||
|
||||
- [x] 취소 확정 — 코드 변경 없음
|
||||
|
||||
### D. 종단도·유토곡선 측점 선택 → 횡단도 카드 정렬 위치 수정 — 완료
|
||||
|
||||
배경: 측점 검색·입력 점프는 만들지 않는다(2026-08-04 사용자 확정). 종단도·유토곡선 측점 클릭 → 해당 횡단도 카드 스크롤은 이미 있었으나, `scrollIntoView({block: "nearest"})`가 카드가 아래에 있을 때 카드 **하단**을 브라우저 바닥에 맞춰 멈춰, 카드 상단이 화면 밖이었다.
|
||||
|
||||
완료 조건:
|
||||
- [x] 종단도·유토곡선 측점 선택 시 해당 카드로 이동 (기존 경로 유지, 두 그래프 모두 `selectStation(…, true)` 연결 확인)
|
||||
- [x] 정렬 위치: 카드 **상단**이 sticky 상단 패널의 접기 손잡이 **바로 아래**에 붙도록 `block: "start"` + `scroll-margin-top`(패널 실측 높이 + 손잡이 24px + 여유 8px). 여유 8px로 선택 강조 빨간 테두리(2px ring)가 잘리지 않는다
|
||||
- [x] 패널 높이가 리사이저·접기로 수시로 변하므로 **스크롤 직전 실측**(`syncScrollMargin`) — 접을 때 drawPanel이 돌지 않아도 정지선이 현재 패널과 맞는다
|
||||
- [x] 예외: 목록 맨 끝 카드는 스크롤이 바닥에 걸려 그 위치까지 못 올라옴 — 브라우저가 알아서 멈추므로 별도 처리 없음
|
||||
- [x] 대상 파일: `B06_wf3_ProfileCross_UI_Section_View.ts` (`revealCard`·`syncScrollMargin`)
|
||||
@@ -0,0 +1,140 @@
|
||||
# 완료 계획서: B04/B05 배수설계 강우량 연동 + 구조물 옵션 체계
|
||||
|
||||
> 완료일: 2026-08-05
|
||||
> 검증 보고서: `docs/raw/verification/2026-08-05_verify_b04_b05_drainage_rainfall_structure.md`
|
||||
> 커밋 이력: fa6953c, 3d5e898, 2876ca5, 75232b9, 0025538, 69aed9d, 8182541, 6273abc (push 완료)
|
||||
|
||||
---
|
||||
|
||||
## B04/B05 배수설계 강우량 연동 + 구조물 옵션 체계 (구현 완료 — 검증 완료)
|
||||
|
||||
> 구현 2026-08-05 완료. 커밋 fa6953c(Phase 1·2), 3d5e898(Phase 3~5), push 완료.
|
||||
> 통합검증(실 프로젝트 acb9170b, 정선권 EPSG:5187): 강우량표 생성 → IDF 적합(잔차 0.62mm/hr)
|
||||
> → 세부유역 4개 전부 유효직경 산출(D488~D5137). 서버 기동·라우터 등록·인증 가드 확인.
|
||||
> 오프라인 테스트 66개 통과, typecheck 통과.
|
||||
|
||||
> 협의일 2026-08-05. 사용자 결정사항: ① 설계강우강도 산정법은 지침 근거 재조사 후 결정(아래 조사 결과),
|
||||
> ② wamis 등우선 자료는 **전 조합(96개) 다운로드 후 영구저장**, ③ HTTP는 **urllib 개조**(의존성 추가 없음),
|
||||
> ④ 관경 미정 상태를 **배수 유효면적·배수 유효직경**으로 표현, ⑤ 기존 캐시 3계층
|
||||
> (업로드 직후 서버 선계산 → 영구저장 / 수정은 브라우저 캐시 / 확정버튼으로 영구저장 반영) 구조를 유지할 것,
|
||||
> ⑥ 유출계수 C=0.8은 config_system에 넣어 사용자가 수정 가능하게.
|
||||
>
|
||||
> **지침 조사 결과(2026-08-05 웹 확인)**: 임도 전용 수리계산 지침은 없음 —
|
||||
> 「임도설치 및 관리 등에 관한 규정」(산림청 훈령)은 배수 수리계산 조문 부재(제12조 물넘이·물받이만 언급).
|
||||
> 구속력 있는 임도 기준은 시행규칙 별표2뿐이며, 별표2 1호가 명시한 방법이
|
||||
> **"100년빈도 확률강우량 + 홍수도달시간 → 합리식"**(방법 1). 도달시간은 유역 지형(유하장·표고차)에서
|
||||
> 계산하되, 하한 5분은 「도로 배수시설 설계 및 관리지침」·「국도건설공사 설계실무요령」의
|
||||
> "강우지속기간 5분 원칙" 준용. → **방법 1 채택 제안** (극한호우 방법 B는 기상청 API 필요, 보류).
|
||||
|
||||
### 검증 완료 근거 (2026-08-05 실측)
|
||||
|
||||
- `wamis_rainfall.py` 오프라인 테스트 66개 전부 통과.
|
||||
- `map.wamis.go.kr` 실서버 응답 확인: 100년/1시간 HTTP 200(69KB), 100년/24시간 HTTP 200(527KB),
|
||||
1시간 미만(30mi) 404. 문경 내삽값 100년 1시간 142.5mm, 24시간 387.5mm — 타당 범위.
|
||||
- venv에 requests 없음 → urllib 어댑터로 실측 성공(의존성 0 확인).
|
||||
|
||||
### Phase 1 — 확률강우량 수급·영구저장 (백엔드) [요구 2·3]
|
||||
|
||||
대상: `common_util/` 신규 모듈, `B04_wf1_Surface_Router_Watershed.py`(전처리 훅), storage 경로
|
||||
|
||||
- [x] `common_util/common_util_wamis_rainfall.py` 신설: 첨부 `wamis_rainfall.py` 이식.
|
||||
requests 제거하고 표준 urllib 세션 어댑터로 개조. CLI(main) 부분 제거, 라이브러리 함수만 유지. 700줄 제한 준수.
|
||||
- [x] 등우선 원본 96개(8빈도×12지속시간) 전역 캐시 다운로드: `resources/wamis_contours/{NNN}yr_{NNhr}.json`.
|
||||
전국 공통 자료이므로 프로젝트별이 아닌 전역 1회 저장(총 약 20~30MB). 파일 존재 시 재다운로드 생략.
|
||||
- [x] 프로젝트 전처리(배수유역 1차 해석) 시 계획노선 중심좌표로 96조합 내삽 →
|
||||
`storage/.../B04_wf1_Surface/drainage/rainfall_table.json` 영구저장 (place, lat/lon, 96값, 산출방법 기록).
|
||||
- [x] 다운로드 실패(외부망 차단 등) 시 배수유역 해석은 정상 진행, rainfall_table에 실패 사유 기록(비치명).
|
||||
- [x] `GET /api/projects/{id}/drainage/rainfall` 조회 API 신설(B05 세션 활용 대비).
|
||||
|
||||
### Phase 2 — 설계강우강도(방법 1)·배수 유효직경 계산 (백엔드) [요구 1·4·14 계산부]
|
||||
|
||||
대상: `common_util/common_util_drainage_detail.py` 확장 또는 계산 전용 모듈 신설, `B04_wf1_Surface_Router_Basins.py`,
|
||||
`config_system`(유출계수 등 계수 등록)
|
||||
|
||||
- [x] 유역별 홍수도달시간 t 계산(별표2 1호 "홍수도달시간"): 이미 산출된 유역 제원
|
||||
**유하장 L·표고차 H**로 Kirpich식 t = 0.0663·L^0.77·S^-0.385 (S=H/L). 하한 5분
|
||||
(도로배수지침·국도건설공사 설계실무요령 "강우지속기간 5분 원칙" 준용, 근거 주석 명기).
|
||||
- [x] 설계강우강도 I(t): wamis 100년빈도 12개 지속시간(1~24h) 내삽값을 General형 강우강도식
|
||||
(환경부 「홍수량 산정 표준지침」 계열)으로 적합 후 t분 강우강도 산출. 적합 결과·잔차는 rainfall_table.json에 기록.
|
||||
- [x] 합리식 Q = (1/3.6)·C·I·A: A = 세부유역 적색(배수 영향) 면적[km²],
|
||||
C = 유출계수 기본 0.8 — **config_system 등록, 사용자 수정 가능** [사용자 결정 ⑥].
|
||||
- [x] 설계유량 = 2.0 × Q (별표2 (가): 최대홍수유출량의 2.0배 이상).
|
||||
- [x] 배수 유효직경 D_eff [사용자 결정 2026-08-05]: 유속의 개별 산출(DEM 지형경사 등)은 **배제** —
|
||||
시공 중 관 경사·길이가 수시 변경되고 계곡부 성토로 지형경사와 무관해지므로.
|
||||
대신 고정 가정으로 일괄 계산: 관 경사 10도(사용자 제시 실무값), 파형강강관 n=0.024로 Manning 유속 산출 후
|
||||
지침 상한 3.0m/s 클램프(세굴 한계), 통수단면 70% 적용(도로설계요령: 원형관 통수단면 70%).
|
||||
D_eff = sqrt(4·A_필요/(0.7·π)), A_필요 = Q_설계/V. 설계유량이 이미 2.0배라 여유 충분.
|
||||
관 경사·n·유속 상하한·70% 계수 전부 **config_system 등록**(사용자 수정 가능).
|
||||
- [x] 세부유역별 산출 필드 추가: `배수 유효면적(m²)`, `배수 유효직경(mm)`, `도달시간(분)`, `설계유량(m³/s)` →
|
||||
`04_detailed_basins.geojson` properties 및 basins API 응답에 포함(기존 캐시 3계층 흐름 그대로 유지).
|
||||
- [x] B05 좌측 패널 유역 제원 표시 확장: 면적/표고차/유하장 + **배수 유효면적/배수 유효직경** 출력
|
||||
(`renderDrainageBasinList()`).
|
||||
|
||||
### Phase 3 — 구조물 드롭다운·하위 옵션 (프론트+데이터모델) [요구 6·7·14]
|
||||
|
||||
대상: `pipe_points.json` 스키마, `B04_wf1_Surface_Router_Basins.py`, `B05_wf2_Route_UI_Drainage_Panel.ts`,
|
||||
`_UI_Profile_Structures.ts`, `_UI_Selection.ts`
|
||||
|
||||
- [x] 구조물 데이터 모델 확장(pipe_points 항목): `structure_type`(배관|기성막이|대피로|기타, 기본 배관),
|
||||
`pipe_type`(이중벽관|삼중벽관|파형강관, 기본 파형강관), `diameter_mm`, `escape_width_m`, `custom_name`.
|
||||
기존 저장분은 기본값으로 마이그레이션(로드 시 보충).
|
||||
- [x] 구조물 드롭다운 UI: 1단 = 배관/기성막이/대피로/기타.
|
||||
- [x] 배관 2단(관종) + 3단(직경) 옵션:
|
||||
- 이중벽관: D150/200/250/300/400/450/500(기본)/600/700/800/900/1000/1200/1500
|
||||
- 삼중벽관: D200/250/300/400/450/500(기본)/600/700/800/900/1000/1200/1500
|
||||
- 파형강관(기본 관종): D150/200/250/300/350/400/450/500(기본)/600/700/800/900/1000/1200/1350/1500/1650/1800/2000
|
||||
- [x] 기성막이: 하위 옵션 "미정" 플레이스홀더(추후 반영).
|
||||
- [x] 대피로: 폭 1.5/2.0(기본)/2.5/3.0m.
|
||||
- [x] 기타: 이름 텍스트 입력(기본값 "기타 구조물").
|
||||
- [x] 사용자 미입력 신규 구조물도 기존처럼 측점/잔여거리/구조물 이름 자동 기입 유지.
|
||||
- [x] [요구 14, 사용자 결정 2026-08-05] 배수 유효직경 기준 자동 지정: 관종 기본 파형강관,
|
||||
기본 직경 = **D800** (법정 예외 하한). D_eff가 800mm 초과 시 바로 위 규격 자동 선택.
|
||||
유역도 내 계산값(D_eff)은 가공 없이 그대로 표시만 하고, 관경 선정은 좌측 패널 드롭다운에서만.
|
||||
소구경(D150~) 리스트 유지(특수사항 수동 선택용). 사용자가 수동 변경 시 자동값 덮어쓰지 않음.
|
||||
- [x] 패널 선택값 기본값(구조물 타입=배관, 관종=파형강관, 직경=D800, 대피로 폭=2.0m,
|
||||
기타 이름="기타 구조물")을 **config_system 파일에서 관리**.
|
||||
- [x] [요구 7] 사이드 패널/종단/배수유역도에서 구조물 선택 시 해당 옵션값 표시(기존 표시 로직 확장).
|
||||
|
||||
### Phase 4 — 우클릭·선택 마킹 (프론트) [요구 8·9·10·11]
|
||||
|
||||
대상: `_UI_Profile_Structures.ts`(mountStructureMenu), `B05_wf2_Route_UI_Drainage_Pipes.ts`,
|
||||
`_UI_Selection.ts`, `B05_wf2_Route_UI_Viewer.ts`, 유토곡선/테이블 컨테이너
|
||||
|
||||
- [x] [요구 8] 종단도·배수유역도 우클릭 메뉴에 구조물 항목(배관/기성막이/대피로/기타) 추가 명령 확장.
|
||||
- [x] [요구 9] 유토곡선·테이블 영역 우클릭 시 브라우저 기본 메뉴 차단(contextmenu preventDefault).
|
||||
- [x] [요구 10] 측점 선택 시 배수유역도 캔버스에도 마킹(선택 동기화 대상에 배수유역도 추가).
|
||||
- [x] [요구 11] 측점 선택 시 3D 마킹 강화. 원형 지양 — **수직 핀 마커 제안**: 지형 표면에서 수직으로 솟는
|
||||
기둥(높이 지형 스케일 비례) + 상단 역삼각 헤드 + 펄스 없는 고대비 강조색. 채택 여부 사용자 확인.
|
||||
|
||||
### Phase 5 — 하단 패널 레이아웃·스타일 (프론트) [요구 12·13]
|
||||
|
||||
대상: `B05_wf2_Route_UI_Profile_Panel.ts`, `B05_wf2_Route_UI_Profile_Table.ts`,
|
||||
`B06_wf3_ProfileCross_UI_Longitudinal.ts`(Y축 렌더 공유)
|
||||
|
||||
- [x] [요구 12] 테이블을 유토곡선과 동일한 바닥 고정 오버레이 서브패널 형태로 재구성.
|
||||
접힘 시 버튼 일렬 배치. 동시 표시 시 위→아래 순서: 종단도 → 테이블 → 유토곡선.
|
||||
- [x] [요구 13] 테이블 행제목: 글자 우측 정렬 + 좌측 여백 확대(버튼 걸림 해소).
|
||||
- [x] [요구 13] 종단도·유토곡선 Y축 텍스트 크기 증가.
|
||||
|
||||
### 협의 종결 사항 (2026-08-05 확정)
|
||||
|
||||
- 산정법 = **방법 1**(100년빈도 + 지형 기반 도달시간 + 합리식, 하한 5분). 기상청 API 보류.
|
||||
- 유속 = 개별 산출 배제, 고정 가정(관 경사 10도, n=0.024, 상한 3.0m/s, 통수단면 70%), 전부 config_system.
|
||||
- 관경 자동 지정 기본값 = **D800**, 계산값(D_eff)은 표시만, 소구경 리스트 유지, 패널 기본값 config_system 관리.
|
||||
- 유출계수 C=0.8 config_system 등록.
|
||||
- 조사 확보한 설계규정 원문은 `docs/raw/guidelines/`에 별도 파일 보관.
|
||||
|
||||
### 세션 작업 이력 (2026-08-05, 검증·아카이빙용 커밋 목록)
|
||||
|
||||
계획서 Phase 1~5 구현 + 사용자 실사용 피드백 7라운드 반영. 전부 push 완료.
|
||||
|
||||
| 커밋 | 내용 |
|
||||
|---|---|
|
||||
| `fa6953c` | Phase 1·2: `common_util_wamis_rainfall.py`(urllib 등우선 수급·전역 캐시·IDF 적합), `/drainage/rainfall` API+전처리 훅, `estimate_pipe_diameter_mm` 구현(Kirpich 하한5분+합리식 C0.8×2.0+Manning 10도 V≤3.0+통수 70%), basins 응답·B05 유역 제원 표기, config_system 계수·구조물 옵션 |
|
||||
| `3d5e898` | Phase 3~5: 구조물 드롭다운(배관/기성막이/대피로/기타+관종·직경/폭/이름), D800 기본 자동지정(유효직경 초과 시 상위 규격, userSized 보존), 우클릭 구조물 추가, 유토·테이블 기본 메뉴 차단, 배수유역도 다이아몬드 마킹, 3D 수직 핀, 테이블 오버레이 서브패널화, 행제목 우측 정렬, Y축 10→13px |
|
||||
| `2876ca5` | 세월교 판정: 유효직경 > D2000(config `DRAINAGE_BRIDGE_THRESHOLD_MM`) → `bridge_required`, "세월교 검토(Ø계산값)" 표기·자동지정 제외(임도설치규정 제12조). 실측 41.4ha 계류 D5137 → 세월교. 하단 패널 밀어올리기(오버레이가 종단을 덮지 않음) |
|
||||
| `75232b9` | 3D 빈 공간 클릭 선택 해제 취소(회전 드래그 오인), 구조물 폼 규칙(타입 변경=신규 전환, 자동 배관 타입 잠금), 접힌 손잡이 정렬, 종단 최소높이 부족 시 메인 패널 자동 확장 |
|
||||
| `0025538` | 3D 흑백 지형 토글(정점색 1회 변환), 유토곡선 접힘 버튼 바닥 고정 규칙, LONG_PAD.left 62→78(좌측 가림 해소, 마스크 동반 확장) |
|
||||
| `69aed9d` | 구조물 종류 빈칸 옵션(초기·리셋=빈칸·미선택 추가 불가), 사이드 최하단 고정 액션 공용 클래스 `ui-sidebar-actions`(B04/B05/B06, B07 기존 유지), 높이 캐스케이드(종단→테이블·유토 비례 축소→동적 하한 차단), 공용 리사이저 min 함수 지원 |
|
||||
| `8182541` | 유토곡선 상단 padTop 40(B05, 범례 겹침 해소 — 공용 렌더러 옵션), 사이드 컨테이너 공통 외곽선 강화(`ui-sidebar-section`), 하단 패널 리사이즈 경량 동기화(프레임당 높이만, 재구성은 120ms 디바운스) — 끊김 해소 |
|
||||
| `6273abc` | 리사이즈 진동·복귀 버그: 드래그 출처 추적(pointerdown 플래그) — 서브패널 드래그 중 임시 축소(인라인) 금지·grow로 메인 밀어올림, 메인 드래그 중 grow 금지(포인터와 진동 차단) |
|
||||
@@ -0,0 +1,81 @@
|
||||
# B05/B06 UI 및 엔진 6종 기능 개선 완료 이력 (2026-08-06)
|
||||
|
||||
### 1. B06 반폭 표시 후속 — 계획선 플롯 이탈 + 0측점 전역 반폭 미반영 수정 (2026-08-06)
|
||||
|
||||
**배경**: 사용자 보고(2026-08-06, 반폭 개편 d93bd6e 직후 화면 확인).
|
||||
1. 반폭으로 지면은 잘리는데 **계획선(설계선)이 플롯 밖으로 뚫고 나감** — 표시
|
||||
자름이 지면 샘플 필터(crossPlotMetrics)만 거치고, 설계선·절성토 면적 밴드·
|
||||
포장층·암 경계선 오버레이는 원래 좌표 그대로 그려서 생긴 문제.
|
||||
2. **0측점이 전역 반폭을 안 따름** — 어제 개별 반폭 테스트 잔재가
|
||||
design.display_half_width_m 로 영구 저장돼 있었고, 개별값이 전역보다
|
||||
우선하는 구조라 [전체 측점 반영]을 눌러도 해당 측점은 고정.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. 횡단 카드 SVG에 플롯 영역 clipPath 추가, 설계선·면적 밴드·포장층·암 경계선 오버레이를 clip 그룹 안에 그림 (`B06_wf3_ProfileCross_UI_Cross_View.ts`)
|
||||
- [x] 2. 오버레이 함수 시그니처 `SVGSVGElement`→`SVGElement` (clip `<g>` 수용, `_UI_Cross_Design.ts`·`_UI_Cross_Areas.ts`)
|
||||
- [x] 3. [전체 측점 반영] 시 개별 반폭 초기화 — 세션에 전역값을 전 측점에 명시해 저장된 개별값(display_half_width_m)보다 우선, 확정 시 저장값도 전역으로 덮임 (`B06_wf3_ProfileCross_UI_Page.ts` applyPanelToAll)
|
||||
- [x] 4. `tsc --noEmit`·`prettier` 통과
|
||||
- [x] 5. 실제 앱 검증 완료
|
||||
- [x] 6. 커밋 + push
|
||||
|
||||
---
|
||||
|
||||
### 2. B06 횡단 반폭 개편 + 재계산 버튼 통합 + 측구 방향 확정 역행 수정 (2026-08-06)
|
||||
|
||||
**배경**: 사용자 지시 3건(2026-08-06). 반폭 기준 20m 적용, 높이 배율 제거, 재계산 버튼 삭제 및 전체 측점 반영으로 기능 통합, 측구 방향 확정 동기화 보강.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] A1. config: `SECTION_CROSS_HALF_WIDTH_M` 기본 15→20
|
||||
- [x] A2. B06 높이 배율 필드 제거, 렌더 배율 1 고정
|
||||
- [x] A3. 반폭 필드를 표준 횡단면 설정 컨테이너 [전체 측점 반영] 위로 이동
|
||||
- [x] A4. 재계산 버튼 삭제. [전체 측점 반영] 확장 및 regenerate에 grade_options 재구성 추가
|
||||
- [x] A5. 횡단 카드 개별 반폭 ◀/▶/↺(±1m·전역 복귀)
|
||||
- [x] A6. 횡단 카드 방위각 표기 제거
|
||||
- [x] A7. 확정·임시저장 시 개별 반폭 저장(design.display_half_width_m) 및 이월 처리
|
||||
- [x] B1. B05 경로확정 uphill 병합 시 저장 설계 재계산·저장(sync_uphill_overrides_into_designs)
|
||||
- [x] C1. `tsc --noEmit`·`ruff format`·`prettier` 통과
|
||||
- [x] C2. 실제 앱 검증 완료
|
||||
- [x] C3. 커밋 + push
|
||||
|
||||
---
|
||||
|
||||
### 3. 3D 측점 라벨 라운드 + B04~B07 사이드 컨테이너 외곽선 강화 (2026-08-06)
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. 3D 라벨 배경 fillRect → roundRect(반경 10) (`B05_wf2_Route_UI_Markers.ts`)
|
||||
- [x] 2. 외곽선 혼합비 강화 45%→25% 경계색(보조색 75%) + 공통 클래스가 테두리 두께·형태까지 소유 (`ui_template_overlay.css`)
|
||||
- [x] 3. B07 설계 도면 그룹에 ui-sidebar-section 적용 + 라운드·패딩
|
||||
- [x] 4. `tsc --noEmit` 통과 + `prettier`
|
||||
- [x] 5. 실제 앱 확인 완료
|
||||
- [x] 6. B04~B06 로컬 border 제거로 일원화
|
||||
|
||||
---
|
||||
|
||||
### 4. B05 좌측 패널 — 공사 시작점·측점 샘플링 컨테이너 일원화 (2026-08-06)
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. 로케일 라벨 변경 "측점 및 샘플링 설정" → "시작 측점 및 샘플링 설정" (`ui_template_locale_b2.ts`)
|
||||
- [x] 2. 시작 측점·누가거리 행을 sectionOptions 최상단으로 prepend, "공사 시작점" 섹션 제거 (`B05_wf2_Route_UI_Panel.ts`)
|
||||
- [x] 3. `tsc --noEmit` 통과 + `prettier` 변경 없음
|
||||
- [x] 4. 실제 앱 확인 완료
|
||||
|
||||
---
|
||||
|
||||
### 5. B05 구조물 배치 목록 배관 중복(복원 유령) 제거 (2026-08-06)
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. `setPipeStations` 복원 유령 제거(±0.5m·structureType 없음 조건) (`B05_wf2_Route_UI_IrregularStations.ts`)
|
||||
- [x] 2. `tsc --noEmit` 통과 + `prettier` 변경 없음
|
||||
- [x] 3. 실제 앱 확인 완료
|
||||
- [x] 4. 커밋 + push
|
||||
|
||||
---
|
||||
|
||||
### 6. B05 3D 뷰 마커 개선 — 측구 램프 축소·측점선 가시성·회전 중심 (2026-08-06)
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. 램프 절반(1.1→0.55, 띄움 0.6→0.3) + 투명 히트 구(1.1, opacity 0) (`Markers.ts`)
|
||||
- [x] 2. 측점선 Line → 납작 띠 BoxGeometry(길이×0.18×0.5, 띄움 0.8) + selectStation Mesh 대응 (`Markers.ts`)
|
||||
- [x] 3. 회전 중심 링+십자 조준점 스프라이트 (`B04_..._UI_Camera.ts`)
|
||||
- [x] 4. `tsc --noEmit` 통과 + `prettier`
|
||||
- [x] 5. 실제 앱 구동 확인 완료
|
||||
@@ -0,0 +1,50 @@
|
||||
# B05 하단 패널 리사이즈 튐·자동 확대 버그 수정 (2026-08-06)
|
||||
|
||||
**배경**: 하단 패널(종단/테이블/유토곡선 연계)에서 메인 패널·서브패널 크기를
|
||||
조절하다 보면 패널이 갑자기 커지거나 튀는 증상. 2026-08-06 분석으로 원인 4개 확정.
|
||||
분석 상세는 아래 "원인 요약" 참조. 대상 파일은 `B05_wf2_Route_UI_Profile_Panel.ts`
|
||||
(주 대상), `B05_wf2_Route_UI_Profile_MassHaul.ts`, `B05_wf2_Route_UI_Profile_TableOverlay.ts`,
|
||||
`ui_template/ui_template_resizer.ts`(보조).
|
||||
|
||||
**원인 요약** (분석 2026-08-06):
|
||||
1. **캐스케이드 왕복 진동(핵심)**: `applyHeightCascade()`가 서브패널 높이를
|
||||
화면에 보이는 현재 높이(임시 축소 반영값)로 측정 → 호출마다 "줄임 ↔ 풀림"
|
||||
번갈아 발생. 풀리는 호출에서 자리 부족(deficit) 판정이 큰 높이 기준으로 돌아
|
||||
메인 패널을 자동 확대 — 사용자가 줄여 놓은 패널이 다음 그리기에서 도로 커짐.
|
||||
2. **자동 확대 상한 불일치**: grow 상한은 창 높이 92%, 메인 리사이저 드래그
|
||||
상한은 부모 높이 90%. 자동 확대 후 손잡이를 잡는 순간 낮은 상한으로 clamp되어
|
||||
뚝 떨어짐. grow가 CSS 변수를 직접 써 세션 저장값과도 어긋남.
|
||||
3. **서브패널 드래그 = 매 프레임 전체 재구성**: 서브패널 리사이저 onResize가
|
||||
매 프레임 전체 draw() 호출(차트 SVG+테이블 재생성) — 무거워서 덜컹거림.
|
||||
드래그 중 grow가 부모를 키워 서브패널 상한이 따라 커지는 되먹임도 있음.
|
||||
4. **손 뗀 뒤 120ms 지연 draw 무방비**: 드래그 플래그가 pointerup 즉시 해제되고
|
||||
디바운스 draw는 그 뒤 실행 → 드래그 중 금지됐던 "풀림+자동 확대"가 손 떼는
|
||||
순간 발동.
|
||||
|
||||
**수정 방침**:
|
||||
- (원인 1) 캐스케이드 판정 기준을 "지금 보이는 높이"에서 **저장된 높이(CSS
|
||||
변수/세션값, 임시 축소 제외)**로 변경 — 몇 번을 호출해도 같은 답이 나오게
|
||||
(멱등). 임시 축소 상태는 별도 변수로 추적하고, 풀림 여부도 저장 높이 기준으로
|
||||
판정한다. 풀림 직후 deficit 재확대 경로는 자연 소멸.
|
||||
- (원인 2) grow 상한을 메인 리사이저 max와 **같은 식**(부모 높이 × 0.9)으로
|
||||
통일. grow 시 세션 저장값은 건드리지 않되(사용자 의도 아님), 드래그 시작
|
||||
시점 clamp로 인한 급락이 없도록 상한만 일치시킨다.
|
||||
- (원인 3) 서브패널 드래그 중에는 메인 패널과 같은 **경량 동기화**(캐스케이드
|
||||
계산+캔버스/차트 높이만)로 바꾸고, 전체 재구성은 손 뗀 뒤 디바운스 1회.
|
||||
onChanged 콜백에 "드래그 중 여부"를 구분해 넘기거나 Panel 쪽에서 판단.
|
||||
- (원인 4) 디바운스 draw에 드래그 출처 유예 적용 — pointerup 후 첫 지연 draw
|
||||
까지는 grow 금지(또는 플래그 해제를 지연 draw 이후로 미룸).
|
||||
|
||||
**완료 조건**: 아래 시나리오에서 튐·자동 확대 없음.
|
||||
- 서브패널 2개 연 상태에서 메인 패널을 최소까지 줄였다 놓아도 도로 커지지 않음.
|
||||
- 자동 확대 직후 메인 손잡이를 잡아도 급락하지 않음.
|
||||
- 서브패널 드래그가 프레임 드랍 없이 부드럽고, 손 뗀 뒤 1회만 재구성.
|
||||
- 클릭·편집 등 일반 redraw에서 서브패널 높이가 흔들리지 않음.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. `applyHeightCascade()` 멱등화 — 저장 높이(`desiredHeight()` 신설: MassHaul·TableOverlay) 기준 판정으로 전환. offsetHeight 측정 제거 (`B05_wf2_Route_UI_Profile_Panel.ts` applyHeightCascade)
|
||||
- [x] 2. grow 상한을 리사이저 max(부모×MAX_PANEL_HEIGHT_RATIO 0.9)와 통일 — 기존 window 92% 폐지
|
||||
- [x] 3. 서브패널 드래그 중 경량 동기화 — Panel의 `subPanelChanged` 분기(드래그 중 `scheduleLightSync`, 그 외 전체 draw). 손 뗀 뒤 `clearDragFlags`가 전체 재구성 1회 예약(120ms)
|
||||
- [x] 4. 디바운스 draw 드래그 출처 유예 — `mainDragCooldown` 플래그: 메인 드래그 pointerup 후 첫 전체 draw까지 grow 금지, draw가 해제
|
||||
- [x] 5. `prettier` 포맷팅(변경 없음) + `tsc --noEmit` 통과
|
||||
- [x] 6. 소스 대조 검증 — 코드 검증자(QA/Verifier)에 의한 1~5 항목 정밀 소스 검증 완료
|
||||
@@ -0,0 +1,39 @@
|
||||
# B05 메인 패널 리사이즈 시 3영역 비례 연동 (2026-08-06)
|
||||
|
||||
**배경**: 리사이즈 안정화(아래 작업, 커밋 `d8723f4`) 이후 사용자 추가 요구.
|
||||
현재는 메인 패널을 줄이면 종단이 먼저 최소까지 줄고 그 다음 서브패널이 줄며,
|
||||
늘리면 종단만 늘어난다. 이를 **메인 패널 크기 조절 시 종단/테이블/유토곡선이
|
||||
같은 비율로 함께 늘고 줄도록** 바꾼다. 최소 높이에 걸리는 영역은 먼저 멈추고
|
||||
(종단 100 / 테이블 140 / 유토곡선 160px), 나머지가 남은 공간을 비율대로 나눈다.
|
||||
|
||||
**롤백 기준**: 결과가 마음에 안 들면 커밋 `d8723f4`로 되돌린다(메모리 기록됨).
|
||||
|
||||
**동작 스펙**:
|
||||
1. 메인 리사이저 드래그 **시작 시점**의 세 영역 실제 높이를 기준 비율로 잡는다.
|
||||
2. 드래그 중 본문 높이 변화를 세 영역에 비례 배분한다. 최소에 닿은 영역은
|
||||
최소에 고정하고, 남은 영역끼리 다시 비례 배분한다(줄일 때). 늘릴 때도 같은
|
||||
비율로 커진다(서브패널 상한 75%/80%는 안전상 유지).
|
||||
3. **손을 떼면** 그 시점의 테이블·유토곡선 높이를 각 서브패널의 저장 높이
|
||||
(리사이저 CSS 변수+세션)로 **확정**한다 — 이후 어떤 redraw에도 그 높이가
|
||||
기준이 되어 멱등성(안정화 수정의 핵심)이 유지된다.
|
||||
4. 접힌 서브패널은 비례 대상에서 제외. 서브패널 개별 드래그·펼침/접힘·
|
||||
밀어올림(grow)·더블클릭 리셋 등 기존 동작은 그대로.
|
||||
|
||||
**대상 파일**: `B05_wf2_Route_UI_Profile_Panel.ts`(비례 배분 로직),
|
||||
`B05_wf2_Route_UI_Profile_MassHaul.ts`·`B05_wf2_Route_UI_Profile_TableOverlay.ts`
|
||||
(저장 높이 확정 API `commitHeight(px)` 신설 — 리사이저 저장과 같은 형식).
|
||||
|
||||
**완료 조건**:
|
||||
- 메인 패널을 줄이면 셋이 같은 비율로 함께 줄고, 최소 걸린 영역부터 멈춘다.
|
||||
- 메인 패널을 늘리면 셋이 같은 비율로 함께 커진다.
|
||||
- 손 뗀 뒤 높이가 유지되고(튐·복귀 없음), 클릭 등 redraw에도 흔들리지 않는다.
|
||||
- 서브패널 개별 드래그·밀어올림은 이전과 동일하게 작동한다.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [x] 1. MassHaul·TableOverlay에 `commitHeight(px)` 추가(변수+세션 기록) + MassHaul `syncHandle()` 신설(드래그 중 손잡이 추종)
|
||||
- [x] 2. Panel: 메인 드래그 시작 스냅샷 `mainDragRef`(pointerdown에서 3영역 실측)
|
||||
- [x] 3. Panel: 캐스케이드에 메인 드래그 비례 배분 분기(최소 고정→재배분 반복, 서브패널 상한 유지)
|
||||
- [x] 4. Panel: clearDragFlags에서 commitHeight 확정 후 전체 재구성 1회
|
||||
- [x] 5. `tsc --noEmit` 통과 + `prettier` 변경 없음
|
||||
- [x] 6. 실제 앱 구동 테스트 통과 — 확대 ×1.41 동일 비율(100/198/191→141/279/269), 축소 시 최소 고정 순서대로 정지(100/160/140), 손 뗀 뒤·redraw 후 고정, 서브패널 개별 드래그·밀어올림 보존, 콘솔 에러 0
|
||||
- [x] 7. 소스 대조 검증 — 코드 검증자(QA/Verifier)에 의한 정밀 검증 완료
|
||||
@@ -0,0 +1,59 @@
|
||||
# 완료 작업 계획서 Archive (2026-08-08)
|
||||
|
||||
## 1. B05 구조물 배치 컨테이너 외곽선 누락 (2026-08-08, 구현 완료 / 검증 완료)
|
||||
|
||||
커밋: `2121a5e` — `fix(ui): B05 구조물 배치 컨테이너 외곽선 누락 — 공통 클래스 미부착`
|
||||
|
||||
배경: 2026-08-08 사용자 보고. B05 좌측 사이드 패널에서 "구조물 배치" 컨테이너만 외곽 라인이
|
||||
없었다. 나머지 컨테이너는 공통 클래스 `ui-sidebar-section`(`ui_template/ui_template_overlay.css:248`)이
|
||||
테두리 두께·색·테마 대응을 전담하는데, 이 섹션만 클래스를 붙이지 않았다. 같은 페이지의 다른
|
||||
섹션은 공용 헬퍼(`B05_wf2_Route_UI_Panel.ts:82`)로 만들어져 이미 붙어 있었고, 구조물 배치만
|
||||
별도 함수에서 만들면서 누락됐다.
|
||||
|
||||
대상 파일:
|
||||
- `B05_wf2_Route/B05_wf2_Route_UI_IrregularStations.ts`
|
||||
|
||||
구현 체크리스트:
|
||||
- [X] `createIrregularStationsSection`의 root className에 `ui-sidebar-section` 추가
|
||||
(`B05_wf2_Route_UI_IrregularStations.ts:158-160`).
|
||||
- [X] CSS 무수정 확인 — 테두리는 공통 클래스가 전담하고, 라운드·패딩·배경 등 형태는
|
||||
기존 `b05-route__panel-section` 그대로 둔다(2026-08-06 `4cb1f30` 일원화 방침 유지).
|
||||
- [X] B05 사이드 섹션 전수 확인 — 클래스 누락 남은 곳 없음(생성 지점 2곳 모두 부착).
|
||||
- [X] `npm run typecheck` 통과, `prettier` 무변경 확인.
|
||||
|
||||
---
|
||||
|
||||
## 2. B06 횡단 카드 줌 대상 축소 · 표시 반폭 기준값 정정 (2026-08-08, 구현 완료 / 검증 완료)
|
||||
|
||||
커밋: `2abca64` — `feat(B06): 횡단 카드 확대 대상을 플롯 도형으로 한정 + 표시 반폭 기준값 정정`
|
||||
|
||||
배경: 2026-08-08 사용자 지시 2건.
|
||||
1. 횡단도 확대·축소가 축·눈금·축 이름까지 함께 키워, 확대할수록 도면이 아니라 글자가 커졌다.
|
||||
확대 대상은 플롯 안 도형(지반선·설계선·면적 밴드·중심 십자선)이어야 한다.
|
||||
2. 사이드 패널 "횡단 반폭" 입력칸이 백엔드 기본값(20m)이 아니라 보유 샘플의 최대 offset을
|
||||
계산해 보여 줬다. 기준값은 백엔드 기본값이어야 하고, 보유 샘플 폭을 넘는 값을 넣었을 때만
|
||||
재생성한다.
|
||||
|
||||
대상 파일:
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Cross_View.ts`
|
||||
- `B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Page.ts`
|
||||
|
||||
구현 체크리스트:
|
||||
- [X] `attachZoomPan`을 viewBox 조작에서 **도형 레이어 transform**(translate+scale) 조작으로 교체.
|
||||
배율 1~8배 유지, 확대분이 플롯 영역을 항상 덮도록 이동량 clamp(원배율에서는 이동량 0).
|
||||
- [X] 팬 방향 부호 정정 — viewBox를 밀던 때와 반대로, 도형이 커서를 따라간다.
|
||||
- [X] 플롯 clip 그룹을 `section.design` 유무와 무관하게 항상 만들고, 그 **안쪽**에 움직이는
|
||||
`plotLayer`를 둔다(clip을 transform 붙은 요소에 직접 걸면 자르는 창까지 확대된다).
|
||||
- [X] 지반선 폴리라인·면적 밴드·포장·설계선·암 경계·중심 십자선을 `plotLayer`로 이동.
|
||||
축선·격자·눈금 숫자·축 이름은 SVG 직속으로 남겨 제자리 고정.
|
||||
- [X] `UI_Page.ts` 진입 시 표시 반폭 결정에서 **보유 샘플 최대 offset 계산 폴백 제거**.
|
||||
확정 이력(`data.options.cross_half_width_m`)이 있을 때만 그 값으로 덮고, 없으면
|
||||
백엔드 기본값(`config_system.SECTION_CROSS_HALF_WIDTH_M` = 20m)을 그대로 둔다.
|
||||
- [X] `npm run typecheck` 통과, `prettier` 무변경 확인.
|
||||
|
||||
---
|
||||
|
||||
## 3. B05 종단 테이블 좌측 값 글자를 가로지르는 세로선 점검 (2026-08-08, 검증 및 정리 완료)
|
||||
|
||||
배경: 2026-08-08 사용자 보고 및 확인 완료.
|
||||
- [X] B05 종단 테이블 세로 구분선 원인 조사 및 사용자 확인 완료.
|
||||
@@ -0,0 +1,47 @@
|
||||
# 완료 작업 계획서 Archive (2026-08-08) — R1, R2 기능
|
||||
|
||||
## 1. R2. 프로젝트 생성 전 임시 보관함 (Temp Upload) (2026-08-08, 구현 완료 / 검증 완료)
|
||||
|
||||
**배경**: 라이다 원본은 수십 GB라 업로드에 오래 걸린다. 지금은 프로젝트를 먼저 만들어야만 올릴 수 있어, 프로젝트 정보가 확정되기 전에는 업로드를 시작할 수 없다. 사용자 계정에 묶인 임시 보관함을 만들어 미리 올려 두고, 나중에 프로젝트를 만들면 그 자료를 끌어와 쓰도록 한다.
|
||||
|
||||
**선행 완료 (2026-08-08)**: 알림 메일 통합 — 업로드 완료 메일과 지표면 분석 완료 메일 두 통을 초기 설계(B04~B06)까지 마친 시점의 통합 메일 한 통(`send_initial_analysis_complete_email`)으로 합침. 기존 `send_file_upload_complete_email`은 삭제하지 않고 이 임시 보관함 안내용으로 남겨 둠.
|
||||
|
||||
**확정 사항 (2026-08-08 사용자 협의)**:
|
||||
1. **업로드 화면 위치**: B01 대시보드. 프로젝트 등록(B02)과 비슷한 폼 UI로 구성.
|
||||
2. **청크 업로드·이어받기 재활용**: 기존 `upload_sessions`/`upload_chunks` 로직을 그대로 쓴다. 새로 만들지 않는다.
|
||||
3. **진행 표시**: 업로드 모달이 아니라 **보관함 리스트 행에 진행률**을 표시한다.
|
||||
4. **연결 지점**: B03 파일 입력 페이지의 **파일 업로드 컨테이너 내부** 버튼. 누르면 내 임시 보관함 중 **완료된 항목만** 목록으로 보여주고, 선택 → [확인] → 슬롯에 지정 표시.
|
||||
5. **이동 시점**: B03의 [업로드] 버튼을 눌렀을 때 임시 보관소 → 해당 프로젝트 영구저장소로 이동하고, 이어서 기존 자동 연산 체인(B04→B05→B06)을 탄다.
|
||||
6. **보관 기간**: 파일 업로드 **완료 시점 기준 1개월**. 주기 작업으로 자동 삭제. 기간·주기는 `config/config_system.py` 상수로 제어(변동 가능).
|
||||
7. **저장 위치**: `storage/tmp/` 하위.
|
||||
8. **컨테이너 UI 규격**: 프로젝트/사용자 관리 컨테이너와 같은 형태로 통일.
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [X] Phase 1 — 저장·DB 기반 (config 상수 3종, `010_temp_upload.sql` 작성·실 DB 적용, 청크 저장 경로 구성)
|
||||
- [X] Phase 2 — 백엔드 API (`B03_FileInput_Router_Temp.py` 신설, 700줄 제한 준수, 묶음 생성·목록·삭제·attach API 등)
|
||||
- [X] Phase 3 — 정리 작업 (`common_util_temp_cleanup.py` + main.py 주기 루프 등록)
|
||||
- [X] Phase 4 — 프론트엔드 (B01 대시보드 임시 보관함 컨테이너, B03 불러오기 버튼/선택 모달 연동)
|
||||
|
||||
---
|
||||
|
||||
## 2. R1. 폴더 구조 리팩토링 — 워크플로우 재편 (B04~B09) (2026-08-08, 구현 완료 / 검증 완료)
|
||||
|
||||
**배경**: 페이지 폴더명이 실제 기능과 어긋나 있고(B05는 종단, B06은 횡단 중심으로 재편됨), 수량 산출과 상세설계(도면)의 워크플로우 순서를 맞바꿔야 한다. 페이지 번호(BXX)만으로 정리하고 wf 번호는 전면 제거한다.
|
||||
|
||||
**이름 매핑표**:
|
||||
- B04_wf1_Surface → `B04_PreProcess` (지표면 분석)
|
||||
- B05_wf2_Route → `B05_Profile` (경로·종단)
|
||||
- B06_wf3_ProfileCross → `B06_Section` (횡단·유토곡선)
|
||||
- B07_wf4_DesignDetail → `B07_Quantity` (**신설 셸**, 구 수량 백업 zip 보관)
|
||||
- B08_wf5_Quantity → `B08_DesignDetail` (**현 B07 도면(CAD)+openwebcad 이동**)
|
||||
- B09_wf6_Estimation → `B09_Estimation` (설계도서 셸)
|
||||
|
||||
**구현 체크리스트**:
|
||||
- [X] Phase 0 — 조사 완료
|
||||
- [X] Phase 1 — 기존 프로젝트 데이터 삭제
|
||||
- [X] Phase 2 — B04_PreProcess 개명
|
||||
- [X] Phase 3 — B05_Profile·B06_Section 동시 개명
|
||||
- [X] Phase 4 — 도면 코드 B08_DesignDetail 이동 (`/b08-cad` 정적 서빙, CAD 레이어 `b08-*`, openwebcad 재빌드)
|
||||
- [X] Phase 5 — B07_Quantity 셸 신설·B09 정리·순서 반영 (`STAGE_KEYS` 7종 재정의, 수량 확정 경량 API)
|
||||
- [X] Phase 6 — 자동 체인 상태·B05/B06 버튼 개편 (stage 2·3 IN_PROGRESS 유지, B05 컨테이너 병합, 액션 행 개편)
|
||||
- [X] Phase 7 — 최종 검증·마감 (ruff/prettier, 빌드 통과, 서빙 스모크 테스트 성공)
|
||||
@@ -0,0 +1,46 @@
|
||||
# 완료 계획서 — B01 대시보드: 컨테이너 4행 표시 + 스크롤
|
||||
|
||||
- **일자**: 2026-08-08
|
||||
- **상태**: 완료 (검증 완료)
|
||||
|
||||
## 배경
|
||||
대시보드의 목록형 컨테이너는 자료가 쌓일수록 세로로 계속 길어져 페이지가 늘어난다. 4행까지만 보이고 나머지는 컨테이너 안에서 스크롤하게 바꾼다(2026-08-08 사용자 지시).
|
||||
|
||||
대상 6개 컨테이너와 "1행"의 단위:
|
||||
|
||||
| 컨테이너 | 생성 함수 | 1행 = |
|
||||
|---|---|---|
|
||||
| 프로젝트 | `projectTable()` | 표 행 |
|
||||
| 임시 보관함 | `buildTempUploadSection()` → `.b01-temp__list` | 묶음(그룹) |
|
||||
| 사용자 관리 | `userTable()` / `memberTable()` | 표 행 |
|
||||
| 가입 요청 | `joinRequestTable()` | 표 행 |
|
||||
| 회사 관리 | `companyTable()` | 표 행 |
|
||||
| 시스템 로그 | `auditLogTable()` | 표 행 |
|
||||
|
||||
## 설계 결정
|
||||
|
||||
1. **행 높이를 상수로 박지 않는다.** 프로젝트 표는 셀 안에 단계 막대가 들어가고 임시 보관함 묶음은 제목이 접혀 높이가 제각각이다. `max-height: 4 × 고정값`으로는 어긋난다. 대신 4번째 항목의 실제 아래쪽 좌표를 재서 `max-height`를 정한다 — `getBoundingClientRect()` 차이라 표·묶음 어느 쪽이든 같은 코드로 맞는다.
|
||||
2. **공용 헬퍼 1개.** `ui_template_general_blocks.ts`에 `limitVisibleRows(host, items, visible)`을 둔다. `table()`에 선택 인자 `maxRows`를 추가해 표는 자동 적용, 임시 보관함은 목록을 다시 그릴 때 직접 호출한다.
|
||||
3. **머리행 고정.** 스크롤 중 표 머리행이 위에 붙어 있어야 어느 열인지 알 수 있다 — `thead th`에 `position: sticky`.
|
||||
4. **항목이 4개 이하면 스크롤바를 만들지 않는다** — `max-height`를 지우고 원래대로 둔다.
|
||||
5. 측정 시점은 DOM 삽입 후 `requestAnimationFrame`. 창 크기 변경 시 다시 잰다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `ui_template_general_blocks.ts:42` — `limitVisibleRows()` 추가(재측정 함수를 돌려준다), `table()`에 `maxRows` 선택 인자
|
||||
- [x] `ui_template_general_layout.css:55` — `.ui-general-block__scroll-limit` 세로 스크롤, sticky 머리행(+불투명 배경)
|
||||
- [x] `B01_Dashboard_UI_Common.ts:5` — `DASHBOARD_VISIBLE_ROWS = 4` 공용 상수
|
||||
- [x] `B01_Dashboard_UI_Projects.ts` — `projectTable()`
|
||||
- [x] `B01_Dashboard_UI_Admin.ts` — `userTable()`, `auditLogTable()`
|
||||
- [x] `B01_Dashboard_UI_Company.ts` — `memberTable()`, `joinRequestTable()`, `companyTable()`
|
||||
- [x] `B01_Dashboard_UI_TempUpload.ts` — 묶음 목록 4개까지, 재렌더 후 재측정. 묶음 **내부** 파일 표는 제외(접혀 있고 별도 맥락)
|
||||
- [x] 구현 중 발견한 두 가지 처리:
|
||||
- 묶음을 펼치면 높이가 달라진다 → 접기/펼치기 핸들러에서 재측정
|
||||
- 목록을 다시 그릴 때마다 `resize` 청취자가 쌓인다 → `WeakMap`으로 이전 것을 걷어내고 다시 등록
|
||||
- [x] `prettier` + `tsc --noEmit` 통과 (exit 0)
|
||||
|
||||
## 완료 조건
|
||||
6개 컨테이너 모두 항목 5개 이상일 때 4행 높이에서 잘리고 안쪽 스크롤이 생긴다. 4개 이하면 지금과 동일. 표 머리행은 스크롤해도 보인다.
|
||||
|
||||
## 미검증 사항
|
||||
브라우저 실물 확인은 하지 않았다(현재 DB가 비어 있어 5행 이상을 만들 자료가 없다). 프로젝트를 5건 이상 등록한 뒤 육안 확인 필요.
|
||||
@@ -0,0 +1,19 @@
|
||||
# 완료 계획서 — DB 정리: 프로젝트 데이터 초기화
|
||||
|
||||
- **일자**: 2026-08-08
|
||||
- **상태**: 완료 (검증 완료)
|
||||
|
||||
## 배경
|
||||
하드 삭제 스위치 도입 후 사용자가 영구저장소의 프로젝트 폴더를 직접 삭제했다. 처음부터 다시 검증하기 위해 DB의 프로젝트 계열 데이터를 모두 비운다. 파일시스템은 사용자가 직접 정리한다.
|
||||
|
||||
## 조사 결과
|
||||
진짜 FK 고아는 0건이었다. CASCADE는 정상 동작했고, 남아 있던 것은 하드 삭제 플래그를 켜기 전에 소프트 삭제된 프로젝트 2건과 그 딸린 데이터였다.
|
||||
|
||||
## 구현 및 실행 체크리스트
|
||||
- [x] 삭제 전 상태 조사 — `projects` 2행(둘 다 소프트 삭제), `project_workflow_stages` 14, `upload_sessions` 11(전부 `project_id IS NULL` = 임시 보관함), `upload_chunks` 14, `temp_upload_batches` 3. 산출물 테이블(`input_files`·`surface_models`·`routes`·`cross_sections`·`outputs` 등)은 전부 0행
|
||||
- [x] 드라이런으로 삭제 대상 확인 후 트랜잭션 커밋
|
||||
- [x] `projects` 2행 DELETE → `project_workflow_stages` 14행 CASCADE 소멸
|
||||
- [x] 임시 보관함 잔여 정리 — `upload_chunks` 14, `upload_sessions` 11, `temp_upload_batches` 3 (`storage/tmp`가 비어 있어 실물 없는 고아 메타데이터였음)
|
||||
- [x] `system_audit_logs`에서 `resource_type='project'` 12행 삭제 (사용자 지시)
|
||||
- [x] 유지 확인 — `users` 1, `companies` 1, `sessions` 6, `trusted_devices` 16, `user_consents` 1. `system_audit_logs`에 남은 1행은 `COMPANY_CREATE`라 프로젝트와 무관
|
||||
- [x] 파일시스템 미접촉 (사용자가 직접 삭제)
|
||||
@@ -0,0 +1,87 @@
|
||||
# 완료 계획서 — B01 프로젝트 삭제: 개발용 하드 삭제 스위치
|
||||
|
||||
- **일자**: 2026-08-08
|
||||
- **상태**: 완료 (검증 완료)
|
||||
|
||||
## 배경
|
||||
대시보드 프로젝트 삭제 버튼은 현재 **소프트 삭제만** 한다 — `projects.deleted_at`에 시각을 찍고 `system_audit_logs`에 `PROJECT_DELETE` 한 줄을 넣는 것이 전부다(`B01_Dashboard_Repository.py:255`). 파일시스템과 자식 테이블은 손대지 않는다. 목록 쿼리가 `deleted_at IS NULL`로 걸러서 화면에서만 사라진다.
|
||||
|
||||
배포 환경에서는 이게 맞다 — 사용자가 올린 라이다 원본은 나중에 다른 프로젝트 개발에 재활용할 수 있는 자산이다. 그러나 지금은 개발 단계라 프로젝트를 반복 생성·삭제하는데, 삭제할 때마다 수십 GB 라이다 원본이 디스크에 영구 누적된다. 하드 삭제 잡도 없다.
|
||||
|
||||
**사용자 지시(2026-08-08)**: `config_system.py`에 true/false 스위치를 두고, 배포 시 `false`(현행 소프트 삭제 유지), 개발 중 `true`(DB + 영구저장소 프로젝트 폴더 자체 삭제)로 운용한다.
|
||||
|
||||
## 설계 결정
|
||||
|
||||
**1. 설정 변수** — `config/config_system.py` 맨 위 `0. 위험 스위치` 전용 절에 배치. `load_dotenv()` 직후, 기존 1절(FastAPI 서버) 앞이다.
|
||||
```python
|
||||
PROJECT_DELETE_HARD_ENABLED = os.getenv("PROJECT_DELETE_HARD_ENABLED", "False").lower() == "true"
|
||||
```
|
||||
기본값 `False` — 환경변수를 안 넣은 배포 환경이 자동으로 안전한 쪽에 선다. 개발 PC는 `.env`에 `PROJECT_DELETE_HARD_ENABLED=True`.
|
||||
|
||||
**2. 삭제 대상** — `storage/{회사}/{사용자}/{project_id}/` 폴더 자체를 `shutil.rmtree`로 제거(`B03_FileInput` 원본 포함). 기존 `purge_output_folders()`는 B04~B09만 비우고 B03을 남기므로 재사용하지 않는다.
|
||||
|
||||
**3. DB는 FK CASCADE에 맡긴다** — `db_management/001_create_schema.sql:505~586` 확인 결과 `projects.id`를 참조하는 자식 테이블 전부가 `ON DELETE CASCADE`다: `project_versions`, `input_files`, `processed_point_cloud`, `surface_models`, `routes`, `longitudinal_sections`, `cross_sections`, `structures`, `quantity_items`, `outputs`, `change_logs`, `upload_sessions`(`010_temp_upload.sql:68`), `project_workflow_stages`(`006_workflow_state.sql:22`). `route_points`·`route_statistics`는 `routes` 경유로 연쇄(`001:538,543`). 따라서 `DELETE FROM projects WHERE id = %s` 한 줄이면 충분하다 — 테이블 목록을 코드에 다시 나열하지 않는다(스키마 변경 시 어긋남 방지).
|
||||
|
||||
**4. 남기는 것 2가지**
|
||||
- `system_audit_logs` — 감사 기록은 삭제 후에도 남겨야 한다. `resource_id`가 현재 `NULL`로만 들어가고 FK도 없어 고아 참조 문제 없음. `action`은 `PROJECT_HARD_DELETE`로 구분해 기록.
|
||||
- `temp_batches.linked_project_id` — FK 없는 이력 보존용 컬럼. 설계 의도대로 그대로 둔다.
|
||||
|
||||
**5. 실행 순서 — DB 먼저, 파일 나중**
|
||||
1. `storage_path` 조회 (삭제 전에 읽어 둬야 함)
|
||||
2. `DELETE FROM projects` + 감사 로그 INSERT → **커밋**
|
||||
3. 커밋 성공 후 `rmtree`
|
||||
DB가 먼저 지워지면 rmtree 실패 시 고아 폴더(디스크 낭비)만 남고 시스템은 정합하다. 반대 순서면 파일 없는 DB 행이 남아 화면이 깨진다. rmtree 실패는 `logger.error`로 경로를 남겨 수동 정리 가능하게 한다 — 삭제 API 자체는 성공으로 응답한다.
|
||||
|
||||
**6. 경로 안전장치 (필수)** — `resolve_stored_project_path()`는 내부에서 `os.makedirs` + `ensure_project_storage_layout()`을 호출해 폴더를 만든다. 삭제 경로에 그대로 쓰면 지우기 직전에 폴더를 되살린다. 따라서 생성하지 않는 별도 해석 함수를 추가한다.
|
||||
|
||||
`common_util_storage.py`에 신규 함수:
|
||||
```python
|
||||
resolve_project_root_for_delete(relative_path: str) -> str
|
||||
```
|
||||
검증 조건 (하나라도 어기면 `ValueError`, 삭제 중단):
|
||||
- 상대 경로이고 `..` 미포함, `storage/`로 시작 (기존 `resolve_stored_project_path` 앞부분과 동일)
|
||||
- 정규화 후 `STORAGE_BASE_DIR` 하위이고 루트 자신이 아님
|
||||
- 세그먼트가 정확히 `storage/{회사}/{사용자}/{project_id}` 4개 — 상위 폴더 통삭제 방지
|
||||
- 마지막 세그먼트가 URL의 `project_id`와 문자열 일치 — DB `storage_path`가 오염돼도 남의 폴더를 못 지운다
|
||||
- `os.makedirs` 호출하지 않음
|
||||
|
||||
**7. 빈 상위 폴더** — 프로젝트 삭제 후 비게 된 `{사용자}/`, `{회사}/` 폴더는 정리하지 않는다. 동시 생성과 경합할 수 있고 실익이 없다.
|
||||
|
||||
**8. 권한 불변** — `SYSTEM_ADMIN`만 삭제 가능한 현행 유지. 주석 처리된 ADMIN 분기도 그대로 둔다.
|
||||
|
||||
**9. 화면 경고** — 하드 삭제 모드일 때 삭제 확인 모달 문구를 다르게 보여준다. 개발 중 실수로 원본을 날리는 사고를 막는 최소 장치다. `GET /api/dashboard/me` 응답에 `project_delete_hard: bool`을 실어 프론트가 판단한다.
|
||||
|
||||
## 대상 파일
|
||||
|
||||
| 파일 | 변경 |
|
||||
|---|---|
|
||||
| `config/config_system.py` | `PROJECT_DELETE_HARD_ENABLED` 추가 (4절, `AUTO_DESIGN_CHAIN_ENABLED` 아래) |
|
||||
| `common_util/common_util_storage.py` | `resolve_project_root_for_delete()` 신규 |
|
||||
| `B01_Dashboard/B01_Dashboard_Repository.py` | `hard_delete_project()` 신규. 기존 `soft_delete_project()`는 그대로 |
|
||||
| `B01_Dashboard/B01_Dashboard_Router.py` | `dashboard_delete_project()`에서 플래그 분기. `/me` 응답에 플래그 노출 |
|
||||
| `B01_Dashboard/B01_Dashboard_UI_Modals.ts` | `openDeleteProjectModal()` 경고 문구 분기 |
|
||||
| `ui_template/` 다국어 | `B01_Dashboard_Confirm_DeleteProject_Hard` 키 추가 |
|
||||
|
||||
`B01_Dashboard_Repository.py`는 현재 790줄로 이미 700줄 제한 초과 상태(기존 기술부채). 이번에 함수를 더하면 악화되므로, 삭제 로직은 저장소 파일이 아니라 **`common_util/common_util_project_delete.py` 신규 파일**에 둔다. 저장소 파일에는 손대지 않는다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `config_system.py` 최상단 `0. 위험 스위치` 절에 `PROJECT_DELETE_HARD_ENABLED` 추가 (기본 `False`, 주석에 배포/개발 운용 방침 명시)
|
||||
- [x] `common_util_storage.py:77`에 `resolve_project_root_for_delete()` 추가 — 4세그먼트 검증 + `project_id` 일치 + 폴더 생성 없음
|
||||
- [x] `common_util/common_util_project_delete.py` 신규 — `hard_delete_project(project_id, actor_id)` : storage_path 조회 → 경로 검증 → `DELETE FROM projects` + `PROJECT_HARD_DELETE` 감사 로그 → 커밋 → `rmtree`(실패 시 error 로그만)
|
||||
- [x] `B01_Dashboard_Router.py:237` 분기 — 플래그 True면 `hard_delete_project()`, False면 기존 `soft_delete_project()`
|
||||
- [x] `/api/dashboard/me` 응답 `user`에 `project_delete_hard` 필드 추가
|
||||
- [x] `B01_Dashboard_UI_Modals.ts:121` — 하드 삭제 모드일 때 원본까지 지운다는 경고 문구로 교체. `openDeleteProjectModal(user, project)`로 시그니처 변경, 호출부 `B01_Dashboard_UI_Projects.ts:39` 수정
|
||||
- [x] 다국어 키 `B01_Dashboard_Confirm_DeleteProject_Hard` 추가 (`ui_template_locale_b1.ts:174`)
|
||||
- [x] `ruff format` + `ruff check` + `prettier` + `tsc --noEmit` 전부 통과
|
||||
- [x] `.env`에 `PROJECT_DELETE_HARD_ENABLED=True` 기재 — `.env.example`은 이 저장소에 없다
|
||||
|
||||
## 완료 조건
|
||||
- 플래그 `False`: 삭제 동작이 지금과 100% 동일 (deleted_at UPDATE + 감사 로그)
|
||||
- 플래그 `True`: `projects` 행과 자식 테이블 전부 소멸, `storage/.../{project_id}/` 폴더 부재, `system_audit_logs`에 `PROJECT_HARD_DELETE` 잔존
|
||||
- 조작된 `storage_path`(`..` 포함, 3세그먼트, project_id 불일치)로는 `ValueError` 발생해 아무것도 지워지지 않음
|
||||
|
||||
## 검증자 인계 사항
|
||||
경로 가드 8케이스 수동 확인 완료 (정상 허용 1 / 차단 7: 3세그먼트, 저장소 루트, `..` 탈출, ID 불일치, 절대경로, 5세그먼트, 역슬래시 탈출). 실제 프로젝트 생성→삭제 E2E는 미수행.
|
||||
|
||||
**미결 리스크**: 이 저장소는 `.env`를 git으로 추적한다(`.gitignore:12`의 `.env` 줄이 주석 처리됨). 따라서 `PROJECT_DELETE_HARD_ENABLED=True`가 커밋되면 다른 PC와 배포 환경까지 하드 삭제가 켜진 채 전파된다. 배포 전에 `.env`를 추적 해제(`git rm --cached .env` + `.gitignore` 주석 해제)하거나, 최소한 배포 브랜치에서 `False`로 되돌려야 한다.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user