
    ujG0                     d   U d Z ddlZddlZddlZddlZddlZddlZddlm	Z	m
Z
mZ ddlmZ ddlmZ dZdZdd	d
did
did
didd
diddg ddZe
ee	f   ed<   dZdedefdZ eddd d d      dededed ede
ee	f   f
d!       Zd"e
ee	f   defd#Z G d$ d%      Zdefd&Zdd'd(Zdefd)Zy)*u  이미지 품질 검증 모듈 — GPT-5.4 비전으로 생성된 이미지 품질 판정.

prompt_loader 통합 (problems.md #14): hard-coded ``v1`` 디렉토리 직접 read 제거
→ ``prompt_loader.load_prompt`` 경유 (DB 우선 + numeric version 정렬 + #6 #13).
    N)AnyDictList)settings)traced_callimage_validationz#https://api.openai.com/v1/responsesobjectFtypestringintegerbooleanarray)r
   items)descriptionscorepassedissues)r
   additionalProperties
propertiesrequiredVALIDATION_SCHEMA<   filenamereturnc                 X    ddl m} | j                  d      r| dd n| } |t        |      S )uV  ``prompts/_base/image_validation`` 에서 stem 로드 (DB 우선).

    파일 모듈 이름은 ``image_validator`` (단수) 이지만 prompts 디렉토리는
    ``image_validation`` (다른 형태) — 흡수 시 매핑 confusion 회피 (review I4).

    Raises FileNotFoundError 또는 RuntimeError (#6 strict mode) — caller 처리.
    r   )load_promptz.mdN)app.modules.prompt_loaderr   endswith_MODULE)r   r   stems      Q/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/image_validator.py_load_promptr#   (   s.     6$--e48CR=(Dw%%    vision_validationopenaic                     |S N )api_keymodelaks       r"   <lambda>r.   7   s    er$   c                     |S r(   r)   )r*   r+   image_bytestext_promptr,   r-   s         r"   r.   r.   8   s    !r$   z[vision judged])	operationprovidermodel_of	prompt_ofoutput_textr*   r+   r0   r1   c                    t        j                  |      j                  d      }|ddd|ddd| dgd	gd
dddt        diddd}t        j
                  j                  t        t        j                  |      j                  d      d|  ddd      }t        j                  }t        j                  }d}	t        d|dz         D ]`  }
	 t        j
                  j                  ||      5 }t        j                   |j#                         j                  d            }ddd        n t)        d'|	       j=                  d(      }t?        |t@              r%|jC                         rt        j                   |      S |j=                  d)      }t?        |tD              r|D ]  }t?        |tF              s|j=                  d*      }t?        |tD              s6|D ]s  }t?        |tF              s|j=                  d+      d(k(  s)|j=                  d,      }t?        |t@              sK|jC                         s\t        j                   |      c c S   t)        d-      # 1 sw Y   ?xY w# t        j$                  j&                  $ r}|j#                         j                  dd      }t)        d|j*                   d|       }	d d!lm}  ||	|j*                  |"       d d#lm} |j*                  d$v r+|
|k  r& ||	      st3        j4                  d|
z         Y d}~U|	|d}~wt        j$                  j6                  t8        j:                  f$ r<}|}	|
|k  rt3        j4                  d|
z         Y d}~t)        d%| d&|       |d}~ww xY w).u  Call OpenAI Responses API with image input for vision analysis.

    ★기록: 이 경로는 urllib 로 Responses API 를 직접 쳐서 litellm 콜백이 안
    닿는다 — 감싸지 않으면 VLM 판정이 Opik 에 한 줄도 안 남는다(2026-08-07).
    asciimessageuser
input_text)r
   textinput_imagezdata:image/png;base64,)r
   	image_url)r
   rolecontentformatjson_schemar   T)r
   namestrictschemag?F)r+   inputr<   temperaturestorezutf-8zBearer zapplication/json)AuthorizationzContent-TypePOST)dataheadersmethodN      )timeoutreplace)errorszOpenAI Vision API error z: r   )mark_openai_failure)statusbody)is_key_level_failure>             zOpenAI Vision API failed after z
 retries: zOpenAI Vision API failed: r6   outputr@   r
   r<   z3OpenAI Vision response did not include output_text.)$base64	b64encodedecoder   urllibrequestRequestOPENAI_API_URLjsondumpsencoder   llm_timeout_validationllm_max_retriesrangeurlopenloadsreaderror	HTTPErrorRuntimeErrorcodeapp.core.openai_keysrS   rV   timesleepURLErrorsocketrP   get
isinstancestrstriplistdict)r*   r+   r0   r1   b64_datarU   reqrP   max_retries
last_errorattemptresppayloadexc
error_textrS   rV   r6   r\   itemr@   partr<   s                          r"   _call_openai_visionr   6   sF     ,33G<H  ");? -'=hZ%H

 %*+	
 1D6 ..
 
 ZZ$$W-&wi0.
  ! C --G**K#'JK!O,	h''W'=**TYY[%7%7%@A >	 -< 7
|DEE ++m,K+s#(9(9(;zz+&&[["F&$DdD)hhy)Ggt,dD)dhhv.>-.O88F+D!$,#zz$//	    L
MMc >= ||%% 	&**79*EJ%(@
"ZL&YZJ A388*>
 B55;.0<

1w;'#%%%v~~6 	hJ+%

1w;'!@ZX[W\]^dgg	hsI   !I*-3I I*I'	"I**NBL L  0NN5NNentity_infoc                    | j                  dd      }t        |t              r	 t        j                  |      }n|}|j                  dg       }|sJt        |t              r:|j                         D ]'  \  }}t        |t              s|j                  |       ) |sydj                  d |D              S # t        j
                  $ r i }Y w xY w)z+Format stable traits into a readable block.stable_traitsz{}visual_anchor_traitsz- (no specific traits listed)
c              3   &   K   | ]	  }d |   yw)z- Nr)   ).0traits     r"   	<genexpr>z'_format_traits_block.<locals>.<genexpr>   s     =}er%\}s   )
rv   rw   rx   rd   rk   JSONDecodeErrorr{   r   appendjoin)r   r   traits_datavisual_traits_kvs         r"   _format_traits_blockr      s    OOOT:M-%	**]3K $OO$:B?MZT: &&(EB!S!$$Q' ) .99=}=== ## 	K	s   B5 5CCc            
           e Zd ZdZ	 	 ddedz  dedz  ddfdZdedeeef   deeef   fd	Z	ded
eeef   de
e   deeef   fdZdedeeef   deeef   fdZy)ImageValidatoru7   GPT-5.4 비전으로 생성된 이미지 품질 판정.Nr*   r+   r   c                 D    || _         |xs t        j                  | _        y r(   )_explicit_keyr   openai_model_model)selfr*   r+   s      r"   __init__zImageValidator.__init__   s     %4x44r$   r0   r   c           
      ~    t               st        d      t        d      }|j                  |j	                  dd      |j	                  dd      |j	                  dd      t        |      |j	                  dd      	      t         fd
 j                        }|j	                  dd      }|t        k\  |d<   |S )uI  참조 이미지가 엔티티 설명과 일치하는지 검증.

        Args:
            image_bytes: PNG image bytes to validate.
            entity_info: Dict with keys: name, entity_type, description, stable_traits.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        6OpenAI API key is not configured for image validation.zreference_validation.mdrC    entity_typer   world_contextNot provided)entity_namer   r   traits_blockr   c                 6    t        | j                        S N)r*   r+   r0   r1   r   r   r   r0   promptr   s    r"   r.   z9ImageValidator.validate_reference_image.<locals>.<lambda>       *kk'"	r$   	fixed_keyr   r   r   )	_has_openai_keyro   r#   rA   rv   r   _with_key_failoverr   PASS_THRESHOLD)r   r0   r   templateresultr   r   s   ``    @r"   validate_reference_imagez'ImageValidator.validate_reference_image   s      WXX 9:#3#r:#r:-k:%///>J ! 
 $ ((
 

7A& N2xr$   
scene_infoentity_namesc           
          t               st        d      t        d      }|j                  |j	                  dd      |j	                  dd      |j	                  dd      |rdj                  |      nd|j	                  d	d
            t         fd j                        }|j	                  dd      }|t        k\  |d<   |S )u  씬 이미지가 프롬프트 및 엔티티와 일치하는지 검증.

        Args:
            image_bytes: PNG image bytes to validate.
            scene_info: Dict with keys: scene_heading, beat_title, still_frame_prompt.
            entity_names: List of entity names expected to be visible.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        r   zscene_validation.mdscene_headingr   
beat_titlestill_frame_promptz, z(none)r   r   )r   r   scene_promptr   r   c                 6    t        | j                        S r   r   r   s    r"   r.   z5ImageValidator.validate_scene_image.<locals>.<lambda>  r   r$   r   r   r   r   )	r   ro   r#   rA   rv   r   r   r   r   )r   r0   r   r   r   r   r   r   s   ``     @r"   validate_scene_imagez#ImageValidator.validate_scene_image   s       WXX 56$.."=!~~lB7#(<bA4@<0h$...I ! 
 $ ((
 

7A& N2xr$   pdf_page_imageexpected_contentc           
          t               st        d      t        d      }|j                  |j	                  dd      t        |j	                  dd            t        |j	                  dd            |j	                  d	d
            t         fd j                        }|j	                  dd      }|t        k\  |d<   |S )u\  PDF 페이지가 올바르게 렌더링되었는지 검증.

        Args:
            pdf_page_image: PNG image bytes of the rendered PDF page.
            expected_content: Dict with keys: page_number, has_image, has_text, section_title.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        r   zpdf_validation.mdpage_numberunknown	has_imageFhas_textTsection_titler   )r   r   r   r   c                 6    t        | j                        S r   r   )r   r   r   r   s    r"   r.   z2ImageValidator.validate_pdf_page.<locals>.<lambda>6  s    *kk*"	r$   r   r   r   r   )	r   ro   r#   rA   rv   rx   r   r   r   )r   r   r   r   r   r   r   s   ``    @r"   validate_pdf_pagez ImageValidator.validate_pdf_page  s      WXX 34(,,]IF*..{EBC)--j$?@*..C	 ! 
 $ ((
 

7A& N2xr$   )NN)__name__
__module____qualname____doc__rx   r   bytesr   r   r   r   r   r   r)   r$   r"   r   r      s    A # 5t5 Tz5 
	5'' #s(^' 
c3h	'R(( cN( 3i	(
 
c3h(T%% sCx.% 
c3h	%r$   r   c                      ddl m}   |        S )Nr   has_openai_key)rq   r   r   s    r"   r   r   C  s    3r$   r   c                $    ddl m}  || d|      S )uU   활성 슬롯 키로 부르고 키 수준 실패면 다음 슬롯으로 (2026-08-01).r   )call_with_key_failoverzimage_validator.vision)wherer   )rq   r   )fnr   r   s      r"   r   r   I  s    ;!
*iA Ar$   c                      ddl m}   |        S )uO   활성 키 슬롯 — 1차 필드를 직접 보면 보조 키가 안 쓰인다.r   
active_key)rq   r   r   s    r"   _active_openai_keyr   Q  s    /<r$   )r   r]   rd   ru   rr   urllib.errorr`   urllib.requesttypingr   r   r   app.core.configr   app.modules.llm.image_tracerr   r    rc   r   rx   __annotations__r   r#   r   r   r   r   boolr   r   r   r)   r$   r"   <module>r      sX         " " $ 4
6 !))$9%h'
	 =% 4S>  &3 &3 & *X;"*	,
hNhNhN hN 	hN
 
#s(^hN,
hNV>d38n > >,E EN  )- AC r$   