
    j                         d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	  ej                  e      ZddZddZ	 	 	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 dd	Zdd
Zy)u.  저빈도 스킵 엔티티 ID 파일 I/O 유틸.

`ref_image_gen` step이 T2I 등장 횟수가 낮은 엔티티(1회 이하 + 변형체 base 아님)는
참조 이미지 생성을 비용 절약 목적으로 스킵하고, 그 canon_id 목록을
`projects/{pid}/checkpoints/images/{eid}/ref_low_freq_skip.json`에 기록한다.

이 목록은 여러 소비자가 참조:
  - `pipeline_gate.py` — 이미지 gate 통과 시 저빈도 스킵 엔티티는 미완료로 세지 않음
  - `reference_image_service.py` — 본 step에서 write
  - `api/v1/images.py` — 상태 응답에 `ref_low_freq_skipped` 개수 노출
  - `core/steps/image_steps.py` CompositeImageGenStep — combo_total / existing_keys 계산에서 제외

모두 경로 구성 + read/parse 로직이 복붙돼 있어 중복 → 유틸로 추출.
    )annotationsN)Path)IterableSetc                V    ddl m} t        |j                        | z  dz  dz  |z  dz  S )u   저빈도 스킵 파일 경로.r   )settingscheckpointsimageszref_low_freq_skip.json)app.core.configr   r   projects_dir)
project_id
episode_idr   s      L/Users/manta/Documents/Projects/TheRoad-I1/backend/app/core/low_freq_skip.pyget_skip_file_pathr      sF    (X""#
	
	 	 		
 #	#    c                   t        | |      }|j                         s
t               S 	 t        j                  |j                  d            }t        |t              rU|j                  dg       D ch c]9  }t        |t              r'|j                  d      r|j                  d      r|d   ; c}S t        |      S # t        $ r+}t        j                  d||       t               cY d}~S d}~ww xY wc c}w )u   저빈도 스킵된 canon_id 집합 반환. 파일 없거나 파싱 실패 시 빈 set.

    파싱 실패는 warning 로그 — combo_total/미완료 판정에 영향을 줄 수 있어
    운영자가 JSON 손상을 감지할 수 있어야 함.
    utf-8encoding"low_freq_skip: %s parse failed: %sN	decisionsskippedcanon_id)r   existssetjsonloads	read_text	Exceptionloggerwarning
isinstancedictget)r   r   	skip_filerawexcds         r   load_low_freq_skip_idsr)   &   s     #:z:Iujj,,g,>? #t#&77;#;
#;a!T"quuY'7AEE*<M jM#;
 	
 s8O  0)S	
 u	
s#   %B= 1>C4=	C1 C,&C1,C1c           	         t        | |      }|j                  j                  dd       |j                  t	        j
                  t        t        |                  d       y)uK   저빈도 스킵 canon_id 집합 저장. 부모 디렉토리 자동 생성.Tparentsexist_okr   r   N)r   parentmkdir
write_textr   dumpssortedr   )r   r   idsr%   s       r   save_low_freq_skip_idsr4   ?   sN     #:z:I4$7F3s8$45Hr   c                    t        | |      }|j                  j                  dd       dt        d |D        d       d}|j	                  t        j                  |d	      d
       y)u   reasoned object 포맷(v2)으로 저장.

    decisions item = {canon_id, skipped: bool, reason: str, ...}.
    `load_low_freq_skip_ids` 가 v2 dict 도 읽으므로 기존 소비처 무변경.
    Tr+      c              3  2   K   | ]  }t        |        y w)N)r#   ).0r(   s     r   	<genexpr>z,save_low_freq_skip_report.<locals>.<genexpr>U   s     (iT!Wis   c                &    | j                  dd      S )Nr    )r$   )r(   s    r   <lambda>z+save_low_freq_skip_report.<locals>.<lambda>V   s    !%%
B/r   )keyversionr   F)ensure_asciir   r   N)r   r.   r/   r2   r0   r   r1   )r   r   r   r%   payloads        r   save_low_freq_skip_reportrB   H   sk     #:z:I4$7(i(/
G 

7/'  r   c                P   t        | |      }|j                         sdg dS 	 t        j                  |j	                  d            }t        |t              r|S d|D cg c]  }|ddd	
 c}dS # t
        $ r&}t        j                  d||       dg dcY d}~S d}~ww xY wc c}w )
uM   reasoned report 반환. v1 array 면 decisions 로 승격, 부재 시 빈 v2.r6   r>   r   r   r   NTz(legacy v1))r   r   reason)
r   r   r   r   r   r   r    r!   r"   r#   )r   r   r%   r&   r'   cids         r   load_low_freq_skip_reportrF   ^   s    ":z:I2../jj,,g,>? #t
 
 G
   /;YL2../
s#   %A1 B#1	B :BB B )r   strr   rG   returnr   )r   rG   r   rG   rH   zSet[str])r   rG   r   rG   r3   zIterable[str]rH   None)r   rG   r   rG   r   zIterable[dict]rH   rI   )r   rG   r   rG   rH   r#   )__doc__
__future__r   r   loggingpathlibr   typingr   r   	getLogger__name__r    r   r)   r4   rB   rF    r   r   <module>rR      s    #     			8	$
2II!$I+8I	I!$1?	,r   