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,37 @@
---
type: page-frontend-detail
status: stable
page_id: A01_Home
related_pages: ["[[A01_Home/A01_frontend]]", "[[ui_templates]]", "[[a00_app_shell_framework]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 A01_Home 코드 인제스트 보완 (2026-07-12)
---
# A01_Home — 세부 구현
## 스타일
| 항목 | 역할 |
|---|---|
| `.a01-home`, `.a01-hero` | 페이지 래퍼·히어로 배경 |
| `.a01-news-list`, `.a01-news-row` | 뉴스 목록과 행 레이아웃 |
| `.a01-feature-grid`, `.a01-feature-card` | 3열 기능 카드 그리드 |
| 900px 이하 | 기능 그리드 1열·히어로 제목 축소 |
## 의존성
| 항목 | 역할 |
|---|---|
| [[a00_app_shell_framework]] | `navigateTo()` 라우팅 |
| `config_frontend.ts` | `ROUTES` 상수 |
| [[ui_templates]] | 공통 버튼·카드·태그·다국어 |
## 데이터 흐름
| 단계 | 흐름 |
|---|---|
| 1 | `renderA01Home(root)``buildHero()`·`buildNews()`·`buildFeatures()` |
| 2 | `buildNews()`는 현재 `MOCK_NEWS` 사용 |
| 3 | CTA 클릭 → A07_Register 또는 A02_ProgDetail 이동 |
## 미해결
| 항목 | 상태 |
|---|---|
| `GET /api/a01/news` | 백엔드 미구현; 현재 뉴스는 하드코딩 |
+60
View File
@@ -0,0 +1,60 @@
---
status: stable
page_id: A01_Home
related_pages: ["[[A02_ProgDetail/A02_frontend]]", "[[A04_NewsHistory/A04_frontend]]", "[[a00_app_shell_framework]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# A01_Home — Frontend
로그인 전 01: 홈 페이지. 소개 히어로 + 최신 소식 + 주요 기능 3가지를 showcase하는 정적 소개 페이지. 백엔드 없음.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A01_Home_UI_Page.ts` | `A01_Home/A01_Home_UI_Page.ts:1-157` | 페이지 렌더링 로직 |
| `A01_Home_UI_Style.css` | `A01_Home/A01_Home_UI_Style.css:1-113` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트
### Hero 섹션
- **함수:** `buildHero()` (A01_Home_UI_Page.ts:52-85)
- **구성:** 제목 + 부제목 + 2개 CTA 버튼 (회원가입/프로그램 상세)
- **색상:** 라벤더 그라디언트 배경 (`--color-mist-violet``--color-canvas`)
### 최신 소식 섹션
- **함수:** `buildNews()` (A01_Home_UI_Page.ts:87-116)
- **데이터:** MOCK_NEWS 배열 (A01_Home_UI_Page.ts:32-36, 백엔드 연결 전)
- **각 항목:** 태그 + 뉴스 제목 + 날짜
### 주요 기능 섹션
- **함수:** `buildFeatures()` (A01_Home_UI_Page.ts:118-146)
- **레이아웃:** 3열 그리드 (반응형: 900px 이하 1열)
- **카드:** createCard 공통 컴포넌트 사용
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onA01_Home_GetStarted_Click()` | A01_Home_UI_Page.ts:41-43 | 회원가입(A07_REGISTER) 이동 |
| `onA01_Home_Explore_Click()` | A01_Home_UI_Page.ts:45-47 | 프로그램 상세(A02_PROG_DETAIL) 이동 |
## 로컬라이제이션
모든 텍스트는 `ui_locales[key][currentLanguageIndex]` 형식으로 [[ui_templates]] 에서 로드.
하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A01_Home_Title` |
| 히어로 부제목 | `A01_Home_Hero_Subtitle` |
| CTA 버튼 (Primary/Secondary) | `A01_Home_Hero_CtaPrimary`, `A01_Home_Hero_CtaSecondary` |
| 소식 섹션 제목 | `A01_Home_News_SectionTitle` |
| 소식 태그 | `A01_Home_News_Tag` |
| 기능 섹션 제목 | `A01_Home_Features_SectionTitle` |
| 기능 카드 1~3 | `A01_Home_Feature{1..3}_Title`, `A01_Home_Feature{1..3}_Desc` |
## 세부 구현
스타일 구조·의존성·데이터 흐름·미해결 사항은 [[A01_Home/A01_components]]에 분리했다.
@@ -0,0 +1,38 @@
---
type: page-frontend-detail
status: stable
page_id: A02_ProgDetail
related_pages: ["[[A02_ProgDetail/A02_frontend]]", "[[ui_templates]]", "[[a00_app_shell_framework]]"]
last_updated: 2026-07-12
source: A02_ProgDetail 코드 인제스트 보완 및 구조화 일관성 적용
---
# A02_ProgDetail — 세부 구현
## 스타일 (CSS)
| 항목 | 역할 |
|---|---|
| `.a02-prog-detail` | 페이지 루트 컨테이너 (gap: section-gap, padding-bottom: spacing-64) |
| `.a02-hero`, `.a02-hero__inner` | 히어로 영역 배경(라벤더 그라디언트) 및 콘텐츠 중앙 정렬 |
| `.a02-workflow-list`, `.a02-workflow-row` | 6단계 워크플로우 리스트(flex 열) 및 배지+텍스트 행 레이아웃 |
| `.a02-workflow-row__badge` | 40×40px 원형 배지 (primary 배경) |
| `.a02-tech-grid`, `.a02-tech-card` | 핵심 기술 소개 3열 카드 그리드 (900px 이하 대응) |
## 의존성
| 항목 | 역할 |
|---|---|
| [[a00_app_shell_framework]] | `navigateTo()` 라우팅 기능 활용 |
| `config_frontend.ts` | `ROUTES` 경로 상수 참조 |
| [[ui_templates]] | 공통 버튼(`createButton`) 및 카드(`createCard`) 엘리먼트 활용 |
| `ui_template_locale.ts` | 다국어 문구(`ui_locales`) 적용 |
## 데이터 흐름
| 단계 | 흐름 |
|---|---|
| 1 | `renderA02ProgDetail(root)` ➡️ `buildHero()` ➡️ `buildWorkflow()` ➡️ `buildTech()` 순서로 구성 요소를 렌더링하고 `root`에 추가 |
| 2 | 사용자가 히어로의 CTA 버튼 클릭 시 `onA02_ProgDetail_Start_Click()` 핸들러가 작동하여 `navigateTo(ROUTES.A07_REGISTER)` 호출 |
## 미해결 / 특이사항
| 항목 | 상태 |
|---|---|
| 정적 페이지 | 백엔드 API와의 연동이 필요 없는 완전한 정적 소개 페이지임 |
@@ -0,0 +1,33 @@
---
status: stable
page_id: A02_ProgDetail
related_pages: ["[[A01_Home/A01_frontend]]", "[[a00_app_shell_framework]]", "[[ui_templates]]", "[[A02_ProgDetail/A02_components]]"]
last_updated: 2026-07-12
---
# A02_ProgDetail — Frontend
로그인 전 프로그램 상세 페이지. 6단계 워크플로우 시각화 + 핵심 기술 소개.
## 구조
- **경로**: `A02_ProgDetail/A02_ProgDetail_UI_Page.ts` (146줄)
- **스타일**: `A02_ProgDetail/A02_ProgDetail_UI_Style.css` (127줄)
## 컴포넌트 분석
| 컴포넌트 | 역할 | 로케일 키 |
|---|---|---|
| `buildHero()` | 히어로 섹션(제목+부제+CTA) | `A02_ProgDetail_Title`, `A02_ProgDetail_Hero_Subtitle`, `A02_ProgDetail_Hero_Cta` |
| `buildWorkflow()` | 6단계 워크플로우 리스트(배지+텍스트) | `A02_ProgDetail_Workflow_SectionTitle`, `A02_ProgDetail_Step[1-6]_Title`, `A02_ProgDetail_Step[1-6]_Desc` |
| `buildTech()` | 기술 3종 카드 그리드 | `A02_ProgDetail_Tech_SectionTitle`, `A02_ProgDetail_Tech[1-3]_Title`, `A02_ProgDetail_Tech[1-3]_Desc` |
## 제약 준수
- ✅ 모든 문구 `ui_template_locale.ts` 참조
- ✅ 색상/간격 `theme.css` 변수만 사용
- ✅ 공통 컴포넌트 `createButton`/`createCard` 사용
- ✅ 이벤트 핸들러 명명 `on[페이지]_[기능]_[액션]`
## 세부 구현
스타일 구조·의존성·데이터 흐름·특이사항은 [[A02_ProgDetail/A02_components]]에 분리했다.
@@ -0,0 +1,67 @@
---
status: stable
page_id: A03_CompDetail
related_pages: ["[[a00_app_shell_framework]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# A03_CompDetail — Frontend
로그인 전 회사 상세 페이지. 미션 + 핵심 가치 + 연락처.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A03_CompDetail_UI_Page.ts` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:1-114` | 페이지 렌더링 로직 |
| `A03_CompDetail_UI_Style.css` | `A03_CompDetail/A03_CompDetail_UI_Style.css:1-100` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트 (섹션 빌더)
| 함수 | 위치 | 역할 |
|---|---|---|
| `buildHero()` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:23-41` | Hero(제목+부제목), mist-violet→canvas 그라디언트 |
| `buildMission()` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:43-57` | 미션 섹션 제목+본문(중앙정렬, max 60자) |
| `buildValues()` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:59-87` | 핵심가치 3열 카드그리드(createCard 3개) |
| `buildContact()` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:89-103` | 연락처 섹션 제목+이메일 |
| `renderA03CompDetail()` | `A03_CompDetail/A03_CompDetail_UI_Page.ts:108-113` | 페이지 진입점(4개 섹션 조립) |
## 로컬라이제이션
모든 텍스트는 `ui_locales[key][currentLanguageIndex]` 형식으로 [[ui_templates]]에서 로드.
하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A03_CompDetail_Title` |
| 히어로 부제목 | `A03_CompDetail_Hero_Subtitle` |
| 미션 섹션 제목 | `A03_CompDetail_Mission_SectionTitle` |
| 미션 본문 | `A03_CompDetail_Mission_Body` |
| 가치 섹션 제목 | `A03_CompDetail_Value_SectionTitle` |
| 가치 1~3 | `A03_CompDetail_Value{1..3}_Title`, `A03_CompDetail_Value{1..3}_Desc` |
| 연락처 섹션 제목 | `A03_CompDetail_Contact_SectionTitle` |
| 연락처 이메일 | `A03_CompDetail_Contact_Email` |
## 스타일 (CSS)
### CSS 클래스 구조
- `.a03-comp-detail` — 페이지 래퍼 (flex column, gap: section-gap)
- `.a03-hero` — 그래디언트 배경
- `.a03-hero__inner` — 중앙 정렬 컨테이너
- `.a03-hero__title` — heading-lg, deep-iris, max 18자
- `.a03-hero__subtitle` — body 텍스트, slate, max 56자
- `.a03-section` — 공통 섹션 (max-width: page-max-width, padding: spacing-32)
- `.a03-section__title` — heading-sm, deep-iris, 중앙 정렬
- `.a03-mission__body` — body, slate, 중앙 정렬, max 60자
- `.a03-value-grid` — 3열 그리드 (gap: spacing-24)
- `.a03-value-card` — 카드 (min-height: 140px)
- `.a03-value__desc` — body-sm, slate
- `.a03-contact__email` — body, charcoal, 중앙 정렬
### 반응형
- **900px 이하:** 가치 그리드 1열, 히어로 제목 폰트 축소
## 의존성
- [[a00_app_shell_framework]] — 공통 헤더·푸터
- [[ui_templates]] — `createCard()` 공통 컴포넌트 + 다국어
@@ -0,0 +1,86 @@
---
status: stable
page_id: A04_NewsHistory
related_pages: ["[[A01_Home/A01_frontend]]", "[[a00_app_shell_framework]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# A04_NewsHistory — Frontend
로그인 전 최신 소식 및 개선 이력 페이지. 타임라인 목록 형태.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A04_NewsHistory_UI_Page.ts` | `A04_NewsHistory/A04_NewsHistory_UI_Page.ts:1-117` | 페이지 렌더링 로직 + Mock 데이터 |
| `A04_NewsHistory_UI_Style.css` | `A04_NewsHistory/A04_NewsHistory_UI_Style.css:1-87` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트 (섹션 빌더)
| 함수 | 위치 | 역할 |
|---|---|---|
| `buildHero()` | `A04_NewsHistory/A04_NewsHistory_UI_Page.ts:50-68` | Hero(제목+부제목), mist-violet→canvas 그라디언트 |
| `buildTimeline()` | `A04_NewsHistory/A04_NewsHistory_UI_Page.ts:70-106` | 타임라인(태그+제목+날짜 행), 빈 상태 처리 |
| `renderA04NewsHistory()` | `A04_NewsHistory/A04_NewsHistory_UI_Page.ts:111-116` | 페이지 진입점(Hero+타임라인 조립) |
- **데이터:** MOCK_HISTORY 배열 (A04_NewsHistory_UI_Page.ts:33-38, 백엔드 연결 전) — 추후 `GET /api/a04/history`로 교체 예정
- **빈 상태:** MOCK_HISTORY.length === 0이면 `A04_NewsHistory_Empty` 로케일 키 표시
## Mock 데이터 구조
**타입:**
```typescript
type HistoryKind = "feature" | "fix" | "notice";
interface HistoryItem {
id: number;
kind: HistoryKind;
title: string;
date: string;
}
```
**태그 변형 매핑** (KIND_META):
- `"feature"` → variant: "accent" → `A04_NewsHistory_Tag_Feature`
- `"fix"` → variant: "success" → `A04_NewsHistory_Tag_Fix`
- `"notice"` → variant: "neutral" → `A04_NewsHistory_Tag_Notice`
## 로컬라이제이션
모든 텍스트는 `ui_locales[key][currentLanguageIndex]` 형식으로 [[ui_templates]]에서 로드.
하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A04_NewsHistory_Title` |
| 히어로 부제목 | `A04_NewsHistory_Hero_Subtitle` |
| 태그 라벨 (feature/fix/notice) | `A04_NewsHistory_Tag_Feature`, `A04_NewsHistory_Tag_Fix`, `A04_NewsHistory_Tag_Notice` |
| 빈 상태 메시지 | `A04_NewsHistory_Empty` |
## 스타일 (CSS)
### CSS 클래스 구조
- `.a04-news-history` — 페이지 래퍼 (flex column, gap: section-gap)
- `.a04-hero` — 그래디언트 배경
- `.a04-hero__inner` — 중앙 정렬 컨테이너
- `.a04-hero__title` — heading-lg, deep-iris, max 18자
- `.a04-hero__subtitle` — body 텍스트, slate, max 56자
- `.a04-section` — 공통 섹션 (max-width: page-max-width, padding: spacing-32)
- `.a04-timeline` — flex 열 (gap: spacing-8)
- `.a04-timeline-row` — flex row, 태그+제목+날짜 (gap: spacing-16, padding: spacing-16 spacing-24)
- `.a04-timeline-row__title` — flex: 1, body-sm, charcoal
- `.a04-timeline-row__date` — caption, ash
- `.a04-timeline__empty` — 중앙 정렬, slate, body
### 반응형
- **900px 이하:** 히어로 제목 폰트 축소
## 의존성
- [[a00_app_shell_framework]] — 공통 헤더·푸터
- [[ui_templates]] — `createTag()` 공통 컴포넌트 + 다국어
## 미해결 사항
- MOCK_HISTORY 데이터: 현재 하드코딩 (4개 항목)
- 백엔드 API 미구현: `GET /api/a04/history` 코드 주석에 언급됨
@@ -0,0 +1,69 @@
---
status: stable
page_id: A05_EduDetail
related_pages: ["[[a00_app_shell_framework]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# A05_EduDetail — Frontend
로그인 전 교육 상세 페이지. 교육 과정 소개 + 문의 CTA.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A05_EduDetail_UI_Page.ts` | `A05_EduDetail/A05_EduDetail_UI_Page.ts:1-106` | 페이지 렌더링 로직 |
| `A05_EduDetail_UI_Style.css` | `A05_EduDetail/A05_EduDetail_UI_Style.css:1-87` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트 (섹션 빌더)
| 함수 | 위치 | 역할 |
|---|---|---|
| `buildHero()` | `A05_EduDetail/A05_EduDetail_UI_Page.ts:33-51` | Hero(제목+부제목), mist-violet→canvas 그라디언트 |
| `buildCourses()` | `A05_EduDetail/A05_EduDetail_UI_Page.ts:53-81` | 교육과정 3열 카드그리드(createCard 3개) |
| `buildCta()` | `A05_EduDetail/A05_EduDetail_UI_Page.ts:83-95` | 문의 CTA 버튼(variant: filled) |
| `renderA05EduDetail()` | `A05_EduDetail/A05_EduDetail_UI_Page.ts:100-105` | 페이지 진입점(3개 섹션 조립) |
## 로컬라이제이션
모든 텍스트는 `ui_locales[key][currentLanguageIndex]` 형식으로 [[ui_templates]]에서 로드.
하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A05_EduDetail_Title` |
| 히어로 부제목 | `A05_EduDetail_Hero_Subtitle` |
| 과정 섹션 제목 | `A05_EduDetail_Course_SectionTitle` |
| 과정 1~3 | `A05_EduDetail_Course{1..3}_Title`, `A05_EduDetail_Course{1..3}_Desc` |
| CTA 버튼 라벨 | `A05_EduDetail_Cta` |
## 스타일 (CSS)
### CSS 클래스 구조
- `.a05-edu-detail` — 페이지 래퍼 (flex column, gap: section-gap)
- `.a05-hero` — 그래디언트 배경
- `.a05-hero__inner` — 중앙 정렬 컨테이너
- `.a05-hero__title` — heading-lg, deep-iris, max 18자
- `.a05-hero__subtitle` — body 텍스트, slate, max 56자
- `.a05-section` — 공통 섹션 (max-width: page-max-width, padding: spacing-32)
- `.a05-section__title` — heading-sm, deep-iris, 중앙 정렬
- `.a05-course-grid` — 3열 그리드 (gap: spacing-24)
- `.a05-course-card` — 카드 (min-height: 140px)
- `.a05-course__desc` — body-sm, slate
- `.a05-cta` — align-items: center (버튼 중앙 정렬)
### 반응형
- **900px 이하:** 과정 그리드 1열, 히어로 제목 폰트 축소
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onA05_EduDetail_Inquiry_Click()` | A05_EduDetail_UI_Page.ts:26-28 | A08_SUPPORT로 이동 |
## 의존성
- [[a00_app_shell_framework]] — `navigateTo()` 라우팅
- `config_frontend.ts``ROUTES` 상수 (A08_SUPPORT)
- [[ui_templates]] — `createButton()`, `createCard()` 공통 컴포넌트 + 다국어
+70
View File
@@ -0,0 +1,70 @@
---
status: stable
page_id: A06_Login
related_pages: ["[[A06_Login/A06_frontend]]", "[[A07_Register/A07_backend]]", "[[A09_Security/A09_backend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A06_Login — Backend
세션 쿠키 기반 로그인 + OTP 재인증 + 세션/비밀번호 관리 API.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A06_Login_Router.py` | `A06_Login/A06_Login_Router.py:1-179` | 인증 API 라우터 (prefix `/api/auth`) |
| `A06_Login_Schema.py` | `A06_Login/A06_Login_Schema.py:1-20` | 요청 검증 Pydantic 모델 3종 |
## API 엔드포인트
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `POST /api/auth/login/request` | `A06_Login/A06_Login_Router.py:51` | 1차 로그인(이메일+비번). 신규 브라우저/주기 재인증 시 `otp_required` 반환 |
| `POST /api/auth/login/verify` | `A06_Login/A06_Login_Router.py:113` | OTP 검증 후 세션 발급 |
| `GET /api/auth/session` | `A06_Login/A06_Login_Router.py:145` | 현재 세션 사용자 조회 (id/email/name/role/company_id/is_master) |
| `POST /api/auth/logout` | `A06_Login/A06_Login_Router.py:160` | 세션 삭제 + 쿠키 제거 |
| `POST /api/auth/password` | `A06_Login/A06_Login_Router.py:170` | 비밀번호 변경 (현재 비번 검증, 재인증 필요 반환) |
## 내부 헬퍼
| 함수 | 위치 | 역할 |
|---|---|---|
| `_user_agent(request)` | `A06_Login/A06_Login_Router.py:40` | User-Agent 헤더 추출(최대 1000자) |
| `_send_otp(user, purpose, label)` | `A06_Login/A06_Login_Router.py:44` | OTP 생성·해시 저장·메일 발송 |
| `_finish_login(user, agent)` | `A06_Login/A06_Login_Router.py:87` | 세션 생성·쿠키 설정·last_login 갱신·로그인 기록 |
## 요청 스키마 (Pydantic)
| 모델 | 위치 | 필드 |
|---|---|---|
| `LoginRequest` | `A06_Login/A06_Login_Schema.py:6` | email(EmailStr), password(8~128자) |
| `OtpVerifyRequest` | `A06_Login/A06_Login_Schema.py:11` | email(EmailStr), otp_code(정규식 `^\d{6}$`) |
| `PasswordChangeRequest` | `A06_Login/A06_Login_Schema.py:16` | current_password, new_password(8~128자), logout_all(기본 True) |
## 로그인 로직 흐름
1. `login/request`: 이메일 조회 → 계정 잠금 확인 → 비밀번호 검증 → 계정 상태 확인(`ACTIVE`/`NO_COMPANY`/`PENDING`만 허용)
2. **OTP 트리거 조건** (둘 중 하나면 OTP 요구):
- 주기적 재인증: `last_email_verified_at``EMAIL_REVERIFY_DAYS` 이전
- 신규 브라우저: `has_known_browser()` 미확인
3. `login/verify`: OTP 검증 → 소비 → `last_email_verified_at` 갱신 → `_finish_login`
4. `_finish_login`: 로그인 실패 초기화 → 브라우저 신뢰 등록 → `auth_expires_at = NOW() + 3개월` → 세션 생성
## 보안 정책 (실 코드 기준)
- **비밀번호 실패**: `record_failed_login()` 누적, 5회 이상 & ADMIN_EMAIL 설정 시 보안 경고 메일 발송
- **계정 잠금**: `account_locked_until`이 미래면 423 반환
- **로그인 기록**: 모든 시도를 `record_login()`으로 기록(성공/실패 사유 포함)
## 의존성 (공통 유틸)
- [[common_util]] — `common_util_auth`(세션/OTP/해시), `common_util_auth_repository`(DB 조회), `common_util_email`(메일 발송)
- 인증/세션/OTP 정책 상세 → [[auth_rbac]]
- `config_system``ADMIN_EMAIL`, `EMAIL_REVERIFY_DAYS`
## ⚠️ 계획서와 실 코드 불일치
[[auth_rbac]]는 계획서 기준(OTP 5분·재시도 3회, 세션 4h/12h, 5회 실패 15분 잠금)으로 기술돼 있으나,
실 코드는 `auth_expires_at = NOW() + 3개월` 갱신·브라우저 신뢰 기반 OTP·5회 실패 시 관리자 알림 메일 방식.
정책 상세 검증 필요 — 규칙 6(실 코드 우선)에 따라 auth_rbac 갱신 대상.
+85
View File
@@ -0,0 +1,85 @@
---
status: stable
page_id: A06_Login
related_pages: ["[[A06_Login/A06_backend]]", "[[A07_Register/A07_frontend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A06_Login — Frontend
로그인 화면. 이메일+비밀번호 1차 → 필요 시 OTP 입력으로 전환하는 단일 폼.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A06_Login_UI_Auth_Page.ts` | `A06_Login/A06_Login_UI_Auth_Page.ts:1-80` | 로그인 폼 렌더링 + 제출 로직 |
| `A06_Login_UI_Style.css` | `A06_Login/A06_Login_UI_Style.css:1-52` | 스타일 (theme.css 변수만 사용) |
| `A06_Login_Api_Fetch.ts` | `A06_Login/A06_Login_Api_Fetch.ts:1-57` | 인증 API 클라이언트 (`credentials: "include"`) |
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderA06Login(root)` | `A06_Login/A06_Login_UI_Auth_Page.ts:17` | 카드+폼(email/password/otp/submit) 조립 |
| `onA06_Login_Submit_Click(event)` | `A06_Login/A06_Login_UI_Auth_Page.ts:34` | 폼 제출 핸들러(검증→요청→OTP 전환/성공) |
## API 클라이언트 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `requestLogin(email, password)` | `A06_Login/A06_Login_Api_Fetch.ts:30` | `POST /auth/login/request` |
| `verifyLogin(email, otpCode)` | `A06_Login/A06_Login_Api_Fetch.ts:34` | `POST /auth/login/verify` |
| `fetchSession()` | `A06_Login/A06_Login_Api_Fetch.ts:38` | `GET /auth/session` → boolean(로그인 여부) |
| `fetchSessionUser()` | `A06_Login/A06_Login_Api_Fetch.ts:43` | `GET /auth/session` → SessionUser 객체 |
| `logout()` | `A06_Login/A06_Login_Api_Fetch.ts:54` | `POST /auth/logout` |
- `SessionUser` 타입: id, email, name?, role?, company_id?, is_master?
- `fetchSession`/`fetchSessionUser`/`logout`은 [[a00_app_shell_framework]]에서도 사용 (공유 자원)
## 제출 로직 (2단계 폼 전환)
1. 이메일 유효성 검사 (isBlank/isValidEmail) → 실패 시 필드 에러
2. OTP 미요구 상태면 비밀번호 필수, OTP 요구 상태면 6자리 숫자 검증
3. `otpRequired` false → `requestLogin`, true → `verifyLogin`
4. 응답 `status === "otp_required"`:
- otp 필드 노출, password 필드 숨김
- 제출 버튼 라벨 → `A06_Login_Verify`
- 토스트 안내(`A06_Login_OtpSent`)
5. 로그인 성공 → 토스트 후 `navigateTo(ROUTES.B01_ACCOUNT)`
## 로컬라이제이션
모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A06_Login_Title` |
| 이메일/비번/OTP 필드 | `A06_Login_Field_Email`, `A06_Login_Field_Password`, `A06_Login_Field_Otp` |
| 제출/인증 버튼 | `A06_Login_Submit`, `A06_Login_Verify` |
| 회원가입 이동 | `A06_Login_ToRegister` |
| 안내/성공 | `A06_Login_OtpSent`, `A06_Login_Success` |
| 오류 | `A06_Login_Error_Email`, `A06_Login_Error_Required`, `A06_Login_Error_Request` |
## 스타일 (CSS)
- `.a06-login` — 페이지 래퍼 (중앙 정렬, 상단 정렬)
- `.a06-login__card` — 로그인 카드 (max-width: 400px, surface 배경, border)
- `.a06-login__title` — heading-sm, deep-iris, 중앙 정렬
- `.a06-login__form` — flex 열 (gap: spacing-16)
- `.a06-login__submit` / `.a06-login__link` — 전체 너비 버튼
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onA06_Login_Submit_Click()` | `A06_Login/A06_Login_UI_Auth_Page.ts:34` | 로그인/OTP 제출 |
| (인라인) 회원가입 버튼 | `A06_Login/A06_Login_UI_Auth_Page.ts:75` | A07_REGISTER로 이동 |
## 의존성
- [[a00_app_shell_framework]] — `navigateTo()` 라우팅
- `config_frontend.ts``ROUTES`, `API_BASE_URL`
- `common_util_validate``isBlank`, `isValidEmail`
- [[ui_templates]] — `createButton`, `createInputField`, `showLoadingOverlay`/`hideLoadingOverlay`, `showToast`
- 백엔드 API → [[A06_Login/A06_backend]]
@@ -0,0 +1,57 @@
---
status: stable
page_id: A07_Register
related_pages: ["[[A07_Register/A07_frontend]]", "[[A06_Login/A06_backend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A07_Register — Backend
회원가입(2단계: 요청 → OTP 검증) + 회사 검색 API. prefix `/api/auth` ([[A06_Login/A06_backend]]와 공유).
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A07_Register_Router.py` | `A07_Register/A07_Register_Router.py:1-76` | 회원가입/회사검색 API 라우터 |
| `A07_Register_Schema.py` | `A07_Register/A07_Register_Schema.py:1-30` | 요청 검증 Pydantic 모델 2종 |
## API 엔드포인트
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `GET /api/auth/companies?q=` | `A07_Register/A07_Register_Router.py:26` | 회사명 검색(1~100자) → id/name 목록 |
| `POST /api/auth/register/request` | `A07_Register/A07_Register_Router.py:31` | 1차 가입 요청(202). OTP 메일 발송, PENDING_EMAIL 계정 생성 |
| `POST /api/auth/register/verify` | `A07_Register/A07_Register_Router.py:56` | OTP 검증 → 가입 완료(NO_COMPANY 상태) |
## 요청 스키마 (Pydantic)
| 모델 | 위치 | 필드 |
|---|---|---|
| `RegisterRequest` | `A07_Register/A07_Register_Schema.py:6` | email, password/password_confirm(8~128), name, position?, phone?, terms_version, terms_agreed, privacy_agreed, marketing_agreed |
| `RegisterVerifyRequest` | `A07_Register/A07_Register_Schema.py:27` | email(EmailStr), otp_code(정규식 `^\d{6}$`) |
- `RegisterRequest.validate_registration` (model_validator): 필수 약관 동의 확인 + 비밀번호 일치 확인
## 가입 로직 흐름
1. `register/request`:
- 기존 이메일 조회 → `PENDING_EMAIL`이 아니면 409(이미 등록됨)
- `PENDING_EMAIL`(미인증)이면 `refresh_pending_registration()`으로 갱신
- 신규면 `create_registration()`으로 생성
- OTP 생성·해시 저장·메일 발송
2. `register/verify`:
- `PENDING_EMAIL` 상태 확인 → OTP 검증(만료/해시) → 소비
- `complete_registration()` → 상태 `NO_COMPANY`로 전환
- **회사 생성/연결은 로그인 후 B01_Dashboard에서 진행** (가입 시점엔 회사 없음)
## 의존성 (공통 유틸)
- [[common_util]] — `common_util_auth`(OTP/해시), `common_util_auth_repository`(가입/회사검색 DB), `common_util_email`(메일)
- 사용자 상태 생명주기(PENDING_EMAIL → NO_COMPANY) → [[auth_rbac]]
- 회원 테이블 → [[db_schema/users_auth]]
## 참고
- 회원가입은 회사 없이 완료됨(NO_COMPANY). 회사 검색 API는 프론트 자동완성용이나,
실제 회사 연결은 B01_Dashboard에서 처리 → [[B01_Dashboard/B01_backend]]
@@ -0,0 +1,91 @@
---
status: stable
page_id: A07_Register
related_pages: ["[[A07_Register/A07_backend]]", "[[A06_Login/A06_frontend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A07_Register — Frontend
회원가입 화면. 정보 입력 + 약관 아코디언 동의 → OTP 입력으로 전환하는 2단계 폼.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A07_Register_UI_Auth_Page.ts` | `A07_Register/A07_Register_UI_Auth_Page.ts:1-208` | **실사용** 회원가입 UI (약관·OTP·실 API) |
| `A07_Register_UI_Page.ts` | `A07_Register/A07_Register_UI_Page.ts:1-142` | ⚠️ 구버전 잔재 (미사용, 아래 참고) |
| `A07_Register_UI_Style.css` | `A07_Register/A07_Register_UI_Style.css:1-103` | 스타일 (theme.css 변수만 사용) |
| `A07_Register_Api_Fetch.ts` | `A07_Register/A07_Register_Api_Fetch.ts:1-47` | 회원가입/회사검색 API 클라이언트 |
> ⚠️ **라우터는 `A07_Register_UI_Auth_Page`만 사용** (router.ts:30 확인). `A07_Register_UI_Page.ts`는
> 회사명+이름+이메일+비번만 받는 단순 폼이며 백엔드 미연동(TODO 주석, `navigateTo`만) — 구버전 잔재로 판단. 정리 권장.
## 컴포넌트 / 함수 (UI_Auth_Page — 실사용)
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderA07Register(root)` | `A07_Register/A07_Register_UI_Auth_Page.ts:68` | 카드+폼 조립, 약관 텍스트 비동기 로드 후 렌더 |
| `onA07_Register_Submit_Click(event)` | `A07_Register/A07_Register_UI_Auth_Page.ts:146` | 제출 핸들러(검증→가입요청→OTP 전환→검증) |
| `checkboxWithAccordion(label, text)` | `A07_Register/A07_Register_UI_Auth_Page.ts:22` | 체크박스+펼침 상세(약관 본문) 컴포넌트 |
| `loadAgreementText(filename)` | `A07_Register/A07_Register_UI_Auth_Page.ts:58` | `resources/legal/*.txt` 약관 파일 fetch |
## API 클라이언트 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `requestRegistration(payload)` | `A07_Register/A07_Register_Api_Fetch.ts:28` | `POST /auth/register/request` |
| `verifyRegistration(email, otpCode)` | `A07_Register/A07_Register_Api_Fetch.ts:32` | `POST /auth/register/verify` |
| `searchCompanies(query)` | `A07_Register/A07_Register_Api_Fetch.ts:39` | `GET /auth/companies?q=` → id/name 목록 |
- `RegisterPayload` 타입: email, password, password_confirm, name, position, phone, terms_version, terms_agreed, privacy_agreed, marketing_agreed
## 제출 로직 (2단계 폼 전환)
1. **입력 검증**: 이름/이메일/비번/비번확인 필수, 이메일 형식, 비번 8자 이상, 비번 일치, 필수 약관(terms/privacy) 체크
2. `requestRegistration()` 호출 (`terms_version: "1.0"` 고정, terms_agreed/privacy_agreed true)
3. 성공 시 `verifyStep = true`: 입력 필드 숨기고 OTP 필드 노출, 버튼 라벨 → `A07_Register_Verify`
4. OTP 제출 → `verifyRegistration()` → 성공 토스트 → `navigateTo(ROUTES.A06_LOGIN)`
## 약관 동의 (아코디언)
- `checkboxWithAccordion`으로 이용약관/개인정보/마케팅 3종 생성
- 약관 본문은 `resources/legal/{terms_of_service,privacy_policy,marketing_consent}.txt`에서 fetch
- 마케팅은 선택(marketing_agreed), 이용약관·개인정보는 필수
## 로컬라이제이션
모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목 | `A07_Register_Title` |
| 필드 | `A07_Register_Field_Name`, `_Position`, `_Email`, `_Phone`, `_Password`, `_PasswordConfirm`, `_Otp` |
| 약관 | `A07_Register_Terms`, `_Privacy`, `_Marketing` |
| 제출/인증 | `A07_Register_Submit`, `A07_Register_Verify` |
| 안내/성공 | `A07_Register_OtpSent`, `A07_Register_Success` |
| 오류 | `A07_Register_Error_Required`, `_Email`, `_Password`, `_PasswordMismatch`, `_Terms`, `_Request` |
| 로그인 이동 | `A07_Register_ToLogin` |
## 스타일 (CSS)
- `.a07-register__card` — 카드 (max-width: 420px)
- `.a07-register__agreement` — 약관 컨테이너(border, overflow hidden)
- `.a07-register__agreement-summary` — 체크박스+라벨 헤더(클릭 시 상세 토글)
- `.a07-register__agreement-details` / `-content` — 펼침 약관 본문(max-height 200px, pre-wrap)
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onA07_Register_Submit_Click()` | `A07_Register/A07_Register_UI_Auth_Page.ts:146` | 가입/OTP 제출 |
| (인라인) 로그인 버튼 | `A07_Register/A07_Register_UI_Auth_Page.ts:201` | A06_LOGIN으로 이동 |
| (인라인) 약관 요약 클릭 | `A07_Register/A07_Register_UI_Auth_Page.ts:47` | 상세 아코디언 토글 |
## 의존성
- [[a00_app_shell_framework]] — `navigateTo()` 라우팅
- `config_frontend.ts``ROUTES`, `API_BASE_URL`
- `common_util_validate``isBlank`, `isValidEmail`, `hasMinLength`
- [[ui_templates]] — `createButton`, `createInputField`, `showLoadingOverlay`/`hideLoadingOverlay`, `showToast`
- 백엔드 API → [[A07_Register/A07_backend]]
@@ -0,0 +1,67 @@
---
status: stable
page_id: A08_Support
related_pages: ["[[A08_Support/A08_frontend]]", "[[A06_Login/A06_backend]]", "[[db_schema/logs_monitoring]]"]
last_updated: 2026-07-12
---
# A08_Support — Backend
기술지원 문의 접수 API. `support_requests` 테이블 저장 + 관리자 알림 메일.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A08_Support_Router.py` | `A08_Support/A08_Support_Router.py:1-68` | 문의 접수 API (prefix `/api/a08`) |
| `A08_Support_Schema.py` | `A08_Support/A08_Support_Schema.py:1-13` | 문의 요청 검증 Pydantic 모델 |
## API 엔드포인트
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `POST /api/a08/support` | `A08_Support/A08_Support_Router.py:27` | 문의 접수. DB 저장 + ADMIN_EMAIL로 알림 메일 |
## 내부 헬퍼
| 함수 | 위치 | 역할 |
|---|---|---|
| `_get_company_name(company_id)` | `A08_Support/A08_Support_Router.py:16` | company_id로 회사명 조회 (없으면 None) |
## 요청 스키마 (Pydantic)
| 모델 | 위치 | 필드 |
|---|---|---|
| `SupportRequestPayload` | `A08_Support/A08_Support_Schema.py:6` | name(1~255), email(EmailStr), phone?(≤30), subject(1~255), message(1~5000) |
## 접수 로직
1. `get_optional_session()`으로 로그인 여부 확인 (비로그인 허용)
2. 로그인 상태면 세션의 `company_id``_get_company_name()`으로 회사명 조회, 비로그인이면 None
3. `support_requests` INSERT (아래 컬럼)
4. ADMIN_EMAIL 설정 시 관리자에게 문의 알림 메일 발송
## DB 저장 컬럼 (실 코드 INSERT 기준)
`support_requests`에 INSERT하는 컬럼:
`company_id, company_name, user_name, user_email, user_phone, subject, message`
→ 테이블 정의 → [[db_schema/logs_monitoring]]
## 특징
- **비로그인도 문의 가능** (`get_optional_session`): 세션 없으면 회사 정보만 None으로 저장
- 로그인 상태면 회사 정보 자동 연결
## 의존성 (공통 유틸)
- [[common_util]] — `common_util_auth`(`get_optional_session`), `common_util_email`(`send_email_background`), `common_util_email_templates`(`support_request_email`)
- `config_system``ADMIN_EMAIL`
- DB → [[db_schema/logs_monitoring]]
## ⚠️ DB 스키마 불일치 (확인 필요)
[[db_schema/logs_monitoring]]의 `support_requests` 컬럼은 계획서 기준
(`user_id, title, priority, status`)인데, 실 코드 INSERT는
`company_id, company_name, user_name, user_email, user_phone, subject, message`.
컬럼명이 다름 — 실 DB 스키마 대조 필요 (규칙6: 실 코드 우선). logs_monitoring 갱신 대상.
@@ -0,0 +1,76 @@
---
status: stable
page_id: A08_Support
related_pages: ["[[A08_Support/A08_backend]]", "[[a00_app_shell_framework]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# A08_Support — Frontend
기술지원 문의 폼. 이름·이메일·연락처·제목·문의내용(textarea) 입력 후 제출.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A08_Support_UI_Page.ts` | `A08_Support/A08_Support_UI_Page.ts:1-217` | 문의 폼 렌더링 + 제출 로직 |
| `A08_Support_UI_Style.css` | `A08_Support/A08_Support_UI_Style.css:1-64` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderA08Support(root)` | `A08_Support/A08_Support_UI_Page.ts:69` | 카드+폼 조립, 세션 정보 자동 채움 |
| `onA08_Support_Submit_Click(event)` | `A08_Support/A08_Support_UI_Page.ts:133` | 제출 핸들러(검증→fetch POST) |
| `createTextAreaField(label, placeholder)` | `A08_Support/A08_Support_UI_Page.ts:32` | 여러 줄 textarea 필드(에러 슬롯 포함) 생성 |
## 제출 로직
1. 필수값 검사(name/email/subject/message), 이메일 형식 검사
2. `fetch("/api/a08/support", POST)` 직접 호출 (별도 Api_Fetch 파일 없음)
3. 성공: 폼 리셋 + 성공 메시지(`A08_Support_Success`) 표시
4. 실패: nameField에 오류 메시지 표시
## 세션 자동 채움
- 렌더 후 백그라운드에서 `fetchSessionUser()`([[A06_Login/A06_frontend]]) 호출
- 로그인 상태면 name/email 필드 자동 입력 (렌더링 지연 없음)
## 로컬라이제이션
모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목/부제 | `A08_Support_Title`, `A08_Support_Subtitle` |
| 필드 | `A08_Support_Field_Name`, `_Email`, `_Phone`, `_Subject`, `_Message` (+각 Placeholder) |
| 제출 | `A08_Support_Submit` |
| 성공 | `A08_Support_Success` |
| 오류 | `A08_Support_Error_Required`, `A08_Support_Error_Email` |
## 스타일 (CSS)
- `.a08-support__card` — 카드 (max-width: 520px)
- `.a08-support__success` — 성공 메시지(success 색, mist-violet 배경), 기본 hidden
- `.a08-support__form` — flex 열 (gap: spacing-16)
- `.a08-support__textarea` — resize vertical, min-height 120px
- `.a08-support__submit` — 전체 너비 버튼
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onA08_Support_Submit_Click()` | `A08_Support/A08_Support_UI_Page.ts:133` | 문의 검증·제출 |
## 의존성
- `config_frontend.ts` — (직접 `/api/a08/support` 상대경로 fetch)
- `common_util_validate``isBlank`, `isValidEmail`
- [[A06_Login/A06_frontend]] — `fetchSessionUser` (세션 자동 채움)
- [[ui_templates]] — `createButton`, `createInputField` (textarea는 자체 구현)
- 백엔드 API → [[A08_Support/A08_backend]]
## 참고
- 문의 폼은 로그인/비로그인 모두 사용 가능 (세션 있으면 name/email 자동 입력)
- textarea는 공통 컴포넌트에 없어 `createTextAreaField`로 자체 구현 (`.ui-field`/`.ui-input` 스타일 상속)
@@ -0,0 +1,77 @@
---
status: stable
page_id: A09_Security
related_pages: ["[[A09_Security/A09_frontend]]", "[[A06_Login/A06_backend]]", "[[A07_Register/A07_backend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A09_Security — Backend
마스터(회사관리자)·시스템관리자 보안/조직 관리 API + 활동 로그 기록. prefix `/api/security`.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A09_Security_Router.py` | `A09_Security/A09_Security_Router.py:1-123` | 마스터/시스템관리자/활동로그 API |
| `A09_Security_Schema.py` | `A09_Security/A09_Security_Schema.py:1-25` | 요청 검증 Pydantic 모델 3종 |
## API 엔드포인트
### 마스터(회사 관리자) 전용 — `require_master`
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `GET /api/security/master/dashboard` | `A09_Security/A09_Security_Router.py:28` | 마스터 대시보드(회사 기준) |
| `GET /api/security/master/members` | `A09_Security/A09_Security_Router.py:33` | 회사 팀원 목록 |
| `GET /api/security/master/join-requests` | `A09_Security/A09_Security_Router.py:38` | 가입 요청 목록 |
| `POST /api/security/master/join-requests/{id}/decision` | `A09_Security/A09_Security_Router.py:43` | 가입 승인/거부 + 결과 메일 발송 |
| `POST /api/security/master/members/{id}/remove` | `A09_Security/A09_Security_Router.py:59` | 팀원 비활성화(내보내기) |
### 시스템 관리자 전용 — `require_system_admin`
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `GET /api/security/admin/dashboard` | `A09_Security/A09_Security_Router.py:86` | 시스템 대시보드 + psutil 리소스(cpu/memory/disk/process) |
| `GET /api/security/admin/{collection}` | `A09_Security/A09_Security_Router.py:101` | 컬렉션 목록(companies/users/support) |
| `PATCH /api/security/admin/{entity}/{id}/status` | `A09_Security/A09_Security_Router.py:110` | 엔티티 상태 변경(company/user/support) |
### 인증 사용자 공통 — `verify_session`
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `POST /api/security/activity` | `A09_Security/A09_Security_Router.py:66` | 활동 로그 기록(202), `activity_logs` INSERT |
## 요청 스키마 (Pydantic)
| 모델 | 위치 | 필드 |
|---|---|---|
| `JoinDecisionRequest` | `A09_Security/A09_Security_Schema.py:8` | approved(bool), comment?(≤1000) |
| `AdminStatusRequest` | `A09_Security/A09_Security_Schema.py:13` | status(3~20), reason(1~500) |
| `ActivityLogRequest` | `A09_Security/A09_Security_Schema.py:18` | action_type(Literal 5종), resource_id?, status(SUCCESS/FAILURE), details(dict) |
- `action_type` Literal: FILE_UPLOAD, FILE_DOWNLOAD, PROJECT_SAVE, DATA_PROCESS, PAGE_VIEW
## DB 저장 (activity_logs)
`POST /activity`가 INSERT하는 컬럼: `user_id, action_type, resource_id, status, details(JSON)`
→ 테이블 정의 → [[db_schema/logs_monitoring]]
## 권한 헬퍼
- `require_master` — 회사 마스터(is_master) 권한
- `require_system_admin` — SYSTEM_ADMIN 역할
- `verify_session` — 로그인 세션
- 권한 체계 상세 → [[auth_rbac]]
## 의존성 (공통 유틸)
- [[common_util]] — `common_util_auth`(권한 데코레이터), `common_util_auth_repository`(마스터/관리자 조회·처리), `common_util_email`(가입 결과 메일)
- 외부 라이브러리: `psutil`(관리자 대시보드 리소스 계측) → [[dependencies]]
- DB → [[db_schema/logs_monitoring]](activity_logs), [[db_schema/users_auth]](join_requests/companies)
## 참고
- 이 백엔드 API는 마스터/시스템관리자용이나, **프론트(`A09_Security_UI_Page.ts`)는 약관 열람만 하고 이 API를 사용하지 않음**.
API 클라이언트(`A09_Security_Api_Fetch.ts`)는 정의만 되고 미연동 상태 → [[A09_Security/A09_frontend]] 참고
- B01_Dashboard의 관리자 기능과 역할이 겹칠 수 있음 (마스터 대시보드/멤버관리) — 향후 연동처 확인 필요
@@ -0,0 +1,78 @@
---
status: stable
page_id: A09_Security
related_pages: ["[[A09_Security/A09_backend]]", "[[A06_Login/A06_frontend]]", "[[auth_rbac]]"]
last_updated: 2026-07-12
---
# A09_Security — Frontend
보안·약관 열람 페이지. 이용약관/개인정보/세션 보안정책 텍스트를 버전과 함께 표시.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `A09_Security_UI_Page.ts` | `A09_Security/A09_Security_UI_Page.ts:1-30` | **실사용** 약관 열람 페이지 렌더링 |
| `A09_Security_Terms.ts` | `A09_Security/A09_Security_Terms.ts:1-16` | 약관 텍스트 데이터(한/영), 버전 상수 |
| `A09_Security_UI_Style.css` | `A09_Security/A09_Security_UI_Style.css:1-14` | 스타일 (theme.css 변수만 사용) |
| `A09_Security_Api_Fetch.ts` | `A09_Security/A09_Security_Api_Fetch.ts:1-61` | ⚠️ API 클라이언트 (정의만, **미사용** — 아래 참고) |
> ⚠️ **프론트/백엔드 기능 불일치**: `A09_Security_UI_Page.ts`는 약관 텍스트 열람만 하며
> `A09_Security_Api_Fetch.ts`를 import하지 않는다. API 클라이언트(마스터/관리자 8함수)는 정의만 되고
> 어디서도 사용되지 않음(grep 확인). 백엔드 API(`/api/security`)는 완비돼 있으나 프론트 연동 미완.
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderA09Security(root)` | `A09_Security/A09_Security_UI_Page.ts:7` | 제목+버전+3개 약관 섹션 렌더 |
## 약관 데이터 (A09_Security_Terms.ts)
| 항목 | 위치 | 내용 |
|---|---|---|
| `SECURITY_TERMS_VERSION` | `A09_Security/A09_Security_Terms.ts:1` | 약관 버전 상수 `"1.0"` |
| `securityTerms.terms` | `A09_Security/A09_Security_Terms.ts:4` | 이용약관(한/영) |
| `securityTerms.privacy` | `A09_Security/A09_Security_Terms.ts:8` | 개인정보(한/영, "IP 미수집" 명시) |
| `securityTerms.security` | `A09_Security/A09_Security_Terms.ts:12` | 세션 보안정책(한/영) |
## API 클라이언트 함수 (⚠️ 미사용, 정의만)
| 함수 | 위치 | 대응 API |
|---|---|---|
| `getMasterDashboard()` | `A09_Security/A09_Security_Api_Fetch.ts:14` | `GET /security/master/dashboard` |
| `getMembers()` | `A09_Security/A09_Security_Api_Fetch.ts:18` | `GET /security/master/members` |
| `getJoinRequests()` | `A09_Security/A09_Security_Api_Fetch.ts:22` | `GET /security/master/join-requests` |
| `decideJoinRequest(id, approved, comment?)` | `A09_Security/A09_Security_Api_Fetch.ts:26` | `POST .../join-requests/{id}/decision` |
| `removeMember(userId)` | `A09_Security/A09_Security_Api_Fetch.ts:37` | `POST .../members/{id}/remove` |
| `getAdminDashboard()` | `A09_Security/A09_Security_Api_Fetch.ts:41` | `GET /security/admin/dashboard` |
| `getAdminCollection(collection)` | `A09_Security/A09_Security_Api_Fetch.ts:45` | `GET /security/admin/{collection}` |
| `updateAdminStatus(entity, id, status, reason)` | `A09_Security/A09_Security_Api_Fetch.ts:51` | `PATCH /security/admin/{entity}/{id}/status` |
## 로컬라이제이션
모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목/버전 | `A09_Security_Title`, `A09_Security_Version` |
| 섹션 제목 | `A09_Security_Terms`, `A09_Security_Privacy`, `A09_Security_Policy` |
- 약관 본문은 로케일이 아닌 `A09_Security_Terms.ts`에 한/영 배열로 별도 관리(`[currentLanguageIndex]`로 선택)
## 스타일 (CSS)
- `.a09-security` — 문서 컨테이너(max-width 800px, surface 배경, border)
- `.a09-security section` — 섹션 간격(margin-top: spacing-32)
## ⚠️ 약관 텍스트와 실 코드 불일치
`securityTerms.security`는 "세션 12시간 또는 4시간 비활동 후 만료"로 안내하나,
A06 실 코드는 `auth_expires_at = NOW() + 3개월` 방식 → [[A06_Login/A06_backend]] 참고.
약관 텍스트도 [[auth_rbac]] 계획서 기준으로 작성됨 (실 정책 검증 후 갱신 필요).
## 의존성
- [[ui_templates]] — `ui_locales`, `currentLanguageIndex` (다국어)
- 백엔드 API(미연동) → [[A09_Security/A09_backend]]
+59
View File
@@ -0,0 +1,59 @@
---
type: page-api
status: stable
page_id: B01_Dashboard
related_pages: ["[[B01_Dashboard/B01_frontend]]", "[[B01_Dashboard/B01_backend]]", "[[B01_Dashboard/B01_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B01_Dashboard_Router.py 실 코드 직접 확인 (2026-07-12)
---
# B01_Dashboard — API
기본 prefix: `/api/dashboard` (`B01_Dashboard_Router.py:57`). 공통 인증·오류 형식은 [[../../concepts/auth_rbac]], [[../../concepts/api_common]] 참조.
## 사용자·회사
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /me` | `B01_Dashboard_Router.py:101` | 로그인 사용자 대시보드 정보 조회 |
| `PATCH /me` | `B01_Dashboard_Router.py:109` | 내 기본정보 수정 |
| `GET /user/projects` | `B01_Dashboard_Router.py:118` | 내 프로젝트와 workflow 상태 조회 |
| `GET /user/company` | `B01_Dashboard_Router.py:123` | 소속 회사 조회 |
| `GET /user/companies` | `B01_Dashboard_Router.py:128` | 가입 대상 회사 검색 |
| `POST /user/company/create` | `B01_Dashboard_Router.py:137` | 회사 생성 후 생성자 연결 |
| `POST /user/company/join` | `B01_Dashboard_Router.py:146` | 회사 가입 신청 |
## 회사 관리자
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /admin/members` | `B01_Dashboard_Router.py:155` | 회사 팀원 목록 |
| `POST /admin/members` | `B01_Dashboard_Router.py:163` | 이메일로 팀원 추가 |
| `DELETE /admin/members/{user_id}` | `B01_Dashboard_Router.py:174` | 팀원 회사 연결 해제 |
| `GET /admin/join-requests` | `B01_Dashboard_Router.py:183` | 회사 가입 신청 목록 |
| `PATCH /admin/join-requests/{request_id}` | `B01_Dashboard_Router.py:188` | 가입 신청 승인·거절 |
| `GET /admin/projects` | `B01_Dashboard_Router.py:206` | 회사 프로젝트 목록 |
## 프로젝트·자동화
| 항목 | 위치 | 역할 |
|---|---|---|
| `PUT /projects/{project_id}` | `B01_Dashboard_Router.py:214` | 프로젝트 기본정보 수정 |
| `DELETE /projects/{project_id}` | `B01_Dashboard_Router.py:230` | SYSTEM_ADMIN 프로젝트 소프트 삭제 |
| `GET /projects/{project_id}/automations` | `B01_Dashboard_Router.py:366` | 자동화 목록; USER 차단 |
| `POST /projects/{project_id}/automations` | `B01_Dashboard_Router.py:379` | 자동화 생성 |
| `PUT /automations/{automation_id}` | `B01_Dashboard_Router.py:396` | 자동화 수정 |
| `DELETE /automations/{automation_id}` | `B01_Dashboard_Router.py:414` | 자동화 삭제 |
| `POST /automations/{automation_id}/execute` | `B01_Dashboard_Router.py:429` | 자동화 실행시각 기록 |
## 시스템 관리자
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /admin/companies` | `B01_Dashboard_Router.py:255` | 전체 회사 조회 |
| `POST /admin/companies` | `B01_Dashboard_Router.py:261` | 시스템 관리자 회사 생성 |
| `GET /admin/users` | `B01_Dashboard_Router.py:270` | 전체 사용자 조회 |
| `PATCH /admin/users/{user_id}/role` | `B01_Dashboard_Router.py:276` | 역할 변경과 마지막 ADMIN 보호 |
| `PUT /admin/users/{user_id}` | `B01_Dashboard_Router.py:294` | 사용자 관리정보 수정 |
| `PATCH /admin/users/{user_id}/company` | `B01_Dashboard_Router.py:313` | 사용자 회사 배정·해제 |
| `GET /admin/join-requests-all` | `B01_Dashboard_Router.py:325` | 전체 가입 신청 조회 |
| `PATCH /admin/join-requests/{request_id}/approve` | `B01_Dashboard_Router.py:331` | 시스템 관리자 가입 승인 |
| `GET /admin/audit-logs` | `B01_Dashboard_Router.py:341` | 시스템 감사 로그 페이지 조회 |
| `GET /admin/resources` | `B01_Dashboard_Router.py:351` | 1~90일 리소스 통계 조회 |
| `GET /admin/projects-all` | `B01_Dashboard_Router.py:360` | 전체 프로젝트 조회 |
@@ -0,0 +1,58 @@
---
type: page-backend
status: stable
page_id: B01_Dashboard
related_pages: ["[[B01_Dashboard/B01_frontend]]", "[[B01_Dashboard/B01_api]]", "[[B01_Dashboard/B01_db]]", "[[B01_Dashboard/B01_dependencies]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B01_Dashboard 실 코드 직접 확인 (2026-07-12)
---
# B01_Dashboard — Backend
## 라우터 권한 헬퍼
| 항목 | 위치 | 역할 |
|---|---|---|
| `require_company_admin()` | `B01_Dashboard_Router.py:60` | ADMIN 또는 SYSTEM_ADMIN 의존성 검사 |
| `_require_company_id()` | `B01_Dashboard_Router.py:68` | 세션 회사 ID 필수 검사 |
| `_same_company()` | `B01_Dashboard_Router.py:75` | 요청자와 대상의 회사 일치 검사 |
| `_can_edit_project()` | `B01_Dashboard_Router.py:79` | 역할·회사 기준 프로젝트 수정 검사 |
| `_can_manage_automation()` | `B01_Dashboard_Router.py:87` | 자동화 관리 권한 검사 |
| `_can_edit_user()` | `B01_Dashboard_Router.py:93` | 사용자 수정 범위 검사 |
상세 권한 규칙은 [[../../concepts/auth_rbac]]에서 1차 정의한다.
## 저장소 함수
| 항목 | 위치 | 역할 |
| ----------------------------------------------------------------------------- | ----------------------------------------- | -------------------------- |
| `get_dashboard_me()` / `update_user_profile()` | `B01_Dashboard_Repository.py:47,65` | 내 정보 조회·수정 |
| `get_workflow_states_for_projects()` | `B01_Dashboard_Repository.py:83` | 여러 프로젝트의 workflow 상태 배치 조회 |
| `list_user_projects()` / `list_company_projects()` / `list_all_projects()` | `B01_Dashboard_Repository.py:144,165,188` | 역할 범위별 프로젝트 목록 조회 |
| `get_project()` / `update_project()` / `soft_delete_project()` | `B01_Dashboard_Repository.py:210,222,253` | 프로젝트 조회·수정·소프트 삭제 |
| `get_user_company()` / `search_companies()` | `B01_Dashboard_Repository.py:273,293` | 소속 회사와 회사 검색 |
| `create_company()` / `create_system_company()` | `B01_Dashboard_Repository.py:307,347` | 사용자·시스템 관리자 회사 생성 |
| `join_company()` | `B01_Dashboard_Repository.py:378` | 회사 가입 신청과 사용자 상태 전이 |
| `list_company_members()` / `add_company_member()` / `remove_company_member()` | `B01_Dashboard_Repository.py:401,416,439` | 회사 팀원 관리 |
| `list_join_requests()` / `process_join_request()` | `B01_Dashboard_Repository.py:453,471` | 가입 신청 조회·승인·거절 |
| `list_all_companies()` / `list_all_users()` | `B01_Dashboard_Repository.py:507,522` | 시스템 관리자 전체 목록 |
| `change_user_role()` / `get_user_admin_target()` / `update_admin_user()` | `B01_Dashboard_Repository.py:538,547,562` | 사용자 역할·관리정보 변경 |
| `count_company_admins()` / `assign_user_company()` | `B01_Dashboard_Repository.py:584,596` | 마지막 관리자 보호와 회사 배정 |
| `list_audit_logs()` / `get_system_resources()` | `B01_Dashboard_Repository.py:609,624` | 감사 로그와 리소스 통계 조회 |
| `list_project_automations()` / `create_project_automation()` | `B01_Dashboard_Repository.py:682,696` | 자동화 목록·생성 |
| `get_project_automation()` / `update_project_automation()` | `B01_Dashboard_Repository.py:722,735` | 자동화 단건 조회·수정 |
| `delete_project_automation()` / `mark_project_automation_executed()` | `B01_Dashboard_Repository.py:758,779` | 자동화 삭제·실행시각 기록 |
## 요청 스키마
| 항목 | 위치 | 역할 |
|---|---|---|
| `UpdateUserRequest` | `B01_Dashboard_Schema.py:6` | 내 정보 수정 검증 |
| `CreateCompanyRequest` | `B01_Dashboard_Schema.py:13` | 회사 생성 검증 |
| `JoinCompanyRequest` / `AddMemberRequest` | `B01_Dashboard_Schema.py:20,24` | 회사 가입·팀원 추가 검증 |
| `ProcessJoinRequest` / `ChangeUserRoleRequest` | `B01_Dashboard_Schema.py:28,33` | 가입 처리·역할 변경 검증 |
| `AssignCompanyRequest` / `UpdateProjectRequest` | `B01_Dashboard_Schema.py:37,41` | 회사 배정·프로젝트 수정 검증 |
| `AdminUpdateUserRequest` / `AutomationRequest` | `B01_Dashboard_Schema.py:51,57` | 관리자 사용자 수정·자동화 검증 |
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| 레거시 상태 추론 | `B01_Dashboard_Repository.py:20` | `_stage_from_status()``project_workflow_stages` 미조회 시 보조값 생성 |
| 700줄 초과 | `B01_Dashboard_Repository.py:1` | 790줄로 프로젝트 단일 파일 제한 초과; 도메인별 저장소 분할 검토 필요 |
+32
View File
@@ -0,0 +1,32 @@
---
type: page-db
status: stable
page_id: B01_Dashboard
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B01_Dashboard/B01_api]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B01_Dashboard_Repository.py 실 코드 직접 확인 (2026-07-12)
---
# B01_Dashboard — DB 사용 관계
공유 테이블의 정의는 concepts에만 둔다. 이 문서는 B01의 사용 목적과 실제 접근 위치만 기록한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `users` | `B01_Dashboard_Repository.py:47` | 내 정보·팀원·전체 사용자·역할·회사 연결 관리 → [[db_schema/users_auth]] |
| `companies` | `B01_Dashboard_Repository.py:273` | 소속·검색·생성·전체 회사 조회 → [[db_schema/users_auth]] |
| `join_requests` | `B01_Dashboard_Repository.py:378` | 회사 가입 신청·승인·거절 → [[db_schema/users_auth]] |
| `projects` | `B01_Dashboard_Repository.py:144` | 역할 범위별 목록·수정·소프트 삭제 → [[db_schema/projects]] |
| `project_workflow_stages` | `B01_Dashboard_Repository.py:83` | 프로젝트 목록의 7단계 상태 배치 조회 → [[workflow_state]] |
| `project_automations` | `B01_Dashboard_Repository.py:682` | 자동화 CRUD와 실행시각 기록 → [[db_schema/projects]] |
| `system_audit_logs` | `B01_Dashboard_Repository.py:245` | 프로젝트·회사·자동화 관리 행위 기록·조회 → [[db_schema/logs_monitoring]] |
| `system_resources` | `B01_Dashboard_Repository.py:624` | 기간별 CPU·메모리·디스크 시계열 조회 → [[db_schema/logs_monitoring]] |
## 트랜잭션 경계
| 항목 | 위치 | 역할 |
|---|---|---|
| 회사 생성 | `B01_Dashboard_Repository.py:307` | 회사 INSERT, 사용자 연결, 감사 로그를 한 트랜잭션으로 처리 |
| 가입 신청 처리 | `B01_Dashboard_Repository.py:471` | `FOR UPDATE`로 신청 행 잠금 후 신청·사용자 상태 동시 변경 |
| 프로젝트 수정·삭제 | `B01_Dashboard_Repository.py:222,253` | 프로젝트 변경과 감사 로그를 같은 트랜잭션으로 처리 |
> B01 코드가 직접 기록하는 감사 테이블은 `audit_logs`가 아니라 `system_audit_logs`다.
@@ -0,0 +1,29 @@
---
type: page-dependencies
status: stable
page_id: B01_Dashboard
related_pages: ["[[B01_Dashboard/B01_frontend]]", "[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B01_Dashboard import 구문 직접 확인 (2026-07-12)
---
# B01_Dashboard — Dependencies
외부 라이브러리의 1차 정의는 [[../../concepts/dependencies]]에 둔다.
| 항목 | 위치 | 역할 |
|---|---|---|
| FastAPI | `B01_Dashboard_Router.py:5` | APIRouter·Depends·HTTPException 제공 |
| Pydantic | `B01_Dashboard_Schema.py:3` | 요청 모델 타입·범위 검증 |
| aiomysql | `B01_Dashboard_Repository.py:10` | MariaDB 비동기 Raw SQL 접근 |
| psutil | `B01_Dashboard_Repository.py:11` | 현재 CPU·메모리 사용률 측정 |
| `shutil` | `B01_Dashboard_Repository.py:6` | 저장소 디스크 사용량 측정 |
| TypeScript DOM | `B01_Dashboard_UI_Page.ts:81` | 역할별 대시보드와 이벤트 구성 |
## 프로젝트 공통 모듈
| 항목 | 위치 | 역할 |
|---|---|---|
| `common_util_auth` | `B01_Dashboard_Router.py:10` | 세션·SYSTEM_ADMIN 권한 의존성 → [[../../concepts/auth_rbac]] |
| `config_db.get_db_pool` | `B01_Dashboard_Repository.py:13` | MariaDB 커넥션 풀 획득 |
| `ui_template_elements` | `B01_Dashboard_UI_Resources.ts:2` | 공통 선형 차트 생성 → [[../../concepts/ui_templates]] |
| `ui_template_locale` | `B01_Dashboard_UI_Resources.ts:1` | 한·영 UI 문자열 선택 → [[../../concepts/ui_templates]] |
@@ -0,0 +1,55 @@
---
type: page-frontend
status: stable
page_id: B01_Dashboard
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B01_Dashboard/B01_api]]", "[[B01_Dashboard/B01_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B01_Dashboard 실 코드 직접 확인 (2026-07-12)
---
# B01_Dashboard — Frontend
## 파일과 진입점
| 항목 | 위치 | 역할 |
|---|---|---|
| `renderB01Dashboard()` | `B01_Dashboard_UI_Page.ts:81` | 사용자 역할과 회사 상태에 맞는 대시보드 렌더링 |
| `request()` | `B01_Dashboard_Api_Fetch.ts:171` | 쿠키 포함 요청과 공통 오류 처리 |
| `buildResourcePanel()` | `B01_Dashboard_UI_Resources.ts:13` | CPU·메모리·디스크 현황과 추이 차트 생성 |
| `B01_Dashboard_UI_Style.css` | `B01_Dashboard_UI_Style.css:1` | 대시보드·표·워크플로우·모달 스타일 |
## UI 권한 헬퍼
| 항목 | 위치 | 역할 |
|---|---|---|
| `canEditProject()` | `B01_Dashboard_UI_Helper.ts:3` | 프로젝트 수정 버튼 표시 판단 |
| `canDeleteProject()` | `B01_Dashboard_UI_Helper.ts:9` | SYSTEM_ADMIN 삭제 버튼 표시 판단 |
| `canAddUser()` | `B01_Dashboard_UI_Helper.ts:15` | ADMIN 이상 팀원 추가 표시 판단 |
| `canChangeRole()` | `B01_Dashboard_UI_Helper.ts:19` | SYSTEM_ADMIN 역할 변경 표시 판단 |
| `canDeleteUser()` | `B01_Dashboard_UI_Helper.ts:24` | 사용자 제거 표시 판단 |
| `canManageAutomation()` | `B01_Dashboard_UI_Helper.ts:30` | ADMIN 이상 자동화 관리 표시 판단 |
> UI 헬퍼는 표시만 제어한다. 최종 권한 검증은 백엔드가 수행한다. 상세: [[../../concepts/auth_rbac]].
## 모달
| 항목 | 위치 | 역할 |
|---|---|---|
| `openEditProjectModal()` | `B01_Dashboard_UI_Modals.ts:75` | 프로젝트 기본정보 수정 |
| `openDeleteProjectModal()` | `B01_Dashboard_UI_Modals.ts:127` | 프로젝트 삭제 확인 |
| `openEditUserModal()` | `B01_Dashboard_UI_Modals.ts:138` | 사용자 정보 수정 |
| `openChangeRoleModal()` | `B01_Dashboard_UI_Modals.ts:183` | 사용자 역할 변경 |
| `openDeleteUserModal()` | `B01_Dashboard_UI_Modals.ts:199` | 사용자 제거 확인 |
| `openCreateCompanyModal()` | `B01_Dashboard_UI_Modals.ts:210` | 회사 생성 |
| `openFindCompanyModal()` | `B01_Dashboard_UI_Modals.ts:235` | 회사 검색·가입 신청 |
| `openAddMemberModal()` | `B01_Dashboard_UI_Modals.ts:273` | 회사 팀원 추가 |
| `openAutomationModal()` | `B01_Dashboard_UI_Modals.ts:286` | 프로젝트 자동화 조회·관리 |
## 공유 자원 연결
| 항목 | 위치 | 역할 |
|---|---|---|
| 인증·역할 | `B01_Dashboard_UI_Page.ts:81` | 역할별 섹션 제어 → [[../../concepts/auth_rbac]] |
| workflow 상태 | `B01_Dashboard_Api_Fetch.ts:370` | 프로젝트 카드 단계 표시 → [[../../concepts/workflow_state]] |
| 공통 UI | `B01_Dashboard_UI_Resources.ts:2` | `createLineChart()` 사용 → [[../../concepts/ui_templates]] |
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| 700줄 초과 | `B01_Dashboard_UI_Page.ts:1` | 739줄로 프로젝트 단일 파일 제한 초과; 기능별 분할 검토 필요 |
@@ -0,0 +1,72 @@
---
status: stable
page_id: B02_ProjRegister
related_pages: ["[[B02_ProjRegister/B02_frontend]]", "[[B02_ProjRegister/B02_db]]", "[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[workflow_state]]", "[[storage_paths]]"]
last_updated: 2026-07-12
---
# B02_ProjRegister — Backend
신규 임도 설계 프로젝트 생성. DB 저장 + 저장소 폴더 초기화 + 워크플로우 7단계 시드 + 감사 로그.
> ⚠️ raw structure.md에는 B02가 프론트만 있는 것으로 기재됐으나, 실 코드에 백엔드가 이미 구현됨 (2026-07-12 확인).
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `B02_ProjRegister_Router.py` | `B02_ProjRegister/B02_ProjRegister_Router.py:1-44` | 프로젝트 생성 API (prefix `/api/b02`) |
| `B02_ProjRegister_Schema.py` | `B02_ProjRegister/B02_ProjRegister_Schema.py:1-30` | 요청/응답 Pydantic 모델 2종 |
| `B02_ProjRegister_Repository.py` | `B02_ProjRegister/B02_ProjRegister_Repository.py:1-136` | Raw SQL 저장소 + 저장소 초기화 |
## API 엔드포인트
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `POST /api/b02/project` | `B02_ProjRegister/B02_ProjRegister_Router.py:15` | 신규 프로젝트 생성. `require_company`로 회사 연결 확인 |
## 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `post_project()` | `B02_ProjRegister/B02_ProjRegister_Router.py:15` | 세션 company_id 검증 → create_project 호출, ValueError→400, 기타→500 |
| `create_project()` | `B02_ProjRegister/B02_ProjRegister_Repository.py:44` | 트랜잭션: projects INSERT + 워크플로우 시드 + 감사로그 + 저장소 초기화 |
| `get_project_by_id()` | `B02_ProjRegister/B02_ProjRegister_Repository.py:120` | 프로젝트 단건 조회(deleted_at IS NULL) |
| `_build_project_storage()` | `B02_ProjRegister/B02_ProjRegister_Repository.py:20` | 저장 경로 생성 + 저장소 루트 이탈 방지 검증 |
| `_initialize_project_storage()` | `B02_ProjRegister/B02_ProjRegister_Repository.py:29` | 단계별 폴더 생성 + workflow.json/project_manifest.json 원자적 기록 |
## 요청/응답 스키마 (Pydantic)
| 모델 | 위치 | 필드 |
|---|---|---|
| `CreateProjectRequest` | `B02_ProjRegister/B02_ProjRegister_Schema.py:6` | name(1~255), region(1~100), road_type(main/branch/fire/stream), project_year(2000~2100), estimated_length_m?(≥0), memo?(≤1000) |
| `CreateProjectResponse` | `B02_ProjRegister/B02_ProjRegister_Schema.py:17` | project_id, name, region, road_type, project_year, estimated_length_m, memo, status, storage_path, created_at |
## 생성 로직 (create_project 트랜잭션)
1. `project_id = uuid4()`, 저장 경로 생성·검증(`_build_project_storage`)
2. **트랜잭션 시작**:
- `projects` INSERT (status `'NEW'`, crs_epsg `5178` 고정)
- `initialize_project_stages(cursor, project_id)` — 워크플로우 7단계 시드 → [[workflow_state]]
- `system_audit_logs` INSERT (`PROJECT_CREATE`, resource_type `project`)
- `_initialize_project_storage()` — 저장소 폴더 + workflow.json + project_manifest.json
- commit (실패 시 rollback)
3. 생성된 프로젝트 재조회 후 반환
## DB 저장 컬럼 (projects INSERT)
`id, user_id, company_id, name, region, road_type, project_year, estimated_length_m, memo, status('NEW'), crs_epsg(5178), storage_path, created_at, updated_at`
→ 테이블 정의 → [[db_schema/projects]]
## 의존성 (공통 유틸)
- [[common_util]] — `common_util_auth`(`require_company`), `common_util_json`(`atomic_write_json`), `common_util_storage`(`PROJECT_STORAGE_LAYOUT_V2`), `common_util_workflow`(`load_project_workflow`), `common_util_workflow_state`(`initialize_project_stages`)
- 워크플로우 시드 → [[workflow_state]]
- 저장소 경로 규칙 → [[storage_paths]]
- `config_system``STORAGE_BASE_DIR`
- DB → [[db_schema/projects]], `system_audit_logs` → [[db_schema/logs_monitoring]]
## 참고
- 프로젝트 생성이 워크플로우 진입점: 생성 직후 프론트는 B03_FileInput으로 이동
- `workflow.json`은 저장소에 기록되나 실제 단계 판정은 `project_workflow_stages`(DB)가 SSOT → [[workflow_state]]
@@ -0,0 +1,30 @@
---
status: stable
page_id: B02_ProjRegister
related_pages: ["[[B02_ProjRegister/B02_backend]]", "[[B02_ProjRegister/B02_frontend]]", "[[db_schema/projects]]", "[[workflow_state]]"]
last_updated: 2026-07-12
---
# B02_ProjRegister — DB
이 페이지가 쓰는 테이블 (정의는 concepts에, 여기선 용도만).
## 쓰는 테이블
| 테이블 | 이 페이지의 용도 | 정의 |
|---|---|---|
| `projects` | 신규 프로젝트 INSERT (status `'NEW'`, crs_epsg `5178`). 단건 조회 | [[db_schema/projects]] |
| `project_workflow_stages` | 생성 시 7단계 시드(`initialize_project_stages`) | [[workflow_state]] |
| `system_audit_logs` | `PROJECT_CREATE` 행위 기록 | [[db_schema/logs_monitoring]] |
## 저장소(파일시스템)
- `storage/{company_id}/{user_id}/{project_id}/` 생성 + `workflow.json`/`project_manifest.json` 원자적 기록
- 경로 생성 책임·규칙 → [[storage_paths]]
- `PROJECT_STORAGE_LAYOUT_V2` 기준 단계별 폴더
## 참고 (계획 당시 의도)
- 계획서(plan_workflow_state_management)에는 "생성 시 project_workflow_stages 7행 시드"가 마이그레이션 Phase 1 과제로 기재됐으나,
**실 코드에 이미 구현됨**(`B02_ProjRegister_Repository.py:90` `initialize_project_stages`). → 규칙6, 실 코드 채택
- `workflow.json`도 저장소에 기록되나 단계 판정 SSOT는 `project_workflow_stages`(DB) → [[workflow_state]]
@@ -0,0 +1,83 @@
---
status: stable
page_id: B02_ProjRegister
related_pages: ["[[B02_ProjRegister/B02_backend]]", "[[B02_ProjRegister/B02_db]]", "[[ui_templates]]", "[[a00_app_shell_framework]]"]
last_updated: 2026-07-12
---
# B02_ProjRegister — Frontend
신규 임도 설계 프로젝트 등록 폼. 기본정보(이름·지역·임도종류·연도·연장·메모) 입력 → 생성 후 B03으로 이동.
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `B02_ProjRegister_UI_Page.ts` | `B02_ProjRegister/B02_ProjRegister_UI_Page.ts:1-179` | 등록 폼 렌더링 + 제출 로직 |
| `B02_ProjRegister_UI_Style.css` | `B02_ProjRegister/B02_ProjRegister_UI_Style.css:1-66` | 스타일 (theme.css 변수만 사용) |
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderB02ProjRegister(root)` | `B02_ProjRegister/B02_ProjRegister_UI_Page.ts:35` | 헤더+카드(2열 그리드 폼) 조립 |
| `onB02_Proj_Submit_Click()` | `B02_ProjRegister/B02_ProjRegister_UI_Page.ts:96` | 제출 핸들러(검증→fetch POST→B03 이동) |
## 입력 필드
| 필드 | 컴포넌트 | 비고 |
|---|---|---|
| 프로젝트명 | createInputField | 필수 |
| 지역 | createInputField | 필수 |
| 임도 종류 | createSelectField | main/branch/fire/stream 4종 |
| 사업연도 | createInputField(number) | 기본 올해, min 2000/max 올해+5 |
| 예상 연장(m) | createInputField(number) | 선택, min 0 |
| 메모 | createInputField(text) | 선택 |
## 제출 로직
1. **1차 유효성 검사**: name/region 필수, project_year 정수·2000~2100, estimated_length_m ≥0(입력 시)
2. `fetch("${API_BASE_URL}/b02/project", POST)` 직접 호출 (별도 Api_Fetch 파일 없음)
- body: name, region, road_type, project_year, estimated_length_m, memo
3. 성공: `localStorage``CURRENT_PROJECT_ID_KEY`로 project_id 저장 → 성공 토스트 → `navigateTo(ROUTES.B03_FILE_INPUT)`
4. 실패: 응답 detail/message 파싱해 에러 토스트
## 로컬라이제이션
모든 텍스트는 [[ui_templates]]에서 로드. 하드코딩 금지.
| UI 요소 | 로케일 키 |
|---|---|
| 제목/부제 | `B02_Proj_Title`, `B02_Proj_Subtitle` |
| 필드 | `B02_Proj_Field_Name`, `_Region`, `_RoadType`, `_Year`, `_Length`, `_Memo` (+각 Placeholder) |
| 임도종류 옵션 | `B02_Proj_RoadType_Main`, `_Branch`, `_Fire`, `_Stream` |
| 제출/성공 | `B02_Proj_Submit`, `B02_Proj_Success` |
| 오류 | `B02_Proj_Error_Required`, `Common_Validation_NumberRange`(공통) |
## 스타일 (CSS)
- `.b02-proj` — 페이지 래퍼 (max-width: 720px)
- `.b02-proj__header` — 제목+부제
- `.b02-proj__grid` — 2열 필드 그리드 (640px 이하 1열)
- `.b02-proj__select` — 드롭다운 화살표 여백
- `.b02-proj__submit` — 좌측 정렬 버튼
- 폼 전체는 `createCard({ raised: true })`로 감쌈
## 이벤트 핸들러
| 함수 | 위치 | 동작 |
|---|---|---|
| `onB02_Proj_Submit_Click()` | `B02_ProjRegister/B02_ProjRegister_UI_Page.ts:96` | 프로젝트 생성 검증·제출 |
## 의존성
- [[a00_app_shell_framework]] — `navigateTo()` 라우팅
- `config_frontend.ts``ROUTES`, `API_BASE_URL`, `CURRENT_PROJECT_ID_KEY`
- `common_util_validate``isBlank`
- [[ui_templates]] — `createButton`, `createInputField`, `createSelectField`, `createCard`, `showLoadingOverlay`/`hideLoadingOverlay`, `showToast`
- 백엔드 API → [[B02_ProjRegister/B02_backend]]
## 참고
- 프론트가 fetch를 직접 호출 (A08처럼 별도 API 클라이언트 파일 없음)
- 프로젝트 생성 성공 시 `CURRENT_PROJECT_ID_KEY`가 이후 워크플로우(B03~) 진입의 컨텍스트가 됨
+32
View File
@@ -0,0 +1,32 @@
---
type: page-api
status: stable
page_id: B03_FileInput
related_pages: ["[[B03_FileInput/B03_frontend]]", "[[B03_FileInput/B03_backend]]", "[[B03_FileInput/B03_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B03_FileInput_Router.py 실 코드 직접 확인 (2026-07-12)
---
# B03_FileInput — API
기본 prefix: `/api/projects` (`B03_FileInput_Router.py:66`). 세션 인증과 공통 오류 형식은 [[../../concepts/auth_rbac]], [[../../concepts/api_common]]을 참조한다.
## 일반 업로드
| 항목 | 위치 | 역할 |
|---|---|---|
| `POST /{project_id}/files` | `B03_FileInput_Router.py:267` | LAS/TIF/TFW/PRJ/DXF 다중 업로드·메타데이터 등록 |
## 청크 업로드
| 항목 | 위치 | 역할 |
|---|---|---|
| `POST /{project_id}/upload-sessions` | `B03_FileInput_Router.py:400` | 청크 업로드 세션 생성 |
| `POST /{project_id}/chunks` | `B03_FileInput_Router.py:443` | 청크 1개 저장 및 진행률 반환 |
| `POST /{project_id}/finalize` | `B03_FileInput_Router.py:507` | 모든 청크 병합·원본 파일 등록 |
| `GET /{project_id}/upload-status/{session_id}` | `B03_FileInput_Router.py:623` | 세션 상태·완료 청크 인덱스 조회 |
## workflow 조회
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /{project_id}/workflow-state` | `B03_FileInput_Router.py:667` | `current_stage`와 7단계 상태 반환 |
전체 경로는 `/api/projects/{project_id}/...`이며, workflow 상태 공통 정의는 [[../../concepts/workflow_state]]에 둔다.
@@ -0,0 +1,49 @@
---
type: page-backend
status: stable
page_id: B03_FileInput
related_pages: ["[[B03_FileInput/B03_frontend]]", "[[B03_FileInput/B03_api]]", "[[B03_FileInput/B03_db]]", "[[B03_FileInput/B03_dependencies]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B03_FileInput 실 코드 직접 확인 (2026-07-12)
---
# B03_FileInput — Backend
## 입력 검증·파일 처리
| 항목 | 위치 | 역할 |
|---|---|---|
| `FileUploadDescriptor` | `B03_FileInput_Schema.py:11` | 경로 이탈·확장자·크기 검증 |
| `resolve_upload_destination()` | `B03_FileInput_Engine.py:15` | 프로젝트 WF0 저장 목적지 결정 |
| `save_upload_stream()` | `B03_FileInput_Engine.py:31` | 일반 업로드 스트림 저장 |
| `resolve_chunk_session_dir()` | `B03_FileInput_Engine.py:64` | 임시 청크 디렉터리 결정 |
| `save_upload_chunk()` / `merge_upload_chunks()` | `B03_FileInput_Engine.py:77,124` | 청크 저장·순서 병합 |
| `remove_chunk_session()` | `B03_FileInput_Engine.py:173` | 청크 임시 디렉터리 정리 |
## 메타데이터 분석
| 항목 | 위치 | 역할 |
|---|---|---|
| `analyze_las_metadata()` | `B03_FileInput_Engine_Analyze.py:13` | LAS 포인트 수·범위·밀도 분석 |
| `analyze_prj_metadata()` | `B03_FileInput_Engine_Analyze.py:62` | PRJ CRS 분석 |
| `analyze_tfw_metadata()` | `B03_FileInput_Engine_Analyze.py:95` | TFW 변환계수 분석 |
| `analyze_tif_metadata()` | `B03_FileInput_Engine_Analyze.py:119` | TIF 래스터·CRS·범위 분석 |
| `analyze_input_metadata()` | `B03_FileInput_Engine_Analyze.py:146` | 확장자별 분석 라우팅 |
## 저장소
| 항목 | 위치 | 역할 |
|---|---|---|
| `create_input_file()` | `B03_FileInput_Repository.py:11` | `input_files` 원본 메타데이터 등록 |
| `get_project_storage_relative_path()` | `B03_FileInput_Repository.py:64` | 프로젝트 상대 저장경로 조회 |
| `create_upload_session()` / `get_upload_session()` | `B03_FileInput_Repository.py:89,135` | 청크 세션 생성·조회 |
| `upsert_upload_chunk()` / `list_completed_chunk_indexes()` | `B03_FileInput_Repository.py:165,216` | 청크 기록·완료 인덱스 조회 |
| `mark_upload_session_completed()` / `mark_upload_session_failed()` | `B03_FileInput_Repository.py:236,253` | 세션 성공·실패 상태 기록 |
## workflow·알림
| 항목 | 위치 | 역할 |
|---|---|---|
| `trigger_wf1_analysis_and_email()` | `B03_FileInput_Router.py:147` | WF1 분석 실행과 완료·실패 이메일 연결 |
| `start_stage()` | `B03_FileInput_Router.py:175` | WF1 시작, stage 1 IN_PROGRESS 전이 |
| `complete_stage()` | `B03_FileInput_Router.py:224,347,570` | WF0/WF1 완료 전이 |
| `fail_stage()` | `B03_FileInput_Router.py:256` | WF1 실패 전이 |
| `get_project_workflow_state()` | `B03_FileInput_Router.py:668` | 단일 프로젝트 workflow 상태 반환 |
workflow 정의: [[../../concepts/workflow_state]], 저장경로: [[../../concepts/storage_paths]].
+29
View File
@@ -0,0 +1,29 @@
---
type: page-db
status: stable
page_id: B03_FileInput
related_pages: ["[[B03_FileInput/B03_backend]]", "[[B03_FileInput/B03_api]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B03_FileInput_Repository.py 실 코드 직접 확인 (2026-07-12)
---
# B03_FileInput — DB 사용 관계
공유 테이블 정의는 [[db_schema/files_surface]]에서 1차 관리한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `input_files` | `B03_FileInput_Repository.py:11` | 원본 파일 유형·상대경로·크기·메타데이터 등록 |
| `upload_sessions` | `B03_FileInput_Repository.py:89` | 청크 업로드 세션과 전체 파일 정보 저장 |
| `upload_chunks` | `B03_FileInput_Repository.py:165` | 청크 인덱스·해시·크기 저장 |
| `projects` | `B03_FileInput_Router.py:111` | 업로드·WF1 진행상태 호환 캐시 갱신 |
| `project_workflow_stages` | `B03_FileInput_Router.py:175` | stage 0/1 시작·완료·실패 전이와 상태 조회 |
## 파일 경로
| 항목 | 위치 | 역할 |
|---|---|---|
| 원본 입력 | `B03_FileInput_Engine.py:15` | `B03_FileInput/input/{file_type}/`에 저장 |
| 청크 임시파일 | `B03_FileInput_Engine.py:64` | 프로젝트 저장소 하위 임시 세션 디렉터리 사용 |
| DB 저장값 | `B03_FileInput_Repository.py:64` | 프로젝트 루트 기준 상대경로 저장 |
경로 규칙 상세: [[storage_paths]].
@@ -0,0 +1,30 @@
---
type: page-dependencies
status: stable
page_id: B03_FileInput
related_pages: ["[[B03_FileInput/B03_frontend]]", "[[B03_FileInput/B03_backend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B03_FileInput import 구문 직접 확인 (2026-07-12)
---
# B03_FileInput — Dependencies
공유 의존성의 1차 정의는 [[../../concepts/dependencies]]에 둔다.
| 항목 | 위치 | 역할 |
|---|---|---|
| FastAPI | `B03_FileInput_Router.py:10` | multipart 파일·폼 요청과 라우터 |
| Pydantic | `B03_FileInput_Schema.py:6` | 파일명·크기·확장자·청크 요청 검증 |
| aiomysql | `B03_FileInput_Router.py:9` | MariaDB 비동기 트랜잭션 |
| laspy | `B03_FileInput_Engine_Analyze.py:7` | LAS 포인트클라우드 메타데이터 분석 |
| rasterio | `B03_FileInput_Engine_Analyze.py:9` | TIF 래스터 메타데이터 분석 |
| pyproj | `B03_FileInput_Engine_Analyze.py:10` | PRJ/TIF CRS 해석 |
| numpy | `B03_FileInput_Engine_Analyze.py:8` | LAS 통계 계산 |
## 공통 모듈
| 항목 | 위치 | 역할 |
|---|---|---|
| `common_util_storage` | `B03_FileInput_Engine.py:11` | 단계별 저장경로 생성·해석 → [[../../concepts/storage_paths]] |
| `common_util_workflow_state` | `B03_FileInput_Router.py:50` | stage 상태 전이 → [[../../concepts/workflow_state]] |
| `common_util_email` | `B03_FileInput_Email.py:8` | 업로드·분석 완료/실패 이메일 발송 |
| `common_util_json` | `B03_FileInput_Router.py:47` | workflow 보조 JSON 원자적 저장 |
@@ -0,0 +1,46 @@
---
type: page-frontend
status: stable
page_id: B03_FileInput
related_pages: ["[[B03_FileInput/B03_backend]]", "[[B03_FileInput/B03_api]]", "[[B03_FileInput/B03_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B03_FileInput 실 코드 직접 확인 (2026-07-12)
---
# B03_FileInput — Frontend
## 페이지·업로드 흐름
| 항목 | 위치 | 역할 |
|---|---|---|
| `renderB03FileInput()` | `B03_FileInput_UI_Page.ts:170` | 파일 입력 화면과 이벤트 등록 |
| 청크 세션 생성 | `B03_FileInput_UI_Page.ts:515` | 대용량 파일을 청크 업로드로 시작 |
| 청크 병합 완료 | `B03_FileInput_UI_Page.ts:558` | 서버 병합 결과를 상태에 저장 |
| WF1 진행률 폴링 | `B03_FileInput_UI_Page.ts:577` | 분석 상태와 진행률 갱신 |
| B04 이동 | `B03_FileInput_UI_Page.ts:609,693` | WF1 완료 시 `ROUTES.B04_WF1_SURFACE`로 이동 |
## API 클라이언트
| 항목 | 위치 | 역할 |
|---|---|---|
| `uploadProjectFiles()` | `B03_FileInput_Api_Fetch.ts:60` | 일반 다중 파일 업로드 |
| `createUploadSession()` | `B03_FileInput_Api_Fetch.ts:82` | 청크 세션 생성 |
| `uploadFileChunk()` | `B03_FileInput_Api_Fetch.ts:100` | 단일 청크 전송 |
| `finalizeUploadSession()` | `B03_FileInput_Api_Fetch.ts:119` | 청크 병합 요청 |
| `fetchUploadStatus()` | `B03_FileInput_Api_Fetch.ts:133` | 청크 업로드 상태 조회 |
| `checkWF1AnalysisStatus()` | `B03_FileInput_Api_Fetch.ts:154` | WF1 분석 상태 조회 |
## 브라우저 상태·오프라인 보조
| 항목 | 위치 | 역할 |
|---|---|---|
| `loadB03ProjectState()` | `B03_FileInput_State.ts:23` | 프로젝트별 저장 상태 복원 |
| `saveB03UploadedFile()` | `B03_FileInput_State.ts:34` | 업로드 파일 결과 저장 |
| `updateB03AnalysisState()` | `B03_FileInput_State.ts:53` | 분석 상태·진행률 저장 |
| `restoreB03ProjectState()` | `B03_FileInput_State.ts:71` | 재진입 시 상태와 완료 콜백 복원 |
| `registerB03ServiceWorker()` | `B03_FileInput_UI_Page.ts:622` | Service Worker 등록 시도와 안내 토스트 |
공통 UI는 [[../../concepts/ui_templates]], 단계 상태는 [[../../concepts/workflow_state]]를 참조한다.
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| 700줄 근접 | `B03_FileInput_UI_Page.ts:1` | 695줄; 추가 기능은 상태·업로드·화면 분할 검토 필요 |
| CSS 대형 파일 | `B03_FileInput_UI_Style.css:1` | 376줄; 공통 테마와 페이지 스타일 분리 검토 |
@@ -0,0 +1,43 @@
---
status: stable
page_id: B04_wf1_Surface
related_pages: ["[[B04_wf1_Surface/B04_backend]]", "[[B04_wf1_Surface/B04_frontend]]", "[[B04_wf1_Surface/B04_db]]", "[[B04_wf1_Surface/B04_dependencies]]"]
last_updated: 2026-07-12
---
# B04_wf1_Surface — API
실제 라우터 prefix: `/api/projects` (`B04_wf1_Surface_Router.py:41`). 총 12개 엔드포인트.
> ⚠️ 이전 계획서 기반 api.md에는 "추정 경로 surface/analyze·models 2개"로만 기재됐으나,
> 실 코드는 아래 12개(분석·모델·미리보기·등고선·VWorld·GeoJSON 포함). 실 코드 채택(규칙6).
## 엔드포인트
| 엔드포인트 | 위치 | 역할 |
|---|---|---|
| `POST /{project_id}/surface/analyze` | `B04_wf1_Surface_Router.py:147` | LAS 구조화+지면필터+모델생성 실행, DB 기록, WF1 stage 전이 |
| `GET /{project_id}/surface/models` | `B04_wf1_Surface_Router.py:241` | 생성된 지표면 모델 목록 |
| `GET /{project_id}/surface/input-files` | `B04_wf1_Surface_Router.py:260` | 분석 대상 LAS/LAZ 입력파일 목록 |
| `GET /{project_id}/surface/point-cloud` | `B04_wf1_Surface_Router.py:279` | 3D 미리보기용 포인트클라우드 샘플 |
| `GET /{project_id}/surface/ground-stats` | `B04_wf1_Surface_Router.py:341` | 필터별 지면 포인트 통계 |
| `GET /{project_id}/surface/status` | `B04_wf1_Surface_Router.py:372` | WF1 분석 상태 조회 (workflow_state 우선, 레거시 폴백) |
| `GET /{project_id}/surface/models/{model_id}/preview` | `B04_wf1_Surface_Router.py:484` | 모델 미리보기 |
| `GET /{project_id}/surface/models/{model_id}/contour` | `B04_wf1_Surface_Router.py:557` | 등고선 생성/조회 |
| `GET /{project_id}/vworld-meta` | `B04_wf1_Surface_Router.py:643` | VWorld 배경지도 메타 |
| `GET /{project_id}/vworld-map` | `B04_wf1_Surface_Router.py:676` | VWorld 배경지도 타일 |
| `GET /{project_id}/geojson` | `B04_wf1_Surface_Router.py:712` | GIS 벡터 GeoJSON |
## 요청/응답 스키마 (Pydantic)
| 모델 | 위치 | 역할 |
|---|---|---|
| `SurfaceAnalyzeRequest` | `B04_wf1_Surface_Schema.py:16` | input_file_id, source_filters?, methods?, force. resolved_filters/methods로 화이트리스트 검증 |
| `SurfaceAnalyzeResponse` | `B04_wf1_Surface_Schema.py:97` | ground_summary, manifest_status, surface_model_ids |
| `SurfaceModelSummary` / `SurfaceModelListResponse` | `B04_wf1_Surface_Schema.py:45,107` | 모델 요약/목록 |
| `SurfaceInputFileSummary` / `...ListResponse` | `B04_wf1_Surface_Schema.py:56,69` | 입력파일 요약/목록 |
| `SurfacePointCloudSampleResponse` | `B04_wf1_Surface_Schema.py:77` | 포인트클라우드 샘플(bounds/points/rgb) |
| `SurfaceGroundStatsResponse` | `B04_wf1_Surface_Schema.py:89` | 필터별 통계 |
- 공통 오류 포맷 `{"status":"error","message":...}` → [[schema_common]]
- 분석 파라미터 화이트리스트: `SURFACE_MODEL_SOURCE_FILTERS`+`ransac`, `SURFACE_MODEL_PRECOMPUTE` (config_system)
@@ -0,0 +1,71 @@
---
status: stable
page_id: B04_wf1_Surface
related_pages: ["[[B04_wf1_Surface/B04_frontend]]", "[[B04_wf1_Surface/B04_api]]", "[[B04_wf1_Surface/B04_db]]", "[[B04_wf1_Surface/B04_dependencies]]", "[[B03_FileInput/B03_backend]]", "[[B05_wf2_Route/B05_backend]]"]
last_updated: 2026-07-12
---
# B04_wf1_Surface — Backend
WF1 지표면 분석. LAS 구조화 → 지면 필터 → 지표면 5종 모델 생성 → 스무딩/등고선. 무거운 연산은 `asyncio.to_thread`로 실행.
## 파일 구성 (오케스트레이터/저장소/라우터)
| 파일 | 위치 | 역할 |
|---|---|---|
| `B04_wf1_Surface_Router.py` | `B04_wf1_Surface_Router.py:1-739` | `/api/projects` 12개 엔드포인트 → [[B04_wf1_Surface/B04_api]] |
| `B04_wf1_Surface_Schema.py` | `B04_wf1_Surface_Schema.py:1-113` | 요청/응답 검증 |
| `B04_wf1_Surface_Repository.py` | `B04_wf1_Surface_Repository.py:1-236` | processed_point_cloud/surface_models Raw SQL |
| `B04_wf1_Surface_Engine.py` | `B04_wf1_Surface_Engine.py:27` | `run_surface_analysis()` 오케스트레이터(구조화→필터→모델빌드) |
## 엔진 서브모듈
| 파일 | 역할 |
|---|---|
| `..._Engine_Structurize.py` | LAS/LAZ 구조화(structured.npz) |
| `..._Engine_Ground.py` | 지면 마스크 생성/요약(`build_ground_masks`, `summarize_masks`) |
| `..._Engine_Filter_Grid/CSF/PMF/RANSAC.py` | 지면 필터 4종 |
| `..._Engine_Pipeline.py` | `build_all_terrain_models()` 지표면 모델 파이프라인 + manifest |
| `..._Engine_ModelContext.py` | 모델 공통 컨텍스트/메시 유틸 |
| `..._Engine_ModelBuild.py` | TIN/DTM/NURBS/implicit/meshfree 생성 |
| `..._Engine_Smooth.py` | DTM/TIN 스무딩 |
| `..._Engine_Contour.py` | 등고선 생성 |
| `..._Engine_GisVector.py` / `..._Engine_VWorld.py` / `..._Engine_MvtHelper.py` | GIS 벡터 / VWorld 배경지도 / MVT 헬퍼 |
## 주요 함수 (Router)
| 함수 | 위치 | 역할 |
|---|---|---|
| `analyze_surface()` | `B04_wf1_Surface_Router.py:147` | 분석 실행: start_stage→to_thread(run_surface_analysis)→DB저장→complete_stage |
| `save_surface_analysis_to_db()` | `B04_wf1_Surface_Router.py:90` | processed_point_cloud + surface_models INSERT (호출자 트랜잭션) |
| `get_wf1_analysis_status()` | `B04_wf1_Surface_Router.py:372` | 상태 조회(project_workflow_stages 우선, 폴백) |
| `update_project_status()` | `B04_wf1_Surface_Router.py:76` | ⚠️ 정의만 있고 **호출처 없음**(죽은 코드, grep 확인) |
## 워크플로우 상태 전이
analyze 흐름에서 `project_workflow_stages`(stage 1) 전이:
- `start_stage(cursor, project_id, 1, params)``B04_wf1_Surface_Router.py:168`
- `complete_stage(cursor, project_id, 1)``B04_wf1_Surface_Router.py:209`
- `fail_stage(cursor, project_id, 1, err)``B04_wf1_Surface_Router.py:227,233`
→ SSOT 정의·전파 규칙 → [[workflow_state]]
## ⚠️ 레거시 폴백 (416,420줄)
`get_wf1_analysis_status()``project_workflow_stages`**우선** 조회하고,
행이 없을 때만(백필 미작동 대비) `projects.status` 문자열 비교로 폴백:
- 416줄 `project_status == "WF1_COMPLETE"`, 420줄 `"WF1_ANALYZING"` (`B04_wf1_Surface_Router.py:412-427`)
이전 wiki는 "구형 잔재, 정리 필요"로 표기했으나, 실제로는 정상 경로가 아닌 **방어적 폴백**.
SSOT가 안정화되면 제거 가능하나 현재는 백필 안전장치로 동작 중.
## 책임
LAS 구조화·지면 필터 → 변환 포인트클라우드 저장 → 지표면 5종 모델 생성 → 스무딩/등고선/미리보기 → 모델 확정 및 하위 단계(B05) stale 전파([[workflow_state]]).
## 의존성
- [[common_util]] — `common_util_workflow_state`(start/complete/fail_stage), 저장소 경로 헬퍼
- 외부 라이브러리 → [[B04_wf1_Surface/B04_dependencies]]
- DB → [[B04_wf1_Surface/B04_db]]
- config: `SURFACE_MODEL_SOURCE_FILTERS`, `SURFACE_MODEL_PRECOMPUTE`, `build_surface_model_config`
+34
View File
@@ -0,0 +1,34 @@
---
status: stable
page_id: B04_wf1_Surface
related_pages: ["[[B04_wf1_Surface/B04_backend]]", "[[B04_wf1_Surface/B04_api]]", "[[B04_wf1_Surface/B04_dependencies]]", "[[db_schema/files_surface]]"]
last_updated: 2026-07-12
---
# B04_wf1_Surface — DB
이 페이지가 쓰는 테이블 (정의는 concepts에, 여기선 용도만).
## 쓰는 테이블
| 테이블 | 이 페이지의 용도 | 정의 |
|---|---|---|
| `processed_point_cloud` | 필터/변환된 포인트클라우드 저장(input_files 참조) | [[db_schema/files_surface]] |
| `surface_models` | TIN/DTM/NURBS/implicit/meshfree 모델 메타데이터 | [[db_schema/files_surface]] |
| `terrain_layers` | 등고선 등 파생 레이어 | [[db_schema/files_surface]] |
| `project_workflow_stages` | WF1(stage 1) 상태 전이·조회 | [[workflow_state]] |
| `input_files` | 분석 대상 LAS/LAZ 조회 | [[db_schema/files_surface]] |
| `projects` | (레거시 폴백) status 조회 | [[db_schema/projects]] |
## Repository 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `create_processed_point_cloud()` | `B04_wf1_Surface_Repository.py` | processed_point_cloud INSERT |
| (surface_models INSERT) | `B04_wf1_Surface_Repository.py` | 모델별 메타데이터 저장 |
| `get_input_file()` | `B04_wf1_Surface_Repository.py` | 입력 LAS 단건 조회 |
## 참고
- `save_surface_analysis_to_db()`([[B04_wf1_Surface/B04_backend]])가 processed_point_cloud + surface_models를 한 트랜잭션으로 저장(호출자가 begin/commit)
- WF1 단계 판정 SSOT는 `project_workflow_stages`. `projects.status`는 레거시 폴백 경로에서만 참조 → [[workflow_state]]
@@ -0,0 +1,34 @@
---
status: stable
page_id: B04_wf1_Surface
related_pages: ["[[B04_wf1_Surface/B04_backend]]", "[[B04_wf1_Surface/B04_frontend]]", "[[dependencies]]"]
last_updated: 2026-07-12
---
# B04_wf1_Surface — Dependencies
이 페이지가 쓰는 외부 라이브러리 (공통 정의는 [[dependencies]]).
## 백엔드 (Python)
| 라이브러리 | 용도 |
|---|---|
| Laspy | LAS/LAZ 구조화 |
| Whitebox / Rasterio | 지면 필터·DEM 처리 |
| Trimesh | TIN/메시 모델 |
| Geopandas / Shapely | GIS 벡터(GeoJSON, 등고선) |
| NumPy | structured.npz 등 배열 연산 |
→ 공통 정의·버전 → [[dependencies]]
## 프론트엔드 (TypeScript)
| 라이브러리 | 위치 | 용도 |
|---|---|---|
| **three.js** | `B04_wf1_Surface_UI_Viewer.ts:1`, `..._TerrainViewer.ts` | WebGL 3D 뷰어(WebGLRenderer, OrbitControls, THREE.Points/Mesh) |
## ⚠️ dependencies concept 정정 필요
[[dependencies]]의 "HTML5 Canvas / WebGL — WebCAD 렌더링 (B04~B06 뷰어, **현재 미구현**)"은
실 코드와 불일치. B04는 three.js로 3D 뷰어가 **구현됨** → [[B04_wf1_Surface/B04_frontend]].
dependencies.md에 three.js 항목 추가·"미구현" 문구 정정 필요 (B05/B06 뷰어 상태는 각 페이지 인제스트 시 확인).
@@ -0,0 +1,66 @@
---
status: stable
page_id: B04_wf1_Surface
related_pages: ["[[B04_wf1_Surface/B04_backend]]", "[[B04_wf1_Surface/B04_api]]", "[[B04_wf1_Surface/B04_dependencies]]", "[[ui_templates]]"]
last_updated: 2026-07-12
---
# B04_wf1_Surface — Frontend
WF1 지표면 분석 화면. 워크플로우 레이아웃(필터/표현 폼 + 모델 목록) + **three.js 기반 3D 뷰어**(포인트클라우드/지형).
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `B04_wf1_Surface_UI_Page.ts` | `B04_wf1_Surface_UI_Page.ts:1-377` | 페이지 조립 + 분석 실행/폴링/뷰어 렌더 |
| `B04_wf1_Surface_UI_Viewer.ts` | `B04_wf1_Surface_UI_Viewer.ts:1-567` | 포인트클라우드 3D 뷰어(three.js) |
| `B04_wf1_Surface_UI_TerrainViewer.ts` | `B04_wf1_Surface_UI_TerrainViewer.ts:1-637` | 지형 메시 3D 뷰어(three.js) |
| `B04_wf1_Surface_Api_Fetch.ts` | `B04_wf1_Surface_Api_Fetch.ts:1-174` | surface API 클라이언트 |
| `B04_wf1_Surface_UI_Style.css` | `B04_wf1_Surface_UI_Style.css` | 폼 그룹/모델 카드/뷰어 스타일 |
## ⚠️ 계획서와 실 코드 불일치 (3D 뷰어)
이전 wiki(계획서 기반)는 **"WebCAD 3D 뷰어 미구현"**으로 기재했으나,
실 코드에는 three.js(WebGL) 기반 뷰어가 **구현되어 있음**:
- `createSurfacePointCloudViewer` (`B04_wf1_Surface_UI_Viewer.ts:187` WebGLRenderer, OrbitControls, THREE.Points)
- `createSurfaceTerrainViewer` (`B04_wf1_Surface_UI_TerrainViewer.ts`)
- UI_Page에서 import·인스턴스화(`B04_wf1_Surface_UI_Page.ts:37,38,110,356`)
→ 규칙6, 실 코드 채택. dependencies도 three.js 반영 → [[B04_wf1_Surface/B04_dependencies]]
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderB04Surface(root)` | `B04_wf1_Surface_UI_Page.ts:95` | 워크플로우 레이아웃+뷰어 조립(async) |
| `createSurfacePointCloudViewer()` | `B04_wf1_Surface_UI_Viewer.ts:187` | 포인트클라우드 뷰어 핸들 생성 |
| `createSurfaceTerrainViewer()` | `B04_wf1_Surface_UI_TerrainViewer.ts` | 지형 메시 뷰어 핸들 생성 |
## API 클라이언트 함수
| 함수 | 위치 | 대응 API |
|---|---|---|
| `analyzeSurface()` | `B04_wf1_Surface_Api_Fetch.ts:117` | `POST .../surface/analyze` |
| `listSurfaceModels()` | `B04_wf1_Surface_Api_Fetch.ts:128` | `GET .../surface/models` |
| `listSurfaceInputFiles()` | `B04_wf1_Surface_Api_Fetch.ts:134` | `GET .../surface/input-files` |
| `fetchSurfacePointCloud()` | `B04_wf1_Surface_Api_Fetch.ts:142` | `GET .../surface/point-cloud` |
| `fetchSurfaceGroundStats()` | `B04_wf1_Surface_Api_Fetch.ts:153` | `GET .../surface/ground-stats` |
| `fetchSurfaceStatus()` | `B04_wf1_Surface_Api_Fetch.ts:161` | `GET .../surface/status` |
| `getVWorldMapUrl()` / `fetchVWorldMeta()` | `B04_wf1_Surface_Api_Fetch.ts:182,186` | VWorld 배경지도 |
| `fetchGisGeoJson()` | `B04_wf1_Surface_Api_Fetch.ts:192` | `GET .../geojson` |
## 처리 흐름
1. 워크플로우 레이아웃(`createWorkflowLayout`) + 스텝(`workflowSteps`) 조립
2. 입력파일/모델 목록 로드 → 필터·표현 폼 구성
3. 분석 실행(`analyzeSurface`) → 상태 폴링(`fetchSurfaceStatus`)
4. 완료 시 포인트클라우드(`fetchSurfacePointCloud`) → `viewer.render()` (`B04_wf1_Surface_UI_Page.ts:356`)
## 의존성
- [[a00_app_shell_framework]] — `navigateTo`, `workflowSteps`(b_page_scaffold)
- `config_frontend.ts``API_BASE_URL`, `CURRENT_PROJECT_ID_KEY`, `ROUTES`, `RENDER_OPTIONS`, `API_TIMEOUT_MS`
- [[ui_templates]] — `createWorkflowLayout`(ui_template_workflow_layout) 등
- **three.js** (WebGL 3D 뷰어) → [[B04_wf1_Surface/B04_dependencies]]
- 백엔드 API → [[B04_wf1_Surface/B04_api]]
+28
View File
@@ -0,0 +1,28 @@
---
type: page-api
status: stable
page_id: B05_wf2_Route
related_pages: ["[[B05_wf2_Route/B05_frontend]]", "[[B05_wf2_Route/B05_backend]]", "[[B05_wf2_Route/B05_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B05_wf2_Route_Router.py 실 코드 직접 확인 (2026-07-12)
---
# B05_wf2_Route — API
기본 prefix: `/api/projects` (`B05_wf2_Route_Router.py:30`). 공통 세션·오류 형식은 [[../../concepts/auth_rbac]], [[../../concepts/api_common]]을 참조한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `POST /{project_id}/route/solve` | `B05_wf2_Route_Router.py:33` | RouteSolveRequest 검증, 경로 계산, 결과 3종 DB 저장 |
| `POST /{project_id}/route/confirm` | `B05_wf2_Route_Router.py:136` | 최신 DRAFT 경로를 CONFIRMED로 변경 |
## solve 처리 결과
| 항목 | 역할 |
|---|---|
| `route_id` | 새 경로 식별자 |
| `total_length_m` | 계산 경로 총연장 |
| `metrics` | 경사·절성토량·비용 등 통계 |
| `required_points_ok` | BP/EP/CP 조건 충족 여부 |
| `route_data_path` | GeoJSON LineString 상대경로 |
오류는 표준 `{"status":"error","message":...}` 형식으로 반환한다.
@@ -0,0 +1,50 @@
---
type: page-backend
status: stable
page_id: B05_wf2_Route
related_pages: ["[[B05_wf2_Route/B05_frontend]]", "[[B05_wf2_Route/B05_api]]", "[[B05_wf2_Route/B05_db]]", "[[B05_wf2_Route/B05_dependencies]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B05_wf2_Route 실 코드 직접 확인 (2026-07-12)
---
# B05_wf2_Route — Backend
## 요청·응답 모델
| 항목 | 위치 | 역할 |
|---|---|---|
| `RoutePoint` / `CirclePoint` | `B05_wf2_Route_Schema.py:10,20` | BP·EP·CP와 AP·FP 입력 검증 |
| `RouteSolveRequest` | `B05_wf2_Route_Schema.py:30` | 지표면·알고리즘·등급·제약·가중치 검증 |
| `RouteSolveResponse` | `B05_wf2_Route_Schema.py:84` | route ID·길이·메트릭·GeoJSON 상대경로 응답 |
| `RouteConfirmResponse` | `B05_wf2_Route_Schema.py:96` | 확정 route ID와 confirmed 상태 응답 |
## 경로 계산 엔진
| 항목 | 위치 | 역할 |
|---|---|---|
| `run_route_design()` | `B05_wf2_Route_Engine.py:68` | 입력 모델을 경로 설계 오케스트레이터로 실행 |
| `solve_optimal_route()` | `B05_wf2_Route_Engine_Solver.py:278` | DTM 격자·비용면·선택 알고리즘 기반 최적화 |
| `single_segment_dijkstra()` | `B05_wf2_Route_Engine_Geometry.py:149` | 단일 구간 격자 Dijkstra 탐색 |
| `solve_ridge_valley_route()` | `B05_wf2_Route_Engine_RidgeValley.py:510` | ridge-valley 후보와 제약 기반 경로 탐색 |
| `load_or_build_skeleton()` | `B05_wf2_Route_Engine_Skeleton.py:275` | D8 흐름누적 skeleton 캐시·생성 |
| `_build_cost_surface()` | `B05_wf2_Route_Engine_Solver.py:173` | 경사·곡률·회피영역 기반 비용면 생성 |
| `_fillet_alignment()` | `B05_wf2_Route_Engine_RidgeValley.py:406` | 굴곡부 fillet 정렬·곡선반경 반영 |
## 기하·결과 변환
| 항목 | 위치 | 역할 |
|---|---|---|
| `compute_gradients()` | `B05_wf2_Route_Engine_Geometry.py:33` | 격자 경사 계산 |
| `circumradius_2d()` | `B05_wf2_Route_Engine_Geometry.py:52` | 3점 곡선반경 계산 |
| `circle_intrusions()` | `B05_wf2_Route_Engine_Geometry.py:90` | 회피·금지 원 침범 검사 |
| `_route_geojson()` / `_sample_render_points()` | `B05_wf2_Route_Engine.py:20,32` | LineString 저장·렌더링 포인트 변환 |
## 라우터·workflow
| 항목 | 위치 | 역할 |
|---|---|---|
| `solve_route()` | `B05_wf2_Route_Router.py:34` | 계산 실행·DB route/points/statistics 저장·stage 2 완료 |
| `confirm_latest_route()` | `B05_wf2_Route_Router.py:137` | 최신 경로 확정·stage 2 완료·후속 단계 stale 전파 |
| `start_stage()` / `complete_stage()` / `fail_stage()` | `B05_wf2_Route_Router.py:51,96,112,117,122,128,152` | WF2 상태 전이 → [[../../concepts/workflow_state]] |
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| 700줄 초과 | `B05_wf2_Route_Engine_RidgeValley.py:1` | 781줄; ridge-valley 기능 분할 검토 |
| 700줄 근접 | `B05_wf2_Route_Engine_Solver.py:1` | 654줄; 비용면·최적화 분리 검토 |
+36
View File
@@ -0,0 +1,36 @@
---
type: page-db
status: stable
page_id: B05_wf2_Route
related_pages: ["[[B05_wf2_Route/B05_backend]]", "[[B05_wf2_Route/B05_api]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B05_wf2_Route_Repository.py 실 코드 직접 확인 (2026-07-12)
---
# B05_wf2_Route — DB 사용 관계
테이블 정의는 [[db_schema/route_profile]]에서 1차 관리한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `routes` | `B05_wf2_Route_Repository.py:27` | surface model·제어점·옵션·GeoJSON 경로 저장 |
| `route_points` | `B05_wf2_Route_Repository.py:75` | chainage·고도·경사별 렌더링 포인트 저장 |
| `route_statistics` | `B05_wf2_Route_Repository.py:109` | 총연장·경사·절성토량·비용 통계 저장 |
## Repository 함수
| 항목 | 위치 | 역할 |
|---|---|---|
| `_validate_stage_path()` | `B05_wf2_Route_Repository.py:18` | B05 route 상대경로 이탈 방지 |
| `create_route()` | `B05_wf2_Route_Repository.py:27` | 계산 결과 route 레코드 생성 |
| `insert_route_points()` | `B05_wf2_Route_Repository.py:75` | 샘플 포인트 일괄 저장 |
| `create_route_statistics()` | `B05_wf2_Route_Repository.py:109` | 경로 통계 저장 |
| `get_latest_route()` | `B05_wf2_Route_Repository.py:148` | 프로젝트 최신 경로 조회 |
| `confirm_route()` | `B05_wf2_Route_Repository.py:175` | 경로 상태 CONFIRMED 전환 |
## 저장 경로
| 항목 | 역할 |
|---|---|
| `routes.route_data_path` | 프로젝트 루트 기준 `B05_wf2_Route/route/` 내 GeoJSON 상대경로 |
| `route_points` | DB에 렌더링용 샘플 좌표 저장 |
상세 경로 규칙: [[storage_paths]].
@@ -0,0 +1,29 @@
---
type: page-dependencies
status: stable
page_id: B05_wf2_Route
related_pages: ["[[B05_wf2_Route/B05_frontend]]", "[[B05_wf2_Route/B05_backend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B05_wf2_Route import 구문·엔진 직접 확인 (2026-07-12)
---
# B05_wf2_Route — Dependencies
공유 의존성 정의는 [[../../concepts/dependencies]]에 둔다.
| 항목 | 역할 |
|---|---|
| Whitebox | D8 흐름누적 기반 skeleton 보조 |
| GeoPandas | 지표면·공간 데이터 처리 |
| Shapely | 경로·금지영역 기하 연산 |
| NumPy | 격자·비용면·Dijkstra 계산 |
| SciPy | 격자 보간·수치 계산 보조 |
| aiomysql | routes·route_points·route_statistics Raw SQL |
## 페이지 파일별 연결
| 파일 | 위치 | 역할 |
|---|---|---|
| `B05_wf2_Route_Engine_Skeleton.py` | `:1` | Whitebox/NumPy 흐름누적 skeleton |
| `B05_wf2_Route_Engine_RidgeValley.py` | `:1` | NumPy 기반 ridge-valley 탐색 |
| `B05_wf2_Route_Repository.py` | `:1` | aiomysql 비동기 저장 |
| `B05_wf2_Route_UI_Page.ts` | `:1` | TypeScript DOM·공통 UI 렌더링 |
@@ -0,0 +1,44 @@
---
type: page-frontend
status: stable
page_id: B05_wf2_Route
related_pages: ["[[B05_wf2_Route/B05_backend]]", "[[B05_wf2_Route/B05_api]]", "[[B05_wf2_Route/B05_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B05_wf2_Route 실 코드 직접 확인 (2026-07-12)
---
# B05_wf2_Route — Frontend
## 화면과 상호작용
| 항목 | 위치 | 역할 |
|---|---|---|
| `renderB05Route()` | `B05_wf2_Route_UI_Page.ts:74` | 제어점·지표면·제약·결과 3단 UI 렌더링 |
| 경로 계산 실행 | `B05_wf2_Route_UI_Page.ts:285` | 입력값을 `solveRoute()`로 전송하고 결과 메트릭 표시 |
| 경로 확정 | `B05_wf2_Route_UI_Page.ts:311` | 최신 계산 경로를 `confirmRoute()`로 확정 |
| workflow 상태 조회 | `B05_wf2_Route_UI_Page.ts:337` | 진입 단계와 진행상태 확인 |
| B 페이지 scaffold | `B05_wf2_Route_UI_Page.ts:354` | 공통 단계 내비게이션과 페이지 이동 |
## API 클라이언트
| 항목 | 위치 | 역할 |
|---|---|---|
| `solveRoute()` | `B05_wf2_Route_Api_Fetch.ts:83` | 경로 탐색 실행 요청 |
| `confirmRoute()` | `B05_wf2_Route_Api_Fetch.ts:94` | 최신 경로 확정 요청 |
| `RouteSolveRequest` | `B05_wf2_Route_Api_Fetch.ts:24` | 지표면·제어점·제약 입력 타입 |
| `RouteSolveResponse` | `B05_wf2_Route_Api_Fetch.ts:40` | 경로 ID·길이·메트릭·저장경로 타입 |
## 입력 그룹
| 항목 | 역할 |
|---|---|
| BP/EP/CP | 시작점·끝점·경유 제어점 |
| AP/FP | 회피 원·금지 원과 반경 |
| filter/method/smooth | 지표면 필터·표현·평활화 선택 |
| grade/paved/curve radius | 임도 등급·포장·최소 곡선반경 |
| uphill/downhill/weights | 종단경사 제한과 비용 가중치 |
현재 코드에는 WebCAD 노선 폴리라인 편집·렌더링 컴포넌트가 없다.
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| WebCAD 미구현 | `B05_wf2_Route_UI_Page.ts:74` | 결과는 메트릭·경로 저장경로 중심으로 표시 |
| CSS 분리 검토 | `B05_wf2_Route_UI_Style.css:1` | 132줄 페이지 전용 스타일 |
@@ -0,0 +1,28 @@
---
type: page-api
status: stable
page_id: B06_wf3_ProfileCross
related_pages: ["[[B06_wf3_ProfileCross/B06_frontend]]", "[[B06_wf3_ProfileCross/B06_backend]]", "[[B06_wf3_ProfileCross/B06_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B06_wf3_ProfileCross_Router.py 실 코드 직접 확인 (2026-07-12)
---
# B06_wf3_ProfileCross — API
기본 prefix: `/api/projects` (`B06_wf3_ProfileCross_Router.py:33`). 공통 세션·오류 형식은 [[../../concepts/auth_rbac]], [[../../concepts/api_common]]을 참조한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `POST /{project_id}/sections/generate` | `B06_wf3_ProfileCross_Router.py:48` | 확정 경로 기반 종단·횡단 생성 및 DB 저장 |
| `GET /{project_id}/sections/{route_id}` | `B06_wf3_ProfileCross_Router.py:151` | 종횡단 결과 요약 조회 |
| `POST /{project_id}/sections/{route_id}/confirm` | `B06_wf3_ProfileCross_Router.py:169` | 해당 route의 종횡단 결과 확정 |
## 생성 응답
| 항목 | 역할 |
|---|---|
| `longitudinal_id` | 종단면 레코드 ID |
| `cross_section_count` | 생성된 횡단면 수 |
| `length_m` | 대상 경로 총연장 |
| `longitudinal_file_path` | 종단면 산출 파일 상대경로 |
오류는 표준 `{"status":"error","message":...}` 형식으로 반환한다.
@@ -0,0 +1,43 @@
---
type: page-backend
status: stable
page_id: B06_wf3_ProfileCross
related_pages: ["[[B06_wf3_ProfileCross/B06_frontend]]", "[[B06_wf3_ProfileCross/B06_api]]", "[[B06_wf3_ProfileCross/B06_db]]", "[[B06_wf3_ProfileCross/B06_dependencies]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B06_wf3_ProfileCross 실 코드 직접 확인 (2026-07-12)
---
# B06_wf3_ProfileCross — Backend
## 요청·응답 모델
| 항목 | 위치 | 역할 |
|---|---|---|
| `SectionGenerateRequest` | `B06_wf3_ProfileCross_Schema.py:8` | route·지표면·측점·횡단 옵션 검증 |
| `SectionGenerateResponse` | `B06_wf3_ProfileCross_Schema.py:26` | 종단 ID·횡단 개수·총연장·파일경로 응답 |
| `SectionConfirmResponse` | `B06_wf3_ProfileCross_Schema.py:38` | 확정 route와 confirmed 상태 응답 |
| `SectionSummaryResponse` | `B06_wf3_ProfileCross_Schema.py:47` | 종단·횡단 결과 요약 응답 |
## 생성 엔진
| 항목 | 위치 | 역할 |
|---|---|---|
| `run_section_generation()` | `B06_wf3_ProfileCross_Engine.py:50` | B05 경로 로드·표고 sampler·종횡단 생성 오케스트레이션 |
| `build_surface_sampler()` | `B06_wf3_ProfileCross_Engine_Sampler.py:126` | DTM·보간 방식 표고 sampler 선택 |
| `DtmGridSampler` | `B06_wf3_ProfileCross_Engine_Sampler.py:26` | DTM 격자 기반 표고 조회 |
| `InterpolatedSurfaceSampler` | `B06_wf3_ProfileCross_Engine_Sampler.py:109` | 표면 보간 기반 표고 조회 |
| `generate_sections()` | `B06_wf3_ProfileCross_Engine_Section.py:116` | 측점·종단·횡단 샘플 생성 |
| `_chainages()` | `B06_wf3_ProfileCross_Engine_Section.py:77` | 측점 간격과 종점 포함 여부로 chainage 생성 |
| `_interpolate_xy()` / `_tangent_at()` | `B06_wf3_ProfileCross_Engine_Section.py:86,95` | 경로 좌표 보간·접선 방향 계산 |
## 라우터·workflow
| 항목 | 위치 | 역할 |
|---|---|---|
| `generate_sections()` | `B06_wf3_ProfileCross_Router.py:49` | 확정 경로 검증·생성·DB 저장·stage 3 완료 |
| `get_sections()` | `B06_wf3_ProfileCross_Router.py:152` | route 종횡단 요약 조회 |
| `confirm_sections()` | `B06_wf3_ProfileCross_Router.py:170` | 결과 확정·stage 3 완료 |
| `start_stage()` / `complete_stage()` / `fail_stage()` | `B06_wf3_ProfileCross_Router.py:68,111,127,132,137,143,187` | WF3 상태 전이 → [[../../concepts/workflow_state]] |
## 재실행
| 항목 | 위치 | 역할 |
|---|---|---|
| `delete_sections_for_route()` | `B06_wf3_ProfileCross_Repository.py:27` | 동일 route 기존 결과 삭제 후 재생성 |
| `_validate_stage_path()` | `B06_wf3_ProfileCross_Repository.py:18` | WF3 저장경로 이탈 방지 |
@@ -0,0 +1,35 @@
---
type: page-db
status: stable
page_id: B06_wf3_ProfileCross
related_pages: ["[[B06_wf3_ProfileCross/B06_backend]]", "[[B06_wf3_ProfileCross/B06_api]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B06_wf3_ProfileCross_Repository.py 실 코드 직접 확인 (2026-07-12)
---
# B06_wf3_ProfileCross — DB 사용 관계
테이블 정의는 [[db_schema/route_profile]]에서 1차 관리한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `longitudinal_sections` | `B06_wf3_ProfileCross_Repository.py:34` | route별 종단 chainage·표고·경사·설계값 저장 |
| `cross_sections` | `B06_wf3_ProfileCross_Repository.py:67` | 측점별 좌우 표고·폭·절성토 데이터 저장 |
## Repository 함수
| 항목 | 위치 | 역할 |
|---|---|---|
| `_validate_stage_path()` | `B06_wf3_ProfileCross_Repository.py:18` | WF3 상대경로 이탈 방지 |
| `delete_sections_for_route()` | `B06_wf3_ProfileCross_Repository.py:27` | 재생성 전 기존 종횡단 삭제 |
| `create_longitudinal_section()` | `B06_wf3_ProfileCross_Repository.py:34` | 종단면 레코드 생성 |
| `insert_cross_sections()` | `B06_wf3_ProfileCross_Repository.py:67` | 횡단면 일괄 저장 |
| `get_longitudinal_section()` | `B06_wf3_ProfileCross_Repository.py:109` | 종단·횡단 결과 조회 |
| `confirm_sections_for_route()` | `B06_wf3_ProfileCross_Repository.py:135` | route 종횡단 상태 확정 |
## 파일 경로
| 항목 | 역할 |
|---|---|
| `longitudinal_file_path` | `B06_wf3_ProfileCross/longitudinal/` 산출물 상대경로 |
| `cross_section_file_path` | `B06_wf3_ProfileCross/cross_sections/` 산출물 상대경로 |
상세 저장 규칙: [[storage_paths]].
@@ -0,0 +1,27 @@
---
type: page-dependencies
status: stable
page_id: B06_wf3_ProfileCross
related_pages: ["[[B06_wf3_ProfileCross/B06_frontend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B06_wf3_ProfileCross import 구문·엔진 직접 확인 (2026-07-12)
---
# B06_wf3_ProfileCross — Dependencies
공유 의존성 정의는 [[../../concepts/dependencies]]에 둔다.
| 항목 | 위치 | 역할 |
|---|---|---|
| NumPy | `B06_wf3_ProfileCross_Engine_Section.py:1` | 경로·chainage·단면 샘플 배열 계산 |
| SciPy | `B06_wf3_ProfileCross_Engine_Sampler.py:1` | 표면 보간 sampler |
| aiomysql | `B06_wf3_ProfileCross_Repository.py:1` | 종횡단 Raw SQL 저장·조회 |
| Pydantic | `B06_wf3_ProfileCross_Schema.py:5` | 생성·조회·확정 요청 검증 |
| FastAPI | `B06_wf3_ProfileCross_Router.py:1` | sections API 라우터 |
## 공통 모듈
| 항목 | 위치 | 역할 |
|---|---|---|
| `common_util_workflow_state` | `B06_wf3_ProfileCross_Router.py:29` | stage 3 상태 전이 → [[../../concepts/workflow_state]] |
| `common_util_storage` | `B06_wf3_ProfileCross_Engine.py:1` | 프로젝트 상대경로 해석 → [[../../concepts/storage_paths]] |
| `ui_template_workflow_layout` | `B06_wf3_ProfileCross_UI_Page.ts:23` | 공통 workflow 화면 구성 |
@@ -0,0 +1,40 @@
---
type: page-frontend
status: stable
page_id: B06_wf3_ProfileCross
related_pages: ["[[B06_wf3_ProfileCross/B06_backend]]", "[[B06_wf3_ProfileCross/B06_api]]", "[[B06_wf3_ProfileCross/B06_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B06_wf3_ProfileCross 실 코드 직접 확인 (2026-07-12)
---
# B06_wf3_ProfileCross — Frontend
## 화면·workflow
| 항목 | 위치 | 역할 |
|---|---|---|
| `renderB06ProfileCross()` | `B06_wf3_ProfileCross_UI_Page.ts:57` | 측점·횡단 옵션·결과 메트릭 화면 렌더링 |
| 종횡단 생성 실행 | `B06_wf3_ProfileCross_UI_Page.ts:201` | 입력 옵션을 `generateSections()`로 전송 |
| 종횡단 확정 | `B06_wf3_ProfileCross_UI_Page.ts:233` | route별 종횡단을 `confirmSections()`로 확정 |
| workflow 상태 조회 | `B06_wf3_ProfileCross_UI_Page.ts:259` | 단계 상태와 진입 가능 단계 조회 |
| workflow layout 구성 | `B06_wf3_ProfileCross_UI_Page.ts:271` | 공통 단계·경로·상태 표시 |
## API 클라이언트
| 항목 | 위치 | 역할 |
|---|---|---|
| `generateSections()` | `B06_wf3_ProfileCross_Api_Fetch.ts:82` | 종단면·횡단면 생성 요청 |
| `getSections()` | `B06_wf3_ProfileCross_Api_Fetch.ts:93` | route별 결과 요약 조회 |
| `confirmSections()` | `B06_wf3_ProfileCross_Api_Fetch.ts:103` | 생성 결과 확정 요청 |
| `SectionGenerateRequest` | `B06_wf3_ProfileCross_Api_Fetch.ts:18` | 측점·폭·샘플 간격 입력 타입 |
| `SectionGenerateResponse` | `B06_wf3_ProfileCross_Api_Fetch.ts:31` | 종단 ID·횡단 개수·길이·파일경로 타입 |
## 입력 옵션
| 항목 | 역할 |
|---|---|
| `route_id` | 확정된 B05 경로 선택 |
| `filter_key`, `method`, `smooth`, `crs` | 지표면 필터·표현·평활화·좌표계 |
| `station_interval_m` | 종단 측점 간격 |
| `cross_half_width_m` | 횡단 좌우 반폭 |
| `cross_sample_interval_m` | 횡단 표고 샘플 간격 |
| `long_sample_interval_m` | 종단 표고 샘플 간격 |
현재 코드에는 종단도·횡단 프로필을 WebCAD로 렌더링하는 전용 뷰어가 없다.
@@ -0,0 +1,45 @@
---
status: stable
page_id: B07_wf4_DesignDetail
related_pages: ["[[B06_wf3_ProfileCross/B06_backend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[db_schema/structure_output]]", "[[storage_paths]]"]
last_updated: 2026-07-12
---
# B07_wf4_DesignDetail — Frontend
WF4 상세 설계(구조물 배치). **워크플로우 셸만 존재, 본문 미구현.**
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `B07_wf4_DesignDetail_UI_Page.ts` | `B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts:1-27` | 워크플로우 셸(헤더+스텝바)만 렌더, 본문은 "준비중" 안내 |
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderB07DesignDetail(root)` | `B07_wf4_DesignDetail_UI_Page.ts:20` | `renderPendingWorkflow()` 호출(title, steps, activeStep:4) |
## 로컬라이제이션
| UI 요소 | 로케일 키 |
|---|---|
| 페이지 제목 | `B07_Design_Title` ("4차 · 상세 설계") |
## 의존성
- [[a00_app_shell_framework]] — `renderPendingWorkflow()`, `workflowSteps()` (b_page_scaffold, B04~B09 공용 셸 헬퍼)
- [[ui_templates]] — `ui_locales`, `currentLanguageIndex`
## 백엔드/DB — 미착수 (계획적 범위 제외)
- 백엔드(Schema/Engine/Repository/Router) 파일 자체가 존재하지 않음
- `raw/guidelines/migration_plan.md:271`**"B07 이후 기능은 이번 마이그레이션 범위에서 제외한다"** (계획 단계에서 명시적으로 제외)
- `raw/guidelines/structure.md:173-174`도 B07을 "워크플로우 셸"로만 기술 — 실 코드와 일치
- DB는 `structures` 테이블 예정 (컬럼 정의는 계획서 기준) → [[db_schema/structure_output]] (사용처에 B07 이미 등록됨)
## 참고
- 이 페이지는 B08/B09와 함께 "셸만 존재" 3종 중 하나. 실 코드·raw 문서 모두 미착수로 일관되게 확인됨(모순 없음)
- 향후 구현 시 `structures` 테이블(구조물 유형: 낙석방지책/돌붙임/계간수로 등) 기준 계획서 필요
@@ -0,0 +1,29 @@
---
type: page-db
status: stable
page_id: B08_wf5_Quantity
related_pages: ["[[B08_wf5_Quantity/B08_frontend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B08 코드 부재 확인 + DB 개념 문서 대조 (2026-07-12)
---
# B08_wf5_Quantity — DB
현재 B08 코드에는 Repository·라우터·DB 쓰기 구현이 없다. 아래는 프로젝트 DB 설계상 예정된 사용 관계다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `quantity_items` | [[db_schema/structure_output]] | 토공·구조물·포장·배수·녹화·안전시설별 수량과 단가 저장 예정 |
| `quantity_data_path` | [[db_schema/overview]] | B08 수량 산출 파일 상대경로 저장 예정 |
## 구현 대기 항목
| 항목 | 현재 상태 |
|---|---|
| B08 Repository | 파일 없음 |
| B08 Router/API | 파일 없음 |
| Pydantic 스키마 | 파일 없음 |
| 수량 계산 엔진 | 파일 없음 |
| `quantity_items` INSERT/UPDATE | 미구현 |
| WF5 상태 전이 | 미구현 |
저장 폴더 계획: `storage/{company_slug}/{user_slug}/{project_id}/B08_wf5_Quantity/quantities/` → [[storage_paths]].
@@ -0,0 +1,42 @@
---
type: page-frontend
status: stable
page_id: B08_wf5_Quantity
related_pages: ["[[B08_wf5_Quantity/B08_db]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 B08_wf5_Quantity 실 코드 직접 확인 (2026-07-12)
---
# B08_wf5_Quantity — Frontend
## 현재 구현
| 항목 | 위치 | 역할 |
|---|---|---|
| `renderB08Quantity()` | `B08_wf5_Quantity_UI_Page.ts:20` | B08 수량 산출 페이지 진입점 |
| `renderPendingWorkflow()` | `B08_wf5_Quantity_UI_Page.ts:21` | 공통 준비 중 workflow 셸 렌더링 |
| `workflowSteps()` | `B08_wf5_Quantity_UI_Page.ts:23` | 전체 workflow 단계 표시 |
| `L()` | `B08_wf5_Quantity_UI_Page.ts:14` | 한·영 locale 문자열 선택 |
## 화면 상태
| 항목 | 값 |
|---|---|
| 제목 | `ui_locales[B08_Quantity_Title]` |
| 활성 단계 | `activeStep: 5` (WF5) |
| 본문 | 미구현; 헤더와 workflow step bar만 표시 |
| 수량 계산 API | 미구현 |
| 수량 결과 테이블·차트 | 미구현 |
| WebCAD/산출물 뷰어 | 미구현 |
## 계획 책임
| 항목 | 계획 역할 |
|---|---|
| 토공·구조물·포장·배수 등 수량 산출 | B07 종횡단·구조물 결과를 수량 항목으로 집계 |
| 수량 결과 저장 | `quantity_items``quantity_data_path` 사용 → [[B08_wf5_Quantity/B08_db]] |
| 다음 단계 연결 | WF6 견적·문서 산출로 전달 |
공통 셸·locale은 [[../../concepts/a00_app_shell_framework]], [[../../concepts/ui_templates]]를 참조한다.
## 기술부채
| 항목 | 위치 | 역할 |
|---|---|---|
| 본문 미구현 | `B08_wf5_Quantity_UI_Page.ts:5` | 수량 계산·조회·확정 기능 설계와 구현 필요 |
@@ -0,0 +1,45 @@
---
status: stable
page_id: B09_wf6_Estimation
related_pages: ["[[B08_wf5_Quantity/B08_frontend]]", "[[db_schema/structure_output]]", "[[storage_paths]]"]
last_updated: 2026-07-12
---
# B09_wf6_Estimation — Frontend
WF6 견적·문서 산출(Excel/PDF/DXF). **워크플로우 셸만 존재, 본문 미구현.**
## 파일 구조
| 파일 | 위치 | 역할 |
|---|---|---|
| `B09_wf6_Estimation_UI_Page.ts` | `B09_wf6_Estimation/B09_wf6_Estimation_UI_Page.ts:1-27` | 워크플로우 셸(헤더+스텝바)만 렌더, 본문은 "준비중" 안내 |
## 컴포넌트 / 함수
| 함수 | 위치 | 역할 |
|---|---|---|
| `renderB09Estimation(root)` | `B09_wf6_Estimation_UI_Page.ts:20` | `renderPendingWorkflow()` 호출(title, steps, activeStep:6) |
## 로컬라이제이션
| UI 요소 | 로케일 키 |
|---|---|
| 페이지 제목 | `B09_Estimation_Title` ("6차 · 견적·문서") |
## 의존성
- [[a00_app_shell_framework]] — `renderPendingWorkflow()`, `workflowSteps()` (b_page_scaffold, B04~B09 공용 셸 헬퍼)
- [[ui_templates]] — `ui_locales`, `currentLanguageIndex`
## 백엔드/DB — 미착수
- 백엔드(Schema/Engine/Repository/Router) 파일 자체가 존재하지 않음
- `raw/guidelines/structure.md:177-178`도 B09를 "워크플로우 셸"로만 기술 — 실 코드와 일치
- DB는 `outputs`/`output_files` 테이블 예정 → [[db_schema/structure_output]] (사용처에 B09 이미 등록됨)
- 산출물 버전 관리(v1/, v2/, ...)는 워크플로우 전체에서 **이 단계에서만** 사용 → [[storage_paths]] (경로패턴 20줄, B09 이미 related_pages 등록됨)
## 참고
- B07/B08과 함께 "셸만 존재" 3종 중 하나. 실 코드·raw 문서 모두 미착수로 일관되게 확인됨(모순 없음)
- 향후 구현 시 Excel/PDF/DXF 3종 산출물 생성 로직 + `outputs_directory_path`/`output_file_path` 경로 기록 필요