Files
Aislo/.agent/db_schema.md
T

779 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DB 스키마 (테이블 및 열 목록)
## 1. 사용자 & 인증 & 조직 그룹
### 1-1. users 테이블
```
users
├── id (INT, PK) — 사용자 고유 번호
├── email (TEXT, UNIQUE) — 로그인 이메일
├── password_hash (TEXT) — 비밀번호 암호화 저장
├── name (TEXT) — 사용자 이름
├── position (TEXT) — 직급 (예: 과장, 대리, 사원)
├── department (TEXT) — 부서명 (예: 설계팀, 영업팀)
├── phone (TEXT) — 연락처 (예: 010-1234-5678)
├── company_id (INT, FK → companies.id, NULL) — 소속 회사
├── role (ENUM) — 역할: SYSTEM_ADMIN, ADMIN, USER (기본값: USER)
├── is_master (BOOLEAN) — 회사 마스터 여부 (기본값: FALSE)
├── status (ENUM) — 상태: PENDING_EMAIL, NO_COMPANY, PENDING, ACTIVE, INACTIVE, REJECTED
├── last_login (DATETIME, NULL) — 마지막 로그인 시간
├── auth_expires_at (DATETIME, NULL) — 인증 유효 만료 시간 (3개월 주기)
├── created_at (TIMESTAMP) — 가입일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
**상태 생명주기:**
- PENDING_EMAIL: 회원가입 후 이메일 인증 전
- NO_COMPANY: 이메일 인증 완료, 회사 미연결 (로그인 가능, B02~B11 차단)
- PENDING: 회사 참여 신청 후 관리자 승인 대기
- ACTIVE: 회사 연결 완료, 모든 기능 접근 가능
- INACTIVE: 퇴사/계정 비활성화
- REJECTED: 회사 참여 신청 거부
### 1-2. companies 테이블
```
companies
├── id (INT, PK) — 회사 고유 번호
├── name (TEXT, UNIQUE) — 회사명
├── business_registration_number (TEXT, UNIQUE) — 사업자등록번호
├── business_address (TEXT) — 사업장 주소
├── business_owner (TEXT) — 사업주 이름
├── business_status (TEXT, ENUM) — 기업 상태: 활동중, 폐업, 휴업 (기본값: 활동중)
├── created_by (INT, FK → users.id) — 회사 생성자 (첫 ADMIN)
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-3. join_requests 테이블 (신규)
```
join_requests
├── id (INT, PK) — 요청 고유 번호
├── user_id (INT, FK → users.id) — 신청한 사용자
├── company_id (INT, FK → companies.id) — 신청 대상 회사
├── requested_at (TIMESTAMP) — 신청 시간
├── status (ENUM) — 상태: PENDING, APPROVED, REJECTED (기본값: PENDING)
├── reviewed_by (INT, FK → users.id, NULL) — 검토한 관리자
├── reviewed_at (TIMESTAMP, NULL) — 검토 시간
└── UNIQUE KEY (user_id, company_id) — 중복 신청 방지
```
**동작:**
- 사용자 가입 신청 → status = PENDING 생성
- 관리자 승인 → status = APPROVED, users.status = ACTIVE, users.company_id 세팅
- 관리자 거부 → status = REJECTED, 이메일 발송
### 1-4. system_audit_logs 테이블 (신규)
```
system_audit_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id) — 행동 수행자
├── action (VARCHAR(50)) — 행동: LOGIN, LOGOUT, USER_CREATE, USER_UPDATE, COMPANY_CREATE, ROLE_CHANGE, etc
├── resource_type (VARCHAR(50)) — 대상 타입: user, company, project, join_request
├── resource_id (INT, NULL) — 대상 ID
├── timestamp (TIMESTAMP, DEFAULT CURRENT_TIMESTAMP) — 기록 시간
├── ip_address (VARCHAR(50), NULL) — IP 주소 (선택사항)
├── user_agent (TEXT, NULL) — 브라우저/OS 정보 (선택사항)
├── INDEX (timestamp) — 조회 성능 최적화
└── INDEX (user_id) — 사용자별 조회 최적화
```
### 1-5. system_resources 테이블 (신규)
```
system_resources
├── id (INT, PK) — 기록 고유 번호
├── timestamp (TIMESTAMP, DEFAULT CURRENT_TIMESTAMP) — 기록 시간
├── cpu_usage_percent (FLOAT) — CPU 사용률 (%)
├── memory_usage_percent (FLOAT) — 메모리 사용률 (%)
├── disk_usage_percent (FLOAT) — 디스크 사용률 (%)
├── active_user_count (INT) — 활성 사용자 수
├── active_project_count (INT) — 활성 프로젝트 수
├── total_storage_mb (FLOAT) — 전체 저장소 사용량 (MB)
└── INDEX (timestamp) — 시계열 조회 최적화
```
---
## 테이블 관계도 (조직 & 인증 그룹)
```
users (사용자)
├── id (PK)
├── email
├── password_hash
├── role (SYSTEM_ADMIN | ADMIN | USER)
├── is_master (마스터 여부)
├── status (생명주기)
├── company_id (FK) ──→ companies.id
├── last_login
└── auth_expires_at
companies (회사)
├── id (PK)
├── name
├── business_registration_number
├── business_address
├── business_owner
├── business_status
├── created_by (FK) ──→ users.id
├── created_at
└── updated_at
join_requests (가입 신청)
├── id (PK)
├── user_id (FK) ──→ users.id
├── company_id (FK) ──→ companies.id
├── requested_at
├── status (PENDING | APPROVED | REJECTED)
├── reviewed_by (FK) ──→ users.id
└── reviewed_at
system_audit_logs (시스템 로그)
├── id (PK)
├── user_id (FK) ──→ users.id
├── action
├── resource_type
├── resource_id
├── timestamp
├── ip_address
└── user_agent
system_resources (리소스 모니터링)
├── id (PK)
├── timestamp
├── cpu_usage_percent
├── memory_usage_percent
├── disk_usage_percent
├── active_user_count
├── active_project_count
└── total_storage_mb
```
---
## 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")
├── raw_file_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. processed_point_cloud 테이블 (변환된 포인트클라우드 데이터 — 영구저장소)
```
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) — 최고 높이
├── 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 (건물)
│ └── ...
├── processing_params (JSONB) — 변환 파라미터
├── processed_at (TIMESTAMP) — 변환 완료 날짜
└── status (TEXT) — 상태: PROCESSING, COMPLETE, FAILED
```
---
## 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 파일
├── processed_cloud_id (INT, FK → processed_point_cloud.id) — 변환된 포인트클라우드 참조
├── status (TEXT) — 상태: PROCESSING, COMPLETE, FAILED
├── crs_epsg (INT) — 좌표계
├── resolution_m (FLOAT) — 해상도 (래스터인 경우, NULL 가능)
├── model_file_path (TEXT) — 모델 저장 경로 (예: "storage/.../processed/surface/dem.tif")
├── bounds (GEOMETRY) — 모델 범위 (다각형)
├── generation_params (JSONB) — 생성 파라미터
│ ├── filter_type (필터링 방식)
│ ├── algorithm (사용 알고리즘)
│ ├── params (알고리즘 파라미터)
│ └── creation_time (생성 시각)
├── created_at (TIMESTAMP)
└── completed_at (TIMESTAMP)
```
### 4-2. terrain_layers 테이블 (WF1 산출물 — 각 지형 레이어)
```
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
├── 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 출력 — 경로 설계 결과)
```
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)
├── 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 경로 좌표 열
├── route_data_path (TEXT) — 경로 데이터 저장 경로 (예: "storage/.../computed/route/route_main.geojson")
├── 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, ...]) — 설계 표고
├── longitudinal_file_path (TEXT) — 저장 경로 (예: "storage/.../computed/sections/longitudinal.json")
└── status (TEXT) — 상태: DRAFT, CONFIRMED
```
### 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) — 메모
├── cross_section_file_path (TEXT) — 저장 경로 (예: "storage/.../computed/sections/cross_0020m.json")
└── status (TEXT) — 상태: DRAFT, CONFIRMED
```
---
## 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) — 설계 노트
├── structure_data_path (TEXT) — 구조물 데이터 저장 경로 (예: "storage/.../computed/structures/struct_0020m_001.json")
├── last_modified_by (INT, FK → users.id) — 마지막 수정자
├── created_at (TIMESTAMP)
└── updated_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) — 계산 날짜
├── quantity_data_path (TEXT) — 수량 데이터 저장 경로 (예: "storage/.../computed/quantities/items.json")
└── data (JSONB) — 계산 과정 메모
├── formula (계산식)
├── source (데이터 출처)
└── ...
```
---
## 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) — 버전 (프로젝트 확정 단계에서 자동 증가)
├── outputs_directory_path (TEXT) — 산출물 저장 폴더 (예: "storage/.../outputs/v1/")
└── 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")
├── output_file_path (TEXT) — 저장 경로 (예: "storage/.../outputs/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 링크 (워크플로우 기반 구조)
### 파일시스템 구조 (6단계 워크플로우에 맞춤)
```
storage/
├── {company_slug}/
│ └── {user_slug}/
│ └── {project_id}/ ← projects.storage_path
│ │
│ ├── B03_FileInput/ ← WF0: 파일 입력
│ │ ├── input/ (원본 입력 파일들)
│ │ │ ├── las/
│ │ │ │ └── cloud_merged.las ← input_files.raw_file_path
│ │ │ ├── tif/
│ │ │ ├── tfw/
│ │ │ ├── prj/
│ │ │ └── dxf/
│ │ └── 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. 핵심 쿼리 예제
### 예제 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(...) |
---
## 요약
### 주요 변경사항
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`: 각 워크플로우 단계별 산출물 위치
### 총 23개 테이블:
1. **사용자/회사/조직**: users, companies, join_requests, system_audit_logs, system_resources (5개)
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 마이그레이션 코드 작성<<<<<< 확인 필요(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` 저장