13 KiB
워크플로우 단계별 상태 관리 재설계 계획서
작성일: 2026-07-10 상태: 설계 제안 (코드는 다른 AI가 진행) 범위: 프로젝트 워크플로우(WF0 파일입력 ~ WF6 견적) 단계별 완료 상태의 DB 저장·조회·무효화(stale) 로직, 그리고 이를 근거로 한 대시보드/파일입력/WF1~WF6 공통 UI의 단계 활성화·페이지 이동 제어
1. 현재 구조 진단 (코드 조사 결과)
1.1 상태값이 단일 문자열 하나로 관리됨
-
projects.status— VARCHAR(50) 단일 컬럼 (001_create_schema.sql:187)- 값 예:
NEW→FILE_UPLOADED→WF1_ANALYZING→WF1_COMPLETE→WF2_COMPLETE→ ... →DONE - 하나의 값만 존재 — "지금 어느 단계까지 왔는가"만 표현, "각 단계가 개별적으로 완료됐는가"는 표현 불가
- 값 예:
-
이 단일 값을 대시보드가
_stage_from_status()로 순서 추론 (B01_Dashboard_Repository.py:20)order = [("FILE_UPLOADED", 1), ("WF1_COMPLETE", 2), ("WF2_COMPLETE", 3), ...] # status 문자열에 토큰이 "포함"되면 그 stage로 간주 → 부분 문자열 매칭
1.2 이로 인한 실제 문제 (사용자 보고와 일치)
-
단계 독립성 없음:
WF2_COMPLETE가 되면 WF1 완료 정보는 문자열에 없고 "순서상 앞이니까 됐겠지"로만 추론. WF1을 다시 열어 재분석해도 그 사실을 status가 담지 못함. -
역방향 재작업(stale) 로직 부재: 사용자가 WF1으로 돌아가 파일/분석을 바꾸면 WF2~WF6 결과는 무효가 되어야 하는데, 이를 표시·차단하는 로직이 전혀 없음. status를 WF1로 되돌리면 이후 완료 이력이 통째로 사라짐(반대로 안 되돌리면 낡은 하위 단계가 열린 채 유지됨).
-
이원화된 죽은 설계:
workflow.json(completed: [],stale_from,current_stage필드)이 프로젝트 생성 시 초기화되지만 (B02_ProjRegister_Repository.py:31), 실제 단계 판정에는 쓰이지 않음.projects.status가 사실상 유일한 근거. 두 메커니즘이 공존하나 어느 쪽도 완전하지 않음. -
상태 갱신처 분산:
statusUPDATE가 여러 라우터에 흩어져 있음- B03_FileInput_Router.py:158,210,240 (
WF1_ANALYZING/WF1_COMPLETE/WF1_FAILED) - B04_wf1_Surface_Router.py:160,199,217 (동일 값 중복 세팅)
- B03과 B04가 같은 WF1 상태를 각자 세팅 → 경쟁·불일치 위험
- B03_FileInput_Router.py:158,210,240 (
1.3 각 단계 산출물 테이블에는 이미 개별 status가 있음 (활용 가능)
surface_models.status(PROCESSING/COMPLETE/FAILED) (001_create_schema.sql:258)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.
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 상태 갱신 지점 통일
단계별 상태 전이를 공통 유틸 함수로 통일하여 라우터 중복 제거:
# 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_COMPLETEUPDATE를 이 함수 호출로 대체. - WF1 상태의 이중 세팅 문제 해소: 파일입력 완료 후 자동 WF1 분석 흐름에서 stage 0
complete→ stage 1start/complete가 한 경로로만 일어나도록 정리.
3.5 재실행 시 사용자 입력 보존 (G5)
- 각 stage 행의
params JSON에 해당 단계 실행 시 사용자가 고른 값을 저장- WF1:
{source_filters, methods, force, input_file_id} - WF2:
{algorithm, road_grade, min_radius, ...}
- WF1:
- 사용자가 단계 재진입 시 UI는
params를 불러와 폼 기본값으로 채움 → 초기 실행과 달리 재계산이 즉시 가능.
4. 프론트엔드 반영
4.1 공통 규칙 (대시보드 + 파일입력 + WF1~6 레이아웃)
- 세 곳 모두
workflow-stateAPI 하나를 근거로 사용 (G4). 개별 페이지가 자체 추론하지 않음. - 버튼 활성화:
stage.state === 'COMPLETE'인 다음 단계까지 이동 가능.STALE은 "재작업 필요" 배지로 표시하되 진입 허용. - 대시보드 워크플로우 버튼(B01_Dashboard_UI_Page.ts:370
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).
- 재설계 후에도 이 시점을 유지하되, "완료" 판정을
workflow-state의 stage 1 == COMPLETE로 통일.
5. 마이그레이션 & 호환
project_workflow_stages테이블 생성 SQL을db_management/에 신규 번호로 추가.- 기존 프로젝트 백필: 현재
projects.status문자열을 해석해 각 프로젝트의 stage 0~N을COMPLETE로, 나머지는NOT_STARTED로 시드하는 1회성 스크립트. projects.status는 당분간 요약 캐시로 유지(외부 참조 호환), 신규 판정은 테이블 기준. 안정화 후 제거 검토.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. 검증 체크리스트
- 신규 프로젝트: 모든 단계
NOT_STARTED, stage 0만 진입 가능 - 파일 업로드 후 WF1 자동 분석 → stage 0/1
COMPLETE, stage 2 진입 가능 - WF2 완료 후 WF1으로 돌아가 재분석 → stage 2~6이
STALE로 전환되는지 STALE단계 재실행 시COMPLETE복귀 + 결과 파일 보존 확인- 대시보드/파일입력/WF 레이아웃 세 곳의 버튼 활성화가 동일 규칙으로 일치
- 재분석 시 이전
params가 폼에 프리필되는지 (G5) - WF1 상태가 B03·B04 두 곳에서 중복 세팅되지 않는지 (단일 경로)
- 전역 헤더(로그인/로그아웃)가 WF 레이아웃 헤더 토글과 무관하게 항상 표시되는지
8. 미해결/확인 필요 사항 (코더 착수 전 결정)
projects.status유지 vs 제거: 당장은 요약 캐시로 유지 권장(대시보드 정렬·필터 등 기존 참조 호환). 완전 제거는 참조처 전수 조사 후.- STALE 하위 결과 파일 처리: 낡은 결과 파일을 즉시 삭제할지, 재실행 시 덮어쓸지. 권장: 보존 후 재실행 시 덮어쓰기(사용자가 비교/복구 가능).
- stage_key 명칭:
FILE_INPUT,WF1_SURFACE,WF2_ROUTE,WF3_PROFILE_CROSS,WF4_DESIGN_DETAIL,WF5_QUANTITY,WF6_ESTIMATION로 확정 제안 (B0N 폴더명과 정합).