ó
    6Þ©j+  ã                   ó€  • S r SSKrSSKJs  Jr  SSKrSSKJ	r	  SSK
Jr  SSKrSSKJr  SSKJrJr  SSKJrJr  SSKJrJrJr  SS	KJrJr  SS
KJrJr  S\	S\	4S jrSS\	S\ S\\	   4S jjr!S r"S\	S\4S jr#S\	S\4S jr$S\	S\4S jr%S\	S\4S jr&S\	S\4S jr'S\	S\4S jr(S\	S\4S jr)S\	S\4S jr*S\	4S jr+g)a  End-to-end tests for the QUE-221 rebuild of CamelotLLMTableExtractor.

The real Camelot + pdf2image stack is stubbed; the test focuses on the
pipeline glue (parallel flavors -> classifier -> unifier -> correction)
and on the new provenance fields landing on ExtractedTable.
é    N)ÚPath)ÚList)ÚMonkeyPatch)ÚClassifierResultÚMockClassifier)ÚLLMTableCorrectionÚMockLLMClient)ÚMockUnifierÚUnifiedTableÚUnifierResult)ÚCamelotCandidateÚis_content_empty)ÚCamelotLLMTableExtractorÚpipelineÚtmp_pathÚreturnc                 ó2   • U S-  nUR                  S5        U$ )Nzdoc.pdfs	   %PDF-stub)Úwrite_bytes)r   Úpdfs     Ú:/home/mande/repo/quber/tests/core/test_camelot_pipeline.pyÚstub_pdfr      s   € Ø
�YÑ
€CØ‡O�O�LÔ!Ø€Jó    Úpagesc                 óŽ   • / n[        SUS-   5       H/  nU SUS S3-  nUR                  S5        UR                  U5        M1     U$ )Né   zpage-Ú04dz.pngs   ‰PNG

)Úranger   Úappend)r   r   ÚoutÚiÚps        r   Ústub_rendersr"      sP   € Ø€CÜ�1�e˜a‘iÖ ˆØ˜˜q ˜g TÐ*Ñ*ˆØ	�‰Ð*Ô+Ø�
‰
�1Žñ !ð €Jr   c                  óT  • Sn [        U 5      o(       d¢  SS[        R                  " 5       ;   d  [        R                  " [         5      (       a  [        R
                  " [         5      OS[        R
                  " U 5      [        R
                  " U5      S.-  n[        [        R                  " U5      5      eS =pSn [        U 5      o(       d¢  SS[        R                  " 5       ;   d  [        R                  " [         5      (       a  [        R
                  " [         5      OS[        R
                  " U 5      [        R
                  " U5      S.-  n[        [        R                  " U5      5      eS =pSn [        U 5      o(       d¢  SS[        R                  " 5       ;   d  [        R                  " [         5      (       a  [        R
                  " [         5      OS[        R
                  " U 5      [        R
                  " U5      S.-  n[        [        R                  " U5      5      eS =pSn [        U 5      o(       + o3(       d¢  SS[        R                  " 5       ;   d  [        R                  " [         5      (       a  [        R
                  " [         5      OS[        R
                  " U 5      [        R
                  " U5      S.-  n[        [        R                  " U5      5      eS =n =pS	n [        U 5      o(       + o3(       d¢  SS[        R                  " 5       ;   d  [        R                  " [         5      (       a  [        R
                  " [         5      OS[        R
                  " U 5      [        R
                  " U5      S.-  n[        [        R                  " U5      5      eS =n =pg )
NÚ z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}r   )Úpy0Úpy2Úpy4ú|   |   |
|---|---|
|   |   |z|  |  |
|  |  |ú| a | b |
|---|---|
| 1 | 2 |z0assert not %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}z| | x |
|---|---|)r   Ú@py_builtinsÚlocalsÚ
@pytest_arÚ_should_repr_global_nameÚ	_safereprÚAssertionErrorÚ_format_explanation)Ú@py_assert1Ú@py_assert3Ú@py_format5Ú@py_assert5Ú@py_format6s        r   Ú-test_is_content_empty_recognizes_empty_shellsr6   %   sû  € ØÐÔ˜BÓÐÕ×××××ÑÔ×××ÑÔ××Õ˜B××ÓÐ×××××ÒØ=Ð>ÔÐ=Ó>Ð>Õ>×>×>×>×>×>Ñ>Ô×>×>×>Ñ>Ô×>×>Õ>Ð=×>×>Ó>Ð>×>×>×>×>×>Ò>Ø.Ð/ÔÐ.Ó/Ð/Õ/×/×/×/×/×/Ñ/Ô×/×/×/Ñ/Ô×/×/Õ/Ð.×/×/Ó/Ð/×/×/×/×/×/Ò/Ø AÐBÔÐ AÓBÐBÔBÐBÕB×B×B×B×B×BÑBÔ×B×B×BÑBÔ×B×BÕBÐ A×B×BÓBÐB×B×B×B×B×BÔBØ 4Ð5ÔÐ 4Ó5Ð5Ô5Ð5Õ5×5×5×5×5×5Ñ5Ô×5×5×5Ñ5Ô×5×5Õ5Ð 4×5×5Ó5Ð5×5×5×5×5×5Õ5r   Úmonkeypatchc           
      ó0	  ^ ^^• [        T 5      nUR                  [        SU 4S j5        [        SSSSSS9m[        S	S
SSSS9mUR                  [        SUU4S j5        [	        [        5       [        [        SSS95      [        5       S9n[        R                  " UR                  U5      5      n[        U5      nSoeU:H  ow(       Gd  [        R                  " SU4SXV45      S[        R                   " 5       ;   d  [        R"                  " [        5      (       a  [        R$                  " [        5      OSS[        R                   " 5       ;   d  [        R"                  " U5      (       a  [        R$                  " U5      OS[        R$                  " U5      [        R$                  " U5      S.-  nSSU0-  n	['        [        R(                  " U	5      5      eS =n=pvUS   oªR*                  nS
oeU:H  ow(       d„  [        R                  " SU4SXV45      [        R$                  " U
5      [        R$                  " U5      [        R$                  " U5      S.-  nSSU0-  n	['        [        R(                  " U	5      5      eS =n
=n=pvUS   oªR,                  nSoeU:H  ow(       d„  [        R                  " SU4SXV45      [        R$                  " U
5      [        R$                  " U5      [        R$                  " U5      S.-  nSSU0-  n	['        [        R(                  " U	5      5      eS =n
=n=pvUS   oªR.                  nS oeULow(       d„  [        R                  " SU4SXV45      [        R$                  " U
5      [        R$                  " U5      [        R$                  " U5      S.-  nSSU0-  n	['        [        R(                  " U	5      5      eS =n
=n=pvUS   oªR.                  oUR0                  nSo·UL oÌ(       d™  [        R                  " S U4S!X{45      [        R$                  " U
5      [        R$                  " U5      [        R$                  " U5      [        R$                  " U5      S".-  n	S#S$U	0-  n['        [        R(                  " U5      5      eS =n
=n=n=pËg )%NÚrender_pagesc                  ó   >• [        TS5      $ ©Nr   ©r"   ©ÚaÚkwr   s     €r   Ú<lambda>ÚDtest_pipeline_filters_empty_shells_before_classify.<locals>.<lambda>0   ó   ø€ Ä<ÐPXÐZ[ÔC\r   úlattice-p1-0Úlatticer   g        r(   ©Úcandidate_idÚflavorÚpageÚaccuracyÚmarkdownústream-p1-0Ústreamg     €X@z)| col | val |
|---|---|
| Revenue | 100 |Úrun_camelot_flavors_parallelc                  ó   >• TT/$ ©N© )r>   r?   Úempty_shellÚreals     €€r   r@   rA   C   s
   ø€ ˜+ tÑ,r   Tzmock-yes©Úis_tableÚreason©Ú
llm_clientÚ
classifierÚunifier©ú==©z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sÚlenr   ©r%   Úpy1Úpy3Úpy6úassert %(py8)sÚpy8r   ©z.%(py3)s
{%(py3)s = %(py1)s.flavor
} == %(py6)s©r_   r`   ra   ©z8%(py3)s
{%(py3)s = %(py1)s.camelot_accuracy
} == %(py6)s)úis not)z?%(py3)s
{%(py3)s = %(py1)s.classifier_decision
} is not %(py6)s©Úis)zY%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.classifier_decision
}.is_table
} is %(py8)s)r_   r`   Úpy5rc   zassert %(py10)sÚpy10)r   Úsetattrr   r   r   r	   r   r   r
   ÚasyncioÚrunÚextract_tablesr]   r,   Ú_call_reprcomparer*   r+   r-   r.   r/   r0   rG   Úcamelot_accuracyÚclassifier_decisionrT   )r   r7   r   Ú	extractorr   Ú@py_assert2r4   Ú@py_assert4Ú@py_format7Ú@py_format9Ú@py_assert0Ú@py_assert7Ú@py_assert6Ú@py_format11rQ   rR   s   `             @@r   Ú2test_pipeline_filters_empty_shells_before_classifyr|   -   sÞ  ú€ Ü
�8Ó
€Cà×Ñœ .Ô2\Ô]ä"Ø#ØØØØ2ñ€Kô Ø"ØØØØ>ñ€Dð ×ÑÜØ&Ý,ôô )Ü “?Ü!Ô"2¸DÈÑ"TÓUÜ“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€Cäˆs‹8Ð�qÐ�q‰=×××Óˆ8×××××ÕŒ3×××ÑŒ3×××××Öˆs×××Ñˆs××Õˆ8××Ó�q××××××ÓØˆq‰6Ð$�=‰=Ð$˜HÐ$˜HÑ$×$×$×$Ò$ˆ=×$×$Ô$ˆ6×$×$Ó$ˆ=×$×$Ó$˜H×$×$×$×$×$×$Õ$Øˆq‰6Ð*×"Ñ"Ð* dÐ* dÑ*×*×*×*Ò*Ð"×*×*Ô*ˆ6×*×*Ó*Ð"×*×*Ó* d×*×*×*×*×*×*Õ*Øˆq‰6Ð1×%Ñ%Ð1¨TÐ1¨TÐ1×1×1×1Ò1Ð%×1×1Ô1ˆ6×1×1Ó1Ð%×1×1Ó1¨T×1×1×1×1×1×1Õ1Øˆq‰6Ð6×%Ñ%Ð6×.Ñ.Ð6°$Ð6°$Ð6×6×6×6Ò6Ð.×6×6Ô6ˆ6×6×6Ó6Ð%×6×6Ó6Ð.×6×6Ó6°$×6×6×6×6×6×6×6Ð6r   c           
      óŒ  ^ • [        T 5      nUR                  [        SU 4S j5        UR                  [        SS 5        [        [	        5       [        [        SSS95      [        5       S9n[        R                  " UR                  U5      5      n/ oTU:H  of(       d¥  [        R                  " S	U4S
XE45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      S.-  nSSU0-  n[#        [        R$                  " U5      5      eS =peg )Nr9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   Ú:test_pipeline_drops_classifier_negatives.<locals>.<lambda>V   rB   r   rM   c                  ó   • [        SSSSSS9/$ )NrC   rD   r   g      E@z!| Note | (1) Some footnote text |rE   ©r   ©r>   r?   s     r   r@   r   Z   s   € ÜØ+Ø ØØØ<ññ
r   FÚfootnoterS   rV   rZ   ©z%(py0)s == %(py3)sr   ©r%   r`   úassert %(py5)srj   )r   rl   r   r   r	   r   r   r
   rm   rn   ro   r,   rp   r*   r+   r-   r.   r/   r0   ©	r   r7   r   rs   r   rt   r1   Ú@py_format4r5   s	   `        r   Ú(test_pipeline_drops_classifier_negativesr‰   T   sÔ   ø€ Ü
�8Ó
€CØ×Ñœ .Ô2\Ô]Ø×ÑÜØ&ñ	
ôô )Ü “?Ü!Ô"2¸EÈ*Ñ"UÓVÜ“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CØÐ�"‰9×××Òˆ3×××××Õˆ3×××Ñˆ3××Õ�"××××××Òr   c                 ó:  ^ ^• [        T 5      nUR                  [        SU 4S j5        [        SSSSSS9mUR                  [        S	U4S
 j5        [	        SSS/ SS9n[        [        US9[        5       [        5       S9n[        R                  " UR                  U5      5      n[        U5      nSovU:H  oˆ(       Gd  [        R                  " SU4SXg45      S[        R                   " 5       ;   d  [        R"                  " [        5      (       a  [        R$                  " [        5      OSS[        R                   " 5       ;   d  [        R"                  " U5      (       a  [        R$                  " U5      OS[        R$                  " U5      [        R$                  " U5      S.-  n	SSU	0-  n
['        [        R(                  " U
5      5      eS =n=p‡US   o»R*                  nSovUL oˆ(       d„  [        R                  " SU4SXg45      [        R$                  " U5      [        R$                  " U5      [        R$                  " U5      S.-  n	SSU	0-  n
['        [        R(                  " U
5      5      eS =n=n=p‡US   o»R,                  nSovU:H  oˆ(       d„  [        R                  " SU4SXg45      [        R$                  " U5      [        R$                  " U5      [        R$                  " U5      S.-  n	SSU	0-  n
['        [        R(                  " U
5      5      eS =n=n=p‡g )Nr9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   Ú:test_pipeline_records_llm_corrected_flag.<locals>.<lambda>p   rB   r   rC   rD   r   ç      V@r)   rE   rM   c                  ó
   >• T/$ rO   rP   ©r>   r?   Úcands     €r   r@   rŒ   x   ó	   ø€ ÐTXÑSYr   ÚSampler$   ú'| a | b |
|---|---|
| 1 | 2 |
| 3 | 4 |)ÚtitleÚcaptionrJ   Ú	footnotesÚheader_rows)Ú
correctionrV   rZ   r\   r]   r   r^   rb   rc   r   Trh   ©z5%(py3)s
{%(py3)s = %(py1)s.llm_corrected
} is %(py6)sre   )z-%(py3)s
{%(py3)s = %(py1)s.title
} == %(py6)s)r   rl   r   r   r   r   r	   r   r
   rm   rn   ro   r]   r,   rp   r*   r+   r-   r.   r/   r0   Úllm_correctedr”   )r   r7   r   Úforced_correctionrs   r   rt   r4   ru   rv   rw   rx   r�   s   `           @r   Ú(test_pipeline_records_llm_corrected_flagrœ   n   sö  ù€ Ü
�8Ó
€CØ×Ñœ .Ô2\Ô]ÜØ#ØØØØ2ñ€Dð ×ÑœÐ"@ÔBYÔZä*ØØØ=ØØñÐô )Ü Ð,=Ñ>Ü!Ó#Ü“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CÜˆs‹8Ð�qÐ�q‰=×××Óˆ8×××××ÕŒ3×××ÑŒ3×××××Öˆs×××Ñˆs××Õˆ8××Ó�q××××××ÓØˆq‰6Ð'×ÑÐ' 4Ð' 4Ð'×'×'×'Ò'Ð×'×'Ô'ˆ6×'×'Ó'Ð×'×'Ó' 4×'×'×'×'×'×'Õ'Øˆq‰6Ð#�<‰<Ð#˜8Ð#˜8Ñ#×#×#×#Ò#ˆ<×#×#Ô#ˆ6×#×#Ó#ˆ<×#×#Ó#˜8×#×#×#×#×#×#Ö#r   c                 óÔ  ^ ^• [        T 5      nUR                  [        SU 4S j5        [        SSSSSS9mUR                  [        S	U4S
 j5        [	        [        5       [        5       [        5       S9n[        R                  " UR                  U5      5      n[        U5      nSoeU:H  ow(       Gd  [        R                  " SU4SXV45      S[        R                  " 5       ;   d  [        R                   " [        5      (       a  [        R"                  " [        5      OSS[        R                  " 5       ;   d  [        R                   " U5      (       a  [        R"                  " U5      OS[        R"                  " U5      [        R"                  " U5      S.-  nSSU0-  n	[%        [        R&                  " U	5      5      eS =n=pvUS   oªR(                  nSoeUL ow(       d„  [        R                  " SU4SXV45      [        R"                  " U
5      [        R"                  " U5      [        R"                  " U5      S.-  nSSU0-  n	[%        [        R&                  " U	5      5      eS =n
=n=pvg )Nr9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   ÚGtest_pipeline_no_correction_means_llm_corrected_false.<locals>.<lambda>Ž   rB   r   rC   rD   r   r�   r)   rE   rM   c                  ó
   >• T/$ rO   rP   r�   s     €r   r@   rŸ   –   r‘   r   rV   rZ   r\   r]   r   r^   rb   rc   r   Frh   r™   re   )r   rl   r   r   r   r	   r   r
   rm   rn   ro   r]   r,   rp   r*   r+   r-   r.   r/   r0   rš   )r   r7   r   rs   r   rt   r4   ru   rv   rw   rx   r�   s   `          @r   Ú5test_pipeline_no_correction_means_llm_corrected_falser¡   Œ   sw  ù€ Ü
�8Ó
€CØ×Ñœ .Ô2\Ô]ÜØ#ØØØØ2ñ€Dð ×ÑœÐ"@ÔBYÔZä(Ü “?Ü!Ó#Ü“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CÜˆs‹8Ð�qÐ�q‰=×××Óˆ8×××××ÕŒ3×××ÑŒ3×××××Öˆs×××Ñˆs××Õˆ8××Ó�q××××××ÓØˆq‰6Ð(×ÑÐ( 5Ð( 5Ð(×(×(×(Ò(Ð×(×(Ô(ˆ6×(×(Ó(Ð×(×(Ó( 5×(×(×(×(×(×(Ö(r   c                 óz  ^ • [        T 5      nUR                  [        SU 4S j5        UR                  [        SS 5        [        [	        5       [        5       [        5       S9n[        R                  " UR                  U5      5      n/ oTU:H  of(       d¥  [        R                  " SU4SXE45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                  " U5      OS[        R                  " U5      S	.-  nS
SU0-  n[!        [        R"                  " U5      5      eS =peg )Nr9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   Ú;test_pipeline_empty_camelot_returns_empty.<locals>.<lambda>¤   rB   r   rM   c                  ó   • / $ rO   rP   r‚   s     r   r@   r¤   ¥   s   € ÑSUr   rV   rZ   r„   r   r…   r†   rj   )r   rl   r   r   r	   r   r
   rm   rn   ro   r,   rp   r*   r+   r-   r.   r/   r0   r‡   s	   `        r   Ú)test_pipeline_empty_camelot_returns_emptyr¦   ¢   sÃ   ø€ Ü
�8Ó
€CØ×Ñœ .Ô2\Ô]Ø×ÑœÐ"@ÑBUÔVä(Ü “?Ü!Ó#Ü“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CØÐ�"‰9×××Òˆ3×××××Õˆ3×××Ñˆ3××Õ�"××××××Òr   c                 óÒ  ^ ^• [        T 5      nUR                  [        SU 4S j5        S[        S[        S[        S[
        4S jnU" SS	S
5      U" SSS
5      U" SSS
5      /mUR                  [        SU4S j5        [        [        5       [        5       [        5       S9n[        R                  " UR                  U5      5      nU Vs/ s H  ofR                  PM     nn/ SQo‡U:H  o™(       do  [        R                  " SU	4SXx45      [        R                   " U5      [        R                   " U5      S.-  n
SSU
0-  n[#        [        R$                  " U5      5      eS=n=p˜gs  snf )z€The graph's correction fan-out completes in arbitrary order; the
final step must restore pages-ascending, row-within-page order.r9   c                  ó   >• [        TS5      $ )Né   r<   r=   s     €r   r@   Ú?test_pipeline_output_ordered_by_page_then_row.<locals>.<lambda>´   rB   r   rG   rH   Úidxr   c                 ó:   • [        U  SU SU 3U USSU SU S3S9$ )Nz-pÚ-g     €V@z| col |
|---|
| pz |rE   r�   )rG   rH   r«   s      r   r�   Ú;test_pipeline_output_ordered_by_page_then_row.<locals>.cand¶   s>   € ÜØ"˜8 2 d V¨1¨S¨EÐ2ØØØØ*¨4¨&°°#°°bÐ9ñ
ð 	
r   rL   r©   r   rD   r   é   rM   c                  ó   >• T$ rO   rP   )r>   r?   Ú
candidatess     €r   r@   rª   Á   s   ø€ ÑS]r   rV   )r   r¯   r©   rZ   )z%(py1)s == %(py4)s)r_   r'   zassert %(py6)sra   N)r   rl   r   ÚstrÚintr   r   r	   r   r
   rm   rn   ro   rH   r,   rp   r.   r/   r0   )r   r7   r   r�   rs   r   Útrx   r2   rt   r3   rv   r±   s   `           @r   Ú-test_pipeline_output_ordered_by_page_then_rowrµ   °   s  ù€ ô �8Ó
€CØ×Ñœ .Ô2\Ô]ð
”Sð 
¤ð 
¬#ð 
Ô2Bô 
ñ �x  AÓ&©¨Y¸¸1Ó(=¹tÀHÈaÐQRÓ?SÐT€JØ×ÑœÐ"@ÔB]Ô^ä(Ü “?Ü!Ó#Ü“ñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CÙÓ šC�q�FŒF™CÐ-Ð ¢IÐ- IÑ-×-×-×-Ò-Ð ×-×-Ô-Ð ×-×-Ó- I×-×-×-×-×-×-Ô-ùÒ s   Ã
E$c           	      ó"  ^ ^^• [        T 5      nUR                  [        SU 4S j5        [        SSSSSS9m[        S	S
SSSS9mUR                  [        SUU4S j5        Sn[	        [        [        USS	/S9/S95      n[        [        5       [        5       US9n[        R                  " UR                  U5      5      n[        U5      nSo‡U:H  o™(       Gd  [        R                  " SU	4SXx45      S[         R"                  " 5       ;   d  [        R$                  " [        5      (       a  [        R&                  " [        5      OSS[         R"                  " 5       ;   d  [        R$                  " U5      (       a  [        R&                  " U5      OS[        R&                  " U5      [        R&                  " U5      S.-  n
SSU
0-  n[)        [        R*                  " U5      5      eS=n=p˜US   oÌR,                  owU:H  o™(       dº  [        R                  " SU	4SXs45      [        R&                  " U5      [        R&                  " U5      S[         R"                  " 5       ;   d  [        R$                  " U5      (       a  [        R&                  " U5      OSS.-  nSSU0-  n[)        [        R*                  " U5      5      eS=n=pyUS   oÌR.                  nSo‡U:H  o™(       d„  [        R                  " SU	4S Xx45      [        R&                  " U5      [        R&                  " U5      [        R&                  " U5      S!.-  n
SSU
0-  n[)        [        R*                  " U5      5      eS=n=n=p˜US   oÌR0                  nSo‡U:H  o™(       d„  [        R                  " SU	4S"Xx45      [        R&                  " U5      [        R&                  " U5      [        R&                  " U5      S!.-  n
SSU
0-  n[)        [        R*                  " U5      5      eS=n=n=p˜g)#zwTwo flavor outputs on one page flow through the unifier and come
back as one canonical table crediting both candidates.r9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   Ú=test_pipeline_unifies_multi_candidate_pages.<locals>.<lambda>Ð   rB   r   rC   rD   r   ç     ÀW@r)   rE   rK   rL   ç      T@r“   rM   c                  ó   >• TT/$ rO   rP   ©r>   r?   rD   rL   s     €€r   r@   r¸   à   ó   ø€ ÐT[Ð]cÑSdr   )rJ   Úsource_candidate_ids)ÚtablesrV   rZ   r\   r]   r   r^   rb   rc   Nr   )z0%(py3)s
{%(py3)s = %(py1)s.markdown
} == %(py5)sÚ
unified_md)r_   r`   rj   úassert %(py7)sÚpy7rd   re   rf   )r   rl   r   r   r
   r   r   r   r	   r   rm   rn   ro   r]   r,   rp   r*   r+   r-   r.   r/   r0   rJ   rG   rq   )r   r7   r   rÀ   rY   rs   r   rt   r4   ru   rv   rw   rx   r5   Ú@py_format8rD   rL   s   `              @@r   Ú+test_pipeline_unifies_multi_candidate_pagesrÄ   Ì   s“  ú€ ô �8Ó
€CØ×Ñœ .Ô2\Ô]äØ#ØØØØ2ñ€Gô Ø"ØØØØ=ñ€Fð ×ÑœÐ"@ÕBdÔeà=€JÜÜäØ'Ø*8¸-Ð)Hñðñ	
ó	€Gô )Ü “?Ü!Ó#Øñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CÜˆs‹8Ð�qÐ�q‰=×××Óˆ8×××××ÕŒ3×××ÑŒ3×××××Öˆs×××Ñˆs××Õˆ8××Ó�q××××××ÓØˆq‰6Ð(�?‰?Ð(˜jÑ(×(×(×(Ò(ˆ?×(×(Ô(ˆ6×(×(Ó(ˆ?×(×(×(×(×(Ô(˜j×(×(×(Ñ(˜j×(×(×(×(×(×(Õ(àˆq‰6Ð%�=‰=Ð%˜IÐ%˜IÑ%×%×%×%Ò%ˆ=×%×%Ô%ˆ6×%×%Ó%ˆ=×%×%Ó%˜I×%×%×%×%×%×%Õ%Øˆq‰6Ð*×"Ñ"Ð* dÐ* dÑ*×*×*×*Ò*Ð"×*×*Ô*ˆ6×*×*Ó*Ð"×*×*Ó* d×*×*×*×*×*×*Ö*r   c                 ó„  ^ ^^• [        T 5      nUR                  [        SU 4S j5        [        SSSSSS9m[        S	S
SSSS9mUR                  [        SUU4S j5         " S S5      n " S S[        5      nU" 5       n[        [        5       U" 5       US9n[        R                  " UR                  U5      5      n[        U5      nSo˜U	:H  oª(       Gd  [        R                  " SU
4SX‰45      S[        R                  " 5       ;   d  [        R                  " [        5      (       a  [        R                   " [        5      OSS[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      [        R                   " U	5      S.-  nSSU0-  n[#        [        R$                  " U5      5      eS=n=p©UR&                  nSS	//o­U
:H  oî(       dº  [        R                  " SU4SXÚ45      S[        R                  " 5       ;   d  [        R                  " U5      (       a  [        R                   " U5      OS[        R                   " U5      [        R                   " U
5      S.-  nS S!U0-  n[#        [        R$                  " U5      5      eS=n=pêg)"zúThe unifier LLM is order-sensitive: each page's candidates must
arrive in Camelot's output order (lattice before stream) even when
the classify fan-out completes in reverse (QUE-241 regression;
observed digit-altering unify merges on shuffled input).r9   c                  ó   >• [        TS5      $ r;   r<   r=   s     €r   r@   Ú\test_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.<lambda>  rB   r   rC   rD   r   r¹   r)   rE   rK   rL   rº   r“   rM   c                  ó   >• TT/$ rO   rP   r¼   s     €€r   r@   rÇ     r½   r   c                   ó*   • \ rS rSrSrS\S\4S jrSrg)Úgtest_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.SlowFirstClassifieri  z?Delays the lattice candidate so stream finishes classify first.rJ   r   c              “   ój   #   • SU;  a  [         R                  " S5      I S h  v•N   [        SSS9$  N7f)Nz	| 3 | 4 |gš™™™™™©?TÚmockrS   )rm   Úsleepr   )ÚselfrJ   s     r   ÚclassifyÚptest_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.SlowFirstClassifier.classify  s1   é € Ø (Ó*Ü—m’m DÓ)×)Ð)Ü#¨T¸&ÑAÐAñ *ùs   ‚ 3¢1£3rP   N)	Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r²   r   rÏ   Ú__static_attributes__rP   r   r   ÚSlowFirstClassifierrÊ     s   † ÙMð	B¨3ð 	BÐ3C÷ 	Br   r×   c                   ó8   ^ • \ rS rSrSU 4S jjrU 4S jrSrU =r$ )Ú^test_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.SpyUnifieri  c                 ó0   >• [         TU ]  5         / U l        g rO   )ÚsuperÚ__init__Úseen_orders)rÎ   Ú	__class__s    €r   rÜ   Úgtest_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.SpyUnifier.__init__  s   ø€ Ü‰GÑÔØ02ˆDÕr   c              “   ó²   >#   • U R                   R                  U Vs/ s H  o3R                  PM     sn5        [        TU ]  X5      I S h  v•N $ s  snf  N	7frO   )rÝ   r   rF   rÛ   Úunify)rÎ   Ú
page_imager±   ÚcrÞ   s       €r   rá   Údtest_pipeline_unifier_sees_camelot_order_despite_classify_completion_order.<locals>.SpyUnifier.unify!  sF   øé € Ø×Ñ×#Ñ#¹ZÓ$HºZ¸§^¤^¹ZÑ$HÔIÜ™™ zÓ>×>Ð>ùò %IÙ>ùs   ƒA�A²AÁAÁ
A)rÝ   )r   N)rÑ   rÒ   rÓ   rÔ   rÜ   rá   rÖ   Ú__classcell__)rÞ   s   @r   Ú
SpyUnifierrÙ     s   ø† ÷	3÷	?ó 	?r   ræ   rV   r¯   rZ   r\   r]   r   r^   rb   rc   N)z3%(py2)s
{%(py2)s = %(py0)s.seen_orders
} == %(py5)sÚspy)r%   r&   rj   rÁ   rÂ   )r   rl   r   r   r
   r   r	   rm   rn   ro   r]   r,   rp   r*   r+   r-   r.   r/   r0   rÝ   )r   r7   r   r×   ræ   rç   rs   r   rt   r4   ru   rv   rw   r1   r2   r5   rÃ   rD   rL   s   `                @@r   ÚJtest_pipeline_unifier_sees_camelot_order_despite_classify_completion_orderrè   ú   sÉ  ú€ ô �8Ó
€CØ×Ñœ .Ô2\Ô]äØ#ØØØØ2ñ€Gô Ø"ØØØØ=ñ€Fð ×ÑœÐ"@ÕBdÔe÷Bñ Bô?”[ô ?ñ ‹,€CÜ(Ü “?Ù&Ó(Øñ€Iô
 �+Š+�i×.Ñ.¨sÓ3Ó
4€CÜˆs‹8Ð�qÐ�q‰=×××Óˆ8×××××ÕŒ3×××ÑŒ3×××××Öˆs×××Ñˆs××Õˆ8××Ó�q××××××ÓØ�?‰?Ð? °Ð>Ð?Ð?Ð?Ñ?×?×?×?Ò?ˆ?×?×?×?×?×?Õ?ˆ3×?×?×?Ñ?ˆ3×?×?Õ?ˆ?×?×?Ó?Ð?×?×?×?×?×?×?Ô?r   c                 ó  • [        [        5       [        5       [        5       S9n[        R
                  " [        5         [        R                  " UR                  U S-  5      5        S S S 5        g ! , (       d  f       g = f)NrV   zmissing.pdf)
r   r	   r   r
   ÚpytestÚraisesÚFileNotFoundErrorrm   rn   ro   )r   rs   s     r   Ú&test_pipeline_raises_on_missing_sourcerí   0  sS   € Ü(Ü “?Ü!Ó#Ü“ñ€Iô
 
�ŠÔ(Õ	)Ü�Š�I×,Ñ,¨X¸Ñ-EÓFÔG÷ 
*×	)Ö	)ús   ¾)A0Á0
A>)r   ),rÕ   Úbuiltinsr*   Ú_pytest.assertion.rewriteÚ	assertionÚrewriter,   rm   Úpathlibr   Útypingr   rê   Ú_pytest.monkeypatchr   Úquber.agents.classifierr   r   Úquber.agents.llm_clientr   r	   Úquber.agents.unifierr
   r   r   Ú%quber.core.extractors.camelot.acquirer   r   Ú!quber.core.extractors.camelot.llmr   r   r   r³   r"   r6   r|   r‰   rœ   r¡   r¦   rµ   rÄ   rè   rí   rP   r   r   Ú<module>rú      s  ðñ÷ „ ƒÝ Ý ã Ý +ç Dß Eß IÑ Iß Tß Pð�tð  ô ñ˜4ð ¨ð °D¸±Jõ ò6ð$7Àð $7ÐT_ô $7ðN°tð È+ô ð4$°tð $È+ô $ð<)ÀDð )ÐWbô )ð,¸ð È;ô ð.¸Dð .È{ô .ð8++¸$ð ++È[ô ++ð\3@Øð3@Ø!,ô3@ðlH°Tõ Hr   