260705_2
This commit is contained in:
+132
-28
@@ -1,44 +1,148 @@
|
||||
# 에이전트 행동 지침 및 라우팅 가이드 (agent.md)
|
||||
# 프로젝트 행동지침 및 기술 스택 명세 (agent.md)
|
||||
|
||||
## 1. 개발 환경 및 기술 스택 기준 (Technical Stack Baseline)
|
||||
에이전트는 코드를 작성, 수정 또는 검증할 때 환경 기준과 호환되도록 구현
|
||||
## 프로젝트 & DB 정보
|
||||
|
||||
### A. 백엔드 및 연산 엔진 (Python)
|
||||
### 📌 프로그램명
|
||||
**Aislo (아이슬로)**
|
||||
- **의미**: AI (인공지능) + Slotti (핀란드어: 임도/산림 도로)
|
||||
- **콘셉트**: 산림의 미래를 열어가는 인공지능 경로 설계 솔루션
|
||||
|
||||
---
|
||||
|
||||
## 1. 기술 환경 및 기본 스택 (Technical Stack Baseline)
|
||||
프로젝트의 코드를 작성, 테스트 또는 검증할 환경 명확화 및 호환 기준 제시.
|
||||
|
||||
### A. 백엔드 프레임워크 (Python)
|
||||
* **Runtime:** Python v3.12 or v3.13.7
|
||||
* **Framework:** FastAPI / Pydantic
|
||||
* **Geometry/GIS Engine:** Trimesh, Whitebox, Geopandas, Shapely, Rasterio, Laspy
|
||||
|
||||
### B. 데이터베이스 (PostgreSQL)
|
||||
* **DBMS:** PostgreSQL v17
|
||||
* **Spatial Extension:** PostGIS v3.4 (공간 쿼리 및 연산 활용)
|
||||
* **DB Driver:** 비동기 연동을 위한 `asyncpg` 기준 구동
|
||||
### 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. 프론트엔드 및 인터페이스 (TypeScript & WebCAD)
|
||||
### C. 프론트엔드 & WebCAD (TypeScript & WebGL)
|
||||
* **Language/Runtime:** TypeScript / Node.js
|
||||
* **인터페이스:** HTML5 Canvas 및 WebGL 기반의 WebCAD 시스템 연동 표준 준수
|
||||
* **Rendering:** HTML5 Canvas 및 WebGL 기반 WebCAD 시스템 구현
|
||||
|
||||
### D. 데이터 및 파일 인코딩 표준
|
||||
* **인코딩:** 텍스트 파일은 UTF-8 표준을 강제 적용
|
||||
### D. 인코딩 및 다국어 표준
|
||||
* **인코딩:** 텍스트 파일은 UTF-8 표준으로 통일
|
||||
|
||||
---
|
||||
|
||||
## 2. 컨텍스트 라우팅 규칙 (Context Routing)
|
||||
명령 수신 시 답변 작성 및 코드 수정 전 다음 단계에 따라 정의서(MD) 필수 로드.
|
||||
## 2. 문맥 라우팅 및 문서 구조 (Context Routing)
|
||||
코드 작성 및 기술 검증 시 문서 우선순위에 따른 계층적 레퍼런스 필수 읽기.
|
||||
|
||||
* **1단계 (필수):** `.agent/structure.md` 즉시 로드 및 현재 디렉토리 구조 분석.
|
||||
* **2단계 (조건 분기):** 프롬프트 성격에 따라 필요한 파일 선별 로드.
|
||||
* **조건 A (UI, 제어, 컴포넌트, WebCAD):** `.agent/frontend.md` 필수 추가 로드.
|
||||
* **조건 B (연산 수식, 설계 기준, DB, PostGIS):** `.agent/backend.md` 필수 추가 로드.
|
||||
* **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` 동시 업데이트.
|
||||
* **영역 격리:** 지정된 작업 영역 외 디렉토리 파일 접근·수정 시 유저 사전 승인 필수.
|
||||
* **정합성 검증:** 이전 맥락 단절을 고려하여 현재 소스 코드와 분할 정의서(`frontend.md`, `backend.md`) 간 정합성 최우선 검토.
|
||||
* **정보 요구 필수:** 연관 파일의 실제 내부 코드가 프롬프트에 제공되지 않은 경우, 임의 추측 코딩을 절대 금지하며 유저에게 파일 내용 제시를 먼저 요구할 것.
|
||||
* **스타일 통합 (자율 포매팅):** 에이전트는 코드 작성을 완료한 후, 반드시 프로젝트 터미널에서 다음 명령어를 직접 실행하여 서식을 정렬한 뒤 최종 저장해야 한다. (유저에게 서식 교정 작업을 전가하지 말 것)
|
||||
* *Python 파일 수정 시:* `ruff format [파일명]` 및 `ruff check --fix [파일명]` 실행
|
||||
* *TypeScript/CSS 파일 수정 시:* `npx prettier --write [파일명]` 실행
|
||||
* **의존성 준수:** 외부 라이브러리 활용 시 프로젝트에 명시된 호환 버전을 반드시 준수하며, 존재하지 않거나 단종된 함수를 유추하여 작성하는 것을 금지함.
|
||||
## 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 [파일명]` 실행
|
||||
* **외부 라이브러리 활용:** 외부 라이브러리 활용 시 프로젝트에 맞춰 호환 라이브러리 선정하고, 불필요한 함수는 작성하지 않도록 최소화.
|
||||
|
||||
---
|
||||
|
||||
## 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 파싱 후 기하 연산
|
||||
|
||||
---
|
||||
|
||||
## 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/structure.md` 읽기
|
||||
2. **프론트 구현:** `.agent/frontend.md` 읽기
|
||||
3. **백엔드 구현:** `.agent/backend.md` 읽기
|
||||
4. **마이그레이션:** `.agent/migration_plan.md` 읽기
|
||||
5. **DB 스키마:** `.agent/db_schema_simple.md` 읽기
|
||||
6. **프로젝트 정보:** `.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) |
|
||||
| **테이블 수** | 18개 |
|
||||
| **폴더 구조** | 워크플로우 기반 (B03~B09) |
|
||||
|
||||
+22
-10
@@ -8,9 +8,12 @@
|
||||
|
||||
---
|
||||
|
||||
## 2. DB 및 PostGIS (Database)
|
||||
* **비동기 통신:** `async/await` 및 `asyncpg` 드라이버 사용 강제.
|
||||
* **공간 연산:** ORM 사용 금지. `ST_Volume`, `ST_Distance` 등 PostGIS 기반 **Raw SQL** 작성 원칙.
|
||||
## 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 형식으로 저장 후 애플리케이션에서 처리.
|
||||
|
||||
---
|
||||
|
||||
@@ -18,6 +21,14 @@
|
||||
* **하드코딩 금지:** 제어 변수/파라미터 하드코딩 금지. `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 파싱 후 기하 연산 처리
|
||||
|
||||
---
|
||||
|
||||
@@ -33,11 +44,12 @@
|
||||
### 5.1 다중 브라우저 동시 작업 원칙
|
||||
* **브라우저 독립성:** 같은 계정/프로젝트를 여러 브라우저에서 동시 접근 가능. 각 브라우저는 서버 데이터 기반 독립 작동.
|
||||
* **클라이언트 상태 격리:** `localStorage`는 브라우저별 격리(도메인 단위 공유). 공유 데이터는 항상 서버 영구저장소 우선.
|
||||
* **영구저장소 설계:** 단계별 계산 결과를 `storage/[고객사]/[사용자]/[프로젝트ID]/result_[단계].json` 형태로 물리 저장.
|
||||
* `result_scan.json` — 포인트 클라우드 필터링 결과
|
||||
* `result_surface.json` — 지표면 모델 선택
|
||||
* `result_route.json` — 경로 설계 결과
|
||||
* `result_section.json` — 종횡단 생성 결과
|
||||
* **영구저장소 설계:** 계산 결과는 `.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 구조:**
|
||||
@@ -56,7 +68,7 @@
|
||||
```python
|
||||
def _patch_workflow_stale(project_id: str, stale_from: str | None) -> None:
|
||||
"""workflow.json의 stale_from 필드만 원자적 업데이트"""
|
||||
wf_path = storage_path / project_id / "workflow.json"
|
||||
wf_path = get_project_workflow_path(project_id)
|
||||
# 기존 상태 유지하며 stale_from만 변경
|
||||
```
|
||||
|
||||
@@ -88,4 +100,4 @@
|
||||
"changed_by": "other_browser"
|
||||
}
|
||||
```
|
||||
* **마이그레이션 시기:** 사용자 수 증가 또는 실시간성 요구 시 적용
|
||||
* **마이그레이션 시기:** 사용자 수 증가 또는 실시간성 요구 시 적용
|
||||
|
||||
@@ -1,682 +0,0 @@
|
||||
# DB 구조 설계 제안서
|
||||
|
||||
**작성일:** 2026-07-05
|
||||
**대상:** 임도 설계 및 견적 자동화 웹앱 (프로젝트 기반 멀티테넌트)
|
||||
**기술 스택:** PostgreSQL v17 + PostGIS v3.4 / asyncpg / FastAPI
|
||||
|
||||
---
|
||||
|
||||
## 1. 설계 원칙
|
||||
|
||||
### 1.1 멀티테넌트 아키텍처
|
||||
- **프로젝트 단위 데이터 격리:** 각 사용자의 프로젝트는 별도의 논리적 네임스페이스로 관리
|
||||
- **하이브리드 저장소:**
|
||||
- **DB:** 사용자, 프로젝트 메타데이터, 설계 결과(경로, 단면, 수량) 저장
|
||||
- **파일시스템:** 원본 입력파일(LAS/TIF/DXF), 중간 산출물(mesh/타일), 최종 산출물(DXF/Excel/PDF) 저장
|
||||
- **스토리지 경로:** `storage/{회사명}/{사용자명}/{프로젝트ID}/` 하위로 자동 생성
|
||||
|
||||
### 1.2 데이터 계층 분리
|
||||
```
|
||||
[입력] LAS, TFW, PRJ, TIF → [분석] DEM/Mesh/필터 → [설계] 경로, 단면 → [산출] DXF, Excel
|
||||
(storage) (DB + storage) (DB + storage) (storage)
|
||||
```
|
||||
|
||||
### 1.3 공간 데이터 전용 설계
|
||||
- PostGIS 기하학적 타입 활용 (geometry, geography)
|
||||
- 좌표계 일관성: EPSG 코드 저장 후 필요 시 변환
|
||||
- 벡터 데이터(경로, 경계선) ↔ 래스터 샘플링 간 추적 가능하도록 설계
|
||||
|
||||
---
|
||||
|
||||
## 2. 핵심 Entity 및 관계
|
||||
|
||||
### 2.1 사용자 및 인증 (users, user_sessions)
|
||||
```
|
||||
users
|
||||
├── id (PK)
|
||||
├── email (UNIQUE)
|
||||
├── password_hash
|
||||
├── name
|
||||
├── company (FK → companies)
|
||||
├── role (admin, user, guest)
|
||||
├── created_at, updated_at
|
||||
|
||||
companies
|
||||
├── id (PK)
|
||||
├── name (UNIQUE)
|
||||
├── created_by (FK → users)
|
||||
├── members [] (users, M:N via user_company_roles)
|
||||
├── created_at, updated_at
|
||||
|
||||
user_sessions (토큰 저장 — optional, Redis 권장)
|
||||
├── id (PK)
|
||||
├── user_id (FK → users)
|
||||
├── token
|
||||
├── expires_at
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 회사 단위로 프로젝트, 사용자를 그룹화
|
||||
- 회사별 권한 분리 (협업 확장성)
|
||||
- 향후 팀 공유 프로젝트 기능 추가 가능
|
||||
|
||||
### 2.2 프로젝트 메타데이터 (projects, project_versions)
|
||||
```
|
||||
projects
|
||||
├── id (PK, UUID)
|
||||
├── user_id (FK → users) — 소유자
|
||||
├── company_id (FK → companies)
|
||||
├── name
|
||||
├── region (지역명: 예 "울진군 금강송면")
|
||||
├── road_type (간선임도, 지선임도, 산불진화임도, 계류보전)
|
||||
├── project_year (사업 연도, INT)
|
||||
├── estimated_length_m (추정 연장)
|
||||
├── memo
|
||||
├── status (NEW, ANALYZING, WF1_COMPLETE, WF2_COMPLETE, ... , CONFIRMED, DONE)
|
||||
├── crs_epsg (좌표계, INT — 예: 5178)
|
||||
├── bbox (GEOMETRY(Polygon)) — 전체 프로젝트 범위
|
||||
├── created_at, updated_at
|
||||
├── deleted_at (soft delete)
|
||||
|
||||
project_versions (버전 관리 — 선택)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── version_num
|
||||
├── snapshot_at (스냅샷 시점)
|
||||
├── status (저장된 상태)
|
||||
└── data (JSONB — 설계 데이터 스냅샷)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 프로젝트 생명주기 추적 (NEW → WF1 → WF2 → ... → CONFIRMED)
|
||||
- 좌표계 메타데이터 저장 (변환 오류 방지)
|
||||
- 버전 관리는 (선택) — 회원가입 초기엔 미필수, 협업 필요 시 추가
|
||||
|
||||
### 2.3 입력 파일 관리 (input_files)
|
||||
```
|
||||
input_files
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── file_type (las, tif, tfw, prj, dxf, dwg, other)
|
||||
├── original_filename
|
||||
├── stored_path (storage/{...}/raw/{file_type}/{filename})
|
||||
├── file_size_mb
|
||||
├── upload_by (FK → users)
|
||||
├── upload_at
|
||||
├── crs_epsg (파일이 가진 좌표계)
|
||||
├── metadata (JSONB — 해상도, 데이터 범위, 포인트 수 등)
|
||||
└── status (UPLOADED, PROCESSED, ARCHIVED)
|
||||
|
||||
point_cloud_metadata
|
||||
├── id (PK)
|
||||
├── input_file_id (FK → input_files, LAS만)
|
||||
├── point_count (INT)
|
||||
├── min_z, max_z, mean_z (높이)
|
||||
├── x_min, x_max, y_min, y_max (공간 범위)
|
||||
├── densitiy_per_sqm (밀도)
|
||||
└── classification_summary (JSONB — {ground: N, vegetation: N, building: N, ...})
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 입력 파일의 생명주기 추적
|
||||
- 포인트클라우드 메타데이터로 전처리 여부 판단
|
||||
- storage 폴더와 DB 간 일관성 확보
|
||||
|
||||
### 2.4 지표면 모델 및 분석 결과 (surface_models, terrain_layers)
|
||||
```
|
||||
surface_models (WF1 출력)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── model_type (dem_grid, tin, mesh_triangulated, contour_lines)
|
||||
├── source_file_id (FK → input_files, LAS)
|
||||
├── status (PROCESSING, COMPLETE, FAILED)
|
||||
├── crs_epsg
|
||||
├── resolution_m (래스터 경우, NULL if 벡터)
|
||||
├── stored_path (storage/{...}/processed/surface/)
|
||||
├── bounds (GEOMETRY(Polygon)) — 모델 범위
|
||||
├── metadata (JSONB — 필터링 파라미터, 생성 시각 등)
|
||||
├── created_at, completed_at
|
||||
|
||||
terrain_layers
|
||||
├── id (PK)
|
||||
├── surface_model_id (FK → surface_models)
|
||||
├── layer_name (지표, 제1층, 제2층 등 15종)
|
||||
├── geometry_type (POINTCLOUD, GRID, MESH, CONTOUR)
|
||||
├── stored_path (GeoJSON / GeoTIFF / LAS 등)
|
||||
└── statistics (JSONB — min_z, max_z, mean_slope, etc.)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- WF1 분석 결과(DEM, TIN, mesh 등)의 출처와 메타데이터 추적
|
||||
- 층(layer)별로 렌더링 최적화 가능
|
||||
- 재분석 시 기존 결과와 비교 가능
|
||||
|
||||
### 2.5 경로 설계 (routes, route_points, route_statistics)
|
||||
```
|
||||
routes (WF2 출력)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── status (DRAFT, CONFIRMED, ARCHIVED)
|
||||
├── start_point (GEOMETRY(Point)) — 지형 위 실제 좌표
|
||||
├── end_point (GEOMETRY(Point))
|
||||
├── start_chainage_m, end_chainage_m (측점)
|
||||
├── total_length_m
|
||||
├── grade_percent[] (JSONB array — 각 구간 종단 경사도)
|
||||
├── constraints (JSONB — max_grade, min_radius, avoidance_zones)
|
||||
├── algorithm_params (JSONB — 비용함수 가중치, 계산 시간 등)
|
||||
├── computed_at
|
||||
└── geometry (GEOMETRY(LineString, Z) — 3D 경로)
|
||||
|
||||
route_points (경로 포인트 샘플, 웹 렌더링용)
|
||||
├── id (PK)
|
||||
├── route_id (FK → routes)
|
||||
├── chainage_m (측점)
|
||||
├── geometry (GEOMETRY(Point, Z))
|
||||
├── elevation_m, slope_percent
|
||||
└── sequence_num
|
||||
|
||||
route_statistics
|
||||
├── id (PK)
|
||||
├── route_id (FK → routes)
|
||||
├── min_slope, max_slope, mean_slope
|
||||
├── cut_volume_m3, fill_volume_m3
|
||||
├── tree_cutting_volume (목재 추정)
|
||||
└── cost_score (알고리즘 점수)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 경로의 기하학적 정보(3D LineString) + 측점 기반 추적
|
||||
- 재계산 시 이전 결과와 버전 비교 가능
|
||||
- 사용자가 수정한 경로도 저장 가능 (draft ↔ confirmed)
|
||||
|
||||
### 2.6 종단면 및 횡단면 (longitudinal_sections, cross_sections)
|
||||
```
|
||||
longitudinal_sections
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── route_id (FK → routes)
|
||||
├── computed_at
|
||||
├── geometry (GEOMETRY(LineString, Z)) — 경로 따라가며 샘플링한 표고
|
||||
├── data (JSONB)
|
||||
│ ├── chainages [0, 20, 40, ...]
|
||||
│ ├── elevations [100.5, 102.3, ...]
|
||||
│ ├── grades [2.5, 1.8, ...]
|
||||
│ └── design_elevations [100.0, 102.0, ...] (설계 기준)
|
||||
└── stored_path (storage/{...}/sections/longitudinal.json)
|
||||
|
||||
cross_sections
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── route_id (FK → routes)
|
||||
├── chainage_m (측점)
|
||||
├── sequence_num (1st, 2nd, ...)
|
||||
├── geometry (GEOMETRY(LineString, Z)) — 지형 횡단면
|
||||
├── geometry_design (GEOMETRY(LineString, Z)) — 설계 횡단면
|
||||
├── data (JSONB)
|
||||
│ ├── left_slope, right_slope
|
||||
│ ├── width_m
|
||||
│ ├── cut_volume_m3, fill_volume_m3
|
||||
│ ├── structures [] (낙석방지책, 돌붙임 등)
|
||||
│ └── notes
|
||||
└── stored_path (storage/{...}/sections/cross_{chainage}.json)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 종단면: 경로 따라 세로 방향 표고 추적
|
||||
- 횡단면: 20m 간격 가로 방향 단면 + 설계 기준
|
||||
- JSONB로 유연한 추가 메타데이터 저장
|
||||
|
||||
### 2.7 설계 및 구조물 (design_details, structures, quantity_items)
|
||||
```
|
||||
structures (WF4 구조물 라이브러리)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── cross_section_id (FK → cross_sections)
|
||||
├── structure_type (낙석방지책, 돌붙임, 계간수로, 낙차공, etc.)
|
||||
├── chainage_m, location (LEFT, CENTER, RIGHT)
|
||||
├── length_m, width_m, height_m (기본 치수)
|
||||
├── material (강재, 콘크리트, 목재, 돌 등)
|
||||
├── quantity (개수)
|
||||
├── unit_price (단가)
|
||||
├── geometry (GEOMETRY(Polygon)) — 3D 구조물 배치
|
||||
├── design_notes (JSONB)
|
||||
└── last_modified_by (FK → users)
|
||||
|
||||
quantity_items (WF5 수량 산출)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── category (토공, 구조물, 포장, 배수, 녹화, 안전시설)
|
||||
├── item_name (예: "절토 일반", "낙석방지책 설치" 등)
|
||||
├── unit (m3, 개, m, m2)
|
||||
├── quantity_design (설계 수량)
|
||||
├── quantity_actual (실제 수량, 사용자 수정 가능)
|
||||
├── unit_price (단가)
|
||||
├── total_price (수량 × 단가)
|
||||
├── standard_reference (설계 기준 참고 자료)
|
||||
├── computed_at
|
||||
└── data (JSONB — 계산 과정 메모)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 구조물은 횡단면별로 배치
|
||||
- 수량 항목은 자동 계산 + 사용자 수정 가능
|
||||
- 단가와 기준 추적으로 감시/감독 기록 남김
|
||||
|
||||
### 2.8 산출물 관리 (outputs, output_files)
|
||||
```
|
||||
outputs
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── output_type (estimation_excel, drawing_dxf, report_pdf, all_bundle)
|
||||
├── status (GENERATING, COMPLETE, FAILED)
|
||||
├── generated_by (FK → users)
|
||||
├── generated_at
|
||||
├── version (프로젝트 확정 단계에서 자동 증가)
|
||||
├── metadata (JSONB)
|
||||
│ ├── template_used
|
||||
│ ├── company_name, project_name
|
||||
│ ├── total_cost
|
||||
│ └── generation_time_sec
|
||||
└── created_at
|
||||
|
||||
output_files
|
||||
├── id (PK)
|
||||
├── output_id (FK → outputs)
|
||||
├── file_type (xlsx, pdf, dxf, dwg, json, zip)
|
||||
├── original_filename
|
||||
├── stored_path (storage/{...}/outputs/{output_type}/{filename})
|
||||
├── file_size_mb
|
||||
├── created_at
|
||||
└── download_count (통계)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 산출물 생성 이력 추적
|
||||
- 같은 프로젝트 다중 버전 관리 (재산출 가능)
|
||||
- 다운로드 통계로 사용 현황 파악
|
||||
|
||||
### 2.9 변경 이력 및 감시 (audit_logs, change_logs)
|
||||
```
|
||||
audit_logs (접근 제어 감시)
|
||||
├── id (PK)
|
||||
├── user_id (FK → users)
|
||||
├── action (CREATE, READ, UPDATE, DELETE, EXPORT)
|
||||
├── entity_type (projects, routes, structures, etc.)
|
||||
├── entity_id
|
||||
├── timestamp
|
||||
├── ip_address
|
||||
└── details (JSONB)
|
||||
|
||||
change_logs (설계 변경 기록)
|
||||
├── id (PK)
|
||||
├── project_id (FK → projects)
|
||||
├── changed_by (FK → users)
|
||||
├── changed_at
|
||||
├── entity_type (routes, cross_sections, structures)
|
||||
├── entity_id
|
||||
├── old_value (JSONB)
|
||||
├── new_value (JSONB)
|
||||
└── reason (사용자 입력 선택)
|
||||
```
|
||||
|
||||
**설계 의도:**
|
||||
- 보안 감시 (audit_logs)
|
||||
- 설계 변경 추적 및 롤백 가능성 (change_logs)
|
||||
|
||||
---
|
||||
|
||||
## 3. 파일시스템 디렉토리 구조
|
||||
|
||||
```
|
||||
storage/
|
||||
├── {company_slug}/
|
||||
│ ├── {user_slug}/
|
||||
│ │ └── {project_uuid}/
|
||||
│ │ ├── raw/ # 입력 원본
|
||||
│ │ │ ├── las/
|
||||
│ │ │ ├── tif/
|
||||
│ │ │ ├── prj/
|
||||
│ │ │ ├── tfw/
|
||||
│ │ │ └── dwg_dxf/
|
||||
│ │ ├── processed/ # 중간 산출물 (DB에 메타데이터만 저장)
|
||||
│ │ │ ├── surface/
|
||||
│ │ │ │ ├── dem_grid_2m.tif
|
||||
│ │ │ │ ├── tin_mesh.obj / .gltf
|
||||
│ │ │ │ └── contours.geojson
|
||||
│ │ │ ├── points/
|
||||
│ │ │ │ ├── ground_filtered.las
|
||||
│ │ │ │ └── ground_sampled_10m.ply
|
||||
│ │ │ └── tiles/
|
||||
│ │ │ └── (3D Tiles / MVT 렌더링 최적화)
|
||||
│ │ ├── sections/ # 단면 데이터 (JSON)
|
||||
│ │ │ ├── longitudinal.json
|
||||
│ │ │ ├── cross_0000m.json
|
||||
│ │ │ ├── cross_0020m.json
|
||||
│ │ │ └── ...
|
||||
│ │ └── outputs/ # 최종 산출물
|
||||
│ │ ├── estimation/
|
||||
│ │ │ ├── v1_2025-07-05.xlsx
|
||||
│ │ │ ├── v2_2025-07-10.xlsx
|
||||
│ │ │ └── ...
|
||||
│ │ ├── drawings/
|
||||
│ │ │ ├── v1_base.dxf
|
||||
│ │ │ ├── v1_detailed.dxf
|
||||
│ │ │ └── ...
|
||||
│ │ ├── reports/
|
||||
│ │ │ ├── v1_report.pdf
|
||||
│ │ │ └── ...
|
||||
│ │ └── bundles/
|
||||
│ │ ├── v1_20250705.zip # 완전 묶음
|
||||
│ │ └── ...
|
||||
│ └── {project_uuid2}/
|
||||
│ └── ...
|
||||
├── temp/ # 임시 처리 파일 (일정 기간 후 삭제)
|
||||
└── cache/ # 렌더링 캐시 (3D Tiles, 이미지 타일)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 주요 쿼리 패턴 및 인덱스 전략
|
||||
|
||||
### 4.1 자주 사용할 조회 패턴
|
||||
```sql
|
||||
-- 사용자의 전체 프로젝트 목록 (상태별 필터)
|
||||
SELECT * FROM projects
|
||||
WHERE user_id = :user_id AND deleted_at IS NULL
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- 프로젝트의 최신 경로
|
||||
SELECT * FROM routes
|
||||
WHERE project_id = :project_id AND status IN ('CONFIRMED', 'DRAFT')
|
||||
ORDER BY computed_at DESC LIMIT 1;
|
||||
|
||||
-- 경로의 횡단면 일괄 조회 (웹 렌더링)
|
||||
SELECT chainage_m, geometry FROM cross_sections
|
||||
WHERE route_id = :route_id AND status = 'CONFIRMED'
|
||||
ORDER BY chainage_m;
|
||||
|
||||
-- 수량 항목별 비용 집계
|
||||
SELECT category, SUM(total_price) as subtotal
|
||||
FROM quantity_items
|
||||
WHERE project_id = :project_id
|
||||
GROUP BY category;
|
||||
|
||||
-- 공간 범위 쿼리 (지도 렌더링)
|
||||
SELECT id, geometry FROM routes
|
||||
WHERE project_id = :project_id
|
||||
AND ST_Intersects(geometry, ST_GeomFromText(:bbox_wkt));
|
||||
```
|
||||
|
||||
### 4.2 권장 인덱스
|
||||
```sql
|
||||
-- 사용자별 프로젝트 조회
|
||||
CREATE INDEX idx_projects_user_deleted
|
||||
ON projects(user_id, deleted_at);
|
||||
|
||||
-- 프로젝트별 경로 최신순
|
||||
CREATE INDEX idx_routes_project_computed
|
||||
ON routes(project_id, computed_at DESC);
|
||||
|
||||
-- 경로의 횡단면 측점 순
|
||||
CREATE INDEX idx_cross_sections_route_chainage
|
||||
ON cross_sections(route_id, chainage_m);
|
||||
|
||||
-- PostGIS 공간 인덱스
|
||||
CREATE INDEX idx_routes_geometry
|
||||
ON routes USING GIST(geometry);
|
||||
|
||||
CREATE INDEX idx_cross_sections_geometry
|
||||
ON cross_sections USING GIST(geometry);
|
||||
|
||||
-- 수량 항목 범위 쿼리
|
||||
CREATE INDEX idx_quantity_project_category
|
||||
ON quantity_items(project_id, category);
|
||||
|
||||
-- 감시 로그 시간순
|
||||
CREATE INDEX idx_audit_logs_timestamp
|
||||
ON audit_logs(user_id, timestamp DESC);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 라우터 구조 (FastAPI)
|
||||
|
||||
### 5.1 라우터 계층화
|
||||
```
|
||||
routers/
|
||||
├── auth.py # 인증 (로그인, 회원가입, 토큰 갱신)
|
||||
├── users.py # 사용자 프로필
|
||||
├── companies.py # 회사 관리
|
||||
├── projects.py # 프로젝트 CRUD + 상태 관리
|
||||
├── files.py # 파일 업로드/다운로드
|
||||
├── workflows/
|
||||
│ ├── wf1_surface.py # WF1: 지표면 분석 (LAS → DEM/mesh)
|
||||
│ ├── wf2_route.py # WF2: 경로 설계
|
||||
│ ├── wf3_sections.py # WF3: 종횡단 생성
|
||||
│ ├── wf4_design.py # WF4: 상세 설계 (구조물)
|
||||
│ ├── wf5_quantity.py # WF5: 수량 산출
|
||||
│ └── wf6_outputs.py # WF6: 산출물 생성 (Excel/PDF/DXF)
|
||||
├── analysis.py # 분석 결과 조회 (읽기 전용)
|
||||
└── admin.py # 관리자 기능 (감시 로그 등)
|
||||
```
|
||||
|
||||
### 5.2 주요 엔드포인트 예시
|
||||
```
|
||||
POST /api/auth/register 사용자 가입
|
||||
POST /api/auth/login 로그인
|
||||
GET /api/users/me 현재 사용자 정보
|
||||
PATCH /api/users/{user_id} 프로필 수정
|
||||
|
||||
POST /api/projects 프로젝트 생성
|
||||
GET /api/projects 사용자 프로젝트 목록
|
||||
GET /api/projects/{proj_id} 프로젝트 상세
|
||||
PATCH /api/projects/{proj_id} 프로젝트 수정 (이름, 메모 등)
|
||||
|
||||
POST /api/projects/{proj_id}/files/upload 파일 업로드
|
||||
GET /api/projects/{proj_id}/files 파일 목록
|
||||
|
||||
POST /api/projects/{proj_id}/wf1/analyze WF1 시작
|
||||
GET /api/projects/{proj_id}/wf1/status WF1 진행률
|
||||
GET /api/projects/{proj_id}/wf1/surface-models 생성된 표면
|
||||
|
||||
POST /api/projects/{proj_id}/wf2/calculate-route WF2 경로 계산
|
||||
GET /api/projects/{proj_id}/wf2/routes 경로 목록
|
||||
PATCH /api/projects/{proj_id}/wf2/routes/{route_id} 경로 수정
|
||||
|
||||
GET /api/projects/{proj_id}/wf3/cross-sections 횡단면 목록
|
||||
GET /api/projects/{proj_id}/wf3/sections/{ch} 특정 측점 상세
|
||||
|
||||
POST /api/projects/{proj_id}/wf4/structures 구조물 추가
|
||||
PATCH /api/projects/{proj_id}/wf4/structures/{id} 구조물 수정
|
||||
|
||||
GET /api/projects/{proj_id}/wf5/quantities 수량 항목
|
||||
PATCH /api/projects/{proj_id}/wf5/quantities/{id} 수량 수정
|
||||
|
||||
POST /api/projects/{proj_id}/wf6/generate-output 산출물 생성
|
||||
GET /api/projects/{proj_id}/outputs 산출물 목록
|
||||
GET /api/projects/{proj_id}/outputs/{id}/download 다운로드
|
||||
|
||||
POST /api/projects/{proj_id}/confirm 프로젝트 확정
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 비동기 작업 및 백그라운드 처리 (Celery / asyncio)
|
||||
|
||||
### 6.1 장시간 작업 큐
|
||||
```
|
||||
작업 목록:
|
||||
1. WF1 분석 (LAS 필터링 → 지형 변환) — 수십 분
|
||||
2. WF2 경로 계산 (최적화 알고리즘) — 수 분
|
||||
3. WF6 산출물 생성 (DXF/Excel/PDF) — 수 분
|
||||
|
||||
구현 방식:
|
||||
- Celery + Redis (프로덕션)
|
||||
또는
|
||||
- FastAPI BackgroundTasks + asyncio (초기 단계)
|
||||
|
||||
상태 추적:
|
||||
- jobs 테이블에 task_id, status, progress, error_msg 저장
|
||||
- WebSocket 또는 polling으로 진행률 클라이언트에 전송
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 마이그레이션 및 테이블 생성 전략
|
||||
|
||||
### 7.1 alembic 기반 버전 관리
|
||||
```
|
||||
alembic/
|
||||
├── versions/
|
||||
│ ├── 001_initial_schema.py (users, companies, projects)
|
||||
│ ├── 002_spatial_tables.py (routes, cross_sections, PostGIS)
|
||||
│ ├── 003_audit_and_logs.py (audit_logs, change_logs)
|
||||
│ └── ...
|
||||
└── env.py
|
||||
```
|
||||
|
||||
### 7.2 초기 마이그레이션 단계
|
||||
1. 사용자 / 회사 / 프로젝트 (로그인 후 필수)
|
||||
2. 입력 파일 메타데이터 (파일 업로드 후 필수)
|
||||
3. 경로 / 단면 / 구조물 (워크플로우 실행 후 필수)
|
||||
4. 감시 로그 (선택, 나중에 추가 가능)
|
||||
|
||||
---
|
||||
|
||||
## 8. 데이터 일관성 및 검증 규칙
|
||||
|
||||
### 8.1 데이터 무결성 제약
|
||||
```sql
|
||||
-- 외래키 제약
|
||||
ALTER TABLE projects
|
||||
ADD CONSTRAINT fk_projects_user FOREIGN KEY (user_id)
|
||||
REFERENCES users(id) ON DELETE RESTRICT;
|
||||
|
||||
-- Unique 제약
|
||||
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE(email);
|
||||
ALTER TABLE companies ADD CONSTRAINT uq_companies_name UNIQUE(name);
|
||||
|
||||
-- Check 제약 (상태 머신)
|
||||
ALTER TABLE projects ADD CONSTRAINT check_project_status
|
||||
CHECK (status IN ('NEW', 'ANALYZING', 'WF1_COMPLETE', ..., 'DONE'));
|
||||
```
|
||||
|
||||
### 8.2 좌표계 검증
|
||||
```python
|
||||
# Python 유효성 검사
|
||||
from pyproj import CRS
|
||||
|
||||
def validate_crs(epsg_code: int):
|
||||
try:
|
||||
crs = CRS.from_epsg(epsg_code)
|
||||
return True
|
||||
except:
|
||||
raise ValueError(f"Invalid EPSG code: {epsg_code}")
|
||||
|
||||
# 좌표 변환 시 예외 처리
|
||||
def transform_coordinates(src_epsg, tgt_epsg, coords):
|
||||
transformer = Transformer.from_crs(f"EPSG:{src_epsg}", f"EPSG:{tgt_epsg}")
|
||||
return transformer.transform(coords.x, coords.y)
|
||||
```
|
||||
|
||||
### 8.3 입력 파일 검증
|
||||
```python
|
||||
# 파일 타입 검증
|
||||
ALLOWED_EXTENSIONS = {
|
||||
'las': laspy.read,
|
||||
'tif': rasterio.open,
|
||||
'prj': lambda f: f.read().decode('utf-8'),
|
||||
'dxf': ezdxf.readfile,
|
||||
}
|
||||
|
||||
# 파일 크기 제한 (config에서 읽기)
|
||||
MAX_FILE_SIZE_MB = 500
|
||||
|
||||
def validate_upload(file, file_type):
|
||||
if file.size > MAX_FILE_SIZE_MB * 1024 * 1024:
|
||||
raise ValueError(f"File too large: {file.size / 1024 / 1024:.1f}MB")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 초기 vs 장기 구현 로드맵
|
||||
|
||||
### Phase 1 (초기, 회원가입 MVP — 2-3주)
|
||||
**필수 테이블:** users, companies, projects, input_files, audit_logs
|
||||
**API:** 인증, 프로젝트 CRUD, 파일 업로드
|
||||
**워크플로우:** 파일 입력(B03)까지만 DB 연동
|
||||
|
||||
### Phase 2 (WF1-WF3, 분석 — 4-6주)
|
||||
**추가 테이블:** surface_models, routes, cross_sections, change_logs
|
||||
**API:** WF1 분석 시작, 경로 계산, 단면 조회
|
||||
**백그라운드:** Celery 작업 큐 도입
|
||||
|
||||
### Phase 3 (WF4-WF6, 산출물 — 6-8주)
|
||||
**추가 테이블:** structures, quantity_items, outputs, output_files
|
||||
**API:** 구조물 추가, 수량 산출, 산출물 생성
|
||||
**기능:** Excel/PDF/DXF 자동 생성
|
||||
|
||||
### Phase 4 (협업 및 감시, 선택)
|
||||
**추가 기능:** 팀 공유 프로젝트, 권한 세분화, 비용 청구
|
||||
**감시:** audit_logs 기반 컴플라이언스 리포트
|
||||
|
||||
---
|
||||
|
||||
## 10. 보안 및 성능 고려사항
|
||||
|
||||
### 10.1 보안
|
||||
- **인증:** JWT 토큰 (httponly cookie)
|
||||
- **권한:** user_id 기반 row-level security (RLS) + 애플리케이션 검증
|
||||
- **파일 접근:** 서명된 URL 또는 토큰 검증
|
||||
- **감시:** audit_logs에 모든 수정 기록
|
||||
- **SQL injection 방지:** ORM (SQLAlchemy) + 파라미터화된 쿼리
|
||||
|
||||
### 10.2 성능 최적화
|
||||
- **쿼리:** 인덱스 전략 (위 4.2 참고)
|
||||
- **캐싱:** Redis에 자주 조회하는 메타데이터 캐시 (설정값, 사용자 권한 등)
|
||||
- **파일 저장소:** 클라우드 스토리지 옵션 (AWS S3, GCS) 고려
|
||||
- **3D 렌더링:** LOD/타일링으로 대용량 지형 최적화
|
||||
- **비동기:** 장시간 작업은 백그라운드 큐에서 처리
|
||||
|
||||
### 10.3 백업 및 복구
|
||||
- **DB 백업:** 일 1회 자동 백업 (point-in-time recovery)
|
||||
- **파일 백업:** 중요 산출물은 별도 저장소에 복제
|
||||
- **버전 관리:** change_logs로 프로젝트 상태 롤백 가능
|
||||
|
||||
---
|
||||
|
||||
## 11. 검토 항목 및 다음 단계
|
||||
|
||||
### 체크리스트
|
||||
- [ ] ERD(Entity-Relationship Diagram) 작성 및 리뷰
|
||||
- [ ] 좌표계 변환 로직 검증 (EPSG 코드)
|
||||
- [ ] 파일 저장소 정책 확정 (로컬 vs 클라우드)
|
||||
- [ ] 권한 모델 세밀화 (팀 공유 필요성 판단)
|
||||
- [ ] 비용 청구 로직 정의 (필요 시)
|
||||
- [ ] 성능 테스트 (대용량 파일, 대량 사용자)
|
||||
- [ ] 법적 데이터 보관 기간 확인
|
||||
|
||||
### 다음 작업
|
||||
1. **ERD 다이어그램 생성** — dbdiagram.io 또는 draw.io
|
||||
2. **alembic 마이그레이션 초안 작성** — Phase 1 테이블부터
|
||||
3. **FastAPI 모델(Pydantic) 정의** — 입력/출력 스키마
|
||||
4. **API 문서(OpenAPI)** — Swagger 자동 생성
|
||||
5. **테스트 DB 구성** — 로컬 PostgreSQL + 샘플 데이터
|
||||
|
||||
---
|
||||
|
||||
## 12. 참고: 구현 언어 & 도구
|
||||
|
||||
| 영역 | 도구 | 용도 |
|
||||
|------|------|------|
|
||||
| **ORM** | SQLAlchemy 2.0 + asyncpg | async DB 쿼리 |
|
||||
| **마이그레이션** | Alembic | DB 버전 관리 |
|
||||
| **검증** | Pydantic v2 | 입력 데이터 검증 |
|
||||
| **공간 쿼리** | GeoAlchemy2 + PostGIS | 기하학 연산 |
|
||||
| **비동기** | Celery + Redis 또는 FastAPI BackgroundTasks | 장시간 작업 |
|
||||
| **파일 처리** | laspy, rasterio, geopandas, ezdxf | 공간 데이터 |
|
||||
| **문서 생성** | openpyxl, reportlab, ezdxf | Excel/PDF/DXF 출력 |
|
||||
|
||||
---
|
||||
|
||||
**이 제안서는 기초 설계 단계입니다. 다음 리뷰에서 변경, 추가, 삭제 사항을 정리한 후 ERD 및 마이그레이션 코드로 구체화하겠습니다.**
|
||||
@@ -9,8 +9,10 @@ users
|
||||
├── email (TEXT, UNIQUE) — 로그인 이메일
|
||||
├── password_hash (TEXT) — 비밀번호 암호화 저장
|
||||
├── name (TEXT) — 사용자 이름
|
||||
├── position (TEXT) — 직급 (예: 과장, 대리, 사원)
|
||||
├── department (TEXT) — 부서명 (예: 설계팀, 영업팀)
|
||||
├── phone (TEXT) — 연락처 (예: 010-1234-5678)
|
||||
├── company_id (INT, FK → companies.id) — 소속 회사
|
||||
├── role (TEXT) — 역할: admin, user, guest
|
||||
├── created_at (TIMESTAMP) — 가입일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
@@ -21,9 +23,14 @@ users
|
||||
companies
|
||||
├── id (INT, PK) — 회사 고유 번호
|
||||
├── name (TEXT, UNIQUE) — 회사명
|
||||
├── business_registration_number (TEXT, UNIQUE) — 사업자등록번호
|
||||
├── business_address (TEXT) — 사업장 주소
|
||||
├── business_owner (TEXT) — 사업주 이름
|
||||
├── business_status (TEXT) — 기업 상태 (예: 활동중, 폐업, 휴업)
|
||||
├── created_by (INT, FK → users.id) — 회사 생성자
|
||||
├── created_at (TIMESTAMP) — 생성일
|
||||
└── updated_at (TIMESTAMP) — 수정일
|
||||
├── updated_at (TIMESTAMP) — 수정일
|
||||
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -67,14 +74,14 @@ project_versions
|
||||
|
||||
## 3. 파일 & 입력 데이터 그룹
|
||||
|
||||
### 3-1. input_files 테이블
|
||||
### 3-1. input_files 테이블 (원본 입력 파일 — 영구저장소)
|
||||
```
|
||||
input_files
|
||||
├── id (INT, PK) — 파일 고유 번호
|
||||
├── project_id (UUID, FK → projects.id) — 속한 프로젝트
|
||||
├── file_type (TEXT) — 파일 종류: las, tif, tfw, prj, dxf, dwg, other
|
||||
├── original_filename (TEXT) — 원래 파일명 (예: "cloud_merged.las")
|
||||
├── stored_path (TEXT) — 저장 경로 (예: "storage/.../raw/las/cloud_merged.las")
|
||||
├── raw_file_path (TEXT) — 원본 저장 경로 (예: "storage/.../raw/las/cloud_merged.las")
|
||||
├── file_size_mb (FLOAT) — 파일 크기 (MB)
|
||||
├── upload_by (INT, FK → users.id) — 업로드한 사용자
|
||||
├── upload_at (TIMESTAMP) — 업로드 날짜
|
||||
@@ -86,11 +93,16 @@ input_files
|
||||
└── status (TEXT) — 상태: UPLOADED, PROCESSED, ARCHIVED
|
||||
```
|
||||
|
||||
### 3-2. point_cloud_metadata 테이블
|
||||
### 3-2. processed_point_cloud 테이블 (변환된 포인트클라우드 데이터 — 영구저장소)
|
||||
```
|
||||
point_cloud_metadata
|
||||
├── id (INT, PK)
|
||||
├── input_file_id (INT, FK → input_files.id) — LAS 파일 참조
|
||||
processed_point_cloud
|
||||
├── id (INT, PK) — 변환 데이터 고유 번호
|
||||
├── input_file_id (INT, FK → input_files.id) — 원본 LAS 파일 참조
|
||||
├── project_id (UUID, FK → projects.id)
|
||||
├── process_type (TEXT) — 변환 방식: filtered, sampled, classified 등
|
||||
├── processed_file_path (TEXT) — 변환된 파일 경로 (예: "storage/.../processed/las/ground_filtered.las")
|
||||
├── converted_format (TEXT) — 변환 포맷: las, ply, laz 등 (컴퓨터가 빠르게 읽기 위해)
|
||||
├── converted_file_path (TEXT) — 변환 포맷 저장 경로 (예: "storage/.../processed/ply/cloud_sampled_10m.ply")
|
||||
├── point_count (INT) — 포인트 개수
|
||||
├── min_z (FLOAT) — 최저 높이
|
||||
├── max_z (FLOAT) — 최고 높이
|
||||
@@ -100,59 +112,70 @@ point_cloud_metadata
|
||||
├── y_min (FLOAT) — 최소 위도
|
||||
├── y_max (FLOAT) — 최대 위도
|
||||
├── density_per_sqm (FLOAT) — 단위 면적당 포인트 밀도
|
||||
└── classification_summary (JSONB) — 분류 요약
|
||||
├── ground (지표면 포인트)
|
||||
├── vegetation (식생)
|
||||
├── building (건물)
|
||||
└── ...
|
||||
├── classification_summary (JSONB) — 분류 요약
|
||||
│ ├── ground (지표면 포인트)
|
||||
│ ├── vegetation (식생)
|
||||
│ ├── building (건물)
|
||||
│ └── ...
|
||||
├── processing_params (JSONB) — 변환 파라미터
|
||||
├── processed_at (TIMESTAMP) — 변환 완료 날짜
|
||||
└── status (TEXT) — 상태: PROCESSING, COMPLETE, FAILED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 지표면 & 분석 결과 그룹
|
||||
|
||||
### 4-1. surface_models 테이블 (WF1 출력)
|
||||
### 4-1. surface_models 테이블 (WF1 출력 — 지표면 분석 결과)
|
||||
```
|
||||
surface_models
|
||||
├── id (INT, PK) — 지표면 모델 고유 번호
|
||||
├── project_id (UUID, FK → projects.id)
|
||||
├── model_type (TEXT) — 모델 종류: dem_grid, tin, mesh_triangulated, contour_lines
|
||||
├── source_file_id (INT, FK → input_files.id) — 원본 LAS 파일
|
||||
├── processed_cloud_id (INT, FK → processed_point_cloud.id) — 변환된 포인트클라우드 참조
|
||||
├── status (TEXT) — 상태: PROCESSING, COMPLETE, FAILED
|
||||
├── crs_epsg (INT) — 좌표계
|
||||
├── resolution_m (FLOAT) — 해상도 (래스터인 경우, NULL 가능)
|
||||
├── stored_path (TEXT) — 저장 경로 (예: "storage/.../processed/surface/dem.tif")
|
||||
├── model_file_path (TEXT) — 모델 저장 경로 (예: "storage/.../processed/surface/dem.tif")
|
||||
├── bounds (GEOMETRY) — 모델 범위 (다각형)
|
||||
├── metadata (JSONB) — 생성 파라미터
|
||||
├── generation_params (JSONB) — 생성 파라미터
|
||||
│ ├── filter_type (필터링 방식)
|
||||
│ ├── creation_time (생성 시각)
|
||||
│ └── ...
|
||||
│ ├── algorithm (사용 알고리즘)
|
||||
│ ├── params (알고리즘 파라미터)
|
||||
│ └── creation_time (생성 시각)
|
||||
├── created_at (TIMESTAMP)
|
||||
└── completed_at (TIMESTAMP)
|
||||
```
|
||||
|
||||
### 4-2. terrain_layers 테이블
|
||||
### 4-2. terrain_layers 테이블 (WF1 산출물 — 각 지형 레이어)
|
||||
```
|
||||
terrain_layers
|
||||
├── id (INT, PK)
|
||||
├── id (INT, PK) — 레이어 고유 번호
|
||||
├── surface_model_id (INT, FK → surface_models.id)
|
||||
├── layer_name (TEXT) — 레이어명 (예: "지표", "제1층", "제2층" 등)
|
||||
├── geometry_type (TEXT) — 기하 종류: POINTCLOUD, GRID, MESH, CONTOUR
|
||||
├── stored_path (TEXT) — 저장 경로 (GeoJSON, GeoTIFF, LAS 등)
|
||||
└── statistics (JSONB) — 통계
|
||||
├── min_z, max_z, mean_slope
|
||||
└── ...
|
||||
├── layer_file_path (TEXT) — 레이어 파일 저장 경로 (예: "storage/.../processed/surface/layer_1.geojson")
|
||||
├── file_format (TEXT) — 파일 형식: geojson, geotiff, ply, obj 등
|
||||
├── file_size_mb (FLOAT) — 파일 크기
|
||||
├── bounds (GEOMETRY) — 레이어 범위
|
||||
├── statistics (JSONB) — 통계
|
||||
│ ├── min_z, max_z, mean_slope
|
||||
│ ├── point_count (포인트 개수)
|
||||
│ └── ...
|
||||
└── created_at (TIMESTAMP)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 경로 & 설계 그룹
|
||||
|
||||
### 5-1. routes 테이블 (WF2 출력)
|
||||
### 5-1. routes 테이블 (WF2 출력 — 경로 설계 결과)
|
||||
```
|
||||
routes
|
||||
├── id (INT, PK) — 경로 고유 번호
|
||||
├── project_id (UUID, FK → projects.id)
|
||||
├── surface_model_id (INT, FK → surface_models.id) — 기반 지표면 모델
|
||||
├── status (TEXT) — 상태: DRAFT, CONFIRMED, ARCHIVED
|
||||
├── start_point (GEOMETRY(Point)) — 시작점 좌표 (3D)
|
||||
├── end_point (GEOMETRY(Point)) — 종료점 좌표 (3D)
|
||||
@@ -170,6 +193,7 @@ routes
|
||||
│ ├── cost_weights (비용함수 가중치)
|
||||
│ └── ...
|
||||
├── geometry (GEOMETRY(LineString, Z)) — 3D 경로 좌표 열
|
||||
├── route_data_path (TEXT) — 경로 데이터 저장 경로 (예: "storage/.../computed/route/route_main.geojson")
|
||||
├── computed_at (TIMESTAMP) — 계산 완료 날짜
|
||||
└── created_at (TIMESTAMP)
|
||||
```
|
||||
@@ -204,7 +228,7 @@ route_statistics
|
||||
|
||||
## 6. 종단면 & 횡단면 그룹
|
||||
|
||||
### 6-1. longitudinal_sections 테이블 (WF3 출력)
|
||||
### 6-1. longitudinal_sections 테이블 (WF3 출력 — 종단면)
|
||||
```
|
||||
longitudinal_sections
|
||||
├── id (INT, PK) — 종단면 고유 번호
|
||||
@@ -217,10 +241,11 @@ longitudinal_sections
|
||||
│ ├── elevations ([100.5, 102.3, 101.8, ...]) — 지표 표고 배열
|
||||
│ ├── grades ([2.5, 1.8, 1.0, ...]) — 경사도 배열 (%)
|
||||
│ └── design_elevations ([100.0, 102.0, 101.5, ...]) — 설계 표고
|
||||
└── stored_path (TEXT) — 저장 경로 (예: "storage/.../sections/longitudinal.json")
|
||||
├── longitudinal_file_path (TEXT) — 저장 경로 (예: "storage/.../computed/sections/longitudinal.json")
|
||||
└── status (TEXT) — 상태: DRAFT, CONFIRMED
|
||||
```
|
||||
|
||||
### 6-2. cross_sections 테이블 (WF3 출력)
|
||||
### 6-2. cross_sections 테이블 (WF3 출력 — 횡단면)
|
||||
```
|
||||
cross_sections
|
||||
├── id (INT, PK) — 횡단면 고유 번호
|
||||
@@ -238,14 +263,15 @@ cross_sections
|
||||
│ ├── fill_volume_m3 (FLOAT) — 성토량
|
||||
│ ├── structures ([]) — 이 단면 내 구조물 ID 배열
|
||||
│ └── notes (TEXT) — 메모
|
||||
└── stored_path (TEXT) — 저장 경로 (예: "storage/.../sections/cross_0020m.json")
|
||||
├── cross_section_file_path (TEXT) — 저장 경로 (예: "storage/.../computed/sections/cross_0020m.json")
|
||||
└── status (TEXT) — 상태: DRAFT, CONFIRMED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 설계 & 구조물 그룹
|
||||
|
||||
### 7-1. structures 테이블 (WF4 출력)
|
||||
### 7-1. structures 테이블 (WF4 출력 — 구조물)
|
||||
```
|
||||
structures
|
||||
├── id (INT, PK) — 구조물 고유 번호
|
||||
@@ -262,11 +288,13 @@ structures
|
||||
├── unit_price (FLOAT) — 단가
|
||||
├── geometry (GEOMETRY(Polygon)) — 3D 배치 도형
|
||||
├── design_notes (JSONB) — 설계 노트
|
||||
├── structure_data_path (TEXT) — 구조물 데이터 저장 경로 (예: "storage/.../computed/structures/struct_0020m_001.json")
|
||||
├── last_modified_by (INT, FK → users.id) — 마지막 수정자
|
||||
└── created_at (TIMESTAMP)
|
||||
├── created_at (TIMESTAMP)
|
||||
└── updated_at (TIMESTAMP)
|
||||
```
|
||||
|
||||
### 7-2. quantity_items 테이블 (WF5 출력)
|
||||
### 7-2. quantity_items 테이블 (WF5 출력 — 수량 산출)
|
||||
```
|
||||
quantity_items
|
||||
├── id (INT, PK) — 수량 항목 고유 번호
|
||||
@@ -280,8 +308,10 @@ quantity_items
|
||||
├── total_price (FLOAT) — 소계 (quantity_actual × unit_price)
|
||||
├── standard_reference (TEXT) — 기준 참고 자료
|
||||
├── computed_at (TIMESTAMP) — 계산 날짜
|
||||
├── quantity_data_path (TEXT) — 수량 데이터 저장 경로 (예: "storage/.../computed/quantities/items.json")
|
||||
└── data (JSONB) — 계산 과정 메모
|
||||
├── formula (계산식)
|
||||
├── source (데이터 출처)
|
||||
└── ...
|
||||
```
|
||||
|
||||
@@ -289,7 +319,7 @@ quantity_items
|
||||
|
||||
## 8. 산출물 & 문서 그룹
|
||||
|
||||
### 8-1. outputs 테이블 (WF6 출력)
|
||||
### 8-1. outputs 테이블 (WF6 출력 — 최종 산출물 세트)
|
||||
```
|
||||
outputs
|
||||
├── id (INT, PK) — 산출물 세트 고유 번호
|
||||
@@ -298,7 +328,8 @@ outputs
|
||||
├── status (TEXT) — 상태: GENERATING, COMPLETE, FAILED
|
||||
├── generated_by (INT, FK → users.id) — 생성자
|
||||
├── generated_at (TIMESTAMP) — 생성 날짜
|
||||
├── version (INT) — 버전 (프로젝트 확정 시 자동 증가)
|
||||
├── version (INT) — 버전 (프로젝트 확정 단계에서 자동 증가)
|
||||
├── outputs_directory_path (TEXT) — 산출물 저장 폴더 (예: "storage/.../outputs/v1/")
|
||||
└── metadata (JSONB) — 메타데이터
|
||||
├── template_used (사용한 양식)
|
||||
├── company_name (회사명)
|
||||
@@ -307,14 +338,14 @@ outputs
|
||||
└── generation_time_sec (생성 소요 시간)
|
||||
```
|
||||
|
||||
### 8-2. output_files 테이블
|
||||
### 8-2. output_files 테이블 (최종 산출 파일들)
|
||||
```
|
||||
output_files
|
||||
├── id (INT, PK) — 파일 고유 번호
|
||||
├── output_id (INT, FK → outputs.id)
|
||||
├── file_type (TEXT) — 파일 형식: xlsx, pdf, dxf, dwg, json, zip
|
||||
├── original_filename (TEXT) — 파일명 (예: "견적서_v1.xlsx")
|
||||
├── stored_path (TEXT) — 저장 경로 (예: "storage/.../outputs/estimation/견적서_v1.xlsx")
|
||||
├── output_file_path (TEXT) — 저장 경로 (예: "storage/.../outputs/v1/견적서.xlsx")
|
||||
├── file_size_mb (FLOAT) — 파일 크기
|
||||
├── created_at (TIMESTAMP) — 생성 날짜
|
||||
└── download_count (INT) — 다운로드 횟수
|
||||
@@ -385,35 +416,100 @@ users (사용자)
|
||||
|
||||
---
|
||||
|
||||
## 11. 파일시스템 경로와 DB 링크
|
||||
## 11. 파일시스템 경로와 DB 링크 (워크플로우 기반 구조)
|
||||
|
||||
### 파일시스템 구조 (6단계 워크플로우에 맞춤)
|
||||
|
||||
```
|
||||
storage/
|
||||
├── {company_slug}/
|
||||
│ └── {user_slug}/
|
||||
│ └── {project_id}/ ← projects.storage_path
|
||||
│ ├── raw/ ← input_files.stored_path
|
||||
│ │ ├── las/
|
||||
│ │ ├── tif/
|
||||
│ │ ├── tfw/
|
||||
│ │ ├── prj/
|
||||
│ │ └── dxf/
|
||||
│ ├── processed/ ← surface_models.stored_path, terrain_layers.stored_path
|
||||
│ │ ├── surface/
|
||||
│ │ ├── points/
|
||||
│ │ └── tiles/
|
||||
│ ├── sections/ ← longitudinal_sections.stored_path, cross_sections.stored_path
|
||||
│ │ ├── longitudinal.json
|
||||
│ │ ├── cross_0000m.json
|
||||
│ │ ├── cross_0020m.json
|
||||
│ │ └── ...
|
||||
│ └── outputs/ ← output_files.stored_path
|
||||
│ ├── estimation/
|
||||
│ ├── drawings/
|
||||
│ ├── reports/
|
||||
│ │
|
||||
│ ├── B03_FileInput/ ← WF0: 파일 입력
|
||||
│ │ ├── input/ (원본 입력 파일들)
|
||||
│ │ │ ├── las/
|
||||
│ │ │ │ └── cloud_merged.las ← input_files.raw_file_path
|
||||
│ │ │ ├── tif/
|
||||
│ │ │ ├── tfw/
|
||||
│ │ │ ├── prj/
|
||||
│ │ │ └── dxf/
|
||||
│ │ └── metadata.json (파일 메타데이터)
|
||||
│ │
|
||||
│ ├── 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
|
||||
│ │ │ └── ...
|
||||
│ │ └── analysis_report.json
|
||||
│ │
|
||||
│ ├── B05_wf2_Route/ ← WF2: 경로 설계
|
||||
│ │ ├── route/
|
||||
│ │ │ ├── route_main.geojson ← routes.route_data_path
|
||||
│ │ │ ├── route_points.json
|
||||
│ │ │ └── route_statistics.json ← route_statistics 데이터
|
||||
│ │ └── design_params.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
|
||||
│ │ │ ├── cross_0040m.json
|
||||
│ │ │ └── ...
|
||||
│ │ └── sections_index.json
|
||||
│ │
|
||||
│ ├── B07_wf4_DesignDetail/ ← WF4: 상세 설계
|
||||
│ │ ├── structures/
|
||||
│ │ │ ├── struct_0020m_001.json ← structures.structure_data_path
|
||||
│ │ │ ├── struct_0020m_002.json
|
||||
│ │ │ ├── struct_0040m_001.json
|
||||
│ │ │ └── ...
|
||||
│ │ └── design_review.json
|
||||
│ │
|
||||
│ ├── B08_wf5_Quantity/ ← WF5: 수량 산출
|
||||
│ │ ├── quantities/
|
||||
│ │ │ └── items.json ← quantity_items.quantity_data_path
|
||||
│ │ ├── breakdown_by_category.json (항목별 집계)
|
||||
│ │ └── cost_summary.json (비용 요약)
|
||||
│ │
|
||||
│ └── B09_wf6_Estimation/ ← WF6: 견적·문서
|
||||
│ ├── v1/ ← outputs.outputs_directory_path
|
||||
│ │ ├── 견적서.xlsx ← output_files.output_file_path
|
||||
│ │ ├── 설계도.dxf
|
||||
│ │ ├── 보고서.pdf
|
||||
│ │ ├── 종단면도.pdf
|
||||
│ │ └── 횡단면도.pdf
|
||||
│ ├── v2/
|
||||
│ │ ├── 견적서.xlsx
|
||||
│ │ └── ...
|
||||
│ └── bundles/
|
||||
│ ├── v1_complete.zip (버전 1 전체 패키지)
|
||||
│ └── v2_complete.zip (버전 2 전체 패키지)
|
||||
```
|
||||
|
||||
**핵심 개념:**
|
||||
- **B03_FileInput/**: WF0 파일 입력 단계 (사용자 업로드 원본)
|
||||
- **B04_wf1_Surface/**: WF1 지표면 분석 결과 (DEM, TIN, 메시, 레이어)
|
||||
- **B05_wf2_Route/**: WF2 경로 설계 결과 (경로 GeoJSON)
|
||||
- **B06_wf3_ProfileCross/**: WF3 종횡단 생성 결과 (종단면, 횡단면 JSON)
|
||||
- **B07_wf4_DesignDetail/**: WF4 상세 설계 결과 (구조물 배치)
|
||||
- **B08_wf5_Quantity/**: WF5 수량 산출 결과 (수량 항목)
|
||||
- **B09_wf6_Estimation/**: WF6 최종 산출물 (Excel, PDF, DXF)
|
||||
|
||||
**장점:**
|
||||
✅ 워크플로우 단계와 폴더가 1:1 대응 → 직관적
|
||||
✅ 각 단계의 입력/출력이 명확히 분리
|
||||
✅ 각 단계 재실행 시 폴더만 초기화하면 됨
|
||||
✅ 버전 관리 (v1, v2, ...) 단순화
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 12. 핵심 쿼리 예제
|
||||
@@ -480,20 +576,86 @@ ORDER BY timestamp DESC;
|
||||
|
||||
## 요약
|
||||
|
||||
**총 17개 테이블:**
|
||||
1. users, companies
|
||||
2. projects, project_versions
|
||||
3. input_files, point_cloud_metadata
|
||||
4. surface_models, terrain_layers
|
||||
5. routes, route_points, route_statistics
|
||||
6. longitudinal_sections, cross_sections
|
||||
7. structures, quantity_items
|
||||
8. outputs, output_files
|
||||
9. audit_logs, change_logs
|
||||
### 주요 변경사항
|
||||
1. ✅ **users 테이블**: position(직급), department(부서), phone(연락처) 추가
|
||||
2. ✅ **companies 테이블**: business_registration_number, business_address, business_owner, business_status 추가
|
||||
3. ✅ **경로 관리 추가**: 모든 데이터의 저장소 경로를 DB에 기록
|
||||
4. ✅ **processed_point_cloud 테이블 신규**: LAS → 빠른 포맷 변환 추적
|
||||
5. ✅ **파일 경로 세분화**:
|
||||
- `raw_file_path`: 원본 파일 위치
|
||||
- `processed_file_path / converted_file_path`: 변환된 파일 위치
|
||||
- `*_file_path`: 각 워크플로우 단계별 산출물 위치
|
||||
|
||||
**다음 단계:**
|
||||
- [ ] PostgreSQL에 위 테이블 생성
|
||||
### 총 18개 테이블:
|
||||
1. **사용자/회사**: users, companies (2개)
|
||||
2. **프로젝트**: projects, project_versions (2개)
|
||||
3. **입력 데이터**: input_files, processed_point_cloud (2개)
|
||||
4. **지표면 분석**: surface_models, terrain_layers (2개)
|
||||
5. **경로 설계**: routes, route_points, route_statistics (3개)
|
||||
6. **단면 설계**: longitudinal_sections, cross_sections (2개)
|
||||
7. **구조물/수량**: structures, quantity_items (2개)
|
||||
8. **산출물**: outputs, output_files (2개)
|
||||
9. **감시/이력**: audit_logs, change_logs (2개)
|
||||
|
||||
### DB vs 파일시스템 역할 분리
|
||||
| 저장 위치 | 내용 | 예시 |
|
||||
|----------|------|------|
|
||||
| **DB** | 메타데이터 + 경로정보 | 파일명, 크기, 좌표계, 상태, 저장경로 |
|
||||
| **파일시스템** | 실제 파일 데이터 | LAS, DXF, Excel, PDF, JSON, GeoJSON 파일들 |
|
||||
|
||||
**워크플로우 6단계에 따른 파일 흐름:**
|
||||
```
|
||||
B03_FileInput/ (WF0: 파일 입력)
|
||||
└─ input/*.las, *.tif, *.prj, *.tfw
|
||||
↓
|
||||
B04_wf1_Surface/ (WF1: 지표면 분석)
|
||||
├─ processed/*.ply (변환된 포인트클라우드)
|
||||
└─ models/*.tif, *.geojson (DEM, TIN, 레이어)
|
||||
↓
|
||||
B05_wf2_Route/ (WF2: 경로 설계)
|
||||
└─ route/*.geojson (최적 경로)
|
||||
↓
|
||||
B06_wf3_ProfileCross/ (WF3: 종횡단 생성)
|
||||
├─ longitudinal/*.json (종단면)
|
||||
└─ cross_sections/*.json (횡단면 배열)
|
||||
↓
|
||||
B07_wf4_DesignDetail/ (WF4: 상세 설계)
|
||||
└─ structures/*.json (구조물 배치)
|
||||
↓
|
||||
B08_wf5_Quantity/ (WF5: 수량 산출)
|
||||
└─ quantities/*.json (수량 항목)
|
||||
↓
|
||||
B09_wf6_Estimation/ (WF6: 견적·문서)
|
||||
├─ v1/*.xlsx, *.pdf, *.dxf (최종 산출물 v1)
|
||||
├─ v2/*.xlsx, *.pdf, *.dxf (최종 산출물 v2)
|
||||
└─ bundles/*.zip (완전 패키지)
|
||||
```
|
||||
|
||||
**각 단계별 재실행:**
|
||||
- 예: B05 경로 설계 재실행 → `B05_wf2_Route/route/` 폴더만 초기화
|
||||
- 예: B09 문서 재생성 → `B09_wf6_Estimation/v3/` 폴더 생성 (v1, v2는 유지)
|
||||
|
||||
### 워크플로우 기반 폴더 구조의 이점
|
||||
|
||||
| 항목 | 이전 (raw/processed/computed) | 현재 (B04/B05/B06/.../B09) |
|
||||
|------|-----|------|
|
||||
| **직관성** | 계층적이나 추상적 | ✅ 워크플로우 단계와 정확히 일치 |
|
||||
| **단계 재실행** | 어느 폴더를 초기화할지 모호 | ✅ 해당 B0X 폴더만 초기화 |
|
||||
| **버전 관리** | outputs/v1, v2, ... 만 가능 | ✅ 모든 단계에서 가능 |
|
||||
| **파일 찾기** | raw/ 또는 processed/ 에서 검색 | ✅ B0X_Naming으로 명확함 |
|
||||
| **API 라우터** | 경로 결정이 복잡 | ✅ `f"storage/{company}/{user}/{project_id}/B{step}_*/..."` |
|
||||
|
||||
### 다음 단계:
|
||||
- [X] MariaDB에 위 테이블 생성 (18개 테이블)
|
||||
- [ ] 관계(FK) 설정
|
||||
- [ ] 인덱스 추가
|
||||
- [ ] Alembic 마이그레이션 코드 작성
|
||||
- [ ] Python ORM(SQLAlchemy) 모델 정의
|
||||
- [ ] 인덱스 추가 (경로 조회, 프로젝트별 필터 등)
|
||||
- [ ] Alembic 마이그레이션 코드 작성<<<<<< 확인 필요(mariaDB사용중)
|
||||
- [ ] Python ORM(SQLAlchemy) 모델 정의<<<<<< 확인 필요(mariaDB사용중)
|
||||
- [ ] 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` 저장
|
||||
+230
-344
@@ -1,388 +1,274 @@
|
||||
# 프로젝트 리팩토링 및 마이그레이션 계획서
|
||||
# B03~B06 기능 마이그레이션 계획서
|
||||
|
||||
**작성일:** 2026-07-05
|
||||
**상태:** 계획 수립 완료
|
||||
**목표:** 기존 POC(Proof of Concept) 코드를 정식 프로덕션 구조로 전환
|
||||
**갱신일:** 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로 통일, 순수 계산부는 실데이터로 검증
|
||||
- 남은 작업: B04~B06 프론트엔드(Vanilla TS/WebCAD), 실제 aislo_db 연동·구형 수치 비교
|
||||
|
||||
---
|
||||
|
||||
## 1. 현황 분석 (Baseline Assessment)
|
||||
## 1. 확정 기준
|
||||
|
||||
### 1.1 기존 상태
|
||||
- **위치:** `0_old/` 폴더에 백업 완료
|
||||
- **제외 사항:** `venv/`, `node_modules/` 미포함 (별도 재설치 필요)
|
||||
- **특성:** 프로토타입 개발용 단순 구조
|
||||
|
||||
### 1.2 신 구조 목표
|
||||
- **거대 모듈 금지:** 기능별/페이지별 분할 자동화
|
||||
- **수직 통합 관리:** 프론트엔드(TypeScript) + 백엔드(Python) 페어링
|
||||
- **명확한 책임 분리:** 시스템 폴더 vs. 페이지 폴더 격리
|
||||
- 기능 동작의 기준 원본은 `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. 신 디렉토리 구조 (New Structure)
|
||||
## 2. 작업 원칙
|
||||
|
||||
```
|
||||
프로젝트_루트/
|
||||
├── .agent/ # 에이전트 명세서
|
||||
│ ├── structure.md # 폴더/파일 명명규칙
|
||||
│ ├── agent.md # 기술스택 및 제약조건
|
||||
│ ├── backend.md # 백엔드 명세서
|
||||
│ ├── frontend.md # 프론트엔드 명세서
|
||||
│ └── migration_plan.md # 본 파일 (마이그레이션 가이드)
|
||||
│
|
||||
├── common_util/ # 공통 유틸리티 모듈
|
||||
│ ├── common_util_geometry.py # 기하학 연산 (Trimesh, Shapely)
|
||||
│ ├── common_util_gis.py # GIS 유틸 (PostGIS, Geopandas)
|
||||
│ ├── common_util_file.py # 파일 I/O 및 경로 관리
|
||||
│ ├── common_util_validation.py # 데이터 검증 (Pydantic)
|
||||
│ ├── common_util_math.py # 수학/통계 함수
|
||||
│ └── common_util_string.ts # 문자열 처리 (TypeScript)
|
||||
│
|
||||
├── db_management/ # DB 스키마 및 마이그레이션
|
||||
│ ├── schema.sql # PostgreSQL + PostGIS 초기 스키마
|
||||
│ ├── migration_001_init.py # 마이그레이션 스크립트 v1
|
||||
│ └── README.md # DB 운영 가이드
|
||||
│
|
||||
├── resources/ # UI 리소스 (로고, 이미지, 폰트)
|
||||
│ ├── images/
|
||||
│ │ ├── logo.svg
|
||||
│ │ ├── favicon.ico
|
||||
│ │ └── banners/
|
||||
│ └── fonts/
|
||||
│ └── noto-sans-kr.woff2
|
||||
│
|
||||
├── storage/ # 영구 저장소 (물리 파일)
|
||||
│ └── README.md # 저장소 구조 설명
|
||||
│
|
||||
├── ui_template/ # 공통 UI 템플릿 및 테마
|
||||
│ ├── ui_template_theme.css # 글로벌 색상/폰트 변수
|
||||
│ ├── ui_template_locale.ts # 다국어 관리 (i18n)
|
||||
│ ├── ui_template_elements.ts # 공통 컴포넌트 정의
|
||||
│ └── ui_template_styles.css # 기본 컴포넌트 스타일
|
||||
│
|
||||
├── templates/ # 보고서/도면 템플릿
|
||||
│ ├── template_report_standard.xlsx # 표준 보고서
|
||||
│ ├── template_drawing_base.dwg # CAD 기본 도면
|
||||
│ └── template_quantity_sheet.xlsx # 수량 산출서
|
||||
│
|
||||
├── config/ # 환경 설정
|
||||
│ ├── config_system.py # 백엔드 파라미터
|
||||
│ ├── config_db.py # DB 연결 설정
|
||||
│ ├── config_frontend.ts # 프론트엔드 상수
|
||||
│ └── .env.example # 환경 변수 템플릿
|
||||
│
|
||||
├── A01_Home/ # 로그인 전 01: 홈
|
||||
│ ├── A01_Home_UI_Page.ts # 페이지 UI 컴포넌트
|
||||
│ ├── A01_Home_UI_Style.css # 페이지 스타일
|
||||
│ ├── A01_Home_Api_Fetch.py # API 라우터
|
||||
│ └── A01_Home_Schema.py # Pydantic 스키마
|
||||
│
|
||||
├── A02_ProgDetail/ ... A08_Support/ # 로그인 전 페이지들 (동일 구조)
|
||||
│
|
||||
├── B01_AccountDetail/ # 로그인 후 01: 계정
|
||||
├── B02_ProjRegister/ # 로그인 후 02: 프로젝트 등록
|
||||
├── B03_FileInput/ # 로그인 후 03: 파일 입력
|
||||
│
|
||||
├── B04_wf1_Surface/ # Workflow 1: 지표면 분석
|
||||
│ ├── B04_wf1_Surface_UI_View.ts # UI 뷰어
|
||||
│ ├── B04_wf1_Surface_UI_Form.ts # 입력 폼
|
||||
│ ├── B04_wf1_Surface_UI_Style.css
|
||||
│ ├── B04_wf1_Surface_Api_Router.py # API 라우터
|
||||
│ ├── B04_wf1_Surface_Engine.py # 연산 엔진 (Trimesh, PostGIS)
|
||||
│ └── B04_wf1_Surface_Schema.py # 데이터 스키마
|
||||
│
|
||||
├── B05_wf2_Route/ ... B09_wf6_Estimation/ # 워크플로우 2-6 (동일 구조)
|
||||
│
|
||||
├── B10_Payment/ & B11_Status/ # 로그인 후 최종 페이지
|
||||
│
|
||||
├── main.py # FastAPI 애플리케이션 진입점
|
||||
├── pyproject.toml # Python 의존성 (Poetry)
|
||||
├── package.json & package-lock.json # Node.js 의존성
|
||||
├── CLAUDE.md # 프로젝트 기본 정보
|
||||
├── README.md # 프로젝트 개요
|
||||
└── .gitignore # Git 제외 목록
|
||||
### 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/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 마이그레이션 단계별 계획 (Phased Migration)
|
||||
|
||||
### Phase 1: 기초 인프라 (Infrastructure Setup) - 1-2주
|
||||
**담당:** 전체
|
||||
**목표:** 설정 및 공통 유틸 정리
|
||||
|
||||
#### 1.1 설정 파일 정리
|
||||
- [ ] `config_system.py` 작성
|
||||
- FastAPI 포트, 로깅 레벨, 알고리즘 파라미터
|
||||
- 예: `MESH_GRID_SIZE`, `UPLOAD_MAX_MB`, `LOG_LEVEL`
|
||||
- [ ] `config_db.py` 작성
|
||||
- PostgreSQL 연결 문자열 (asyncpg)
|
||||
- PostGIS 테이블 정의 위치
|
||||
- [ ] `config_frontend.ts` 작성
|
||||
- API 기본 URL, 파일 업로드 제한 크기
|
||||
- WebCAD 렌더링 옵션
|
||||
|
||||
#### 1.2 공통 유틸 마이그레이션
|
||||
- [ ] **0_old/** 에서 기존 유틸 함수 추출
|
||||
- [ ] `common_util/` 내 분류:
|
||||
- `common_util_geometry.py` ← Trimesh, Shapely 관련
|
||||
- `common_util_gis.py` ← PostGIS 쿼리, Geopandas 변환
|
||||
- `common_util_file.py` ← LAS, DEM, DWG 읽기/쓰기
|
||||
- `common_util_validation.py` ← Pydantic 모델, 데이터 검증
|
||||
|
||||
#### 1.3 UI 템플릿 기초 설정
|
||||
- [ ] `ui_template_theme.css` 작성
|
||||
- CSS 변수 정의 (라이트/다크 모드)
|
||||
- 예: `--color-bg`, `--color-text`, `--color-primary`
|
||||
- [ ] `ui_template_locale.ts` 작성
|
||||
- 한글 / 영문 키-값 쌍
|
||||
- 예: `A01_Home_Title: ["반갑습니다", "Welcome"]`
|
||||
- [ ] `ui_template_elements.ts` 작성
|
||||
- 공통 버튼, 입력창, 카드 컴포넌트 정의
|
||||
|
||||
#### 1.4 DB 스키마 및 마이그레이션 준비
|
||||
- [ ] `db_management/schema.sql` 작성
|
||||
- 사용자, 프로젝트, 파일 정보 테이블
|
||||
- PostGIS 기하학 타입 (geometry, raster)
|
||||
- [ ] `db_management/migration_001_init.py` 작성
|
||||
- asyncpg 기반 초기화 스크립트
|
||||
|
||||
#### 1.5 FastAPI 메인 애플리케이션 구조
|
||||
- [ ] `main.py` 작성 (라우터 등록만)
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
# A01_Home, B04_wf1_Surface 등에서 라우터 import
|
||||
# app.include_router(a01_router, prefix="/api/a01")
|
||||
```
|
||||
각 페이지는 자기 폴더만 직접 생성·수정한다. 상위 단계 변경 시 하위 결과를 임의 삭제하지 않고 `stale_from`을 갱신한다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 로그인 전 페이지 (Pre-Login Pages) - 2-3주
|
||||
**담당:** 프론트엔드 + 백엔드 (페이지별 페어)
|
||||
**목표:** A01~A08 페이지 완성
|
||||
## 3. 구형 기능 분석 결과
|
||||
|
||||
#### 2.1 페이지별 구조 (A01_Home 예시)
|
||||
각 페이지는 다음 파일 구성:
|
||||
### 3.1 B03_FileInput
|
||||
|
||||
**프론트엔드 (TypeScript)**
|
||||
- `A01_Home_UI_Page.ts` ← 페이지 진입점 + 컴포넌트 조립
|
||||
- `A01_Home_UI_Style.css` ← 페이지별 커스텀 스타일
|
||||
구형 원본:
|
||||
|
||||
**백엔드 (Python)**
|
||||
- `A01_Home_Api_Fetch.py` ← FastAPI 라우터 (`/api/a01/...`)
|
||||
- `A01_Home_Schema.py` ← Pydantic 요청/응답 모델
|
||||
- `0_old/main.py`: `check_sample`, `upload_files`
|
||||
- 보조 참고: `0_old/backend/app/analyzer.py`의 파일 분석 함수
|
||||
|
||||
#### 2.2 A01_Home (홈) - Week 1
|
||||
- [ ] `A01_Home_UI_Page.ts` 작성
|
||||
- 로고, 최신 소식 패널, 사이트맵 링크
|
||||
- [ ] `A01_Home_Api_Fetch.py` 작성
|
||||
- `GET /api/a01/news` ← 최신 소식 조회
|
||||
- [ ] 기본 스타일 적용
|
||||
책임:
|
||||
|
||||
#### 2.3 A02_ProgDetail ~ A08_Support - Week 2-3
|
||||
동일 구조로 순차 진행:
|
||||
- [ ] A02_ProgDetail (프로그램 상세)
|
||||
- [ ] A03_CompDetail (회사 상세)
|
||||
- [ ] A04_NewsHistory (뉴스/이력)
|
||||
- [ ] A05_EduDetail (교육)
|
||||
- [ ] A06_Login (로그인) ← **핵심: JWT 토큰 발급**
|
||||
- [ ] A07_Register (회원가입) ← **핵심: 사용자 생성, 이메일 검증**
|
||||
- [ ] A08_Support (기술 지원)
|
||||
- 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`
|
||||
|
||||
책임:
|
||||
|
||||
- 확정 경로와 확정 지표면 모델 검증
|
||||
- 종단 표고 프로필 생성
|
||||
- 지정 측점 간격의 횡단면 생성
|
||||
- 결과 조회·확정 및 재현성 서명 관리
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: 로그인 후 기초 페이지 (Post-Login Basic Pages) - 1-2주
|
||||
**담당:** 프론트엔드 + 백엔드
|
||||
**목표:** B01~B03 페이지 완성
|
||||
## 4. 함수 단위 실행 순서
|
||||
|
||||
#### 3.1 B01_AccountDetail (계정 상세)
|
||||
- [ ] 사용자 정보 조회/수정
|
||||
- [ ] 구독 상태, 다운로드 이력
|
||||
### Stage 0 — 공통 기반
|
||||
|
||||
#### 3.2 B02_ProjRegister (프로젝트 등록)
|
||||
- [ ] 프로젝트 생성 폼
|
||||
- [ ] 고객사명 / 사용자명 / 프로젝트ID 관리
|
||||
- [ ] **핵심:** `storage/[고객사]/[사용자]/[프로젝트ID]/` 자동 생성
|
||||
- [x] `config_system.py`의 저장 경로에서 불필요한 `projects` 경로 제거
|
||||
- [x] 프로젝트 루트와 B03~B06 단계별 경로를 안전하게 만드는 공통 경로 함수 추가
|
||||
- [x] JSON 원자적 쓰기 함수 이전
|
||||
- [x] `workflow.json` 읽기 및 stale 필드 원자적 갱신 함수 이전
|
||||
- [x] 공통 함수별 테스트 작성
|
||||
|
||||
#### 3.3 B03_FileInput (파일 입력)
|
||||
- [ ] 파일 업로드 UI (Drag & Drop)
|
||||
- [ ] LAS, DEM, DWG 파일 검증
|
||||
- [ ] `storage/` 경로에 물리 저장
|
||||
### 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)
|
||||
- [ ] B04 폼·WebCAD 뷰어를 Vanilla TypeScript로 재작성
|
||||
- [ ] 샘플 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)
|
||||
- [ ] B05 경로 편집·제약조건·결과 뷰어를 Vanilla TypeScript로 재작성
|
||||
- [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)
|
||||
- [ ] B06 종단도·횡단 카드 UI를 Vanilla TypeScript로 재작성
|
||||
- [ ] 구형 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 연동은 나스 서버 준비 후).
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Workflow 엔진 (1-4) - 4-6주
|
||||
**담당:** 백엔드 주 (프론트엔드 보조)
|
||||
**목표:** B04~B07 워크플로우 엔진 완성
|
||||
## 5. API 및 상태 원칙
|
||||
|
||||
#### 4.1 B04_wf1_Surface (지표면 분석) - Week 1-2
|
||||
**구조:**
|
||||
```
|
||||
B04_wf1_Surface/
|
||||
├── B04_wf1_Surface_UI_View.ts # WebCAD 뷰어
|
||||
├── B04_wf1_Surface_UI_Form.ts # 입력 폼 (좌측 패널)
|
||||
├── B04_wf1_Surface_UI_Style.css
|
||||
├── B04_wf1_Surface_Api_Router.py # GET/POST 엔드포인트
|
||||
├── B04_wf1_Surface_Engine.py # Trimesh 메쉬 생성 로직
|
||||
└── B04_wf1_Surface_Schema.py # Pydantic 모델
|
||||
```
|
||||
|
||||
**구현:**
|
||||
- [ ] LAS 포인트클라우드 → Trimesh 메쉬 변환
|
||||
- [ ] 메쉬 좌표 직렬화 (JSON)
|
||||
- [ ] WebGL 캔버스에서 렌더링
|
||||
- [ ] 메쉬 정보 DB 저장 (경로만)
|
||||
|
||||
#### 4.2 B05_wf2_Route (경로 설계) - Week 2-3
|
||||
- [ ] 지도 UI (2D 평면도)
|
||||
- [ ] 드래그로 경로 그리기
|
||||
- [ ] Geopandas + PostGIS 거리 계산
|
||||
- [ ] 경로 점 배열 저장
|
||||
|
||||
#### 4.3 B06_wf3_ProfileCross (종횡단) - Week 3-4
|
||||
- [ ] 경로 기반 고도 프로필 생성
|
||||
- [ ] 종단도 + 횡단도 계산 (PostGIS)
|
||||
- [ ] 기울기, 높이차 자동 산출
|
||||
|
||||
#### 4.4 B07_wf4_DesignDetail (상세 설계) - Week 4-5
|
||||
- [ ] 도로 포장 두께, 기울기 입력
|
||||
- [ ] Whitebox 토양 분석
|
||||
- [ ] 3D 설계 도면 생성
|
||||
- API 경로는 `/api/projects/{project_id}/...` 형태를 유지하되 페이지별 Router에서 선언한다.
|
||||
- 모든 JSON 요청은 Pydantic 검증 후 Engine으로 전달한다.
|
||||
- 긴 지형 연산은 이벤트 루프를 막지 않도록 실행 방식을 별도 검토한다.
|
||||
- 오류 응답은 `{"status": "error", "message": "원인"}` 형식을 유지한다.
|
||||
- 프론트 API 호출은 로딩 Overlay를 항상 시작·해제한다.
|
||||
- UI 문자열은 `ui_template_locale.ts`에 먼저 등록한 뒤 참조한다.
|
||||
- 상위 단계 재계산 시 하위 결과는 stale 처리하며 다른 페이지 폴더를 직접 삭제하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: 워크플로우 후반 (5-6) + 최종 페이지 - 2-3주
|
||||
**담당:** 백엔드 주 + 프론트엔드
|
||||
**목표:** B08~B11 완성
|
||||
## 6. 검증 체크리스트
|
||||
|
||||
#### 5.1 B08_wf5_Quantity (수량 산출)
|
||||
- [ ] 포장재, 흙 깍기/쌓기 수량 계산
|
||||
- [ ] Excel 템플릿 자동 채우기
|
||||
각 함수 이전 후:
|
||||
|
||||
#### 5.2 B09_wf6_Estimation (견적/문서)
|
||||
- [ ] 단가 × 수량 = 금액 계산
|
||||
- [ ] PDF 보고서 생성 (Jinja2 템플릿)
|
||||
- [ ] DWG 도면 생성 (pyDWG)
|
||||
- [ ] 구형 함수의 입력·출력 계약 비교
|
||||
- [ ] 하드코딩 설정 제거 및 `config_system.py` 연결
|
||||
- [ ] 신규 단계별 저장 경로 사용 확인
|
||||
- [ ] 경로 이탈 및 파일명 공격 방지 확인
|
||||
- [ ] 예외 처리와 표준 오류 응답 확인
|
||||
- [ ] Python: `ruff format`, `ruff check --fix`
|
||||
- [ ] TypeScript/CSS: `npx prettier --write`
|
||||
- [ ] 단위 테스트 실행
|
||||
- [ ] 관련 통합 테스트 실행
|
||||
- [ ] 단일 파일 700줄 미만 확인
|
||||
|
||||
#### 5.3 B10_Payment (결재)
|
||||
- [ ] 견적서 검토 및 승인
|
||||
- [ ] 결재자 서명 프로세스
|
||||
단계 완료 후:
|
||||
|
||||
#### 5.4 B11_Status (상태/다운로드)
|
||||
- [ ] 프로젝트 상태 조회
|
||||
- [ ] 최종 산출물 다운로드
|
||||
- [ ] B03 업로드 파일과 DB 메타데이터 일치
|
||||
- [ ] B04 지표면 모델의 범위·점 수·표고 통계 비교
|
||||
- [ ] B05 경로 길이·경사·곡선반경·제약 위반 비교
|
||||
- [ ] B06 측점 수·종단 표고·횡단 좌표 비교
|
||||
- [ ] 다중 브라우저 polling 및 stale 전파 확인
|
||||
|
||||
---
|
||||
|
||||
## 4. 기술 구현 가이드 (Technical Guidelines)
|
||||
## 7. 작업 경계와 보류 항목
|
||||
|
||||
### 4.1 백엔드 파이썬 스타일
|
||||
```python
|
||||
# config 반드시 import
|
||||
from config.config_system import MESH_GRID_SIZE
|
||||
from config.config_db import DB_URL
|
||||
|
||||
# 공통 유틸 사용
|
||||
from common_util.common_util_geometry import create_mesh_from_points
|
||||
from common_util.common_util_validation import validate_project_id
|
||||
|
||||
# Raw SQL (ORM 금지)
|
||||
query = "SELECT ST_Volume(geom) FROM surfaces WHERE project_id = $1"
|
||||
result = await db_pool.fetchval(query, project_id)
|
||||
|
||||
# 에러 처리
|
||||
try:
|
||||
mesh = create_mesh(points)
|
||||
except ValueError as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
```
|
||||
|
||||
### 4.2 프론트엔드 TypeScript 스타일
|
||||
```typescript
|
||||
// CSS 변수 사용 (색상 하드코딩 금지)
|
||||
const styles = `
|
||||
.panel {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
`;
|
||||
|
||||
// 다국어 (i18n 필수)
|
||||
const title = ui_locales.A01_Home_Title[currentLanguageIndex];
|
||||
|
||||
// 이벤트 핸들러 명명법
|
||||
function onB04_Surface_Calculate_Click() { ... }
|
||||
|
||||
// 로딩 스피너 (API 호출 시 필수)
|
||||
showLoadingOverlay();
|
||||
const result = await fetchAPI("/api/b04/calculate", data);
|
||||
hideLoadingOverlay();
|
||||
```
|
||||
|
||||
### 4.3 파일 정보 저장 규칙
|
||||
- **DB에 저장:** 메타데이터만
|
||||
- 파일명, 원본 경로, 업로드 시간, 파일 크기
|
||||
- **물리 저장:** `storage/[고객사]/[사용자]/[프로젝트ID]/`
|
||||
- LAS, DEM, 중간 계산 파일, 최종 메쉬
|
||||
- DB 테이블·FK·인덱스 정의 변경은 본 작업에서 수행하지 않는다.
|
||||
- `.agent/db_schema_simple.md`는 DB 담당자의 작업 영역이므로 직접 수정하지 않는다.
|
||||
- DB 스키마가 확정되지 않은 Repository 함수는 인터페이스만 계획하고 임의 열을 만들지 않는다.
|
||||
- B07 이후 기능은 이번 마이그레이션 범위에서 제외한다.
|
||||
- 구형 샘플 데이터는 검증 입력으로만 사용하고 신규 영구저장소로 자동 복사하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 의존성 (Dependencies)
|
||||
## 8. 다음 실행 단위
|
||||
|
||||
### Python 라이브러리
|
||||
```
|
||||
fastapi==0.104.1
|
||||
pydantic==2.5.0
|
||||
asyncpg==0.29.0
|
||||
trimesh==3.23.0
|
||||
geopandas==0.14.0
|
||||
shapely==2.0.1
|
||||
rasterio==1.3.8
|
||||
laspy==2.4.1
|
||||
whitebox==2.3.0
|
||||
python-multipart==0.0.6
|
||||
jinja2==3.1.2
|
||||
pydwg==0.2.0
|
||||
```
|
||||
|
||||
### Node.js 라이브러리
|
||||
```
|
||||
typescript@5.x
|
||||
react@18.x (또는 vanilla TS)
|
||||
webpack@5.x
|
||||
tailwindcss@3.x (또는 커스텀 CSS)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 체크리스트 (Checklist)
|
||||
|
||||
### 시작 전 (Pre-Migration)
|
||||
- [ ] `0_old/` 백업 확인
|
||||
- [ ] git 커밋 (`"Refactor: Start migration to new structure"`)
|
||||
- [ ] 모든 폴더 생성 완료 (Phase 1에서 수행)
|
||||
|
||||
### 각 Phase별 마무리
|
||||
- [ ] 코드 포맷팅 (`ruff format`, `prettier --write`)
|
||||
- [ ] 린터 체크 (`ruff check`)
|
||||
- [ ] 단위 테스트 작성
|
||||
- [ ] git 커밋 (Phase별로 구분)
|
||||
|
||||
### 최종 (Post-Migration)
|
||||
- [ ] 전체 시스템 통합 테스트
|
||||
- [ ] 프로덕션 배포 (Docker)
|
||||
- [ ] 사용자 승인
|
||||
|
||||
---
|
||||
|
||||
## 7. 참고 자료
|
||||
|
||||
- **폴더 구조:** `.agent/structure.md`
|
||||
- **백엔드 명세:** `.agent/backend.md`
|
||||
- **프론트엔드 명세:** `.agent/frontend.md`
|
||||
- **기술 스택:** `.agent/agent.md`
|
||||
|
||||
---
|
||||
|
||||
**다음 단계:** Phase 1 시작 → `config/` 및 `common_util/` 작성
|
||||
첫 구현은 Stage 0의 저장 경로 함수 하나로 시작한다. 함수와 테스트를 검증한 뒤 다음 공통 함수로 이동한다.
|
||||
|
||||
+13
-2
@@ -26,6 +26,9 @@ my-project/
|
||||
├── common_util/ # 공통 유틸리티 (공통 기능 코드)
|
||||
│ ├── common_util_validate.ts # 프론트 1차 유효성 검사 (이메일/빈값/최소길이)
|
||||
│ ├── common_util_string.ts
|
||||
│ ├── common_util_storage.py # 프로젝트 및 워크플로우 단계별 저장 경로
|
||||
│ ├── common_util_json.py # JSON 원자적 저장
|
||||
│ ├── common_util_workflow.py # 공유 workflow.json 상태 처리
|
||||
│ └── common_util_format.py
|
||||
├── db_management/ # DB 관리 폴더 (PostgreSQL 마이그레이션 및 스키마 관리)
|
||||
│ ├── schema.sql
|
||||
@@ -86,9 +89,17 @@ my-project/
|
||||
│ └── B02_ProjRegister_UI_Style.css
|
||||
├── B03_FileInput/ # 로그인 후 03: 파일 입력 (헤더+준비중 안내)
|
||||
│ ├── B03_FileInput_UI_Page.ts
|
||||
│ └── B03_FileInput_UI_Style.css
|
||||
│ ├── 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
|
||||
│ ├── B04_wf1_Surface_UI_Page.ts
|
||||
│ ├── B04_wf1_Surface_Engine_Structurize.py # LAS/LAZ 구조화
|
||||
│ └── B04_wf1_Surface_Engine_Filter_Grid.py # grid minimum-Z 필터
|
||||
├── B05_wf2_Route/ # 로그인 후 05: 2차 workflow (경로설계) — 워크플로우 셸
|
||||
│ └── B05_wf2_Route_UI_Page.ts
|
||||
├── B06_wf3_ProfileCross/ # 로그인 후 06: 3차 workflow (종횡단 생성) — 워크플로우 셸
|
||||
|
||||
Reference in New Issue
Block a user