260710_1
This commit is contained in:
+551
-484
File diff suppressed because it is too large
Load Diff
@@ -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 폴더명과 정합).
|
||||
Reference in New Issue
Block a user