Files
Aislo/.agent/db_schema.md
T
2026-07-10 19:01:33 +09:00

852 lines
37 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 스키마 (테이블 및 열 목록)
**최종 업데이트:** 2026-07-10
**DB 명:** `aislo_db` (MariaDB v10.6+, utf8mb4_unicode_ci)
**총 테이블 수:** 34개 (33개 현재 운영 + 1개 향후 사용)
---
## 1. 사용자 & 인증 & 조직 그룹
### 1-1. users 테이블
```
users
├── id (INT, PK) — 사용자 고유 번호
├── email (VARCHAR(255), UNIQUE) — 로그인 이메일
├── password_hash (VARCHAR(255)) — 비밀번호 암호화 저장
├── name (VARCHAR(255)) — 사용자 이름
├── position (VARCHAR(100), NULL) — 직급 (예: 과장, 대리, 사원)
├── department (VARCHAR(100), NULL) — 부서명 (예: 설계팀, 영업팀)
├── phone (VARCHAR(20), NULL) — 연락처 (예: 010-1234-5678)
├── company_id (INT, FK → companies.id, NULL) — 소속 회사
├── role (ENUM) — 역할: SYSTEM_ADMIN, ADMIN, USER (기본값: USER)
├── is_master (TINYINT(1)) — 회사 마스터 여부 (기본값: 0)
├── status (ENUM) — 상태: PENDING_EMAIL, NO_COMPANY, PENDING, ACTIVE, INACTIVE, REJECTED (기본값: PENDING_EMAIL)
├── last_login (DATETIME, NULL) — 마지막 로그인 시간
├── auth_expires_at (DATETIME, NULL) — 인증 유효 만료 시간 (3개월 주기)
├── last_email_verified_at (TIMESTAMP, NULL) — 마지막 이메일 인증 시간
├── login_failures (INT) — 연속 로그인 실패 횟수 (기본값: 0, 보안)
├── last_failed_at (TIMESTAMP, NULL) — 마지막 로그인 실패 시간
├── account_locked_until (TIMESTAMP, NULL) — 계정 잠금 해제 시간
├── created_at (TIMESTAMP) — 가입일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
**상태 생명주기:**
- PENDING_EMAIL: 회원가입 후 이메일 인증 전
- NO_COMPANY: 이메일 인증 완료, 회사 미연결 (로그인 가능, B02~B11 차단)
- PENDING: 회사 참여 신청 후 관리자 승인 대기
- ACTIVE: 회사 연결 완료, 모든 기능 접근 가능
- INACTIVE: 퇴사/계정 비활성화
- REJECTED: 회사 참여 신청 거부
### 1-2. companies 테이블
```
companies
├── id (INT, PK) — 회사 고유 번호
├── name (VARCHAR(255), UNIQUE) — 회사명
├── business_registration_number (VARCHAR(20), UNIQUE, NULL) — 사업자등록번호
├── business_address (VARCHAR(255), NULL) — 사업장 주소
├── business_owner (VARCHAR(100), NULL) — 사업주 이름
├── business_status (VARCHAR(50), NULL) — 기업 상태
├── company_code (VARCHAR(20), NULL) — 회사 코드
├── phone_number (VARCHAR(20), NULL) — 회사 연락처
├── representative_name (VARCHAR(100), NULL) — 대표자명
├── master_user_id (INT, NULL) — 마스터 사용자 ID
├── status (ENUM) — 상태: ACTIVE, INACTIVE, SUSPENDED (기본값: ACTIVE)
├── subscription_status (ENUM) — 구독 상태: FREE, TRIAL, PAID, EXPIRED (기본값: FREE)
├── created_by (INT, FK → users.id, NULL) — 회사 생성자
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-3. join_requests 테이블 (가입 신청 - 향후 사용)
```
join_requests
├── id (INT, PK) — 요청 고유 번호
├── user_id (INT, FK → users.id) — 신청한 사용자
├── company_id (INT, FK → companies.id) — 신청 대상 회사
├── requested_at (TIMESTAMP) — 신청 시간
├── status (ENUM) — 상태: PENDING, APPROVED, REJECTED (기본값: PENDING)
├── reviewed_by (INT, FK → users.id, NULL) — 검토한 관리자
├── reviewed_at (TIMESTAMP, NULL) — 검토 시간
├── review_comment (TEXT, NULL) — 검토 의견/거부 사유
└── UNIQUE KEY (user_id, company_id) — 중복 신청 방지
```
**동작 (향후 구현 예정):**
- 사용자 가입 신청 → status = PENDING 생성
- 관리자 승인 → status = APPROVED, users.status = ACTIVE, users.company_id 세팅
- 관리자 거부 → status = REJECTED, review_comment에 거부 사유 기록, 이메일 발송
### 1-5. email_otps 테이블 (이메일 인증)
```
email_otps
├── id (INT, PK) — OTP 고유 번호
├── email (VARCHAR(255)) — 이메일 주소
├── otp_code (VARCHAR(10)) — OTP 코드
├── purpose (VARCHAR(50)) — 목적: REGISTRATION, PASSWORD_RESET, EMAIL_VERIFICATION
├── expires_at (TIMESTAMP) — 만료 시간
├── verified_at (TIMESTAMP, NULL) — 인증 시간
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-6. sessions 테이블 (세션 관리)
```
sessions
├── id (INT, PK) — 세션 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── session_token (VARCHAR(255), UNIQUE) — 세션 토큰
├── expires_at (TIMESTAMP) — 세션 만료 시간
├── created_at (TIMESTAMP) — 생성일
├── last_accessed_at (TIMESTAMP, NULL) — 마지막 접근 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-7. trusted_devices 테이블 (장치 신뢰 관리)
```
trusted_devices
├── id (INT, PK) — 장치 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── device_fingerprint (VARCHAR(255)) — 장치 지문
├── device_name (VARCHAR(100), NULL) — 장치명
├── ip_address (VARCHAR(50)) — IP 주소
├── user_agent (TEXT) — 브라우저/OS 정보
├── trusted_until (TIMESTAMP, NULL) — 신뢰 만료 시간
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-8. user_consents 테이블 (사용자 동의 관리)
```
user_consents
├── id (INT, PK) — 동의 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── consent_type (VARCHAR(100)) — 동의 종류: TERMS, PRIVACY, MARKETING, etc
├── consented (TINYINT(1)) — 동의 여부 (1 = 동의, 0 = 미동의)
├── consent_version (VARCHAR(20)) — 약관 버전
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-9. login_logs 테이블 (로그인 로그)
```
login_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
├── email (VARCHAR(255), NULL) — 로그인 시도 이메일
├── status (VARCHAR(50)) — 상태: SUCCESS, FAILURE, LOCKED
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
├── reason (TEXT, NULL) — 실패 사유
├── created_at (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-10. activity_logs 테이블 (활동 로그)
```
activity_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
├── action (VARCHAR(100)) — 행동: LOGIN, LOGOUT, FILE_UPLOAD, PROJECT_CREATE, etc
├── entity_type (VARCHAR(100), NULL) — 대상 타입: project, file, route, etc
├── entity_id (VARCHAR(50), NULL) — 대상 ID
├── description (TEXT, NULL) — 행동 설명
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── created_at (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-11. audit_logs 테이블 (감시 로그 - 보안)
```
audit_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id) — 행동 수행자
├── action (VARCHAR(100)) — 행동: CREATE, READ, UPDATE, DELETE, EXPORT, DOWNLOAD
├── entity_type (VARCHAR(100)) — 대상 타입: projects, routes, structures, outputs
├── entity_id (VARCHAR(50)) — 대상 ID
├── details (LONGTEXT, NULL) — 상세 정보 (JSON 형식)
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── timestamp (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-12. system_audit_logs 테이블 (시스템 감시)
```
system_audit_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id) — 행동 수행자
├── action (VARCHAR(50)) — 행동: LOGIN, LOGOUT, USER_CREATE, COMPANY_CREATE, ROLE_CHANGE
├── resource_type (VARCHAR(50)) — 대상 타입: user, company, project, join_request
├── resource_id (INT, NULL) — 대상 ID
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
├── timestamp (TIMESTAMP) — 기록 시간
└── INDEX (timestamp, user_id) — 성능 최적화
```
### 1-13. system_resources 테이블 (시스템 리소스 모니터링)
```
system_resources
├── id (INT, PK) — 기록 고유 번호
├── cpu_usage_percent (FLOAT, NULL) — CPU 사용률 (%)
├── memory_usage_percent (FLOAT, NULL) — 메모리 사용률 (%)
├── disk_usage_percent (FLOAT, NULL) — 디스크 사용률 (%)
├── active_user_count (INT, NULL) — 활성 사용자 수
├── active_project_count (INT, NULL) — 활성 프로젝트 수
├── total_storage_mb (FLOAT, NULL) — 전체 저장소 사용량 (MB)
├── timestamp (TIMESTAMP) — 기록 시간
└── INDEX (timestamp) — 시계열 조회 최적화
```
**리소스 측정 방식:**
- **현재값 실측:** 요청 시점 실시간 측정 (DB 저장 없이 응답에 포함)
- **이력 조회:** `system_resources` 테이블에서 기본 30일치 시계열 조회
- **활성 지표:** active_user_count는 유효 세션 수, active_project_count는 미삭제 프로젝트 수 실시간 집계
### 1-14. support_requests 테이블 (고객 지원)
```
support_requests
├── id (INT, PK) — 요청 고유 번호
├── user_id (INT, FK → users.id) — 신청자
├── title (VARCHAR(255)) — 제목
├── description (LONGTEXT) — 설명
├── category (VARCHAR(100), NULL) — 카테고리
├── priority (VARCHAR(50), NULL) — 우선순위: LOW, MEDIUM, HIGH, URGENT
├── status (VARCHAR(50)) — 상태: OPEN, IN_PROGRESS, RESOLVED, CLOSED
├── assigned_to (INT, FK → users.id, NULL) — 담당자
├── resolution (LONGTEXT, NULL) — 해결 내용
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 2. 프로젝트 그룹
### 2-1. projects 테이블
```
projects
├── id (CHAR(36), PK) — 프로젝트 고유 ID (UUID)
├── user_id (INT, FK → users.id) — 프로젝트 소유자
├── company_id (INT, FK → companies.id) — 소속 회사
├── name (VARCHAR(255)) — 프로젝트명 (예: "2025년 산불진화임도")
├── region (VARCHAR(100), NULL) — 지역명 (예: "울진군 금강송면")
├── road_type (VARCHAR(100), NULL) — 임도 종류: 간선임도, 지선임도, 산불진화임도, 계류보전
├── project_year (INT, NULL) — 사업 연도 (예: 2025)
├── estimated_length_m (FLOAT, NULL) — 추정 연장 (미터)
├── memo (TEXT, NULL) — 비고/메모
├── status (VARCHAR(50), NULL) — 상태: NEW, FILE_UPLOADED, WF1_ANALYZING, WF1_COMPLETE, ... (기본값: NEW)
├── crs_epsg (INT, NULL) — 좌표계 (예: 5178 = 한국 표준, 기본값: 5178)
├── storage_path (VARCHAR(500), NULL) — 파일시스템 경로 (예: "storage/company/user/project_uuid")
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 2-2. project_versions 테이블 (프로젝트 버전 관리)
```
project_versions
├── id (INT, PK) — 버전 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── version_num (INT) — 버전 번호 (1, 2, 3, ...)
├── status (VARCHAR(50), NULL) — 저장된 상태
├── data (LONGTEXT, NULL) — 설계 데이터 스냅샷 (JSON 형식)
├── snapshot_at (TIMESTAMP) — 스냅샷 시점
└── created_at (TIMESTAMP) — 생성일
```
### 2-3. project_automations 테이블 (프로젝트 자동화 규칙)
```
project_automations
├── id (INT, PK) — 자동화 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── name (VARCHAR(100)) — 자동화 규칙명
├── logic_type (VARCHAR(50)) — 로직 타입: trigger_on_stage_complete, auto_notify, etc
├── config_json (LONGTEXT) — 자동화 설정 (JSON)
├── status (ENUM) — 상태: DRAFT, ACTIVE, INACTIVE (기본값: DRAFT)
├── created_by (INT, FK → users.id) — 생성자
├── updated_by (INT, FK → users.id, NULL) — 마지막 수정자
├── last_executed_at (DATETIME, NULL) — 마지막 실행 시간
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 3. 파일 & 업로드 관리 그룹
### 3-1. upload_sessions 테이블 (파일 업로드 세션)
```
upload_sessions
├── id (INT, PK) — 업로드 세션 고유 번호
├── user_id (INT, FK → users.id) — 업로드자
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── session_id (VARCHAR(255), UNIQUE) — 세션 ID
├── file_name (VARCHAR(255)) — 파일명
├── file_size_mb (FLOAT) — 파일 크기
├── chunk_count (INT) — 청크 개수
├── uploaded_chunks (INT) — 업로드된 청크 수
├── status (VARCHAR(50)) — 상태: PENDING, IN_PROGRESS, COMPLETED, FAILED (기본값: PENDING)
├── started_at (TIMESTAMP) — 시작 시간
├── completed_at (TIMESTAMP, NULL) — 완료 시간
└── expires_at (TIMESTAMP) — 세션 만료 시간
```
### 3-2. upload_chunks 테이블 (파일 청크 관리)
```
upload_chunks
├── id (INT, PK) — 청크 고유 번호
├── upload_session_id (INT, FK → upload_sessions.id) — 업로드 세션 ID
├── chunk_index (INT) — 청크 순서
├── chunk_size_mb (FLOAT) — 청크 크기
├── checksum (VARCHAR(255), NULL) — 체크섬 (무결성 검사)
├── status (VARCHAR(50)) — 상태: PENDING, UPLOADED, VERIFIED (기본값: PENDING)
├── uploaded_at (TIMESTAMP, NULL) — 업로드 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 3-3. input_files 테이블 (원본 입력 파일)
```
input_files
├── id (INT, PK) — 파일 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 속한 프로젝트
├── file_type (VARCHAR(50), NULL) — 파일 종류: las, tif, tfw, prj, dxf, dwg, other
├── original_filename (VARCHAR(255)) — 원래 파일명 (예: "cloud_merged.las")
├── raw_file_path (VARCHAR(500)) — 원본 저장 경로 (예: "storage/.../raw/las/cloud_merged.las")
├── file_size_mb (FLOAT, NULL) — 파일 크기 (MB)
├── upload_by (INT, FK → users.id, NULL) — 업로드한 사용자
├── upload_at (TIMESTAMP) — 업로드 날짜
├── crs_epsg (INT, NULL) — 파일 좌표계 (예: 5178)
├── metadata (LONGTEXT, NULL) — 파일 메타데이터 (JSON)
├── status (VARCHAR(50), NULL) — 상태: UPLOADED, PROCESSED, ARCHIVED (기본값: UPLOADED)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 4. 지표면 분석 & 처리 그룹
### 4-1. processed_point_cloud 테이블 (변환된 포인트클라우드)
```
processed_point_cloud
├── id (INT, PK) — 변환 데이터 고유 번호
├── input_file_id (INT, FK → input_files.id) — 원본 LAS 파일 참조
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── process_type (VARCHAR(50), NULL) — 변환 방식: filtered, sampled, classified
├── processed_file_path (VARCHAR(500), NULL) — 변환된 파일 경로
├── converted_format (VARCHAR(50), NULL) — 변환 포맷: las, ply, laz 등
├── converted_file_path (VARCHAR(500), NULL) — 변환 포맷 저장 경로
├── point_count (INT, NULL) — 포인트 개수
├── min_z (FLOAT, NULL) — 최저 높이
├── max_z (FLOAT, NULL) — 최고 높이
├── mean_z (FLOAT, NULL) — 평균 높이
├── x_min (FLOAT, NULL) — 최소 경도
├── x_max (FLOAT, NULL) — 최대 경도
├── y_min (FLOAT, NULL) — 최소 위도
├── y_max (FLOAT, NULL) — 최대 위도
├── density_per_sqm (FLOAT, NULL) — 단위 면적당 포인트 밀도
├── classification_summary (LONGTEXT, NULL) — 분류 요약 (JSON)
├── processing_params (LONGTEXT, NULL) — 변환 파라미터 (JSON)
├── processed_at (TIMESTAMP) — 변환 완료 날짜
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 4-2. surface_models 테이블 (지표면 모델)
```
surface_models
├── id (INT, PK) — 지표면 모델 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── model_type (VARCHAR(50), NULL) — 모델 종류: dem_grid, tin, mesh_triangulated, contour_lines
├── source_file_id (INT, FK → input_files.id, NULL) — 원본 LAS 파일
├── processed_cloud_id (INT, FK → processed_point_cloud.id, NULL) — 변환된 포인트클라우드 참조
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
├── crs_epsg (INT, NULL) — 좌표계
├── resolution_m (FLOAT, NULL) — 해상도 (래스터인 경우)
├── model_file_path (VARCHAR(500), NULL) — 모델 저장 경로 (예: "storage/.../surface/dem.tif")
├── generation_params (LONGTEXT, NULL) — 생성 파라미터 (JSON)
├── created_at (TIMESTAMP) — 생성일
├── completed_at (TIMESTAMP, NULL) — 완료 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 4-3. terrain_layers 테이블 (지형 레이어)
```
terrain_layers
├── id (INT, PK) — 레이어 고유 번호
├── surface_model_id (INT, FK → surface_models.id) — 지표면 모델 참조
├── layer_name (VARCHAR(100), NULL) — 레이어명 (예: "지표", "제1층", "제2층")
├── geometry_type (VARCHAR(50), NULL) — 기하 종류: POINTCLOUD, GRID, MESH, CONTOUR
├── layer_file_path (VARCHAR(500), NULL) — 레이어 파일 저장 경로
├── file_format (VARCHAR(50), NULL) — 파일 형식: geojson, geotiff, ply, obj 등
├── file_size_mb (FLOAT, NULL) — 파일 크기
├── statistics (LONGTEXT, NULL) — 통계 (JSON)
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 5. 경로 & 설계 그룹
### 5-1. routes 테이블 (경로 설계)
```
routes
├── id (INT, PK) — 경로 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── surface_model_id (INT, FK → surface_models.id, NULL) — 기반 지표면 모델
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED, ARCHIVED (기본값: DRAFT)
├── start_chainage_m (FLOAT, NULL) — 시작점 측점 (m)
├── end_chainage_m (FLOAT, NULL) — 종료점 측점 (m)
├── total_length_m (FLOAT, NULL) — 총 연장 (m)
├── grade_percent (LONGTEXT, NULL) — 각 구간 종단 경사도 (JSON array)
├── constraints (LONGTEXT, NULL) — 설계 제약조건 (JSON)
├── algorithm_params (LONGTEXT, NULL) — 알고리즘 파라미터 (JSON)
├── route_data_path (VARCHAR(500), NULL) — 경로 데이터 저장 경로 (GeoJSON)
├── computed_at (TIMESTAMP, NULL) — 계산 완료 날짜
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
**참고:** 경로 좌표는 JSON 형식으로 `route_data_path` 파일에 저장됨 (GeoJSON LineString)
### 5-2. route_points 테이블 (경로 샘플 포인트)
```
route_points
├── id (INT, PK) — 포인트 고유 번호
├── route_id (INT, FK → routes.id) — 경로 ID
├── chainage_m (FLOAT, NULL) — 측점 (m)
├── elevation_m (FLOAT, NULL) — 높이
├── slope_percent (FLOAT, NULL) — 경사도 (%)
├── sequence_num (INT, NULL) — 순서번호
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 5-3. route_statistics 테이블 (경로 통계)
```
route_statistics
├── id (INT, PK) — 통계 고유 번호
├── route_id (INT, FK → routes.id) — 경로 ID
├── min_slope (FLOAT, NULL) — 최소 경사도 (%)
├── max_slope (FLOAT, NULL) — 최대 경사도 (%)
├── mean_slope (FLOAT, NULL) — 평균 경사도 (%)
├── cut_volume_m3 (FLOAT, NULL) — 절토량 (m³)
├── fill_volume_m3 (FLOAT, NULL) — 성토량 (m³)
├── tree_cutting_volume (FLOAT, NULL) — 목재 절감 추정량
└── cost_score (FLOAT, NULL) — 알고리즘 점수
```
---
## 6. 종단면 & 횡단면 그룹
### 6-1. longitudinal_sections 테이블 (종단면)
```
longitudinal_sections
├── id (INT, PK) — 종단면 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── route_id (INT, FK → routes.id) — 경로 ID
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
│ ├── chainages (측점 배열, m)
│ ├── elevations (지표 표고 배열)
│ ├── grades (경사도 배열, %)
│ └── design_elevations (설계 표고 배열)
├── longitudinal_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 6-2. cross_sections 테이블 (횡단면)
```
cross_sections
├── id (INT, PK) — 횡단면 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── route_id (INT, FK → routes.id) — 경로 ID
├── chainage_m (FLOAT, NULL) — 측점 (예: 0, 20, 40, ... m)
├── sequence_num (INT, NULL) — 횡단면 순번 (1, 2, ...)
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
│ ├── left_slope (좌측 사면 경사, %)
│ ├── right_slope (우측 사면 경사, %)
│ ├── width_m (노폭)
│ ├── cut_volume_m3 (절토량)
│ ├── fill_volume_m3 (성토량)
│ ├── structures (이 단면 내 구조물 ID 배열)
│ └── notes (메모)
├── cross_section_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 7. 구조물 & 수량 그룹
### 7-1. structures 테이블 (구조물)
```
structures
├── id (INT, PK) — 구조물 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── cross_section_id (INT, FK → cross_sections.id, NULL) — 횡단면 ID
├── structure_type (VARCHAR(100), NULL) — 종류: 낙석방지책, 돌붙임, 계간수로, 낙차공 등
├── chainage_m (FLOAT, NULL) — 측점 (m)
├── location (VARCHAR(50), NULL) — 위치: LEFT, CENTER, RIGHT
├── length_m (FLOAT, NULL) — 길이
├── width_m (FLOAT, NULL) — 폭
├── height_m (FLOAT, NULL) — 높이
├── material (VARCHAR(100), NULL) — 재료: 강재, 콘크리트, 목재, 돌 등
├── quantity (INT, NULL) — 개수
├── unit_price (FLOAT, NULL) — 단가
├── design_notes (LONGTEXT, NULL) — 설계 노트 (JSON)
├── structure_data_path (VARCHAR(500), NULL) — 구조물 데이터 저장 경로
├── last_modified_by (INT, FK → users.id, NULL) — 마지막 수정자
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 7-2. quantity_items 테이블 (수량 항목)
```
quantity_items
├── id (INT, PK) — 수량 항목 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── category (VARCHAR(100), NULL) — 대분류: 토공, 구조물, 포장, 배수, 녹화, 안전시설
├── item_name (VARCHAR(255), NULL) — 항목명 (예: "절토 일반", "낙석방지책 설치")
├── unit (VARCHAR(50), NULL) — 단위: m3, 개, m, m2
├── quantity_design (FLOAT, NULL) — 설계 수량
├── quantity_actual (FLOAT, NULL) — 실제 수량 (사용자 수정 가능)
├── unit_price (FLOAT, NULL) — 단가
├── total_price (FLOAT, NULL) — 소계 (quantity_actual × unit_price)
├── standard_reference (VARCHAR(255), NULL) — 기준 참고 자료
├── quantity_data_path (VARCHAR(500), NULL) — 수량 데이터 저장 경로
├── data (LONGTEXT, NULL) — 계산 과정 메모 (JSON)
│ ├── formula (계산식)
│ ├── source (데이터 출처)
│ └── ...
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 8. 산출물 & 문서 그룹
### 8-1. outputs 테이블 (최종 산출물 세트)
```
outputs
├── id (INT, PK) — 산출물 세트 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── output_type (VARCHAR(100), NULL) — 종류: estimation_excel, drawing_dxf, report_pdf, all_bundle
├── status (VARCHAR(50), NULL) — 상태: GENERATING, COMPLETE, FAILED (기본값: GENERATING)
├── generated_by (INT, FK → users.id, NULL) — 생성자
├── generated_at (TIMESTAMP) — 생성 날짜
├── version (INT, NULL) — 버전 (기본값: 1)
├── outputs_directory_path (VARCHAR(500), NULL) — 산출물 저장 폴더 (예: "storage/.../outputs/v1/")
├── metadata (LONGTEXT, NULL) — 메타데이터 (JSON)
│ ├── template_used (사용한 양식)
│ ├── company_name (회사명)
│ ├── project_name (프로젝트명)
│ ├── total_cost (총 비용)
│ └── generation_time_sec (생성 소요 시간)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 8-2. output_files 테이블 (최종 산출 파일)
```
output_files
├── id (INT, PK) — 파일 고유 번호
├── output_id (INT, FK → outputs.id) — 산출물 ID
├── file_type (VARCHAR(50), NULL) — 파일 형식: xlsx, pdf, dxf, dwg, json, zip
├── original_filename (VARCHAR(255), NULL) — 파일명 (예: "견적서_v1.xlsx")
├── output_file_path (VARCHAR(500), NULL) — 저장 경로 (예: "storage/.../outputs/v1/견적서.xlsx")
├── file_size_mb (FLOAT, NULL) — 파일 크기
├── download_count (INT, NULL) — 다운로드 횟수 (기본값: 0)
├── created_at (TIMESTAMP) — 생성 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 9. 변경 이력 & 감시 그룹
### 9-1. change_logs 테이블 (설계 변경 기록)
```
change_logs
├── id (INT, PK) — 변경 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── changed_by (INT, FK → users.id) — 변경자
├── changed_at (TIMESTAMP) — 변경 날짜
├── entity_type (VARCHAR(100)) — 대상 타입: routes, cross_sections, structures, quantity_items
├── entity_id (VARCHAR(50)) — 대상 ID
├── old_value (LONGTEXT, NULL) — 변경 전 값 (JSON)
├── new_value (LONGTEXT, NULL) — 변경 후 값 (JSON)
├── reason (TEXT, NULL) — 변경 사유
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 10. 테이블 관계도
```
users (사용자)
├── id (PK)
├── email
├── password_hash
├── role (SYSTEM_ADMIN | ADMIN | USER)
├── is_master
├── status (생명주기)
├── company_id (FK) ──→ companies.id
├── last_login
└── auth_expires_at
companies (회사)
├── id (PK)
├── name
├── status (ACTIVE | INACTIVE | SUSPENDED)
├── subscription_status (FREE | TRIAL | PAID | EXPIRED)
├── created_by (FK) ──→ users.id
└── created_at
projects (프로젝트)
├── id (UUID, PK)
├── user_id (FK) ──→ users.id
├── company_id (FK) ──→ companies.id
├── name
├── status
├── crs_epsg
├── storage_path
└── created_at
├─→ input_files (입력 파일)
│ └─→ processed_point_cloud (변환된 포인트클라우드)
│ └─→ surface_models (지표면 모델)
│ └─→ terrain_layers (지형 레이어)
├─→ routes (경로)
│ ├─→ route_points (경로 포인트)
│ ├─→ route_statistics (경로 통계)
│ ├─→ longitudinal_sections (종단면)
│ └─→ cross_sections (횡단면)
│ └─→ structures (구조물)
├─→ quantity_items (수량 항목)
├─→ outputs (산출물)
│ └─→ output_files (산출물 파일)
├─→ project_versions (프로젝트 버전)
├─→ project_automations (프로젝트 자동화)
├─→ audit_logs (감시 로그)
└─→ change_logs (변경 로그)
```
---
## 11. 파일시스템 경로와 DB 링크 (워크플로우 기반 구조)
### 파일시스템 구조 (6단계 워크플로우)
```
storage/
├── {company_slug}/
│ └── {user_slug}/
│ └── {project_id}/ ← projects.storage_path
│ │
│ ├── B03_FileInput/ ← WF0: 파일 입력
│ │ └── input/ (원본 입력 파일들)
│ │ ├── las/
│ │ │ └── cloud_merged.las ← input_files.raw_file_path
│ │ ├── tif/
│ │ ├── tfw/
│ │ ├── prj/
│ │ └── dxf/
│ │
│ ├── B04_wf1_Surface/ ← WF1: 지표면 분석
│ │ ├── processed/ (변환된 포인트클라우드)
│ │ │ ├── cloud_filtered.las ← processed_point_cloud.processed_file_path
│ │ │ └── cloud_sampled.ply ← processed_point_cloud.converted_file_path
│ │ └── models/ (지표면 모델 & 레이어)
│ │ ├── dem_2m.tif ← surface_models.model_file_path
│ │ ├── tin_mesh.obj
│ │ ├── layer_ground.geojson ← terrain_layers.layer_file_path
│ │ └── layer_1.geojson
│ │
│ ├── B05_wf2_Route/ ← WF2: 경로 설계
│ │ └── route/
│ │ ├── route_main.geojson ← routes.route_data_path
│ │ ├── route_points.json
│ │ └── route_statistics.json
│ │
│ ├── B06_wf3_ProfileCross/ ← WF3: 종횡단 생성
│ │ ├── longitudinal/
│ │ │ └── longitudinal.json ← longitudinal_sections.longitudinal_file_path
│ │ └── cross_sections/
│ │ ├── cross_0000m.json ← cross_sections.cross_section_file_path
│ │ ├── cross_0020m.json
│ │ └── ...
│ │
│ ├── B07_wf4_DesignDetail/ ← WF4: 상세 설계
│ │ └── structures/
│ │ ├── struct_0020m_001.json ← structures.structure_data_path
│ │ └── ...
│ │
│ ├── B08_wf5_Quantity/ ← WF5: 수량 산출
│ │ ├── quantities/
│ │ │ └── items.json ← quantity_items.quantity_data_path
│ │ └── ...
│ │
│ └── B09_wf6_Estimation/ ← WF6: 견적·문서
│ ├── v1/ ← outputs.outputs_directory_path
│ │ ├── 견적서.xlsx ← output_files.output_file_path
│ │ ├── 설계도.dxf
│ │ └── ...
│ └── v2/
│ └── ...
```
**핵심 개념:**
- **B03~B09**: 워크플로우 6단계와 1:1 대응
- **각 단계의 입력/출력**: 명확히 분리
- **버전 관리**: B09 단계에서만 (v1, v2, ...)
---
## 12. 핵심 쿼리 예제
### 예제 1: 사용자의 모든 프로젝트 조회
```sql
SELECT p.id, p.name, p.status, p.created_at
FROM projects p
WHERE p.user_id = :user_id AND p.deleted_at IS NULL
ORDER BY p.created_at DESC;
```
### 예제 2: 프로젝트의 최신 경로 조회
```sql
SELECT r.id, r.total_length_m, r.status
FROM routes r
WHERE r.project_id = :project_id AND r.deleted_at IS NULL
ORDER BY r.computed_at DESC
LIMIT 1;
```
### 예제 3: 경로의 모든 횡단면 조회
```sql
SELECT cs.id, cs.chainage_m, cs.data
FROM cross_sections cs
WHERE cs.route_id = :route_id AND cs.deleted_at IS NULL
ORDER BY cs.chainage_m;
```
### 예제 4: 프로젝트 총 비용 계산
```sql
SELECT
SUM(quantity_actual * unit_price) as total_cost,
category
FROM quantity_items
WHERE project_id = :project_id AND deleted_at IS NULL
GROUP BY category;
```
### 예제 5: 사용자의 감시 로그 (최근 30일)
```sql
SELECT action, entity_type, timestamp
FROM audit_logs
WHERE user_id = :user_id AND timestamp > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY timestamp DESC;
```
---
## 13. 데이터 타입 가이드
| 타입 | 설명 | 예제 |
|------|------|------|
| INT | 정수 | 1, 2, 100 |
| FLOAT | 실수 | 1.5, 2.25, 100.75 |
| VARCHAR(n) | 문자열 (고정 길이) | "project_name" |
| TEXT / LONGTEXT | 문자열 (가변, 대용량) | 설명문, JSON 데이터 |
| TIMESTAMP | 날짜 + 시간 (자동 업데이트) | 2025-07-05 15:30:00 |
| DATETIME | 날짜 + 시간 (수동) | 2025-07-05 15:30:00 |
| TINYINT(1) | 참/거짓 (0 또는 1) | 0, 1 |
| ENUM | 열거형 | ACTIVE, INACTIVE |
| CHAR(36) | UUID (고정 36자) | 550e8400-e29b-41d4-a716-446655440000 |
---
## 14. 총 테이블 수 및 분류
### 34개 테이블 분류
**1. 사용자/회사/조직 (14개)**
- users, companies, join_requests (향후), email_otps, sessions, trusted_devices, user_consents
- login_logs, activity_logs, audit_logs, system_audit_logs, system_resources, support_requests
**2. 프로젝트 관리 (3개)**
- projects, project_versions, project_automations
**3. 파일/업로드 (3개)**
- upload_sessions, upload_chunks, input_files
**4. 지표면 분석 (3개)**
- processed_point_cloud, surface_models, terrain_layers
**5. 경로 설계 (3개)**
- routes, route_points, route_statistics
**6. 종횡단 설계 (2개)**
- longitudinal_sections, cross_sections
**7. 구조물/수량 (2개)**
- structures, quantity_items
**8. 산출물 (2개)**
- outputs, output_files
**9. 변경/이력 (1개)**
- change_logs
---
## 15. DB vs 파일시스템 역할 분리
| 저장 위치 | 내용 | 예시 |
|----------|------|------|
| **DB (MariaDB)** | 메타데이터 + 경로정보 + 상태 | 파일명, 크기, 좌표계, 상태, 저장경로, 통계 |
| **파일시스템** | 실제 파일 데이터 | LAS, PLY, TIF, DXF, Excel, PDF, JSON 파일들 |
**설계 원칙:**
- DB에는 "어디에 무엇이 있는가"의 메타정보만 저장
- 실제 대용량 파일은 파일시스템에 저장
- 각 워크플로우 단계의 입력/출력 경로를 DB에 기록
- JSON 형식으로 좌표 및 복잡한 데이터 구조를 저장
---
## 16. 마이그레이션 상태
### ✅ 완료됨
- [x] 33개 테이블 생성 (MariaDB)
- [x] 관계(FK) 설정
- [x] Soft delete (deleted_at) 지원
- [x] 인덱스 추가 (주요 조회 경로)
### 📋 진행 중
- [ ] API 라우터에서 파일 경로 저장 로직 추가
- B03 파일 업로드 → `input_files.raw_file_path` 저장
- B04 WF1 실행 → `surface_models.model_file_path`, `terrain_layers.layer_file_path` 저장
- B05 WF2 실행 → `routes.route_data_path` 저장
- B06 WF3 실행 → `longitudinal_sections.longitudinal_file_path`, `cross_sections.cross_section_file_path` 저장
- B07 WF4 실행 → `structures.structure_data_path` 저장
- B08 WF5 실행 → `quantity_items.quantity_data_path` 저장
- B09 WF6 실행 → `outputs.outputs_directory_path`, `output_files.output_file_path` 저장
### 🔮 향후 계획
- [ ] Python Pydantic 모델 정의 (ORM 대신)
- [ ] API 엔드포인트 보안 감시 강화
- [ ] 실시간 알림 시스템 (WebSocket)