이미지 생성 파이프라인 캔버스 — 구현 계획서

TheRoad Scene Lab · 2026-06-29 · 동반 문서: design.html(설계 SOT, §9 Codex 반영 포함)
Goal. 프로젝트의 이미지 생성 파이프라인을 에피소드 단위 노드-링크 캔버스로 시각화. 모든 노드=이미지, 엣지=실제 생성 입력, 노드 클릭 시 상류 조상 하이라이트 + 프롬프트 표시.
Architecture. 백엔드 read-only 신규 엔드포인트가 DB image_asset + checkpoint(background_render groups 등)에서 nodes/edges를 구조키 조인으로 조립(메인 파이프라인 무접촉). 프론트는 React Flow + dagre로 렌더, 클릭 시 역방향 BFS로 상류 하이라이트.
Tech. FastAPI/SQLAlchemy/PostgreSQL · React19+TS+Vite · @xyflow/react v12 · dagre.
Global Constraints (절대규칙 준수)

파일 구조

파일책임신규/수정
backend/app/schemas/pipeline.pyPipelineGraphResponse / PipelineNode / PipelineEdge Pydantic 스키마신규
backend/app/services/pipeline_graph_service.pynodes/edges 조립(읽기 전용): DB 노드 + checkpoint virtual 노드 + 엣지 복원 + verification신규
backend/app/api/v1/pipeline.pyGET /pipeline-graph 라우트 (프로젝트 prefix)신규
backend/app/api/v1/__init__.py 또는 라우터 등록부pipeline 라우터 include수정(1줄)
backend/tests/services/test_pipeline_graph_service.py엣지 복원 결정론 단위 테스트신규
frontend/src/hooks/api/usePipelineGraph.tsreact-query 훅 (GET pipeline-graph)신규
frontend/src/components/pipeline/layout.tsdagre 레이어드 좌표 계산 + 역방향 BFS 조상 계산신규
frontend/src/components/pipeline/PipelineNode.tsx커스텀 이미지 노드(썸네일 lazy + 배지 + 디밍)신규
frontend/src/components/pipeline/NodeDetailPanel.tsx풀해상 + 프롬프트 펼침 + 직접입력/상류 분리 + 라벨 배지신규
frontend/src/pages/PipelineCanvas.tsx페이지: 에피소드 선택·필터·React Flow·하이라이트 상태신규
frontend/src/components/pipeline/__tests__/layout.test.tsBFS/레이아웃 결정론 테스트(vitest)신규
frontend/src/App.tsx라우트 /projects/:id/pipeline수정
frontend/src/components/layout/Sidebar.tsx프로젝트 메뉴 항목수정
frontend/src/i18n/ko.jsonnav.project.pipeline수정
frontend/package.json@xyflow/react, dagre, @types/dagre수정

데이터 계약 (Interfaces)

PipelineNode {
  id: str                      # ImageAsset.id  OR  "virtual:<sha1(png_path)>"
  node_origin: "image_asset" | "checkpoint_virtual"
  asset_type: str              # reference|floor_plan|chain_bg|background_chain_node|composite|scene|guide
  guide_kind: str | None       # virtual 노드일 때 (composition_guide|pose_guide|anchor_sketch ...)
  entity_id, entity_short_id, entity_name, entity_type: str | None
  still_id: str | None;  scene_index: int | None;  shot_index: int | None
  episode_id: str | None;  variant_label: str | None
  thumb_url: str | None        # image_asset 노드만 (/file?thumb=1). virtual 은 full_url 직접
  full_url: str
  prompt: str | None           # image_asset.prompt_used
  status, created_at: str | None
}
PipelineEdge {
  source: str;  target: str          # node ids
  kind: "i2i" | "reference" | "background" | "fp" | "prev_scene"
  confidence: "direct_fk" | "checkpoint_structural" | "lineage_reference" | "call_log_label_seen" | "planned_only"
  verification_status: "structural_inferred" | "label_seen" | "unverified" | "conflict"
  bg_id: str | None
}
PipelineGraphResponse { episode_id, nodes: PipelineNode[], edges: PipelineEdge[], episodes: {id,label}[] }

태스크

Task 1 — 백엔드 스키마 + 서비스 골격 결정론 TDD

Files: Create schemas/pipeline.py, services/pipeline_graph_service.py(골격), tests/services/test_pipeline_graph_service.py.

Produces: build_pipeline_graph(db, project_id, episode_id) -> PipelineGraphResponse. list_episodes(db, project_id).

  1. 실패 테스트: 빈 프로젝트 → nodes=[], edges=[], episodes=[].
  2. 스키마 정의 + 서비스가 image_asset(project_id, episode_id)만 노드로 변환(엣지 빈 배열)하도록 최소 구현.
  3. 테스트: fixture로 scene/reference/floor_plan/chain_bg asset 4개 삽입 → 노드 4개, asset_type·thumb_url·prompt 매핑 확인.
  4. 커밋.

Task 2 — 엣지 복원: i2i + reference lineage 결정론 TDD

Files: Modify service, Modify test.

  1. 테스트: parent_image_id 체인 A→B→C → i2i 엣지 2개. reference_image_ids=[r1,r2] 인 scene → reference 엣지 2개. 존재하지 않는 ref id는 엣지 생성 안 함(노드 없는 엣지 금지).
  2. 구현: 노드 id set으로 dangling 엣지 필터. JSON 파싱 안전 처리(null/빈문자).
  3. 테스트 통과 → 커밋.

Task 3 — 엣지 복원: FP→배경→씬 (checkpoint groups SOT) 결정론 TDD

Files: Modify service, Modify test. 설계 §9.1 그대로.

  1. checkpoint 로더: background_render.data.groups(Phase7) → 없으면 background_chain_render.data.groups(Phase5) → legacy data.locations[loc].shot_backgrounds[]. (체크포인트 로드는 기존 _load_prev_checkpoint 패턴 재사용, 읽기 전용)
  2. groups[bg_id] = {png_path, shot_ids[], location_id} 에서: shot_ids(Sxx_Shotyy) → (scene_index, shot_index) 파싱 → 해당 scene 노드.
  3. bg 노드 찾기: DB image_asset where asset_type='chain_bg' AND episode_id AND variant_type=bg_id. 없으면 png_path 기반 virtual 노드(node_origin='checkpoint_virtual').
  4. 엣지: bg→scene(kind=background). bg_map엔 있으나 실제 attach 불명 → confidence=planned_only, verification=unverified. FP→bg: floor_plan asset(entity_id==location_id)→bg(kind=fp, confidence=checkpoint_structural 또는 inferred).
  5. prev_scene: scene_still.dependent_scene_id → 직전 씬 primary scene 노드(kind=prev_scene).
  6. 테스트: groups fixture(bg1: shot_ids=['S12_Shot8'], location_id=L03) + scene 노드(scene_index=12,shot_index=8) + chain_bg(variant_type=bg1) → bg→scene + fp→bg 엣지. DB에 chain_bg 없을 때 virtual 노드 생성 확인. shot_ids 매칭 scene 없으면 엣지 0.
  7. 커밋.

Task 4 — call_log 검증 오버레이 + verification_status 결정론 TDD

Files: Modify service, Modify test. 설계 §9.2.

  1. 기존 image_service_helpers._lookup_actual_refs_batch 패턴 참고하여 llm_call_logmetadata_json.still_id(+entity_id)·operation_type으로 조인 → 씬별 actual_labels_seen 수집(읽기 전용, 새 helper).
  2. 엣지의 verification_status 갱신: 라벨로 확인되면 label_seen, 구조만이면 structural_inferred 유지, 충돌이면 conflict. (라벨→UUID 단정 금지)
  3. 노드에 prompt_authoritative(user_prompt) 선택 필드 부착(있을 때만).
  4. 테스트: call_log fixture로 still의 background_chain_ref 라벨 존재 시 해당 bg→scene 엣지 verification=label_seen. 라벨 없으면 그대로. 커밋.

Task 5 — 엔드포인트 + 라우트 등록 + 실데이터 응답 육안 + 실데이터 검증

Files: Create api/v1/pipeline.py, Modify 라우터 등록.

  1. GET /api/v1/projects/{project_id}/pipeline-graph?episode_id=verify_project_access 의존성 재사용, build_pipeline_graph 호출. episode_id 없으면 첫 에피소드.
  2. 라우터 include(기존 images 라우터 등록부와 동일 패턴).
  3. 기존 프로젝트(예: 금월도)에 대해 curl로 응답 받아 node/edge 수·thumb_url 200 확인(실데이터, LLM 비용 0).
  4. 커밋(백엔드 1차 완료).

Task 6 — 프론트 deps + 데이터 훅 + 레이아웃/BFS 유틸 결정론 TDD(유틸)

Files: Modify package.json, Create usePipelineGraph.ts, components/pipeline/layout.ts, __tests__/layout.test.ts.

  1. npm i @xyflow/react dagre @types/dagre.
  2. vitest 테스트: 엣지 A→B→C에서 upstreamAncestors('C')={'A','B'}, directInputs('C')=['B']. 사이클 방어(visited). 분기 그래프 검증.
  3. 구현 + 통과. react-query 훅(useStillImages 패턴) 작성.
  4. 커밋.

Task 7 — 캔버스 페이지 + 커스텀 노드 렌더 시각 검증

Files: Create PipelineNode.tsx, PipelineCanvas.tsx. App/Sidebar/i18n 수정.

  1. AppShell title projectId 래핑 + 에피소드 선택기 + React Flow(<ReactFlow nodes edges>) + dagre 좌표 주입.
  2. PipelineNode: 썸네일(thumb_url, loading="lazy") + 타입/엔티티 배지 + 엔티티별 색상(design 범례) + 디밍 클래스. virtual 노드 점선 테두리 + guide 배지.
  3. 라우트 /projects/:id/pipeline + Sidebar 항목 + nav.project.pipeline ko.json.
  4. dev 서버(3000) + 기존 프로젝트로 렌더 → 노드/썸네일 표시 육안 확인. (playwright screenshot 가능)
  5. 커밋.

Task 8 — 클릭 하이라이트/디밍 + direct 토글 + 필터 시각 검증

  1. 노드 클릭 → upstreamAncestors set 계산 → set 밖 노드/엣지 opacity 0.15. 배경 클릭 시 해제.
  2. 토글 버튼: 전체 조상 ↔ direct-inputs-only. 엣지 색=kind, 점선=planned_only/unverified.
  3. 타입 필터(ref/bg/scene/guide/fp/composite) + selected-scene 필터.
  4. 실데이터로 씬 노드 클릭 → FP→bg→씬 상류 체인이 정확히 밝아지는지 + planned/actual 구분 육안.
  5. 커밋.

Task 9 — 노드 상세 패널 (이미지 + 프롬프트 + 참조) 시각 검증

  1. NodeDetailPanel: 풀해상 이미지(full_url) + 프롬프트(prompt, 펼침/접힘) + prompt_authoritative(있으면) + 직접 입력 목록 / 상류 체인 분리 + lineage 배지 + verification 배지.
  2. 실데이터로 프롬프트 펼침·이미지 로드 확인.
  3. 커밋.

Task 10 — 가이드/스케치 checkpoint virtual 노드 + 시각 설계 §9.3

  1. 서비스에 checkpoint manifest 로더 추가: outdoor composition guide / registered pose guide / anchor sketch 산출물 경로를 checkpoint(또는 산출 디렉터리 manifest)에서 읽어 virtual 노드화(node_origin='checkpoint_virtual', asset_type='guide', guide_kind). 실제 저장 위치는 구현 시 확인.
  2. 이 가이드가 어느 씬/샷 생성에 입력됐는지 구조키로 엣지(guide→scene). 불확실하면 planned_only.
  3. 결정론 테스트(manifest fixture→virtual 노드/엣지) + 실데이터 육안.
  4. 커밋.

※ Codex 권고: MVP에서 Task 1~9(ImageAsset 노드) 먼저 완성 후 본 태스크. 단 서비스 인터페이스는 처음부터 dual-source 전제.

Task 11 — 실데이터 E2E + Codex 리뷰 + 정리 육안

  1. 기존 프로젝트(금월도 등) 에피소드 1개로 전체 캔버스 렌더 → 노드/엣지가 실제 이미지 디렉터리 산출물과 일치 육안.
  2. 씬 클릭 상류 하이라이트가 llm_call_log 실제 attach와 모순 없는지 교차 확인.
  3. 외부 웹서버로 스크린샷/캔버스 공유 → 사용자 육안 리뷰 + Codex 코드 리뷰.
  4. 수정 → 사용자 GO 시 커밋/푸시.

Self-Review (spec coverage)

— 끝. 실행 방식: 결정론 백엔드(Task1~5)는 inline TDD, 프론트 시각 부분은 단계별 육안+Codex 리뷰. 사용자/Codex 합의대로 진행.