ó
    À‘´jâ%  ã                  ó   • S r SSKJr  SSKrSSKJr  SSKJrJrJ	r	  SSK
Jr  SSKJr  SSKJr  SS	KJr  SS
KJr  \ " S S5      5       rSrSrSrSrS\-  r S         SS jjr S         SS jjr S         SS jjrSS jrg)u  Hybrid retrieval over the pgvector playground schema.

Two stages. First, two rankings over one document's chunks are fused:
pgvector cosine distance (meaning â€” a paraphrased question still lands) and
Postgres full-text search over the same content (letter â€” a question that
names a statement or line item verbatim finds it even when the whole-chunk
embedding dilutes the match). Reciprocal rank fusion combines them without
tuned weights; a query whose keywords match nothing degrades to the pure
vector ranking.

Second, a ranker orders the fused list, because rank order alone was
measured insufficient: on the evaluation corpus the answering chunk sat as
deep as fused rank 63 (a yield buried under same-vocabulary neighbors) or
rank 32 (a guidance row losing to on-vocabulary sibling tables), and no
scoring change recovered them. Which ranker runs is the playground's `ranker`
setting, and both satisfy `ranking.ChunkRanker`.

With `haiku`, the default, a selection agent reads a wide, source-diverse
window of the fused ranking as one prompt and picks which chunks the question
actually needs; the final context is composed from its picks, backfilled from
fused order. The agent stage answered all eleven evaluation probes,
deterministically, where fused order alone answered eight. Before the agent
sees the window, line records are capped at two per parent table â€” one
table's near-identical rows otherwise fill the window and crowd out other
sources; each table's whole-table chunk is never capped, so the full table
remains available even when its rows are.

With `jev`, every page and every chunk of the whole fused list is judged in
its own request, so neither the window nor the cap applies: both exist only
because an LLM reads the pool as one prompt. The context is the chunks that
cleared the ranker's cut, highest score first, then the chunks whose grading
calls failed twice, in fused order, up to `k`. Nothing else is backfilled,
because a chunk added from fused order is one Jev graded as not bearing on
the question or one on a page Jev rejected.
é    )ÚannotationsN)Ú	dataclass)ÚDictÚListÚOptional)Úlogger)Údb)Úembed_query)ÚChunkRanker)Úget_settingsc                  óV   • \ rS rSr% S\S'   S\S'   S\S'   S\S'   S\S	'   S
rS\S'   Srg
)ÚRetrievedChunké3   ÚstrÚchunk_idÚ
chunk_typeÚintÚpageÚcontentÚfloatÚscoreNzOptional[str]Úparent_chunk_id© )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__annotations__r   Ú__static_attributes__r   ó    Ú8/home/mande/repo/quber/src/quber/playground/retrieval.pyr   r   3   s%   ‡ àƒMØƒOØ
ƒIØƒLØƒLØ%)€O�]Ö)r    r   é   éÈ   éZ   é   c              ƒ  ó&  #   • [        5       R                  R                  nUS:X  a$  SSKJn  Un[        X[        U5      I Sh  v•N nSnO1SSKJn	  U	n[        [        X[        U5      I Sh  v•N [        5      nSn U" X5      I Sh  v•N n
/ n[        5       nU
 HT  nX~   nUR                   U;  a,  UR#                  UR                   5        UR%                  U5        ['        U5      U:¼  d  MT    O   U(       aX  U HR  n['        U5      U:¼  a    OBUR                   U;  d  M&  UR#                  UR                   5        UR%                  U5        MT     [)        U5       VVs0 s H  u  noÿR                   US	-   _M     nnn[        R*                  " S
U['        U5      ['        U5      U Vs/ s H  nUUR                      PM     sn5        U$  GNˆ GNb GNI! [         a%  n[        R                  " SXK5        / SpŠ SnAGNqSnAff = fs  snnf s  snf 7f)zËThe playground's retrieval: the fused list, ordered by the configured ranker.

Falls back to fused order if the ranker fails, so a provider outage
degrades ranking quality instead of breaking retrieval.
Újevr   )ÚrankNF)ÚselectTz2{} ranker failed ({}); falling back to fused orderé   z6{} ranker sent {} of {} candidates; fused positions {})r   Ú
playgroundÚrankerÚquber.playground.rankingr(   Ú_fused_windowÚFUSED_LIMITÚquber.playground.selectionr)   Ú_cap_line_recordsÚWINDOWÚ
PARENT_CAPÚ	Exceptionr   ÚwarningÚsetr   ÚaddÚappendÚlenÚ	enumerateÚdebug)Údoc_keyÚqueryÚkÚtypesÚnamer(   r,   ÚpoolÚbackfillr)   ÚpickedÚexcÚoutÚseenÚjÚcÚnÚpositions                     r!   ÚretrieverK   T   s»  é € ô ‹>×$Ñ$×+Ñ+€Dàˆuƒ}Ý1àˆÜ" 7´;ÀÓF×FˆØ‰å5àˆÜ ¤}°WÄVÈUÓ'S×!SÔU_Ó`ˆØˆð$Ù˜eÓ*×*ˆð
 !#€CÜ“U€DÛˆØ‰GˆØ�:‰:˜TÓ!Ø�H‰H�Q—Z‘ZÔ Ø�J‰J�qŒMÜˆs‹8�q�=Ùñ ö ÛˆAÜ�3‹x˜1‹}ÙØ�z‰z Õ%Ø—‘˜Ÿ™Ô$Ø—
‘
˜1–ñ ô /8¸¬oÔ>ªo¡d a¨—
‘
˜A ™EÒ!©o€HÑ>Ü
‡L‚LØ@ØÜˆC‹ÜˆD‹	Ù'*Ó+¢s !ˆ�!—*‘*Ô	¡sÑ+ôð €JòQ Gò "Tò +øÜó $Ü�ŠÐKÈTÔWØ˜t–ûð$üó* ?ùò 	,ùs…   ‚AHÁGÁ(HÁ,GÁ-HÁ=G ÂGÂ	G ÂAHÃ//HÄ">HÅ HÅ<-HÆ)HÇ
HÇHÇG Ç
HÇG>Ç8HÇ>HÈHc                óB   • [         R                  " [        XX#5      5      $ ©N)ÚasyncioÚrunrK   )r<   r=   r>   r?   s       r!   Úretrieve_syncrP   �   s   € ô �;Š;”x °Ó9Ó:Ð:r    c              ƒ  ó.  #   • [         R                  " [        U5      I Sh  v•N nU(       a  SOSnSU SU S3nX@U[        [        US.nU(       a  X7S'   [
        R                  " 5        ISh  v•N nUR                  Xg5      I Sh  v•N R                  5       I Sh  v•N n	SSS5      ISh  v•N   W	 V
s/ s H,  n
[        U
S	   U
S
   U
S   U
S   [        U
S   5      U
S   S9PM.     sn
$  NÊ N Nh NT NF! , ISh  v•N  (       d  f       N[= fs  sn
f 7f)a  The top `window` chunks of the fused vector+keyword ranking.

Nothing here holds the event loop. The query embedding is a model forward
pass on this host, compute with nothing to await, so it runs in a worker
thread; the fused query is awaited on an async connection.
Nz!AND c.chunk_type = ANY(%(types)s)Ú a)  
        WITH vec AS (
            SELECT c.id, row_number() OVER (ORDER BY c.embedding <=> %(qvec)s::vector) AS rank
            FROM ade_playground.chunks c
            JOIN ade_playground.documents d ON d.id = c.document_id
            WHERE d.doc_key = %(doc_key)s AND c.embedding IS NOT NULL u¼  
            ORDER BY c.embedding <=> %(qvec)s::vector
            LIMIT %(pool)s
        ),
        -- OR-joined lexemes of the question: AND semantics (websearch_to_tsquery)
        -- fails whole questions, since one filler word absent from a chunk
        -- ("find", "tell") kills the match. ts_rank_cd with log-length
        -- normalization (flag 1) then rewards the chunk covering the most
        -- question terms, densest first â€” unnormalized, a boilerplate table
        -- repeating one query word hundreds of times (a subsidiary list
        -- repeating the company name) outranks a short exact-coverage line.
        oq AS (
            SELECT to_tsquery('english',
                COALESCE(NULLIF(array_to_string(
                    tsvector_to_array(to_tsvector('english', %(q)s)), ' | '), ''),
                'zzznomatchzzz')) AS q
        ),
        kw AS (
            SELECT c.id, row_number() OVER (
                       ORDER BY ts_rank_cd(c.content_tsv, oq.q, 1) DESC
                   ) AS rank
            FROM ade_playground.chunks c
            JOIN ade_playground.documents d ON d.id = c.document_id, oq
            WHERE d.doc_key = %(doc_key)s AND c.embedding IS NOT NULL a3  
              AND c.content_tsv @@ oq.q
            LIMIT %(pool)s
        ),
        fused AS (
            SELECT COALESCE(vec.id, kw.id) AS id,
                   COALESCE(1.0 / (%(rrf)s + vec.rank), 0) +
                   COALESCE(1.0 / (%(rrf)s + kw.rank), 0) AS score
            FROM vec FULL OUTER JOIN kw ON kw.id = vec.id
        )
        SELECT c.chunk_id, c.chunk_type, c.page, c.content, fused.score, c.parent_chunk_id
        FROM fused JOIN ade_playground.chunks c ON c.id = fused.id
        ORDER BY fused.score DESC
        LIMIT %(cand)s
    )Úqvecr<   ÚqrA   ÚrrfÚcandr?   r   r*   r%   é   é   é   )r   r   r   r   r   r   )rN   Ú	to_threadr
   ÚPOOLÚRRF_Kr	   Úconnect_asyncÚexecuteÚfetchallr   r   )r<   r=   Úwindowr?   rS   Útype_filterÚsqlÚparamsÚconnÚrowsÚrs              r!   r.   r.   “   s%  é € ô ×"Ò"¤;°Ó6×6€DÞ9>Ñ5ÀB€KðGð
 HSÀmð TGð. HSÀmð Tð9*€CðV °UÄDÔQVÐ`fÑg€FÞØˆw‰Ü×Ò×!Ô! TØ ŸL™L¨Ó5×5×?Ñ?ÓA×Aˆ÷ "×!ñ ó
ò ˆAô 	Ø�q‘TØ˜‘tØ�1‘Ø�a‘DÜ˜˜!™“+Ø˜a™Dô	
ñ ñ
ð 
ñg 7ñb "Ù5ÑA÷ "×!×!Ð!üò
ùsˆ   ‚D¡C,¢ADÁ.C.Á/DÁ2C6ÂC0ÂC6ÂC2ÂC6Â"DÂ-C4Â.DÂ63DÃ)DÃ.DÃ0C6Ã2C6Ã4DÃ6DÃ<C?Ã=DÄ	Dc                óì   • 0 n/ nU  Hi  nUR                   S:X  aE  UR                  (       a4  UR                  UR                  S5      nXQ:¼  a  MG  US-   X$R                  '   UR                  U5        Mk     U$ )uô   Keep at most `cap` line records per parent table, in fused order.

Everything that is not a line record â€” prose, figures, and each table's
whole-table chunk â€” passes through untouched, so a capped table's full
content is still in the pool.
Ú	line_itemr   r*   )r   r   Úgetr8   )ÚcandsÚcaprF   rE   rH   rI   s         r!   r1   r1   Ü   sn   € ð €DØ "€CÛˆØ�<‰<˜;Ó&¨1×+<×+<Ø—‘˜×*Ñ*¨AÓ.ˆAØ‹xÙØ&'¨!¡eˆD×"Ñ"Ñ#Ø�
‰
�1Žñ ð €Jr    )é
   N)
r<   r   r=   r   r>   r   r?   úOptional[List[str]]ÚreturnúList[RetrievedChunk]rM   )
r<   r   r=   r   r`   r   r?   rm   rn   ro   )rj   ro   rk   r   rn   ro   )Ú__doc__Ú
__future__r   rN   Údataclassesr   Útypingr   r   r   Úlogurur   Úquber.playgroundr	   Úquber.playground.embeddingr
   r-   r   Úquber.settingsr   r   r\   r[   r2   r3   r/   rK   rP   r.   r1   r   r    r!   Ú<module>rx      s  ðñ"õH #ã Ý !ß 'Ñ 'å å Ý 2Ý 0Ý 'ð ÷*ð *ó ð*ð 	€Ø
€ð
 
€ð
 €
ð �$‰h€ð IMð6Øð6Øð6Ø!$ð6Ø2Eð6àõ6ðt IMð;Øð;Øð;Ø!$ð;Ø2Eð;àõ;ð IMðFØðFØðFØ&)ðFØ2EðFàõFõRr    