This commit is contained in:
2026-07-15 18:33:33 +09:00
parent 6b1583103f
commit f533082537
201 changed files with 19776 additions and 563 deletions
@@ -0,0 +1,86 @@
---
status: stable
related_pages: ["[[A01_Home/A01_frontend]]", "[[A02_ProgDetail/A02_frontend]]", "[[B01_Dashboard/B01_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]"]
last_updated: 2026-07-12
source: A00_Common 공유 프레임워크 소스 코드
---
# A00_Common — App Shell Framework
모든 페이지의 공통 스켈레톤. 헤더(로고/네비/언어·테마/인증버튼) + 콘텐츠 아울렛 + 푸터를 통합 관리.
## 파일 구성
| 파일 | 역할 |
|---|---|
| `app_shell.ts` (284줄) | 헤더·푸터 렌더링, 테마·언어 토글, 인증 상태별 버튼 채우기 |
| `b_page_scaffold.ts` (163줄) | B 그룹(B03~B09) 페이지용 스캐폴드—"준비 중" 임시 페이지 래퍼, 워크플로우 셸 헬퍼 |
| `router.ts` (158줄) | SPA 라우터(해시 기반), 인증 가드, 지연 로딩 |
## app_shell 구성요소
### 테마 & 언어 관리
- `initThemeAndLang()` — localStorage 복원 (THEME_STORAGE_KEY, LANG_STORAGE_KEY)
- `onApp_Theme_Toggle_Click()` — light ↔ dark 전환
- `onApp_Lang_Toggle_Click()` — 한국어 ↔ 영문 전환 + 현재 페이지 재렌더
### 헤더 구성 (64px 높이, sticky)
- **로고**: `BRAND_ICON_URL`(prog_icon.jpg) + 브랜드명("Aislo")
- **네비게이션**: 5개 링크
- App_Nav_Program → A02_PROG_DETAIL
- App_Nav_Company → A03_COMP_DETAIL
- App_Nav_News → A04_NEWS_HISTORY
- App_Nav_Education → A05_EDU_DETAIL
- App_Nav_Support → A08_SUPPORT
- **우측 액션**:
- 언어 버튼 ("EN" / "한")
- 테마 버튼 ("☀" / "☾")
- 인증 버튼 (로그인/회원가입 또는 계정/로그아웃)
### 인증 버튼 (비동기 fillAuthActions)
**미로그인 상태**: "로그인", "회원가입"
**로그인 상태**: 사용자명(또는 "내 계정"), "로그아웃"
### 로그아웃 (onApp_Logout_Click)
- `A06_Login.logout()` 호출 → 세션 쿠키 제거
- 오류 무시 후 홈(`A01_HOME`)으로 이동
### 푸터
- 높이 56px
- 저작권 텍스트 (App_Footer_Copyright)
## router 라우팅 테이블
| 라우트 | 렌더 함수 위치 |
|---|---|
| A01_HOME | A01_Home/A01_Home_UI_Page.ts |
| A02_PROG_DETAIL | A02_ProgDetail/A02_ProgDetail_UI_Page.ts |
| A03_COMP_DETAIL | A03_CompDetail/A03_CompDetail_UI_Page.ts |
| A04_NEWS_HISTORY | A04_NewsHistory/A04_NewsHistory_UI_Page.ts |
| A05_EDU_DETAIL | A05_EduDetail/A05_EduDetail_UI_Page.ts |
| A06_LOGIN | A06_Login/A06_Login_UI_Auth_Page.ts |
| A07_REGISTER | A07_Register/A07_Register_UI_Auth_Page.ts |
| A08_SUPPORT | A08_Support/A08_Support_UI_Page.ts |
| A09_SECURITY | A09_Security/A09_Security_UI_Page.ts |
| B01_ACCOUNT | B01_Dashboard/B01_Dashboard_UI_Page.ts |
| B02_PROJ_REGISTER | B02_ProjRegister/B02_ProjRegister_UI_Page.ts |
| B03_FILE_INPUT | B03_FileInput/B03_FileInput_UI_Page.ts |
| B04_WF1_SURFACE | B04_wf1_Surface/B04_wf1_Surface_UI_Page.ts |
| B05_WF2_ROUTE | B05_wf2_Route/B05_wf2_Route_UI_Page.ts |
| B06_WF3_PROFILE_CROSS | B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Page.ts |
| B07_WF4_DESIGN_DETAIL | B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts |
| B08_WF5_QUANTITY | B08_wf5_Quantity/B08_wf5_Quantity_UI_Page.ts |
| B09_WF6_ESTIMATION | B09_wf6_Estimation/B09_wf6_Estimation_UI_Page.ts |
| B10_PAYMENT | B10_Payment/B10_Payment_UI_Page.ts |
| B11_STATUS | B11_Status/B11_Status_UI_Page.ts |
## 인증 가드
- **PROTECTED_ROUTES** (config_frontend.ts 정의): 토큰 없으면 A06_LOGIN으로 리다이렉트
- **워크플로우 라우트** (B02~B11): 추가 검증
- 사용자 없음 → A06_LOGIN으로 리다이렉트
- company_id 미보유 & SYSTEM_ADMIN 아님 → B01_ACCOUNT로 리다이렉트 (회사 가입 강제)
## 세부 스캐폴드·CSS·종속성
`b_page_scaffold`, CSS 주입, 공통 종속성, 전체 사용처는 [[a00_app_shell_framework_scaffold]]에 분리했다.
@@ -0,0 +1,37 @@
---
type: concept
status: stable
related_pages: ["[[a00_app_shell_framework]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 A00_Common 코드 및 페이지 인제스트 대조 (2026-07-12)
---
# A00_Common — 스캐폴드·CSS·종속성
## b_page_scaffold
| 항목 | 역할 |
|---|---|
| `renderPendingContent` | 비 workflow 페이지용 준비 중 블록 |
| `renderPendingWorkflow` | workflow 셸과 좌·우 준비 중 영역 |
| `workflowSteps()` | 7단계 workflow 라벨 배열 |
## CSS 인젝션
| 항목 | 역할 |
|---|---|
| `injectShellStyles` | 셸 스타일을 `<head>`에 1회 주입 |
| `app-shell-style` | 헤더·아울렛·푸터 스타일 ID |
| theme 변수 | 헤더 64px·푸터 56px·아울렛 최소높이 |
## 종속성
| 항목 | 역할 |
|---|---|
| `ui_template_locale.ts` | 언어 상태·문자열 |
| `ui_template_elements.ts` | 버튼·workflow 셸 |
| `config_frontend.ts` | routes·theme·protected routes |
| `A06_Login_Api_Fetch.ts` | 세션 조회·로그아웃 |
## 사용처
| 페이지 | 역할 |
|---|---|
| A01~A09 | 공통 헤더·푸터·아울렛 |
| B01~B09 | 페이지별 workflow 셸·단계 내비게이션 |
+27
View File
@@ -0,0 +1,27 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]"]
last_updated: 2026-07-12
source: raw/plans/plan_workflow_state_management.md, raw/guidelines/backend.md + 실 코드 확인 2026-07-12
---
# API 공통 (여러 페이지가 공유하는 엔드포인트)
## 공통 오류 응답 포맷 (전 라우터)
```json
{"status": "error", "message": "원인"}
```
→ [[schema_common]]
## 워크플로우 상태 조회
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /api/projects/{project_id}/workflow-state` | `B03_FileInput_Router.py:667` | 단일 프로젝트의 `current_stage``stages[]` 조회 |
| `get_workflow_states_for_projects()` | `B01_Dashboard_Repository.py:83` | [[B01_Dashboard/B01_backend|B01_Dashboard]] 목록용 다중 프로젝트 상태 배치 조회 |
상태 모델과 사용처: [[workflow_state]].
## 폴링 패턴 (legacy workflow.json 설계; 현재 구현은 workflow-state API 사용)
- 클라이언트가 3초마다 `GET /api/projects/{project_id}/workflow` 호출
- 과거 설계의 stale 감지 UX 참고용이며, 실제 상태 조회는 [[workflow_state]] API를 사용한다.
+69
View File
@@ -0,0 +1,69 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/backend.md, raw/guidelines/frontend.md, raw/guidelines/agent.md
---
# 인증 / RBAC
## 세션 인증 (backend.md 6.3)
- 방식: Secure HttpOnly 세션 쿠키 (`session_id`, JWT 아님), `SameSite=Lax`
- 저장소: `sessions` 테이블 → [[db_schema/users_auth]]
- 유효시간: 활동 기준 4시간 또는 절대 12시간 중 먼저 도달하는 쪽
- DB 풀: `autocommit=True` (세션 즉시 조회 가능하도록)
- 프론트: 모든 API 요청에 `credentials: "include"` (frontend.md 5.1)
## OTP / 비밀번호
- OTP 6자리, 5분 유효, 해시 저장(bcrypt/PBKDF2), 재시도 최대 3회(2s/4s/8s 백오프)
- 비밀번호 최소 8자, bcrypt(라운드≥12) 또는 PBKDF2, 평문 저장 절대 금지
- 5회 연속 실패 시 계정 15분 잠금
## 사용자 상태 생명주기
```
PENDING_EMAIL → NO_COMPANY → (PENDING | ACTIVE) → (INACTIVE | REJECTED)
```
| 상태 | 접근 가능 범위 |
|---|---|
| NO_COMPANY | 로그인 가능, B01만 (B02~B11 차단) |
| PENDING | 로그인 가능, B01만 (승인 대기) |
| ACTIVE | 전체 (B01 + B02~B11) |
## 역할 (users.role)
| 역할 | 전체회사조회 | 역할변경 | 팀원관리 | 가입승인 | 프로젝트조회 범위 |
|---|---|---|---|---|---|
| SYSTEM_ADMIN | ✅ | ✅ | ✅ | ✅ | 전체 |
| ADMIN | ✗ | ✗ | ✅(자기회사) | ✅(자기회사) | 회사별 |
| USER | ✗ | ✗ | ✗ | ✗ | 회사별 |
`is_master` 플래그: TRUE=회사 생성자/ADMIN(팀원 승인권), FALSE=일반 팀원
## 권한 검증 헬퍼 (B01_Dashboard)
| 항목 | 위치 | 역할 |
|---|---|---|
| `_can_edit_project()` | `B01_Dashboard_Router.py:79` | 역할·회사 기준 프로젝트 수정 권한 |
| `_can_manage_automation()` | `B01_Dashboard_Router.py:87` | USER 차단과 프로젝트 범위 검사 |
| `_can_edit_user()` | `B01_Dashboard_Router.py:93` | 사용자 수정 범위 검사 |
| `count_company_admins()` | `B01_Dashboard_Repository.py:584` | 마지막 ADMIN 삭제·강등 방지 |
| `canEditProject()` 외 5개 | `B01_Dashboard_UI_Helper.ts:3` | 프론트 표시 제어; 백엔드 권한을 대체하지 않음 |
**원칙:** 백엔드가 모든 수정/삭제/역할변경 API에서 반드시 재검증 (프론트 UI는 표시 제어만, 신뢰 금지). 위반 시 403 `{"status":"error","message":"Access denied"}`.
## 인증 갱신
로그인 시 `users.auth_expires_at = NOW() + 3개월` 자동 연장. 만료 시 재인증 모달.
## 라우팅 가드 (frontend.md 5.2)
B02~B11 진입 시 `fetchDashboardMe()``company_id`+`status` 확인 → NO_COMPANY/PENDING이면 B01로 강제 리다이렉트.
## 사용처 (역참조)
- [[B01_Dashboard/B01_backend|B01_Dashboard]] — 권한 헬퍼 5종, RBAC 접근제어
- [[A06_Login/A06_backend]] — 세션 발급/OTP 재인증, 상태 확인
- [[A07_Register/A07_backend]] — 사용자 상태 생명주기(PENDING_EMAIL→NO_COMPANY)
- [[A09_Security/A09_backend]] — require_master/require_system_admin 권한 데코레이터
## ⚠️ 계획서-실코드 불일치 (A그룹 인제스트 발견)
이 페이지의 세션/OTP 정책은 **계획서 기준**(세션 12h/4h 만료, OTP 5분·재시도3회·5회실패 15분잠금)이나,
실 코드([[A06_Login/A06_backend]])는 `auth_expires_at = NOW()+3개월` 갱신·신규브라우저/주기 재인증 OTP·5회실패시 관리자 알림 메일 방식.
[[A09_Security/A09_frontend]] 약관 텍스트도 계획서 기준(12h/4h)으로 표기됨.
→ 규칙6(실 코드 우선)에 따라 실 정책 검증 후 이 페이지 갱신 필요. 계획 당시 의도로 위 내용은 보존.
+35
View File
@@ -0,0 +1,35 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/structure.md, raw/guidelines/backend.md
---
# 공통 유틸 (common_util/)
> 둘 이상의 페이지가 실제로 공유하는 경로·원자적 파일쓰기·기하 유틸만 이 폴더에 배치 (migration_plan.md 2.2)
| 파일 | 역할 | 사용처 |
|---|---|---|
| `common_util_validate.ts` | 프론트 1차 유효성 검사 (이메일/빈값/최소길이) | 전 페이지 |
| `common_util_auth.py` | 비밀번호·OTP·세션 쿠키·권한 검증 | 전 페이지 → [[auth_rbac]] |
| `common_util_auth_repository.py` | 인증·조직·관리 Raw SQL 저장소 | 인증 관련 |
| `common_util_email.py` | 비동기 SMTP 발송+재시도(최대3회) | OTP/승인 알림 |
| `common_util_email_templates.py` | 인증·조직 알림 이메일 템플릿 | common_util_email.py |
| `common_util_string.ts` | 문자열 유틸 | 미상세 |
| `common_util_storage.py` | 프로젝트·워크플로우 단계별 저장 경로 | → [[storage_paths]] |
| `common_util_json.py` | JSON 원자적 저장 | workflow.json 등 |
| `common_util_workflow.py` | 공유 workflow.json 상태 처리 | ⚠️ [[workflow_state]] 재설계로 대체/정리 예정 |
| `common_util_resource_monitor.py` | 시스템 리소스 계측(2분 주기) + log + DB 기록 | `sample_resources_loop()` → [[B01_Dashboard/B01_backend|B01_Dashboard]] |
| `common_util_format.py` | 포맷 유틸 | 미상세 |
## 이메일 발송 (common_util_email.py)
- 개발: Google Gmail SMTP / 상용: Google Workspace 또는 AWS SES
- 발송 유형: OTP, 가입확인, 팀원승인요청(ADMIN), 승인/거부결과, 회사참여신청(SYSTEM_ADMIN), 인증갱신알림
## 리소스 모니터링 (common_util_resource_monitor.py)
- `main.py` lifespan에서 `asyncio.create_task` 등록/취소
- 계측: `psutil.cpu_percent`, `psutil.virtual_memory().percent`, `shutil.disk_usage`
- 이중 기록: `system_resources` 테이블 (DB) + `log/system_resources.log` (탭 구분 1줄, `.gitignore`)
- 30일 롤링 삭제 (DB `DELETE`, 로그파일 오래된 줄 제거)
@@ -0,0 +1,95 @@
---
type: concept
status: stable
related_pages: ["[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 파일/지표면분석 테이블
← [[overview]]
## upload_sessions (업로드 세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `varchar(36)` | NO | PRI | 세션 UUID |
| `project_id` | `varchar(36)` | NO | MUL | FK→projects.id |
| `original_filename` | `varchar(255)` | NO | | 원본 파일명 |
| `file_size_bytes` / `chunk_size_bytes` | `bigint(20)` | NO | | 파일크기 / 청크크기 |
| `total_chunks` / `completed_chunks` | `int(11)` | - | | 전체 청크수 / 완료 청크수 |
| `status` | `enum('PENDING','IN_PROGRESS','COMPLETED','FAILED')` | YES | MUL | `PENDING` |
| `created_at` / `updated_at` | `timestamp` | YES | | 생성 / 최종수정 일시 |
## upload_chunks (업로드 청크 데이터)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `session_id` | `varchar(36)` | NO | MUL | FK→upload_sessions.id |
| `chunk_index` | `int(11)` | NO | | 청크 인덱스 (0, 1, 2...) |
| `chunk_hash` | `varchar(64)` | YES | | 청크 검증용 체크섬 |
| `size_bytes` | `bigint(20)` | NO | | 청크 용량 |
| `stored_at` | `varchar(255)` | YES | | 임시 파일 저장 경로 |
| `completed_at` | `timestamp` | YES | MUL | 완료 일시 |
## input_files (입력 원본 파일)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `file_type` | `varchar(50)` | YES | | `las`/`tif`/`prj`/`tfw`/`dxf` 등 |
| `original_filename` / `raw_file_path` | `varchar` | NO | | 파일명 / storage 상대경로 |
| `file_size_mb` | `float` | YES | | 파일 크기 (MB) |
| `upload_by` / `upload_at` | - | - | MUL | 업로드 유저 FK / 일시 |
| `crs_epsg` | `int(11)` | YES | | EPSG 좌표계 정보 |
| `metadata` | `longtext` | YES | | 헤더/포인트수 등 분석 JSON |
| `status` | `varchar(50)` | YES | | `UPLOADED` ➡️ `PROCESSED` ➡️ `ARCHIVED` |
## processed_point_cloud (필터/변환 포인트클라우드)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `input_file_id` / `project_id` | - | NO | MUL | FK→input_files / projects |
| `process_type` | `varchar(50)` | YES | | 변환 필터 분류 |
| `processed_file_path` / `converted_file_path` | `varchar` | YES | | 저장 경로 / PLY/LAZ 경로 |
| `converted_format` | `varchar(50)` | YES | | 변환 포맷 |
| `point_count` | `int(11)` | YES | | 포인트 개수 |
| `min_z` / `max_z` / `mean_z` | `float` | YES | | 고도 최저 / 최고 / 평균 |
| `x_min` ~ `y_max` | `float` | YES | | X/Y 좌표 바운딩 박스 범위 |
| `density_per_sqm` | `float` | YES | | ㎡당 점 밀도 |
| `classification_summary` | `longtext` | YES | | ground/unclassified 통계 |
| `processing_params` | `longtext` | YES | | 분석 파라미터 JSON |
| `processed_at` / `status` | - | - | | 변환 일시 / `PROCESSING`\|`COMPLETE`\|`FAILED` |
## surface_models (지표면 모델 및 등고선)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `model_type` | `varchar(50)` | YES | | `dem_grid`/`tin`/`mesh`/`contour` |
| `source_file_id` / `processed_cloud_id` | `int(11)` | YES | MUL | FK→input_files / processed |
| `status` | `varchar(50)` | YES | | `PROCESSING` ➡️ `COMPLETE` / `FAILED` |
| `crs_epsg` / `resolution_m` | - | YES | | EPSG 좌표 / DEM 격자 해상도 |
| `model_file_path` | `varchar(500)` | YES | | 스토리지 상대 경로 |
| `generation_params` | `longtext` | YES | | 격자화 알고리즘 인자 JSON |
| `created_at` / `completed_at` | `timestamp` | - | | 생성 / 완료 일시 |
## terrain_layers (지형 레이어)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `surface_model_id` | `int(11)` | NO | MUL | FK→surface_models.id |
| `layer_name` | `varchar(100)` | YES | | 레이어 영문/한글 식별자 |
| `geometry_type` | `varchar(50)` | YES | | `POINTCLOUD`/`GRID`/`MESH`/`CONTOUR` |
| `layer_file_path` | `varchar(500)` | YES | | 개별 레이어 실 파일 경로 |
| `file_format` / `file_size_mb` | - | YES | | 포맷 (ply, tfw, las 등) / 크기 |
| `statistics` | `longtext` | YES | | 통계 수치 JSON |
| `created_at` | `timestamp` | NO | | 생성 일시 |
## input_files.status 값
`UPLOADED → PROCESSED → ARCHIVED`
## surface_models.status / processed_point_cloud.status 값
`PROCESSING → COMPLETE / FAILED`
관련 함수 위치 → [[B04_wf1_Surface/B04_db]], 사용 라이브러리 → [[dependencies]]
@@ -0,0 +1,102 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/guidelines/backend.md
---
# DB: 로그/모니터링 테이블
← [[overview]]
## login_logs (로그인 시도 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | YES | MUL | FK→users.id |
| `email` | `varchar(255)` | NO | | |
| `status` | `enum('SUCCESS','FAILURE')` | NO | | |
| `failure_reason` / `user_agent` | `varchar(100)` / `text` | YES | | |
| `login_at` | `timestamp` | NO | MUL | `current_timestamp()` |
## activity_logs (사용자 활동 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `action_type` | `varchar(100)` | NO | | 행위 종류 |
| `action_at` | `timestamp` | NO | | `current_timestamp()` |
| `resource_id` / `status` | `varchar(255)` / `enum` | - | | 리소스 ID / SUCCESS\|FAILURE |
| `details` | `longtext` | YES | | JSON 상세 데이터 |
## audit_logs (감사 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | YES | MUL | FK→users.id |
| `action` / `entity_type` | `varchar(100)` | YES | | CRUD/EXPORT/DOWNLOAD 등 |
| `entity_id` | `varchar(36)` | YES | | 대상 UUID 등 |
| `timestamp` / `ip_address` | `timestamp` / `varchar(45)` | - | | 발생시각 / IP 주소 |
| `details` | `longtext` | YES | | JSON 상세 |
## system_audit_logs (프로젝트/조직 변경 감사 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id (행위자) |
| `action` / `resource_type` | `varchar(50)` | - | | PROJECT_CREATE 등 / 종류 |
| `resource_id` | `int(11)` | YES | | |
| `timestamp` | `timestamp` | YES | MUL | `current_timestamp()` |
| `ip_address` / `user_agent` | `varchar(50)` / `text` | YES | | IP 주소 / 브라우저 에이전트 |
## system_admin_logs (시스템 관리자 행위 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `admin_user_id` | `int(11)` | NO | MUL | FK→users.id (관리자 ID) |
| `action_type` / `target_id` | `varchar(100)` / `varchar(255)`| - | | 변경종류(USER_STATUS) / 대상 ID |
| `action_at` | `timestamp` | NO | | `current_timestamp()` |
| `details` | `longtext` | YES | | JSON 형태의 변경 상세 |
## system_resources (시스템 자원 계측)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `timestamp` | `timestamp` | YES | MUL | `current_timestamp()` |
| `cpu_usage_percent` / `memory_usage_percent` | `float` | YES | | |
| `disk_usage_percent` / `total_storage_mb` | `float` | - | | 디스크사용률 / 전체 스토리지 용량 |
| `active_user_count` / `active_project_count` | `int(11)` | NO | | 활성 사용자 / 프로젝트 수 |
## support_requests (기술 지원 요청)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `company_id` | `int(11)` | YES | MUL | FK→companies.id |
| `company_name` / `user_name` | `varchar(255)` | - | | 회사명 / 작성자명 |
| `user_email` / `user_phone` | `varchar(255)` / `varchar(30)` | - | | 이메일 / 전화번호 |
| `subject` / `message` | `varchar(255)` / `text` | NO | | 제목 / 문의 메시지 본문 |
| `status` | `enum('NEW','REVIEWED','RESOLVED','DELETED')` | NO | MUL | `NEW` |
| `reviewed_by` | `int(11)` | YES | MUL | FK→users.id (검토자) |
| `created_at` / `reviewed_at` / `review_notes` | - | - | | 접수 / 검토일시 / 메모 |
## change_logs (설계 변경 이력)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | | FK→projects.id |
| `changed_by` | `int(11)` | YES | MUL | FK→users.id (수정자) |
| `changed_at` | `timestamp` | NO | | `current_timestamp()` |
| `entity_type` / `entity_id` | `varchar(100)` / `int(11)` | YES | | 대상엔티티(routes 등) / 대상 PK |
| `old_value` / `new_value` | `longtext` | YES | | 변경 전 / 후 JSON 스냅샷 |
| `reason` | `varchar(255)` | YES | | 변경 사유 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (SYSTEM_ADMIN 리소스 현황·시스템 로그 섹션), [[A08_Support/A08_backend|A08_Support]] (support_requests INSERT)
## system_audit_logs — 사용처 (B01, B02)
- **프로젝트 생성**: `B02_ProjRegister_Repository.py:92` (`PROJECT_CREATE` 기록 ➡️ [[B02_ProjRegister/B02_backend]])
- **기타 관리 행위**: `B01_Dashboard_Repository.py:245,265` (수정·삭제), `:336,367` (회사 생성), `:609` (감사 로그 조회), `:758,779` (자동화 제어)
## 리소스 API (SYSTEM_ADMIN 전용)
`GET /api/dashboard/admin/resources?days=30`
- 실시간 계측(`current`), 30일 다운샘플링 시계열 조회(`history`, ~4MB ➡️ ~55KB 압축), 활성 지표(`stats`) 제공.
+64
View File
@@ -0,0 +1,64 @@
---
type: concept
status: stable
related_pages: []
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/guidelines/agent.md
---
# DB 스키마 개요
**DB명:** `aislo_db` / MariaDB v10.6+ / utf8mb4_unicode_ci / 드라이버 aiomysql
> ✅ **모순 해결 (2026-07-12, 실 DB 직접 조회 `SHOW TABLES`로 검증):** 실제 운영 DB는 **34개 테이블**. `db_schema.md`(34개)가 정확했고 `agent.md`의 18개/23개는 갱신 안 된 값 — agent.md 쪽 오기로 판정.
>
> **raw 문서에 없던 실제 테이블 2개 발견:**
> - `project_workflow_stages` — [[../workflow_state]] 설계가 **이미 구현되어 실 DB에 존재**함 (컬럼 구성이 계획서와 정확히 일치). status를 draft→stable로 갱신함.
> - `system_admin_logs` — 시스템 관리자(SYSTEM_ADMIN)가 회원/조직 상태(`status`) 등을 변경할 때 감사 이력을 INSERT하는 Write-Only 성격의 테이블 ➡️ [[logs_monitoring]].
>
> ⚠️ **미확정/미연동 테이블 격리 규칙**:
> - 분석 중이거나 소스코드와의 구체적인 연결고리(백엔드/프론트엔드 실사용처)가 아직 확정되지 않은 테이블이 신규 추가될 경우, 상위 공식 그룹 파일에 포함하지 않고 [[unconfirmed/README|미확정 폴더(unconfirmed/)]] 내에 개별 `[테이블명].md` 마크다운 파일로 저장하여 격리 관리합니다.
## 테이블 그룹 (9개)
| 그룹 | 테이블 수 | 상세 페이지 |
|---|---|---|
| 사용자/인증/조직 (핵심) | 7 | [[users_auth]] |
| 로그/모니터링 | 8 | [[logs_monitoring]] |
| 프로젝트 관리 | 3 | [[projects]] |
| 파일/지표면분석 | 6 | [[files_surface]] |
| 경로/종횡단 | 5 | [[route_profile]] |
| 구조물/수량/산출물 | 4 | [[structure_output]] |
| 미확정/연결 대기 테이블 | 0 | [[unconfirmed/README]] |
## 파일 경로 추적 컬럼 (DB에 경로만 기록, 실 파일은 파일시스템)
| 테이블.컬럼 | 용도 | 사용 단계 |
|---|---|---|
| `input_files.raw_file_path` | 원본 입력 파일 | B03 |
| `processed_point_cloud.converted_file_path` | 변환된 포인트클라우드 | B04 |
| `surface_models.model_file_path` | 지표면 모델 | B04 |
| `routes.route_data_path` | 경로 데이터(GeoJSON) | B05 |
| `longitudinal_sections.longitudinal_file_path` | 종단면 | B06 |
| `cross_sections.cross_section_file_path` | 횡단면 | B06 |
| `structures.structure_data_path` | 구조물 배치 | B07 |
| `quantity_items.quantity_data_path` | 수량 항목 | B08 |
| `outputs.outputs_directory_path` | 산출물 폴더 | B09 |
| `output_files.output_file_path` | 개별 산출 파일 | B09 |
상세 경로 규칙 → [[storage_paths]]
## 설계 원칙
- DB에는 "어디에 무엇이 있는가"의 메타정보만 저장. 실제 대용량 파일(LAS/PLY/TIF/DXF/Excel/PDF/JSON)은 파일시스템.
- 공간 기하 데이터(GEOMETRY 타입 미지원) → JSON(GeoJSON 형식)으로 저장, 애플리케이션(Shapely/Geopandas)에서 파싱.
- 모든 주요 테이블에 soft delete(`deleted_at`) 적용.
## 테이블 관계 (핵심 흐름)
```
users ─company_id→ companies
projects ─user_id/company_id→ users/companies
└→ input_files → processed_point_cloud → surface_models → terrain_layers
└→ routes → route_points/route_statistics/longitudinal_sections/cross_sections → structures
└→ quantity_items
└→ outputs → output_files
```
+56
View File
@@ -0,0 +1,56 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B02_ProjRegister/B02_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/plans/plan_workflow_state_management.md
---
# DB: 프로젝트 관리 테이블
← [[overview]]
## projects (프로젝트)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `char(36)` | NO | PRI | 프로젝트 UUID |
| `user_id` / `company_id` | `int(11)` | NO | MUL | FK→users/companies |
| `name` | `varchar(255)` | NO | | 프로젝트명 |
| `region` / `road_type` | `varchar(100)` | YES | | 지역 / 임도종류 |
| `project_year` | `int(11)` | YES | | 사업년도 |
| `estimated_length_m` | `float` | YES | | 계획 연장(m) |
| `memo` | `text` | YES | | 메모 |
| `status` | `varchar(50)` | YES | | `NEW` (호환용 캐시) |
| `crs_epsg` | `int(11)` | YES | | `5178` (좌표계 EPSG) |
| `storage_path` | `varchar(500)` | YES | | 상대경로 ➡️ [[storage_paths]] |
| `created_at` / `updated_at` / `deleted_at` | `timestamp` | - | | 생성 / 수정 / soft-delete |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (목록), [[B02_ProjRegister/B02_backend|B02_ProjRegister]] (생성)
## project_versions (프로젝트 버전 스냅샷)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `version_num` | `int(11)` | NO | | 버전 번호 |
| `status` | `varchar(50)` | YES | | 버전 상태 |
| `data` | `longtext` | YES | | 설계 스냅샷 JSON 데이터 |
| `snapshot_at` / `created_at` | `timestamp` | NO | | 생성일시 |
## project_automations (프로젝트 자동화 정책)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `name` | `varchar(100)` | NO | | 자동화 명칭 |
| `logic_type` | `varchar(50)` | NO | | `trigger_on_stage_complete` 등 |
| `config_json` | `longtext` | NO | | 설정 값 JSON |
| `status` | `enum('DRAFT','ACTIVE','INACTIVE')` | NO | MUL | `DRAFT` |
| `created_by` / `updated_by` | `int(11)` | - | MUL | FK→users.id |
| `last_executed_at` | `datetime` | YES | | 최종 실행 일시 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (자동화 로직 관리, USER는 조회 차단)
## project_workflow_stages (단계별 상세 상태)
- 본 테이블은 워크플로우 7단계(stage 0~6)의 세부 진행도(state, params, progress_percent 등)를 관리하는 SSOT입니다.
- 상세 테이블 정보 및 사용처 ➡️ [[workflow_state]]
@@ -0,0 +1,69 @@
---
type: concept
status: stable
related_pages: ["[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 경로/종횡단 테이블
← [[overview]]
## routes (노선 경로)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `surface_model_id` | `int(11)` | YES | MUL | FK→surface_models.id |
| `status` | `varchar(50)` | YES | | `DRAFT` ➡️ `CONFIRMED` ➡️ `ARCHIVED` |
| `start_chainage_m` / `end_chainage_m` | `float` | YES | | 시작 시점 / 끝 시점 연장(m) |
| `total_length_m` | `float` | YES | | 총 연장(m) |
| `grade_percent` | `longtext` | YES | | 경사도 정보 JSON |
| `constraints` / `algorithm_params` | `longtext` | YES | | 회피 구역 JSON / 알고리즘 가중치 파라미터 JSON |
| `route_data_path` | `varchar(500)` | YES | | 노선 데이터 (GeoJSON) 파일 경로 |
| `computed_at` / `created_at` | - | - | | 연산 완료 일시 / 생성 일시 |
## route_points (경로 좌표점)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `route_id` | `int(11)` | NO | MUL | FK→routes.id |
| `chainage_m` | `float` | YES | | 시점 거리(m) |
| `elevation_m` / `slope_percent` | `float` | YES | | 표고(m) / 종단 경사율(%) |
| `sequence_num` | `int(11)` | YES | | 점 순서 |
## route_statistics (노선 통계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `route_id` | `int(11)` | NO | MUL | FK→routes.id |
| `min_slope` / `max_slope` / `mean_slope` | `float` | YES | | 최저 / 최고 / 평균 경사(%) |
| `cut_volume_m3` / `fill_volume_m3` | `float` | YES | | 절토량(㎥) / 성토량(㎥) |
| `tree_cutting_volume` / `cost_score` | `float` | YES | | 벌채량 / 평점 비용 스코어 |
## longitudinal_sections (종단면 설계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `route_id` | `char(36)` / `int` | NO | MUL | FK→projects / routes |
| `computed_at` | `timestamp` | YES | | 종단 계산 일시 |
| `data` | `longtext` | YES | | 지반고/계획고/구배 등 설계 데이터 JSON |
| `longitudinal_file_path` | `varchar(500)` | YES | | 종단도면 캐드 파일 경로 |
| `status` | `varchar(50)` | YES | | `DRAFT` / `CONFIRMED` |
## cross_sections (횡단면 설계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `route_id` | `char(36)` / `int` | NO | MUL | FK→projects / routes |
| `chainage_m` / `sequence_num` | - | YES | | 측정 체인 거리(m) / 단면 순서 |
| `data` | `longtext` | YES | | 좌우 사면 경사, 폭, 절성토 단면적 JSON |
| `cross_section_file_path` | `varchar(500)` | YES | | 횡단도면 캐드 파일 경로 |
| `status` | `varchar(50)` | YES | | `DRAFT` / `CONFIRMED` |
## routes.status 흐름
`DRAFT → CONFIRMED → ARCHIVED`
## cross_sections.data.structures
횡단면 하나에 배치된 구조물 ID 배열 → [[structure_output]]#structures 참조
@@ -0,0 +1,70 @@
---
type: concept
status: stable
related_pages: ["[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 구조물/수량/산출물 테이블
← [[overview]]
## structures (배치 구조물)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `cross_section_id` | - | - | MUL | FK→projects / cross_sections |
| `structure_type` | `varchar(100)` | YES | | 낙석방지책 / 계간수로 / 돌붙임 등 |
| `chainage_m` | `float` | YES | | 구조물 배치 체인 시점 거리(m) |
| `location` | `varchar(50)` | YES | | 좌 / 우 / 종단 등 배치 위치 |
| `length_m` / `width_m` / `height_m` | `float` | YES | | 길이 / 폭 / 높이 규격(m) |
| `material` | `varchar(100)` | YES | | 구조물 재질/자재 |
| `quantity` / `unit_price` | - | YES | | 수량 / 단가 |
| `design_notes` | `longtext` | YES | | 특기사항 등 메모 |
| `structure_data_path` | `varchar(500)` | YES | | 세부 구조 상세 데이터 파일 경로 |
| `last_modified_by` | `int(11)` | YES | MUL | FK→users.id (최종 수정자) |
| `created_at` / `updated_at` | `timestamp` | - | | 생성 / 수정 일시 |
## quantity_items (수량 산출 항목)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `category` | `varchar(100)` | YES | | 토공 / 구조물 / 포장 / 배수 등 공종 |
| `item_name` / `unit` | `varchar` | YES | | 품목 항목명 / 단위 (㎡, m 등) |
| `quantity_design` / `quantity_actual` | `float` | YES | | 설계 수량 / 최종 반영 수량 |
| `unit_price` / `total_price` | `float` | YES | | 단가 / 총 금액 |
| `standard_reference` | `varchar(255)` | YES | | 품셈 근거 코드/출처 |
| `computed_at` / `quantity_data_path` | - | YES | | 계산 일시 / 상세 산출근거 데이터 파일 경로 |
| `data` | `longtext` | YES | | 계산 세부 과정 통계 JSON |
## outputs (최종 견적/도면 산출 세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `output_type` | `varchar(100)` | YES | | `estimation_excel`/`drawing_dxf`/`report_pdf`/`all_bundle` |
| `status` | `varchar(50)` | YES | | `GENERATING` ➡️ `COMPLETE` / `FAILED` |
| `generated_by` / `generated_at` | - | - | MUL | 생성자 FK / 생성일시 |
| `version` | `int(11)` | YES | | 산출 버전 |
| `outputs_directory_path` | `varchar(500)` | YES | | 산출 파일 번들 디렉토리 경로 |
| `metadata` | `longtext` | YES | | 기타 도면 스케일 등 정보 JSON |
## output_files (개별 산출 파일 리스트)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `output_id` | `int(11)` | NO | MUL | FK→outputs.id |
| `file_type` | `varchar(50)` | YES | | `xlsx`/`pdf`/`dxf`/`dwg`/`json`/`zip` 등 |
| `original_filename` | `varchar(255)` | YES | | 원본 파일 식별 이름 |
| `output_file_path` | `varchar(500)` | YES | | 개별 파일 저장 경로 |
| `file_size_mb` / `download_count` | - | YES | | 파일 용량 / 다운로드 누적 카운트 |
| `created_at` | `timestamp` | NO | | 생성일시 |
## quantity_items 총비용 계산 예
```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;
```
@@ -0,0 +1,7 @@
# 미확정 테이블 보관소 (Unconfirmed DB Schemas)
본 디렉토리는 소스코드 연동 처리가 완료되지 않았거나, 비즈니스 로직(백엔드/프론트엔드) 상의 구체적인 연결고리가 식별되지 않은 미확정 테이블의 명세서 및 임시 분석 내용을 개별 마크다운 파일로 저장·격리하는 공간입니다.
## 운영 규칙
1. **임시 격리**: 신규 테이블 추가 또는 레거시 테이블 중 사용처를 즉시 규명하기 어려운 경우 `[테이블명].md`로 파일을 생성해 기록합니다.
2. **확정 이관**: 소스코드에서의 실제 사용처(라우터, 리포지토리 등)가 명확해지면, 상위의 공식 테이블 그룹(예: `users_auth.md`, `logs_monitoring.md` 등)으로 표 명세를 이관하고 본 폴더 내의 마크다운은 삭제합니다.
@@ -0,0 +1,92 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 사용자/인증/조직 테이블
← [[overview]]
## users (사용자)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `email` | `varchar(255)` | NO | UNI | |
| `password_hash` | `varchar(255)` | NO | | |
| `name` | `varchar(255)` | NO | | |
| `position` / `department` | `varchar(100)` | YES | | 직위 / 부서 |
| `phone` | `varchar(20)` | YES | | |
| `company_id` | `int(11)` | YES | MUL | FK→companies.id |
| `role` | `enum('SYSTEM_ADMIN','ADMIN','USER')` | NO | MUL | `USER` |
| `is_master` | `tinyint(1)` | NO | | `0` (마스터 여부) |
| `status` | `enum('PENDING_EMAIL','NO_COMPANY','PENDING','ACTIVE','INACTIVE','REJECTED')` | NO | MUL | `PENDING_EMAIL` |
| `auth_expires_at` / `last_login` | `datetime` | YES | | 인증만료 / 최종로그인 |
| `login_failures` | `int(11)` | NO | | `0` (실패 횟수) |
| `created_at` / `updated_at` / `deleted_at` | `timestamp` | - | | 생성 / 수정 / soft-delete |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (사용자 관리), 전 페이지 공통 인증
## companies (회사)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `name` | `varchar(255)` | NO | UNI | 회사명 |
| `business_registration_number` | `varchar(20)` | YES | UNI | 사업자번호 |
| `status` | `enum('ACTIVE','INACTIVE','SUSPENDED')` | NO | | `ACTIVE` |
| `subscription_status` | `enum('FREE','TRIAL','PAID','EXPIRED')` | NO | | `FREE` |
| `created_by` | `int(11)` | YES | MUL | FK→users.id (생성자) |
| `company_code` | `varchar(20)` | YES | | 참여용 코드 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]]
## sessions (세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `varchar(255)` | NO | PRI | 세션 ID (쿠키 값) |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `user_agent_hash` | `char(64)` | NO | | |
| `expires_at` | `timestamp` | NO | MUL | 만료일시 |
| `last_activity_at` | `timestamp` | NO | | 최종 활동일시 |
**사용처:** 전 페이지 공통 (세션 인증) — 상세 → [[auth_rbac]]
## email_otps (이메일 OTP)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `purpose` | `enum('REGISTER','LOGIN','REVERIFY','PASSWORD_CHANGE')` | NO | | 용도 |
| `otp_hash` | `varchar(255)` | NO | | OTP 해시값 |
| `expires_at` / `consumed_at` | `timestamp` | - | | 만료일시 / 사용일시 |
## trusted_devices (신뢰 기기)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `user_agent_hash` | `char(64)` | NO | | 브라우저 지문 해시 |
| `verified_at` / `last_used_at` | `timestamp` | NO | | 검증 / 최종 사용 일시 |
## join_requests (회사 가입 신청)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` / `company_id` | `int(11)` | NO | MUL | FK→users/companies |
| `status` | `enum('PENDING','APPROVED','REJECTED')` | NO | | `PENDING` |
| `requested_at` / `reviewed_at` | `timestamp` | - | | 신청 / 검토 일시 |
| `reviewed_by` | `int(11)` | YES | MUL | FK→users.id (검토자) |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (`B01_Dashboard_Repository.py:378` 가입 신청·처리)
## user_consents (약관 동의)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `terms_version` | `varchar(20)` | NO | | 동의한 약관 버전 |
| `terms_agreed` / `privacy_agreed` | `tinyint(1)` | NO | | 필수 동의여부 (1=동의) |
| `marketing_agreed` | `tinyint(1)` | NO | | `0` (선택 마케팅동의) |
| `agreed_at` | `timestamp` | NO | | 동의 일시 |
+36
View File
@@ -0,0 +1,36 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/agent.md, raw/guidelines/migration_plan.md
---
# 외부 라이브러리 의존성
## 백엔드 (Python 3.12.7)
| 라이브러리 | 용도 | 사용 페이지 |
|---|---|---|
| FastAPI / Pydantic | API 프레임워크 / 요청·응답 검증 | 전체; [[B01_Dashboard/B01_backend|B01_Dashboard]] 라우터·요청 모델 |
| Trimesh | 3D 메시 처리 | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] (모델 생성) |
| Whitebox | 지형 분석(D8 흐름누적 등) | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] (skeleton) |
| Geopandas | 공간 데이터프레임 | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| Shapely | 기하 연산 (JSON 파싱 후) | [[B05_wf2_Route/B05_backend|B05_wf2_Route]], [[B06_wf3_ProfileCross/B06_backend|B06_wf3_ProfileCross]] |
| NumPy | 격자·통계·비용면 및 LAS 통계 계산 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| SciPy | 수치 보간·계산 보조 | [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| Rasterio | 래스터(DEM/TIF) 입출력·메타데이터 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] |
| Laspy | LAS/LAZ 포인트클라우드 파싱 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] |
| Pyproj | PRJ/TIF 좌표계 해석 | [[B03_FileInput/B03_backend|B03_FileInput]] |
| aiomysql | 비동기 MariaDB 드라이버 (Raw SQL) | 전체 |
| psutil | CPU·메모리 사용률 계측 | [[B01_Dashboard/B01_backend|B01_Dashboard]] 리소스 현황 |
## 프론트엔드 (TypeScript/Node.js)
| 항목 | 용도 | 사용처 |
|---|---|---|
| **three.js** (WebGL) | 3D 뷰어(WebGLRenderer, OrbitControls, Points/Mesh) | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] 구현 확인. B05/B06은 각 페이지에서 미구현 확인 |
| Vite | 빌드 도구 | 전 프론트 |
> ⚠️ B04는 three.js 기반 3D 뷰어가 구현되어 있고, B05/B06은 현재 문서 기준 WebCAD 노선·프로필 뷰어가 미구현이다.
## 선정 원칙 (agent.md 3절)
프로젝트에 맞춰 호환 라이브러리 선정, 불필요한 함수 작성 최소화. `asyncmy` 대신 `aiomysql` 채택 이유: Cython 빌드가 Windows 환경에서 불필요.
+19
View File
@@ -0,0 +1,19 @@
---
type: concept
status: stable
related_pages: []
last_updated: 2026-07-12
source: raw/guidelines/backend.md
---
# 공통 스키마 (Pydantic 요청/응답 규칙)
## 검증 원칙 (backend.md 4절)
- 모든 JSON 요청은 라우터 진입 전 Pydantic 모델로 타입·범위 검증.
- 모든 지형/메쉬 연산은 `try-except` 필수.
- 에러 응답 표준 포맷: `{"status": "error", "message": "원인"}` — 전 라우터 공통, 프론트도 동일 포맷 기대.
## 명명 규칙
파일명 `[폴더명]_Schema.py`, 페이지 폴더 내부 배치 (전역 공유 스키마 폴더 없음 — 필요 시 여기에 항목 추가).
> 현재 raw 자료에서 페이지 간 공유되는 구체적 Pydantic 모델(예: 공통 페이지네이션, 공통 좌표 타입)은 확인되지 않음. 발견 시 이 페이지에 추가.
+36
View File
@@ -0,0 +1,36 @@
---
type: concept
status: stable
related_pages: ["[[B02_ProjRegister/B02_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]", "[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/guidelines/structure.md, raw/guidelines/db_schema.md, raw/guidelines/backend.md, raw/guidelines/agent.md
---
# 저장 경로 규칙 (Workflow-based Folder Structure)
## 경로 패턴
```
storage/{company_slug}/{user_slug}/{project_id}/
├── B03_FileInput/input/{file_type}/ WF0 원본 입력
├── B04_wf1_Surface/processed/, models/ WF1 변환·모델
├── B05_wf2_Route/route/ WF2 경로
├── B06_wf3_ProfileCross/longitudinal/, cross_sections/ WF3 종횡단
├── B07_wf4_DesignDetail/structures/ WF4 구조물
├── B08_wf5_Quantity/quantities/ WF5 수량
└── B09_wf6_Estimation/v1/, v2/, ... WF6 산출물 (버전은 여기서만)
```
## 원칙 (backend.md 3절)
1. **하드코딩 금지** — 경로는 `config/config_system.py`에서 import.
2. **물리 파일은 DB 저장 금지** — 대용량 파일은 파일시스템, DB에는 경로만.
3. **단계별 루트 강제** — 각 B0x 폴더명은 실제 워크플로우 페이지명과 동일해야 함. `raw/`, `processed/`, `computed/`를 프로젝트 루트 공용 폴더로 만들지 않음.
4. **경로 생성 책임 분리** — 페이지별 백엔드는 자기 단계 폴더만 생성·수정. 다른 단계 산출물을 직접 삭제·덮어쓰지 않고 stale로 전파 → [[workflow_state]].
5. **상대 경로만 DB 저장** — 프로젝트 루트 기준 상대경로. 사용자 입력 경로 직접 결합 금지(경로 이탈 방지), 절대경로 저장 금지.
## DB 컬럼 ↔ 실제 경로 매핑
상세 표 → [[db_schema/overview]] "파일 경로 추적 컬럼"
## 파일명 규칙 (structure.md 1절)
- 페이지 폴더: 로그인 전 `A0x_`, 로그인 후 `B0x_`, 워크플로우는 `wf[번호]` 접두사
- 파일명: `폴더명(페이지명)_기능_기타사양.확장자`
- 공통 유틸: `common_util/common_util_기능.확장자`
+85
View File
@@ -0,0 +1,85 @@
---
status: stable
related_pages: ["[[A01_Home/A01_frontend]]", "[[A02_ProgDetail/A02_frontend]]", "[[B01_Dashboard/B01_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]"]
last_updated: 2026-07-12
source: ui_template/ 공통 UI 소스 코드 및 스타일시트
---
# UI Templates — Localization & Components
프론트엔드 공통 UI 요소: 다국어(i18n), 공통 컴포넌트, 테마 스타일. 디자인 시스템: [[design.md]]
## ui_template_locale.ts (다국어 관리)
**파일**: `ui_template/ui_template_locale.ts`
### 핵심 기능
| 항목 | 위치 | 역할 |
|---|---|---|
| `currentLanguageIndex` | `ui_template/ui_template_locale.ts:12` | 현재 활성화된 언어 인덱스 (ko=0, en=1) |
| `ui_locales` | `ui_template/ui_template_locale.ts:25` | 다국어 문구가 등록된 2차원 사전 객체 |
| `t(key)` | `ui_template/ui_template_locale.ts:152` | 지정된 키에 대응하는 현재 언어 텍스트 반환 헬퍼 |
### 사용처 (역참조)
- [[A01_Home/A01_frontend]] — MOCK_NEWS 태그, 섹션 제목
- [[A02_ProgDetail/A02_frontend]] — 워크플로우 스텝, 기술 카드
- [[a00_app_shell_framework]] — 헤더 네비, 언어 토글, 버튼 라벨
- 모든 A0x/B0x 페이지 — 동적 텍스트 로드
## ui_template_elements.ts (공통 컴포넌트)
**파일**: `ui_template/ui_template_elements.ts`
### 제공 컴포넌트
| 항목 | 위치 | 역할 |
|---|---|---|
| `createButton(opts)` | `ui_template/ui_template_elements.ts:60` | 버튼. variant: `filled`(기본)/`ghost`/`pill`/`danger` |
| `createInputField(opts)` | `ui_template/ui_template_elements.ts:102` | 입력필드(라벨+입력+에러슬롯), setError 핸들 반환 |
| `createSelectField(opts)` | `ui_template/ui_template_elements.ts:164` | 드롭다운 select 필드 |
| `createCard(opts)` | `ui_template/ui_template_elements.ts:209` | 카드(title + body 배열, raised 옵션) |
| `createTag(label, variant?)` | `ui_template/ui_template_elements.ts:228` | 태그. variant: `accent`/`neutral`(기본)/`success`/`warning`/`danger` |
| `showLoadingOverlay()` | `ui_template/ui_template_elements.ts:253` | 로딩 스피너 오버레이 표시(참조카운트) |
| `hideLoadingOverlay()` | `ui_template/ui_template_elements.ts:259` | 로딩 오버레이 해제 |
| `showToast(msg, kind?, ms?)` | `ui_template/ui_template_elements.ts:282` | 토스트 알림. kind: success/error/info/warning |
| `createWorkflowShell(opts)` | `ui_template/ui_template_elements.ts:315` | 워크플로우 3단 레이아웃(헤더+좌측패널+우측뷰어) |
| `createLineChart(opts)` | `ui_template/ui_template_elements.ts:402` | 시계열 스플라인 차트(인라인 SVG, 외부 라이브러리 없음) |
| `injectBaseStyles()` | `ui_template/ui_template_elements.ts:843` | 공통 컴포넌트 기본 스타일 1회 주입 |
### 사용처 (역참조)
- [[A01_Home/A01_frontend]] — createButton, createCard, createTag
- [[A02_ProgDetail/A02_frontend]] — createButton, createCard
- [[A03_CompDetail/A03_frontend]] — createCard
- [[A04_NewsHistory/A04_frontend]] — createTag
- [[A05_EduDetail/A05_frontend]] — createButton, createCard
- [[a00_app_shell_framework]] — createButton(헤더 네비/인증), createWorkflowShell
- B그룹 모든 페이지 — createWorkflowShell, createLineChart(B01 대시보드)
## theme.css (스타일 변수)
**파일**: `ui_template/theme.css`
### 핵심 변수
| 항목 | 위치 | 역할 |
|---|---|---|
| `--color-primary`, `--color-primary-text` | `ui_template/theme.css:10` | 기본 색상 및 기본 텍스트 색상 |
| `--color-canvas`, `--color-paper` | `ui_template/theme.css:15` | 기본 캔버스 배경 및 레이어 카드 배경 |
| `--color-deep-iris`, `--color-mist-violet` | `ui_template/theme.css:22` | 라벤더 그라디언트 및 브랜드 서브 컬러 |
| `--spacing-8`, `--spacing-16` 등 | `ui_template/theme.css:45` | 기본 마진/패딩 간격 시스템 (8px 배수) |
| `--text-heading-lg`, `--text-body` 등 | `ui_template/theme.css:70` | 타이포그래피 크기 및 라인하이트 스케일 |
| `--radius-cards`, `--radius-icons` | `ui_template/theme.css:110` | 8px 등의 둥근 테두리 곡률 반지름 변수 |
| `--z-header` | `ui_template/theme.css:125` | 전역 헤더 고정용 z-index 인덱스 값 |
## 제약 (backend.md §1)
1. **다국어**: 모든 UI 문구는 반드시 `ui_locales` 등록 후 `L(key)` 또는 `t(key)`로만 참조
2. **스타일**: CSS는 `theme.css` 변수만 사용, 하드코드 색상/단위 금지
3. **컴포넌트 재사용**: `createButton`, `createCard`, `createTag`, `createWorkflowShell` 우선 사용
4. **명명**: 이벤트 핸들러는 `on[페이지]_[기능]_[액션]` 규칙 준수
## 의존성
- **Locale**: 모든 페이지에서 `currentLanguageIndex` 조회
- **요소**: 페이지별로 필요한 컴포넌트만 import
+74
View File
@@ -0,0 +1,74 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B02_ProjRegister/B02_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]", "[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/plans/plan_workflow_state_management.md (계획, 2026-07-10) + 실 코드/DB 직접 확인 (2026-07-12)
---
# Workflow 상태 관리 — 구현 완료 (실 DB 확인됨)
> **status: stable.** 2026-07-12 실 코드 전수 확인 + DB 직접 접속(`SHOW TABLES`/`DESCRIBE`)으로 검증. 계획서(2026-07-10)의 설계가 **B01~B06 전 단계에 이미 적용 완료**됨. 정식 검증보고서는 없지만 코드·DB 실측이 계획과 일치해 stable로 채택. 남은 잔재 1건은 아래 참고.
## SSOT: `project_workflow_stages` 테이블 (실 DB 확인)
| 컬럼 | 타입 |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| id | INT PK |
| project_id | CHAR(36), FK |
| stage_no | TINYINT (0~6) |
| stage_key | VARCHAR(30) — FILE_INPUT/WF1_SURFACE/WF2_ROUTE/WF3_PROFILE_CROSS/WF4_DESIGN_DETAIL/WF5_QUANTITY/WF6_ESTIMATION |
| state | ENUM('NOT_STARTED','IN_PROGRESS','COMPLETE','FAILED','STALE') |
| progress_percent | TINYINT |
| params | LONGTEXT(JSON) — 재실행 시 폼 프리필용 |
| message, started_at, completed_at, updated_at | 진행/이력 |
생성 SQL: `db_management/006_workflow_state.sql`, 백필: `db_management/migrate_workflow_state.py` (기존 프로젝트 `projects.status` 해석 후 시드)
## 공통 유틸 `common_util/common_util_workflow_state.py`
| 함수 | 역할 |
|---|---|
| `initialize_project_stages(cursor, project_id)` | 프로젝트 생성 시 7행 NOT_STARTED 시드 |
| `start_stage(cursor, project_id, stage_no, params)` | IN_PROGRESS 전이 + N+1~6의 COMPLETE를 STALE로 전파 + `projects.status` 캐시 갱신 |
| `complete_stage(cursor, project_id, stage_no)` | COMPLETE 전이 (stage 6 완료 시 `projects.status='DONE'`) |
| `fail_stage(cursor, project_id, stage_no, message)` | FAILED 전이 |
| `update_stage_progress(cursor, project_id, stage_no, progress)` | 진행률만 갱신 |
| `get_workflow_state(cursor, project_id)` | 단건 조회, `current_stage` 계산 포함 |
## 실 연동 현황 (파일:줄번호)
| 페이지 | 위치 | 연동 stage_no |
|---|---|---|
| [[B02_ProjRegister/B02_backend|B02_ProjRegister]] | `B02_ProjRegister_Repository.py:90` `initialize_project_stages` 호출 | 시드(0~6) |
| [[B03_FileInput/B03_backend|B03_FileInput]] | `B03_FileInput_Router.py:175,224,256,347,570` | stage 0, 1 |
| [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] | `B04_wf1_Surface_Router.py:168,209,227,233` | stage 1 |
| [[B05_wf2_Route/B05_backend|B05_wf2_Route]] | `B05_wf2_Route_Router.py:51,96,112,117,122,128,152` | stage 2 |
| [[B06_wf3_ProfileCross/B06_backend|B06_wf3_ProfileCross]] | `B06_wf3_ProfileCross_Router.py:68,111,127,132,137,143,187` | stage 3 |
| [[B01_Dashboard/B01_backend|B01_Dashboard]] | `B01_Dashboard_Repository.py:83` `get_workflow_states_for_projects()` (배치 조회, 프로젝트 목록에 `workflow_state` 필드 포함) | 조회 전용 |
## 조회 API (실제 존재, 계획서와 경로 일치)
`GET /{project_id}/workflow-state``B03_FileInput_Router.py:667`
```json
{"status": "success", "workflow_state": {"project_id": "...", "current_stage": 2, "stages": [...]}}
```
실제 전체 경로는 B03 라우터 prefix를 포함한 `/api/projects/{project_id}/workflow-state`이다.
## 상태 전이 (구현대로)
```
NOT_STARTED → IN_PROGRESS → COMPLETE
└→ FAILED → (재실행) IN_PROGRESS
COMPLETE → (하위 단계 재작업) → STALE → (재실행) IN_PROGRESS
```
stage N이 IN_PROGRESS 전이 시 N+1~6의 COMPLETE를 전부 STALE로 전파 (`start_stage` 내부 UPDATE 쿼리로 확인됨).
## projects.status 문자열 캐시 (호환용, 계획서 방침대로 유지 중)
`start_stage`/`complete_stage`/`fail_stage``project_workflow_stages``projects.status`**동시에** 갱신 (`WF{n}_ANALYZING`/`WF{n}_COMPLETE`/`WF{n}_FAILED`, stage6 COMPLETE 시 `DONE`).
## B04 상태 조회 — 레거시 폴백 (당초 "잔재"로 표기했으나 정정)
`get_wf1_analysis_status()`([[B04_wf1_Surface/B04_backend]], `B04_wf1_Surface_Router.py:372`)는
`project_workflow_stages`(SSOT)를 **우선** 조회하고, 행이 없을 때만(백필 미작동 대비)
`projects.status` 문자열 비교로 폴백함(416 `WF1_COMPLETE`, 420 `WF1_ANALYZING`, `:412-427`).
→ B04 실 코드 확인 결과 **정상 경로가 아닌 방어적 폴백**. SSOT 안정화 후 제거 가능하나 현재는 안전장치로 유효.
별개로 `update_project_status()`(`:76`)는 정의만 있고 미호출(죽은 코드) → 정리 권장.
## 미해결 사항 (계획서 원문, 아직 결정 안 됨)
- `projects.status` 완전 제거 여부 — 현재는 유지 중(위 확인)
- STALE 하위 결과 파일: 즉시 삭제 vs 재실행 시 덮어쓰기 — 코드 레벨 확인 안 함, 별도 점검 필요