Files
Aislo/.agent/plan_workflow_state_management.md
T
2026-07-10 19:01:33 +09:00

13 KiB

워크플로우 단계별 상태 관리 재설계 계획서

작성일: 2026-07-10 상태: 설계 제안 (코드는 다른 AI가 진행) 범위: 프로젝트 워크플로우(WF0 파일입력 ~ WF6 견적) 단계별 완료 상태의 DB 저장·조회·무효화(stale) 로직, 그리고 이를 근거로 한 대시보드/파일입력/WF1~WF6 공통 UI의 단계 활성화·페이지 이동 제어


1. 현재 구조 진단 (코드 조사 결과)

1.1 상태값이 단일 문자열 하나로 관리됨

  • projects.statusVARCHAR(50) 단일 컬럼 (001_create_schema.sql:187)

    • 값 예: NEWFILE_UPLOADEDWF1_ANALYZINGWF1_COMPLETEWF2_COMPLETE → ... → DONE
    • 하나의 값만 존재 — "지금 어느 단계까지 왔는가"만 표현, "각 단계가 개별적으로 완료됐는가"는 표현 불가
  • 이 단일 값을 대시보드가 _stage_from_status()순서 추론 (B01_Dashboard_Repository.py:20)

    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), 실제 단계 판정에는 쓰이지 않음. projects.status가 사실상 유일한 근거. 두 메커니즘이 공존하나 어느 쪽도 완전하지 않음.

  4. 상태 갱신처 분산: status UPDATE가 여러 라우터에 흩어져 있음

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).

대안(경량): 신규 테이블 없이 projectsstage_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_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 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. 마이그레이션 & 호환

  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.pystale_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. 미해결/확인 필요 사항 (코더 착수 전 결정)

  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 폴더명과 정합).