# DB 스키마 (테이블 및 열 목록) ## 1. 사용자 & 인증 그룹 ### 1-1. users 테이블 ``` users ├── id (INT, PK) — 사용자 고유 번호 ├── email (TEXT, UNIQUE) — 로그인 이메일 ├── password_hash (TEXT) — 비밀번호 암호화 저장 ├── name (TEXT) — 사용자 이름 ├── company_id (INT, FK → companies.id) — 소속 회사 ├── role (TEXT) — 역할: admin, user, guest ├── created_at (TIMESTAMP) — 가입일 ├── updated_at (TIMESTAMP) — 수정일 └── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete) ``` ### 1-2. companies 테이블 ``` companies ├── id (INT, PK) — 회사 고유 번호 ├── name (TEXT, UNIQUE) — 회사명 ├── created_by (INT, FK → users.id) — 회사 생성자 ├── created_at (TIMESTAMP) — 생성일 └── updated_at (TIMESTAMP) — 수정일 ``` --- ## 2. 프로젝트 그룹 ### 2-1. projects 테이블 ``` projects ├── id (UUID, PK) — 프로젝트 고유 ID (예: 550e8400-e29b-41d4-a716-446655440000) ├── user_id (INT, FK → users.id) — 프로젝트 소유자 ├── company_id (INT, FK → companies.id) — 소속 회사 ├── name (TEXT) — 프로젝트명 (예: "2025년 산불진화임도") ├── region (TEXT) — 지역명 (예: "울진군 금강송면") ├── road_type (TEXT) — 임도 종류: 간선임도, 지선임도, 산불진화임도, 계류보전 ├── project_year (INT) — 사업 연도 (예: 2025) ├── estimated_length_m (FLOAT) — 추정 연장 (미터) ├── memo (TEXT) — 비고/메모 ├── status (TEXT) — 상태: NEW, FILE_UPLOADED, WF1_ANALYZING, WF1_COMPLETE, WF2_COMPLETE, ... , CONFIRMED, DONE ├── crs_epsg (INT) — 좌표계 (예: 5178 = 한국 표준) ├── bbox (GEOMETRY) — 프로젝트 전체 범위 (다각형) ├── created_at (TIMESTAMP) — 생성일 ├── updated_at (TIMESTAMP) — 수정일 ├── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete) └── storage_path (TEXT) — 파일시스템 경로 (예: "storage/회사/사용자/project_uuid") ``` ### 2-2. project_versions 테이블 (선택사항) ``` project_versions ├── id (INT, PK) ├── project_id (UUID, FK → projects.id) ├── version_num (INT) — 버전 번호 (1, 2, 3, ...) ├── snapshot_at (TIMESTAMP) — 스냅샷 시점 ├── status (TEXT) — 저장된 상태 ├── data (JSONB) — 설계 데이터 스냅샷 (JSON 형식) └── created_at (TIMESTAMP) ``` --- ## 3. 파일 & 입력 데이터 그룹 ### 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") ├── file_size_mb (FLOAT) — 파일 크기 (MB) ├── upload_by (INT, FK → users.id) — 업로드한 사용자 ├── upload_at (TIMESTAMP) — 업로드 날짜 ├── crs_epsg (INT) — 파일 좌표계 (예: 5178) ├── metadata (JSONB) — 파일 메타데이터 (해상도, 범위, 포인트 수 등) │ ├── resolution_m (좌표계 기준 해상도) │ ├── data_range (데이터 범위) │ └── ... └── status (TEXT) — 상태: UPLOADED, PROCESSED, ARCHIVED ``` ### 3-2. point_cloud_metadata 테이블 ``` point_cloud_metadata ├── id (INT, PK) ├── input_file_id (INT, FK → input_files.id) — LAS 파일 참조 ├── point_count (INT) — 포인트 개수 ├── min_z (FLOAT) — 최저 높이 ├── max_z (FLOAT) — 최고 높이 ├── mean_z (FLOAT) — 평균 높이 ├── x_min (FLOAT) — 최소 경도 ├── x_max (FLOAT) — 최대 경도 ├── y_min (FLOAT) — 최소 위도 ├── y_max (FLOAT) — 최대 위도 ├── density_per_sqm (FLOAT) — 단위 면적당 포인트 밀도 └── classification_summary (JSONB) — 분류 요약 ├── ground (지표면 포인트) ├── vegetation (식생) ├── building (건물) └── ... ``` --- ## 4. 지표면 & 분석 결과 그룹 ### 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 파일 ├── status (TEXT) — 상태: PROCESSING, COMPLETE, FAILED ├── crs_epsg (INT) — 좌표계 ├── resolution_m (FLOAT) — 해상도 (래스터인 경우, NULL 가능) ├── stored_path (TEXT) — 저장 경로 (예: "storage/.../processed/surface/dem.tif") ├── bounds (GEOMETRY) — 모델 범위 (다각형) ├── metadata (JSONB) — 생성 파라미터 │ ├── filter_type (필터링 방식) │ ├── creation_time (생성 시각) │ └── ... ├── created_at (TIMESTAMP) └── completed_at (TIMESTAMP) ``` ### 4-2. terrain_layers 테이블 ``` terrain_layers ├── 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 └── ... ``` --- ## 5. 경로 & 설계 그룹 ### 5-1. routes 테이블 (WF2 출력) ``` routes ├── id (INT, PK) — 경로 고유 번호 ├── project_id (UUID, FK → projects.id) ├── status (TEXT) — 상태: DRAFT, CONFIRMED, ARCHIVED ├── start_point (GEOMETRY(Point)) — 시작점 좌표 (3D) ├── end_point (GEOMETRY(Point)) — 종료점 좌표 (3D) ├── start_chainage_m (FLOAT) — 시작점 측점 (m) ├── end_chainage_m (FLOAT) — 종료점 측점 (m) ├── total_length_m (FLOAT) — 총 연장 (m) ├── grade_percent (JSONB array) — 각 구간 종단 경사도 (%) │ └── [2.5, 1.8, 3.2, ...] ├── constraints (JSONB) — 설계 제약조건 │ ├── max_grade (% 최대경사도) │ ├── min_radius (m 최소곡선반경) │ ├── avoidance_zones ([폴리곤 배열]) │ └── ... ├── algorithm_params (JSONB) — 알고리즘 파라미터 │ ├── cost_weights (비용함수 가중치) │ └── ... ├── geometry (GEOMETRY(LineString, Z)) — 3D 경로 좌표 열 ├── computed_at (TIMESTAMP) — 계산 완료 날짜 └── created_at (TIMESTAMP) ``` ### 5-2. route_points 테이블 (웹 렌더링용 샘플) ``` route_points ├── id (INT, PK) ├── route_id (INT, FK → routes.id) ├── chainage_m (FLOAT) — 측점 (m) ├── geometry (GEOMETRY(Point, Z)) — 포인트 좌표 ├── elevation_m (FLOAT) — 높이 ├── slope_percent (FLOAT) — 경사도 (%) └── sequence_num (INT) — 순서번호 ``` ### 5-3. route_statistics 테이블 ``` route_statistics ├── id (INT, PK) ├── route_id (INT, FK → routes.id) ├── min_slope (FLOAT) — 최소 경사도 (%) ├── max_slope (FLOAT) — 최대 경사도 (%) ├── mean_slope (FLOAT) — 평균 경사도 (%) ├── cut_volume_m3 (FLOAT) — 절토량 (m³) ├── fill_volume_m3 (FLOAT) — 성토량 (m³) ├── tree_cutting_volume (FLOAT) — 목재 절감 추정량 └── cost_score (FLOAT) — 알고리즘 점수 ``` --- ## 6. 종단면 & 횡단면 그룹 ### 6-1. longitudinal_sections 테이블 (WF3 출력) ``` longitudinal_sections ├── id (INT, PK) — 종단면 고유 번호 ├── project_id (UUID, FK → projects.id) ├── route_id (INT, FK → routes.id) ├── computed_at (TIMESTAMP) — 계산 날짜 ├── geometry (GEOMETRY(LineString, Z)) — 종단면 지형 좌표열 ├── data (JSONB) — 상세 데이터 │ ├── chainages ([0, 20, 40, 60, ...]) — 측점 배열 (m) │ ├── 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") ``` ### 6-2. cross_sections 테이블 (WF3 출력) ``` cross_sections ├── id (INT, PK) — 횡단면 고유 번호 ├── project_id (UUID, FK → projects.id) ├── route_id (INT, FK → routes.id) ├── chainage_m (FLOAT) — 측점 (예: 0, 20, 40, ... m) ├── sequence_num (INT) — 횡단면 순번 (1번째, 2번째, ...) ├── geometry (GEOMETRY(LineString, Z)) — 지형 횡단선 좌표열 ├── geometry_design (GEOMETRY(LineString, Z)) — 설계 횡단선 좌표열 ├── data (JSONB) — 상세 데이터 │ ├── left_slope (FLOAT, %) — 좌측 사면 경사 │ ├── right_slope (FLOAT, %) — 우측 사면 경사 │ ├── width_m (FLOAT) — 노폭 │ ├── cut_volume_m3 (FLOAT) — 절토량 │ ├── fill_volume_m3 (FLOAT) — 성토량 │ ├── structures ([]) — 이 단면 내 구조물 ID 배열 │ └── notes (TEXT) — 메모 └── stored_path (TEXT) — 저장 경로 (예: "storage/.../sections/cross_0020m.json") ``` --- ## 7. 설계 & 구조물 그룹 ### 7-1. structures 테이블 (WF4 출력) ``` structures ├── id (INT, PK) — 구조물 고유 번호 ├── project_id (UUID, FK → projects.id) ├── cross_section_id (INT, FK → cross_sections.id) ├── structure_type (TEXT) — 종류: 낙석방지책, 돌붙임, 계간수로, 낙차공, 등 ├── chainage_m (FLOAT) — 측점 (m) ├── location (TEXT) — 위치: LEFT, CENTER, RIGHT ├── length_m (FLOAT) — 길이 ├── width_m (FLOAT) — 폭 ├── height_m (FLOAT) — 높이 ├── material (TEXT) — 재료: 강재, 콘크리트, 목재, 돌 등 ├── quantity (INT) — 개수 ├── unit_price (FLOAT) — 단가 ├── geometry (GEOMETRY(Polygon)) — 3D 배치 도형 ├── design_notes (JSONB) — 설계 노트 ├── last_modified_by (INT, FK → users.id) — 마지막 수정자 └── created_at (TIMESTAMP) ``` ### 7-2. quantity_items 테이블 (WF5 출력) ``` quantity_items ├── id (INT, PK) — 수량 항목 고유 번호 ├── project_id (UUID, FK → projects.id) ├── category (TEXT) — 대분류: 토공, 구조물, 포장, 배수, 녹화, 안전시설 ├── item_name (TEXT) — 항목명 (예: "절토 일반", "낙석방지책 설치") ├── unit (TEXT) — 단위: m3, 개, m, m2 ├── quantity_design (FLOAT) — 설계 수량 ├── quantity_actual (FLOAT) — 실제 수량 (사용자 수정 가능) ├── unit_price (FLOAT) — 단가 ├── total_price (FLOAT) — 소계 (quantity_actual × unit_price) ├── standard_reference (TEXT) — 기준 참고 자료 ├── computed_at (TIMESTAMP) — 계산 날짜 └── data (JSONB) — 계산 과정 메모 ├── formula (계산식) └── ... ``` --- ## 8. 산출물 & 문서 그룹 ### 8-1. outputs 테이블 (WF6 출력) ``` outputs ├── id (INT, PK) — 산출물 세트 고유 번호 ├── project_id (UUID, FK → projects.id) ├── output_type (TEXT) — 종류: estimation_excel, drawing_dxf, report_pdf, all_bundle ├── status (TEXT) — 상태: GENERATING, COMPLETE, FAILED ├── generated_by (INT, FK → users.id) — 생성자 ├── generated_at (TIMESTAMP) — 생성 날짜 ├── version (INT) — 버전 (프로젝트 확정 시 자동 증가) └── metadata (JSONB) — 메타데이터 ├── template_used (사용한 양식) ├── company_name (회사명) ├── project_name (프로젝트명) ├── total_cost (총 비용) └── generation_time_sec (생성 소요 시간) ``` ### 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") ├── file_size_mb (FLOAT) — 파일 크기 ├── created_at (TIMESTAMP) — 생성 날짜 └── download_count (INT) — 다운로드 횟수 ``` --- ## 9. 변경 이력 & 감시 그룹 ### 9-1. audit_logs 테이블 (보안 감시) ``` audit_logs ├── id (INT, PK) — 로그 고유 번호 ├── user_id (INT, FK → users.id) — 행동 수행자 ├── action (TEXT) — 행동: CREATE, READ, UPDATE, DELETE, EXPORT, DOWNLOAD ├── entity_type (TEXT) — 대상 타입: projects, routes, structures, outputs 등 ├── entity_id (TEXT) — 대상 ID ├── timestamp (TIMESTAMP) — 시간 ├── ip_address (TEXT) — IP 주소 └── details (JSONB) — 상세 정보 ├── old_value (변경 전 값) ├── new_value (변경 후 값) └── reason (변경 사유) ``` ### 9-2. change_logs 테이블 (설계 변경 기록) ``` change_logs ├── id (INT, PK) — 변경 고유 번호 ├── project_id (UUID, FK → projects.id) ├── changed_by (INT, FK → users.id) — 변경자 ├── changed_at (TIMESTAMP) — 변경 날짜 ├── entity_type (TEXT) — 대상 타입: routes, cross_sections, structures, quantity_items ├── entity_id (INT) — 대상 ID ├── old_value (JSONB) — 변경 전 값 ├── new_value (JSONB) — 변경 후 값 └── reason (TEXT) — 변경 사유 ``` --- ## 10. 테이블 간 관계도 (폴더처럼 표현) ``` users (사용자) ├── ↓ FK: company_id ├─→ companies (회사) │ ├── ↓ FK │ └─→ projects (프로젝트) │ ├── ↓ FK │ ├─→ input_files (입력 파일) │ │ └─→ point_cloud_metadata (포인트클라우드 메타) │ ├─→ surface_models (지표면 모델) │ │ └─→ terrain_layers (지형 레이어) │ ├─→ routes (경로) │ │ ├─→ route_points (경로 포인트) │ │ ├─→ route_statistics (경로 통계) │ │ ├─→ longitudinal_sections (종단면) │ │ └─→ cross_sections (횡단면) │ │ └─→ structures (구조물) │ ├─→ quantity_items (수량 항목) │ ├─→ outputs (산출물) │ │ └─→ output_files (산출물 파일) │ ├─→ project_versions (프로젝트 버전) │ ├─→ audit_logs (감시 로그) │ └─→ change_logs (변경 로그) ``` --- ## 11. 파일시스템 경로와 DB 링크 ``` 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/ │ └── bundles/ ``` --- ## 12. 핵심 쿼리 예제 ### 예제 1: 사용자의 모든 프로젝트 조회 ```sql SELECT p.id, p.name, p.status, p.created_at FROM projects p WHERE p.user_id = :user_id AND p.deleted_at IS NULL ORDER BY p.created_at DESC; ``` ### 예제 2: 프로젝트의 최신 경로 조회 ```sql SELECT r.id, r.total_length_m, r.geometry, r.status FROM routes r WHERE r.project_id = :project_id ORDER BY r.computed_at DESC LIMIT 1; ``` ### 예제 3: 경로의 모든 횡단면 조회 ```sql SELECT cs.id, cs.chainage_m, cs.geometry, cs.data FROM cross_sections cs WHERE cs.route_id = :route_id ORDER BY cs.chainage_m; ``` ### 예제 4: 프로젝트 총 비용 계산 ```sql SELECT SUM(quantity_actual * unit_price) as total_cost, category FROM quantity_items WHERE project_id = :project_id GROUP BY category; ``` ### 예제 5: 사용자의 감시 로그 (최근 30일) ```sql SELECT action, entity_type, timestamp, details FROM audit_logs WHERE user_id = :user_id AND timestamp > NOW() - INTERVAL '30 days' ORDER BY timestamp DESC; ``` --- ## 13. 데이터 타입 가이드 | 타입 | 설명 | 예제 | |------|------|------| | INT | 정수 | 1, 2, 100 | | FLOAT | 실수 | 1.5, 2.25, 100.75 | | TEXT | 문자열 (가변) | "프로젝트명", "울진군" | | TIMESTAMP | 날짜 + 시간 | 2025-07-05 15:30:00 | | BOOLEAN | 참/거짓 | TRUE, FALSE | | UUID | 36자 고유 ID | 550e8400-e29b-41d4-a716-446655440000 | | JSONB | JSON 데이터 | {"key": "value", "array": [1,2,3]} | | GEOMETRY | 지리 좌표 | POINT(127.5 37.5), LINESTRING(...) | --- ## 요약 **총 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 **다음 단계:** - [ ] PostgreSQL에 위 테이블 생성 - [ ] 관계(FK) 설정 - [ ] 인덱스 추가 - [ ] Alembic 마이그레이션 코드 작성 - [ ] Python ORM(SQLAlchemy) 모델 정의