Ë
    àAæja  ã                  ó¨   — d Z ddlm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  ej                  d«      ZdZh d£Zd	„ Zdd
„Z	 d	 	 	 	 	 dd„Zy)a  Semantic layer: LLM judge over time-aligned (original ASR, dubbed ASR, dub subtitle) triples.

The LLM never computes timestamps; it only judges line pairs that the timing layer already aligned,
so every semantic issue inherits exact times from the deterministic layer.
é    )ÚannotationsN)ÚThreadPoolExecutor)ÚPathÚdubevalaŠ  You are a QA reviewer for film dubbing from {src} into {tgt}. The human reviewer speaks
neither language, so you are their only check on meaning. Each item is one original utterance with:
`orig` (ASR of the original {src} audio), `dub` (ASR of the dubbed {tgt} audio placed over the same
time window), and optionally `script` (the dub's own subtitle text for that window). ASR may contain
small spelling errors and phonetic name misspellings; do not report those. Neighbouring items may
legitimately shift a few words across a boundary; only report content loss/addition that is not
covered by the adjacent items. Judge against natural, idiomatic {tgt}, not a literal translation.

For EVERY item return an English rendering of `orig` and of `dub` (copy as-is if already English).
Then report only real problems:
- MEANING_MISMATCH: dub says something different from orig
- MISSING_CONTENT: a meaningful part of orig is absent in dub (and not in neighbouring items)
- ADDED_CONTENT: dub adds meaning not in orig
- TONE_MISMATCH: register/emotion clearly different (e.g. insult vs polite, joke lost)
- NAME_OR_TERM_ERROR: names/terms changed or wrong
- UNTRANSLATED: dub is still in {src} or in a language other than {tgt}
- SCRIPT_DEVIATION: spoken dub clearly differs in meaning from `script`
Severity: critical = plot/joke broken or wrong language; major = noticeable meaning loss; minor = nuance.

Return JSON: {{"items":[{{"i":<index>,"orig_en":"...","dub_en":"...","issues":[{{"category":"...",
"severity":"critical|major|minor","confidence":0.0-1.0,"explanation":"English, one sentence",
"fix_hint":"concrete instruction for the dubbing system"}}]}}]}}

Items:
{items}>   ÚUNTRANSLATEDÚADDED_CONTENTÚTONE_MISMATCHÚMISSING_CONTENTÚMEANING_MISMATCHÚSCRIPT_DEVIATIONÚNAME_OR_TERM_ERRORc                 óŠ  — ddl m}   | t        j                  j	                  d«      xs4 t        j                  j	                  d«      xs t        j                  d   t        j                  j	                  d«      xs@ t        j                  j	                  d«      xs t        j                  j	                  d«      d	d¬
«      S )Nr   ©ÚOpenAIÚDUBEVAL_LLM_API_KEYÚOMNIROUTE_API_KEYÚOPENAI_API_KEYÚDUBEVAL_LLM_BASE_URLÚOMNIROUTE_BASE_URLÚOPENAI_BASE_URLé–   )Úapi_keyÚbase_urlÚtimeoutÚmax_retries)Úopenair   ÚosÚenvironÚgetr   s    ú1/root/repos/pocs/video-evals/src/dubeval/judge.pyÚ_clientr!   0   sŒ   € Ýáœ"Ÿ*™*Ÿ.™.Ð)>Ó?ò 2Ä2Ç:Á:Ç>Á>ÐReÓCfò 2Ü—Z‘ZÐ 0Ñ1ÜŸ:™:Ÿ>™>Ð*@ÓAò 7ÄRÇZÁZÇ^Á^ÐThÓEiò 7Ü—Z‘Z—^‘^Ð$5Ó6ÀÐQRôTð Tó    c           	     óì  — t         j                  ||t        j                  |dd¬«      ¬«      }| j                  j
                  j                  |d|dœgddid¬	«      }|j                  d   j                  j                  xs d
j                  «       }|j                  d«      j                  d«      j                  d«      }t        j                  |«      j                  dg «      }|st        d«      ‚|S )NFr   )Úensure_asciiÚindent)ÚsrcÚtgtÚitemsÚuser)ÚroleÚcontentÚtypeÚjson_object)ÚmodelÚmessagesÚresponse_formatÚtemperaturez{}z```jsonz```r(   zempty items)ÚPROMPTÚformatÚjsonÚdumpsÚchatÚcompletionsÚcreateÚchoicesÚmessager+   ÚstripÚremoveprefixÚremovesuffixÚloadsr   Ú
ValueError)	Úclientr.   r(   r&   r'   r+   ÚrÚtextÚ	items_outs	            r    Ú_chunkrD   9   sÙ   € Ü�m‰m ¨´D·J±J¸uÐSXÐabÔ4cˆmÓd€GØ�‰×Ñ×&Ñ&¨UÀvÐZaÑ>bÐ=cØ8>ÀÐ7NÐ\]ð 	'ó 	_€Aà�I‰I�a‰L× Ñ ×(Ñ(Ò0¨D×7Ñ7Ó9€DØ×Ñ˜YÓ'×4Ñ4°UÓ;×HÑHÈÓO€DÜ—
‘
˜4Ó ×$Ñ$ W¨bÓ1€IÙÜ˜Ó'Ð'ØÐr"   c           	     óæ  ‡‡‡‡‡‡‡— t        «       Š|j                  «       r#t        j                  |j	                  «       «      ni Št        dt        | «      |«      D �cg c]
  }| |||z    ‘Œ }	}d„ Šˆˆˆˆˆˆˆfd„}
i }t        |«      5 }|j                  |
|	«      D ]‹  \  }}|€Œ	|‰|<   |D ]y  }t        |t        «      sŒt        |j                  d«      t        «      sŒ4|j                  d«      xs g D �cg c]  }|j                  d«      t        v sŒ|‘Œ c}|d<   |||d   <   Œ{ Œ� 	 ddd«       |j                  t        j                  ‰d¬	«      «       |S c c}w c c}w # 1 sw Y   Œ;xY w)
zõrows: [{"i", "orig", "dub", "script"}]. Returns {i: {"orig_en", "dub_en", "issues"}}.

    Each chunk tries every model in order (2 attempts each); results are cached per input hash so a
    rerun only re-asks chunks that failed or changed.
    r   c                ó�   — t        j                  t        j                  | dd¬«      j	                  «       «      j                  «       S )NFT)r$   Ú	sort_keys)ÚhashlibÚsha1r4   r5   ÚencodeÚ	hexdigest)Úcs    r    Úkeyzjudge.<locals>.keyP   s0   € Ü�|‰|œDŸJ™J q°uÈÔM×TÑTÓVÓW×aÑaÓcÐcr"   c           
     ó”  •—  ‰| «      }|‰v r|‰|   fS d }‰	D ]'  }t        d«      D ]  }	 |t        ‰|| ‰
‰«      fc c S  Œ) ‰j                  d| d   d   › d	| d
   d   › d|› d�«       t        j                  ‰d
   «       |d fS # t        $ r8}|› dt        |«      d d › �}t	        j
                  d|dz   z  «       Y d }~Œ›d }~ww xY w)Né   z: é    é   é   zLLM judge failed for lines r   Úiú-éÿÿÿÿz (ú))	ÚrangerD   Ú	ExceptionÚstrÚtimeÚsleepÚappendÚlogÚwarning)rL   ÚkÚlastr.   ÚattemptÚeÚcachedr@   rM   Úmodelsr&   r'   Úwarningss         €€€€€€€r    Úrunzjudge.<locals>.runS   sø   ø€ Ù�‹FˆØ�‰;Ø�f˜Q‘i�<ÐØˆØò 	2ˆEÜ  ›8ò 2�ð2Øœf V¨U°A°s¸CÓ@Ð@Ô@ñ2ð	2ð 	�‰Ð5°a¸±d¸3±i°[ÀÀ!ÀBÁ%ÈÁ*ÀÈRÐPTÈvÐUVÐWÔXÜ�‰�H˜R‘LÔ!Ø�$ˆwˆøô !ò 2Ø#˜W B¤s¨1£v¨d¨s | nÐ5�DÜ—J‘J˜q G¨a¡KÑ0×1Ñ1ûð2ús   «BÂ	CÂ.CÃCNrS   ÚissuesÚcategoryF)r$   )r!   Úexistsr4   r>   Ú	read_textrW   Úlenr   ÚmapÚ
isinstanceÚdictr   ÚintÚCATSÚ
write_textr5   )Úrowsrd   r&   r'   Úcachere   ÚchunkÚworkersr_   Úchunksrf   ÚoutÚexÚresÚitemÚxrc   r@   rM   s    ``` `          @@@r    Újudger|   E   sS  þ€ ô ‹Y€FØ49·L±L´N”4—:‘:˜eŸo™oÓ/Ô0È€FÜ).¨q´#°d³)¸UÓ)CÖD Aˆd�1�Q˜‘YÒÐD€FÐDòd÷ò ð  €CÜ	˜GÓ	$ð *¨Ø—f‘f˜S &Ó)ò 	*‰FˆAˆsØˆ{ØØˆF�1‰IØò *�Ü˜d¤DÕ)¬j¸¿¹À#»ÌÕ.LØ15·±¸(Ó1CÒ1IÀrÖ%g¨AÈQÏUÉUÐS]ÓM^ÔbfÒMf¢aÒ%g�D˜‘NØ%)�C˜˜S™	’Nñ*ñ		*÷*ð 
×Ñ”T—Z‘Z °UÔ;Ô<Ø€JùòA Eùò: &h÷*ð *ús6   ÁEÂ6E'ÃE'Ã"E'Ã9E"
ÄE"
ÄE'Å"E'Å'E0)
r.   rY   r(   ú
list[dict]r&   rY   r'   rY   Úreturnr}   )é
   rQ   )rr   r}   rd   ú	list[str]r&   rY   r'   rY   rs   r   re   r€   rt   ro   ru   ro   r~   zdict[int, dict])Ú__doc__Ú
__future__r   rH   r4   Úloggingr   rZ   Úconcurrent.futuresr   Úpathlibr   Ú	getLoggerr]   r2   rp   r!   rD   r|   © r"   r    ú<module>rˆ      sq   ðñõ
 #ã Û Û Û 	Û Ý 1Ý à€g×Ñ˜	Ó"€ð
€ò4,€òTó	ð +,ð)Øð)Ø$'ð)Ø0?ô)r"   