
    -ǚj@                     8   d Z ddlZddlZddlZddlmZmZmZmZm	Z	m
Z
 ddlmZ ddlmZ ddlmZmZmZmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlm Z m!Z! ddl"m#Z# ddl$m%Z%m&Z&m'Z' ddl(m)Z)  ejT                  e+      Z, eddg      Z-dZ.de/dedede)fdZ0e-jc                  de2e'          ee       ee       ee      fde/dedefd       Z3e-ji                  de'       e
d       e
d       e	d      d ee       ee       ee      fde5de/d ed!ede/dedefd"       Z6 G d# d$e#      Z7e-ji                  d%       ee       ee       ee      fd&e7de/dedefd'       Z8e-jc                  d(e'       ee       ee       ee      fd)e/de/dedefd*       Z9e-jc                  d+       ee       ee       ee      fd)e/de/dedefd,       Z:e-jw                  d(e'       ee       ee       ee      fd)e/d&e&d!ede/dedefd-       Z<e-j{                  d(       ee       ee       ee      fd)e/d!ede/dedef
d.       Z>e-ji                  d/d01       ee       ee       ee      fd)e/d!ede/dedef
d2       Z?e-ji                  d3d01       ee       ee       ee      fd)e/d!ede/dedef
d4       Z@e-jc                  d5      d6 ee       ee       ee      fd)e/d7e5de/dedef
d8       ZAd9ZBe-ji                  d:      e ee       ee       ee      fd)e/de/dedefd;              ZCe-jc                  d<       ee       ee       ee      fd)e/de/dedefd=       ZDe-jc                  d>       ee       ee      fd)e/de/defd?       ZEy)@u"   에피소드 관리 API 라우터.    N)	APIRouterDependsRequest
UploadFileFileFormStreamingResponse)Session)api_endpointget_dbget_current_userverify_project_accesssettings)SessionLocal)AppError)t)UserAccount)EpisodePipelineProgress)	BaseModel)EpisodeCreateEpisodeUpdateEpisodeResponse)EpisodeServicez&/api/v1/projects/{project_id}/episodesepisodes)prefixtagsu8  DEPRECATED endpoint %s called (episode=%s). AnalysisService 경로는 architecture refactor Phase 4에서 제거됩니다. 공식 실행 경로는 StepRunner (POST /api/v1/projects/{project_id}/episodes/{episode_id}/steps/run-all) 입니다. 참조: docs/architecture-refactor-final/02-final-roadmap.md §Phase 4
project_iddbuserreturnc                 2    t        || |j                        S )N)r!   r    actor_id)r   id)r    r!   r"   s      I/Users/manta/Documents/Projects/TheRoad-I1/backend/app/api/v1/episodes.py_make_servicer(   (   s    
      /)response_modelcurrent_userc                 <    t        | ||      }|j                         S N)r(   list_episodes)r    r!   r,   services       r'   r/   r/   4   s!     JL9G  ""r)   .episode_numbertitlefilerequestc                    K   |r"|j                   r|j                   j                  nd }|j                          d {   }|j                  xs d}	t	        |||      }
|
j                  | |||	|      S 7 6w)Nzscreenplay.pdf)clienthostreadfilenamer(   create_episode)r1   r2   r3   r4   r    r!   r,   ip	pdf_bytesr9   r0   s              r'   r:   r:   >   sj      !(GNN		Biik!I}}0 0HJL9G!!.%HbQQ "s   :A5A37A5c                   `    e Zd ZU dZee   ed<   dZeed<   dZeed<   dZ	e
dz  ed<   d	Zeed
<   y)RunQueueRequestu%   여러 편을 순서대로 돌린다.episode_idsanalysiscategoryresumemodeNimage_call_capFapprove_image_generation)__name__
__module____qualname____doc__liststr__annotations__rA   rC   rD   intrE   bool r)   r'   r>   r>   O   s>    /cHcD# "&NC$J%%*d*r)   r>   z
/run-queuebodyc           	          ddl m}  ||| j                  | j                  | j                  || j
                  | j                        S )uK  이 프로젝트의 여러 화를 **화수 순서대로 하나씩** 돌린다.

    ★순차가 요구사항이다 — 앞 화가 끝나야 요소 명부가 서고, 그래야 다음 화가
     같은 인물·배경·소품을 같은 신원으로 이어받는다. 다른 프로젝트끼리는
     그대로 나란히 돈다.
    r   )dispatch_project_queue)r    r?   rA   rC   r!   rD   rE   )&app.services.analysis_dispatch_servicerR   r?   rA   rC   rD   rE   )rP   r    r!   r,   rR   s        r'   	run_queuerT   [   sA     N!4+;+;TYY2**!%!>!>	 r)   z/{episode_id}
episode_idc                 >    t        |||      }|j                  |       S r.   )r(   get_episode)rU   r    r!   r,   r0   s        r'   rW   rW   r   s#     JL9Gz**r)   z/{episode_id}/fulltextc                 $   ddl m} |j                  t              j	                  t        j
                  | k(  t        j                  |k(        j                         }|st        dt        d      d      |j                  xs d|j                  dS )uE   에피소드 시나리오 전문 반환 (씬 세그먼트 표시용).r   r   episode.not_found  codemessagestatus_code )fulltextsegment_context_chars)app.core.configr   queryr   filterr&   r    firstr   r   r`   scene_segment_context_chars)rU   r    r!   r,   r   episodes         r'   get_episode_fulltextrh   }   s{     )hhw&&wzzZ'?ASASWaAabhhjG/;N9O]`aa$$*!)!E!E r)   c                     |j                   r|j                   j                  nd }|j                  d      }t        |||      }|j	                  | ||      S )NT)exclude_none)r6   r7   
model_dumpr(   update_episode)	rU   rP   r4   r    r!   r,   r;   updatesr0   s	            r'   rl   rl      sN     !(		DBoo4o0GJL9G!!*gr::r)   c                     |j                   r|j                   j                  nd }t        |||      }|j                  | |       ddiS )NokT)r6   r7   r(   delete_episode)rU   r4   r    r!   r,   r;   r0   s          r'   rp   rp      sC     !(		DBJL9G:r*$<r)   z/{episode_id}/analyzeT)
deprecatedc                 ~    ddl m} t        j                  t        d|         ||| dd|      }dt        d      d	|S )
u  Start LLM analysis for an episode (background thread).

    DEPRECATED (Phase 4에서 StepRunner 경로로 내부 교체됨):
        공식 경로는 `/api/v1/projects/{project_id}/episodes/{episode_id}/steps/run-all?category=analysis`.
        본 엔드포인트는 dispatch_category_run(category="analysis")으로 내부 위임되며,
        호환성 유지 목적으로 한시 보존됨. 차기 릴리스에서 제거 예정.
        참조: docs/architecture-refactor-final/02-final-roadmap.md §Phase 4
    r   )dispatch_category_runz/analyzer@   rB   )r    rU   rA   rC   r!   Tzanalysis.startedro   r]   )rS   rs   loggerwarning!_ANALYSIS_SERVICE_DEPRECATION_MSGr   )rU   r4   r    r!   r,   rs   results          r'   analyze_episodery      sL      M
NN4j*M #F 1%7#8CFCCr)   z/{episode_id}/reanalyze-scenesc                    ddl m} t        j                  t        d|        |j                  t              j                  t        j                  | k(  t        j                  |k(        j                         j                         }|st        dt        d      d      |j                  dk(  rt        dt        d      d	      |j                  st        d
t        d
      d      |j                  }d|_        d|_        |j#                          	  ||| |      }ddd|S # t$        $ r ||_        |j#                           w xY w)uK  씬만 재분석 (요소 유지).

    DEPRECATED (Phase 4에서 StepRunner 경로로 내부 교체됨):
        scene_save + downstream(category=analysis, active) force 실행.
        공식 경로는 `/steps/{step_id}?mode=force` 개별 호출.
        참조: docs/architecture-refactor-final/02-final-roadmap.md §Phase 4
    r   )dispatch_scene_reanalysisz/reanalyze-scenesrY   rZ   r[   	analyzingzanalysis.already_runningi  zanalysis.no_texti  N)r    rU   r!   Tu&   씬 재분석이 시작되었습니다rt   )rS   r{   ru   rv   rw   rc   r   rd   r&   r    with_for_updatere   r   r   statusr`   analysis_errorcommit	Exception)	rU   r4   r    r!   r,   r{   rg   prior_statusrx   s	            r'   reanalyze_scenesr      s-    Q
NN46I:V 		

j('*<*<
*J	K				  /;N9O]`aa~~$6B\@]knoo.:L8M[^__ >>L GN!GIIK	*!!
 #KVvVV	  %
		s   D$ $"Ez/{episode_id}/segment-previewiX  	thresholdc                    |j                  t              j                  t        j                  | k(  t        j                  |k(        j                         }|r|j                  sdddg dS ddlm}  ||j                  |      }|D cg c]  }|d   |k\  s| }	}t        |      t        |	      t        |      ||D cg c]  }|d   |d   |d   |d   |k\  d c}d	S c c}w c c}w )
uJ   정규식 세그먼테이션 미리보기 — threshold별 예상 씬 수.r   )base_scenessplit_candidatesestimated_totalscenes)_segment_scenes)split_thresholdlengthscene_indexheading)indexr   r   
will_split)r   r   r   r   r   )
rc   r   rd   r&   r    re   r`   'app.modules.pipeline.scene_extractor_v2r   len)
rU   r   r    r!   r,   rg   r   segmentsslong_sceness
             r'   segment_previewr     s    hhw&&wzzZ'?ASASWaAabhhjG'** aAY[\\Gw//KH&Ch!H+*B1hKC 8},x= 
  =)Y<H+kY6	 
  D
s   	C"C" C')r@   reference_image_generationimage_generationwebbook
pdf_renderz/{episode_id}/repair-projectionc                 ~   ddl m} ddlm} |j	                  t
              j                  t
        j                  | k(  t
        j                  |k(        j                         }|st        dt        d      d      |j                   |d      || d      j                         }|D ci c]  }|d   |d	    }	}	  ||| ||	xs d
      }
d|
t%        |	j'                               dS c c}w # t        $ rF}t        j!                  d| d
d |       |j#                          t        dt        d      d      d
}~ww xY w)u  W4 P3-3 — sync_status='failed' step_run의 DB projection만 재시도.

    checkpoint는 canonical이므로 건드리지 않음. LLM 재호출 없음.
    5-way sync 실행 + 같은 트랜잭션에서 failed→synced 전환 (Codex Medium: split commit 회피).
    sync_error가 우리가 캡처한 값과 일치할 때만 전환 — concurrent 프로세스의 새 실패는
    덮어쓰지 않음 (Codex High).

    Returns:
        {
            "ok": true,
            "sync_result": {entity/relation/scene_still/outlook/episode 결과},
            "repaired_steps": [repair 시도한 step_id 리스트]
        }

    예외:
        - 404 episode.not_found
        - sync 실패 시 AppError → @api_endpoint가 내부 오류로 정규화(500)
    r   )text)orchestrate_full_syncrY   rZ   r[   zqSELECT step_id, sync_error FROM step_run WHERE project_id = :pid AND episode_id = :eid AND sync_status = 'failed')pideid   N)repair_step_idsz+repair-projection failed for episode %s: %s   zrepair.sync_failedi  T)ro   sync_resultrepaired_steps)
sqlalchemyr   app.services.checkpoint_syncr   rc   r   rd   r&   r    re   r   r   executefetchallr   ru   errorrollbackrJ   keys)rU   r    r!   r,   sql_textr   epfailed_rowsr
repair_maprx   excs               r'   repair_projectionr   &  sQ   4 ,B 		

j('*<*<
*J	K	 
 $)*
 	
 **X	S *-/ 08xz  >I(I[1qt[J(I
&
B
8Jd
 z01 ' )J  	
9:bq>3	
 	%*+
 	
	
s   (C(;C- -	D<6AD77D<z/{episode_id}/progressc                 Z   |j                  t              j                  t        j                  |k(  t        j                  | k(        j                         }d }i }t        D ]B  }|D cg c]  }|j                  |k(  s| }	}|	rt        |	d       }
 ||
      ||<   >d||<   D |S c c}w )z%Get pipeline progress for an episode.c           	          | j                   | j                  xs d| j                  xs d| j                  xs d| j                  | j
                  | j                  | j                  dS )Nr_   r   r~   current_stepcompleted_stepstotal_stepserror_message
started_at
updated_atcompleted_atr   )rows    r'   _format_progressz&get_progress.<locals>._format_progress  s[    jj,,2"227a??/a ......,,	
 		
r)   c                 6    | j                   xs | j                  S r.   r   r   ps    r'   <lambda>zget_progress.<locals>.<lambda>  s    all.Jall.Jr)   keyN)	rc   r   rd   r    rU   all_OPERATION_KEYS	operationmax)rU   r    r!   r,   all_progressr   rx   r   r   matchesbests              r'   get_progressr   o  s     	!"	'':5'':5

 
 

 F*AlakkS.@1lAw$JKD*40F3KF3K  M Bs   (B(=B(z/{episode_id}/progress/streamc                 F    K    fd}t         |       dddd      S w)z,SSE endpoint for real-time progress updates.c                   K   t               } 	 	 | j                  t              j                  t        j                  k(  t        j
                  k(        j                         }i }t        D ]p  }|D cg c]  }|j                  |k(  s| }}|s%t        |d       }|j                  |j                  xs d|j                  xs d|j                  xs dd||<   r dt        j                  |d       d	 |rt        d
 |D              rd n/| j!                          t#        j$                  d       d {    7	 | j'                          y c c}w 7 # | j'                          w xY ww)Nc                 6    | j                   xs | j                  S r.   r   r   s    r'   r   z7stream_progress.<locals>.event_stream.<locals>.<lambda>  s    !,,:V!,,:Vr)   r   r_   r   )r~   r   r   r   zdata: F)ensure_asciiz

c              3   8   K   | ]  }|j                   d v   yw))	completedr   N)r~   ).0r   s     r'   	<genexpr>z8stream_progress.<locals>.event_stream.<locals>.<genexpr>  s     V1AHH(>>s   zdata: {"done": true}

   )r   rc   r   rd   r    rU   r   r   r   r   r~   r   r   r   jsondumps
expire_allasynciosleepclose)	r!   progress_rowsdatar   r   r   r   rU   r    s	          r'   event_streamz%stream_progress.<locals>.event_stream  sR    ^#	HH-.V(33zA(33zA SU  *C*7N-Q1;;#;Mq-GN"70VW&*kk,0,=,=,C/3/C/C/Hq+/+;+;+@q	%S		 + tzz$UCDDII "VVV44mmA&&&A : 
 HHJ- O( 'HHJsH   E5A%E  4E	EE  B.E   EE  E5E   E22E5ztext/event-streamzno-cacheno)zCache-ControlzX-Accel-Buffering)
media_typeheadersr	   )rU   r    r,   r   s   ``  r'   stream_progressr     s,     %N &",4H s   !)FrI   r   r   loggingfastapir   r   r   r   r   r   fastapi.responsesr
   sqlalchemy.ormr   
OrmSessionapp.api.depsr   r   r   r   rb   r   app.core.databaser   app.core.errorsr   app.i18n.loaderr   app.models.catalogr   app.models.projectr   r   pydanticr   app.schemas.episoder   r   r   app.services.episode_servicer   	getLoggerrF   ru   routerrw   rK   r(   getrJ   r/   postrM   r:   r>   rT   rW   rh   patchrl   deleterp   ry   r   r   r   r   r   r   rO   r)   r'   <module>r      s   (    G G / 0 V V $ * $  * 8  M M 7			8	$	3

M "			 	 		 C_ 5634V_ '(8 9### # 7# S1s)cCy34V_ '(8 9RRR R 	R
 R 	R R 2R 	+i 	+ \ 34V_ '(8 9	
 	 	 , OO< 34V_ '(8 9	+++ 	+ 	+ =+ $% 34V_ '(8 9	 	 	 &" oo>
 34V_ '(8 9;;
; ; 	;
 	; ; ?;  34V_ '(8 9


 
 		

 
  
 $6 34V_ '(8 9DDD D 		D
 D 7D> -$? 34V_ '(8 91W1W1W 1W 		1W
 1W @1Wh +, 34V_ '(8 9  		
  -B j ./ 34V_ '(8 9	DDD 	D 	D  0DN $% 34V_ '(8 9	%%% 	% 	% &%P +, 34 '(8 9222 2 -2r)   