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
+14
View File
@@ -0,0 +1,14 @@
---
trigger: always_on
description: Consult the graphify knowledge graph at graphify-out/ for codebase and architecture questions.
---
## graphify
This project has a graphify knowledge graph at graphify-out/.
Rules:
- For codebase or architecture questions, when `graphify-out/graph.json` exists, first run `graphify query "<question>"` (CLI) or `query_graph` (MCP). Use `graphify path "<A>" "<B>"` / `shortest_path` for relationships and `graphify explain "<concept>"` / `get_node` for focused concepts. These return a scoped subgraph, usually much smaller than `GRAPH_REPORT.md` or raw grep output.
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context
- After modifying code files in this session, run `graphify update .` to keep the graph current (AST-only, no API cost)
+10
View File
@@ -0,0 +1,10 @@
---
name: graphify
description: Turn any folder of files into a navigable knowledge graph
---
# Workflow: graphify
Follow the graphify skill installed at ~/.gemini/config/skills/graphify/SKILL.md to run the full pipeline.
If no path argument is given, use `.` (current directory).
+24
View File
@@ -0,0 +1,24 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "C:\\Users\\umsan\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\graphify.EXE hook-guard search"
}
]
},
{
"matcher": "Read|Glob",
"hooks": [
{
"type": "command",
"command": "C:\\Users\\umsan\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\graphify.EXE hook-guard read"
}
]
}
]
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "C:\\Users\\umsan\\AppData\\Local\\Programs\\Python\\Python312\\Scripts\\graphify.EXE hook-check"
}
]
}
]
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"promptDelete": false
}
+1
View File
@@ -0,0 +1 @@
{}
+1
View File
@@ -0,0 +1 @@
[]
+33
View File
@@ -0,0 +1,33 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"footnotes": false,
"properties": true,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"bases": true,
"webviewer": false
}
+22
View File
@@ -0,0 +1,22 @@
{
"collapse-filter": false,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": false,
"colorGroups": [],
"collapse-display": false,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": false,
"centerStrength": 0.390625,
"repelStrength": 10,
"linkStrength": 1,
"linkDistance": 250,
"scale": 0.5359795996225794,
"close": false
}
+222
View File
@@ -0,0 +1,222 @@
{
"main": {
"id": "99c9496f9af0d35f",
"type": "split",
"children": [
{
"id": "030658548bffdd02",
"type": "tabs",
"children": [
{
"id": "c66e6631a498571e",
"type": "leaf",
"state": {
"type": "graph",
"state": {},
"icon": "lucide-git-fork",
"title": "그래프 뷰"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "1fdf49368d0b8d12",
"type": "split",
"children": [
{
"id": "286216e41ef17318",
"type": "tabs",
"children": [
{
"id": "bf4a66eeb6ba12b7",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "파일 탐색기"
}
},
{
"id": "d69b070685f35c13",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "검색"
}
},
{
"id": "f48a2962a3eee224",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "북마크"
}
}
]
}
],
"direction": "horizontal",
"width": 439.5
},
"right": {
"id": "47e841f62a2c8446",
"type": "split",
"children": [
{
"id": "ed0c1ac6b73a8ced",
"type": "tabs",
"children": [
{
"id": "9eee98dc05d3fde2",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "백링크"
}
},
{
"id": "1e0ba87b26453941",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "나가는 링크"
}
},
{
"id": "aea498d48aaa60a0",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "태그"
}
},
{
"id": "dde952791b603350",
"type": "leaf",
"state": {
"type": "all-properties",
"state": {
"sortOrder": "frequency",
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-archive",
"title": "모든 속성"
}
},
{
"id": "2daeda4e90730082",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "개요"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"left-ribbon": {
"hiddenItems": {
"canvas:새 캔버스 만들기": false,
"graph:그래프 뷰 열기": false,
"switcher:빠른 전환기 열기": false,
"daily-notes:오늘의 일일 노트 열기": false,
"templates:템플릿 삽입": false,
"command-palette:명령어 팔레트 열기": false,
"bases:새 베이스 생성하기": false,
"terminal:Open terminal": false
}
},
"active": "c66e6631a498571e",
"lastOpenFiles": [
"raw/guidelines/backend.md",
"raw/AGENTS.md",
"wiki/pages/B02_ProjRegister/B02_backend.md",
"무제.canvas",
"raw/plans/plan_workflow_state_management.md",
"raw/PLAN.md.tmp.23164.6d84a6365004",
"wiki/graphify-out/graph.html",
"wiki/graphify-out/manifest.json",
"wiki/graphify-out/GRAPH_REPORT.md",
"wiki/graphify-out/graph.json",
"wiki/graphify-out/cache/stat-index.json",
"wiki/graphify-out/cache/ast/v0.9.12/9691ebb53407e479750a796e82774905fe22139f5b049c3f7dc0ad6534c378cf.json",
"wiki/graphify-out/cache/ast/v0.9.12/2808d040f080236ab87ff5d059ee801796b07a63f6a114bd2a2109e0b5806e12.json",
"wiki/graphify-out/cache/ast/v0.9.12/1b226e5d018fc38a72090165fa26143b93b6e1c0a7e1073d8c15e0cdddbeb171.json",
"wiki/graphify-out/cache/ast/v0.9.12/e5a0d7854dc5f172c997de38f2c4dc21dc761794e098626c17dfffe673b47711.json",
"wiki/graphify-out/cache/ast/v0.9.12/2be5bc2fd35d20f0f1eb7c1c707e58347f2b10bcc8843f04fffa74fbdca4d2a7.json",
"wiki/pages/B08_wf5_Quantity/B08_db.md",
"wiki/concepts/db_schema/unconfirmed/README.md",
"wiki/concepts/ui_templates.md",
"wiki/concepts/workflow_state.md",
"wiki/concepts/storage_paths.md",
"wiki/concepts/common_util.md",
"wiki/concepts/auth_rbac.md",
"wiki/concepts/dependencies.md",
"wiki/concepts/api_common.md",
"raw/PLAN.md",
"raw/plans/2026-07-12_plan_add_phase4_role.md",
"raw/verification/2026-07-12_verify_add_phase4_role.md",
"raw/guidelines/frontend.md",
"wiki/index.md",
"wiki/pages/A02_ProgDetail/A02_components.md",
"wiki/pages/A02_ProgDetail/A02_frontend.md",
"wiki/pages/A03_CompDetail/A03_frontend.md",
"wiki/pages/A01_Home/A01_components.md",
"wiki/pages/A01_Home/A01_frontend.md",
"wiki/concepts/db_schema/route_profile.md",
"무제 3.canvas",
"무제 2.canvas",
"무제 1.canvas",
"graphify-out/2026-07-12/GRAPH_REPORT.md"
]
}
+111
View File
@@ -0,0 +1,111 @@
## 나는 누구인가
- 이름:
- 하는 일: 임도설계 실무자. Aislo(라이다 데이터 기반 임도 설계·견적 자동화 웹앱) 프로젝트의 기획·설계자.
- 핵심 가치: 코드를 직접 작성하거나 깊이 이해하지는 못하지만, 도메인 전문성을 바탕으로 결과물(화면, 산출물)을 보고 실무적으로 맞는지 검증할 수 있다. 이 검증력을 근거로 AI에게 방향성과 구체적 지시를 내린다.
## 나의 역할들
### 임도설계 실무자 (도메인 전문가)
- 하는 일: 임도 설계 실무 지식을 바탕으로 요구사항을 정의하고, AI가 만든 결과물이 실무 기준에 맞는지 검증한다.
- 주요 관심사: 결과물의 실무적 타당성, 설계 로직의 정확성.
### 프로젝트 기획·설계자
- 하는 일: AI 코드 어시스턴트에게 방향성과 지시를 제공하고, 계획서 작성과 검토를 주도한다.
- 주요 관심사: AI와의 소통 효율, 문서·컨텍스트 구조화, 반복되는 방향성 이탈 방지.
## 나의 비전과 목표
- 이루고자 하는 것: 라이다 데이터를 활용해 임도 설계를 자동화하는 웹앱(Aislo)을 완성한다. 동시에, 개발 과정에서 문서(백엔드/프론트엔드/DB/구조/계획서/검증보고서)가 비대해져 컨텍스트·토큰을 낭비하지 않도록, 페이지 단위로 구조화·연결된 위키 체계를 구축한다.
- 타겟 독자/고객:
- 1차: AI 코드 어시스턴트 자신 — 코드 작성 전후로 필요한 부분만 효율적으로 찾아 읽는 용도.
- 2차: 6개월~1년 후의 AI 세션 및 팀원 — 프로젝트 구조, 설계 이유, 사용된 함수·변수, DB 정보를 스스로 파악해 개선하거나 인수인계받는 용도. (본인이 직접 위키를 다시 찾아볼 일은 거의 없음)
## AI에게 기대하는 것
- 코드 작성 전후로 관련된 위키·계획서만 효율적으로 찾아 읽고, 문서 전체를 뒤지지 않기.
- 계획서 작성 → 코드 작업 → 계획서 체크리스트 점검 → 코드 검증 → 검증보고서 작성 → 계획서 업데이트의 흐름을 주기적으로 관리하기.
- 같은 실수나 배경 설명을 반복하지 않기 — 이전 결정사항과 컨텍스트를 유지하기.
- 함수·변수 단위, DB 컬럼 단위까지 추적 가능하도록 문서화하기.
## 작업 규칙
- 톤: 간결하고 명확하게. 불필요한 수식어 없이 핵심만
- 언어: 한국어 기본
- 결과물 형태: 계획서와 검증보고서는 각각 하나의 파일로 구성하여 지속 업데이트한다. 문서는 비대해지지 않도록 페이지 단위로 구조화하고, 하위에 관련 백엔드·프론트엔드·DB·API·의존성 정보를 연결한다.
---
## Wiki Schema (위키 운영 규칙)
> 이 규칙은 `docs/` 볼트에만 적용된다. 프로젝트 루트의 docs 폴더 상위의 `.agent/` 폴더(실제 코딩 라우팅용)와는 완전히 독립적이며, 서로 건드리지 않는다. 위키가 충분히 성숙하면 그때 `.agent`를 대체할지 판단한다.
### 레이어 구조
- **raw/** — 불변 원본. 계획서·검증보고서·행동지침 등을 유형별로 넣는다. AI는 이 폴더를 절대 수정하지 않는다.
- **wiki/** — AI가 raw를 읽고 편집·유지하는 산출물. 페이지(B01~B09) 기준 폴더 + 개념(concepts) 폴더로 구성.
- **output/** — 최종 결과물(보고서, 슬라이드 등).
### 위키 관리자(Librarian) 역할
AI는 이 볼트에서 "위키 관리자"로 행동한다. 단순 챗봇이 아니라, raw를 읽고 wiki를 지속적으로 편집·정리·연결하는 책임을 진다. wiki는 사용자가 아니라 AI가 쓰고 관리한다.
### 운영 방식
- **Ingest** (원본 반영): raw/에 새 파일이 들어오면 → 내용을 읽고 핵심을 파악 → 관련된 `wiki/pages/B0x_*/``wiki/concepts/*.md`를 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
- **Query** (질의 응답): 코드 작업 전후로 질문을 받으면 → `wiki/index.md`를 먼저 확인 → 관련 페이지만 열어서 답변 → raw/는 위키에 없는 정보가 필요할 때만 최후 수단으로 참조. 답변 중 새로 정리된 내용(비교, 분석 등)은 대화에 흘려보내지 말고 관련 wiki 페이지에 다시 파일링한다.
- **Lint** (건강 점검): 요청 시 위키 전체를 점검 — 페이지 간 모순, 오래된 내용(최근 raw로 갱신되지 않은 부분), 고아 페이지(연결 없음), 빠진 크로스링크를 찾아 보고한다.
### 위키 규칙
1. **raw/는 절대 수정 금지** (불변 원본)
2. wiki 페이지 생성·삭제 시 **index.md 필수 업데이트**
3. 모든 오퍼레이션마다 **log.md에 기록**
4. 내부 참조는 **위키링크(`[[페이지명]]`)** 형식 사용
5. 모든 wiki 페이지에 **YAML frontmatter** 작성. `status`는 반드시 아래 3개 값 중 하나만 사용:
- `draft` — 계획서만 반영됨, 아직 검증 안 됨
- `stable` — 검증보고서로 확인된 내용까지 반영됨
- `stale` — raw에 새 자료가 들어왔는데 아직 이 페이지에 반영 못함 (Lint 시 최우선 처리 대상)
그 외 `related_pages`, `last_updated`도 함께 기록한다. **`wiki/pages/B0x_이름/`, `wiki/pages/A0x_이름/` 하위 파일은 다음 규칙을 따른다:**
- **파일명 형식:** `{page_id}_{기능명}.md` (예: `A01_frontend.md`, `B05_backend.md`, `B03_db.md`)
- **frontmatter에 `page_id` 필드 필수:** `page_id: A01_Home` 또는 `page_id: B05_wf2_Route` — 폴더 밖에서 파일 하나만 봐도 어느 페이지 소속인지 식별 가능해야 한다.
- concepts 페이지에는 이 규칙을 적용하지 않는다(페이지 단위가 아니므로).
6. 모순 발견 시 **양쪽 소스 모두 인용**하고 어느 쪽이 최신인지 명시. **계획서와 검증보고서가 충돌하면 검증보고서를 채택한다** (검증보고서 = 실제 코드가 검증된 상태, 계획서 = 그 시점의 의도). 계획서 쪽 내용은 지우지 말고 "계획 당시 의도"로 표시해 남긴다.
7. 소스 요약은 사실만 기록, 해석·종합은 concepts 페이지에서
8. 질의 시 **index.md 먼저**, raw/는 마지막 수단
9. **새 페이지 생성보다 기존 페이지 업데이트를 우선**
10. index 항목은 한 줄, 120자 이내로 요약
11. **공유 자원은 1차 정의를 concepts에만 둔다.** 공유 자원이란 두 페이지 이상이 함께 쓰는 것 전부를 말한다 — DB 테이블·컬럼, 공통 함수(`common_util_*`), API 엔드포인트, Pydantic 요청/응답 스키마, 외부 라이브러리(의존성), workflow 상태 등. 페이지의 `db.md`/`backend.md`/`api.md`/`dependencies.md`에는 정의를 복사하지 않고, "이 페이지가 무슨 용도로 쓰는지" + `[[concepts/해당개념#항목명]]` 위키링크만 남긴다.
12. **concepts 페이지에는 사용처(역참조) 목록을 직접 적는다.** 예: `db_schema.md`의 "routes 테이블" 항목 아래에 `사용처: [[B05_wf2_Route]], [[B06_wf3_ProfileCross]]`. 새 페이지가 그 자원을 쓰게 되면 concepts 쪽 사용처 목록도 함께 갱신한다 (Obsidian 자동 백링크에만 의존하지 않음).
13. **함수·API·테이블 등 개별 항목은 표 형식으로만 기록한다** (서술문 금지). 최소 컬럼: `항목 | 위치(파일:줄번호) | 역할`. 코드 AI가 grep 없이 바로 해당 위치로 이동할 수 있어야 한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `generate_dem()` | `B04_wf1_Surface/B04_wf1_Surface_Engine.py:142` | 포인트클라우드 → DEM 변환 |
14. **페이지 하나(파일 하나)는 100줄을 넘지 않는다.** 넘으면 즉시 하위 주제별로 분할한다 (예: `concepts/db_schema.md``concepts/db_schema/routes.md`, `concepts/db_schema/input_files.md` ...). 분할 시 상위 페이지는 하위 페이지로 가는 링크 목록만 남기고, `index.md`도 함께 갱신한다.
### 폴더 구조
```
docs/
├── raw/ (불변 원본)
│ ├── plans/ 계획서
│ ├── verification/ 검증보고서
│ └── guidelines/ 행동지침
├── wiki/
│ ├── index.md 전체 카탈로그 (카테고리별 한 줄 목록)
│ ├── log.md 연대기 로그 (append-only)
│ ├── pages/ 페이지(B01~B09) 기준 — 프로젝트 구조의 B01~B09 폴더와 1:1 대응
│ │ └── B0x_이름/
│ │ ├── frontend.md
│ │ ├── backend.md
│ │ ├── db.md
│ │ ├── api.md
│ │ └── dependencies.md
│ └── concepts/ 페이지를 관통하는 공통 개념 (DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸, API 공통, 의존성, 공통 스키마, workflow 상태 등)
└── output/ 최종 산출물
```
- 페이지 하위 파일은 raw 반영 시 **필요한 것만 생성**한다 (5개를 미리 다 만들지 않음).
- `wiki/pages/`의 카테고리는 프로젝트의 실제 B01~B09 폴더명과 항상 일치시킨다.
## graphify
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
Rules:
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
+111
View File
@@ -0,0 +1,111 @@
## 나는 누구인가
- 이름:
- 하는 일: 임도설계 실무자. Aislo(라이다 데이터 기반 임도 설계·견적 자동화 웹앱) 프로젝트의 기획·설계자.
- 핵심 가치: 코드를 직접 작성하거나 깊이 이해하지는 못하지만, 도메인 전문성을 바탕으로 결과물(화면, 산출물)을 보고 실무적으로 맞는지 검증할 수 있다. 이 검증력을 근거로 AI에게 방향성과 구체적 지시를 내린다.
## 나의 역할들
### 임도설계 실무자 (도메인 전문가)
- 하는 일: 임도 설계 실무 지식을 바탕으로 요구사항을 정의하고, AI가 만든 결과물이 실무 기준에 맞는지 검증한다.
- 주요 관심사: 결과물의 실무적 타당성, 설계 로직의 정확성.
### 프로젝트 기획·설계자
- 하는 일: AI 코드 어시스턴트에게 방향성과 지시를 제공하고, 계획서 작성과 검토를 주도한다.
- 주요 관심사: AI와의 소통 효율, 문서·컨텍스트 구조화, 반복되는 방향성 이탈 방지.
## 나의 비전과 목표
- 이루고자 하는 것: 라이다 데이터를 활용해 임도 설계를 자동화하는 웹앱(Aislo)을 완성한다. 동시에, 개발 과정에서 문서(백엔드/프론트엔드/DB/구조/계획서/검증보고서)가 비대해져 컨텍스트·토큰을 낭비하지 않도록, 페이지 단위로 구조화·연결된 위키 체계를 구축한다.
- 타겟 독자/고객:
- 1차: AI 코드 어시스턴트 자신 — 코드 작성 전후로 필요한 부분만 효율적으로 찾아 읽는 용도.
- 2차: 6개월~1년 후의 AI 세션 및 팀원 — 프로젝트 구조, 설계 이유, 사용된 함수·변수, DB 정보를 스스로 파악해 개선하거나 인수인계받는 용도. (본인이 직접 위키를 다시 찾아볼 일은 거의 없음)
## AI에게 기대하는 것
- 코드 작성 전후로 관련된 위키·계획서만 효율적으로 찾아 읽고, 문서 전체를 뒤지지 않기.
- 계획서 작성 → 코드 작업 → 계획서 체크리스트 점검 → 코드 검증 → 검증보고서 작성 → 계획서 업데이트의 흐름을 주기적으로 관리하기.
- 같은 실수나 배경 설명을 반복하지 않기 — 이전 결정사항과 컨텍스트를 유지하기.
- 함수·변수 단위, DB 컬럼 단위까지 추적 가능하도록 문서화하기.
## 작업 규칙
- 톤: 간결하고 명확하게. 불필요한 수식어 없이 핵심만
- 언어: 한국어 기본
- 결과물 형태: 계획서와 검증보고서는 각각 하나의 파일로 구성하여 지속 업데이트한다. 문서는 비대해지지 않도록 페이지 단위로 구조화하고, 하위에 관련 백엔드·프론트엔드·DB·API·의존성 정보를 연결한다.
---
## Wiki Schema (위키 운영 규칙)
> 이 규칙은 `docs/` 볼트에만 적용된다. 프로젝트 루트의 docs 폴더 상위의 `.agent/` 폴더(실제 코딩 라우팅용)와는 완전히 독립적이며, 서로 건드리지 않는다. 위키가 충분히 성숙하면 그때 `.agent`를 대체할지 판단한다.
### 레이어 구조
- **raw/** — 불변 원본. 계획서·검증보고서·행동지침 등을 유형별로 넣는다. AI는 이 폴더를 절대 수정하지 않는다.
- **wiki/** — AI가 raw를 읽고 편집·유지하는 산출물. 페이지(B01~B09) 기준 폴더 + 개념(concepts) 폴더로 구성.
- **output/** — 최종 결과물(보고서, 슬라이드 등).
### 위키 관리자(Librarian) 역할
AI는 이 볼트에서 "위키 관리자"로 행동한다. 단순 챗봇이 아니라, raw를 읽고 wiki를 지속적으로 편집·정리·연결하는 책임을 진다. wiki는 사용자가 아니라 AI가 쓰고 관리한다.
### 운영 방식
- **Ingest** (원본 반영): raw/에 새 파일이 들어오면 → 내용을 읽고 핵심을 파악 → 관련된 `wiki/pages/B0x_*/``wiki/concepts/*.md`를 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
- **Query** (질의 응답): 코드 작업 전후로 질문을 받으면 → `wiki/index.md`를 먼저 확인 → 관련 페이지만 열어서 답변 → raw/는 위키에 없는 정보가 필요할 때만 최후 수단으로 참조. 답변 중 새로 정리된 내용(비교, 분석 등)은 대화에 흘려보내지 말고 관련 wiki 페이지에 다시 파일링한다.
- **Lint** (건강 점검): 요청 시 위키 전체를 점검 — 페이지 간 모순, 오래된 내용(최근 raw로 갱신되지 않은 부분), 고아 페이지(연결 없음), 빠진 크로스링크를 찾아 보고한다.
### 위키 규칙
1. **raw/는 절대 수정 금지** (불변 원본)
2. wiki 페이지 생성·삭제 시 **index.md 필수 업데이트**
3. 모든 오퍼레이션마다 **log.md에 기록**
4. 내부 참조는 **위키링크(`[[페이지명]]`)** 형식 사용
5. 모든 wiki 페이지에 **YAML frontmatter** 작성. `status`는 반드시 아래 3개 값 중 하나만 사용:
- `draft` — 계획서만 반영됨, 아직 검증 안 됨
- `stable` — 검증보고서로 확인된 내용까지 반영됨
- `stale` — raw에 새 자료가 들어왔는데 아직 이 페이지에 반영 못함 (Lint 시 최우선 처리 대상)
그 외 `related_pages`, `last_updated`도 함께 기록한다. **`wiki/pages/B0x_이름/`, `wiki/pages/A0x_이름/` 하위 파일은 다음 규칙을 따른다:**
- **파일명 형식:** `{page_id}_{기능명}.md` (예: `A01_frontend.md`, `B05_backend.md`, `B03_db.md`)
- **frontmatter에 `page_id` 필드 필수:** `page_id: A01_Home` 또는 `page_id: B05_wf2_Route` — 폴더 밖에서 파일 하나만 봐도 어느 페이지 소속인지 식별 가능해야 한다.
- concepts 페이지에는 이 규칙을 적용하지 않는다(페이지 단위가 아니므로).
6. 모순 발견 시 **양쪽 소스 모두 인용**하고 어느 쪽이 최신인지 명시. **계획서와 검증보고서가 충돌하면 검증보고서를 채택한다** (검증보고서 = 실제 코드가 검증된 상태, 계획서 = 그 시점의 의도). 계획서 쪽 내용은 지우지 말고 "계획 당시 의도"로 표시해 남긴다.
7. 소스 요약은 사실만 기록, 해석·종합은 concepts 페이지에서
8. 질의 시 **index.md 먼저**, raw/는 마지막 수단
9. **새 페이지 생성보다 기존 페이지 업데이트를 우선**
10. index 항목은 한 줄, 120자 이내로 요약
11. **공유 자원은 1차 정의를 concepts에만 둔다.** 공유 자원이란 두 페이지 이상이 함께 쓰는 것 전부를 말한다 — DB 테이블·컬럼, 공통 함수(`common_util_*`), API 엔드포인트, Pydantic 요청/응답 스키마, 외부 라이브러리(의존성), workflow 상태 등. 페이지의 `db.md`/`backend.md`/`api.md`/`dependencies.md`에는 정의를 복사하지 않고, "이 페이지가 무슨 용도로 쓰는지" + `[[concepts/해당개념#항목명]]` 위키링크만 남긴다.
12. **concepts 페이지에는 사용처(역참조) 목록을 직접 적는다.** 예: `db_schema.md`의 "routes 테이블" 항목 아래에 `사용처: [[B05_wf2_Route]], [[B06_wf3_ProfileCross]]`. 새 페이지가 그 자원을 쓰게 되면 concepts 쪽 사용처 목록도 함께 갱신한다 (Obsidian 자동 백링크에만 의존하지 않음).
13. **함수·API·테이블 등 개별 항목은 표 형식으로만 기록한다** (서술문 금지). 최소 컬럼: `항목 | 위치(파일:줄번호) | 역할`. 코드 AI가 grep 없이 바로 해당 위치로 이동할 수 있어야 한다.
| 항목 | 위치 | 역할 |
|---|---|---|
| `generate_dem()` | `B04_wf1_Surface/B04_wf1_Surface_Engine.py:142` | 포인트클라우드 → DEM 변환 |
14. **페이지 하나(파일 하나)는 100줄을 넘지 않는다.** 넘으면 즉시 하위 주제별로 분할한다 (예: `concepts/db_schema.md``concepts/db_schema/routes.md`, `concepts/db_schema/input_files.md` ...). 분할 시 상위 페이지는 하위 페이지로 가는 링크 목록만 남기고, `index.md`도 함께 갱신한다.
### 폴더 구조
```
docs/
├── raw/ (불변 원본)
│ ├── plans/ 계획서
│ ├── verification/ 검증보고서
│ └── guidelines/ 행동지침
├── wiki/
│ ├── index.md 전체 카탈로그 (카테고리별 한 줄 목록)
│ ├── log.md 연대기 로그 (append-only)
│ ├── pages/ 페이지(B01~B09) 기준 — 프로젝트 구조의 B01~B09 폴더와 1:1 대응
│ │ └── B0x_이름/
│ │ ├── frontend.md
│ │ ├── backend.md
│ │ ├── db.md
│ │ ├── api.md
│ │ └── dependencies.md
│ └── concepts/ 페이지를 관통하는 공통 개념 (DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸, API 공통, 의존성, 공통 스키마, workflow 상태 등)
└── output/ 최종 산출물
```
- 페이지 하위 파일은 raw 반영 시 **필요한 것만 생성**한다 (5개를 미리 다 만들지 않음).
- `wiki/pages/`의 카테고리는 프로젝트의 실제 B01~B09 폴더명과 항상 일치시킨다.
## graphify
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
Rules:
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
+7
View File
@@ -0,0 +1,7 @@
# output/ 폴더 규칙
wiki/ 내용을 바탕으로 생성한 최종 산출물(보고서, 슬라이드, 요약 문서 등)을 둔다.
## 규칙
- 여기 있는 파일은 wiki/의 특정 시점 스냅샷이다. 원본이 아니므로, 내용 갱신이 필요하면 wiki/를 먼저 갱신한 뒤 다시 생성한다.
- 파일명에 생성 날짜를 포함해 버전을 구분한다.
+7
View File
@@ -0,0 +1,7 @@
# output/ 폴더 규칙
wiki/ 내용을 바탕으로 생성한 최종 산출물(보고서, 슬라이드, 요약 문서 등)을 둔다.
## 규칙
- 여기 있는 파일은 wiki/의 특정 시점 스냅샷이다. 원본이 아니므로, 내용 갱신이 필요하면 wiki/를 먼저 갱신한 뒤 다시 생성한다.
- 파일명에 생성 날짜를 포함해 버전을 구분한다.
+15
View File
@@ -0,0 +1,15 @@
# raw/ 폴더 규칙
이 폴더는 불변 원본 저장소다. 계획서, 검증보고서, 행동지침 등 사용자가 넣거나 세션 중 생성된 원본 자료를 유형별로 보관한다.
## 제약
- **절대 수정하지 않는다.** 이 폴더의 파일은 읽기 전용 소스로만 취급한다. 오탈자나 오래된 내용이 있어도 여기서 고치지 않고, wiki/ 쪽에서 반영·정정한다.
- 파일을 삭제하지 않는다.
## 하위 폴더
- `plans/` — 계획서
- `verification/` — 검증보고서. **계획서와 내용이 충돌하면 이쪽을 채택한다** (실제 코드가 검증된 상태이므로).
- `guidelines/` — 행동지침
## 이 폴더가 업데이트되면
`docs/CLAUDE.md`의 Wiki Schema에 따라 Ingest 절차를 수행한다: 새 파일을 읽고 → 관련 `wiki/pages/`, `wiki/concepts/` 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
+15
View File
@@ -0,0 +1,15 @@
# raw/ 폴더 규칙
이 폴더는 불변 원본 저장소다. 계획서, 검증보고서, 행동지침 등 사용자가 넣거나 세션 중 생성된 원본 자료를 유형별로 보관한다.
## 제약
- **절대 수정하지 않는다.** 이 폴더의 파일은 읽기 전용 소스로만 취급한다. 오탈자나 오래된 내용이 있어도 여기서 고치지 않고, wiki/ 쪽에서 반영·정정한다.
- 파일을 삭제하지 않는다.
## 하위 폴더
- `plans/` — 계획서
- `verification/` — 검증보고서. **계획서와 내용이 충돌하면 이쪽을 채택한다** (실제 코드가 검증된 상태이므로).
- `guidelines/` — 행동지침
## 이 폴더가 업데이트되면
`docs/CLAUDE.md`의 Wiki Schema에 따라 Ingest 절차를 수행한다: 새 파일을 읽고 → 관련 `wiki/pages/`, `wiki/concepts/` 갱신 → `wiki/index.md` 갱신 → `wiki/log.md`에 기록.
+45
View File
@@ -0,0 +1,45 @@
# PLAN: 위키 린트(Lint) 결과 조치 및 건강도 개선
본 계획은 위키 린트 결과 발견된 구조적 문제들을 정밀 수정(Surgical Edit)하여 위키 건강도 및 무결성을 회복하는 것을 목표로 합니다.
## 작업 대상 및 조치 계획
### 1. frontmatter status 누락 조치
* **대상 파일**: `docs/wiki/concepts/db_schema/unconfirmed/README.md`
* **조치 내용**: YAML frontmatter를 추가하고 `status: draft`를 명시하여 상태 관리 정합성을 맞춥니다.
### 2. 파일 줄 수 제한 초과 조치 (100줄 이내 축소)
* **대상 파일**: `docs/wiki/concepts/db_schema/logs_monitoring.md`
* **조치 내용**: 현재 103줄로 100줄 제한을 약간 초과한 상태입니다. 하단부의 사용처 설명 단락의 개행을 정리하여 파일 크기를 100줄 이하로 최적화합니다.
### 3. 고아 페이지(Orphaned Pages) 연결 보완 ✅ 완료
* **대상 파일**: 4개의 의존성(`dependencies.md`) 페이지들
- `pages/B01_Dashboard/B01_dependencies.md`
- `pages/B03_FileInput/B03_dependencies.md`
- `pages/B05_wf2_Route/B05_dependencies.md`
- `pages/B06_wf3_ProfileCross/B06_dependencies.md`
* **조치 내용**: 각 폴더의 메인 문서(예: `B01_backend.md`, `B03_backend.md` 등) frontmatter의 `related_pages` 리스트에 해당 의존성 문서 링크(예: `"[[B01_Dashboard/B01_dependencies]]"`)를 추가하여, 고아 상태를 해제하고 문서 간 유기적 연결을 형성합니다.
### 4. 위키 링크 정비 (Broken Links 교정)
* **대상 파일**: `docs/wiki/AGENTS.md``docs/wiki/CLAUDE.md`
* **조치 내용**: 문서 내 특수문자나 인코딩으로 인해 비정상적으로 생성된 깨진 위키링크(`[[]]` 등)를 확인하여 삭제하거나 정상화합니다.
### 5. support_requests 컬럼 불일치 확인 및 동기화
* **대상**: `[[A08_Support/A08_backend]]`, `[[db_schema/logs_monitoring]]`, 실 DB
* **조치 내용**: 계획서 vs 실 코드 vs 실 DB의 컬럼 정의를 맞춘다.
- 계획서: user_id/title/priority/status
- 실 코드: company_id/company_name/user_name/user_email/user_phone/subject/message
### 6. A07/A09 구버전 코드 정리
* **대상**: `A07_Register_UI_Page.ts` (구버전 잔재), `A09_Security_Api_Fetch.ts` (미연동)
* **조치 내용**: 불필요한 코드 제거 및 wiki 문서 갱신
### 7. B04 레거시 코드 명확화
* **대상**: `B04_wf1_Surface_Router.py:412-427`, `B04_wf1_Surface_Router.py:76`
* **조치 내용**: `projects.status` 문자열 비교의 의도(폴백 vs 구형) 명확화 및 `update_project_status()` 호출처 추적
---
## 검증 계획
1. 수정 완료 후 `wiki_lint.py`를 다시 실행하여 린트 오류가 모두 제거(또는 정상 수준으로 감소)되었는지 확인합니다.
2. 검증이 완료되면 `verify` 보고서를 작성하고 `PLAN.md`를 초기화합니다.
+200
View File
@@ -0,0 +1,200 @@
# 프로젝트 행동지침 및 기술 스택 명세 (agent.md)
## 프로젝트 & DB 정보
### 📌 프로그램명
**Aislo (아이슬로)**
- **의미**: AI (인공지능) + Slotti (핀란드어: 임도/산림 도로)
- **콘셉트**: 산림의 미래를 열어가는 인공지능 경로 설계 솔루션
---
## 1. 기술 환경 및 기본 스택 (Technical Stack Baseline)
프로젝트의 코드를 작성, 테스트 또는 검증할 환경 명확화 및 호환 기준 제시.
### A. 백엔드 프레임워크 (Python)
* **Runtime:** Python v3.12.7
* **Framework:** FastAPI / Pydantic
* **Geometry/GIS Engine:** Trimesh, Whitebox, Geopandas, Shapely, Rasterio, Laspy
### B. 데이터베이스 (MariaDB)
* **DBMS:** MariaDB v10.6+
* **Character Set:** utf8mb4 (한글 완벽 지원)
* **Collation:** utf8mb4_unicode_ci
* **DB Driver (비동기):** `aiomysql` (순수 Python, Windows 호환. asyncmy는 Cython 빌드 필요로 미채택)
* **쿼리 방식:** Raw SQL (ORM 사용 금지)
* **공간 데이터:** JSON 기반 저장 (MariaDB는 PostGIS 미지원)
### C. 프론트엔드 & WebCAD (TypeScript & WebGL)
* **Language/Runtime:** TypeScript / Node.js
* **Rendering:** HTML5 Canvas 및 WebGL 기반 WebCAD 시스템 구현
### D. 인코딩 및 다국어 표준
* **인코딩:** 텍스트 파일은 UTF-8 표준으로 통일
---
## 2. 문맥 라우팅 및 문서 구조 (Context Routing)
코드 작성 및 기술 검증 시 문서 우선순위에 따른 계층적 레퍼런스 필수 읽기.
* **1단계 (필수):** `.agent/structure.md` 읽기 후 신규 폴더 생성 기준 분석.
* **2단계 (선택 읽기):** 프로젝트 구현에 필요한 세부 기술 명세 읽기.
* **그룹 A (UI, 스타일, 컴포넌트, WebCAD):** `.agent/frontend.md` 필수 추가 읽기.
* **그룹 B (알고리즘, 저장, DB, MariaDB 값 제어):** `.agent/backend.md` 필수 추가 읽기.
* **그룹 C (DB 구조 변경):** DB 구조 변경 시 `.agent/db_schema.md` 선택적 읽기.
---
## 3. 코드 작성 기본 제약 (Constraints)
* **구조 준수:** `.agent/structure.md` 트리 구조 엄격 준수.
* **700줄 제한:** 단일 파일 코드 작성/수정 시 700줄 이상이 될 경우 사전 공지. 기능별 파일 분할 후 `structure.md` 갱신 필수.
* **안전 보관:** 백엔드 작업 시 보안 저장소 경로 명시 및 임의 삭제 방지 필수.
* **경로 활용:** 단계별 페이지 기반 폴더 구조(B03~B09)와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
* **일관성 검증:** 설계 단계에서부터 DB 설계, 파일 경로, API 라우팅이 모두 동일한 워크플로우 기준으로 통일되어야 한다. (불일치 발생 시 설계 단계에서 재논의 필수)
* **코드 포맷팅 (자동화 도구):** 프로젝트의 코드 작성이 완료되는 시, 반드시 프로젝트 루트에서 각 언어별 포맷터를 재실행하여 스타일을 통일해야 한다.
* *Python 포맷팅 명령어:* `ruff format [파일명]``ruff check --fix [파일명]` 실행
* *TypeScript/CSS 포맷팅 명령어:* `npx prettier --write [파일명]` 실행
* **외부 라이브러리 활용:** 외부 라이브러리 활용 시 프로젝트에 맞춰 호환 라이브러리 선정하고, 불필요한 함수는 작성하지 않도록 최소화.
* **서버실행은 사용자가 실행:** 필요한 경우 의존성 설치까지는 OK, 단 서버실행은 사용자가 진행
---
## 4. 데이터베이스 상세 명세 (Database Specification)
### 4.1 스키마 기본 정보
- **DB 명:** `aislo_db`
- **DBMS:** MariaDB v10.6+
- **인코딩:** utf8mb4_unicode_ci
- **테이블 수:** 18개
- **드라이버:** aiomysql (비동기)
### 4.2 파일 경로 추적 (Path Tracking)
**모든 중간 산출물의 파일 경로를 DB에 기록:**
- `input_files.raw_file_path` — 원본 입력 파일
- `processed_point_cloud.converted_file_path` — 변환된 포인트클라우드
- `surface_models.model_file_path` — 지표면 모델
- `routes.route_data_path` — 경로 데이터
- `longitudinal_sections.longitudinal_file_path` — 종단면
- `cross_sections.cross_section_file_path` — 횡단면
- `structures.structure_data_path` — 구조물 배치
- `quantity_items.quantity_data_path` — 수량 항목
- `outputs.outputs_directory_path` — 산출물 폴더
- `output_files.output_file_path` — 개별 산출 파일
### 4.3 저장소 구조 (Workflow-based Folder Structure)
```
storage/{company_slug}/{user_slug}/{project_id}/
├── B03_FileInput/input/ (WF0: 원본 입력)
├── B04_wf1_Surface/processed/ (WF1: 변환된 포인트클라우드 & 모델)
├── B05_wf2_Route/route/ (WF2: 경로 설계)
├── B06_wf3_ProfileCross/ (WF3: 종단면 & 횡단면)
├── B07_wf4_DesignDetail/structures/ (WF4: 구조물 배치)
├── B08_wf5_Quantity/quantities/ (WF5: 수량 산출)
└── B09_wf6_Estimation/v1,v2,.../ (WF6: 최종 산출물)
```
### 4.4 공간 데이터 처리 (MariaDB 특성)
- **지하형 기하 데이터:** GEOMETRY 타입 미지원 → JSON으로 저장
- **좌표 저장 예:** `{"type": "Point", "coordinates": [127.5, 37.5]}`
- **경로 저장 예:** `{"type": "LineString", "coordinates": [[127.5, 37.5], [127.6, 37.6]]}`
- **애플리케이션 처리:** Python의 Shapely, Geopandas에서 JSON 파싱 후 기하 연산
### 4.5 역할 기반 접근 제어 (RBAC - Role-Based Access Control)
**3가지 사용자 역할:**
| 역할 | 영문 | 설명 | 접근 권한 |
|------|------|------|---------|
| 시스템 관리자 | SYSTEM_ADMIN | 최고 관리자 | 전체 회사/사용자/프로젝트 조회 및 관리 |
| 회사 관리자 | ADMIN | 회사의 주요 아이디 (초기 로그인) | 회사 내 팀원/프로젝트 관리, 가입 요청 승인 |
| 일반 사용자 | USER | 팀원 | 개인 프로젝트 생성/수정, 워크플로우 진행 |
**회사 마스터 (is_master) 플래그:**
- TRUE: 회사 생성자 또는 ADMIN 역할 (팀원 승인/거부 권한)
- FALSE: 일반 팀원
**사용자 상태 생명주기:**
```
PENDING_EMAIL (임시)
↓ (이메일 인증)
NO_COMPANY (로그인 가능, 회사 미연결)
↓ (회사 생성 또는 참여 신청)
PENDING (승인 대기) 또는 ACTIVE (회사 연결 완료)
ACTIVE (모든 기능 접근)
↓ (퇴사/비활성화)
INACTIVE 또는 REJECTED
```
---
## 5. 워크플로우 6단계 (6-Stage Workflow)
```
WF0: B03_FileInput (파일 입력)
↓ (LAS, TIF, TFW, PRJ, DXF 업로드)
WF1: B04_wf1_Surface (지표면 분석)
↓ (DEM, TIN, 포인트클라우드 변환)
WF2: B05_wf2_Route (경로 설계)
↓ (최적 경로 계산)
WF3: B06_wf3_ProfileCross (종횡단 생성)
↓ (종단면, 횡단면 생성)
WF4: B07_wf4_DesignDetail (상세 설계)
↓ (구조물 배치)
WF5: B08_wf5_Quantity (수량 산출)
↓ (수량 항목 계산)
WF6: B09_wf6_Estimation (견적·문서)
↓ (Excel, PDF, DXF 생성)
```
---
## 6. 주요 문서 참고 순서
1. **계획서 읽기:** 구현 전 반드시 읽을 문서
- `.agent/plan_register_split.md` — B01_Dashboard 설계 및 역할별 기능
- `.agent/plan_register_split_validation.md` — 검증 항목 및 테스트 체크리스트
2. **구조 설계:** `.agent/structure.md` 읽기
- 폴더/파일 명명 규칙
- 전체 디렉토리 트리 구조
3. **DB 스키마:** `.agent/db_schema.md` 읽기
- 사용자/회사/조직 테이블 (5개)
- 프로젝트 및 워크플로우 테이블
- 테이블 관계도
4. **백엔드 구현:** `.agent/backend.md` 읽기
- 아키텍처 및 라우팅
- DB 및 MariaDB 설정
- 인증 시스템 (로그인/역할/인가)
- 역할 기반 접근 제어 (RBAC)
5. **프론트 구현:** `.agent/frontend.md` 읽기
- UI 요소 및 레이아웃 제약
- 인증 및 라우팅 가드
- 역할 기반 대시보드 렌더링
6. **마이그레이션:** `.agent/migration_plan.md` 읽기 (필요 시)
7. **프로젝트 정보:** `.agent/project_info.md` 읽기 (필요 시)
---
## 7. 요약
| 항목 | 값 |
|------|-----|
| **프로그램명** | Aislo (아이슬로) |
| **DB 명** | aislo_db |
| **DBMS** | MariaDB v10.6+ |
| **인코딩** | utf8mb4_unicode_ci |
| **Backend** | Python 3.12+ / FastAPI |
| **Frontend** | TypeScript / Node.js |
| **드라이버** | aiomysql |
| **워크플로우** | 6단계 (WF0~WF6) |
| **테이블 수** | 23개 (조직/인증 5개 포함) |
| **폴더 구조** | 워크플로우 기반 (B03~B09) |
| **인증 방식** | HttpOnly Secure 세션 쿠키 |
| **사용자 역할** | SYSTEM_ADMIN, ADMIN, USER (3가지) |
| **대시보드** | B01_Dashboard (역할별 동적 렌더링) |
+300
View File
@@ -0,0 +1,300 @@
# 백엔드 제어 명세서 (backend.md)
## 1. 아키텍처 및 라우팅 (Architecture)
* **수직 통합:** 백엔드 로직은 페이지 폴더(`A01_`, `B01_` 등)별로 격리. 거대 단일 모듈 금지.
* **파일 분할:** 파일 수 제한 없음. 코드 집중 방지를 위해 기능별 분할 자율화.
* **파일명 규칙:** `[폴더명]_Router.py`, `[폴더명]_Engine.py`, `[폴더명]_Schema.py` 형태 준수.
* **루트 main.py:** 라우터 등록(`include_router`)만 수행. 비즈니스 로직 작성 금지.
---
## 2. DB 및 MariaDB (Database)
* **비동기 통신:** `async/await``aiomysql` 드라이버 사용 강제. (asyncmy는 Cython 빌드 필요로 Windows 환경 미채택)
* **쿼리 방식:** ORM 사용 금지. **Raw SQL** 작성 원칙. 파라미터 바인딩은 `%s` 플레이스홀더 사용.
* **커넥션/트랜잭션:** 풀에서 `pool.acquire()`로 커넥션 확보 후 `connection.cursor()`로 커서 생성. 다건 쓰기는 `connection.begin()``commit()` / 예외 시 `rollback()`.
* **자동 증가 ID:** `INSERT``cursor.lastrowid`로 조회 (PostgreSQL의 `RETURNING id` 미지원).
* **공간 데이터:** GEOMETRY 타입 미지원. 좌표, 다각형, 경로 등은 JSON 형식으로 저장 후 애플리케이션에서 처리.
---
## 3. 설정 및 저장소 (Config & Storage)
* **하드코딩 금지:** 제어 변수/파라미터 하드코딩 금지. `config/config_system.py`에서 `import` 필수.
* **물리 파일 격리:** 포인트 클라우드, 분석 중간 파일, 메쉬, 임시 커서는 DB 저장 금지.
* **저장 경로:** `storage/[고객사명]/[사용자명]/[프로젝트ID]/`에 물리 파일 저장 후 DB에는 경로만 기록.
* **단계별 루트 강제:** 프로젝트 저장소 내부는 실제 워크플로우 페이지명과 동일한 `B03_FileInput/` ~ `B09_wf6_Estimation/` 폴더로 분리한다. `raw/`, `processed/`, `computed/`를 프로젝트 루트의 공용 폴더로 만들지 않는다.
* **경로 정의 우선순위:** 단계별 세부 폴더와 DB 경로 열의 기준은 `.agent/db_schema_simple.md`의 「파일시스템 경로와 DB 링크」를 따른다.
* **경로 생성 책임:** 페이지별 백엔드는 자기 단계 폴더만 생성·수정한다. 다른 단계의 산출물을 직접 삭제하거나 덮어쓰지 않고 stale 상태를 통해 재계산 필요성을 전파한다.
* **DB 저장 형식:** DB에는 프로젝트 루트 기준 상대 경로를 기록하고, 실제 파일 접근 시 설정의 저장소 루트와 안전하게 결합한다. 사용자 입력 경로를 직접 결합하거나 절대 경로를 DB에 저장하지 않는다.
* **MariaDB 특성:**
- 공간 기하 데이터(좌표, 폴리곤, 경로 등)는 GEOMETRY 타입 미지원 → JSON 또는 TEXT로 저장
- 예: `{"type": "Point", "coordinates": [127.5, 37.5]}` (GeoJSON 형식)
- 애플리케이션(Python)에서 JSON 파싱 후 기하 연산 처리
---
## 4. 검증 및 예외 처리 (Error Handling)
* **데이터 검증:** 모든 JSON 요청은 라우터 진입 전 Pydantic 모델로 타입/범위 필수 검증.
* **예외 포획:** 모든 지형/메쉬 연산은 `try-except` 필수 처리.
* **반환 포맷:** 에러 발생 시 `{"status": "error", "message": "원인"}` 표준 포맷 반환.
---
## 5. 워크플로우 상태 동기화 (Workflow State Sync)
### 5.1 다중 브라우저 동시 작업 원칙
* **브라우저 독립성:** 같은 계정/프로젝트를 여러 브라우저에서 동시 접근 가능. 각 브라우저는 서버 데이터 기반 독립 작동.
* **클라이언트 상태 격리:** `localStorage`는 브라우저별 격리(도메인 단위 공유). 공유 데이터는 항상 서버 영구저장소 우선.
* **영구저장소 설계:** 계산 결과는 `.agent/db_schema_simple.md`에 정의된 페이지별 단계 폴더에 저장한다.
* `B03_FileInput/` — 원본 입력 및 파일 메타데이터
* `B04_wf1_Surface/` — 변환 포인트클라우드, 지표면 모델 및 분석 결과
* `B05_wf2_Route/` — 경로, 경로점 및 설계 파라미터
* `B06_wf3_ProfileCross/` — 종단·횡단 결과 및 인덱스
* **워크플로우 상태:** 여러 단계가 공유하는 `workflow.json`의 실제 위치는 저장소 경로 유틸에서 단일하게 정의하며, 원자적 쓰기를 적용한다. 단계별 결과 파일의 위치를 프로젝트 루트의 `result_*.json` 이름으로 추정하지 않는다.
### 5.2 Stale 상태 감지 및 전파
* **workflow.json 구조:**
```json
{
"current_stage": "route",
"completed": ["scan", "surface"],
"stale_from": "route",
"stage1_confirmed": {...}
}
```
* **Stale 발생 조건:**
* 상위 단계(예: surface) 재계산 → 하위 단계(route, section) 자동으로 `stale_from` 설정
* 다른 브라우저에서 변경 감지 → 기존 결과 무효화
* **상태 업데이트 함수:**
```python
def _patch_workflow_stale(project_id: str, stale_from: str | None) -> None:
"""workflow.json의 stale_from 필드만 원자적 업데이트"""
wf_path = get_project_workflow_path(project_id)
# 기존 상태 유지하며 stale_from만 변경
```
### 5.3 클라이언트 폴링 패턴 (Polling)
* **주기:** GET `/api/projects/{project_id}/workflow` — 3초마다 호출
* **응답:** workflow.json 전체 반환 (현재 단계, 완료 이력, stale 상태 포함)
* **클라이언트 동작:**
1. stale 감지 → 사용자에게 선택지 제시
2. "계속하기" → 기존 결과 유지 (위험 경고)
3. "재계산하기" → 상위 단계 재실행 (모든 하위 결과 초기화)
* **구현 예시:**
```typescript
setInterval(async () => {
const workflow = await fetch(`/api/projects/${projectId}/workflow`);
if (workflow.stale_from && workflow.stale_from !== currentStage) {
handleStaleness(workflow.stale_from); // 사용자에게 알림
}
}, 3000);
```
### 5.4 WebSocket 확장 (미래 선택사항)
* **실시간 알림:** 폴링 대신 WebSocket으로 업그레이드 가능 (응답성 향상)
* **전송 메시지:**
```json
{
"type": "workflow_changed",
"project_id": "...",
"stale_from": "route",
"changed_by": "other_browser"
}
```
* **마이그레이션 시기:** 사용자 수 증가 또는 실시간성 요구 시 적용
---
## 6. 로그인 및 인증 시스템 (Authentication & Authorization)
### 6.1 이메일 기반 OTP 인증 (Email OTP)
* **OTP 생성:** 6자리 난수 (000000~999999)
* **저장 방식:** DB에 bcrypt/PBKDF2 해시값 저장 (평문 절대 금지)
* **유효 시간:** 5분 (config에서 설정 가능)
* **발송:** Google SMTP (개발) → AWS SES (상용화) 전환 예정
* **재시도 로직:** 최대 3회 (지수 백오프: 2초, 4초, 8초)
### 6.2 비밀번호 정책 (Password Policy)
* **최소 길이:** 8자 이상
* **권장 구성:** 대문자/소문자/숫자/특수문자 (강제 아님, Google 기준)
* **해시 알고리즘:** bcrypt (라운드 12 이상) 또는 PBKDF2
* **저장 방식:** 해시값만 DB에 저장 (평문 절대 금지)
### 6.3 세션 관리 (Session Management) - 확정: Secure HttpOnly 세션 쿠키
* **방식:** Secure HttpOnly 세션 쿠키 (JWT 아님)
* **쿠키 이름:** `session_id`
* **유효 시간:** 12시간
* **Secure 플래그:** true (HTTPS만 전송, 개발 환경 localhost에서는 false)
* **HttpOnly 플래그:** true (JavaScript 접근 불가)
* **SameSite:** Lax (CSRF 방지)
* **저장소:** MariaDB `sessions` 테이블
* **활동 감지:** 마지막 요청으로부터 4시간 (파일 업로드 등 장시간 작업 고려)
* **세션 만료 판정:**
```python
if (current_time - last_activity_at > 4시간) or (current_time - created_at > 12시간):
→ 세션 자동 만료
```
* **만료 처리:** 프론트엔드는 만료 5분 전 알림, 만료 후 모든 요청 거부 (401 Unauthorized)
* **DB 풀 설정:** `autocommit=True` (pooled 커넥션의 트랜잭션 스냅샷 문제 방지 — 새 세션 INSERT 후 다른 커넥션에서 즉시 조회 가능)
### 6.4 로그인 이력 기록 (Audit Log)
* **기록 항목:**
- `user_id`, `email`, `login_timestamp`, `user_agent` (브라우저/OS), `status` (SUCCESS/FAILURE)
* **보관 기간:** 1년 (자동 삭제)
* **무차별 대입 방지:** 5회 이상 실패 시 계정 15분 잠금
* **IP 주소:** 법적 이슈로 수집하지 않음 (권장)
### 6.4.1 사용자 상태 생명주기 (User Status Lifecycle)
* **PENDING_EMAIL:** 회원가입 후 이메일 인증 전 (임시)
* **NO_COMPANY:** 이메일 인증 완료, 회사 미연결 (로그인 가능, B01_Dashboard 접근 가능, B02~B11 워크플로우 차단)
* **PENDING:** 회사 참여 신청 후 ADMIN 승인 대기 (로그인 가능, B01_Dashboard 접근 가능, B02~B11 워크플로우 차단)
* **ACTIVE:** 회사 연결 완료, 모든 기능 접근 가능 (B01_Dashboard + B02~B11)
* **INACTIVE:** 퇴사/계정 휴활성화
* **REJECTED:** 회사 참여 신청 거부
### 6.5 회사 연결 필수화 (Mandatory Company Linking)
* **접근 제어:**
- B02~B11 워크플로우 라우터에 `Depends(require_company)` 의존성 추가
- `session["company_id"] is None` 이면 403 Forbidden 반환 (`"회사 연결이 필요합니다."`)
* **라우팅 가드 (프론트):**
- B02~B11 진입 시 `fetchSessionUser()` → `company_id` 확인
- 없으면 B01_ACCOUNT로 리다이렉트
* **회사 생성/연결:**
- `POST /api/account/company/create`: 신규 회사 생성 후 사용자를 MASTER로 자동 연결 (status='ACTIVE')
- `POST /api/account/company/join`: 기존 회사 참여 신청 (status='PENDING', 마스터 승인 필요)
### 6.6 권한 체크 (Authorization)
* **마스터 권한:**
- 팀원 목록 조회, 승인/거부, 내보내기
- 회사 정보 수정, 구독 관리
- 마스터 대시보드 접근
* **팀원 권한:**
- 프로젝트 생성/수정/조회
- 워크플로우 실행 (B03~B09)
- 개인 프로필 수정
* **시스템관리자 권한:**
- 모든 회사/사용자 데이터 접근 (슈퍼유저)
- 회사/사용자 생성/수정/삭제
- 관리자 대시보드 (모니터링, Q&A 관리)
* **검증 방식:** 라우터 진입 전 JWT/토큰 검증, 필요시 데이터베이스에서 권한 확인
### 6.7 로그인 기반 인증 갱신 (Login-based Auth Renewal)
* **목적:** 활성 사용자의 인증 유지, 비활성 사용자 자동 차단
* **방식:** 로그인 시 인증 유효 기간 자동 연장 (3개월)
* **구현:**
- `users.last_login` → 마지막 로그인 시간 업데이트
- `users.auth_expires_at` → 매 로그인 시 NOW() + 3개월로 설정
* **인증 확인:**
- auth_expires_at > NOW() → 인증 유효 (바로 대시보드 이동)
- auth_expires_at <= NOW() → 재인증 모달 표시
* **장점:**
- 활동적인 사용자: 지속적으로 인증 유지
- 휴면 사용자: 자동으로 재인증 요구
- 일관된 정책: 고정 3개월이 아닌 활동 기반 연장
### 6.8 역할 기반 접근 제어 (Role-Based Access Control)
**사용자 역할 (users.role)**
- `SYSTEM_ADMIN`: 시스템 관리자 (최고 권한)
- `ADMIN`: 회사 관리자 (회사 내 관리권 한정)
- `USER`: 일반 사용자
### 6.9 대시보드 권한 검증 및 감시 로깅 (B01_Dashboard Authorization & Audit)
* **권한 검증 헬퍼 필수:** `.agent/plan_dashboard_management.md` 섹션 9의 Python 헬퍼 함수 구현
- `can_edit_project(user, project)` - 프로젝트 수정 권한
- `can_delete_project(user)` - 프로젝트 삭제 권한 (SYSTEM_ADMIN만)
- `can_change_role(user, target_user, new_role)` - 역할 변경 권한
- ADMIN이 SYSTEM_ADMIN으로 변경 시도 시 즉시 False 반환
- ADMIN은 USER ↔ ADMIN만 변경 가능 (같은 회사만)
- `can_manage_automation(user, automation)` - 자동화 로직 관리 권한 (USER 차단)
- `is_last_admin(company_id, exclude_user_id)` - 회사의 마지막 ADMIN 확인
* **백엔드 권한 재검증 필수:** 모든 수정/삭제/역할변경 API에서 권한 재검증
- 클라이언트 UI 신뢰 금지 (프론트엔드는 UI만 제어)
- 권한 없는 사용자가 API 직접 호출 시도 → 403 Forbidden
- 권한 오류 메시지: `{"status": "error", "message": "Access denied"}`
* **감시 로깅 (audit_logs):** 다음 행동들을 기록
- 프로젝트 삭제: `action="DELETE_PROJECT"`, `resource_type="project"`
- 사용자 역할 변경: `action="CHANGE_ROLE_TO_{NEW_ROLE}"`, `resource_type="user"`
- 사용자 삭제: `action="DELETE_USER"`, `resource_type="user"`
- 자동화 로직 삭제: `action="DELETE_AUTOMATION"`, `resource_type="automation"`
* **USER 자동화 조회 권한 차단:** `/api/projects/{pid}/automations` GET
- 요청 사용자의 role이 USER인 경우 403 Forbidden 반환
- 메시지: `"Automation logic viewing is not allowed for users"`
* **마지막 ADMIN 보호:** 사용자 삭제 API에서 체크
- `is_last_admin(company_id, exclude_user_id=user_id)` 호출
- True 반환 시 400 Bad Request: `"Cannot delete the last admin of the company"`
- 역할 변경 API에서도 동일 로직 적용
**마스터 플래그 (users.is_master)**
- `TRUE`: 회사 생성자 또는 ADMIN 역할 (팀원 승인/거부 권한)
- `FALSE`: 일반 팀원
**접근 제어 규칙:**
| 기능 | SYSTEM_ADMIN | ADMIN | USER |
|------|-------------|-------|------|
| 전체 회사 조회 | ✅ | ✗ | ✗ |
| 전체 사용자 조회 | ✅ | ✗ | ✗ |
| 사용자 역할 변경 | ✅ | ✗ | ✗ |
| 회사 생성 | ✅ | ✅ | ✅ |
| 회사 팀원 관리 | ✅ | ✅ (자신 회사만) | ✗ |
| 가입 요청 승인 | ✅ | ✅ (자신 회사만) | ✗ |
| 시스템 로그 조회 | ✅ | ✗ | ✗ |
| 프로젝트 생성 | ✅ | ✅ | ✅ |
| 프로젝트 조회 | ✅ (전체) | ✅ (회사별) | ✅ (개인) |
### 6.9 이메일 발송 (Email Infrastructure)
* **SMTP 설정:**
- **개발:** Google Gmail SMTP (umsangdon@gmail.com)
- **상용화:** Google Workspace 또는 AWS SES로 전환
* **발송 유형:**
- OTP 인증 코드
- 가입 확인 메일
- 팀원 가입 승인 요청 (ADMIN 수신)
- 팀원 승인/거부 결과 (사용자에게)
- 회사 참여 신청 알림 (SYSTEM_ADMIN 수신)
- 로그인 기반 인증 갱신 알림
* **구현:** `common_util/common_util_email.py` 모듈
- 비동기 발송 (asyncio.create_task)
- 재시도 로직 (최대 3회)
- 템플릿 렌더링 (common_util_email_templates.py)
* **자세한 내용:** `.agent/email_infrastructure_plan.md` 참고
---
## 7. 시스템 리소스 모니터링 (System Resource Monitoring)
### 7.1 계측 백그라운드 태스크
* **구현:** `common_util/common_util_resource_monitor.py`의 `sample_resources_loop()`
* **등록:** `main.py` lifespan에서 `asyncio.create_task`로 시작, 종료 시 cancel (기존 `cleanup_expired_sessions` 패턴과 동일)
* **계측 주기:** 2분 (`config_system.RESOURCE_SAMPLE_INTERVAL_SEC = 120`)
* **계측 항목:** CPU(`psutil.cpu_percent`), 메모리(`psutil.virtual_memory().percent`), 디스크(`shutil.disk_usage`)
* **방어:** 계측 예외는 로깅 후 무시하여 앱 안정성 유지
### 7.2 이중 기록 (DB + 로그 파일)
* **DB 기록:** `system_resources` 테이블에 INSERT (활성 세션/프로젝트 수 포함) — 대시보드 그래프 조회 소스
* **로그 파일:** 루트 `log/system_resources.log` 단일 파일에 탭 구분 1줄 append
- 형식: `<ISO timestamp>\tcpu=<v>\tmem=<v>\tdisk=<v>\tstorage_mb=<v>`
- `log/`는 `.gitignore` 처리 (런타임 생성물)
### 7.3 1개월 롤링 보관 (Retention)
* **보관 기간:** 30일 (`config_system.RESOURCE_LOG_RETENTION_DAYS = 30`)
* **DB:** 매 계측 시 `timestamp < NOW() - 30일` 행 DELETE
* **로그 파일:** 매 계측 시 30일 경과 줄 제거 후 새 줄 추가(오래된 줄 삭제 + 신규 줄 추가 방식) → 파일 크기 무한 증가 방지
### 7.4 리소스 API
* **엔드포인트:** `GET /api/dashboard/admin/resources?days=30` (SYSTEM_ADMIN 전용, 범위 1~90일)
* **응답:**
- `current`: 요청 시점 실시간 계측값 (그래프 미저장)
- `history`: `system_resources` 테이블의 최근 N일 시계열 (그래프 렌더링용, **다운샘플링 적용**)
- `stats`: 활성 사용자/프로젝트/저장소 지표
* **다운샘플링 (조회 시):**
- 30일 원본은 약 21,600점(2분 간격) → 그대로 전송 시 페이로드 과다(~4MB)
- SQL에서 시간버킷 평균으로 축소: `bucket = max(120초, days*86400 / 300)` → 최대 약 300점
- `GROUP BY FLOOR(UNIX_TIMESTAMP(timestamp) / bucket)` + `AVG(...)`로 집계
- 계측 간격(2분)보다 버킷이 작으면 원본 해상도 유지 (하루 이하 조회 시)
- 효과: 30일 조회 페이로드 ~4MB → ~55KB 수준
+851
View File
@@ -0,0 +1,851 @@
# DB 스키마 (테이블 및 열 목록)
**최종 업데이트:** 2026-07-10
**DB 명:** `aislo_db` (MariaDB v10.6+, utf8mb4_unicode_ci)
**총 테이블 수:** 34개 (33개 현재 운영 + 1개 향후 사용)
---
## 1. 사용자 & 인증 & 조직 그룹
### 1-1. users 테이블
```
users
├── id (INT, PK) — 사용자 고유 번호
├── email (VARCHAR(255), UNIQUE) — 로그인 이메일
├── password_hash (VARCHAR(255)) — 비밀번호 암호화 저장
├── name (VARCHAR(255)) — 사용자 이름
├── position (VARCHAR(100), NULL) — 직급 (예: 과장, 대리, 사원)
├── department (VARCHAR(100), NULL) — 부서명 (예: 설계팀, 영업팀)
├── phone (VARCHAR(20), NULL) — 연락처 (예: 010-1234-5678)
├── company_id (INT, FK → companies.id, NULL) — 소속 회사
├── role (ENUM) — 역할: SYSTEM_ADMIN, ADMIN, USER (기본값: USER)
├── is_master (TINYINT(1)) — 회사 마스터 여부 (기본값: 0)
├── status (ENUM) — 상태: PENDING_EMAIL, NO_COMPANY, PENDING, ACTIVE, INACTIVE, REJECTED (기본값: PENDING_EMAIL)
├── last_login (DATETIME, NULL) — 마지막 로그인 시간
├── auth_expires_at (DATETIME, NULL) — 인증 유효 만료 시간 (3개월 주기)
├── last_email_verified_at (TIMESTAMP, NULL) — 마지막 이메일 인증 시간
├── login_failures (INT) — 연속 로그인 실패 횟수 (기본값: 0, 보안)
├── last_failed_at (TIMESTAMP, NULL) — 마지막 로그인 실패 시간
├── account_locked_until (TIMESTAMP, NULL) — 계정 잠금 해제 시간
├── created_at (TIMESTAMP) — 가입일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
**상태 생명주기:**
- PENDING_EMAIL: 회원가입 후 이메일 인증 전
- NO_COMPANY: 이메일 인증 완료, 회사 미연결 (로그인 가능, B02~B11 차단)
- PENDING: 회사 참여 신청 후 관리자 승인 대기
- ACTIVE: 회사 연결 완료, 모든 기능 접근 가능
- INACTIVE: 퇴사/계정 비활성화
- REJECTED: 회사 참여 신청 거부
### 1-2. companies 테이블
```
companies
├── id (INT, PK) — 회사 고유 번호
├── name (VARCHAR(255), UNIQUE) — 회사명
├── business_registration_number (VARCHAR(20), UNIQUE, NULL) — 사업자등록번호
├── business_address (VARCHAR(255), NULL) — 사업장 주소
├── business_owner (VARCHAR(100), NULL) — 사업주 이름
├── business_status (VARCHAR(50), NULL) — 기업 상태
├── company_code (VARCHAR(20), NULL) — 회사 코드
├── phone_number (VARCHAR(20), NULL) — 회사 연락처
├── representative_name (VARCHAR(100), NULL) — 대표자명
├── master_user_id (INT, NULL) — 마스터 사용자 ID
├── status (ENUM) — 상태: ACTIVE, INACTIVE, SUSPENDED (기본값: ACTIVE)
├── subscription_status (ENUM) — 구독 상태: FREE, TRIAL, PAID, EXPIRED (기본값: FREE)
├── created_by (INT, FK → users.id, NULL) — 회사 생성자
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-3. join_requests 테이블 (가입 신청 - 향후 사용)
```
join_requests
├── id (INT, PK) — 요청 고유 번호
├── user_id (INT, FK → users.id) — 신청한 사용자
├── company_id (INT, FK → companies.id) — 신청 대상 회사
├── requested_at (TIMESTAMP) — 신청 시간
├── status (ENUM) — 상태: PENDING, APPROVED, REJECTED (기본값: PENDING)
├── reviewed_by (INT, FK → users.id, NULL) — 검토한 관리자
├── reviewed_at (TIMESTAMP, NULL) — 검토 시간
├── review_comment (TEXT, NULL) — 검토 의견/거부 사유
└── UNIQUE KEY (user_id, company_id) — 중복 신청 방지
```
**동작 (향후 구현 예정):**
- 사용자 가입 신청 → status = PENDING 생성
- 관리자 승인 → status = APPROVED, users.status = ACTIVE, users.company_id 세팅
- 관리자 거부 → status = REJECTED, review_comment에 거부 사유 기록, 이메일 발송
### 1-5. email_otps 테이블 (이메일 인증)
```
email_otps
├── id (INT, PK) — OTP 고유 번호
├── email (VARCHAR(255)) — 이메일 주소
├── otp_code (VARCHAR(10)) — OTP 코드
├── purpose (VARCHAR(50)) — 목적: REGISTRATION, PASSWORD_RESET, EMAIL_VERIFICATION
├── expires_at (TIMESTAMP) — 만료 시간
├── verified_at (TIMESTAMP, NULL) — 인증 시간
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-6. sessions 테이블 (세션 관리)
```
sessions
├── id (INT, PK) — 세션 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── session_token (VARCHAR(255), UNIQUE) — 세션 토큰
├── expires_at (TIMESTAMP) — 세션 만료 시간
├── created_at (TIMESTAMP) — 생성일
├── last_accessed_at (TIMESTAMP, NULL) — 마지막 접근 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-7. trusted_devices 테이블 (장치 신뢰 관리)
```
trusted_devices
├── id (INT, PK) — 장치 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── device_fingerprint (VARCHAR(255)) — 장치 지문
├── device_name (VARCHAR(100), NULL) — 장치명
├── ip_address (VARCHAR(50)) — IP 주소
├── user_agent (TEXT) — 브라우저/OS 정보
├── trusted_until (TIMESTAMP, NULL) — 신뢰 만료 시간
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-8. user_consents 테이블 (사용자 동의 관리)
```
user_consents
├── id (INT, PK) — 동의 고유 번호
├── user_id (INT, FK → users.id) — 사용자 ID
├── consent_type (VARCHAR(100)) — 동의 종류: TERMS, PRIVACY, MARKETING, etc
├── consented (TINYINT(1)) — 동의 여부 (1 = 동의, 0 = 미동의)
├── consent_version (VARCHAR(20)) — 약관 버전
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-9. login_logs 테이블 (로그인 로그)
```
login_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
├── email (VARCHAR(255), NULL) — 로그인 시도 이메일
├── status (VARCHAR(50)) — 상태: SUCCESS, FAILURE, LOCKED
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
├── reason (TEXT, NULL) — 실패 사유
├── created_at (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-10. activity_logs 테이블 (활동 로그)
```
activity_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id, NULL) — 사용자 ID
├── action (VARCHAR(100)) — 행동: LOGIN, LOGOUT, FILE_UPLOAD, PROJECT_CREATE, etc
├── entity_type (VARCHAR(100), NULL) — 대상 타입: project, file, route, etc
├── entity_id (VARCHAR(50), NULL) — 대상 ID
├── description (TEXT, NULL) — 행동 설명
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── created_at (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-11. audit_logs 테이블 (감시 로그 - 보안)
```
audit_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id) — 행동 수행자
├── action (VARCHAR(100)) — 행동: CREATE, READ, UPDATE, DELETE, EXPORT, DOWNLOAD
├── entity_type (VARCHAR(100)) — 대상 타입: projects, routes, structures, outputs
├── entity_id (VARCHAR(50)) — 대상 ID
├── details (LONGTEXT, NULL) — 상세 정보 (JSON 형식)
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── timestamp (TIMESTAMP) — 기록 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 1-12. system_audit_logs 테이블 (시스템 감시)
```
system_audit_logs
├── id (INT, PK) — 로그 고유 번호
├── user_id (INT, FK → users.id) — 행동 수행자
├── action (VARCHAR(50)) — 행동: LOGIN, LOGOUT, USER_CREATE, COMPANY_CREATE, ROLE_CHANGE
├── resource_type (VARCHAR(50)) — 대상 타입: user, company, project, join_request
├── resource_id (INT, NULL) — 대상 ID
├── ip_address (VARCHAR(50), NULL) — IP 주소
├── user_agent (TEXT, NULL) — 브라우저/OS 정보
├── timestamp (TIMESTAMP) — 기록 시간
└── INDEX (timestamp, user_id) — 성능 최적화
```
### 1-13. system_resources 테이블 (시스템 리소스 모니터링)
```
system_resources
├── id (INT, PK) — 기록 고유 번호
├── cpu_usage_percent (FLOAT, NULL) — CPU 사용률 (%)
├── memory_usage_percent (FLOAT, NULL) — 메모리 사용률 (%)
├── disk_usage_percent (FLOAT, NULL) — 디스크 사용률 (%)
├── active_user_count (INT, NULL) — 활성 사용자 수
├── active_project_count (INT, NULL) — 활성 프로젝트 수
├── total_storage_mb (FLOAT, NULL) — 전체 저장소 사용량 (MB)
├── timestamp (TIMESTAMP) — 기록 시간
└── INDEX (timestamp) — 시계열 조회 최적화
```
**리소스 측정 방식:**
- **현재값 실측:** 요청 시점 실시간 측정 (DB 저장 없이 응답에 포함)
- **이력 조회:** `system_resources` 테이블에서 기본 30일치 시계열 조회
- **활성 지표:** active_user_count는 유효 세션 수, active_project_count는 미삭제 프로젝트 수 실시간 집계
### 1-14. support_requests 테이블 (고객 지원)
```
support_requests
├── id (INT, PK) — 요청 고유 번호
├── user_id (INT, FK → users.id) — 신청자
├── title (VARCHAR(255)) — 제목
├── description (LONGTEXT) — 설명
├── category (VARCHAR(100), NULL) — 카테고리
├── priority (VARCHAR(50), NULL) — 우선순위: LOW, MEDIUM, HIGH, URGENT
├── status (VARCHAR(50)) — 상태: OPEN, IN_PROGRESS, RESOLVED, CLOSED
├── assigned_to (INT, FK → users.id, NULL) — 담당자
├── resolution (LONGTEXT, NULL) — 해결 내용
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 2. 프로젝트 그룹
### 2-1. projects 테이블
```
projects
├── id (CHAR(36), PK) — 프로젝트 고유 ID (UUID)
├── user_id (INT, FK → users.id) — 프로젝트 소유자
├── company_id (INT, FK → companies.id) — 소속 회사
├── name (VARCHAR(255)) — 프로젝트명 (예: "2025년 산불진화임도")
├── region (VARCHAR(100), NULL) — 지역명 (예: "울진군 금강송면")
├── road_type (VARCHAR(100), NULL) — 임도 종류: 간선임도, 지선임도, 산불진화임도, 계류보전
├── project_year (INT, NULL) — 사업 연도 (예: 2025)
├── estimated_length_m (FLOAT, NULL) — 추정 연장 (미터)
├── memo (TEXT, NULL) — 비고/메모
├── status (VARCHAR(50), NULL) — 상태: NEW, FILE_UPLOADED, WF1_ANALYZING, WF1_COMPLETE, ... (기본값: NEW)
├── crs_epsg (INT, NULL) — 좌표계 (예: 5178 = 한국 표준, 기본값: 5178)
├── storage_path (VARCHAR(500), NULL) — 파일시스템 경로 (예: "storage/company/user/project_uuid")
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 2-2. project_versions 테이블 (프로젝트 버전 관리)
```
project_versions
├── id (INT, PK) — 버전 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── version_num (INT) — 버전 번호 (1, 2, 3, ...)
├── status (VARCHAR(50), NULL) — 저장된 상태
├── data (LONGTEXT, NULL) — 설계 데이터 스냅샷 (JSON 형식)
├── snapshot_at (TIMESTAMP) — 스냅샷 시점
└── created_at (TIMESTAMP) — 생성일
```
### 2-3. project_automations 테이블 (프로젝트 자동화 규칙)
```
project_automations
├── id (INT, PK) — 자동화 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── name (VARCHAR(100)) — 자동화 규칙명
├── logic_type (VARCHAR(50)) — 로직 타입: trigger_on_stage_complete, auto_notify, etc
├── config_json (LONGTEXT) — 자동화 설정 (JSON)
├── status (ENUM) — 상태: DRAFT, ACTIVE, INACTIVE (기본값: DRAFT)
├── created_by (INT, FK → users.id) — 생성자
├── updated_by (INT, FK → users.id, NULL) — 마지막 수정자
├── last_executed_at (DATETIME, NULL) — 마지막 실행 시간
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 3. 파일 & 업로드 관리 그룹
### 3-1. upload_sessions 테이블 (파일 업로드 세션)
```
upload_sessions
├── id (INT, PK) — 업로드 세션 고유 번호
├── user_id (INT, FK → users.id) — 업로드자
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── session_id (VARCHAR(255), UNIQUE) — 세션 ID
├── file_name (VARCHAR(255)) — 파일명
├── file_size_mb (FLOAT) — 파일 크기
├── chunk_count (INT) — 청크 개수
├── uploaded_chunks (INT) — 업로드된 청크 수
├── status (VARCHAR(50)) — 상태: PENDING, IN_PROGRESS, COMPLETED, FAILED (기본값: PENDING)
├── started_at (TIMESTAMP) — 시작 시간
├── completed_at (TIMESTAMP, NULL) — 완료 시간
└── expires_at (TIMESTAMP) — 세션 만료 시간
```
### 3-2. upload_chunks 테이블 (파일 청크 관리)
```
upload_chunks
├── id (INT, PK) — 청크 고유 번호
├── upload_session_id (INT, FK → upload_sessions.id) — 업로드 세션 ID
├── chunk_index (INT) — 청크 순서
├── chunk_size_mb (FLOAT) — 청크 크기
├── checksum (VARCHAR(255), NULL) — 체크섬 (무결성 검사)
├── status (VARCHAR(50)) — 상태: PENDING, UPLOADED, VERIFIED (기본값: PENDING)
├── uploaded_at (TIMESTAMP, NULL) — 업로드 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 3-3. input_files 테이블 (원본 입력 파일)
```
input_files
├── id (INT, PK) — 파일 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 속한 프로젝트
├── file_type (VARCHAR(50), NULL) — 파일 종류: las, tif, tfw, prj, dxf, dwg, other
├── original_filename (VARCHAR(255)) — 원래 파일명 (예: "cloud_merged.las")
├── raw_file_path (VARCHAR(500)) — 원본 저장 경로 (예: "storage/.../raw/las/cloud_merged.las")
├── file_size_mb (FLOAT, NULL) — 파일 크기 (MB)
├── upload_by (INT, FK → users.id, NULL) — 업로드한 사용자
├── upload_at (TIMESTAMP) — 업로드 날짜
├── crs_epsg (INT, NULL) — 파일 좌표계 (예: 5178)
├── metadata (LONGTEXT, NULL) — 파일 메타데이터 (JSON)
├── status (VARCHAR(50), NULL) — 상태: UPLOADED, PROCESSED, ARCHIVED (기본값: UPLOADED)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 4. 지표면 분석 & 처리 그룹
### 4-1. processed_point_cloud 테이블 (변환된 포인트클라우드)
```
processed_point_cloud
├── id (INT, PK) — 변환 데이터 고유 번호
├── input_file_id (INT, FK → input_files.id) — 원본 LAS 파일 참조
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── process_type (VARCHAR(50), NULL) — 변환 방식: filtered, sampled, classified
├── processed_file_path (VARCHAR(500), NULL) — 변환된 파일 경로
├── converted_format (VARCHAR(50), NULL) — 변환 포맷: las, ply, laz 등
├── converted_file_path (VARCHAR(500), NULL) — 변환 포맷 저장 경로
├── point_count (INT, NULL) — 포인트 개수
├── min_z (FLOAT, NULL) — 최저 높이
├── max_z (FLOAT, NULL) — 최고 높이
├── mean_z (FLOAT, NULL) — 평균 높이
├── x_min (FLOAT, NULL) — 최소 경도
├── x_max (FLOAT, NULL) — 최대 경도
├── y_min (FLOAT, NULL) — 최소 위도
├── y_max (FLOAT, NULL) — 최대 위도
├── density_per_sqm (FLOAT, NULL) — 단위 면적당 포인트 밀도
├── classification_summary (LONGTEXT, NULL) — 분류 요약 (JSON)
├── processing_params (LONGTEXT, NULL) — 변환 파라미터 (JSON)
├── processed_at (TIMESTAMP) — 변환 완료 날짜
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 4-2. surface_models 테이블 (지표면 모델)
```
surface_models
├── id (INT, PK) — 지표면 모델 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── model_type (VARCHAR(50), NULL) — 모델 종류: dem_grid, tin, mesh_triangulated, contour_lines
├── source_file_id (INT, FK → input_files.id, NULL) — 원본 LAS 파일
├── processed_cloud_id (INT, FK → processed_point_cloud.id, NULL) — 변환된 포인트클라우드 참조
├── status (VARCHAR(50), NULL) — 상태: PROCESSING, COMPLETE, FAILED (기본값: PROCESSING)
├── crs_epsg (INT, NULL) — 좌표계
├── resolution_m (FLOAT, NULL) — 해상도 (래스터인 경우)
├── model_file_path (VARCHAR(500), NULL) — 모델 저장 경로 (예: "storage/.../surface/dem.tif")
├── generation_params (LONGTEXT, NULL) — 생성 파라미터 (JSON)
├── created_at (TIMESTAMP) — 생성일
├── completed_at (TIMESTAMP, NULL) — 완료 시간
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 4-3. terrain_layers 테이블 (지형 레이어)
```
terrain_layers
├── id (INT, PK) — 레이어 고유 번호
├── surface_model_id (INT, FK → surface_models.id) — 지표면 모델 참조
├── layer_name (VARCHAR(100), NULL) — 레이어명 (예: "지표", "제1층", "제2층")
├── geometry_type (VARCHAR(50), NULL) — 기하 종류: POINTCLOUD, GRID, MESH, CONTOUR
├── layer_file_path (VARCHAR(500), NULL) — 레이어 파일 저장 경로
├── file_format (VARCHAR(50), NULL) — 파일 형식: geojson, geotiff, ply, obj 등
├── file_size_mb (FLOAT, NULL) — 파일 크기
├── statistics (LONGTEXT, NULL) — 통계 (JSON)
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 5. 경로 & 설계 그룹
### 5-1. routes 테이블 (경로 설계)
```
routes
├── id (INT, PK) — 경로 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── surface_model_id (INT, FK → surface_models.id, NULL) — 기반 지표면 모델
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED, ARCHIVED (기본값: DRAFT)
├── start_chainage_m (FLOAT, NULL) — 시작점 측점 (m)
├── end_chainage_m (FLOAT, NULL) — 종료점 측점 (m)
├── total_length_m (FLOAT, NULL) — 총 연장 (m)
├── grade_percent (LONGTEXT, NULL) — 각 구간 종단 경사도 (JSON array)
├── constraints (LONGTEXT, NULL) — 설계 제약조건 (JSON)
├── algorithm_params (LONGTEXT, NULL) — 알고리즘 파라미터 (JSON)
├── route_data_path (VARCHAR(500), NULL) — 경로 데이터 저장 경로 (GeoJSON)
├── computed_at (TIMESTAMP, NULL) — 계산 완료 날짜
├── created_at (TIMESTAMP) — 생성일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
**참고:** 경로 좌표는 JSON 형식으로 `route_data_path` 파일에 저장됨 (GeoJSON LineString)
### 5-2. route_points 테이블 (경로 샘플 포인트)
```
route_points
├── id (INT, PK) — 포인트 고유 번호
├── route_id (INT, FK → routes.id) — 경로 ID
├── chainage_m (FLOAT, NULL) — 측점 (m)
├── elevation_m (FLOAT, NULL) — 높이
├── slope_percent (FLOAT, NULL) — 경사도 (%)
├── sequence_num (INT, NULL) — 순서번호
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 5-3. route_statistics 테이블 (경로 통계)
```
route_statistics
├── id (INT, PK) — 통계 고유 번호
├── route_id (INT, FK → routes.id) — 경로 ID
├── min_slope (FLOAT, NULL) — 최소 경사도 (%)
├── max_slope (FLOAT, NULL) — 최대 경사도 (%)
├── mean_slope (FLOAT, NULL) — 평균 경사도 (%)
├── cut_volume_m3 (FLOAT, NULL) — 절토량 (m³)
├── fill_volume_m3 (FLOAT, NULL) — 성토량 (m³)
├── tree_cutting_volume (FLOAT, NULL) — 목재 절감 추정량
└── cost_score (FLOAT, NULL) — 알고리즘 점수
```
---
## 6. 종단면 & 횡단면 그룹
### 6-1. longitudinal_sections 테이블 (종단면)
```
longitudinal_sections
├── id (INT, PK) — 종단면 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── route_id (INT, FK → routes.id) — 경로 ID
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
│ ├── chainages (측점 배열, m)
│ ├── elevations (지표 표고 배열)
│ ├── grades (경사도 배열, %)
│ └── design_elevations (설계 표고 배열)
├── longitudinal_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 6-2. cross_sections 테이블 (횡단면)
```
cross_sections
├── id (INT, PK) — 횡단면 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── route_id (INT, FK → routes.id) — 경로 ID
├── chainage_m (FLOAT, NULL) — 측점 (예: 0, 20, 40, ... m)
├── sequence_num (INT, NULL) — 횡단면 순번 (1, 2, ...)
├── status (VARCHAR(50), NULL) — 상태: DRAFT, CONFIRMED (기본값: DRAFT)
├── data (LONGTEXT, NULL) — 상세 데이터 (JSON)
│ ├── left_slope (좌측 사면 경사, %)
│ ├── right_slope (우측 사면 경사, %)
│ ├── width_m (노폭)
│ ├── cut_volume_m3 (절토량)
│ ├── fill_volume_m3 (성토량)
│ ├── structures (이 단면 내 구조물 ID 배열)
│ └── notes (메모)
├── cross_section_file_path (VARCHAR(500), NULL) — 저장 경로 (JSON)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 7. 구조물 & 수량 그룹
### 7-1. structures 테이블 (구조물)
```
structures
├── id (INT, PK) — 구조물 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── cross_section_id (INT, FK → cross_sections.id, NULL) — 횡단면 ID
├── structure_type (VARCHAR(100), NULL) — 종류: 낙석방지책, 돌붙임, 계간수로, 낙차공 등
├── chainage_m (FLOAT, NULL) — 측점 (m)
├── location (VARCHAR(50), NULL) — 위치: LEFT, CENTER, RIGHT
├── length_m (FLOAT, NULL) — 길이
├── width_m (FLOAT, NULL) — 폭
├── height_m (FLOAT, NULL) — 높이
├── material (VARCHAR(100), NULL) — 재료: 강재, 콘크리트, 목재, 돌 등
├── quantity (INT, NULL) — 개수
├── unit_price (FLOAT, NULL) — 단가
├── design_notes (LONGTEXT, NULL) — 설계 노트 (JSON)
├── structure_data_path (VARCHAR(500), NULL) — 구조물 데이터 저장 경로
├── last_modified_by (INT, FK → users.id, NULL) — 마지막 수정자
├── created_at (TIMESTAMP) — 생성일
├── updated_at (TIMESTAMP) — 수정일
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 7-2. quantity_items 테이블 (수량 항목)
```
quantity_items
├── id (INT, PK) — 수량 항목 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── category (VARCHAR(100), NULL) — 대분류: 토공, 구조물, 포장, 배수, 녹화, 안전시설
├── item_name (VARCHAR(255), NULL) — 항목명 (예: "절토 일반", "낙석방지책 설치")
├── unit (VARCHAR(50), NULL) — 단위: m3, 개, m, m2
├── quantity_design (FLOAT, NULL) — 설계 수량
├── quantity_actual (FLOAT, NULL) — 실제 수량 (사용자 수정 가능)
├── unit_price (FLOAT, NULL) — 단가
├── total_price (FLOAT, NULL) — 소계 (quantity_actual × unit_price)
├── standard_reference (VARCHAR(255), NULL) — 기준 참고 자료
├── quantity_data_path (VARCHAR(500), NULL) — 수량 데이터 저장 경로
├── data (LONGTEXT, NULL) — 계산 과정 메모 (JSON)
│ ├── formula (계산식)
│ ├── source (데이터 출처)
│ └── ...
├── computed_at (TIMESTAMP, NULL) — 계산 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 8. 산출물 & 문서 그룹
### 8-1. outputs 테이블 (최종 산출물 세트)
```
outputs
├── id (INT, PK) — 산출물 세트 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── output_type (VARCHAR(100), NULL) — 종류: estimation_excel, drawing_dxf, report_pdf, all_bundle
├── status (VARCHAR(50), NULL) — 상태: GENERATING, COMPLETE, FAILED (기본값: GENERATING)
├── generated_by (INT, FK → users.id, NULL) — 생성자
├── generated_at (TIMESTAMP) — 생성 날짜
├── version (INT, NULL) — 버전 (기본값: 1)
├── outputs_directory_path (VARCHAR(500), NULL) — 산출물 저장 폴더 (예: "storage/.../outputs/v1/")
├── metadata (LONGTEXT, NULL) — 메타데이터 (JSON)
│ ├── template_used (사용한 양식)
│ ├── company_name (회사명)
│ ├── project_name (프로젝트명)
│ ├── total_cost (총 비용)
│ └── generation_time_sec (생성 소요 시간)
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
### 8-2. output_files 테이블 (최종 산출 파일)
```
output_files
├── id (INT, PK) — 파일 고유 번호
├── output_id (INT, FK → outputs.id) — 산출물 ID
├── file_type (VARCHAR(50), NULL) — 파일 형식: xlsx, pdf, dxf, dwg, json, zip
├── original_filename (VARCHAR(255), NULL) — 파일명 (예: "견적서_v1.xlsx")
├── output_file_path (VARCHAR(500), NULL) — 저장 경로 (예: "storage/.../outputs/v1/견적서.xlsx")
├── file_size_mb (FLOAT, NULL) — 파일 크기
├── download_count (INT, NULL) — 다운로드 횟수 (기본값: 0)
├── created_at (TIMESTAMP) — 생성 날짜
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 9. 변경 이력 & 감시 그룹
### 9-1. change_logs 테이블 (설계 변경 기록)
```
change_logs
├── id (INT, PK) — 변경 고유 번호
├── project_id (CHAR(36), FK → projects.id) — 프로젝트 ID
├── changed_by (INT, FK → users.id) — 변경자
├── changed_at (TIMESTAMP) — 변경 날짜
├── entity_type (VARCHAR(100)) — 대상 타입: routes, cross_sections, structures, quantity_items
├── entity_id (VARCHAR(50)) — 대상 ID
├── old_value (LONGTEXT, NULL) — 변경 전 값 (JSON)
├── new_value (LONGTEXT, NULL) — 변경 후 값 (JSON)
├── reason (TEXT, NULL) — 변경 사유
└── deleted_at (TIMESTAMP, NULL) — 삭제일 (soft delete)
```
---
## 10. 테이블 관계도
```
users (사용자)
├── id (PK)
├── email
├── password_hash
├── role (SYSTEM_ADMIN | ADMIN | USER)
├── is_master
├── status (생명주기)
├── company_id (FK) ──→ companies.id
├── last_login
└── auth_expires_at
companies (회사)
├── id (PK)
├── name
├── status (ACTIVE | INACTIVE | SUSPENDED)
├── subscription_status (FREE | TRIAL | PAID | EXPIRED)
├── created_by (FK) ──→ users.id
└── created_at
projects (프로젝트)
├── id (UUID, PK)
├── user_id (FK) ──→ users.id
├── company_id (FK) ──→ companies.id
├── name
├── status
├── crs_epsg
├── storage_path
└── created_at
├─→ input_files (입력 파일)
│ └─→ processed_point_cloud (변환된 포인트클라우드)
│ └─→ surface_models (지표면 모델)
│ └─→ terrain_layers (지형 레이어)
├─→ routes (경로)
│ ├─→ route_points (경로 포인트)
│ ├─→ route_statistics (경로 통계)
│ ├─→ longitudinal_sections (종단면)
│ └─→ cross_sections (횡단면)
│ └─→ structures (구조물)
├─→ quantity_items (수량 항목)
├─→ outputs (산출물)
│ └─→ output_files (산출물 파일)
├─→ project_versions (프로젝트 버전)
├─→ project_automations (프로젝트 자동화)
├─→ audit_logs (감시 로그)
└─→ change_logs (변경 로그)
```
---
## 11. 파일시스템 경로와 DB 링크 (워크플로우 기반 구조)
### 파일시스템 구조 (6단계 워크플로우)
```
storage/
├── {company_slug}/
│ └── {user_slug}/
│ └── {project_id}/ ← projects.storage_path
│ │
│ ├── B03_FileInput/ ← WF0: 파일 입력
│ │ └── input/ (원본 입력 파일들)
│ │ ├── las/
│ │ │ └── cloud_merged.las ← input_files.raw_file_path
│ │ ├── tif/
│ │ ├── tfw/
│ │ ├── prj/
│ │ └── dxf/
│ │
│ ├── B04_wf1_Surface/ ← WF1: 지표면 분석
│ │ ├── processed/ (변환된 포인트클라우드)
│ │ │ ├── cloud_filtered.las ← processed_point_cloud.processed_file_path
│ │ │ └── cloud_sampled.ply ← processed_point_cloud.converted_file_path
│ │ └── models/ (지표면 모델 & 레이어)
│ │ ├── dem_2m.tif ← surface_models.model_file_path
│ │ ├── tin_mesh.obj
│ │ ├── layer_ground.geojson ← terrain_layers.layer_file_path
│ │ └── layer_1.geojson
│ │
│ ├── B05_wf2_Route/ ← WF2: 경로 설계
│ │ └── route/
│ │ ├── route_main.geojson ← routes.route_data_path
│ │ ├── route_points.json
│ │ └── route_statistics.json
│ │
│ ├── B06_wf3_ProfileCross/ ← WF3: 종횡단 생성
│ │ ├── longitudinal/
│ │ │ └── longitudinal.json ← longitudinal_sections.longitudinal_file_path
│ │ └── cross_sections/
│ │ ├── cross_0000m.json ← cross_sections.cross_section_file_path
│ │ ├── cross_0020m.json
│ │ └── ...
│ │
│ ├── B07_wf4_DesignDetail/ ← WF4: 상세 설계
│ │ └── structures/
│ │ ├── struct_0020m_001.json ← structures.structure_data_path
│ │ └── ...
│ │
│ ├── B08_wf5_Quantity/ ← WF5: 수량 산출
│ │ ├── quantities/
│ │ │ └── items.json ← quantity_items.quantity_data_path
│ │ └── ...
│ │
│ └── B09_wf6_Estimation/ ← WF6: 견적·문서
│ ├── v1/ ← outputs.outputs_directory_path
│ │ ├── 견적서.xlsx ← output_files.output_file_path
│ │ ├── 설계도.dxf
│ │ └── ...
│ └── v2/
│ └── ...
```
**핵심 개념:**
- **B03~B09**: 워크플로우 6단계와 1:1 대응
- **각 단계의 입력/출력**: 명확히 분리
- **버전 관리**: B09 단계에서만 (v1, v2, ...)
---
## 12. 핵심 쿼리 예제
### 예제 1: 사용자의 모든 프로젝트 조회
```sql
SELECT p.id, p.name, p.status, p.created_at
FROM projects p
WHERE p.user_id = :user_id AND p.deleted_at IS NULL
ORDER BY p.created_at DESC;
```
### 예제 2: 프로젝트의 최신 경로 조회
```sql
SELECT r.id, r.total_length_m, r.status
FROM routes r
WHERE r.project_id = :project_id AND r.deleted_at IS NULL
ORDER BY r.computed_at DESC
LIMIT 1;
```
### 예제 3: 경로의 모든 횡단면 조회
```sql
SELECT cs.id, cs.chainage_m, cs.data
FROM cross_sections cs
WHERE cs.route_id = :route_id AND cs.deleted_at IS NULL
ORDER BY cs.chainage_m;
```
### 예제 4: 프로젝트 총 비용 계산
```sql
SELECT
SUM(quantity_actual * unit_price) as total_cost,
category
FROM quantity_items
WHERE project_id = :project_id AND deleted_at IS NULL
GROUP BY category;
```
### 예제 5: 사용자의 감시 로그 (최근 30일)
```sql
SELECT action, entity_type, timestamp
FROM audit_logs
WHERE user_id = :user_id AND timestamp > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY timestamp DESC;
```
---
## 13. 데이터 타입 가이드
| 타입 | 설명 | 예제 |
|------|------|------|
| INT | 정수 | 1, 2, 100 |
| FLOAT | 실수 | 1.5, 2.25, 100.75 |
| VARCHAR(n) | 문자열 (고정 길이) | "project_name" |
| TEXT / LONGTEXT | 문자열 (가변, 대용량) | 설명문, JSON 데이터 |
| TIMESTAMP | 날짜 + 시간 (자동 업데이트) | 2025-07-05 15:30:00 |
| DATETIME | 날짜 + 시간 (수동) | 2025-07-05 15:30:00 |
| TINYINT(1) | 참/거짓 (0 또는 1) | 0, 1 |
| ENUM | 열거형 | ACTIVE, INACTIVE |
| CHAR(36) | UUID (고정 36자) | 550e8400-e29b-41d4-a716-446655440000 |
---
## 14. 총 테이블 수 및 분류
### 34개 테이블 분류
**1. 사용자/회사/조직 (14개)**
- users, companies, join_requests (향후), email_otps, sessions, trusted_devices, user_consents
- login_logs, activity_logs, audit_logs, system_audit_logs, system_resources, support_requests
**2. 프로젝트 관리 (3개)**
- projects, project_versions, project_automations
**3. 파일/업로드 (3개)**
- upload_sessions, upload_chunks, input_files
**4. 지표면 분석 (3개)**
- processed_point_cloud, surface_models, terrain_layers
**5. 경로 설계 (3개)**
- routes, route_points, route_statistics
**6. 종횡단 설계 (2개)**
- longitudinal_sections, cross_sections
**7. 구조물/수량 (2개)**
- structures, quantity_items
**8. 산출물 (2개)**
- outputs, output_files
**9. 변경/이력 (1개)**
- change_logs
---
## 15. DB vs 파일시스템 역할 분리
| 저장 위치 | 내용 | 예시 |
|----------|------|------|
| **DB (MariaDB)** | 메타데이터 + 경로정보 + 상태 | 파일명, 크기, 좌표계, 상태, 저장경로, 통계 |
| **파일시스템** | 실제 파일 데이터 | LAS, PLY, TIF, DXF, Excel, PDF, JSON 파일들 |
**설계 원칙:**
- DB에는 "어디에 무엇이 있는가"의 메타정보만 저장
- 실제 대용량 파일은 파일시스템에 저장
- 각 워크플로우 단계의 입력/출력 경로를 DB에 기록
- JSON 형식으로 좌표 및 복잡한 데이터 구조를 저장
---
## 16. 마이그레이션 상태
### ✅ 완료됨
- [x] 33개 테이블 생성 (MariaDB)
- [x] 관계(FK) 설정
- [x] Soft delete (deleted_at) 지원
- [x] 인덱스 추가 (주요 조회 경로)
### 📋 진행 중
- [ ] API 라우터에서 파일 경로 저장 로직 추가
- B03 파일 업로드 → `input_files.raw_file_path` 저장
- B04 WF1 실행 → `surface_models.model_file_path`, `terrain_layers.layer_file_path` 저장
- B05 WF2 실행 → `routes.route_data_path` 저장
- B06 WF3 실행 → `longitudinal_sections.longitudinal_file_path`, `cross_sections.cross_section_file_path` 저장
- B07 WF4 실행 → `structures.structure_data_path` 저장
- B08 WF5 실행 → `quantity_items.quantity_data_path` 저장
- B09 WF6 실행 → `outputs.outputs_directory_path`, `output_files.output_file_path` 저장
### 🔮 향후 계획
- [ ] Python Pydantic 모델 정의 (ORM 대신)
- [ ] API 엔드포인트 보안 감시 강화
- [ ] 실시간 알림 시스템 (WebSocket)
+445
View File
@@ -0,0 +1,445 @@
# Wiza — Style Reference
> Twilight prospecting observatory bathed in lavender mist
**Theme:** light
Wiza operates in a light-mode universe where deep violet ink commands attention against a soft lavender atmosphere. The brand lives between a confident enterprise tool and an approachable consumer product—white surfaces, thin borders, and compact Inter type for dense data interfaces, paired with Britti Sans display headlines that feel geometric and assured. The signature move is the gradient wash: pale lavender backgrounds bleeding white-to-violet-to-peach, making even a sprawling data table feel sunlit. Purple is reserved for authority—headlines, logo, filled actions—while the rest of the UI stays in quiet neutrals, letting contact data breathe.
## Tokens — Colors
| Name | Value | Token | Role |
|------|-------|-------|------|
| Deep Iris | `#26114a` | `--color-deep-iris` | Violet supporting accent for decorative details and low-frequency emphasis. Do not promote it to the primary CTA color |
| Plum Velvet | `#312749` | `--color-plum-velvet` | Navigation text, secondary headings, card titles, footer copy — slightly lighter companion to Deep Iris |
| Royal Amethyst | `#3e0079` | `--color-royal-amethyst` | Links, input focus rings, accent strokes, icon highlights — the most saturated violet |
| Mist Violet | `#edecff` | `--color-mist-violet` | Soft highlight washes, tinted section backgrounds, decorative blurs — the lavender atmosphere |
| Lavender Wash | `linear-gradient(90deg, rgb(185, 154, 255), rgb(125, 67, 255) 10%, rgba(125, 67, 255, 0))` | `--color-lavender-wash` | Decorative gradient stops and hero atmosphere |
| Twilight Beam | `linear-gradient(to right, rgb(207, 138, 255), rgb(255, 102, 193), rgb(255, 173, 116), rgb(170, 129, 255))` | `--color-twilight-beam` | Multi-stop decorative gradient for hero glow effects |
| Canvas | `#ffffff` | `--color-canvas` | Page background, card surfaces, input fields, button text on dark fills |
| Paper | `#f6f7fa` | `--color-paper` | Subtle surface lift, table row alternation, secondary card backgrounds |
| Mist | `#e6e2e3` | `--color-mist` | Hairline borders, dividers, subtle separators |
| Smoke | `#c1c7cf` | `--color-smoke` | Disabled states, skeleton placeholders, muted background blocks |
| Ash | `#9491a1` | `--color-ash` | Muted helper text, placeholder copy, secondary metadata |
| Slate | `#615e6e` | `--color-slate` | Secondary body text, descriptions, table metadata |
| Charcoal | `#333333` | `--color-charcoal` | Body text, link text, icon strokes in neutral contexts |
| Carbon | `#222222` | `--color-carbon` | Dark UI elements, near-black accents |
| Ink | `#000000` | `--color-ink` | Maximum contrast text and borders in specific dark UI elements |
## Tokens — Typography
### Britti Sans — Display and heading typeface used exclusively at 2464px, always weight 500. The 1.0 line-height is distinctive — headings sit tight with no leading slack, creating geometric, almost architectural presence. Substitute with Plus Jakarta Sans (500) or General Sans (500) for closest geometric match. · `--font-britti-sans`
- **Substitute:** Plus Jakarta Sans
- **Weights:** 500
- **Sizes:** 24px, 32px, 40px, 56px, 64px
- **Line height:** 1.00
- **Role:** Display and heading typeface used exclusively at 2464px, always weight 500. The 1.0 line-height is distinctive — headings sit tight with no leading slack, creating geometric, almost architectural presence. Substitute with Plus Jakarta Sans (500) or General Sans (500) for closest geometric match.
### Inter — Workhorse UI and body typeface. Weight 400 for body, 500 for buttons/nav/table headers, 700 for emphasized labels. The tight size range (1216px) and compact line-heights signal a data-dense product, not a marketing site. Substitute with Inter directly or IBM Plex Sans. · `--font-inter`
- **Substitute:** Inter
- **Weights:** 400, 500, 700
- **Sizes:** 12px, 14px, 16px
- **Line height:** 1.301.50
- **Role:** Workhorse UI and body typeface. Weight 400 for body, 500 for buttons/nav/table headers, 700 for emphasized labels. The tight size range (1216px) and compact line-heights signal a data-dense product, not a marketing site. Substitute with Inter directly or IBM Plex Sans.
### Type Scale
| Role | Size | Line Height | Letter Spacing | Token |
|------|------|-------------|----------------|-------|
| caption | 12px | 1.3 | — | `--text-caption` |
| body-sm | 14px | 1.4 | — | `--text-body-sm` |
| body | 16px | 1.5 | — | `--text-body` |
| subheading | 24px | 1 | — | `--text-subheading` |
| heading-sm | 32px | 1 | — | `--text-heading-sm` |
| heading | 40px | 1 | — | `--text-heading` |
| heading-lg | 56px | 1 | — | `--text-heading-lg` |
| display | 64px | 1 | — | `--text-display` |
## Tokens — Spacing & Shapes
**Base unit:** 8px
**Density:** compact
### Spacing Scale
| Name | Value | Token |
|------|-------|-------|
| 8 | 8px | `--spacing-8` |
| 16 | 16px | `--spacing-16` |
| 24 | 24px | `--spacing-24` |
| 32 | 32px | `--spacing-32` |
| 40 | 40px | `--spacing-40` |
| 48 | 48px | `--spacing-48` |
| 56 | 56px | `--spacing-56` |
| 64 | 64px | `--spacing-64` |
| 96 | 96px | `--spacing-96` |
| 104 | 104px | `--spacing-104` |
| 216 | 216px | `--spacing-216` |
### Border Radius
| Element | Value |
|---------|-------|
| cards | 8px |
| icons | 8px |
| large | 24px |
| pills | 1440px |
| inputs | 8px |
| buttons | 8px |
### Shadows
| Name | Value | Token |
|------|-------|-------|
| sm | `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105...` | `--shadow-sm` |
| lg | `rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59,...` | `--shadow-lg` |
| lg-2 | `rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47...` | `--shadow-lg-2` |
| sm-2 | `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105...` | `--shadow-sm-2` |
| sm-3 | `rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151,...` | `--shadow-sm-3` |
### Layout
- **Page max-width:** 1200px
- **Section gap:** 64-80px
- **Card padding:** 16-24px
- **Element gap:** 8px
## Components
### Filled Brand Button
**Role:** Primary action trigger in hero and CTA blocks
Background #26114a, text #ffffff, 8px radius, 16px vertical / 24px horizontal padding, Inter 500 at 16px. Carries the brand weight — only used for the most important conversion per screen.
### Ghost Outlined Button
**Role:** Secondary action paired beside a filled button
Background transparent, border 1px #26114a, text #26114a, 8px radius, 16px/24px padding, Inter 500 at 16px. Matches the filled button's exact dimensions for optical pairing.
### Pill Navigation Button
**Role:** Top-level nav items in the floating header
1440px radius, 8px/16px padding, Inter 500 at 14px, text #312749. Active state may use #edecff background. The full-pill shape is distinctive — not rounded rectangles, but true capsules.
### Logo Lockup
**Role:** Brand identity in header and footer
Purple triangular icon mark paired with 'wiza' wordmark in Britti Sans 500 or Inter 700. Icon uses a gradient from #b99aff to #7d43ff. Appears in a white pill container with 8px radius and subtle shadow.
### Product Screenshot Frame
**Role:** Hero product demonstration and feature illustrations
White surface with 8px radius, surrounded by the deep layered shadow stack (rgba(14,59,101,0.06) large blur). Floats over the lavender gradient background, creating depth without borders.
### Data Table Row
**Role:** Contact/prospect list display in product mockups
Alternating #ffffff and #f6f7fa backgrounds, 1px #e6e2e3 bottom border, 12px/16px cell padding, Inter 400 at 14px for data, Inter 500 for headers. Dense, compact rows with colored status dots.
### Logo Cloud Bar
**Role:** Social proof — client brand logos
Monochrome (Charcoal #333333) logo strip on white background, centered, with consistent 4060px height per logo. No colorful logos — all desaturated for visual calm.
### Rating Badge Pair
**Role:** Third-party validation scores (G2, Chrome Web Store)
White pill cards (1440px radius), Inter 500 at 14px for score, 12px for platform name. Star icon in #3e0079 or #f5b800. Displayed in a centered pair with 24px gap.
### Section Heading Block
**Role:** Intro text for content sections
Optional pill tag above (Mist Violet #edecff background, Royal Amethyst #3e0079 text, 1440px radius, Inter 500 12px, 4px/12px padding). Main heading in Britti Sans 500 at 4056px, Deep Iris #26114a. Subtext in Inter 400 at 1618px, Slate #615e6e.
### Profile Card (Magic Wand Feature)
**Role:** Contact preview cards in feature illustrations
White surface, 8px radius, subtle border, 12px/16px padding. Contains circular avatar (32px), name in Inter 500 14px #26114a, title in Inter 400 12px #615e6, company in Inter 400 12px #9491a1. Arranged in 3-column grid with 16px gaps.
### Floating Navigation Bar
**Role:** Sticky header container
White background with 8px radius, floats over hero with the layered shadow. Contains logo, nav links (Pill Navigation Button style), and login/signup actions. Max-width contained with side padding.
### Tag/Badge (Pill)
**Role:** Feature labels, job titles, status indicators
1440px radius, 4px/12px padding, Inter 500 at 12px. Variants: #edecff bg with #3e0079 text (accent tags), #f6f7fa bg with #615e6 text (neutral tags), or status-specific colors.
### Input Field
**Role:** Search and form inputs in product UI
1px #e6e2e3 border, 8px radius, 10px/14px padding, Inter 400 at 14px, placeholder in Ash #9491a1. Focus state uses 2px Royal Amethyst #3e0079 border ring.
### Feature Icon Tile
**Role:** Small decorative icon containers in section intros
16px or 24px square tiles with 8px radius, Mist Violet #edecff background, purple stroke icon inside. Used sparingly to mark section types.
## Do's and Don'ts
### Do
- Use 8px radius for all rectangular UI surfaces — cards, buttons, inputs, icon containers. The 8px is the signature, not 4px or 12px.
- Use 1440px radius exclusively for pills, tags, and capsule-shaped nav items — never for cards or panels.
- Set Britti Sans headings to line-height 1.0 exactly. The tight leading is what makes the type feel architectural.
- Use Deep Iris #26114a for headlines and filled button backgrounds. Never use it for body text larger than 24px — it overwhelms at small sizes.
- Pair every filled button with an adjacent ghost button of identical dimensions for the hero CTA pattern.
- Anchor section intros with a small pill tag above the heading in Mist Violet + Royal Amethyst.
- Use the Lavender Wash gradient on hero and feature section backgrounds — it defines the atmosphere.
- Keep Inter at 1216px for all UI text. The compact size range signals a data-dense product.
### Don't
- Do not use Royal Amethyst #3e0079 for body text — it vibrates uncomfortably at small sizes; reserve it for links, icons, and accent strokes.
- Do not apply 4px or 12px radius to cards — the 8px is non-negotiable for visual consistency.
- Do not use line-height above 1.0 for Britti Sans headings — the tightness is the point.
- Do not introduce new chromatic colors outside the violet family — the palette is deliberately monochromatic-violet.
- Do not use heavy drop shadows. The blue-tinted shadows are subtle (max 32px blur at 6% opacity).
- Do not center body text in multi-line paragraphs — only headlines and short intros get center alignment.
- Do not place dark text directly on the lavender gradient — use a white surface card between them for legibility.
- Do not use rounded images or photos in feature illustrations — the design is iconographic, not photographic.
## Surfaces
| Level | Name | Value | Purpose |
|-------|------|-------|---------|
| 0 | Canvas | `#ffffff` | Page background, outermost layer |
| 1 | Paper | `#f6f7fa` | Card surfaces, table containers, subtle lifts |
| 2 | Mist Violet | `#edecff` | Tinted highlight sections, gradient start point |
| 3 | Lavender Glow | `#b99aff` | Hero gradient stops, atmospheric overlays |
## Elevation
- **Cards and elevated panels:** `rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px`
- **Buttons and nav items:** `rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px`
- **Product feature highlights:** `rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px`
## Imagery
The visual language is almost entirely product-screenshot and iconographic — no lifestyle photography, no stock imagery, no people in context. Product mockups show a contact-prospecting data table with purple status indicators, person avatars, and company logos. Decorative visuals use soft purple gradient washes with floating profile cards arranged in grid patterns. Icons are simple, single-stroke, geometric shapes (a triangle logo, outlined feature icons). Logos in the trust bar are desaturated to monochrome. The overall feel is that of a polished SaaS dashboard screenshot rather than a marketing site with imagery.
## Layout
Centered, max-width 1200px container with generous vertical breathing room (6480px section gaps). The hero is a full-bleed lavender-to-white gradient with a centered headline stack and dual CTA buttons, followed by a floating product screenshot that breaks the container edge slightly. Below the fold, content alternates between white surface sections and light lavender tinted sections. The logo cloud is a single centered row. Feature sections use centered heading blocks followed by 2-column or 3-column card grids. A sticky floating navigation bar (pill-shaped, centered) sits over the hero.
## Agent Prompt Guide
primary action: no distinct CTA color
**Quick Color Reference**
- text (primary): #26114a
- text (secondary): #615e6e
- text (muted): #9491a1
- background: #ffffff
- surface: #f6f7fa
- border: #e6e2e3
- brand/accent: #3e0079
- filled button background: #26114a
**Example Component Prompts**
1. **Hero Section**: White-to-lavender gradient background (linear-gradient from #ffffff to #edecff). Centered headline 'Find verified emails and phone numbers' in Britti Sans weight 500 at 56px, color #26114a, line-height 1.0. Subtext in Inter 400 at 18px, color #615e6e. Two buttons side by side: filled button (background #26114a, text #ffffff, 8px radius, 16px/24px padding, Inter 500 16px) and ghost button (border 1px #26114a, text #26114a, same dimensions). Below: a product screenshot framed in a white card with 8px radius and the deep blue-tinted shadow.
2. **Data Table Card**: White surface card, 8px radius, 1px #e6e2e3 border, 16px/24px padding. Table header row in Inter 500 at 12px, color #615e6e, uppercase, with 1px #e6e2e3 bottom border. Data rows alternate #ffffff and #f6f7fa backgrounds, Inter 400 at 14px, color #26114a. Action buttons in each row: small pill tags with 1440px radius, #edecff background, #3e0079 text, Inter 500 at 12px.
3. **Section Heading with Pill Tag**: Optional pill tag above heading: 1440px radius, #edecff background, #3e0079 text, Inter 500 at 12px, 4px/12px padding. Main heading in Britti Sans 500 at 40px, #26114a, line-height 1.0. Subtext in Inter 400 at 16px, #615e6e. All centered.
4. **Profile Preview Card**: White surface, 8px radius, 1px #e6e2e3 border, 12px/16px padding. 32px circular avatar at top-left. Name in Inter 500 at 14px, #26114a. Title in Inter 400 at 12px, #615e6e. Company in Inter 400 at 12px, #9491a1. Arranged in 3-column grid with 16px gaps, centered in section.
5. **Trust Logo Bar**: Single horizontal row of 6 client logos, all rendered in monochrome #333333, centered, each logo 4050px height. White background, 64px vertical padding above and below. Below: two rating badge pills (1440px radius, white bg, subtle border) with star icon and score in Inter 500 14px, platform name in Inter 400 12px #615e6e.
## Gradient System
The lavender atmosphere is built from three gradient layers:
- **Hero wash**: linear-gradient(90deg, #b99aff, #7d43ff 10%, transparent) — fades right, creating a directional glow
- **Decorative multi-stop**: linear-gradient(to right, #cf8aff, #ff66c1, #ffad74, #ff63d1, #aa81ff) — used sparingly for special highlights
- **Section tint**: subtle white-to-#edecff vertical fade for alternating content sections
Gradients should always sit behind white content surfaces — never behind text directly.
## Similar Brands
- **Apollo.io** — Same deep-violet brand color, similar data-prospecting product, and comparable lavender gradient hero treatment
- **Lusha** — Shared violet/indigo palette, compact data table UI, and light-mode product-first layout
- **Clearbit** — Similar light-mode SaaS with subtle gradient backgrounds and confident sans-serif display type
- **ZoomInfo** — Enterprise data product with dense tabular UI, compact Inter type, and restrained brand color usage
- **Cognism** — Purple-tinted brand identity with soft gradient hero sections and data-table product showcases
## Quick Start
### CSS Custom Properties
```css
:root {
/* Colors */
--color-deep-iris: #26114a;
--color-plum-velvet: #312749;
--color-royal-amethyst: #3e0079;
--color-mist-violet: #edecff;
--color-lavender-wash: #b99aff;
--gradient-lavender-wash: linear-gradient(90deg, rgb(185, 154, 255), rgb(125, 67, 255) 10%, rgba(125, 67, 255, 0));
--color-twilight-beam: #cf8aff;
--gradient-twilight-beam: linear-gradient(to right, rgb(207, 138, 255), rgb(255, 102, 193), rgb(255, 173, 116), rgb(170, 129, 255));
--color-canvas: #ffffff;
--color-paper: #f6f7fa;
--color-mist: #e6e2e3;
--color-smoke: #c1c7cf;
--color-ash: #9491a1;
--color-slate: #615e6e;
--color-charcoal: #333333;
--color-carbon: #222222;
--color-ink: #000000;
/* Typography — Font Families */
--font-britti-sans: 'Britti Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-inter: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Typography — Scale */
--text-caption: 12px;
--leading-caption: 1.3;
--text-body-sm: 14px;
--leading-body-sm: 1.4;
--text-body: 16px;
--leading-body: 1.5;
--text-subheading: 24px;
--leading-subheading: 1;
--text-heading-sm: 32px;
--leading-heading-sm: 1;
--text-heading: 40px;
--leading-heading: 1;
--text-heading-lg: 56px;
--leading-heading-lg: 1;
--text-display: 64px;
--leading-display: 1;
/* Typography — Weights */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
/* Spacing */
--spacing-unit: 8px;
--spacing-8: 8px;
--spacing-16: 16px;
--spacing-24: 24px;
--spacing-32: 32px;
--spacing-40: 40px;
--spacing-48: 48px;
--spacing-56: 56px;
--spacing-64: 64px;
--spacing-96: 96px;
--spacing-104: 104px;
--spacing-216: 216px;
/* Layout */
--page-max-width: 1200px;
--section-gap: 64-80px;
--card-padding: 16-24px;
--element-gap: 8px;
/* Border Radius */
--radius-sm: 2px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-3xl: 24px;
--radius-3xl-2: 28px;
--radius-3xl-3: 40px;
--radius-full: 56px;
--radius-full-2: 1440px;
/* Named Radii */
--radius-cards: 8px;
--radius-icons: 8px;
--radius-large: 24px;
--radius-pills: 1440px;
--radius-inputs: 8px;
--radius-buttons: 8px;
/* Shadows */
--shadow-sm: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px;
--shadow-lg: rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px;
--shadow-lg-2: rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
--shadow-sm-2: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px;
--shadow-sm-3: rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151, 0.04) 0px 6px 4px 0px, rgba(47, 1, 151, 0.06) 0px 3px 3px 0px, rgba(47, 1, 151, 0.08) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
/* Surfaces */
--surface-canvas: #ffffff;
--surface-paper: #f6f7fa;
--surface-mist-violet: #edecff;
--surface-lavender-glow: #b99aff;
}
```
### Tailwind v4
```css
@theme {
/* Colors */
--color-deep-iris: #26114a;
--color-plum-velvet: #312749;
--color-royal-amethyst: #3e0079;
--color-mist-violet: #edecff;
--color-lavender-wash: #b99aff;
--color-twilight-beam: #cf8aff;
--color-canvas: #ffffff;
--color-paper: #f6f7fa;
--color-mist: #e6e2e3;
--color-smoke: #c1c7cf;
--color-ash: #9491a1;
--color-slate: #615e6e;
--color-charcoal: #333333;
--color-carbon: #222222;
--color-ink: #000000;
/* Typography */
--font-britti-sans: 'Britti Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-inter: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
/* Typography — Scale */
--text-caption: 12px;
--leading-caption: 1.3;
--text-body-sm: 14px;
--leading-body-sm: 1.4;
--text-body: 16px;
--leading-body: 1.5;
--text-subheading: 24px;
--leading-subheading: 1;
--text-heading-sm: 32px;
--leading-heading-sm: 1;
--text-heading: 40px;
--leading-heading: 1;
--text-heading-lg: 56px;
--leading-heading-lg: 1;
--text-display: 64px;
--leading-display: 1;
/* Spacing */
--spacing-8: 8px;
--spacing-16: 16px;
--spacing-24: 24px;
--spacing-32: 32px;
--spacing-40: 40px;
--spacing-48: 48px;
--spacing-56: 56px;
--spacing-64: 64px;
--spacing-96: 96px;
--spacing-104: 104px;
--spacing-216: 216px;
/* Border Radius */
--radius-sm: 2px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-3xl: 24px;
--radius-3xl-2: 28px;
--radius-3xl-3: 40px;
--radius-full: 56px;
--radius-full-2: 1440px;
/* Shadows */
--shadow-sm: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px, rgba(18, 55, 105, 0.08) 0px 0px 0px 1px;
--shadow-lg: rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px, rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px, rgba(14, 59, 101, 0.04) 0px 1px 1px 0px;
--shadow-lg-2: rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px, rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
--shadow-sm-2: rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px;
--shadow-sm-3: rgba(47, 1, 151, 0.01) 0px 11px 4px 0px, rgba(47, 1, 151, 0.04) 0px 6px 4px 0px, rgba(47, 1, 151, 0.06) 0px 3px 3px 0px, rgba(47, 1, 151, 0.08) 0px 1px 1px 0px, rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
}
```
+120
View File
@@ -0,0 +1,120 @@
# 인터페이스 및 UI 제어 명세서 (frontend.md)
## 1. 글로벌 테마 변수 강제 (Theme Constraints)
* **임의 색상 지정 금지:** 하드코딩된 Hex 코드 직접 입력 금지.
* **CSS 변수 필수 참조:** 테마 전환(라이트/다크) 및 일관성을 위해 `ui_template_theme.css` 선언 변수(`var(--color-bg)`, `var(--color-text)` 등)만 사용.
* **신규 컴포넌트/템플릿:** 신규 컴포넌트나 템플릿이 필요한 경우`.agent\design.md` 파일을 참고하여 양식을 `ui_template\ui_template_elements.ts`, `ui_template\ui_template_locale.ts`, `ui_template\ui_template_theme.css` 양식화하여 사용할 것
## 2. UI 요소 및 레이아웃 제약 (Layout Constraints)
* **공통 컴포넌트 최우선:** 임의 스타일링 금지. `ui_template_elements.ts` 규격 및 공통 컴포넌트 최우선 상속/참조.
* **3단 기본 레이아웃 준수:** 워크플로우 페이지 기본 배치 강제 (예외 요청 제외)
1. 상단 영역: 페이지 타이틀 및 현재 진행 단계 상태
2. 좌측 패널: 데이터 입력 폼 및 설정 제어 영역 (너비 고정)
3. 우측 영역: WebCAD 도면 뷰어 또는 결과 데이터 그리드 (나머지 전체 너비)
### 2.1 공통 컴포넌트 목록 (`ui_template_elements.ts`)
* **기본:** `createButton`, `createInputField`, `createCard`, `createTag`, `createWorkflowShell`
* **오버레이/알림:** `showLoadingOverlay`, `hideLoadingOverlay`, `showToast`
* **스플라인 차트:** `createLineChart(opts)` — 외부 라이브러리 없이 인라인 SVG로 시계열 렌더링
- **곡선:** Catmull-Rom → 3차 베지어 변환으로 스플라인(부드러운 곡선) 렌더
- **색상:** `--color-chart-0~3` 테마 변수 사용 (하드코딩 금지)
- **범례:** 그래프 영역 우상단에 반투명 오버레이 배치 (`opts.series[].name`)
- **X축:** `opts.xLabels` 전달 시 양끝 포함 5개 라벨 균등 표기
- **결측:** `series[].values``null` 포함 시 해당 구간 선 끊김 처리
- 데이터 2점 미만이면 빈 상태(`—`) 반환
- 사용 예: B01_Dashboard 리소스 현황(CPU/메모리/디스크 30일 추이, X축=시간)
---
## 3. 인덱스 기반 다국어 제어 (i18n Constraints)
* **텍스트 하드코딩 금지:** 라벨, 버튼 문구, 안내 메시지 등 모든 UI 문자열의 컴포넌트 내 직접 입력 절대 금지.
* **🚨 동시 수정 프로토콜 (선언 후 구현):**
1. **선(先) 등록:** 신규 문구 필요 시, `ui_template_locale.ts` 최하단에 키와 언어 배열 `[한국어, 영어]` 우선 추가.
2. **후(後) 참조:** 컴포넌트 내 `ui_locales.키값[현재언어인덱스]` 형태로만 호출.
* *제약 조건:* UI 수정 요청 시 다국어 파일 업데이트 코드 미제시 경우 가이드 위반(에러) 처리.
---
## 4. JS/TS 코드 제어 및 상태 (Logic Constraints)
* **이벤트 핸들러 명명법:** `on[페이지명]_[기능명]_[액션]` 패턴 필수 준수. (예: `onB04_Surface_Calculate_Click`)
* **비동기 상태 제어:** API 호출 시 즉시 로딩 스피너(Overlay) 활성화, 통신 완료(성공/실패) 후 해제 로직 필수 포함.
* **1차 유효성 검사:** 백엔드 전송 전 빈 값 및 데이터 타입(숫자 범위 등) 프론트엔드 선 검증, 에러 시 입력창 하단 표기.
```typescript
export const ui_locales = {
A01_Home_Title: ["반갑습니다", "Welcome"],
B04_wf1_Surface_Btn: ["지표면 분석 실행", "Run Surface Analysis"]
};
```
---
## 5. 인증, 인가, 역할 및 라우팅 (Authentication, Authorization, Roles & Routing)
### 5.1 세션 쿠키 기반 인증
* **방식:** HttpOnly Secure 세션 쿠키 (localStorage 토큰 미사용)
* **API 통신:** 모든 API 요청에 `credentials: "include"` 설정으로 쿠키 자동 전송
* **로그아웃:** `/auth/logout` API 호출 → 서버 세션 삭제 → 쿠키 자동 제거
* **인증 갱신:** 로그인 시 `users.auth_expires_at = NOW() + 3개월` 자동 설정
- 유효한 인증: `auth_expires_at > NOW()` → 바로 대시보드 이동
- 만료된 인증: `auth_expires_at <= NOW()` → 재인증 모달 표시
### 5.1.1 역할 기반 대시보드 렌더링
* **USER 역할:**
- 프로젝트 리스트 섹션
- 회사 정보 섹션 (회사 생성/연결 팝업)
- 기본정보 & 보안 섹션
* **ADMIN 역할:**
- 프로젝트 리스트 섹션 (회사 전체 프로젝트)
- 회사 정보 섹션
- 팀원 관리 섹션
- 가입 요청 섹션
- 기본정보 & 보안 섹션
* **SYSTEM_ADMIN 역할:**
- 리소스 현황 섹션 (CPU, MEM, DISK, 활성 사용자/프로젝트)
- 회사 관리 섹션 (전체 회사 리스트 + 추가/수정/삭제)
- 사용자 관리 섹션 (전체 사용자 + 역할 변경 + 회사 할당)
- 가입 요청 관리 섹션 (전체 가입 신청 + 승인/거절)
- 시스템 로그 섹션 (필터링 지원)
- 리소스 모니터링 그래프 (시계열 데이터)
- 기본정보 & 보안 섹션
### 5.2 라우팅 가드
* **인증 확인:** `isAuthenticated()` 함수로 세션 유효성 검사 (PROTECTED_ROUTES 진입 전)
* **회사 연결 확인:** B02~B11 워크플로우 진입 시 `fetchDashboardMe()` 호출 → `company_id` + `status` 확인
- `status` 확인:
- NO_COMPANY: B01_Dashboard로 강제 리다이렉트 (회사 연결 필요)
- PENDING: B01_Dashboard로 강제 리다이렉트 (승인 대기 중)
- ACTIVE: 워크플로우 진입 허용
- `company_id` 없으면 B01_Dashboard로 리다이렉트
* **역할 기반 라우팅:**
- SYSTEM_ADMIN: 모든 페이지 접근 가능
- ADMIN: B01_Dashboard (관리 섹션) + B02~B11 워크플로우 접근 가능
- USER: B01_Dashboard (사용자 섹션) + B02~B11 워크플로우 접근 가능
* **상태 전이:**
- 회원가입 후 이메일 인증 완료: `users.status = 'NO_COMPANY'` (로그인 가능, 회사 미연결)
- 회사 생성: `users.status = 'ACTIVE'`, `company_id` 세팅, `role = 'ADMIN'`, `is_master = TRUE`
- 회사 참여 신청: `users.status = 'PENDING'` (관리자 승인 대기)
- 관리자 승인: `users.status = 'ACTIVE'`, `company_id` 세팅
### 5.3 대시보드 권한 기반 UI 제어 (B01_Dashboard)
* **권한 검증 헬퍼 필수:** `.agent/plan_dashboard_management.md` 섹션 9의 헬퍼 함수 참고
- `canEditProject(user, project)` - 프로젝트 수정 권한
- `canDeleteProject(user)` - 프로젝트 삭제 권한 (SYSTEM_ADMIN만)
- `canAddUser(user)` - 사용자 추가 권한
- `canChangeRole(user, targetUser)` - 역할 변경 권한 (ADMIN: USER↔ADMIN만, SYSTEM_ADMIN 제외)
- `canDeleteUser(user, targetUser)` - 사용자 삭제 권한 (마지막 ADMIN 보호)
- `canManageAutomation(user, automation)` - 자동화 로직 관리 권한
- `isLastAdmin(company, excludeUserId)` - 회사의 마지막 ADMIN 확인
* **UI 컴포넌트 구현:** 권한에 따라 버튼/필드 표시/비활성화
- 프로젝트: 수정 모달, 삭제 확인 모달 (삭제는 SYSTEM_ADMIN만)
- 사용자: 추가 모달 (가입 계정 목록), 수정 모달, 역할변경 모달, 삭제 확인 모달
- 자동화 로직: 생성/수정/삭제 모달 (ADMIN/SYSTEM_ADMIN만)
- 모든 삭제 모달에 경고 메시지 포함 (되돌릴 수 없음)
* **다국어 반영:** 계획서 8장의 모든 키를 `ui_template_locale.ts`에 추가 후 컴포넌트에서 `L(키)` 사용
- 모달 제목, 버튼 레이블, 확인 메시지, 오류 메시지 모두 다국어 처리
- 하드코딩된 문구 절대 금지
+278
View File
@@ -0,0 +1,278 @@
# B03~B06 기능 마이그레이션 계획서
**갱신일:** 2026-07-05
**상태:** 백엔드 마이그레이션 완료 — Stage 0~4 (B03~B06) 백엔드 이전 완료. 남은 항목은 프론트엔드(Vanilla TS) 재작성과 실제 DB 연동 검증.
**범위:** `B03_FileInput` ~ `B06_wf3_ProfileCross`
**DB:** MariaDB v10.6+ (aiomysql 드라이버, Raw SQL)
**진척 요약:**
- Stage 0 (공통 기반): ✅ 완료
- Stage 1 B03 (파일 입력): ✅ 백엔드 완료
- Stage 2 B04 (지표면 분석): ✅ 백엔드 완료 (필터 4종 + 모델 5종 + 스무딩 + 등고선 + 파이프라인)
- Stage 3 B05 (경로 설계): ✅ 백엔드 완료 (Dijkstra + ridge-valley + skeleton)
- Stage 4 B06 (종횡단 생성): ✅ 백엔드 완료 (sampler + 종횡단 + Repository/Router)
- 공통: 전 페이지 DB 접근을 aiomysql Raw SQL로 통일, 순수 계산부는 실데이터로 검증
- Stage 2~4 프론트엔드 폼/결과 UI: ✅ 완료 (B04~B06 UI_Page + Api_Fetch + CSS + locale, tsc 타입체크 통과)
- 남은 작업: B04~B06 **WebCAD 3D/차트 뷰어**, 실제 aislo_db 연동·구형 수치 비교
---
## 1. 확정 기준
- 기능 동작의 기준 원본은 `0_old/main.py``0_old/utils/`이다.
- `0_old/backend/app/`은 이전 프로토타입 참고 자료이며 최종 동작 기준으로 사용하지 않는다.
- 신규 배치 구조는 `.agent/structure.md`를 따른다. 구형 파일 구조는 승계하지 않는다.
- 백엔드는 `.agent/backend.md`, 프론트엔드는 `.agent/frontend.md`를 따른다.
- 저장 구조 및 DB 경로 열은 `.agent/db_schema_simple.md`의 단계별 파일시스템 구조를 따른다.
- DB 스키마 작성·수정은 별도 담당 작업이다. 이 마이그레이션은 확정된 DB 계약만 `aiomysql`와 Raw SQL로 사용한다.
- 루트 `main.py`에는 페이지 라우터 등록만 추가한다.
- 구형 React/TSX UI는 복사하지 않고 현재 Vanilla TypeScript 구조와 공통 UI 템플릿에 맞게 재작성한다.
---
## 2. 작업 원칙
### 2.1 함수 하나씩 이전
각 작업 단위는 다음 순서를 지킨다.
1. 대상 구형 함수와 직접 의존성 확인
2. 신규 페이지 폴더에 책임에 맞는 파일 하나 생성
3. 함수 하나만 이전·수정
4. 해당 함수 단위 테스트 또는 최소 실행 검증
5. 포맷터·린터 실행
6. 검증 완료 후 다음 함수 진행
한 번에 구형 모듈 전체를 복사하지 않는다. 구형 전역 경로, 동기식 파일 처리, 하드코딩 설정은 그대로 가져오지 않는다.
### 2.2 파일 분리
- `*_Router.py`: FastAPI 엔드포인트와 응답 변환
- `*_Schema.py`: Pydantic 요청·응답 검증
- `*_Engine_*.py`: 페이지 고유 연산
- `*_Repository.py`: `asyncpg` Raw SQL과 DB 경로 메타데이터 처리
- `common_util/`: 둘 이상의 페이지가 실제로 공유하는 경로·원자적 파일 쓰기·기하 유틸만 배치
- 단일 파일이 700줄에 도달하기 전에 기능별 파일 분할을 제안하고 승인 후 `structure.md`를 갱신한다.
### 2.3 저장소 경계
```text
storage/{company_slug}/{user_slug}/{project_id}/
├── B03_FileInput/
├── B04_wf1_Surface/
├── B05_wf2_Route/
└── B06_wf3_ProfileCross/
```
각 페이지는 자기 폴더만 직접 생성·수정한다. 상위 단계 변경 시 하위 결과를 임의 삭제하지 않고 `stale_from`을 갱신한다.
---
## 3. 구형 기능 분석 결과
### 3.1 B03_FileInput
구형 원본:
- `0_old/main.py`: `check_sample`, `upload_files`
- 보조 참고: `0_old/backend/app/analyzer.py`의 파일 분석 함수
책임:
- LAS/TIF/TFW/PRJ/DXF 업로드 검증
- `B03_FileInput/input/{file_type}/` 저장
- 파일 메타데이터 추출 및 `input_files` 기록
- 업로드 완료 상태 반환
B04로 넘길 기능:
- `process_pipeline`
- `run_ground_analysis`
- 포인트 필터링 및 지표면 모델 생성
### 3.2 B04_wf1_Surface
구형 원본:
- `0_old/main.py`: `process_pipeline`, `ensure_terrain_models`, 지표면·포인트 API
- `0_old/utils/structurizer.py`
- `filter_grid_min_z.py`, `filter_csf.py`, `filter_pmf.py`, `filter_ransac.py`
- `terrain_model_converter.py`, `surface_smoother.py`, `contour_extractor.py`
책임:
- LAS 구조화와 지면 필터 생성
- 변환 포인트클라우드 저장
- TIN/DTM/NURBS/implicit/meshfree 모델 생성
- 스무딩, 미리보기, 등고선 생성
- 지표면 모델 선택 확정 및 하위 단계 stale 전파
### 3.3 B05_wf2_Route
구형 원본:
- `0_old/main.py`: 경로점 CRUD, solve/result/confirm, stale 서명 처리
- `0_old/utils/route_solver.py`
- `0_old/utils/route_solver_ridgevalley.py`
- `0_old/utils/terrain_skeleton.py`
책임:
- BP/EP/CP/AP/FP 입력 검증과 저장
- Dijkstra 및 ridge-valley 경로 계산
- 경사·곡선반경·회피/금지 구역 검증
- 경로 결과 조회·확정 및 B06 stale 전파
### 3.4 B06_wf3_ProfileCross
구형 원본:
- `0_old/main.py`: sections generate/get/confirm 및 입력 서명 처리
- `0_old/utils/surface_elevation_sampler.py`
- `0_old/utils/section_generator.py`
책임:
- 확정 경로와 확정 지표면 모델 검증
- 종단 표고 프로필 생성
- 지정 측점 간격의 횡단면 생성
- 결과 조회·확정 및 재현성 서명 관리
---
## 4. 함수 단위 실행 순서
### Stage 0 — 공통 기반
- [x] `config_system.py`의 저장 경로에서 불필요한 `projects` 경로 제거
- [x] 프로젝트 루트와 B03~B06 단계별 경로를 안전하게 만드는 공통 경로 함수 추가
- [x] JSON 원자적 쓰기 함수 이전
- [x] `workflow.json` 읽기 및 stale 필드 원자적 갱신 함수 이전
- [x] 공통 함수별 테스트 작성
### Stage 1 — B03_FileInput
- [x] `B03_FileInput_Schema.py`: 파일 종류·크기·확장자 검증 모델
- [x] `B03_FileInput_Engine.py`: 안전한 파일명과 목적 경로 결정 함수
- [x] `B03_FileInput_Engine.py`: 업로드 스트림 저장 함수
- [x] `B03_FileInput_Engine_Analyze.py`: LAS 메타데이터 분석 함수
- [x] `B03_FileInput_Engine_Analyze.py`: PRJ/TIF/TFW 메타데이터 분석 함수
- [x] `B03_FileInput_Repository.py`: `input_files` 저장 함수
- [x] `B03_FileInput_Router.py`: 업로드 엔드포인트
- [x] B03 프론트 드롭존·목록·검증·로딩 UI를 공통 컴포넌트 기반으로 작성
- [x] 업로드 통합 테스트
### Stage 2 — B04_wf1_Surface
- [x] LAS 구조화 함수 이전
- [x] grid-min-z 필터 함수 이전 및 검증
- [x] CSF 필터 함수 이전 및 검증
- [x] PMF 필터 함수 이전 및 검증
- [x] RANSAC 필터 함수 이전 및 검증
- [x] 지표면 모델 공통 컨텍스트 생성 함수 이전 (ModelContext + 메시 유틸)
- [x] TIN 생성 함수 이전
- [x] DTM 생성 함수 이전
- [x] NURBS 생성 함수 이전
- [x] implicit 생성 함수 이전
- [x] meshfree 생성 함수 이전
- [x] DTM/TIN 스무딩 함수 이전
- [x] 등고선 생성 함수 이전
- [x] 미리보기·메타데이터·모델 확정 라우트 추가 (analyze/models 라우트 + 파이프라인 manifest)
- [x] `processed_point_cloud`, `surface_models`, `terrain_layers` Raw SQL 저장 함수 추가 (aiomysql)
- [x] B04 폼·모델 목록 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
- [ ] B04 WebCAD 3D 뷰어(포인트클라우드/메시 렌더) 추가
- [ ] 샘플 LAS 결과를 구형 출력과 비교 검증
### Stage 3 — B05_wf2_Route
- [x] 경로점 Pydantic 모델 및 유효성 검사 이전 (Schema)
- [x] 경로점 저장·조회·초기화 함수 이전 (Repository)
- [x] 비용면 생성 및 캐시 함수 이전 (Solver)
- [x] Dijkstra 단일 구간 탐색 함수 이전 (Geometry)
- [x] 최적 경로 조립 함수 이전 (Solver solve_optimal_route)
- [x] 지형 skeleton 생성 함수 이전 (Skeleton, D8 흐름누적)
- [x] ridge-valley 탐색 함수 이전 (RidgeValley, 정속경사+fillet)
- [ ] 입력 서명·stale 판정 함수 이전
- [x] 경로 확정 함수 이전 (Repository confirm_route + confirm 라우트)
- [x] `routes`, `route_points`, `route_statistics` Raw SQL 저장 함수 추가 (aiomysql)
- [x] B05 경로점 입력·제약조건·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
- [ ] B05 경로 WebCAD 뷰어(노선 폴리라인 렌더/편집) 추가
- [x] 기존 route solver 테스트를 신규 구조로 이관·통과
### Stage 4 — B06_wf3_ProfileCross
- [x] 지표면 sampler 인터페이스 이전 (Engine_Sampler: SurfaceElevationSampler Protocol)
- [x] DTM grid sampler 함수 이전 (DtmGridSampler, 4-꼭짓점 valid 검증)
- [x] 보간 sampler 함수 이전 (InterpolatedSurfaceSampler + build_surface_sampler 5종)
- [x] 측점 배열 생성 함수 이전 (Engine_Section: _chainages)
- [x] 경로 보간 및 접선 계산 함수 이전 (_interpolate_xy, _tangent_at)
- [x] 종단·횡단 생성 함수 이전 (generate_sections)
- [ ] 입력 서명·중복 실행 방지 함수 이전 (delete_sections_for_route로 멱등 재실행만 구현)
- [x] 결과 조회·확정 라우트 추가 (sections/generate·get·confirm 라우트)
- [x] `longitudinal_sections`, `cross_sections` Raw SQL 저장 함수 추가 (aiomysql Repository)
- [x] B06 측점·횡단 옵션·결과 메트릭 UI를 Vanilla TypeScript로 재작성 (UI_Page + Api_Fetch + CSS + locale)
- [ ] B06 종단도·횡단 카드 WebCAD 뷰어(프로필 차트 렌더) 추가
- [ ] 구형 section 결과와 수치 비교 검증
**B06 백엔드 구현 파일:**
- `B06_wf3_ProfileCross_Engine_Sampler.py` — 표고 sampler (Protocol + DTM/보간 5종)
- `B06_wf3_ProfileCross_Engine_Section.py` — 종횡단 생성 (측점/접선/횡단 샘플)
- `B06_wf3_ProfileCross_Engine.py` — 오케스트레이터 (경로 GeoJSON→종횡단→파일 저장)
- `B06_wf3_ProfileCross_Repository.py` — aiomysql Raw SQL (2 테이블)
- `B06_wf3_ProfileCross_Schema.py` / `_Router.py` — Pydantic + FastAPI 라우트
**검증 상태:** B04(DTM)→B05(경로 70.71m)→B06(종단면+횡단면 5건) end-to-end 파일 생성 확인.
DB 저장 함수는 FakeCursor 기반 문법 검증만 수행 (실제 aislo_db 연동은 나스 서버 준비 후).
---
## 5. API 및 상태 원칙
- API 경로는 `/api/projects/{project_id}/...` 형태를 유지하되 페이지별 Router에서 선언한다.
- 모든 JSON 요청은 Pydantic 검증 후 Engine으로 전달한다.
- 긴 지형 연산은 이벤트 루프를 막지 않도록 실행 방식을 별도 검토한다.
- 오류 응답은 `{"status": "error", "message": "원인"}` 형식을 유지한다.
- 프론트 API 호출은 로딩 Overlay를 항상 시작·해제한다.
- UI 문자열은 `ui_template_locale.ts`에 먼저 등록한 뒤 참조한다.
- 상위 단계 재계산 시 하위 결과는 stale 처리하며 다른 페이지 폴더를 직접 삭제하지 않는다.
---
## 6. 검증 체크리스트
각 함수 이전 후:
- [ ] 구형 함수의 입력·출력 계약 비교
- [ ] 하드코딩 설정 제거 및 `config_system.py` 연결
- [ ] 신규 단계별 저장 경로 사용 확인
- [ ] 경로 이탈 및 파일명 공격 방지 확인
- [ ] 예외 처리와 표준 오류 응답 확인
- [ ] Python: `ruff format`, `ruff check --fix`
- [ ] TypeScript/CSS: `npx prettier --write`
- [ ] 단위 테스트 실행
- [ ] 관련 통합 테스트 실행
- [ ] 단일 파일 700줄 미만 확인
단계 완료 후:
- [ ] B03 업로드 파일과 DB 메타데이터 일치
- [ ] B04 지표면 모델의 범위·점 수·표고 통계 비교
- [ ] B05 경로 길이·경사·곡선반경·제약 위반 비교
- [ ] B06 측점 수·종단 표고·횡단 좌표 비교
- [ ] 다중 브라우저 polling 및 stale 전파 확인
---
## 7. 작업 경계와 보류 항목
- DB 테이블·FK·인덱스 정의 변경은 본 작업에서 수행하지 않는다.
- `.agent/db_schema_simple.md`는 DB 담당자의 작업 영역이므로 직접 수정하지 않는다.
- DB 스키마가 확정되지 않은 Repository 함수는 인터페이스만 계획하고 임의 열을 만들지 않는다.
- B07 이후 기능은 이번 마이그레이션 범위에서 제외한다.
- 구형 샘플 데이터는 검증 입력으로만 사용하고 신규 영구저장소로 자동 복사하지 않는다.
---
## 8. 다음 실행 단위
첫 구현은 Stage 0의 저장 경로 함수 하나로 시작한다. 함수와 테스트를 검증한 뒤 다음 공통 함수로 이동한다.
+189
View File
@@ -0,0 +1,189 @@
# 프로젝트 구조 명세서 (structure.md)
## 1. 폴더 및 파일 명명 규칙 (Naming Convention)
- **기본 원칙:** 프론트엔드(TypeScript)와 백엔드(Python) 파일을 수직 통합 관리
- **페이지 폴더 명명:** 로그인 전은 `A01_`, 로그인 후는 `B01_` 형태 적용, 워크플로우는 `wf[번호]` 접두사를 사용
- **시스템 폴더 명명:** 공통 유틸, DB, 리소스, 영구저장소 등 시스템 및 데이터 인프라 폴더는 접두사 ID 없이 명확한 영문명 분리
- **파일 명명:** 반드시 `폴더명(페이지명)_기능_기타사양.확장자` 형태 작성
- **공통 모듈:** 공통 유틸 코드는 `common_util` 폴더 내에 `common_util_기능.확장자` 형태 보관
---
## 2. 전체 디렉토리 트리 구조
```text
my-project/
├── .agent/
│ ├── agent.md
│ └── structure.md # 본 파일
├── A00_Common/ # 프론트엔드 공통 인프라 (부트스트랩, 라우팅, 셸)
│ ├── main.ts # 앱 진입점 (스타일 주입 → 테마 복원 → 셸 → 라우터)
│ ├── router.ts # 해시 기반 SPA 라우터 + 인증 가드 + 지연 로딩 (A·B 전 라우트 등록)
│ ├── app_shell.ts # 공통 헤더/푸터 (로고, 네비, 언어·테마·로그인 토글)
│ ├── b_page_scaffold.ts # B 그룹 공통 스캐폴드 (헤더+준비중 안내 / 워크플로우 셸 래퍼)
│ └── vite-env.d.ts # Vite 클라이언트 타입 선언
├── common_util/ # 공통 유틸리티 (공통 기능 코드)
│ ├── common_util_validate.ts # 프론트 1차 유효성 검사 (이메일/빈값/최소길이)
│ ├── common_util_auth.py # 비밀번호·OTP·세션 쿠키·권한 검증
│ ├── common_util_auth_repository.py # 인증·조직·관리 Raw SQL 저장소
│ ├── common_util_email.py # 비동기 SMTP 발송 및 재시도
│ ├── common_util_email_templates.py # 인증·조직 알림 이메일 템플릿
│ ├── common_util_string.ts
│ ├── common_util_storage.py # 프로젝트 및 워크플로우 단계별 저장 경로
│ ├── common_util_json.py # JSON 원자적 저장
│ ├── common_util_workflow.py # 공유 workflow.json 상태 처리
│ ├── common_util_resource_monitor.py # 시스템 리소스 계측(2분 주기) + log/ 로그 + DB 기록
│ └── common_util_format.py
├── db_management/ # DB 관리 폴더 (PostgreSQL 마이그레이션 및 스키마 관리)
│ ├── schema.sql
│ ├── migration_v1.py
│ └── 002_auth_security.sql # 기존 DB 로그인·보안 증분 마이그레이션
├── resources/ # 웹앱 UI 구성 리소스 (사진, 회사 로고 등 에셋)
│ ├── logo.png
│ ├── banner.jpg
│ └── legal/ # 약관 및 정책 텍스트 (회원가입 UI에서 사용)
│ ├── terms_of_service.txt # 이용약관
│ ├── privacy_policy.txt # 개인정보처리방침
│ └── marketing_consent.txt # 마케팅 정보 수신 동의
├── storage/ # 영구 저장소 (데이터 커서 및 대용량 파일 별도 저장 로직용)
│ └── [고객사명]/
│ └── [사용자명]/
│ └── [프로젝트ID]/ # 프로젝트별 내부 폴더/파일 자동 생성 (별도 폴더 ID 미사용)
├── log/ # 런타임 리소스 로그 (.gitignore, 1개월 롤링)
│ └── system_resources.log # CPU/메모리/디스크 계측 단일 파일 (2분 주기)
├── ui_template/ # 공통 UI 템플릿 및 테마
│ ├── ui_template_theme.css # 글로벌 디자인 테마 및 CSS 변수 (라이트/다크)
│ ├── ui_template_locale.ts # 다국어 텍스트 배열 방식 관리 파일
│ └── ui_template_elements.ts # 공통 컴포넌트 및 그룹 템플릿 정의
├── templates/ # 보고서 출력용 마스터 양식 (DWG, Excel, PPT 등 기본 원본 템플릿 파일)
│ ├── template_report_form.xlsx
│ └── template_drawing_base.dwg
├── config/ # 프로그램 내부 제어 및 시스템 환경 설정 폴더
│ ├── config_system.py # 백엔드 작동 제어 및 알고리즘 파라미터
│ ├── config_db.py # 데이터베이스 연결 제어 설정
│ ├── config_frontend.ts # 프론트엔드 제어 및 웹 환경 설정
│ ├── package.json # Node.js 의존성 (npm/vite)
│ ├── tsconfig.json # TypeScript 설정
│ └── vite.config.ts # Vite 빌드 설정
├── A01_Home/ # 로그인 전 01: 홈 (소개, 소식, 사이트맵 등)
│ ├── A01_Home_UI_Page.ts
│ ├── A01_Home_UI_Style.css
│ └── A01_Home_Api_Fetch.py
├── A02_ProgDetail/ # 로그인 전 02: 프로그램 상세 (6단계 워크플로우 + 핵심 기술)
│ ├── A02_ProgDetail_UI_Page.ts
│ └── A02_ProgDetail_UI_Style.css
├── A03_CompDetail/ # 로그인 전 03: 회사 상세 (미션 + 핵심 가치 + 연락처)
│ ├── A03_CompDetail_UI_Page.ts
│ └── A03_CompDetail_UI_Style.css
├── A04_NewsHistory/ # 로그인 전 04: 최신소식 및 개선 이력 (타임라인)
│ ├── A04_NewsHistory_UI_Page.ts
│ └── A04_NewsHistory_UI_Style.css
├── A05_EduDetail/ # 로그인 전 05: 교육 상세 (과정 소개 + 문의 CTA)
│ ├── A05_EduDetail_UI_Page.ts
│ └── A05_EduDetail_UI_Style.css
├── A06_Login/ # 로그인 전 06: 로그인 (이메일 + 비밀번호 폼)
│ ├── A06_Login_UI_Auth_Page.ts # 세션 쿠키·OTP 로그인 UI (현재 사용 중)
│ ├── A06_Login_UI_Style.css
│ ├── A06_Login_Api_Fetch.ts
│ ├── A06_Login_Schema.py
│ └── A06_Login_Router.py
├── A07_Register/ # 로그인 전 07: 회원가입 (회사/이름/이메일/비밀번호 폼)
│ ├── A07_Register_UI_Page.ts
│ ├── A07_Register_UI_Auth_Page.ts # 조직 유형·약관·OTP 회원가입 UI
│ ├── A07_Register_UI_Style.css
│ ├── A07_Register_Api_Fetch.ts
│ ├── A07_Register_Schema.py
│ └── A07_Register_Router.py
├── A08_Support/ # 로그인 전 08: 기술지원 요청 (문의 폼 + textarea)
│ ├── A08_Support_UI_Page.ts
│ └── A08_Support_UI_Style.css
├── A09_Security/ # 로그인 전 09: 보안 및 약관 동의 (필수/선택 약관 확인)
│ ├── A09_Security_UI_Page.ts # 약관 동의 페이지 (이용약관/개인정보/마케팅/보안정책)
│ ├── A09_Security_UI_Style.css # A09 고유 약관 박스/체크박스 스타일
│ ├── A09_Security_Api_Fetch.ts # 동의 내용 제출 API 클라이언트
│ ├── A09_Security_Terms.ts # 약관 텍스트 데이터 (한/영 다국어)
│ ├── A09_Security_Schema.py # 조직·관리자 보안 요청 검증
│ └── A09_Security_Router.py # 마스터·시스템 관리자·활동 로그 API
│ # ※ B01·B02 본문 완성 / B03~B11 헤더·셸만 (본문은 0_old 참고 후 구체화)
├── B01_Dashboard/ # 로그인 후 01: 대시보드 (역할별 정보 표시)
│ ├── B01_Dashboard_UI_Page.ts # 역할별 섹션 조건부 렌더링
│ ├── B01_Dashboard_UI_Modals.ts # 프로젝트, 사용자, 자동화 제어 모달 모음
│ ├── B01_Dashboard_UI_Helper.ts # 역할별 접근 권한 확인 헬퍼
│ ├── B01_Dashboard_UI_Resources.ts # CPU/Memory/Disk 리소스 렌더링 컴포넌트
│ ├── B01_Dashboard_UI_Style.css # 프로젝트 테이블, 워크플로우, 팝업 스타일
│ ├── B01_Dashboard_Api_Fetch.ts # dashboard API 클라이언트
│ ├── B01_Dashboard_Schema.py # 요청/응답 Pydantic 검증
│ ├── B01_Dashboard_Repository.py # Raw SQL 저장소 (사용자, 회사, 팀원, 가입 신청)
│ └── B01_Dashboard_Router.py # 역할별 API 엔드포인트
├── B02_ProjRegister/ # 로그인 후 02: 프로젝트 등록 (기본정보 입력 폼)
│ ├── B02_ProjRegister_UI_Page.ts
│ └── B02_ProjRegister_UI_Style.css
├── B03_FileInput/ # 로그인 후 03: 파일 입력 (헤더+준비중 안내)
│ ├── B03_FileInput_UI_Page.ts
│ ├── B03_FileInput_UI_Style.css
│ ├── B03_FileInput_Api_Fetch.ts # 다중 파일 업로드 API 클라이언트
│ ├── B03_FileInput_Schema.py # 업로드 파일 Pydantic 검증
│ ├── B03_FileInput_Engine.py # 업로드 저장 경로·스트림 처리
│ ├── B03_FileInput_Engine_Analyze.py # 원본 파일 메타데이터 분석
│ ├── B03_FileInput_Repository.py # input_files asyncpg Raw SQL
│ └── B03_FileInput_Router.py # 다중 파일 업로드 API
├── B04_wf1_Surface/ # 로그인 후 04: 1차 workflow (지표면 모델 분석)
│ ├── B04_wf1_Surface_UI_Page.ts # 3단 레이아웃 (필터/표현 선택 폼 + 모델 목록)
│ ├── B04_wf1_Surface_UI_Style.css # B04 고유 폼 그룹/모델 카드 스타일
│ ├── B04_wf1_Surface_Api_Fetch.ts # surface/analyze·models API 클라이언트
│ ├── B04_wf1_Surface_Schema.py # 분석 요청/응답 Pydantic 검증
│ ├── B04_wf1_Surface_Engine.py # 지표면 분석 오케스트레이터
│ ├── B04_wf1_Surface_Engine_Structurize.py # LAS/LAZ 구조화
│ ├── B04_wf1_Surface_Engine_Ground.py # 지면 분석 진입
│ ├── B04_wf1_Surface_Engine_Filter_Grid.py # grid minimum-Z 필터
│ ├── B04_wf1_Surface_Engine_Filter_CSF.py # CSF 필터
│ ├── B04_wf1_Surface_Engine_Filter_PMF.py # PMF 필터
│ ├── B04_wf1_Surface_Engine_Filter_RANSAC.py # RANSAC 필터
│ ├── B04_wf1_Surface_Engine_ModelContext.py # 모델 공통 컨텍스트/메시 유틸
│ ├── B04_wf1_Surface_Engine_ModelBuild.py # TIN/DTM/NURBS/implicit/meshfree 생성
│ ├── B04_wf1_Surface_Engine_Smooth.py # DTM/TIN 스무딩
│ ├── B04_wf1_Surface_Engine_Contour.py # 등고선 생성
│ ├── B04_wf1_Surface_Engine_Pipeline.py # 파이프라인 manifest
│ ├── B04_wf1_Surface_Repository.py # processed_point_cloud/surface_models aiomysql Raw SQL
│ └── B04_wf1_Surface_Router.py # surface/analyze·models API
├── B05_wf2_Route/ # 로그인 후 05: 2차 workflow (경로설계)
│ ├── B05_wf2_Route_UI_Page.ts # 3단 레이아웃 (제어점/제약 폼 + 결과 메트릭)
│ ├── B05_wf2_Route_UI_Style.css # B05 고유 제어점 행/제약 그룹 스타일
│ ├── B05_wf2_Route_Api_Fetch.ts # route/solve·confirm API 클라이언트
│ ├── B05_wf2_Route_Schema.py # 경로 탐색 요청/응답 Pydantic 검증
│ ├── B05_wf2_Route_Engine.py # 경로 설계 오케스트레이터
│ ├── B05_wf2_Route_Engine_Geometry.py # Dijkstra 단일 구간 탐색
│ ├── B05_wf2_Route_Engine_Solver.py # 비용면 생성 + 최적 경로 조립
│ ├── B05_wf2_Route_Engine_Skeleton.py # 지형 skeleton (D8 흐름누적)
│ ├── B05_wf2_Route_Engine_RidgeValley.py # ridge-valley 탐색
│ ├── B05_wf2_Route_Repository.py # routes/route_points/route_statistics aiomysql Raw SQL
│ └── B05_wf2_Route_Router.py # route/solve·confirm API
├── B06_wf3_ProfileCross/ # 로그인 후 06: 3차 workflow (종횡단 생성)
│ ├── B06_wf3_ProfileCross_UI_Page.ts # 3단 레이아웃 (측점/횡단 옵션 폼 + 결과 메트릭)
│ ├── B06_wf3_ProfileCross_UI_Style.css # B06 고유 옵션 그룹/결과 메트릭 스타일
│ ├── B06_wf3_ProfileCross_Api_Fetch.ts # sections/generate·get·confirm API 클라이언트
│ ├── B06_wf3_ProfileCross_Schema.py # 종횡단 생성 요청/응답 Pydantic 검증
│ ├── B06_wf3_ProfileCross_Engine.py # 종횡단 생성 오케스트레이터
│ ├── B06_wf3_ProfileCross_Engine_Sampler.py # 표고 sampler (Protocol + DTM/보간 5종)
│ ├── B06_wf3_ProfileCross_Engine_Section.py # 종횡단 생성 (측점/접선/횡단 샘플)
│ ├── B06_wf3_ProfileCross_Repository.py # longitudinal/cross_sections aiomysql Raw SQL
│ └── B06_wf3_ProfileCross_Router.py # sections/generate·get·confirm API
├── B07_wf4_DesignDetail/ # 로그인 후 07: 4차 workflow (상세설계) — 워크플로우 셸
│ └── B07_wf4_DesignDetail_UI_Page.ts
├── B08_wf5_Quantity/ # 로그인 후 08: 5차 workflow (수량 산출) — 워크플로우 셸
│ └── B08_wf5_Quantity_UI_Page.ts
├── B09_wf6_Estimation/ # 로그인 후 09: 6차 workflow (견적/문서) — 워크플로우 셸
│ └── B09_wf6_Estimation_UI_Page.ts
├── B10_Payment/ # 로그인 후 10: 결재 페이지 (헤더+준비중 안내)
│ └── B10_Payment_UI_Page.ts
└── B11_Status/ # 로그인 후 11: 상태 출력 페이지 (헤더+준비중 안내)
└── B11_Status_UI_Page.ts
├── main.py # FastAPI 애플리케이션 진입점
├── requirements.txt # Python 의존성 (pip)
├── .env.example # 환경 변수 템플릿
└── node_modules/ # Node.js 패키지 (.gitignore)
└── .build/ # Vite 빌드 결과 (.gitignore)
```
@@ -0,0 +1,207 @@
# 워크플로우 단계별 상태 관리 재설계 계획서
**작성일:** 2026-07-10
**상태:** 설계 제안 (코드는 다른 AI가 진행)
**범위:** 프로젝트 워크플로우(WF0 파일입력 ~ WF6 견적) 단계별 완료 상태의 DB 저장·조회·무효화(stale) 로직, 그리고 이를 근거로 한 대시보드/파일입력/WF1~WF6 공통 UI의 단계 활성화·페이지 이동 제어
---
## 1. 현재 구조 진단 (코드 조사 결과)
### 1.1 상태값이 단일 문자열 하나로 관리됨
- `projects.status`**VARCHAR(50) 단일 컬럼** ([001_create_schema.sql:187](../db_management/001_create_schema.sql#L187))
- 값 예: `NEW``FILE_UPLOADED``WF1_ANALYZING``WF1_COMPLETE``WF2_COMPLETE` → ... → `DONE`
- **하나의 값만 존재** — "지금 어느 단계까지 왔는가"만 표현, "각 단계가 개별적으로 완료됐는가"는 표현 불가
- 이 단일 값을 대시보드가 `_stage_from_status()`**순서 추론** ([B01_Dashboard_Repository.py:20](../B01_Dashboard/B01_Dashboard_Repository.py#L20))
```python
order = [("FILE_UPLOADED", 1), ("WF1_COMPLETE", 2), ("WF2_COMPLETE", 3), ...]
# status 문자열에 토큰이 "포함"되면 그 stage로 간주 → 부분 문자열 매칭
```
### 1.2 이로 인한 실제 문제 (사용자 보고와 일치)
1. **단계 독립성 없음**: `WF2_COMPLETE`가 되면 WF1 완료 정보는 문자열에 없고 "순서상 앞이니까 됐겠지"로만 추론. WF1을 다시 열어 재분석해도 그 사실을 status가 담지 못함.
2. **역방향 재작업(stale) 로직 부재**: 사용자가 WF1으로 돌아가 파일/분석을 바꾸면 WF2~WF6 결과는 **무효**가 되어야 하는데, 이를 표시·차단하는 로직이 전혀 없음. status를 WF1로 되돌리면 이후 완료 이력이 통째로 사라짐(반대로 안 되돌리면 낡은 하위 단계가 열린 채 유지됨).
3. **이원화된 죽은 설계**: `workflow.json`(`completed: []`, `stale_from`, `current_stage` 필드)이 프로젝트 생성 시 초기화되지만 ([B02_ProjRegister_Repository.py:31](../B02_ProjRegister/B02_ProjRegister_Repository.py#L31)), **실제 단계 판정에는 쓰이지 않음**. `projects.status`가 사실상 유일한 근거. 두 메커니즘이 공존하나 어느 쪽도 완전하지 않음.
4. **상태 갱신처 분산**: `status` UPDATE가 여러 라우터에 흩어져 있음
- [B03_FileInput_Router.py:158,210,240](../B03_FileInput/B03_FileInput_Router.py#L158) (`WF1_ANALYZING`/`WF1_COMPLETE`/`WF1_FAILED`)
- [B04_wf1_Surface_Router.py:160,199,217](../B04_wf1_Surface/B04_wf1_Surface_Router.py#L160) (동일 값 중복 세팅)
- **B03과 B04가 같은 WF1 상태를 각자 세팅** → 경쟁·불일치 위험
### 1.3 각 단계 산출물 테이블에는 이미 개별 status가 있음 (활용 가능)
- `surface_models.status` (`PROCESSING`/`COMPLETE`/`FAILED`) ([001_create_schema.sql:258](../db_management/001_create_schema.sql#L258))
- `processed_point_cloud.status`, `input_files.status` 등
- **즉 "단계별 완료"의 근거 데이터는 이미 DB에 존재** — 이를 요약할 상위 레이어만 없음
---
## 2. 재설계 목표
| # | 목표 | 설명 |
|---|------|------|
| G1 | 단계별 독립 상태 | 각 WF(0~6)의 상태를 개별적으로 저장 (`not_started` / `in_progress` / `complete` / `failed` / `stale`) |
| G2 | 단일 진실 원천(SSOT) | 상태의 근거를 **DB 한 곳**으로 통일. `projects.status` 단일 문자열 추론 폐기, `workflow.json` 이원화 제거 |
| G3 | 역방향 재작업 무효화 | N단계 재실행 시 N+1~6 단계를 자동 `stale`로 전환 (결과는 보존하되 "낡음" 표시) |
| G4 | UI 단일 규칙 | 대시보드·파일입력·WF1~6 공통 레이아웃이 **같은 상태 API**를 보고 버튼 활성/비활성·이동 결정 |
| G5 | 재실행 시 사용자 입력 보존 | 재분석 시 이전에 사용자가 선택/입력한 파라미터를 재사용 (초기 실행과 달리 값이 이미 있음) |
---
## 3. 제안 설계
### 3.1 데이터 모델 — `project_workflow_stages` 테이블 신규 (권장안)
프로젝트당 워크플로우 단계별로 1행. `projects.status` 문자열 추론을 대체하는 SSOT.
```sql
CREATE TABLE IF NOT EXISTS project_workflow_stages (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id CHAR(36) NOT NULL,
stage_no TINYINT NOT NULL, -- 0=파일입력, 1=WF1 ... 6=WF6
stage_key VARCHAR(30) NOT NULL, -- 'FILE_INPUT','WF1_SURFACE',...,'WF6_ESTIMATION'
state ENUM('NOT_STARTED','IN_PROGRESS','COMPLETE','FAILED','STALE')
NOT NULL DEFAULT 'NOT_STARTED',
progress_percent TINYINT NOT NULL DEFAULT 0,
params JSON NULL, -- 해당 단계에서 사용자가 선택/입력한 값 (재실행 시 재사용, G5)
message VARCHAR(255) NULL, -- 진행/오류 메시지
started_at TIMESTAMP NULL,
completed_at TIMESTAMP NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_project_stage (project_id, stage_no),
INDEX idx_pws_project (project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
- 프로젝트 생성 시 7행(stage 0~6)을 `NOT_STARTED`로 시드.
- `projects.status`는 **표시용 요약 캐시로 강등**(호환 유지)하거나 제거. 판정 근거는 이 테이블로 일원화.
- `workflow.json` 초기화 로직은 제거하거나 이 테이블과 동기화(권장: 제거하고 DB로 통일 — MariaDB가 SSOT).
**대안(경량):** 신규 테이블 없이 `projects`에 `stage_states JSON` 컬럼 하나 추가하여 `{"0":"COMPLETE","1":"COMPLETE","2":"STALE",...}` 저장. 마이그레이션은 가볍지만 단계별 params/타임스탬프 확장성이 낮음. **권장은 3.1 테이블 방식** (각 단계 params·이력 필요하므로).
### 3.2 상태 전이 규칙
```
NOT_STARTED ──(실행 시작)──▶ IN_PROGRESS ──(성공)──▶ COMPLETE
└──(실패)──▶ FAILED ──(재실행)──▶ IN_PROGRESS
COMPLETE ──(하위 단계 재작업으로 상위가 재실행됨)──▶ STALE ──(재실행)──▶ IN_PROGRESS
```
- **stale 전파 (G3):** stage N이 `IN_PROGRESS`로 전이될 때, stage N+1..6 중 `COMPLETE`인 것을 모두 `STALE`로 변경.
- `STALE`은 결과 파일/DB 행을 삭제하지 않음 — "낡음" 플래그만. 사용자가 해당 단계를 다시 실행하면 `COMPLETE`로 복귀.
- **진입 가능 규칙 (활성화):** stage N은 stage N-1이 `COMPLETE`일 때만 진입(이동) 가능. 단 파일입력(stage 0)은 항상 가능.
- `STALE`/`FAILED` 단계는 진입 가능하되(재작업 목적) 이후 단계로는 못 넘어감.
### 3.3 상태 조회 API (SSOT 단일 엔드포인트)
기존 분산된 status 대신 프로젝트 단위 통합 조회를 신설:
```
GET /api/projects/{project_id}/workflow-state
→ {
"project_id": "...",
"current_stage": 2, // 진입 가능한 최고 단계
"stages": [
{"stage_no":0,"stage_key":"FILE_INPUT","state":"COMPLETE","progress_percent":100},
{"stage_no":1,"stage_key":"WF1_SURFACE","state":"COMPLETE","progress_percent":100},
{"stage_no":2,"stage_key":"WF2_ROUTE","state":"IN_PROGRESS","progress_percent":40},
{"stage_no":3,"stage_key":"WF3_...","state":"STALE","progress_percent":0},
...
]
}
```
- 대시보드 프로젝트 목록 API도 이 요약(`current_stage` + 단계별 state 배열)을 포함하도록 확장.
- 기존 `GET /surface/status`(WF1 진행률)는 이 테이블의 stage 1 행을 읽어 반환하도록 내부만 교체(외부 계약 유지 → B03 폴링 호환).
### 3.4 상태 갱신 지점 통일
단계별 상태 전이를 **공통 유틸 함수**로 통일하여 라우터 중복 제거:
```python
# common_util/common_util_workflow_state.py (신규)
async def start_stage(conn, project_id, stage_no, params=None): ... # IN_PROGRESS + 하위 STALE 전파
async def complete_stage(conn, project_id, stage_no): ... # COMPLETE + completed_at
async def fail_stage(conn, project_id, stage_no, message): ... # FAILED
async def get_workflow_state(conn, project_id) -> list[dict]: ... # 조회
```
- B03/B04가 각자 하던 `WF1_ANALYZING`/`WF1_COMPLETE` UPDATE를 이 함수 호출로 대체.
- **WF1 상태의 이중 세팅 문제 해소:** 파일입력 완료 후 자동 WF1 분석 흐름에서 stage 0 `complete` → stage 1 `start`/`complete`가 한 경로로만 일어나도록 정리.
### 3.5 재실행 시 사용자 입력 보존 (G5)
- 각 stage 행의 `params JSON`에 해당 단계 실행 시 사용자가 고른 값을 저장
- WF1: `{source_filters, methods, force, input_file_id}`
- WF2: `{algorithm, road_grade, min_radius, ...}`
- 사용자가 단계 재진입 시 UI는 `params`를 불러와 폼 기본값으로 채움 → 초기 실행과 달리 재계산이 즉시 가능.
---
## 4. 프론트엔드 반영
### 4.1 공통 규칙 (대시보드 + 파일입력 + WF1~6 레이아웃)
- 세 곳 모두 **`workflow-state` API 하나**를 근거로 사용 (G4). 개별 페이지가 자체 추론하지 않음.
- 버튼 활성화: `stage.state === 'COMPLETE'`인 다음 단계까지 이동 가능. `STALE`은 "재작업 필요" 배지로 표시하되 진입 허용.
- 대시보드 워크플로우 버튼([B01_Dashboard_UI_Page.ts:370](../B01_Dashboard/B01_Dashboard_UI_Page.ts#L370) `workflow()`)의 `enabled` 계산을 `stages` 배열 기반으로 교체.
### 4.2 헤더 토글과 전역 헤더 구분 (혼동 방지 메모)
- WF1~6 공통 레이아웃(`ui_template_workflow_layout`)의 헤더 숨김/표시 토글은 **그 페이지 내부 작업 헤더**에만 작용. 로그인/로그아웃/사용자명이 있는 **전역 상단 헤더(`app_shell.ts`)와는 별개**임.
- 코더 주의: 화면 확장 목적으로 헤더를 숨길 때 전역 헤더(`app-header`)를 건드리지 말 것. 두 헤더는 DOM/스타일이 분리돼 있어야 함.
### 4.3 페이지 이동 시점 (이미 구현됨 — 유지)
- 파일 업로드 → WF1 분석 완료 폴링 → 완료 후 B04 이동은 이미 올바르게 구현([B03_FileInput_UI_Page.ts:606-609](../B03_FileInput/B03_FileInput_UI_Page.ts#L606)).
- 재설계 후에도 이 시점을 유지하되, "완료" 판정을 `workflow-state`의 stage 1 == COMPLETE로 통일.
---
## 5. 마이그레이션 & 호환
1. `project_workflow_stages` 테이블 생성 SQL을 `db_management/`에 신규 번호로 추가.
2. 기존 프로젝트 백필: 현재 `projects.status` 문자열을 해석해 각 프로젝트의 stage 0~N을 `COMPLETE`로, 나머지는 `NOT_STARTED`로 시드하는 1회성 스크립트.
3. `projects.status`는 당분간 요약 캐시로 유지(외부 참조 호환), 신규 판정은 테이블 기준. 안정화 후 제거 검토.
4. `workflow.json` 초기화 로직 제거(또는 테이블과 동기화). `common_util_workflow.py`의 `stale_from`은 새 stale 전파로 대체되므로 정리 대상.
---
## 6. 구현 순서 (코더용)
| Phase | 작업 | 파일(예상) |
|-------|------|-----------|
| 1 | 테이블 생성 SQL + 생성 시 7행 시드 | `db_management/00X_*.sql`, B02 생성 로직 |
| 2 | 상태 전이 공통 유틸 (start/complete/fail/get + stale 전파) | `common_util/common_util_workflow_state.py` |
| 3 | `GET /workflow-state` API + 대시보드 목록 API 확장 | B01, 신규 라우터 or 공통 |
| 4 | B03/B04 등 기존 status UPDATE를 공통 유틸 호출로 교체 | B03/B04 Router |
| 5 | 각 단계 실행 시 `params` 저장 + 재진입 시 폼 프리필 | 각 WF Router/UI |
| 6 | 프론트: 세 UI를 `workflow-state` 기반 활성화로 통일 | B01, B03, `ui_template_workflow_layout` |
| 7 | 기존 프로젝트 백필 스크립트 + `workflow.json` 정리 | migration |
---
## 7. 검증 체크리스트
- [x] 신규 프로젝트: 모든 단계 `NOT_STARTED`, stage 0만 진입 가능
- [x] 파일 업로드 후 WF1 자동 분석 → stage 0/1 `COMPLETE`, stage 2 진입 가능
- [x] WF2 완료 후 WF1으로 돌아가 재분석 → stage 2~6이 `STALE`로 전환되는지
- [x] `STALE` 단계 재실행 시 `COMPLETE` 복귀 + 결과 파일 보존 확인
- [x] 대시보드/파일입력/WF 레이아웃 세 곳의 버튼 활성화가 동일 규칙으로 일치
- [x] 재분석 시 이전 `params`가 폼에 프리필되는지 (G5)
- [x] WF1 상태가 B03·B04 두 곳에서 중복 세팅되지 않는지 (단일 경로)
- [x] 전역 헤더(로그인/로그아웃)가 WF 레이아웃 헤더 토글과 무관하게 항상 표시되는지
---
## 8. 미해결/확인 필요 사항 (코더 착수 전 결정)
1. **`projects.status` 유지 vs 제거**: 당장은 요약 캐시로 유지 권장(대시보드 정렬·필터 등 기존 참조 호환). 완전 제거는 참조처 전수 조사 후.
2. **STALE 하위 결과 파일 처리**: 낡은 결과 파일을 즉시 삭제할지, 재실행 시 덮어쓸지. 권장: **보존 후 재실행 시 덮어쓰기**(사용자가 비교/복구 가능).
3. **stage_key 명칭**: `FILE_INPUT`, `WF1_SURFACE`, `WF2_ROUTE`, `WF3_PROFILE_CROSS`, `WF4_DESIGN_DETAIL`, `WF5_QUANTITY`, `WF6_ESTIMATION` 로 확정 제안 (B0N 폴더명과 정합).
@@ -0,0 +1,8 @@
# 검증 보고서: 4단계 기타 작업자 역할 추가 검증
* **검증일**: 2026-07-12
* **검증 대상 파일**: [AGENTS.md](file:///C:/Program_coding/%EC%9E%84%EB%8F%84%EC%84%A4%EA%B3%84%20%EB%B0%8F%20%EA%B2%AC%EC%A0%81%EC%9E%90%EB%8F%99%ED%99%94%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8%20%EA%B0%9C%EB%B0%9C/AGENTS.md)
* **검증 결과**:
1. `#### 4단계: 기타 작업자 (위키 관리 / 작업 보조)` 역할이 `AGENTS.md`에 성공적으로 기술되었습니다.
2. 3중 역할 분담 협업 체계 다이어그램에도 `[4. 위키 관리]` 흐름이 정확히 명시되었습니다.
3. 사용자가 요청한 세부 조건(인제스트/린트 시 `.agents/instructions.md` 참조, 위키 업데이트 시 `docs/wiki`로 이동하여 `graphify .` 실행)이 올바르게 반영된 것을 확인하였습니다.
+8
View File
@@ -0,0 +1,8 @@
1. 나는 누구인가: 나는 임도설계 실무자이자 Aislo(라이다 기반 임도 설계·견적 자동화 웹앱) 프로젝트의 기획·설계자야. 코드를 직접 작성하거나 깊이 이해하지는 못하지만, 도메인 전문성을 바탕으로 결과물(프론트엔드 화면, 산출물)을 보고 검증할 수 있는 능력이 있고, 이를 바탕으로 AI 코드 어시스턴트들에게 방향성과 구체적 지시를 내리는 사람이야.
2. 기록의 목적 : 내가 기록하는 목적은 바이브 코딩 할 때 AI 코드 어시스턴스들과 더 효율적은 소통을 하고 개발하고 싶어. 웹앱을 프론트엔드, 백엔드, DB, API 등을 구조화 및 연결하여 AI코드 어시스턴스가 더욱 쉽게 문서 전체를 찾는게 아니라. 효과적으로 관련 문서들만 찾게 도움을 주고 같은 실수나 배경 설명을 줄이기 위해 사용하려고해. 그리고 al 어시스턴트 에이전트도 계획서 작성 > 코드작업 > 계획서의 체크리스트 점검 > 코드 검증 > 검증보고서 > 계획서 업데이트를 함으로써 항상 계획를 업데이트 함으로써 주기적인 관리도 맡기고 싶어.
- 심화 배경: 현재 상위 `.agent` 폴더에 백엔드/프론트엔드/DB/폴더구조/계획서/검증보고서 등 5~10개의 md 파일로 관리 중인데, 개발이 진행될수록 이 문서들이 비대해져서 컨텍스트·토큰 낭비를 일으키고 있음. 그 결과 AI와의 작업 흐름이 자주 끊기고, 코딩 방향성이 반복적으로 틀어지는 문제가 재발하고 있음. 필요한 것은 더 많은 기록이 아니라, 문서를 비대해지지 않게 서로 연결하고 정리해서 여러 AI 코드 어시스턴트들에게 필요한 부분만 효율적으로 공유하는 것.
3. 최종 결과 : 라이다 데이터를 활용하여 임도 설계에 대한 자동화하는 웹앱 프로그램을 개발하고 싶어.
- 위키의 주 독자: AI 코드 어시스턴트 자신 (코드 작성 전후로 읽는 용도). 흐름은 사용자와 대화 → AI가 계획서 작성/업데이트 → 계획서가 위키 내용을 참조 → AI가 위키를 효율적으로 추적하되 함수·변수 단위, DB 컬럼 정보까지 내려가서 확인 가능해야 함.
- 사용자 본인이 위키를 직접 다시 찾아볼 일은 거의 없음. 대신 6개월 후의 AI(또는 새 세션의 AI)와 팀원이 이 위키를 보고 "왜 이렇게 설계했는지, 어떤 함수/변수를 썼는지, DB에 뭐가 있는지"를 스스로 파악해서 개선 작업이나 인수인계에 활용하는 것이 1년 후 이상적인 모습.
+20
View File
@@ -0,0 +1,20 @@
# wiki/ 폴더 규칙
AI가 raw/의 원본을 읽고 편집·유지하는 산출물 계층. 자세한 운영 규칙(Ingest/Query/Lint, 위키 규칙 14가지)은 `docs/CLAUDE.md`의 Wiki Schema 섹션을 따른다.
## 구조
- `index.md` — 전체 카탈로그. 모든 질의는 여기서 시작한다.
- `log.md` — 연대기 로그. append-only.
- `pages/B0x_이름/` — 프로젝트의 실제 B01~B09 폴더와 1:1 대응하는 페이지 단위 폴더. 하위에 `frontend.md`, `backend.md`, `db.md`, `api.md`, `dependencies.md`를 필요한 것만 생성한다.
- `concepts/` — 여러 페이지가 함께 쓰는 공유 자원의 1차 정의처. DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸(`common_util_*`), API 공통 엔드포인트, 공통 Pydantic 스키마, 외부 라이브러리 의존성, workflow 상태 관리 등.
## 작성 규칙
- 모든 페이지에 YAML frontmatter 포함. `status``draft`/`stable`/`stale` 셋 중 하나만 사용 (draft=계획서만 반영, stable=검증보고서로 확인, stale=raw 갱신됐는데 미반영).
- **`pages/` 하위 파일에는 반드시 `page_id: B0x_이름`(또는 `A0x_이름`)을 frontmatter에 추가한다.** 파일명(`frontend.md` 등)만으로는 소속 페이지를 알 수 없으므로, 폴더 밖에서 봐도 식별 가능해야 한다. concepts 파일에는 해당 없음.
- 내부 참조는 `[[페이지명]]` 위키링크 형식.
- 새 페이지를 만들기 전에 기존 페이지를 업데이트할 수 있는지 먼저 확인한다.
- 소스 요약은 사실만, 해석·종합은 concepts 페이지에서 다룬다.
- 계획서와 검증보고서가 충돌하면 **검증보고서를 채택**한다 (실제 코드 상태에 더 가까운 소스). 계획서 내용은 지우지 않고 "계획 당시 의도"로 남긴다.
- **공유 자원(두 페이지 이상이 쓰는 테이블·함수·API·의존성 등)은 concepts에만 정의한다.** 페이지 쪽 파일(`db.md` 등)에는 용도 설명 + 위키링크만 남긴다. concepts 쪽에는 사용처(역참조) 목록을 직접 적어 유지한다.
- **함수·API·테이블 등 개별 항목은 `항목 | 위치(파일:줄번호) | 역할` 표 형식으로만 기록한다.** 서술문 금지 — grep 없이 바로 코드 위치를 찾을 수 있어야 한다.
- **페이지(파일) 하나는 100줄을 넘지 않는다.** 넘으면 하위 주제별로 즉시 분할하고 index.md를 갱신한다.
+20
View File
@@ -0,0 +1,20 @@
# wiki/ 폴더 규칙
AI가 raw/의 원본을 읽고 편집·유지하는 산출물 계층. 자세한 운영 규칙(Ingest/Query/Lint, 위키 규칙 14가지)은 `docs/CLAUDE.md`의 Wiki Schema 섹션을 따른다.
## 구조
- `index.md` — 전체 카탈로그. 모든 질의는 여기서 시작한다.
- `log.md` — 연대기 로그. append-only.
- `pages/B0x_이름/` — 프로젝트의 실제 B01~B09 폴더와 1:1 대응하는 페이지 단위 폴더. 하위에 `frontend.md`, `backend.md`, `db.md`, `api.md`, `dependencies.md`를 필요한 것만 생성한다.
- `concepts/` — 여러 페이지가 함께 쓰는 공유 자원의 1차 정의처. DB 스키마, 인증/RBAC, 저장 경로 규칙, 공통 유틸(`common_util_*`), API 공통 엔드포인트, 공통 Pydantic 스키마, 외부 라이브러리 의존성, workflow 상태 관리 등.
## 작성 규칙
- 모든 페이지에 YAML frontmatter 포함. `status``draft`/`stable`/`stale` 셋 중 하나만 사용 (draft=계획서만 반영, stable=검증보고서로 확인, stale=raw 갱신됐는데 미반영).
- **`pages/` 하위 파일에는 반드시 `page_id: B0x_이름`(또는 `A0x_이름`)을 frontmatter에 추가한다.** 파일명(`frontend.md` 등)만으로는 소속 페이지를 알 수 없으므로, 폴더 밖에서 봐도 식별 가능해야 한다. concepts 파일에는 해당 없음.
- 내부 참조는 `[[페이지명]]` 위키링크 형식.
- 새 페이지를 만들기 전에 기존 페이지를 업데이트할 수 있는지 먼저 확인한다.
- 소스 요약은 사실만, 해석·종합은 concepts 페이지에서 다룬다.
- 계획서와 검증보고서가 충돌하면 **검증보고서를 채택**한다 (실제 코드 상태에 더 가까운 소스). 계획서 내용은 지우지 않고 "계획 당시 의도"로 남긴다.
- **공유 자원(두 페이지 이상이 쓰는 테이블·함수·API·의존성 등)은 concepts에만 정의한다.** 페이지 쪽 파일(`db.md` 등)에는 용도 설명 + 위키링크만 남긴다. concepts 쪽에는 사용처(역참조) 목록을 직접 적어 유지한다.
- **함수·API·테이블 등 개별 항목은 `항목 | 위치(파일:줄번호) | 역할` 표 형식으로만 기록한다.** 서술문 금지 — grep 없이 바로 코드 위치를 찾을 수 있어야 한다.
- **페이지(파일) 하나는 100줄을 넘지 않는다.** 넘으면 하위 주제별로 즉시 분할하고 index.md를 갱신한다.
@@ -0,0 +1,86 @@
---
status: stable
related_pages: ["[[A01_Home/A01_frontend]]", "[[A02_ProgDetail/A02_frontend]]", "[[B01_Dashboard/B01_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]"]
last_updated: 2026-07-12
source: A00_Common 공유 프레임워크 소스 코드
---
# A00_Common — App Shell Framework
모든 페이지의 공통 스켈레톤. 헤더(로고/네비/언어·테마/인증버튼) + 콘텐츠 아울렛 + 푸터를 통합 관리.
## 파일 구성
| 파일 | 역할 |
|---|---|
| `app_shell.ts` (284줄) | 헤더·푸터 렌더링, 테마·언어 토글, 인증 상태별 버튼 채우기 |
| `b_page_scaffold.ts` (163줄) | B 그룹(B03~B09) 페이지용 스캐폴드—"준비 중" 임시 페이지 래퍼, 워크플로우 셸 헬퍼 |
| `router.ts` (158줄) | SPA 라우터(해시 기반), 인증 가드, 지연 로딩 |
## app_shell 구성요소
### 테마 & 언어 관리
- `initThemeAndLang()` — localStorage 복원 (THEME_STORAGE_KEY, LANG_STORAGE_KEY)
- `onApp_Theme_Toggle_Click()` — light ↔ dark 전환
- `onApp_Lang_Toggle_Click()` — 한국어 ↔ 영문 전환 + 현재 페이지 재렌더
### 헤더 구성 (64px 높이, sticky)
- **로고**: `BRAND_ICON_URL`(prog_icon.jpg) + 브랜드명("Aislo")
- **네비게이션**: 5개 링크
- App_Nav_Program → A02_PROG_DETAIL
- App_Nav_Company → A03_COMP_DETAIL
- App_Nav_News → A04_NEWS_HISTORY
- App_Nav_Education → A05_EDU_DETAIL
- App_Nav_Support → A08_SUPPORT
- **우측 액션**:
- 언어 버튼 ("EN" / "한")
- 테마 버튼 ("☀" / "☾")
- 인증 버튼 (로그인/회원가입 또는 계정/로그아웃)
### 인증 버튼 (비동기 fillAuthActions)
**미로그인 상태**: "로그인", "회원가입"
**로그인 상태**: 사용자명(또는 "내 계정"), "로그아웃"
### 로그아웃 (onApp_Logout_Click)
- `A06_Login.logout()` 호출 → 세션 쿠키 제거
- 오류 무시 후 홈(`A01_HOME`)으로 이동
### 푸터
- 높이 56px
- 저작권 텍스트 (App_Footer_Copyright)
## router 라우팅 테이블
| 라우트 | 렌더 함수 위치 |
|---|---|
| A01_HOME | A01_Home/A01_Home_UI_Page.ts |
| A02_PROG_DETAIL | A02_ProgDetail/A02_ProgDetail_UI_Page.ts |
| A03_COMP_DETAIL | A03_CompDetail/A03_CompDetail_UI_Page.ts |
| A04_NEWS_HISTORY | A04_NewsHistory/A04_NewsHistory_UI_Page.ts |
| A05_EDU_DETAIL | A05_EduDetail/A05_EduDetail_UI_Page.ts |
| A06_LOGIN | A06_Login/A06_Login_UI_Auth_Page.ts |
| A07_REGISTER | A07_Register/A07_Register_UI_Auth_Page.ts |
| A08_SUPPORT | A08_Support/A08_Support_UI_Page.ts |
| A09_SECURITY | A09_Security/A09_Security_UI_Page.ts |
| B01_ACCOUNT | B01_Dashboard/B01_Dashboard_UI_Page.ts |
| B02_PROJ_REGISTER | B02_ProjRegister/B02_ProjRegister_UI_Page.ts |
| B03_FILE_INPUT | B03_FileInput/B03_FileInput_UI_Page.ts |
| B04_WF1_SURFACE | B04_wf1_Surface/B04_wf1_Surface_UI_Page.ts |
| B05_WF2_ROUTE | B05_wf2_Route/B05_wf2_Route_UI_Page.ts |
| B06_WF3_PROFILE_CROSS | B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Page.ts |
| B07_WF4_DESIGN_DETAIL | B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts |
| B08_WF5_QUANTITY | B08_wf5_Quantity/B08_wf5_Quantity_UI_Page.ts |
| B09_WF6_ESTIMATION | B09_wf6_Estimation/B09_wf6_Estimation_UI_Page.ts |
| B10_PAYMENT | B10_Payment/B10_Payment_UI_Page.ts |
| B11_STATUS | B11_Status/B11_Status_UI_Page.ts |
## 인증 가드
- **PROTECTED_ROUTES** (config_frontend.ts 정의): 토큰 없으면 A06_LOGIN으로 리다이렉트
- **워크플로우 라우트** (B02~B11): 추가 검증
- 사용자 없음 → A06_LOGIN으로 리다이렉트
- company_id 미보유 & SYSTEM_ADMIN 아님 → B01_ACCOUNT로 리다이렉트 (회사 가입 강제)
## 세부 스캐폴드·CSS·종속성
`b_page_scaffold`, CSS 주입, 공통 종속성, 전체 사용처는 [[a00_app_shell_framework_scaffold]]에 분리했다.
@@ -0,0 +1,37 @@
---
type: concept
status: stable
related_pages: ["[[a00_app_shell_framework]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: 상위 프로젝트 A00_Common 코드 및 페이지 인제스트 대조 (2026-07-12)
---
# A00_Common — 스캐폴드·CSS·종속성
## b_page_scaffold
| 항목 | 역할 |
|---|---|
| `renderPendingContent` | 비 workflow 페이지용 준비 중 블록 |
| `renderPendingWorkflow` | workflow 셸과 좌·우 준비 중 영역 |
| `workflowSteps()` | 7단계 workflow 라벨 배열 |
## CSS 인젝션
| 항목 | 역할 |
|---|---|
| `injectShellStyles` | 셸 스타일을 `<head>`에 1회 주입 |
| `app-shell-style` | 헤더·아울렛·푸터 스타일 ID |
| theme 변수 | 헤더 64px·푸터 56px·아울렛 최소높이 |
## 종속성
| 항목 | 역할 |
|---|---|
| `ui_template_locale.ts` | 언어 상태·문자열 |
| `ui_template_elements.ts` | 버튼·workflow 셸 |
| `config_frontend.ts` | routes·theme·protected routes |
| `A06_Login_Api_Fetch.ts` | 세션 조회·로그아웃 |
## 사용처
| 페이지 | 역할 |
|---|---|
| A01~A09 | 공통 헤더·푸터·아울렛 |
| B01~B09 | 페이지별 workflow 셸·단계 내비게이션 |
+27
View File
@@ -0,0 +1,27 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]"]
last_updated: 2026-07-12
source: raw/plans/plan_workflow_state_management.md, raw/guidelines/backend.md + 실 코드 확인 2026-07-12
---
# API 공통 (여러 페이지가 공유하는 엔드포인트)
## 공통 오류 응답 포맷 (전 라우터)
```json
{"status": "error", "message": "원인"}
```
→ [[schema_common]]
## 워크플로우 상태 조회
| 항목 | 위치 | 역할 |
|---|---|---|
| `GET /api/projects/{project_id}/workflow-state` | `B03_FileInput_Router.py:667` | 단일 프로젝트의 `current_stage``stages[]` 조회 |
| `get_workflow_states_for_projects()` | `B01_Dashboard_Repository.py:83` | [[B01_Dashboard/B01_backend|B01_Dashboard]] 목록용 다중 프로젝트 상태 배치 조회 |
상태 모델과 사용처: [[workflow_state]].
## 폴링 패턴 (legacy workflow.json 설계; 현재 구현은 workflow-state API 사용)
- 클라이언트가 3초마다 `GET /api/projects/{project_id}/workflow` 호출
- 과거 설계의 stale 감지 UX 참고용이며, 실제 상태 조회는 [[workflow_state]] API를 사용한다.
+69
View File
@@ -0,0 +1,69 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/backend.md, raw/guidelines/frontend.md, raw/guidelines/agent.md
---
# 인증 / RBAC
## 세션 인증 (backend.md 6.3)
- 방식: Secure HttpOnly 세션 쿠키 (`session_id`, JWT 아님), `SameSite=Lax`
- 저장소: `sessions` 테이블 → [[db_schema/users_auth]]
- 유효시간: 활동 기준 4시간 또는 절대 12시간 중 먼저 도달하는 쪽
- DB 풀: `autocommit=True` (세션 즉시 조회 가능하도록)
- 프론트: 모든 API 요청에 `credentials: "include"` (frontend.md 5.1)
## OTP / 비밀번호
- OTP 6자리, 5분 유효, 해시 저장(bcrypt/PBKDF2), 재시도 최대 3회(2s/4s/8s 백오프)
- 비밀번호 최소 8자, bcrypt(라운드≥12) 또는 PBKDF2, 평문 저장 절대 금지
- 5회 연속 실패 시 계정 15분 잠금
## 사용자 상태 생명주기
```
PENDING_EMAIL → NO_COMPANY → (PENDING | ACTIVE) → (INACTIVE | REJECTED)
```
| 상태 | 접근 가능 범위 |
|---|---|
| NO_COMPANY | 로그인 가능, B01만 (B02~B11 차단) |
| PENDING | 로그인 가능, B01만 (승인 대기) |
| ACTIVE | 전체 (B01 + B02~B11) |
## 역할 (users.role)
| 역할 | 전체회사조회 | 역할변경 | 팀원관리 | 가입승인 | 프로젝트조회 범위 |
|---|---|---|---|---|---|
| SYSTEM_ADMIN | ✅ | ✅ | ✅ | ✅ | 전체 |
| ADMIN | ✗ | ✗ | ✅(자기회사) | ✅(자기회사) | 회사별 |
| USER | ✗ | ✗ | ✗ | ✗ | 회사별 |
`is_master` 플래그: TRUE=회사 생성자/ADMIN(팀원 승인권), FALSE=일반 팀원
## 권한 검증 헬퍼 (B01_Dashboard)
| 항목 | 위치 | 역할 |
|---|---|---|
| `_can_edit_project()` | `B01_Dashboard_Router.py:79` | 역할·회사 기준 프로젝트 수정 권한 |
| `_can_manage_automation()` | `B01_Dashboard_Router.py:87` | USER 차단과 프로젝트 범위 검사 |
| `_can_edit_user()` | `B01_Dashboard_Router.py:93` | 사용자 수정 범위 검사 |
| `count_company_admins()` | `B01_Dashboard_Repository.py:584` | 마지막 ADMIN 삭제·강등 방지 |
| `canEditProject()` 외 5개 | `B01_Dashboard_UI_Helper.ts:3` | 프론트 표시 제어; 백엔드 권한을 대체하지 않음 |
**원칙:** 백엔드가 모든 수정/삭제/역할변경 API에서 반드시 재검증 (프론트 UI는 표시 제어만, 신뢰 금지). 위반 시 403 `{"status":"error","message":"Access denied"}`.
## 인증 갱신
로그인 시 `users.auth_expires_at = NOW() + 3개월` 자동 연장. 만료 시 재인증 모달.
## 라우팅 가드 (frontend.md 5.2)
B02~B11 진입 시 `fetchDashboardMe()``company_id`+`status` 확인 → NO_COMPANY/PENDING이면 B01로 강제 리다이렉트.
## 사용처 (역참조)
- [[B01_Dashboard/B01_backend|B01_Dashboard]] — 권한 헬퍼 5종, RBAC 접근제어
- [[A06_Login/A06_backend]] — 세션 발급/OTP 재인증, 상태 확인
- [[A07_Register/A07_backend]] — 사용자 상태 생명주기(PENDING_EMAIL→NO_COMPANY)
- [[A09_Security/A09_backend]] — require_master/require_system_admin 권한 데코레이터
## ⚠️ 계획서-실코드 불일치 (A그룹 인제스트 발견)
이 페이지의 세션/OTP 정책은 **계획서 기준**(세션 12h/4h 만료, OTP 5분·재시도3회·5회실패 15분잠금)이나,
실 코드([[A06_Login/A06_backend]])는 `auth_expires_at = NOW()+3개월` 갱신·신규브라우저/주기 재인증 OTP·5회실패시 관리자 알림 메일 방식.
[[A09_Security/A09_frontend]] 약관 텍스트도 계획서 기준(12h/4h)으로 표기됨.
→ 규칙6(실 코드 우선)에 따라 실 정책 검증 후 이 페이지 갱신 필요. 계획 당시 의도로 위 내용은 보존.
+35
View File
@@ -0,0 +1,35 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/structure.md, raw/guidelines/backend.md
---
# 공통 유틸 (common_util/)
> 둘 이상의 페이지가 실제로 공유하는 경로·원자적 파일쓰기·기하 유틸만 이 폴더에 배치 (migration_plan.md 2.2)
| 파일 | 역할 | 사용처 |
|---|---|---|
| `common_util_validate.ts` | 프론트 1차 유효성 검사 (이메일/빈값/최소길이) | 전 페이지 |
| `common_util_auth.py` | 비밀번호·OTP·세션 쿠키·권한 검증 | 전 페이지 → [[auth_rbac]] |
| `common_util_auth_repository.py` | 인증·조직·관리 Raw SQL 저장소 | 인증 관련 |
| `common_util_email.py` | 비동기 SMTP 발송+재시도(최대3회) | OTP/승인 알림 |
| `common_util_email_templates.py` | 인증·조직 알림 이메일 템플릿 | common_util_email.py |
| `common_util_string.ts` | 문자열 유틸 | 미상세 |
| `common_util_storage.py` | 프로젝트·워크플로우 단계별 저장 경로 | → [[storage_paths]] |
| `common_util_json.py` | JSON 원자적 저장 | workflow.json 등 |
| `common_util_workflow.py` | 공유 workflow.json 상태 처리 | ⚠️ [[workflow_state]] 재설계로 대체/정리 예정 |
| `common_util_resource_monitor.py` | 시스템 리소스 계측(2분 주기) + log + DB 기록 | `sample_resources_loop()` → [[B01_Dashboard/B01_backend|B01_Dashboard]] |
| `common_util_format.py` | 포맷 유틸 | 미상세 |
## 이메일 발송 (common_util_email.py)
- 개발: Google Gmail SMTP / 상용: Google Workspace 또는 AWS SES
- 발송 유형: OTP, 가입확인, 팀원승인요청(ADMIN), 승인/거부결과, 회사참여신청(SYSTEM_ADMIN), 인증갱신알림
## 리소스 모니터링 (common_util_resource_monitor.py)
- `main.py` lifespan에서 `asyncio.create_task` 등록/취소
- 계측: `psutil.cpu_percent`, `psutil.virtual_memory().percent`, `shutil.disk_usage`
- 이중 기록: `system_resources` 테이블 (DB) + `log/system_resources.log` (탭 구분 1줄, `.gitignore`)
- 30일 롤링 삭제 (DB `DELETE`, 로그파일 오래된 줄 제거)
@@ -0,0 +1,95 @@
---
type: concept
status: stable
related_pages: ["[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 파일/지표면분석 테이블
← [[overview]]
## upload_sessions (업로드 세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `varchar(36)` | NO | PRI | 세션 UUID |
| `project_id` | `varchar(36)` | NO | MUL | FK→projects.id |
| `original_filename` | `varchar(255)` | NO | | 원본 파일명 |
| `file_size_bytes` / `chunk_size_bytes` | `bigint(20)` | NO | | 파일크기 / 청크크기 |
| `total_chunks` / `completed_chunks` | `int(11)` | - | | 전체 청크수 / 완료 청크수 |
| `status` | `enum('PENDING','IN_PROGRESS','COMPLETED','FAILED')` | YES | MUL | `PENDING` |
| `created_at` / `updated_at` | `timestamp` | YES | | 생성 / 최종수정 일시 |
## upload_chunks (업로드 청크 데이터)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `session_id` | `varchar(36)` | NO | MUL | FK→upload_sessions.id |
| `chunk_index` | `int(11)` | NO | | 청크 인덱스 (0, 1, 2...) |
| `chunk_hash` | `varchar(64)` | YES | | 청크 검증용 체크섬 |
| `size_bytes` | `bigint(20)` | NO | | 청크 용량 |
| `stored_at` | `varchar(255)` | YES | | 임시 파일 저장 경로 |
| `completed_at` | `timestamp` | YES | MUL | 완료 일시 |
## input_files (입력 원본 파일)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `file_type` | `varchar(50)` | YES | | `las`/`tif`/`prj`/`tfw`/`dxf` 등 |
| `original_filename` / `raw_file_path` | `varchar` | NO | | 파일명 / storage 상대경로 |
| `file_size_mb` | `float` | YES | | 파일 크기 (MB) |
| `upload_by` / `upload_at` | - | - | MUL | 업로드 유저 FK / 일시 |
| `crs_epsg` | `int(11)` | YES | | EPSG 좌표계 정보 |
| `metadata` | `longtext` | YES | | 헤더/포인트수 등 분석 JSON |
| `status` | `varchar(50)` | YES | | `UPLOADED` ➡️ `PROCESSED` ➡️ `ARCHIVED` |
## processed_point_cloud (필터/변환 포인트클라우드)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `input_file_id` / `project_id` | - | NO | MUL | FK→input_files / projects |
| `process_type` | `varchar(50)` | YES | | 변환 필터 분류 |
| `processed_file_path` / `converted_file_path` | `varchar` | YES | | 저장 경로 / PLY/LAZ 경로 |
| `converted_format` | `varchar(50)` | YES | | 변환 포맷 |
| `point_count` | `int(11)` | YES | | 포인트 개수 |
| `min_z` / `max_z` / `mean_z` | `float` | YES | | 고도 최저 / 최고 / 평균 |
| `x_min` ~ `y_max` | `float` | YES | | X/Y 좌표 바운딩 박스 범위 |
| `density_per_sqm` | `float` | YES | | ㎡당 점 밀도 |
| `classification_summary` | `longtext` | YES | | ground/unclassified 통계 |
| `processing_params` | `longtext` | YES | | 분석 파라미터 JSON |
| `processed_at` / `status` | - | - | | 변환 일시 / `PROCESSING`\|`COMPLETE`\|`FAILED` |
## surface_models (지표면 모델 및 등고선)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `model_type` | `varchar(50)` | YES | | `dem_grid`/`tin`/`mesh`/`contour` |
| `source_file_id` / `processed_cloud_id` | `int(11)` | YES | MUL | FK→input_files / processed |
| `status` | `varchar(50)` | YES | | `PROCESSING` ➡️ `COMPLETE` / `FAILED` |
| `crs_epsg` / `resolution_m` | - | YES | | EPSG 좌표 / DEM 격자 해상도 |
| `model_file_path` | `varchar(500)` | YES | | 스토리지 상대 경로 |
| `generation_params` | `longtext` | YES | | 격자화 알고리즘 인자 JSON |
| `created_at` / `completed_at` | `timestamp` | - | | 생성 / 완료 일시 |
## terrain_layers (지형 레이어)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `surface_model_id` | `int(11)` | NO | MUL | FK→surface_models.id |
| `layer_name` | `varchar(100)` | YES | | 레이어 영문/한글 식별자 |
| `geometry_type` | `varchar(50)` | YES | | `POINTCLOUD`/`GRID`/`MESH`/`CONTOUR` |
| `layer_file_path` | `varchar(500)` | YES | | 개별 레이어 실 파일 경로 |
| `file_format` / `file_size_mb` | - | YES | | 포맷 (ply, tfw, las 등) / 크기 |
| `statistics` | `longtext` | YES | | 통계 수치 JSON |
| `created_at` | `timestamp` | NO | | 생성 일시 |
## input_files.status 값
`UPLOADED → PROCESSED → ARCHIVED`
## surface_models.status / processed_point_cloud.status 값
`PROCESSING → COMPLETE / FAILED`
관련 함수 위치 → [[B04_wf1_Surface/B04_db]], 사용 라이브러리 → [[dependencies]]
@@ -0,0 +1,102 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/guidelines/backend.md
---
# DB: 로그/모니터링 테이블
← [[overview]]
## login_logs (로그인 시도 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | YES | MUL | FK→users.id |
| `email` | `varchar(255)` | NO | | |
| `status` | `enum('SUCCESS','FAILURE')` | NO | | |
| `failure_reason` / `user_agent` | `varchar(100)` / `text` | YES | | |
| `login_at` | `timestamp` | NO | MUL | `current_timestamp()` |
## activity_logs (사용자 활동 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `action_type` | `varchar(100)` | NO | | 행위 종류 |
| `action_at` | `timestamp` | NO | | `current_timestamp()` |
| `resource_id` / `status` | `varchar(255)` / `enum` | - | | 리소스 ID / SUCCESS\|FAILURE |
| `details` | `longtext` | YES | | JSON 상세 데이터 |
## audit_logs (감사 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | YES | MUL | FK→users.id |
| `action` / `entity_type` | `varchar(100)` | YES | | CRUD/EXPORT/DOWNLOAD 등 |
| `entity_id` | `varchar(36)` | YES | | 대상 UUID 등 |
| `timestamp` / `ip_address` | `timestamp` / `varchar(45)` | - | | 발생시각 / IP 주소 |
| `details` | `longtext` | YES | | JSON 상세 |
## system_audit_logs (프로젝트/조직 변경 감사 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id (행위자) |
| `action` / `resource_type` | `varchar(50)` | - | | PROJECT_CREATE 등 / 종류 |
| `resource_id` | `int(11)` | YES | | |
| `timestamp` | `timestamp` | YES | MUL | `current_timestamp()` |
| `ip_address` / `user_agent` | `varchar(50)` / `text` | YES | | IP 주소 / 브라우저 에이전트 |
## system_admin_logs (시스템 관리자 행위 로그)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `admin_user_id` | `int(11)` | NO | MUL | FK→users.id (관리자 ID) |
| `action_type` / `target_id` | `varchar(100)` / `varchar(255)`| - | | 변경종류(USER_STATUS) / 대상 ID |
| `action_at` | `timestamp` | NO | | `current_timestamp()` |
| `details` | `longtext` | YES | | JSON 형태의 변경 상세 |
## system_resources (시스템 자원 계측)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `timestamp` | `timestamp` | YES | MUL | `current_timestamp()` |
| `cpu_usage_percent` / `memory_usage_percent` | `float` | YES | | |
| `disk_usage_percent` / `total_storage_mb` | `float` | - | | 디스크사용률 / 전체 스토리지 용량 |
| `active_user_count` / `active_project_count` | `int(11)` | NO | | 활성 사용자 / 프로젝트 수 |
## support_requests (기술 지원 요청)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `company_id` | `int(11)` | YES | MUL | FK→companies.id |
| `company_name` / `user_name` | `varchar(255)` | - | | 회사명 / 작성자명 |
| `user_email` / `user_phone` | `varchar(255)` / `varchar(30)` | - | | 이메일 / 전화번호 |
| `subject` / `message` | `varchar(255)` / `text` | NO | | 제목 / 문의 메시지 본문 |
| `status` | `enum('NEW','REVIEWED','RESOLVED','DELETED')` | NO | MUL | `NEW` |
| `reviewed_by` | `int(11)` | YES | MUL | FK→users.id (검토자) |
| `created_at` / `reviewed_at` / `review_notes` | - | - | | 접수 / 검토일시 / 메모 |
## change_logs (설계 변경 이력)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | | FK→projects.id |
| `changed_by` | `int(11)` | YES | MUL | FK→users.id (수정자) |
| `changed_at` | `timestamp` | NO | | `current_timestamp()` |
| `entity_type` / `entity_id` | `varchar(100)` / `int(11)` | YES | | 대상엔티티(routes 등) / 대상 PK |
| `old_value` / `new_value` | `longtext` | YES | | 변경 전 / 후 JSON 스냅샷 |
| `reason` | `varchar(255)` | YES | | 변경 사유 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (SYSTEM_ADMIN 리소스 현황·시스템 로그 섹션), [[A08_Support/A08_backend|A08_Support]] (support_requests INSERT)
## system_audit_logs — 사용처 (B01, B02)
- **프로젝트 생성**: `B02_ProjRegister_Repository.py:92` (`PROJECT_CREATE` 기록 ➡️ [[B02_ProjRegister/B02_backend]])
- **기타 관리 행위**: `B01_Dashboard_Repository.py:245,265` (수정·삭제), `:336,367` (회사 생성), `:609` (감사 로그 조회), `:758,779` (자동화 제어)
## 리소스 API (SYSTEM_ADMIN 전용)
`GET /api/dashboard/admin/resources?days=30`
- 실시간 계측(`current`), 30일 다운샘플링 시계열 조회(`history`, ~4MB ➡️ ~55KB 압축), 활성 지표(`stats`) 제공.
+64
View File
@@ -0,0 +1,64 @@
---
type: concept
status: stable
related_pages: []
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/guidelines/agent.md
---
# DB 스키마 개요
**DB명:** `aislo_db` / MariaDB v10.6+ / utf8mb4_unicode_ci / 드라이버 aiomysql
> ✅ **모순 해결 (2026-07-12, 실 DB 직접 조회 `SHOW TABLES`로 검증):** 실제 운영 DB는 **34개 테이블**. `db_schema.md`(34개)가 정확했고 `agent.md`의 18개/23개는 갱신 안 된 값 — agent.md 쪽 오기로 판정.
>
> **raw 문서에 없던 실제 테이블 2개 발견:**
> - `project_workflow_stages` — [[../workflow_state]] 설계가 **이미 구현되어 실 DB에 존재**함 (컬럼 구성이 계획서와 정확히 일치). status를 draft→stable로 갱신함.
> - `system_admin_logs` — 시스템 관리자(SYSTEM_ADMIN)가 회원/조직 상태(`status`) 등을 변경할 때 감사 이력을 INSERT하는 Write-Only 성격의 테이블 ➡️ [[logs_monitoring]].
>
> ⚠️ **미확정/미연동 테이블 격리 규칙**:
> - 분석 중이거나 소스코드와의 구체적인 연결고리(백엔드/프론트엔드 실사용처)가 아직 확정되지 않은 테이블이 신규 추가될 경우, 상위 공식 그룹 파일에 포함하지 않고 [[unconfirmed/README|미확정 폴더(unconfirmed/)]] 내에 개별 `[테이블명].md` 마크다운 파일로 저장하여 격리 관리합니다.
## 테이블 그룹 (9개)
| 그룹 | 테이블 수 | 상세 페이지 |
|---|---|---|
| 사용자/인증/조직 (핵심) | 7 | [[users_auth]] |
| 로그/모니터링 | 8 | [[logs_monitoring]] |
| 프로젝트 관리 | 3 | [[projects]] |
| 파일/지표면분석 | 6 | [[files_surface]] |
| 경로/종횡단 | 5 | [[route_profile]] |
| 구조물/수량/산출물 | 4 | [[structure_output]] |
| 미확정/연결 대기 테이블 | 0 | [[unconfirmed/README]] |
## 파일 경로 추적 컬럼 (DB에 경로만 기록, 실 파일은 파일시스템)
| 테이블.컬럼 | 용도 | 사용 단계 |
|---|---|---|
| `input_files.raw_file_path` | 원본 입력 파일 | B03 |
| `processed_point_cloud.converted_file_path` | 변환된 포인트클라우드 | B04 |
| `surface_models.model_file_path` | 지표면 모델 | B04 |
| `routes.route_data_path` | 경로 데이터(GeoJSON) | B05 |
| `longitudinal_sections.longitudinal_file_path` | 종단면 | B06 |
| `cross_sections.cross_section_file_path` | 횡단면 | B06 |
| `structures.structure_data_path` | 구조물 배치 | B07 |
| `quantity_items.quantity_data_path` | 수량 항목 | B08 |
| `outputs.outputs_directory_path` | 산출물 폴더 | B09 |
| `output_files.output_file_path` | 개별 산출 파일 | B09 |
상세 경로 규칙 → [[storage_paths]]
## 설계 원칙
- DB에는 "어디에 무엇이 있는가"의 메타정보만 저장. 실제 대용량 파일(LAS/PLY/TIF/DXF/Excel/PDF/JSON)은 파일시스템.
- 공간 기하 데이터(GEOMETRY 타입 미지원) → JSON(GeoJSON 형식)으로 저장, 애플리케이션(Shapely/Geopandas)에서 파싱.
- 모든 주요 테이블에 soft delete(`deleted_at`) 적용.
## 테이블 관계 (핵심 흐름)
```
users ─company_id→ companies
projects ─user_id/company_id→ users/companies
└→ input_files → processed_point_cloud → surface_models → terrain_layers
└→ routes → route_points/route_statistics/longitudinal_sections/cross_sections → structures
└→ quantity_items
└→ outputs → output_files
```
+56
View File
@@ -0,0 +1,56 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B02_ProjRegister/B02_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md, raw/plans/plan_workflow_state_management.md
---
# DB: 프로젝트 관리 테이블
← [[overview]]
## projects (프로젝트)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `char(36)` | NO | PRI | 프로젝트 UUID |
| `user_id` / `company_id` | `int(11)` | NO | MUL | FK→users/companies |
| `name` | `varchar(255)` | NO | | 프로젝트명 |
| `region` / `road_type` | `varchar(100)` | YES | | 지역 / 임도종류 |
| `project_year` | `int(11)` | YES | | 사업년도 |
| `estimated_length_m` | `float` | YES | | 계획 연장(m) |
| `memo` | `text` | YES | | 메모 |
| `status` | `varchar(50)` | YES | | `NEW` (호환용 캐시) |
| `crs_epsg` | `int(11)` | YES | | `5178` (좌표계 EPSG) |
| `storage_path` | `varchar(500)` | YES | | 상대경로 ➡️ [[storage_paths]] |
| `created_at` / `updated_at` / `deleted_at` | `timestamp` | - | | 생성 / 수정 / soft-delete |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (목록), [[B02_ProjRegister/B02_backend|B02_ProjRegister]] (생성)
## project_versions (프로젝트 버전 스냅샷)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `version_num` | `int(11)` | NO | | 버전 번호 |
| `status` | `varchar(50)` | YES | | 버전 상태 |
| `data` | `longtext` | YES | | 설계 스냅샷 JSON 데이터 |
| `snapshot_at` / `created_at` | `timestamp` | NO | | 생성일시 |
## project_automations (프로젝트 자동화 정책)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `name` | `varchar(100)` | NO | | 자동화 명칭 |
| `logic_type` | `varchar(50)` | NO | | `trigger_on_stage_complete` 등 |
| `config_json` | `longtext` | NO | | 설정 값 JSON |
| `status` | `enum('DRAFT','ACTIVE','INACTIVE')` | NO | MUL | `DRAFT` |
| `created_by` / `updated_by` | `int(11)` | - | MUL | FK→users.id |
| `last_executed_at` | `datetime` | YES | | 최종 실행 일시 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (자동화 로직 관리, USER는 조회 차단)
## project_workflow_stages (단계별 상세 상태)
- 본 테이블은 워크플로우 7단계(stage 0~6)의 세부 진행도(state, params, progress_percent 등)를 관리하는 SSOT입니다.
- 상세 테이블 정보 및 사용처 ➡️ [[workflow_state]]
@@ -0,0 +1,69 @@
---
type: concept
status: stable
related_pages: ["[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 경로/종횡단 테이블
← [[overview]]
## routes (노선 경로)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `surface_model_id` | `int(11)` | YES | MUL | FK→surface_models.id |
| `status` | `varchar(50)` | YES | | `DRAFT` ➡️ `CONFIRMED` ➡️ `ARCHIVED` |
| `start_chainage_m` / `end_chainage_m` | `float` | YES | | 시작 시점 / 끝 시점 연장(m) |
| `total_length_m` | `float` | YES | | 총 연장(m) |
| `grade_percent` | `longtext` | YES | | 경사도 정보 JSON |
| `constraints` / `algorithm_params` | `longtext` | YES | | 회피 구역 JSON / 알고리즘 가중치 파라미터 JSON |
| `route_data_path` | `varchar(500)` | YES | | 노선 데이터 (GeoJSON) 파일 경로 |
| `computed_at` / `created_at` | - | - | | 연산 완료 일시 / 생성 일시 |
## route_points (경로 좌표점)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `route_id` | `int(11)` | NO | MUL | FK→routes.id |
| `chainage_m` | `float` | YES | | 시점 거리(m) |
| `elevation_m` / `slope_percent` | `float` | YES | | 표고(m) / 종단 경사율(%) |
| `sequence_num` | `int(11)` | YES | | 점 순서 |
## route_statistics (노선 통계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `route_id` | `int(11)` | NO | MUL | FK→routes.id |
| `min_slope` / `max_slope` / `mean_slope` | `float` | YES | | 최저 / 최고 / 평균 경사(%) |
| `cut_volume_m3` / `fill_volume_m3` | `float` | YES | | 절토량(㎥) / 성토량(㎥) |
| `tree_cutting_volume` / `cost_score` | `float` | YES | | 벌채량 / 평점 비용 스코어 |
## longitudinal_sections (종단면 설계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `route_id` | `char(36)` / `int` | NO | MUL | FK→projects / routes |
| `computed_at` | `timestamp` | YES | | 종단 계산 일시 |
| `data` | `longtext` | YES | | 지반고/계획고/구배 등 설계 데이터 JSON |
| `longitudinal_file_path` | `varchar(500)` | YES | | 종단도면 캐드 파일 경로 |
| `status` | `varchar(50)` | YES | | `DRAFT` / `CONFIRMED` |
## cross_sections (횡단면 설계)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `route_id` | `char(36)` / `int` | NO | MUL | FK→projects / routes |
| `chainage_m` / `sequence_num` | - | YES | | 측정 체인 거리(m) / 단면 순서 |
| `data` | `longtext` | YES | | 좌우 사면 경사, 폭, 절성토 단면적 JSON |
| `cross_section_file_path` | `varchar(500)` | YES | | 횡단도면 캐드 파일 경로 |
| `status` | `varchar(50)` | YES | | `DRAFT` / `CONFIRMED` |
## routes.status 흐름
`DRAFT → CONFIRMED → ARCHIVED`
## cross_sections.data.structures
횡단면 하나에 배치된 구조물 ID 배열 → [[structure_output]]#structures 참조
@@ -0,0 +1,70 @@
---
type: concept
status: stable
related_pages: ["[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 구조물/수량/산출물 테이블
← [[overview]]
## structures (배치 구조물)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` / `cross_section_id` | - | - | MUL | FK→projects / cross_sections |
| `structure_type` | `varchar(100)` | YES | | 낙석방지책 / 계간수로 / 돌붙임 등 |
| `chainage_m` | `float` | YES | | 구조물 배치 체인 시점 거리(m) |
| `location` | `varchar(50)` | YES | | 좌 / 우 / 종단 등 배치 위치 |
| `length_m` / `width_m` / `height_m` | `float` | YES | | 길이 / 폭 / 높이 규격(m) |
| `material` | `varchar(100)` | YES | | 구조물 재질/자재 |
| `quantity` / `unit_price` | - | YES | | 수량 / 단가 |
| `design_notes` | `longtext` | YES | | 특기사항 등 메모 |
| `structure_data_path` | `varchar(500)` | YES | | 세부 구조 상세 데이터 파일 경로 |
| `last_modified_by` | `int(11)` | YES | MUL | FK→users.id (최종 수정자) |
| `created_at` / `updated_at` | `timestamp` | - | | 생성 / 수정 일시 |
## quantity_items (수량 산출 항목)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `category` | `varchar(100)` | YES | | 토공 / 구조물 / 포장 / 배수 등 공종 |
| `item_name` / `unit` | `varchar` | YES | | 품목 항목명 / 단위 (㎡, m 등) |
| `quantity_design` / `quantity_actual` | `float` | YES | | 설계 수량 / 최종 반영 수량 |
| `unit_price` / `total_price` | `float` | YES | | 단가 / 총 금액 |
| `standard_reference` | `varchar(255)` | YES | | 품셈 근거 코드/출처 |
| `computed_at` / `quantity_data_path` | - | YES | | 계산 일시 / 상세 산출근거 데이터 파일 경로 |
| `data` | `longtext` | YES | | 계산 세부 과정 통계 JSON |
## outputs (최종 견적/도면 산출 세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `project_id` | `char(36)` | NO | MUL | FK→projects.id |
| `output_type` | `varchar(100)` | YES | | `estimation_excel`/`drawing_dxf`/`report_pdf`/`all_bundle` |
| `status` | `varchar(50)` | YES | | `GENERATING` ➡️ `COMPLETE` / `FAILED` |
| `generated_by` / `generated_at` | - | - | MUL | 생성자 FK / 생성일시 |
| `version` | `int(11)` | YES | | 산출 버전 |
| `outputs_directory_path` | `varchar(500)` | YES | | 산출 파일 번들 디렉토리 경로 |
| `metadata` | `longtext` | YES | | 기타 도면 스케일 등 정보 JSON |
## output_files (개별 산출 파일 리스트)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `output_id` | `int(11)` | NO | MUL | FK→outputs.id |
| `file_type` | `varchar(50)` | YES | | `xlsx`/`pdf`/`dxf`/`dwg`/`json`/`zip` 등 |
| `original_filename` | `varchar(255)` | YES | | 원본 파일 식별 이름 |
| `output_file_path` | `varchar(500)` | YES | | 개별 파일 저장 경로 |
| `file_size_mb` / `download_count` | - | YES | | 파일 용량 / 다운로드 누적 카운트 |
| `created_at` | `timestamp` | NO | | 생성일시 |
## quantity_items 총비용 계산 예
```sql
SELECT SUM(quantity_actual * unit_price) AS total_cost, category
FROM quantity_items WHERE project_id = :project_id AND deleted_at IS NULL
GROUP BY category;
```
@@ -0,0 +1,7 @@
# 미확정 테이블 보관소 (Unconfirmed DB Schemas)
본 디렉토리는 소스코드 연동 처리가 완료되지 않았거나, 비즈니스 로직(백엔드/프론트엔드) 상의 구체적인 연결고리가 식별되지 않은 미확정 테이블의 명세서 및 임시 분석 내용을 개별 마크다운 파일로 저장·격리하는 공간입니다.
## 운영 규칙
1. **임시 격리**: 신규 테이블 추가 또는 레거시 테이블 중 사용처를 즉시 규명하기 어려운 경우 `[테이블명].md`로 파일을 생성해 기록합니다.
2. **확정 이관**: 소스코드에서의 실제 사용처(라우터, 리포지토리 등)가 명확해지면, 상위의 공식 테이블 그룹(예: `users_auth.md`, `logs_monitoring.md` 등)으로 표 명세를 이관하고 본 폴더 내의 마크다운은 삭제합니다.
@@ -0,0 +1,92 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/db_schema.md
---
# DB: 사용자/인증/조직 테이블
← [[overview]]
## users (사용자)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `email` | `varchar(255)` | NO | UNI | |
| `password_hash` | `varchar(255)` | NO | | |
| `name` | `varchar(255)` | NO | | |
| `position` / `department` | `varchar(100)` | YES | | 직위 / 부서 |
| `phone` | `varchar(20)` | YES | | |
| `company_id` | `int(11)` | YES | MUL | FK→companies.id |
| `role` | `enum('SYSTEM_ADMIN','ADMIN','USER')` | NO | MUL | `USER` |
| `is_master` | `tinyint(1)` | NO | | `0` (마스터 여부) |
| `status` | `enum('PENDING_EMAIL','NO_COMPANY','PENDING','ACTIVE','INACTIVE','REJECTED')` | NO | MUL | `PENDING_EMAIL` |
| `auth_expires_at` / `last_login` | `datetime` | YES | | 인증만료 / 최종로그인 |
| `login_failures` | `int(11)` | NO | | `0` (실패 횟수) |
| `created_at` / `updated_at` / `deleted_at` | `timestamp` | - | | 생성 / 수정 / soft-delete |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (사용자 관리), 전 페이지 공통 인증
## companies (회사)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `name` | `varchar(255)` | NO | UNI | 회사명 |
| `business_registration_number` | `varchar(20)` | YES | UNI | 사업자번호 |
| `status` | `enum('ACTIVE','INACTIVE','SUSPENDED')` | NO | | `ACTIVE` |
| `subscription_status` | `enum('FREE','TRIAL','PAID','EXPIRED')` | NO | | `FREE` |
| `created_by` | `int(11)` | YES | MUL | FK→users.id (생성자) |
| `company_code` | `varchar(20)` | YES | | 참여용 코드 |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]]
## sessions (세션)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `varchar(255)` | NO | PRI | 세션 ID (쿠키 값) |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `user_agent_hash` | `char(64)` | NO | | |
| `expires_at` | `timestamp` | NO | MUL | 만료일시 |
| `last_activity_at` | `timestamp` | NO | | 최종 활동일시 |
**사용처:** 전 페이지 공통 (세션 인증) — 상세 → [[auth_rbac]]
## email_otps (이메일 OTP)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `purpose` | `enum('REGISTER','LOGIN','REVERIFY','PASSWORD_CHANGE')` | NO | | 용도 |
| `otp_hash` | `varchar(255)` | NO | | OTP 해시값 |
| `expires_at` / `consumed_at` | `timestamp` | - | | 만료일시 / 사용일시 |
## trusted_devices (신뢰 기기)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `user_agent_hash` | `char(64)` | NO | | 브라우저 지문 해시 |
| `verified_at` / `last_used_at` | `timestamp` | NO | | 검증 / 최종 사용 일시 |
## join_requests (회사 가입 신청)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `int(11)` | NO | PRI | `auto_increment` |
| `user_id` / `company_id` | `int(11)` | NO | MUL | FK→users/companies |
| `status` | `enum('PENDING','APPROVED','REJECTED')` | NO | | `PENDING` |
| `requested_at` / `reviewed_at` | `timestamp` | - | | 신청 / 검토 일시 |
| `reviewed_by` | `int(11)` | YES | MUL | FK→users.id (검토자) |
**사용처:** [[B01_Dashboard/B01_backend|B01_Dashboard]] (`B01_Dashboard_Repository.py:378` 가입 신청·처리)
## user_consents (약관 동의)
| 컬럼 | 타입 | Null | Key | Default / Extra |
|---|---|---|---|---|
| `id` | `bigint(20)` | NO | PRI | `auto_increment` |
| `user_id` | `int(11)` | NO | MUL | FK→users.id |
| `terms_version` | `varchar(20)` | NO | | 동의한 약관 버전 |
| `terms_agreed` / `privacy_agreed` | `tinyint(1)` | NO | | 필수 동의여부 (1=동의) |
| `marketing_agreed` | `tinyint(1)` | NO | | `0` (선택 마케팅동의) |
| `agreed_at` | `timestamp` | NO | | 동의 일시 |
+36
View File
@@ -0,0 +1,36 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]"]
last_updated: 2026-07-12
source: raw/guidelines/agent.md, raw/guidelines/migration_plan.md
---
# 외부 라이브러리 의존성
## 백엔드 (Python 3.12.7)
| 라이브러리 | 용도 | 사용 페이지 |
|---|---|---|
| FastAPI / Pydantic | API 프레임워크 / 요청·응답 검증 | 전체; [[B01_Dashboard/B01_backend|B01_Dashboard]] 라우터·요청 모델 |
| Trimesh | 3D 메시 처리 | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] (모델 생성) |
| Whitebox | 지형 분석(D8 흐름누적 등) | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] (skeleton) |
| Geopandas | 공간 데이터프레임 | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| Shapely | 기하 연산 (JSON 파싱 후) | [[B05_wf2_Route/B05_backend|B05_wf2_Route]], [[B06_wf3_ProfileCross/B06_backend|B06_wf3_ProfileCross]] |
| NumPy | 격자·통계·비용면 및 LAS 통계 계산 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| SciPy | 수치 보간·계산 보조 | [[B05_wf2_Route/B05_backend|B05_wf2_Route]] |
| Rasterio | 래스터(DEM/TIF) 입출력·메타데이터 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] |
| Laspy | LAS/LAZ 포인트클라우드 파싱 | [[B03_FileInput/B03_backend|B03_FileInput]], [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] |
| Pyproj | PRJ/TIF 좌표계 해석 | [[B03_FileInput/B03_backend|B03_FileInput]] |
| aiomysql | 비동기 MariaDB 드라이버 (Raw SQL) | 전체 |
| psutil | CPU·메모리 사용률 계측 | [[B01_Dashboard/B01_backend|B01_Dashboard]] 리소스 현황 |
## 프론트엔드 (TypeScript/Node.js)
| 항목 | 용도 | 사용처 |
|---|---|---|
| **three.js** (WebGL) | 3D 뷰어(WebGLRenderer, OrbitControls, Points/Mesh) | [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] 구현 확인. B05/B06은 각 페이지에서 미구현 확인 |
| Vite | 빌드 도구 | 전 프론트 |
> ⚠️ B04는 three.js 기반 3D 뷰어가 구현되어 있고, B05/B06은 현재 문서 기준 WebCAD 노선·프로필 뷰어가 미구현이다.
## 선정 원칙 (agent.md 3절)
프로젝트에 맞춰 호환 라이브러리 선정, 불필요한 함수 작성 최소화. `asyncmy` 대신 `aiomysql` 채택 이유: Cython 빌드가 Windows 환경에서 불필요.
+19
View File
@@ -0,0 +1,19 @@
---
type: concept
status: stable
related_pages: []
last_updated: 2026-07-12
source: raw/guidelines/backend.md
---
# 공통 스키마 (Pydantic 요청/응답 규칙)
## 검증 원칙 (backend.md 4절)
- 모든 JSON 요청은 라우터 진입 전 Pydantic 모델로 타입·범위 검증.
- 모든 지형/메쉬 연산은 `try-except` 필수.
- 에러 응답 표준 포맷: `{"status": "error", "message": "원인"}` — 전 라우터 공통, 프론트도 동일 포맷 기대.
## 명명 규칙
파일명 `[폴더명]_Schema.py`, 페이지 폴더 내부 배치 (전역 공유 스키마 폴더 없음 — 필요 시 여기에 항목 추가).
> 현재 raw 자료에서 페이지 간 공유되는 구체적 Pydantic 모델(예: 공통 페이지네이션, 공통 좌표 타입)은 확인되지 않음. 발견 시 이 페이지에 추가.
+36
View File
@@ -0,0 +1,36 @@
---
type: concept
status: stable
related_pages: ["[[B02_ProjRegister/B02_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]", "[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/guidelines/structure.md, raw/guidelines/db_schema.md, raw/guidelines/backend.md, raw/guidelines/agent.md
---
# 저장 경로 규칙 (Workflow-based Folder Structure)
## 경로 패턴
```
storage/{company_slug}/{user_slug}/{project_id}/
├── B03_FileInput/input/{file_type}/ WF0 원본 입력
├── B04_wf1_Surface/processed/, models/ WF1 변환·모델
├── B05_wf2_Route/route/ WF2 경로
├── B06_wf3_ProfileCross/longitudinal/, cross_sections/ WF3 종횡단
├── B07_wf4_DesignDetail/structures/ WF4 구조물
├── B08_wf5_Quantity/quantities/ WF5 수량
└── B09_wf6_Estimation/v1/, v2/, ... WF6 산출물 (버전은 여기서만)
```
## 원칙 (backend.md 3절)
1. **하드코딩 금지** — 경로는 `config/config_system.py`에서 import.
2. **물리 파일은 DB 저장 금지** — 대용량 파일은 파일시스템, DB에는 경로만.
3. **단계별 루트 강제** — 각 B0x 폴더명은 실제 워크플로우 페이지명과 동일해야 함. `raw/`, `processed/`, `computed/`를 프로젝트 루트 공용 폴더로 만들지 않음.
4. **경로 생성 책임 분리** — 페이지별 백엔드는 자기 단계 폴더만 생성·수정. 다른 단계 산출물을 직접 삭제·덮어쓰지 않고 stale로 전파 → [[workflow_state]].
5. **상대 경로만 DB 저장** — 프로젝트 루트 기준 상대경로. 사용자 입력 경로 직접 결합 금지(경로 이탈 방지), 절대경로 저장 금지.
## DB 컬럼 ↔ 실제 경로 매핑
상세 표 → [[db_schema/overview]] "파일 경로 추적 컬럼"
## 파일명 규칙 (structure.md 1절)
- 페이지 폴더: 로그인 전 `A0x_`, 로그인 후 `B0x_`, 워크플로우는 `wf[번호]` 접두사
- 파일명: `폴더명(페이지명)_기능_기타사양.확장자`
- 공통 유틸: `common_util/common_util_기능.확장자`
+85
View File
@@ -0,0 +1,85 @@
---
status: stable
related_pages: ["[[A01_Home/A01_frontend]]", "[[A02_ProgDetail/A02_frontend]]", "[[B01_Dashboard/B01_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]"]
last_updated: 2026-07-12
source: ui_template/ 공통 UI 소스 코드 및 스타일시트
---
# UI Templates — Localization & Components
프론트엔드 공통 UI 요소: 다국어(i18n), 공통 컴포넌트, 테마 스타일. 디자인 시스템: [[design.md]]
## ui_template_locale.ts (다국어 관리)
**파일**: `ui_template/ui_template_locale.ts`
### 핵심 기능
| 항목 | 위치 | 역할 |
|---|---|---|
| `currentLanguageIndex` | `ui_template/ui_template_locale.ts:12` | 현재 활성화된 언어 인덱스 (ko=0, en=1) |
| `ui_locales` | `ui_template/ui_template_locale.ts:25` | 다국어 문구가 등록된 2차원 사전 객체 |
| `t(key)` | `ui_template/ui_template_locale.ts:152` | 지정된 키에 대응하는 현재 언어 텍스트 반환 헬퍼 |
### 사용처 (역참조)
- [[A01_Home/A01_frontend]] — MOCK_NEWS 태그, 섹션 제목
- [[A02_ProgDetail/A02_frontend]] — 워크플로우 스텝, 기술 카드
- [[a00_app_shell_framework]] — 헤더 네비, 언어 토글, 버튼 라벨
- 모든 A0x/B0x 페이지 — 동적 텍스트 로드
## ui_template_elements.ts (공통 컴포넌트)
**파일**: `ui_template/ui_template_elements.ts`
### 제공 컴포넌트
| 항목 | 위치 | 역할 |
|---|---|---|
| `createButton(opts)` | `ui_template/ui_template_elements.ts:60` | 버튼. variant: `filled`(기본)/`ghost`/`pill`/`danger` |
| `createInputField(opts)` | `ui_template/ui_template_elements.ts:102` | 입력필드(라벨+입력+에러슬롯), setError 핸들 반환 |
| `createSelectField(opts)` | `ui_template/ui_template_elements.ts:164` | 드롭다운 select 필드 |
| `createCard(opts)` | `ui_template/ui_template_elements.ts:209` | 카드(title + body 배열, raised 옵션) |
| `createTag(label, variant?)` | `ui_template/ui_template_elements.ts:228` | 태그. variant: `accent`/`neutral`(기본)/`success`/`warning`/`danger` |
| `showLoadingOverlay()` | `ui_template/ui_template_elements.ts:253` | 로딩 스피너 오버레이 표시(참조카운트) |
| `hideLoadingOverlay()` | `ui_template/ui_template_elements.ts:259` | 로딩 오버레이 해제 |
| `showToast(msg, kind?, ms?)` | `ui_template/ui_template_elements.ts:282` | 토스트 알림. kind: success/error/info/warning |
| `createWorkflowShell(opts)` | `ui_template/ui_template_elements.ts:315` | 워크플로우 3단 레이아웃(헤더+좌측패널+우측뷰어) |
| `createLineChart(opts)` | `ui_template/ui_template_elements.ts:402` | 시계열 스플라인 차트(인라인 SVG, 외부 라이브러리 없음) |
| `injectBaseStyles()` | `ui_template/ui_template_elements.ts:843` | 공통 컴포넌트 기본 스타일 1회 주입 |
### 사용처 (역참조)
- [[A01_Home/A01_frontend]] — createButton, createCard, createTag
- [[A02_ProgDetail/A02_frontend]] — createButton, createCard
- [[A03_CompDetail/A03_frontend]] — createCard
- [[A04_NewsHistory/A04_frontend]] — createTag
- [[A05_EduDetail/A05_frontend]] — createButton, createCard
- [[a00_app_shell_framework]] — createButton(헤더 네비/인증), createWorkflowShell
- B그룹 모든 페이지 — createWorkflowShell, createLineChart(B01 대시보드)
## theme.css (스타일 변수)
**파일**: `ui_template/theme.css`
### 핵심 변수
| 항목 | 위치 | 역할 |
|---|---|---|
| `--color-primary`, `--color-primary-text` | `ui_template/theme.css:10` | 기본 색상 및 기본 텍스트 색상 |
| `--color-canvas`, `--color-paper` | `ui_template/theme.css:15` | 기본 캔버스 배경 및 레이어 카드 배경 |
| `--color-deep-iris`, `--color-mist-violet` | `ui_template/theme.css:22` | 라벤더 그라디언트 및 브랜드 서브 컬러 |
| `--spacing-8`, `--spacing-16` 등 | `ui_template/theme.css:45` | 기본 마진/패딩 간격 시스템 (8px 배수) |
| `--text-heading-lg`, `--text-body` 등 | `ui_template/theme.css:70` | 타이포그래피 크기 및 라인하이트 스케일 |
| `--radius-cards`, `--radius-icons` | `ui_template/theme.css:110` | 8px 등의 둥근 테두리 곡률 반지름 변수 |
| `--z-header` | `ui_template/theme.css:125` | 전역 헤더 고정용 z-index 인덱스 값 |
## 제약 (backend.md §1)
1. **다국어**: 모든 UI 문구는 반드시 `ui_locales` 등록 후 `L(key)` 또는 `t(key)`로만 참조
2. **스타일**: CSS는 `theme.css` 변수만 사용, 하드코드 색상/단위 금지
3. **컴포넌트 재사용**: `createButton`, `createCard`, `createTag`, `createWorkflowShell` 우선 사용
4. **명명**: 이벤트 핸들러는 `on[페이지]_[기능]_[액션]` 규칙 준수
## 의존성
- **Locale**: 모든 페이지에서 `currentLanguageIndex` 조회
- **요소**: 페이지별로 필요한 컴포넌트만 import
+74
View File
@@ -0,0 +1,74 @@
---
type: concept
status: stable
related_pages: ["[[B01_Dashboard/B01_backend]]", "[[B02_ProjRegister/B02_backend]]", "[[B03_FileInput/B03_backend]]", "[[B04_wf1_Surface/B04_backend]]", "[[B05_wf2_Route/B05_backend]]", "[[B06_wf3_ProfileCross/B06_backend]]", "[[B07_wf4_DesignDetail/B07_frontend]]", "[[B08_wf5_Quantity/B08_frontend]]", "[[B09_wf6_Estimation/B09_frontend]]"]
last_updated: 2026-07-12
source: raw/plans/plan_workflow_state_management.md (계획, 2026-07-10) + 실 코드/DB 직접 확인 (2026-07-12)
---
# Workflow 상태 관리 — 구현 완료 (실 DB 확인됨)
> **status: stable.** 2026-07-12 실 코드 전수 확인 + DB 직접 접속(`SHOW TABLES`/`DESCRIBE`)으로 검증. 계획서(2026-07-10)의 설계가 **B01~B06 전 단계에 이미 적용 완료**됨. 정식 검증보고서는 없지만 코드·DB 실측이 계획과 일치해 stable로 채택. 남은 잔재 1건은 아래 참고.
## SSOT: `project_workflow_stages` 테이블 (실 DB 확인)
| 컬럼 | 타입 |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| id | INT PK |
| project_id | CHAR(36), FK |
| stage_no | TINYINT (0~6) |
| stage_key | VARCHAR(30) — FILE_INPUT/WF1_SURFACE/WF2_ROUTE/WF3_PROFILE_CROSS/WF4_DESIGN_DETAIL/WF5_QUANTITY/WF6_ESTIMATION |
| state | ENUM('NOT_STARTED','IN_PROGRESS','COMPLETE','FAILED','STALE') |
| progress_percent | TINYINT |
| params | LONGTEXT(JSON) — 재실행 시 폼 프리필용 |
| message, started_at, completed_at, updated_at | 진행/이력 |
생성 SQL: `db_management/006_workflow_state.sql`, 백필: `db_management/migrate_workflow_state.py` (기존 프로젝트 `projects.status` 해석 후 시드)
## 공통 유틸 `common_util/common_util_workflow_state.py`
| 함수 | 역할 |
|---|---|
| `initialize_project_stages(cursor, project_id)` | 프로젝트 생성 시 7행 NOT_STARTED 시드 |
| `start_stage(cursor, project_id, stage_no, params)` | IN_PROGRESS 전이 + N+1~6의 COMPLETE를 STALE로 전파 + `projects.status` 캐시 갱신 |
| `complete_stage(cursor, project_id, stage_no)` | COMPLETE 전이 (stage 6 완료 시 `projects.status='DONE'`) |
| `fail_stage(cursor, project_id, stage_no, message)` | FAILED 전이 |
| `update_stage_progress(cursor, project_id, stage_no, progress)` | 진행률만 갱신 |
| `get_workflow_state(cursor, project_id)` | 단건 조회, `current_stage` 계산 포함 |
## 실 연동 현황 (파일:줄번호)
| 페이지 | 위치 | 연동 stage_no |
|---|---|---|
| [[B02_ProjRegister/B02_backend|B02_ProjRegister]] | `B02_ProjRegister_Repository.py:90` `initialize_project_stages` 호출 | 시드(0~6) |
| [[B03_FileInput/B03_backend|B03_FileInput]] | `B03_FileInput_Router.py:175,224,256,347,570` | stage 0, 1 |
| [[B04_wf1_Surface/B04_backend|B04_wf1_Surface]] | `B04_wf1_Surface_Router.py:168,209,227,233` | stage 1 |
| [[B05_wf2_Route/B05_backend|B05_wf2_Route]] | `B05_wf2_Route_Router.py:51,96,112,117,122,128,152` | stage 2 |
| [[B06_wf3_ProfileCross/B06_backend|B06_wf3_ProfileCross]] | `B06_wf3_ProfileCross_Router.py:68,111,127,132,137,143,187` | stage 3 |
| [[B01_Dashboard/B01_backend|B01_Dashboard]] | `B01_Dashboard_Repository.py:83` `get_workflow_states_for_projects()` (배치 조회, 프로젝트 목록에 `workflow_state` 필드 포함) | 조회 전용 |
## 조회 API (실제 존재, 계획서와 경로 일치)
`GET /{project_id}/workflow-state``B03_FileInput_Router.py:667`
```json
{"status": "success", "workflow_state": {"project_id": "...", "current_stage": 2, "stages": [...]}}
```
실제 전체 경로는 B03 라우터 prefix를 포함한 `/api/projects/{project_id}/workflow-state`이다.
## 상태 전이 (구현대로)
```
NOT_STARTED → IN_PROGRESS → COMPLETE
└→ FAILED → (재실행) IN_PROGRESS
COMPLETE → (하위 단계 재작업) → STALE → (재실행) IN_PROGRESS
```
stage N이 IN_PROGRESS 전이 시 N+1~6의 COMPLETE를 전부 STALE로 전파 (`start_stage` 내부 UPDATE 쿼리로 확인됨).
## projects.status 문자열 캐시 (호환용, 계획서 방침대로 유지 중)
`start_stage`/`complete_stage`/`fail_stage``project_workflow_stages``projects.status`**동시에** 갱신 (`WF{n}_ANALYZING`/`WF{n}_COMPLETE`/`WF{n}_FAILED`, stage6 COMPLETE 시 `DONE`).
## B04 상태 조회 — 레거시 폴백 (당초 "잔재"로 표기했으나 정정)
`get_wf1_analysis_status()`([[B04_wf1_Surface/B04_backend]], `B04_wf1_Surface_Router.py:372`)는
`project_workflow_stages`(SSOT)를 **우선** 조회하고, 행이 없을 때만(백필 미작동 대비)
`projects.status` 문자열 비교로 폴백함(416 `WF1_COMPLETE`, 420 `WF1_ANALYZING`, `:412-427`).
→ B04 실 코드 확인 결과 **정상 경로가 아닌 방어적 폴백**. SSOT 안정화 후 제거 가능하나 현재는 안전장치로 유효.
별개로 `update_project_status()`(`:76`)는 정의만 있고 미호출(죽은 코드) → 정리 권장.
## 미해결 사항 (계획서 원문, 아직 결정 안 됨)
- `projects.status` 완전 제거 여부 — 현재는 유지 중(위 확인)
- STALE 하위 결과 파일: 즉시 삭제 vs 재실행 시 덮어쓰기 — 코드 레벨 확인 안 함, 별도 점검 필요
@@ -0,0 +1,48 @@
{
"0": "DB: 로그/모니터링 테이블",
"1": "auth_rbac.md",
"2": "workflow_state.md",
"3": "개념 (Concepts)",
"4": "Wiki Log",
"5": "ui_templates.md",
"6": "A09_Security — Backend",
"7": "A00_Common — App Shell Framework",
"8": "UI Templates — Localization & Components",
"9": "A04_NewsHistory — Frontend",
"10": "A07_Register — Frontend",
"11": "A08_Support — Backend",
"12": "A08_Support — Frontend",
"13": "B02_ProjRegister — Frontend",
"14": "인증 / RBAC",
"15": "A01_Home — Frontend",
"16": "A05_EduDetail — Frontend",
"17": "A06_Login — Backend",
"18": "A06_Login — Frontend",
"19": "A09_Security — Frontend",
"20": "B02_ProjRegister — Backend",
"21": "Workflow 상태 관리 — 구현 완료 (실 DB 확인됨)",
"22": "A03_CompDetail — Frontend",
"23": "B04_wf1_Surface — Backend",
"24": "DB: 경로/종횡단 테이블",
"25": "A07_Register — Backend",
"26": "B04_wf1_Surface — Frontend",
"27": "B07_wf4_DesignDetail — Frontend",
"28": "B09_wf6_Estimation — Frontend",
"29": "B05_wf2_Route — Backend",
"30": "A01_Home — 세부 구현",
"31": "A02_ProgDetail — 세부 구현",
"32": "A02_ProgDetail — Frontend",
"33": "B05_wf2_Route — Frontend",
"34": "B02_ProjRegister — DB",
"35": "B04_wf1_Surface — DB",
"36": "B04_wf1_Surface — Dependencies",
"37": "B06_wf3_ProfileCross — Frontend",
"38": "wiki/ 폴더 규칙",
"39": "wiki/ 폴더 규칙",
"40": "B04_wf1_Surface — API",
"41": "B05_wf2_Route — DB 사용 관계",
"42": "B06_wf3_ProfileCross — DB 사용 관계",
"43": "B01_Dashboard — DB 사용 관계",
"44": "B03_FileInput — DB 사용 관계",
"45": "B08_wf5_Quantity — DB"
}
+1
View File
@@ -0,0 +1 @@
.
+275
View File
@@ -0,0 +1,275 @@
# Graph Report - wiki (2026-07-12)
## Corpus Check
- 69 files · ~23,462 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 530 nodes · 511 edges · 46 communities (43 shown, 3 thin omitted)
- Extraction: 100% EXTRACTED · 0% INFERRED · 0% AMBIGUOUS
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `35c65c92`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- DB: 로그/모니터링 테이블
- auth_rbac.md
- workflow_state.md
- 개념 (Concepts)
- Wiki Log
- ui_templates.md
- A09_Security — Backend
- A00_Common — App Shell Framework
- UI Templates — Localization & Components
- A04_NewsHistory — Frontend
- A07_Register — Frontend
- A08_Support — Backend
- A08_Support — Frontend
- B02_ProjRegister — Frontend
- 인증 / RBAC
- A01_Home — Frontend
- A05_EduDetail — Frontend
- A06_Login — Backend
- A06_Login — Frontend
- A09_Security — Frontend
- B02_ProjRegister — Backend
- Workflow 상태 관리 — 구현 완료 (실 DB 확인됨)
- A03_CompDetail — Frontend
- B04_wf1_Surface — Backend
- DB: 경로/종횡단 테이블
- A07_Register — Backend
- B04_wf1_Surface — Frontend
- B07_wf4_DesignDetail — Frontend
- B09_wf6_Estimation — Frontend
- B05_wf2_Route — Backend
- A01_Home — 세부 구현
- A02_ProgDetail — 세부 구현
- A02_ProgDetail — Frontend
- B05_wf2_Route — Frontend
- B02_ProjRegister — DB
- B04_wf1_Surface — DB
- B04_wf1_Surface — Dependencies
- B06_wf3_ProfileCross — Frontend
- wiki/ 폴더 규칙
- wiki/ 폴더 규칙
- B04_wf1_Surface — API
- B05_wf2_Route — DB 사용 관계
- B06_wf3_ProfileCross — DB 사용 관계
- B01_Dashboard — DB 사용 관계
- B03_FileInput — DB 사용 관계
- B08_wf5_Quantity — DB
## God Nodes (most connected - your core abstractions)
1. `Wiki Log` - 33 edges
2. `개념 (Concepts)` - 12 edges
3. `DB: 로그/모니터링 테이블` - 11 edges
4. `인증 / RBAC` - 10 edges
5. `페이지 — 로그인 후 워크플로우 (B01~B09)` - 10 edges
6. `페이지 — 로그인 전 (A01~A09)` - 10 edges
7. `A07_Register — Frontend` - 10 edges
8. `A08_Support — Backend` - 10 edges
9. `A08_Support — Frontend` - 10 edges
10. `B02_ProjRegister — Frontend` - 10 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
## Communities (46 total, 3 thin omitted)
### Community 0 - "DB: 로그/모니터링 테이블"
Cohesion: 0.05
Nodes (38): DB: 파일/지표면분석 테이블, input_files.status 값, input_files (입력 원본 파일), processed_point_cloud (필터/변환 포인트클라우드), surface_models.status / processed_point_cloud.status 값, surface_models (지표면 모델 및 등고선), terrain_layers (지형 레이어), upload_chunks (업로드 청크 데이터) (+30 more)
### Community 1 - "auth_rbac.md"
Cohesion: 0.05
Nodes (33): API 공통 (여러 페이지가 공유하는 엔드포인트), 공통 오류 응답 포맷 (전 라우터), 워크플로우 상태 조회, 폴링 패턴 (legacy workflow.json 설계; 현재 구현은 workflow-state API 사용), companies (회사), DB: 사용자/인증/조직 테이블, email_otps (이메일 OTP), join_requests (회사 가입 신청) (+25 more)
### Community 2 - "workflow_state.md"
Cohesion: 0.06
Nodes (28): 공통 유틸 (common_util/), 리소스 모니터링 (common_util_resource_monitor.py), 이메일 발송 (common_util_email.py), 백엔드 (Python 3.12.7), 선정 원칙 (agent.md 3절), 외부 라이브러리 의존성, 프론트엔드 (TypeScript/Node.js), DB 컬럼 ↔ 실제 경로 매핑 (+20 more)
### Community 3 - "개념 (Concepts)"
Cohesion: 0.06
Nodes (35): [[a00_app_shell_framework]], [[a00_app_shell_framework_scaffold]], [[A01_Home/A01_frontend|A01_Home]], [[A02_ProgDetail/A02_frontend|A02_ProgDetail]], [[A03_CompDetail/A03_frontend|A03_CompDetail]], [[A04_NewsHistory/A04_frontend|A04_NewsHistory]], [[A05_EduDetail/A05_frontend|A05_EduDetail]], [[A06_Login/A06_backend|A06_Login]] (+27 more)
### Community 4 - "Wiki Log"
Cohesion: 0.06
Nodes (33): [2026-07-12] ingest | A01_Home 실 코드 반영, [2026-07-12] ingest | A02_ProgDetail + A00_Common Framework, [2026-07-12] ingest | A03_CompDetail, [2026-07-12] ingest | A04_NewsHistory, [2026-07-12] ingest | A05_EduDetail, [2026-07-12] ingest | A06_Login (backend + frontend 상세화), [2026-07-12] ingest | A07_Register (backend + frontend 상세화), [2026-07-12] ingest | A08_Support (backend + frontend 상세화) (+25 more)
### Community 5 - "ui_templates.md"
Cohesion: 0.07
Nodes (23): A00_Common — 스캐폴드·CSS·종속성, b_page_scaffold, CSS 인젝션, 사용처, 종속성, B01_Dashboard — Dependencies, 프로젝트 공통 모듈, B01_Dashboard — Frontend (+15 more)
### Community 6 - "A09_Security — Backend"
Cohesion: 0.17
Nodes (11): A09_Security — Backend, API 엔드포인트, DB 저장 (activity_logs), 권한 헬퍼, 마스터(회사 관리자) 전용 — `require_master`, 시스템 관리자 전용 — `require_system_admin`, 요청 스키마 (Pydantic), 의존성 (공통 유틸) (+3 more)
### Community 7 - "A00_Common — App Shell Framework"
Cohesion: 0.18
Nodes (11): A00_Common — App Shell Framework, app_shell 구성요소, router 라우팅 테이블, 로그아웃 (onApp_Logout_Click), 세부 스캐폴드·CSS·종속성, 인증 가드, 인증 버튼 (비동기 fillAuthActions), 테마 & 언어 관리 (+3 more)
### Community 8 - "UI Templates — Localization & Components"
Cohesion: 0.18
Nodes (11): theme.css (스타일 변수), ui_template_elements.ts (공통 컴포넌트), ui_template_locale.ts (다국어 관리), UI Templates — Localization & Components, 사용처 (역참조), 사용처 (역참조), 의존성, 제공 컴포넌트 (+3 more)
### Community 9 - "A04_NewsHistory — Frontend"
Cohesion: 0.18
Nodes (10): A04_NewsHistory — Frontend, CSS 클래스 구조, Mock 데이터 구조, 로컬라이제이션, 미해결 사항, 반응형, 스타일 (CSS), 의존성 (+2 more)
### Community 10 - "A07_Register — Frontend"
Cohesion: 0.18
Nodes (10): A07_Register — Frontend, API 클라이언트 함수, 로컬라이제이션, 스타일 (CSS), 약관 동의 (아코디언), 의존성, 이벤트 핸들러, 제출 로직 (2단계 폼 전환) (+2 more)
### Community 11 - "A08_Support — Backend"
Cohesion: 0.18
Nodes (10): A08_Support — Backend, API 엔드포인트, ⚠️ DB 스키마 불일치 (확인 필요), DB 저장 컬럼 (실 코드 INSERT 기준), 내부 헬퍼, 요청 스키마 (Pydantic), 의존성 (공통 유틸), 접수 로직 (+2 more)
### Community 12 - "A08_Support — Frontend"
Cohesion: 0.18
Nodes (10): A08_Support — Frontend, 로컬라이제이션, 세션 자동 채움, 스타일 (CSS), 의존성, 이벤트 핸들러, 제출 로직, 참고 (+2 more)
### Community 13 - "B02_ProjRegister — Frontend"
Cohesion: 0.18
Nodes (10): B02_ProjRegister — Frontend, 로컬라이제이션, 스타일 (CSS), 의존성, 이벤트 핸들러, 입력 필드, 제출 로직, 참고 (+2 more)
### Community 14 - "인증 / RBAC"
Cohesion: 0.20
Nodes (10): OTP / 비밀번호, ⚠️ 계획서-실코드 불일치 (A그룹 인제스트 발견), 권한 검증 헬퍼 (B01_Dashboard), 라우팅 가드 (frontend.md 5.2), 사용자 상태 생명주기, 사용처 (역참조), 세션 인증 (backend.md 6.3), 역할 (users.role) (+2 more)
### Community 15 - "A01_Home — Frontend"
Cohesion: 0.20
Nodes (9): A01_Home — Frontend, Hero 섹션, 로컬라이제이션, 세부 구현, 이벤트 핸들러, 주요 기능 섹션, 최신 소식 섹션, 컴포넌트 (+1 more)
### Community 16 - "A05_EduDetail — Frontend"
Cohesion: 0.20
Nodes (9): A05_EduDetail — Frontend, CSS 클래스 구조, 로컬라이제이션, 반응형, 스타일 (CSS), 의존성, 이벤트 핸들러, 컴포넌트 (섹션 빌더) (+1 more)
### Community 17 - "A06_Login — Backend"
Cohesion: 0.20
Nodes (9): A06_Login — Backend, API 엔드포인트, ⚠️ 계획서와 실 코드 불일치, 내부 헬퍼, 로그인 로직 흐름, 보안 정책 (실 코드 기준), 요청 스키마 (Pydantic), 의존성 (공통 유틸) (+1 more)
### Community 18 - "A06_Login — Frontend"
Cohesion: 0.20
Nodes (9): A06_Login — Frontend, API 클라이언트 함수, 로컬라이제이션, 스타일 (CSS), 의존성, 이벤트 핸들러, 제출 로직 (2단계 폼 전환), 컴포넌트 / 함수 (+1 more)
### Community 19 - "A09_Security — Frontend"
Cohesion: 0.20
Nodes (9): A09_Security — Frontend, API 클라이언트 함수 (⚠️ 미사용, 정의만), 로컬라이제이션, 스타일 (CSS), 약관 데이터 (A09_Security_Terms.ts), ⚠️ 약관 텍스트와 실 코드 불일치, 의존성, 컴포넌트 / 함수 (+1 more)
### Community 20 - "B02_ProjRegister — Backend"
Cohesion: 0.20
Nodes (9): API 엔드포인트, B02_ProjRegister — Backend, DB 저장 컬럼 (projects INSERT), 생성 로직 (create_project 트랜잭션), 요청/응답 스키마 (Pydantic), 의존성 (공통 유틸), 참고, 파일 구조 (+1 more)
### Community 21 - "Workflow 상태 관리 — 구현 완료 (실 DB 확인됨)"
Cohesion: 0.22
Nodes (9): B04 상태 조회 — 레거시 폴백 (당초 "잔재"로 표기했으나 정정), projects.status 문자열 캐시 (호환용, 계획서 방침대로 유지 중), SSOT: `project_workflow_stages` 테이블 (실 DB 확인), Workflow 상태 관리 — 구현 완료 (실 DB 확인됨), 공통 유틸 `common_util/common_util_workflow_state.py`, 미해결 사항 (계획서 원문, 아직 결정 안 됨), 상태 전이 (구현대로), 실 연동 현황 (파일:줄번호) (+1 more)
### Community 22 - "A03_CompDetail — Frontend"
Cohesion: 0.22
Nodes (8): A03_CompDetail — Frontend, CSS 클래스 구조, 로컬라이제이션, 반응형, 스타일 (CSS), 의존성, 컴포넌트 (섹션 빌더), 파일 구조
### Community 23 - "B04_wf1_Surface — Backend"
Cohesion: 0.22
Nodes (8): B04_wf1_Surface — Backend, ⚠️ 레거시 폴백 (416,420줄), 엔진 서브모듈, 워크플로우 상태 전이, 의존성, 주요 함수 (Router), 책임, 파일 구성 (오케스트레이터/저장소/라우터)
### Community 24 - "DB: 경로/종횡단 테이블"
Cohesion: 0.25
Nodes (8): cross_sections.data.structures, cross_sections (횡단면 설계), DB: 경로/종횡단 테이블, longitudinal_sections (종단면 설계), route_points (경로 좌표점), route_statistics (노선 통계), routes.status 흐름, routes (노선 경로)
### Community 25 - "A07_Register — Backend"
Cohesion: 0.25
Nodes (7): A07_Register — Backend, API 엔드포인트, 가입 로직 흐름, 요청 스키마 (Pydantic), 의존성 (공통 유틸), 참고, 파일 구조
### Community 26 - "B04_wf1_Surface — Frontend"
Cohesion: 0.25
Nodes (7): API 클라이언트 함수, B04_wf1_Surface — Frontend, ⚠️ 계획서와 실 코드 불일치 (3D 뷰어), 의존성, 처리 흐름, 컴포넌트 / 함수, 파일 구조
### Community 27 - "B07_wf4_DesignDetail — Frontend"
Cohesion: 0.25
Nodes (7): B07_wf4_DesignDetail — Frontend, 로컬라이제이션, 백엔드/DB — 미착수 (계획적 범위 제외), 의존성, 참고, 컴포넌트 / 함수, 파일 구조
### Community 28 - "B09_wf6_Estimation — Frontend"
Cohesion: 0.25
Nodes (7): B09_wf6_Estimation — Frontend, 로컬라이제이션, 백엔드/DB — 미착수, 의존성, 참고, 컴포넌트 / 함수, 파일 구조
### Community 29 - "B05_wf2_Route — Backend"
Cohesion: 0.29
Nodes (6): B05_wf2_Route — Backend, 경로 계산 엔진, 기술부채, 기하·결과 변환, 라우터·workflow, 요청·응답 모델
### Community 30 - "A01_Home — 세부 구현"
Cohesion: 0.33
Nodes (5): A01_Home — 세부 구현, 데이터 흐름, 미해결, 스타일, 의존성
### Community 31 - "A02_ProgDetail — 세부 구현"
Cohesion: 0.33
Nodes (5): A02_ProgDetail — 세부 구현, 데이터 흐름, 미해결 / 특이사항, 스타일 (CSS), 의존성
### Community 32 - "A02_ProgDetail — Frontend"
Cohesion: 0.33
Nodes (5): A02_ProgDetail — Frontend, 구조, 세부 구현, 제약 준수, 컴포넌트 분석
### Community 33 - "B05_wf2_Route — Frontend"
Cohesion: 0.33
Nodes (5): API 클라이언트, B05_wf2_Route — Frontend, 기술부채, 입력 그룹, 화면과 상호작용
### Community 34 - "B02_ProjRegister — DB"
Cohesion: 0.40
Nodes (4): B02_ProjRegister — DB, 쓰는 테이블, 저장소(파일시스템), 참고 (계획 당시 의도)
### Community 35 - "B04_wf1_Surface — DB"
Cohesion: 0.40
Nodes (4): B04_wf1_Surface — DB, Repository 함수, 쓰는 테이블, 참고
### Community 36 - "B04_wf1_Surface — Dependencies"
Cohesion: 0.40
Nodes (4): B04_wf1_Surface — Dependencies, ⚠️ dependencies concept 정정 필요, 백엔드 (Python), 프론트엔드 (TypeScript)
### Community 37 - "B06_wf3_ProfileCross — Frontend"
Cohesion: 0.40
Nodes (4): API 클라이언트, B06_wf3_ProfileCross — Frontend, 입력 옵션, 화면·workflow
### Community 38 - "wiki/ 폴더 규칙"
Cohesion: 0.50
Nodes (3): wiki/ 폴더 규칙, 구조, 작성 규칙
### Community 39 - "wiki/ 폴더 규칙"
Cohesion: 0.50
Nodes (3): wiki/ 폴더 규칙, 구조, 작성 규칙
### Community 40 - "B04_wf1_Surface — API"
Cohesion: 0.50
Nodes (3): B04_wf1_Surface — API, 엔드포인트, 요청/응답 스키마 (Pydantic)
### Community 41 - "B05_wf2_Route — DB 사용 관계"
Cohesion: 0.50
Nodes (3): B05_wf2_Route — DB 사용 관계, Repository 함수, 저장 경로
### Community 42 - "B06_wf3_ProfileCross — DB 사용 관계"
Cohesion: 0.50
Nodes (3): B06_wf3_ProfileCross — DB 사용 관계, Repository 함수, 파일 경로
## Knowledge Gaps
- **379 isolated node(s):** `구조`, `작성 규칙`, `구조`, `작성 규칙`, `파일 구성` (+374 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **3 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `UI Templates — Localization & Components` connect `UI Templates — Localization & Components` to `ui_templates.md`?**
_High betweenness centrality (0.015) - this node is a cross-community bridge._
- **Why does `A00_Common — App Shell Framework` connect `A00_Common — App Shell Framework` to `ui_templates.md`?**
_High betweenness centrality (0.014) - this node is a cross-community bridge._
- **What connects `구조`, `작성 규칙`, `구조` to the rest of the system?**
_379 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `DB: 로그/모니터링 테이블` be split into smaller, more focused modules?**
_Cohesion score 0.045454545454545456 - nodes in this community are weakly interconnected._
- **Should `auth_rbac.md` be split into smaller, more focused modules?**
_Cohesion score 0.05110336817653891 - nodes in this community are weakly interconnected._
- **Should `workflow_state.md` be split into smaller, more focused modules?**
_Cohesion score 0.06156156156156156 - nodes in this community are weakly interconnected._
- **Should `개념 (Concepts)` be split into smaller, more focused modules?**
_Cohesion score 0.05555555555555555 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "label": "B03_db.md", "file_type": "document", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_b03_fileinput_db_\uc0ac\uc6a9_\uad00\uacc4", "label": "B03_FileInput \u2014 DB \uc0ac\uc6a9 \uad00\uacc4", "file_type": "document", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_\ud30c\uc77c_\uacbd\ub85c", "label": "\ud30c\uc77c \uacbd\ub85c", "file_type": "document", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L22"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_fileinput_b03_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_fileinput_b03_api_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_b03_fileinput_db_\uc0ac\uc6a9_\uad00\uacc4", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_db_schema_files_surface_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_b03_fileinput_db_\uc0ac\uc6a9_\uad00\uacc4", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_\ud30c\uc77c_\uacbd\ub85c", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L22", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_storage_paths_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_db.md", "source_location": "L29", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_md", "label": "README.md", "file_type": "document", "source_file": "concepts/db_schema/unconfirmed/README.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_\ubbf8\ud655\uc815_\ud14c\uc774\ube14_\ubcf4\uad00\uc18c_unconfirmed_db_schemas", "label": "\ubbf8\ud655\uc815 \ud14c\uc774\ube14 \ubcf4\uad00\uc18c (Unconfirmed DB Schemas)", "file_type": "document", "source_file": "concepts/db_schema/unconfirmed/README.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_\uc6b4\uc601_\uaddc\uce59", "label": "\uc6b4\uc601 \uaddc\uce59", "file_type": "document", "source_file": "concepts/db_schema/unconfirmed/README.md", "source_location": "L5"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_\ubbf8\ud655\uc815_\ud14c\uc774\ube14_\ubcf4\uad00\uc18c_unconfirmed_db_schemas", "relation": "contains", "confidence": "EXTRACTED", "source_file": "concepts/db_schema/unconfirmed/README.md", "source_location": "L1", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_\ubbf8\ud655\uc815_\ud14c\uc774\ube14_\ubcf4\uad00\uc18c_unconfirmed_db_schemas", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_db_schema_unconfirmed_readme_\uc6b4\uc601_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "concepts/db_schema/unconfirmed/README.md", "source_location": "L5", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "label": "B06_dependencies.md", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_b06_wf3_profilecross_dependencies", "label": "B06_wf3_ProfileCross \u2014 Dependencies", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_\uacf5\ud1b5_\ubaa8\ub4c8", "label": "\uacf5\ud1b5 \ubaa8\ub4c8", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L22"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_wf3_profilecross_b06_frontend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_wf3_profilecross_b06_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_b06_wf3_profilecross_dependencies", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_dependencies_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_b06_wf3_profilecross_dependencies", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_\uacf5\ud1b5_\ubaa8\ub4c8", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L22", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_workflow_state_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L25", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_storage_paths_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_dependencies.md", "source_location": "L26", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "label": "B05_db.md", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_b05_wf2_route_db_\uc0ac\uc6a9_\uad00\uacc4", "label": "B05_wf2_Route \u2014 DB \uc0ac\uc6a9 \uad00\uacc4", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_repository_\ud568\uc218", "label": "Repository \ud568\uc218", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L20"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_\uc800\uc7a5_\uacbd\ub85c", "label": "\uc800\uc7a5 \uacbd\ub85c", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L30"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_wf2_route_b05_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_wf2_route_b05_api_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_b05_wf2_route_db_\uc0ac\uc6a9_\uad00\uacc4", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_db_schema_route_profile_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_b05_wf2_route_db_\uc0ac\uc6a9_\uad00\uacc4", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_repository_\ud568\uc218", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L20", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_b05_wf2_route_db_\uc0ac\uc6a9_\uad00\uacc4", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_\uc800\uc7a5_\uacbd\ub85c", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L30", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_storage_paths_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_db.md", "source_location": "L36", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "label": "B08_db.md", "file_type": "document", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_b08_wf5_quantity_db", "label": "B08_wf5_Quantity \u2014 DB", "file_type": "document", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_\uad6c\ud604_\ub300\uae30_\ud56d\ubaa9", "label": "\uad6c\ud604 \ub300\uae30 \ud56d\ubaa9", "file_type": "document", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L19"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_wf5_quantity_b08_frontend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_b08_wf5_quantity_db", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_db_schema_structure_output_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L16", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_db_schema_overview_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L17", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_b08_wf5_quantity_db", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_\uad6c\ud604_\ub300\uae30_\ud56d\ubaa9", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L19", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_b08_db_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b08_wf5_quantity_storage_paths_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B08_wf5_Quantity/B08_db.md", "source_location": "L29", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "label": "B06_api.md", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_b06_wf3_profilecross_api", "label": "B06_wf3_ProfileCross \u2014 API", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_\uc0dd\uc131_\uc751\ub2f5", "label": "\uc0dd\uc131 \uc751\ub2f5", "file_type": "document", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L20"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_wf3_profilecross_b06_frontend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_wf3_profilecross_b06_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_wf3_profilecross_b06_db_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_b06_wf3_profilecross_api", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_auth_rbac_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_api_common_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_b06_wf3_profilecross_api", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b06_wf3_profilecross_b06_api_\uc0dd\uc131_\uc751\ub2f5", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B06_wf3_ProfileCross/B06_api.md", "source_location": "L20", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_md", "label": "AGENTS.md", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_wiki_\ud3f4\ub354_\uaddc\uce59", "label": "wiki/ \ud3f4\ub354 \uaddc\uce59", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_\uad6c\uc870", "label": "\uad6c\uc870", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L5"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_\uc791\uc131_\uaddc\uce59", "label": "\uc791\uc131 \uaddc\uce59", "file_type": "document", "source_file": "AGENTS.md", "source_location": "L11"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_wiki_\ud3f4\ub354_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L1", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_wiki_\ud3f4\ub354_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_\uad6c\uc870", "relation": "contains", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_wiki_\ud3f4\ub354_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_\uc791\uc131_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L11", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_agents_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_\ud398\uc774\uc9c0\uba85_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "AGENTS.md", "source_location": "L14", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_md", "label": "CLAUDE.md", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_wiki_\ud3f4\ub354_\uaddc\uce59", "label": "wiki/ \ud3f4\ub354 \uaddc\uce59", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_\uad6c\uc870", "label": "\uad6c\uc870", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L5"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_\uc791\uc131_\uaddc\uce59", "label": "\uc791\uc131 \uaddc\uce59", "file_type": "document", "source_file": "CLAUDE.md", "source_location": "L11"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_wiki_\ud3f4\ub354_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CLAUDE.md", "source_location": "L1", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_wiki_\ud3f4\ub354_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_\uad6c\uc870", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CLAUDE.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_wiki_\ud3f4\ub354_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_\uc791\uc131_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CLAUDE.md", "source_location": "L11", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_claude_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_\ud398\uc774\uc9c0\uba85_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "CLAUDE.md", "source_location": "L14", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_md", "label": "schema_common.md", "file_type": "document", "source_file": "concepts/schema_common.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uacf5\ud1b5_\uc2a4\ud0a4\ub9c8_pydantic_\uc694\uccad_\uc751\ub2f5_\uaddc\uce59", "label": "\uacf5\ud1b5 \uc2a4\ud0a4\ub9c8 (Pydantic \uc694\uccad/\uc751\ub2f5 \uaddc\uce59)", "file_type": "document", "source_file": "concepts/schema_common.md", "source_location": "L9"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uac80\uc99d_\uc6d0\uce59_backend_md_4\uc808", "label": "\uac80\uc99d \uc6d0\uce59 (backend.md 4\uc808)", "file_type": "document", "source_file": "concepts/schema_common.md", "source_location": "L11"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uba85\uba85_\uaddc\uce59", "label": "\uba85\uba85 \uaddc\uce59", "file_type": "document", "source_file": "concepts/schema_common.md", "source_location": "L16"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uacf5\ud1b5_\uc2a4\ud0a4\ub9c8_pydantic_\uc694\uccad_\uc751\ub2f5_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "concepts/schema_common.md", "source_location": "L9", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uacf5\ud1b5_\uc2a4\ud0a4\ub9c8_pydantic_\uc694\uccad_\uc751\ub2f5_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uac80\uc99d_\uc6d0\uce59_backend_md_4\uc808", "relation": "contains", "confidence": "EXTRACTED", "source_file": "concepts/schema_common.md", "source_location": "L11", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uacf5\ud1b5_\uc2a4\ud0a4\ub9c8_pydantic_\uc694\uccad_\uc751\ub2f5_\uaddc\uce59", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_schema_common_\uba85\uba85_\uaddc\uce59", "relation": "contains", "confidence": "EXTRACTED", "source_file": "concepts/schema_common.md", "source_location": "L16", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "label": "B03_dependencies.md", "file_type": "document", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_b03_fileinput_dependencies", "label": "B03_FileInput \u2014 Dependencies", "file_type": "document", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_\uacf5\ud1b5_\ubaa8\ub4c8", "label": "\uacf5\ud1b5 \ubaa8\ub4c8", "file_type": "document", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L24"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_fileinput_b03_frontend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_fileinput_b03_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_b03_fileinput_dependencies", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_dependencies_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_b03_fileinput_dependencies", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_\uacf5\ud1b5_\ubaa8\ub4c8", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L24", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_storage_paths_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L27", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b03_fileinput_b03_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_workflow_state_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B03_FileInput/B03_dependencies.md", "source_location": "L28", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
@@ -0,0 +1 @@
{"nodes": [{"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_md", "label": "B05_dependencies.md", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L1"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_b05_wf2_route_dependencies", "label": "B05_wf2_Route \u2014 Dependencies", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L10"}, {"id": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_\ud398\uc774\uc9c0_\ud30c\uc77c\ubcc4_\uc5f0\uacb0", "label": "\ud398\uc774\uc9c0 \ud30c\uc77c\ubcc4 \uc5f0\uacb0", "file_type": "document", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L23"}], "edges": [{"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_wf2_route_b05_frontend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_wf2_route_b05_backend_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L5", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_b05_wf2_route_dependencies", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L10", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_md", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_concepts_dependencies_md", "relation": "references", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L12", "weight": 1.0}, {"source": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_b05_wf2_route_dependencies", "target": "c_program_coding_\uc784\ub3c4\uc124\uacc4_\ubc0f_\uacac\uc801\uc790\ub3d9\ud654_\ud504\ub85c\uadf8\ub7a8_\uac1c\ubc1c_docs_wiki_pages_b05_wf2_route_b05_dependencies_\ud398\uc774\uc9c0_\ud30c\uc77c\ubcc4_\uc5f0\uacb0", "relation": "contains", "confidence": "EXTRACTED", "source_file": "pages/B05_wf2_Route/B05_dependencies.md", "source_location": "L23", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More