
    uj*                     B   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	 ddl
mZmZ ddlmZ ddlmZ dZdd	d
did
did
did
didddddd
diddg ddZdedefdZde	defdZ eddd d d      	 d&ded ed!edz  deeef   fd"       Z G d# d$      Zdefd%Zy)'u  PDF 유효성 검증 모듈 — GPT 비전으로 생성된 PDF의 품질을 검증.

prompt_loader 통합 (problems.md #14): hard-coded ``v1`` 디렉토리 직접 read 제거
→ ``prompt_loader.load_prompt`` 경유 (DB 우선 + numeric version 정렬 + #6 #13).
    N)Path)AnyDict)settings)traced_callpdf_validationobjectFtypebooleaninteger   
   )r
   minimummaximumarraystring)r
   items)text_readableimages_presentlayout_correctcaption_visibleoverall_qualityissues)r
   additionalProperties
propertiesrequiredfilenamereturnc                 X    ddl m} | j                  d      r| dd n| } |t        |      S )u   ``prompts/_base/pdf_validation`` 에서 stem 로드 (DB 우선).

    Raises FileNotFoundError 또는 RuntimeError (#6 strict mode) — caller 처리.
    r   )load_promptz.mdN)app.modules.prompt_loaderr    endswith_MODULE)r   r    stems      O/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/pdf_validator.py_load_promptr'   2   s.    
 6$--e48CR=(Dw%%    pdf_pathc                    ddl }ddl}|j                         5 }t        |      dz  }	 |j	                  ddddddd	d
t        |       t        |      g
ddd       |j                  j                         D ],  }|j                  dk(  s|j                         c cddd       S  	 	 t        |      dz  }|j	                  ddddddt        |       dt        |      g	ddd       |j                         r|j                         cddd       S 	 ddd       t        d      # |j                  t        f$ r Y w xY w# |j                  t        f$ r Y Ew xY w# 1 sw Y   t        d      xY w)zConvert the first page of a PDF to a PNG image.

    Uses fpdf2 to render a summary page since pypdf cannot rasterize.
    Falls back to reading the raw PDF bytes for vision API.
    r   Npagepdftoppmz-pngz-f1z-lz-r150T   )checkcapture_outputtimeoutz.pngzpage.pngsipsz-sformatpngz--resampleHeight2000z--outz_Could not convert PDF to image. Install poppler (pdftoppm) or run on macOS with sips available.)
subprocesstempfileTemporaryDirectoryr   runstrparentiterdirsuffix
read_bytesCalledProcessErrorFileNotFoundErrorexistsRuntimeError)r)   r7   r8   tmpdirout_path	candidatepng_outs          r&   _pdf_first_page_to_pngrH   =   s    		$	$	&&<&(	NN##%MM #   &__446	##v-$//11+ 
'	&& 7	6lZ/GNN(E&MS\ #   ~~))+U 
'	&R  S 
'^ 	/ 3 --/@A 		* --/@A 		W 
'^ 	/ s`   EAD D "D #E%AD;E D85E7D88E;EEEEE)openaic                  "    t         j                  S N)r   openai_model)aks     r&   <lambda>rO   }   s
    h&;&;r(   c                     |S rK    )image_bytessystem_promptrM   rN   s       r&   rO   rO   ~   s    =r(   z[pdf judged])	operationprovidermodel_of	prompt_ofoutput_textrR   rS   _api_keyc                 Z    |ddl m}  | fdd      S |}|st        d      t        j                         j                  d      }t        j                  d	d
d| ddddgdgddddddt        did}t        j                  j                  d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.      }tA        |tB              r|jE                         s|j?                  d/      }tA        |tF              r|D ]  }tA        |tH              s|j?                  d0      }tA        |tF              s6|D ]^  }tA        |tH              s|j?                  d1      d.k(  s)|j?                  d2      }tA        |tB              sK|jE                         s\|}   |st        d3      t        j&                  |      S # 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t5        j6                  d!|z         Y d}~B|
|d}~wt        j*                  j8                  t:        j<                  f$ r<}|}
||	k  rt5        j6                  d!|z         Y d}~t        d+|	 d,|       |d}~ww xY w)4u   Call OpenAI vision API with an image for PDF validation.

    ★기록: urllib 직접 호출이라 litellm 콜백 밖이다 (2026-08-07).
    Nr   )call_with_key_failoverc                      t        |       S )N)rY   )_call_vision_api)_krR   rS   s    r&   rO   z"_call_vision_api.<locals>.<lambda>   s    ']RPr(   zpdf_validator.vision)wherez!OpenAI API key is not configured.asciiuserinput_imagezdata:image/png;base64,)r
   	image_url
input_textzValidate this webbook PDF page.)r
   text)rolecontentg?Fr4   json_schemapdf_validation_resultT)r
   namestrictschema)modelinstructionsinputtemperaturestorere   z#https://api.openai.com/v1/responseszutf-8zBearer zapplication/json)AuthorizationzContent-TypePOST)dataheadersmethodr      )r2   replace)errorszOpenAI API error z: )mark_openai_failure)statusbody)is_key_level_failure>             zOpenAI API failed after z
 retries: zOpenAI API failed: rX   outputrg   r
   re   z3OpenAI vision response did not include output text.)%app.core.openai_keysr[   rC   base64	b64encodedecoder   rL   VALIDATION_SCHEMAurllibrequestRequestjsondumpsencodellm_timeout_validationllm_max_retriesrangeurlopenloadsreaderror	HTTPErrorcoderz   r}   timesleepURLErrorsocketr2   get
isinstancer;   striplistdict)rR   rS   rY   r[   api_key	image_b64r|   r   r2   max_retries
last_errorattemptresppayloadexc
error_textrz   r}   rX   r   itemrg   partre   s   ``                      r&   r]   r]   |   s    ?%P(* 	* G>??  -44W=I &&%  !.'=i[%I
 !- A	
 %/+	
)D< nn$$-ZZ$$W-&wi0.
  % G --G**K#'JK!O,	a'''AT**TYY[%7%7%@A B	 -< 0=>> ++m,Kk3'{/@/@/BX&fd#!$-((9-!'40#D!$-$((62Bm2S#xx/%dC0TZZ\*.K! $  PQQ::k""g BA ||%% 	&**79*EJ%(9#((2j\&RSJ A388*>
 B55;.0<

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

1w;'!9+jQTPUVW]``	asI   =!J3I;J;J	 JN*%BL>;L>>0N*.N%N%%N*c                   P    e Zd ZdZdedeeef   fdZde	e   de	eeef      fdZ
y)PDFValidatoru+   PDF 품질 검증기 — GPT 비전 기반.r)   r   c                     |j                         st        d|       t        d      }t        |      }t	        ||      }t        |      |d<   |j                  |d<   |S )a  Validate a generated PDF using GPT vision.

        Converts the first page to an image, sends it to GPT for
        layout and quality analysis.

        Args:
            pdf_path: Path to the PDF file to validate.

        Returns:
            Validation result dict with text_readable, images_present,
            layout_correct, caption_visible, overall_quality, issues.
        zPDF not found: zvalidate.mdr)   pdf_filename)rB   rA   r'   rH   r]   r;   rj   )selfr)   rS   rR   results        r&   validatezPDFValidator.validate   sc      #ohZ$@AA$]3,X6!+}= !]z!)~r(   	pdf_pathsc                     g }|D ]%  }	 | j                  |      }|j                  |       ' |S # t        $ rE}|j                  t        |      |j                  t        |      dddddd| gd	       Y d}~sd}~ww xY w)zValidate multiple PDFs.

        Args:
            pdf_paths: List of PDF file paths.

        Returns:
            List of validation results.
        Fr   zValidation failed: )	r)   r   r   r   r   r   r   r   r   N)r   append	Exceptionr;   rj   )r   r   resultsr)   r   r   s         r&   validate_batchzPDFValidator.validate_batch  s     !Hx0v& "$   $'M(0!$S).*/*/+0+,%8#>"?
 s   "/	A=;A88A=N)__name__
__module____qualname____doc__r   r   r;   r   r   r   r   rQ   r(   r&   r   r      sF    5 $sCx. 4d	d38n	r(   r   c                      ddl m}   |        S )uO   활성 키 슬롯 — 1차 필드를 직접 보면 보조 키가 안 쓰인다.r   
active_key)r   r   r   s    r&   _active_openai_keyr   6  s    /<r(   rK   )r   r   r   r   r   urllib.errorr   urllib.requestpathlibr   typingr   r   app.core.configr   app.modules.llm.image_tracerr   r$   r   r;   r'   bytesrH   r]   r   r   rQ   r(   r&   <module>r      s*           $ 4
 ! ),!9-!9-"I.
 h'
% :&3 &3 &<T <e <~ '(;P')  v#v#v# Djv# 
#s(^	v#	)v#r; ;zC r(   