683 lines
24 KiB
Markdown
683 lines
24 KiB
Markdown
# 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 및 마이그레이션 코드로 구체화하겠습니다.**
|