ó
    À‘´jóÖ  ã            
      ód	  • % S r SSKJr  SSKrSSKrSSKrSSKrSSKrSSKrSSK	r	SSK
Jr  SSKJrJrJrJrJrJr  SSKJrJrJr  SSKJr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#J$r$J%r%J&r&J'r'J(r(J)r)J*r*  SSK+J,r,  SSK-J.r/  SSK-J0r1  SSK2J3r3J4r4J5r5  SSK6J7r7J8r8  SSK9J:r:  SSK;J<r<  \" \=5      R}                  S5      r?\<" 5       R€                  R‚                  rB\BS-  rC\" \=5      Rˆ                  S   rE\" SS9rF\#RŽ                  " \F\<" 5       R€                  5      rH\R’                  " 5       rJ\FR—                  S5      S}S j5       rL\FR—                  S5      S}S j5       rM\FR�                  SSS9S~S j5       rO " S S \5      rP " S! S"\5      rQ " S# S$\5      rR " S% S&\5      rSSS' jrTS€S( jrU\T" 5       rV\U" 5       rW\R°                  " S)5      rY        S�S* jrZS‚SƒS+ jjr[S„S, jr\\FR�                  S-\S.9S…S/ j5       r]\FR�                  S05      S†S1 j5       r^S2r_S3\`S4'   S‡S5 jraSˆS6 jrb\FR�                  S75      S‰S8 j5       rc " S9 S:\5      rdSŠS; jre\FRÍ                  S<5      S‹S= j5       rgSŒS> jrhS�S? jriSŽS@ jrj\FR×                  S<5      S�SA j5       rl\FR�                  SB5      S�SC j5       rmS‘SD jrnS’S“SE jjroS”SF jrp " SG SH\5      rq\FR�                  SI\\q   SJ9S•SK j5       rr0 rsSL\`SM'   \Rè                  " 5       ruSNrv\Rî                  " \<" 5       R€                  Rð                  5      rySO/ SPQ0rzSQSRSS.STSUSS.SV.r{SQr|SWSX1r}S–SY jr~ " SZ S[\5      rS—S\ jr€S˜S] jr�S™S^ jr‚SšS_ jrƒS™S` jr„S›Sa jr…SœSb jr†\FGR                  Sc\SJ9SSSSSSSS\|4	                       S�Sd jj5       rˆ\FR�                  Se\SJ9SžSŸSf jj5       r‰S„Sg jrŠS Sh jr‹S¡Si jrŒ\FGR                  Sj\SSJ9S¢Sk j5       r�\FGR                  Sl5      S£Sm j5       rŽ\(GR                  " 5       r�\3" \�Sn9r‘ " So Sp\5      r’\FGR                  Sq\4SJ9S¤Sr j5       r“S¥Ss jr”\FR�                  St\4SJ9S¥Su j5       r•\FR�                  Sv5      S¦Sw j5       r–S§Sx jr—\FR�                  Sy5      S¨Sz j5       r˜\FR�                  S{5      S¨S| j5       r™g)©uÜ  FastAPI backend for the answering playground.

Serves the single-page UI and these endpoints:
  GET  /api/documents            -> list ingested documents: filing metadata,
                                    derived label, overlap and review flags
  GET  /api/document/{id}/pdf    -> the source PDF (for PDF.js)
  GET  /api/document/{id}/flags  -> the document's review queue, in page order
  PATCH  /api/documents/{id}     -> edit metadata (title, folder, filing
                                    fields); organization only, never identity
  DELETE /api/documents/{id}     -> remove the document outright (hard
                                    delete; 409 while its ingest or a batch
                                    run against it is still going)
  POST /api/chat                 -> retrieve -> Opus answer -> grounded refs
  POST /api/chat/stream          -> the same answer as server-sent events
  POST /api/upload               -> raw PDF body; runs the quber workflow
                                    (fuse -> figure -> ingest) as a job
  GET  /api/jobs/{job_id}        -> job stage (uploaded/waiting/extracting/
                                    reading figures/ingesting/done/failed)
                                    plus a tail of its log
  POST /api/batch                -> start a run of many questions -> job id
  GET  /api/batch/{id}           -> the run's state and rows finished so far
  GET  /api/batch/{id}/events    -> SSE, one event per question completing
  GET  /api/batch/{id}/export.xlsx and .csv -> the run as a workbook / text
  GET  /healthz                  -> readiness: 200 once the database answers
                                    and the embedding model is loaded
  GET  /static/{path}            -> the UI's assets

`quber.playground.auth.install` adds `/api/me`, and when sign-in is on also
`/login`, `/callback` and `/logout`.

Document identity is the integer id plus the content hash. The storage key
(`doc_key`, the hash's first 16 hex chars) names the staged files and is what
ingest subprocesses take. The document list leaves it out, but the PATCH
response returns the full document row and so carries it. Labels are derived
from the filing metadata at read time â€” see `quber.playground.metadata`.

Uploads take the PDF as the raw request body (no multipart dependency). The
bytes are hashed before anything runs: the same bytes as an existing document
are rejected outright (409, kind "duplicate"), and a filing-tuple match is
refused (409, kind "tuple") unless the request carries the replace intent â€”
then the swap happens when the new ingest completes. An s3:// source cannot
be hashed before its job stages it, so those checks run inside the job and a
failure carries the same structured `error_info`. Every upload is admitted;
a semaphore bounds how many run their pipelines at once
(`settings.playground.upload_concurrency`), and a job holding no slot yet
reports the `waiting` stage rather than appearing stalled on its first step.

Run:
  uv run quber playground
é    )ÚannotationsN)ÚPath)ÚAnyÚDictÚListÚLiteralÚLiteralStringÚOptional)ÚFastAPIÚHTTPExceptionÚRequest)ÚFileResponseÚHTMLResponseÚResponseÚStreamingResponse)Úlogger)Ú	BaseModel)Ú
fetch_file)ÚauthÚdbÚ	embeddingÚexportÚgpuÚjobsÚmetadataÚstorage)ÚDocumentIdentity)Úanswer)Úanswer_stream)ÚBatchRunnerÚRunStateÚparse_questions)ÚRetrievedChunkÚretrieve)Údocument_key)Úget_settingsÚstaticÚuploadsé   zQuber Playground)ÚtitleÚstartupc                 óT   • SS jn [         R                  " U SS9R                  5         g)z‰Load the embedding model in the background at startup. It otherwise
loads lazily on the first question, costing that question ~5 seconds.c                 óX   • [         R                  " S5        [        R                  5         g )Nzwarm up)r   Úembed_queryÚ_EMBEDDING_READYÚset© ó    Ú2/home/mande/repo/quber/src/quber/playground/app.pyÚwarmÚ"warm_embedding_model.<locals>.warme   s   € Ü×Ò˜iÔ(Ü×ÑÕr2   T)ÚtargetÚdaemonN©ÚreturnÚNone)Ú	threadingÚThreadÚstart)r4   s    r3   Úwarm_embedding_modelr>   `   s"   € ô
ô ×Ò˜D¨Ñ.×4Ñ4Õ6r2   c                 ó€   • [         R                  " 5         [         R                  " S [        R                  [
        S9  g)zjThe job tables exist before anything writes to them, and this process
starts beating for the jobs it runs.c                 óŒ   • [         R                  5        V s/ s H   o S   [        R                  ;  d  M  U S   PM"     sn $ s  sn f )NÚstageÚjob_id)ÚJOBSÚvaluesr   ÚTERMINAL_STAGES)Újs    r3   Ú<lambda>Ú"open_job_records.<locals>.<lambda>r   s2   € ´·±´Ói²¨AÀGÁ*ÔTX×ThÑThÑBh›K˜A˜hœK±ÒiùÒis
   —Aµ	A)Ú
upload_idsÚrun_idsÚstoreN)r   Úensure_tablesÚstart_heartbeatÚBATCHÚrunning_idsÚBATCH_STOREr1   r2   r3   Úopen_job_recordsrQ   l   s-   € ô 	×ÒÔÜ×ÒÙiÜ×!Ñ!Üór2   z/healthzF)Úinclude_in_schemac            
     ó˜  • [         R                  5       (       d  [        SSSS9$  [        R                  " 5        n U R                  S5      R                  5         SSS5        [        S	SS
9$ ! , (       d  f       N= f! [         aA  n[        [        R                  " SS[        U5      R                   305      SSS9s SnA$ SnAff = f)a  Readiness, not liveness: 200 only once the database answers and the
embedding model is loaded, 503 before. The load balancer's target group
and the scale-to-zero startup page both poll this, so a 200 means the
app can take a question, not merely that the process is up.z${"status":"loading embedding model"}i÷  zapplication/json)Ústatus_codeÚ
media_typezSELECT 1NÚstatuszdatabase unreachable: z{"status":"ok"}©rU   )r/   Úis_setr   r   ÚconnectÚexecuteÚfetchoneÚ	ExceptionÚjsonÚdumpsÚtypeÚ__name__)ÚconnÚexcs     r3   Úhealthzrc   x   s±   € ô ×"Ñ"×$Ñ$ÜØ2ÀÐPbñ
ð 	
ð
Ü�ZŠZŒ\˜TØ�L‰L˜Ó$×-Ñ-Ô/÷ ô Ð%Ð2DÑEÐE÷ �\ûäó 
ÜÜ�JŠJ˜Ð$:¼4À»9×;MÑ;MÐ:NÐ"OÐPÓQØØ)ñ
õ 	
ûð
ús:   ¦A> » A-ÁA> Á-
A;Á7A> Á;A> Á>
C	Â6CÂ>C	ÃC	c                  óF   • \ rS rSr% S\S'   S\S'   SrS\S'   SrS	\S
'   Srg)ÚChatRequestéŽ   ÚintÚdoc_idÚstrÚquestioné
   ÚkÚautoú Literal['auto', 'value', 'text']Úwantr1   N©r`   Ú
__module__Ú__qualname__Ú__firstlineno__Ú__annotations__rl   ro   Ú__static_attributes__r1   r2   r3   re   re   Ž   s"   ‡ ØƒKØƒMØ€A€sƒKð .4€DÐ
*Ö3r2   re   c                  óÔ   • \ rS rSr% S\S'   S\S'   S\S'   S\S	'   S\S
'   SrS\S'   SrS\S'   SrS\S'   SrS\S'   Sr	S\S'   Sr
S\S'   SrS\S'   SrS\S'   SrS\S'   SrS\S'   Srg)Ú	Referenceé—   ri   Úref_idúOptional[str]Úref_typerg   ÚpageúOptional[dict]ÚbboxÚlabelNrV   ÚnoteÚtextúOptional[int]ÚrowÚcolFÚboolÚflaggedÚchartÚsegmentÚreasonÚprintedr1   )r`   rq   rr   rs   rt   rV   r€   r�   rƒ   r„   r†   r‡   rˆ   r‰   rŠ   ru   r1   r2   r3   rw   rw   —   s…   ‡ ØƒKØÓØ
ƒIØ
ÓØƒJð !€FˆMÓ Ø€Dˆ-ÓØ€Dˆ-ÓØ€CˆÓØ€CˆÓØ€GˆTÓà€Eˆ=ÓØ!€Gˆ]Ó!ð !€FˆMÓ Ø!€Gˆ]Ö!r2   rw   c                  ó>   • \ rS rSr% S\S'   S\S'   S\S'   S\S'   S	rg
)ÚRetrievedInfoé¯   ri   Úchunk_idÚ
chunk_typerg   r|   ÚfloatÚscorer1   N)r`   rq   rr   rs   rt   ru   r1   r2   r3   rŒ   rŒ   ¯   s   ‡ ØƒMØƒOØ
ƒIØ†Lr2   rŒ   c                  óP   • \ rS rSr% S\S'   S\S'   S\S'   SrS\S	'   S
rS\S'   Srg
)ÚChatResponseé¶   ri   r   úList[Reference]Ú
referenceszList[RetrievedInfo]Ú	retrievedÚproseÚshapeNr}   Úvaluer1   )r`   rq   rr   rs   rt   r™   rš   ru   r1   r2   r3   r“   r“   ¶   s)   ‡ ØƒKØÓØ"Ó"ð
 €Eˆ3ÓØ €Eˆ>Ö r2   r“   c                 ó2   • SSK Jn   [        S U  5       5      $ )Nr   ©ÚCELL_STATUS_REFERENCEc              3  ó^   #   • U  H#  oR                   (       d  M  UR                  v •  M%     g 7f©N)ÚinspectÚcode)Ú.0Úss     r3   Ú	<genexpr>Ú!_inspect_codes.<locals>.<genexpr>Ç   s   é € ÐHÒ%: ¿i½i“V�Q—V–VÒ%:ùs   ‚-š-)Úquber.core.extractors.baser�   Ú	frozensetrœ   s    r3   Ú_inspect_codesr¨   Ä   s   € Ý@äÑHÑ%:ÓHÓHÐHr2   c                 óf   • SSK Jn   U  Vs0 s H  oR                  UR                  _M     sn$ s  snf )Nr   rœ   )r¦   r�   r¡   r   )r�   r£   s     r3   Ú_status_reasonsrª   Ê   s)   € Ý@á%:Ó;Ò%: �F‰F�A—G‘GŠOÑ%:Ñ;Ð;ùÒ;s   ‹ .zprints '([^']*)'c                óê   • U (       d  gSnU S:X  a4  U(       a-  [         R                  U5      nU(       a  UR                  S5      nU(       a  U(       a  SU SU S3U4$ [        R	                  X 5      U4$ )zMThe plain-language line for a status, and the printed figure a misread names.)NNNÚvalue_misreadé   zThe page prints z here, not z. Check which is right.)Ú_PRINTED_IN_NOTEÚsearchÚgroupÚSTATUS_REASONSÚget)rV   r€   ÚservedrŠ   Úms        r3   Ú_reasonrµ   Ü   sp   € ö ØØ€GØ�Ó ¦TÜ×#Ñ# DÓ)ˆÞØ—g‘g˜a“jˆGÞ–6Ø! ' ¨+°f°XÐ=TÐUÐW^Ð^Ð^Ü×Ñ˜fÓ-¨wÐ6Ð6r2   c                ó†   • U S:X  a  SU S3$ U S;   a  SU S3$ U S:X  a  SU S3$ U S:X  a  SU S	3$ U S
:X  a  SU S3$ SU S3$ )z‹What the source is, in the reader's words. The status never rides
the label: it has its own field, and its plain-language line is `reason`.Ú	tableCellzPage z, table cell)ÚtableÚ
chunkTablez, tableÚfigureValuez, chart valueÚ	line_itemz, table rowÚpicturez, figurez, textr1   )r{   Úpage1rV   s      r3   Ú_labelr¾   ì   s‡   € ð �;ÓØ�u�g˜\Ð*Ð*ØÐ*Ó*Ø�u�g˜WÐ%Ð%Ø�=Ó Ø�u�g˜]Ð+Ð+Ø�;ÓØ�u�g˜[Ð)Ð)Ø�9ÓØ�u�g˜XÐ&Ð&Ø�5�'˜Ð Ð r2   c                 óô   • [         S-  R                  5       n S HZ  n[        R                  " [         U-  R	                  5       5      R                  5       SS nU R                  SU 3SU SU 35      n M\     U $ )uª  The index shell with content-hashed asset links.

A browser that cached a previous deploy of app.js kept serving it on a
plain reload, because the asset URLs never change. Stamping each mutable
asset with a hash of its current bytes makes any change a new URL; the
pinned vendor files stay as they are. Hashing runs per request â€” the
files are small and the reload server would otherwise need cache
invalidation of its own.
z
index.html)z
styles.csszapp.csszapp.jsNé   z/static/z?v=)ÚSTATICÚ	read_textÚhashlibÚsha256Ú
read_bytesÚ	hexdigestÚreplace)ÚhtmlÚnameÚdigests      r3   Ú_stamped_indexrË   ü   sw   € ô �\Ñ!×,Ñ,Ó.€DÛ3ˆÜ—’¤¨$¡× :Ñ :Ó <Ó=×GÑGÓIÈ"È1ÐMˆØ�|‰|˜h t fÐ-°¸$¸¸sÀ6À(Ð/KÓLŠñ 4ð €Kr2   Ú/)Úresponse_classc                 ó*   • [        [        5       SS0S9$ )NzCache-Controlzno-cache)Úheaders)r   rË   r1   r2   r3   ÚindexrÐ     s   € ô œÓ(°?ÀJÐ2OÑPÐPr2   z/static/{name:path}c                óÞ   • [         U -  nUR                  5       (       a6  UR                  5       R                  [         R                  5       5      (       d  [	        SSU  35      e[        U5      $ )Né”  z	no asset )rÁ   Úis_fileÚresolveÚis_relative_tor   r   )rÉ   Úpaths     r3   Ústatic_assetr×     sT   € ô
 �D‰=€DØ�<‰<�>‰> §¡£×!>Ñ!>¼v¿~¹~Ó?O×!PÑ!PÜ˜C 9¨T¨FÐ!3Ó4Ð4Ü˜ÓÐr2   zwd.id, d.doc_key, d.filename, d.page_count, d.ade_version, d.title, d.folder, d.filing_type, d.year, d.period, d.versionr	   Ú_DOC_COLUMNSc                óÄ   • U S   U S   U S   U S   U S   S:X  a  SOSU S	   U S
   U S   U S   U S   U S   S.n[         R                  " U S   U S   U S   U S   5      US'   U$ )Nr   r­   é   r)   é   zquber-fusionÚfusionÚadeé   é   é   rÀ   é	   rk   )ÚidÚdoc_keyÚfilenameÚ
page_countÚformatr*   ÚfolderÚfiling_typeÚyearÚperiodÚversionr   )r   Úfiling_label)ÚrÚdocs     r3   Ú	_doc_dictrï   )  s�   € à�‰dØ�Q‘4Ø�a‘DØ˜‘dØ ™d nÓ4‘(¸%Ø�1‘Ø�A‘$Ø˜‘tØ�!‘Ø�A‘$Ø�R‘5ñ€Cô ×(Ò(¨¨1©¨q°©t°Q°q±T¸1¸R¹5ÓA€Cˆ�LØ€Jr2   c                óð   • [         R                  " 5        nUR                  S[        -   S-   U 45      R	                  5       nS S S 5        Wc  [        SSU  35      e[        U5      $ ! , (       d  f       N+= f)NúSELECT z0 FROM ade_playground.documents d WHERE d.id = %srÒ   úno document )r   rY   rZ   rØ   r[   r   rï   )rh   ra   rí   s      r3   Ú_doc_rowró   ;  sl   € Ü	�ŠŒ˜Ø�L‰LØœÑ$Ð'YÑYØˆIó
÷ ‰(‹*ð 	
÷ 
ð
 	�yÜ˜C <°¨xÐ!8Ó9Ð9Ü�Q‹<Ð÷ 
�ús   –,A'Á'
A5z/api/documentsc                 ód  • [         R                  " 5        n U R                  S[        -   S-   [	        [
        5      45      R                  5       nS S S 5        / nW H*  n[        U5      nUS	 US   US'   UR                  U5        M,     [        R                  " U5        U$ ! , (       d  f       NX= f)Nrñ   zÿ, COUNT(g.id) FILTER (WHERE g.status = ANY(%s)) AS flags
               FROM ade_playground.documents d
               LEFT JOIN ade_playground.groundings g ON g.document_id = d.id
               GROUP BY d.id ORDER BY lower(coalesce(d.title, d.filename))rã   é   Úflags)r   rY   rZ   rØ   ÚlistÚINSPECT_CODESÚfetchallrï   Úappendr   Úannotate_overlaps)ra   ÚrowsÚdocsrí   rî   s        r3   Ú	documentsrþ   F  s©   € ô
 
�ŠŒ˜Ø�|‰|ØÜñðNñNô ”-Ó Ð"ó
÷ ‰(‹*ð 	÷ 
ð €DÛˆÜ˜‹lˆà�	ˆNØ˜‘uˆˆG‰Ø�‰�CÖñ ô ×Ò˜tÔ$Ø€K÷% 
�ús   –9B!Â!
B/c                  ón   • \ rS rSr% SrSrS\S'   SrS\S'   SrS\S'   Sr	S\S'   Sr
S\S	'   SrS\S
'   Srg)ÚDocumentPatchi`  u[  The editable metadata: presentation (title, folder) and the filing
fields. Edits change organization and labels only â€” never identity, and
never blocked by collisions; two documents landing on one tuple simply
both carry the possible-duplicate flag. Fields arrive as strings so an
emptied input clears its column; year and version parse to ints.Nrz   r*   rç   rè   ré   rê   rë   r1   )r`   rq   rr   rs   Ú__doc__r*   rt   rç   rè   ré   rê   rë   ru   r1   r2   r3   r   r   `  sF   ‡ ñHð  €Eˆ=ÓØ €FˆMÓ Ø!%€K�Ó%Ø€Dˆ-ÓØ €FˆMÓ Ø!€Gˆ]Ö!r2   r   c                óž   •  [        U5      nX$s=::  a  U::  d  O  [        SU  SU SU 35      eU$ ! [         a    [        SU  S35      S ef = f)Né�  z must be a numberz must be between z and )rg   Ú
ValueErrorr   )rÉ   ÚrawÚloÚhirš   s        r3   Ú
_parse_intr  o  sl   € ðGÜ�C“ˆð Õ˜"ÕÜ˜C D 6Ð):¸2¸$¸eÀBÀ4Ð!HÓIÐIØ€Løô	 ó GÜ˜C D 6Ð):Ð!;Ó<À$ÐFðGús	   ‚1 ±Az/api/documents/{doc_id}c                óL  • UR                   b+  UR                   R                  5       (       d  [        SS5      e/ n/ nSUR                   4SUR                  4SUR                  4SUR
                  4/nU HF  u  pVUc  M
  UR                  US-   5        UR                  UR                  5       =(       d    S5        MH     UR                  bY  UR                  S	5        UR                  UR                  R                  5       (       a  [        S
UR                  SS5      OS5        UR                  bY  UR                  S5        UR                  UR                  R                  5       (       a  [        SUR                  SS5      OS5        U(       d  [        SS5      eSSR                  U5      -   S-   n[        R                  " 5        nUR                  U/ UQU P75      R                  5       n	SSS5        W	c  [        SSU  35      e[        U 5      $ ! , (       d  f       N+= f)z¼Update a document's metadata. The ingested artifacts are untouched;
an emptied field clears its column (folder falls back under the
ungrouped heading, filing fields drop out of the label).Nr  ztitle cannot be emptyr*   rç   rè   rê   z = %sz	year = %sré   il  i4  zversion = %srë   r­   iç  znothing to updatez$UPDATE ade_playground.documents SET z, z WHERE id = %s RETURNING idrÒ   rò   )r*   Ústripr   rç   rè   rê   rú   ré   r  rë   Újoinr   rY   rZ   r[   ró   )
rh   ÚpatchÚsetsÚparamsÚtext_fieldsÚcolumnr  Úqueryra   rƒ   s
             r3   Úpatch_documentr  y  sÍ  € ð
 ‡{�{Ñ u§{¡{×'8Ñ'8×':Ñ':Ü˜CÐ!8Ó9Ð9Ø "€DØ€Fà	�%—+‘+ÐØ	�5—<‘<Ð Ø	˜×)Ñ)Ð*Ø	�5—<‘<Ð ð	>€Kó #‰ˆØ‹?Ø�K‰K˜ Ñ(Ô)Ø�M‰M˜#Ÿ)™)›+×-¨Ö.ñ #ð ‡z�zÑØ�‰�KÔ Ø�‰ÀEÇJÁJ×DTÑDT×DVÑDV”j ¨¯©°T¸4Ô@Ð\`ÔaØ‡}�}Ñ Ø�‰�NÔ#Ø�‰ÀeÇmÁm×FYÑFY×F[ÑF[”j ¨E¯M©M¸1¸cÔBÐaeÔfÞÜ˜CÐ!4Ó5Ð5à.Ø
�)‰)�D‹/ñ	à
'ñ	(ð 
ô
 
�ŠŒ˜Ø�l‰l˜5Ð"3 FÐ"3¨FÑ"3Ó4×=Ñ=Ó?ˆ÷ 
à
�{Ü˜C <°¨xÐ!8Ó9Ð9Ü�FÓÐ÷	 
�ús   Ç
&HÈ
H#c                óÎ   • [         R                  " 5        H1  nUR                  S5      U :X  a    gUR                  S5      U:X  d  M1    g   [        R	                  U 5      (       a  gg)zÃWhy this document cannot be pulled out from under running work, or
None. An ingest can touch it two ways: a replace targeting it, or an
s3-sourced job that will resolve to the same bytes and key.Ú
replace_idzIa replacement for this document is still ingesting; wait for it to finishrã   zBan ingest of this document is still running; wait for it to finishzGa batch run against this document is still going; wait for it to finishN)r   Úlive_uploadsr²   rN   Úrunning_for)rh   rã   Újobs      r3   Ú_ingest_busyr     sX   € ô × Ò Ö"ˆØ�7‰7�<Ó  FÓ*Ù^Ø�7‰7�9Ó Õ(ÙWñ	 #ô
 ×Ñ˜× Ñ ØXØr2   c                ó„   • SnU Vs/ s H  n[         U  U 3-  PM     nnUR                  [        U  S3-  5        U$ s  snf )a'  The staged files a document's storage key names: the served PDF and
the cached parse and figure JSONs in the data directory, and the uploaded
original. The pipeline workdir is not in the list; `_remove_doc_files`
removes it separately. The `uploads/<doc_key>.source.json` manifest is in
neither.)	ú.pdfz	.ade.jsonú.unified.jsonú.tables.jsonú.fusion.jsonú.scanned-tables.jsonú.figures.jsonú.figure-values.jsonz.charts.jsonr  )ÚDATA_DIRrú   ÚUPLOADS)rã   Úsuffixesr£   Úpathss       r3   Ú
_doc_filesr%  ®  sN   € ð
€Hñ 2:Ó:²¨AŒX˜7˜) A 3˜Ô'±€EÐ:Ø	‡L�L”˜g˜Y dÐ+Ñ+Ô,Ø€Lùò ;s   ‡=c                óþ   • Sn[        U 5       H/  nUR                  5       (       d  M  UR                  5         US-  nM1     [        U  S3-  nUR                  5       (       a  [        R
                  " U5        US-  nU$ )Nr   r­   ú
-artifacts)r%  ÚexistsÚunlinkr"  ÚshutilÚrmtree)rã   ÚremovedrÖ   Ú	artifactss       r3   Ú_remove_doc_filesr.  Ä  so   € Ø€GÜ˜7Ö#ˆØ�;‰;�=‹=Ø�K‰KŒMØ�q‰LŠGñ $ô ˜W˜I ZÐ0Ñ0€IØ×Ñ×ÑÜ�Š�iÔ Ø�1‰ˆØ€Nr2   c           	     óº  • [        U 5      n[        XS   5      nU(       a  [        SU5      e[        R                  " 5        nUR                  SU 45      R                  5       nUR                  SU 45        SSS5        Wc   e[        US   5      n[        R                  " SU US   =(       d    US   US	   US
   US9  XS	   US
   US.$ ! , (       d  f       N[= f)uÒ  Remove a document outright: the row (chunks, groundings, vectors and
flags cascade with it), the staged files, and the upload artifacts.

A hard delete, documented as such: there is no tombstone, and the
cascade plus the file removal leave nothing for a re-ingest to
resurrect. Refused with 409 while an ingest touching the document or a
batch run against it is still going â€” the server owns no cancellation,
so the work finishes or fails before the ground moves.
rã   é™  a  SELECT COUNT(DISTINCT c.id), COUNT(DISTINCT g.id)
               FROM ade_playground.documents d
               LEFT JOIN ade_playground.chunks c ON c.document_id = d.id
               LEFT JOIN ade_playground.groundings g ON g.document_id = d.id
               WHERE d.id = %sú2DELETE FROM ade_playground.documents WHERE id = %sNzZdocument removed: {id} '{title}' ({chunks} chunks, {groundings} groundings, {files} files)r*   rä   r   r­   )râ   r*   ÚchunksÚ
groundingsÚfiles)r,  r2  r3  r4  )
ró   r  r   r   rY   rZ   r[   r.  r   Úinfo)rh   rî   Úbusyra   ÚcountsÚremoved_filess         r3   Údelete_documentr9  Ñ  sä   € ô �6Ó
€CÜ˜ I¡Ó/€DÞÜ˜C Ó&Ð&Ü	�ŠŒ˜Ø—‘ð"ð
 ˆIó
÷ ‰(‹*ð 	ð 	�‰ÐIÈFÈ9ÔU÷ 
ð ÑÐÐÜ% c¨)¡nÓ5€MÜ
‡K‚KØdØØ�'‰l×-˜c *™oØ�a‰yØ˜!‘9Øòð °©À&ÈÁ)ÐVcÑdÐd÷) 
�ús   Á5CÃ
Cz/api/document/{doc_id}/pdfc                ó‚   • [        U 5      n[        R                  " US   5      nUc  [        SSU  35      e[	        USS9$ )Nrã   rÒ   zNo PDF staged for document zapplication/pdfrW   )ró   r   Ú	local_pdfr   r   )rh   rî   rÖ   s      r3   Úpdfr<  ÷  sH   € ä
�6Ó
€CÜ×Ò˜S ™^Ó,€DØ�|Ü˜CÐ#>¸v¸hÐ!GÓHÐHÜ˜Ð):Ñ;Ð;r2   c                ó\   • [        U [        5      (       a  U $ [        R                  " U 5      $ )zCA groundings bbox as a dict, whether the driver returned it parsed.)Ú
isinstanceÚdictr]   Úloads)r  s    r3   Ú
_bbox_dictrA     s!   € ä˜S¤$×'Ñ'ˆ3Ð<¬T¯ZªZ¸«_Ð<r2   c                óœ   • U S   US   U-   :*  =(       a8    U S   US   U-   :*  =(       a#    U S   US   U-
  :¬  =(       a    U S   US   U-
  :¬  $ )zCWhether `outer` wholly contains `inner`, with a hairline tolerance.ÚleftÚtopÚrightÚbottomr1   )ÚouterÚinnerÚepss      r3   Ú_box_containsrJ    sn   € ð 	ˆf‰˜˜v™¨Ñ,Ñ,÷ 	5Ø�%‰L˜E %™L¨3Ñ.Ñ.÷	5à�'‰N˜e G™n¨sÑ2Ñ2÷	5ð �(‰O˜u X™°Ñ4Ñ4ð	r2   c                ó  ^^• U(       d  / $ [         R                  " 5        nUR                  SX45      R                  5       nSSS5        W Vs0 s H  oDS   U_M
     nn[	        5       nUR                  5        HŠ  nUS   S:w  d  US   c  M  [        US   [        5      (       a  US   O$US   (       a  [        R                  " US   5      O0 nU=(       d    0 R                  S5      nU(       d  My  UR                  U5        MŒ     [	        5       n	UR                  5        H¡  nUS   S	:w  d  US   c  M  [        US   [        5      (       a  US   O$US   (       a  [        R                  " US   5      O0 nU=(       d    0 R                  S
5      n
U
c  Mu  U	R                  US   R                  SS5      S    SU
 35        M£     SS	1mUR                  5        Vs/ s H1  oDS   T;   d  M  US   c  M  US   =(       d    S[        US   5      4PM3     snmSUU4S jjn/ nU GH¼  nUR                  U5      nU(       d  M  US   S:X  a  US   U;   a  M2  US   S:X  a  US   U	;   a  MF  U" U5      (       a  MU  US   =(       d    SS-   n[        US   [        5      (       a  US   O$US   (       a  [        R                  " US   5      OSnUS   US   US   nnn[        US   [        5      (       a  US   O$US   (       a  [        R                  " US   5      OSn[        UUU5      u  nnUR                  [!        US   US   UU[#        US   UU5      UUUU=(       d    0 R                  S
5      U=(       d    0 R                  S5      [%        U=(       a	    U[&        ;   5      U=(       d    0 R                  S5      U=(       d    0 R                  S5      UUS95        GM¿     U$ ! , (       d  f       GN¬= fs  snf s  snf )zJMap cited chunk/cell ids to page + bbox overlays via the groundings table.zÛSELECT g.ref_id, g.ref_type, g.page, g.bbox, g.status, g.note,
                      g.cell_text, g.position
               FROM ade_playground.groundings g
               WHERE g.document_id = %s AND g.ref_id = ANY(%s)Nr   r­   rº   r)   rà   Úpicture_refr·   rƒ   Ú-z-line-rÚ   c                óp   >^ ^• T S   T;   d  T S   c  g[        T S   5      m[        UU 4S jT 5       5      $ )Nr­   r)   Fc              3  óp   >#   • U  H+  u  pUTS    =(       d    S:H  =(       a    [        TU5      v •  M-     g7f)rÚ   r   N)rJ  )r¢   r|   ÚfinerG  rí   s      €€r3   r¤   Ú5_resolve_refs.<locals>.buries_fine.<locals>.<genexpr>C  s1   øé € ÐcÒXbÉ*È$�4˜A˜a™DŸI AÑ&×E¬=¸ÀÓ+EÔEÒXbùs   ƒ36)rA  Úany)rí   rG  Ú
fine_boxesÚ
fine_typess   `@€€r3   Úburies_fineÚ"_resolve_refs.<locals>.buries_fine?  s:   ú€ ØˆQ‰4�:Ó  1¡¡ØÜ˜1˜Q™4Ó ˆÜÕcÑXbÓcÓcÐcr2   r¼   r»   rÛ   rÞ   rß   r„   r‡   r   )ry   r{   r|   r~   r   rV   r€   r�   rƒ   r„   r†   r‡   rˆ   r‰   rŠ   )rí   Útupler9   r…   )r   rY   rZ   rù   r0   rD   r>  r?  r]   r@  r²   ÚaddÚsplitrA  rµ   rú   rw   r¾   r…   rø   )rh   Ú	cited_idsra   rü   rí   Úby_idÚcovered_picturesÚpositionrL  Úcovered_linesrƒ   rU  ÚrefsÚcidr½   r~   rV   r€   Ú	cell_textr‰   rŠ   rS  rT  s                        @@r3   Ú_resolve_refsrb    s³  ù€ æØˆ	Ü	�ŠŒ˜Ø�|‰|ðBð Ðó
÷ ‰(‹*ð 	÷ 
ñ #Ó#šd˜ˆq‰T�1ŠW™d€EÐ#ô “uÐØ�\‰\Ž^ˆØˆQ‰4�=Ó  A a¡D¡LÙÜ% a¨¡d¬D×1Ñ1�1�Q’4ÈAÈaÏD¼¿
º
À1ÀQÁ4Ô8HÐVXˆØ—~ 2×*Ñ*¨=Ó9ˆßˆ;Ø× Ñ  Ö-ñ ô “E€MØ�\‰\Ž^ˆØˆQ‰4�;Ó ! A¡$¡,ÙÜ% a¨¡d¬D×1Ñ1�1�Q’4ÈAÈaÏD¼¿
º
À1ÀQÁ4Ô8HÐVXˆØ�~˜2×"Ñ" 5Ó)ˆØ‹?Ø×Ñ  1¡§¡¨C°Ó!3°AÑ!6Ð 7°v¸c¸UÐCÖDñ ð   Ð-€Jà16·±´óÚ1?¨AÀQÁ4È:ÑCUÓ'ÐZ[Ð\]ÑZ^Ó'ˆ!ˆA‰$�)�!”j  1¡Ó&Ó'±ñ€J÷dð dð €DÜˆØ�I‰I�c‹NˆÞÙØˆQ‰4�9Ó  1¡Ð)9Ó!9ÙØˆQ‰4�;Ó 1 Q¡4¨=Ó#8ÙÙ�q�>‰>ÙØ�1‘—˜˜a‘ˆÜ! ! A¡$¬×-Ñ-ˆq�ŠtÈÈ!Ï´D·J²J¸qÀ¹tÔ4DÐRVˆØ"# A¡$¨¨!©¨a°©d�i�ˆÜ% a¨¡d¬D×1Ñ1�1�Q’4ÈAÈaÏD¼¿
º
À1ÀQÁ4Ô8HÐVZˆÜ! &¨$°	Ó:‰ˆ�Ø�‰ÜØ˜‘tØ˜1™ØØÜ˜Q˜q™T 5¨&Ó1ØØØØ—^ ×(Ñ(¨Ó/Ø—^ ×(Ñ(¨Ó/Ü˜V×?¨´-Ñ(?Ó@Ø—~ 2×*Ñ*¨7Ó3Ø!Ÿ R×,Ñ,¨WÓ5ØØñ÷	
ñ ðD €K÷k 
Žüò $ùò>s#   ¡"O(ÁO:ÇO?Ç-O?Ç5 O?Ï(
O7c                  óv   • \ rS rSr% SrS\S'   S\S'   S\S'   S	\S
'   S\S'   SrS\S'   SrS\S'   SrS\S'   Sr	g)ÚFlagik  z‰One item of the document's review queue: a cell whose provenance
status is registered for inspection, or a table-level dropped-text flag.ri   ry   rz   r{   rg   r|   r}   r~   rV   Nr€   Ú r�   r‰   r1   )
r`   rq   rr   rs   r  rt   r€   r�   r‰   ru   r1   r2   r3   rd  rd  k  sB   ‡ ñPð ƒKØÓØ
ƒIØ
ÓØƒKØ€Dˆ-ÓØ€Dˆ#ƒNØ €FˆMÖ r2   rd  z/api/document/{doc_id}/flags)Úresponse_modelc                ó  • [         R                  " 5        nUR                  SU [        [        5      45      R                  5       nSSS5        W Vs/ s H‘  n[        US   US   US   =(       d    SS-   [        US   [        5      (       a  US   O$US   (       a  [        R                  " US   5      OSUS   US   US	   =(       d    S
[        US   US   US	   5      S   S9PM“     sn$ ! , (       d  f       N­= fs  snf )z3Every flagged item for one document, in page order.zâSELECT g.ref_id, g.ref_type, g.page, g.bbox, g.status, g.note, g.cell_text
               FROM ade_playground.groundings g
               WHERE g.document_id = %s AND g.status = ANY(%s)
               ORDER BY g.page, g.ref_idNr   r­   rÚ   r)   rÛ   rÞ   rß   re  )ry   r{   r|   r~   rV   r€   r�   r‰   )r   rY   rZ   r÷   rø   rù   rd  r>  r?  r]   r@  rµ   )rh   ra   rü   rí   s       r3   Údocument_flagsrh  y  sû   € ô 
�ŠŒ˜Ø�|‰|ð,ð ”Tœ-Ó(Ð)ó
÷ ‰(‹*ð 	÷ 
ñ& óò ˆAô 	Ø�Q‘4Ø�q‘TØ�A‘$—)˜!˜q‘Ü# A a¡D¬$×/Ñ/��1’È!ÈAÏ$´d·j²jÀÀ1ÁÔ6FÐTXØ�Q‘4Ø�1‘Ø�1‘—˜Ü˜1˜Q™4  1¡ q¨¡tÓ,¨QÑ/ô		
ñ ñð ÷ 
�üòs   –0C-ÁBC>Ã-
C;zdict[str, dict]rC   éÈ   rÜ   )ÚuploadedÚwaitingÚ
extractingúreading figuresÚ	ingestingúdpt-3Úgrouped)ÚmodelÚchunkingúdpt-2Úflat)ro  rs  z10-Qz10-Kc                óÊ  • U R                  S5      =(       d    SR                  5       R                  5       nU(       a  U[        ;   a  SOSnUSU 34$ SSKnSSKnSSKJn  SS	KJ	n   UR                  U5       n	U	R                  n
SSS5        X!R                   S
3-  nU" USSU5        UR                  U" 5       R                  UR                  5       W
5      5      nUR!                  SS9  UR"                  S:X  a  SSUR$                   34$ SSUR$                   34$ ! , (       d  f       NŸ= f! [&         a(  nSS[)        U5      R*                   SU S34s SnA$ SnAff = f)uÎ  The figure scan's page breadth for one upload, and the reason for it.

Typed documents are decided by their filing type alone. An untyped document
is never allowed to default to full coverage â€” the type field is optional
and routinely left blank, and a blank must not buy an 80-page scan by
accident. A cover attribution agent reads the first page instead and
decides presentation (all pages) against prose filing (nominated only);
any failure in that read falls back to nominated-only, with the fallback
stated in the returned reason so the record shows breadth was reduced and
why. The verdict decides breadth only â€” it is never written to the
document's filing type, which stays exactly as the user entered it.
rè   re  Ú	nominatedÚallzfiling type r   N)Úget_cover_attributor)Úrender_pagez
.cover.pngr­   éd   T©Ú
missing_okÚpresentationzattributed presentation: zattributed filing: zattribution failed (ú: z); nominated-only fallback)r²   r
  ÚupperÚNOMINATED_ONLY_TYPESÚasyncioÚfitzÚquber.agents.cover_attributionrx  Úquber.files.pdfry  Úopenrå   ÚstemÚrunÚ	attributerÅ   r)  Úkindr‰   r\   r_   r`   )r  r<  Úworkdirrè   Úscoper�  r‚  rx  ry  rî   rå   ÚcoverÚverdictrb   s                 r3   Ú_scan_breadthrŽ  ¿  sQ  € ð —7‘7˜=Ó)×/¨R×6Ñ6Ó8×>Ñ>Ó@€KÞØ*Ô.BÓB‘ÈˆØ˜ [ MÐ2Ð2Ð2ÛãåCÝ+ð
Ø�Y‰Y�sŒ^˜sØŸ™ˆJ÷ àŸX™X˜J jÐ1Ñ1ˆÙ�C˜˜C Ô'Ø—+‘+Ñ2Ó4×>Ñ>¸u×?OÑ?OÓ?QÐS]Ó^Ó_ˆØ�‰ ˆÑ%Ø�<‰<˜>Ó)ØÐ5°g·n±nÐ5EÐFÐFÐFØÐ1°'·.±.Ð1AÐBÐBÐB÷ �^ûô ó 
àØ"¤4¨£9×#5Ñ#5Ð"6°b¸¸Ð=WÐXð
õ 	
ûð
ús=   Á(D0 Á9DÂBD0 ÄD0 Ä
D-Ä)D0 Ä0
E"Ä:EÅE"ÅE"c                  ó˜   • \ rS rSr% S\S'   S\S'   SrS\S'   SrS\S'   S\S	'   \S
   rS\S'   Sr	S\S'   Sr
S\S'   SrS\S'   S\S'   Srg)Ú	JobStatusiè  ri   rB   rä   Nrz   r*   rç   rA   rÜ   ú	List[str]ÚstagesÚerrorr}   Ú
error_infor‚   rh   Úlogr1   )r`   rq   rr   rs   rt   r*   rç   ÚPIPELINE_STAGESr’  r“  r”  rh   ru   r1   r2   r3   r�  r�  è  s[   ‡ ØƒKØƒMØ€Eˆ=ÓØ €FˆMÓ ð
 ƒJØ'¨Ñ1€FˆIÓ1Ø€Eˆ=Óð "&€J�Ó%à €FˆMÓ Ø	†Nr2   r�  c                óº  • XS'   U S   R                  SU SSR                  U5       35        [        R                  " U 5        [        R
                  " U[        [        R                  [        R                  SS9nUR                  c   eUR                   HJ  nU S   R                  UR                  5       5        [        U S   5      [        :”  d  M=  U S   S [        * 2	 ML     UR                  5         [        R                  " U S   [         U S    S	3-  5        [        R                  " U 5        UR"                  S
:w  a  [%        U SUR"                   35      eg )NrA   r•  z--- r~  Ú T)ÚcwdÚstdoutÚstderrr�   rã   r'  r   z exited with code )rú   r  r   Úsave_uploadÚ
subprocessÚPopenÚ	REPO_ROOTÚPIPEÚSTDOUTrš  ÚrstripÚlenÚ	_LOG_TAILÚwaitr   Úpublish_workdirr"  Ú
returncodeÚRuntimeError)r  rA   ÚcmdÚprocÚlines        r3   Ú
_run_stager¬  ÿ  s&  € Øˆ�LØˆ�J×Ñ˜˜U˜G 2 c§h¡h¨s£m _Ð5Ô6Ü×Ò�SÔÜ×Ò˜C¤Y´z·±Ìz×O`ÑO`ÐgkÑl€DØ�;‰;Ñ"Ð"Ð"Ø—”ˆØˆE‰
×Ñ˜$Ÿ+™+›-Ô(Üˆs�5‰z‹?œYÕ&Ø�E‘
˜;œY˜J˜;Ò'ñ ð 	‡I�I„Kô ×Ò˜C 	™N¬G¸¸Y¹Ð8HÈ
Ð6SÑ,SÔTÜ×Ò�SÔØ‡�˜!ÓÜ˜e˜WÐ$6°t·±Ð6GÐHÓIÐIð r2   c                ó>  ^ • T S   nSU 4S jjnST S'   [         R                  " T 5        [        T SSSSSUS	[        U5      S
S/	5        [        R
                  " X2U5        [         R                  " T 5        [        T SSSSSUS[        U5      S	[        U5      /	5        g)a  The extracting stage where this host has no GPU, in three steps run
one after another: the table engine runs here as `quber table`, then the
docling parse goes to the worker and is waited on, then `quber fuse`
reads the two sets of artifacts from the workdir.rã   c                óv   >• TS   R                  U 5        [        TS   5      [        :”  a  TS   S [        * 2	 g g )Nr•  )rú   r£  r¤  )r«  r  s    €r3   r•  Ú#_run_hosted_extraction.<locals>.log  s<   ø€ ØˆE‰
×Ñ˜$ÔÜˆs�5‰z‹?œYÓ&Ø�E‘
˜;œY˜J˜;Ñ'ð 'r2   rl  rA   Úuvr‡  Úquberr¸   ú-oz--llm-backendÚapiÚfusez--artifacts-dirN)r«  ri   r9   r:   )r   rœ  r¬  ri   r   Úparse)r  r<  rŠ  rã   r•  s   `    r3   Ú_run_hosted_extractionr¶    s¢   ø€ ð
 �)‰n€G÷(ð
  €Cˆ�LÜ×Ò�SÔÜØˆ\˜D %¨°'¸3ÀÄcÈ'ÃlÐTcÐejÐkôô ‡I‚Iˆg Ô$Ü×Ò�SÔÜØØØ	ˆu�g˜v sÐ,=¼sÀ7»|ÈTÔSVÐW^ÓS_Ð`õr2   c                ób   • SU S'   [         R                  " U 5        [        R                  5         g)z¨Hold until a pipeline slot frees. Admitted-but-waiting is its own
stage: a queued document has something honest to show rather than
appearing stalled on its first step.rk  rA   N)r   rœ  Ú_UPLOAD_SLOTSÚacquire)r  s    r3   Ú_await_slotrº  ,  s'   € ð €Cˆ�LÜ×Ò�SÔÜ×ÑÕr2   c           
     óä  ^ ^
• [         R                  " 5        nUR                  ST S   45      R                  5       nSSS5        W(       a7  US   =(       d    US   nSSU SUS	   =(       d    S
 S3US   US   US	   S.S.$ [        R
                  " 5        Vs/ s H  oDS   T S   :w  d  M  UPM     nn[        U 4S jU 5       5      (       a  SSS.$ [        R                  " T S   T S   T S   T S   T S   5      m
T
c  gU Hu  n[        R                  " UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      5      nUT
:X  d  Mp  SSS.s  $    [         R                  " 5        nUR                  S[        -   S-   5      R                  5       nSSS5        [        U
4S jS W 5        5       S5      nUc  gT S   (       d)  SSUS     S!US   =(       d    S
 S3US"   US#   US   S.S.$ [        US"   US$   5      n	U	(       a	  S%S&U	 S3S.$ US"   T S''   US$   T S('   g! , (       d  f       GN= fs  snf ! , (       d  f       N¬= f))u|  The reason this upload must not run, as the structured error_info the
modal renders â€” or None. Checks, in order: the same bytes already in the
library (or already being ingested), then the filing tuple against
existing documents and in-flight jobs. A tuple match with the replace
intent records the target on the job instead of refusing, unless the
target is under running work.zXSELECT id, title, folder, filename FROM ade_playground.documents WHERE content_hash = %sÚcontent_hashNr­   r)   Ú	duplicateu/   This exact file is already in the library â€” 'z' in rÚ   Ú	UngroupedÚ.r   )râ   r*   rç   )r‰  ÚmessageÚexistingrB   c              3  óP   >#   • U  H  oR                  S 5      TS    :H  v •  M     g7f)rã   N)r²   )r¢   rF   r  s     €r3   r¤   Ú#_admission_error.<locals>.<genexpr>I  s!   øé € Ð
>²v°!�5‰5�Ó˜s 9™~Ö-²vùs   ƒ#&z*This exact file is already being ingested.)r‰  rÀ  rç   rè   ré   rê   rë   rW  z?A document with this filing metadata is already being ingested.rñ   z  FROM ade_playground.documents dc           	   3  ó†   >#   • U  H6  n[         R                  " US    US   US   US   US   5      T:X  d  M2  Uv •  M8     g7f)rç   rè   ré   rê   rë   N)r   Ú	tuple_key)r¢   ÚdÚkeys     €r3   r¤   rÃ  [  sN   øé € ð 	
â1�Ü×!Ò! ! H¡+¨q°Ñ/?ÀÀ6ÁÈAÈhÉKÐYZÐ[dÑYeÓfÐjmÑm÷ ‰AÚ1ùs
   ƒ1A¸	Ac              3  ó8   #   • U  H  n[        U5      v •  M     g 7frŸ   )rï   )r¢   rí   s     r3   r¤   rÃ  ]  s   é € Ð1ªD q”i —l�lªDùs   ‚rÇ   zA r   z already exists in râ   r*   rã   r6  zCannot replace: r  Úreplace_key)r   rY   rZ   r[   r   r  rR  r   rÅ  r²   rØ   rù   Únextr  )r  ra   Údupr*   rF   ÚothersÚ	other_keyrü   Úmatchr6  rÇ  s   `         @r3   Ú_admission_errorrÏ  5  sœ  ù€ ô 
�ŠŒ˜Ø�l‰lØfØ�Ñ Ð"ó
÷ ‰(‹*ð 	÷ 
ö
 Ø�A‘× ˜#˜a™&ˆàØHÈÈÈuÐUXÐYZÑU[×UjÐ_jÐTkÐklÐmØ" 1™v°°A±À#ÀaÁ&ÑIñ
ð 	
ô
 ×*Ò*Ô,ÓMÒ,�A°(±¸sÀ8¹}Ñ0L�aÑ,€FÐMÜ
Ô
>±vÓ
>×>Ñ>Ø#Ð0\Ñ]Ð]ä
×
Ò
˜S ™]¨C°Ñ,>ÀÀFÁÈSÐQYÉ]Ð\_Ð`iÑ\jÓ
k€CØ
�{ØÛˆÜ×&Ò&Ø�E‰E�(‹O˜QŸU™U =Ó1°1·5±5¸³=À!Ç%Á%ÈÃ/ÐST×SXÑSXÐYbÓScó
ˆ	ð ˜ÕàØ\ñò ñ ô 
�ŠŒ˜Ø�|‰|˜I¬Ñ4Ð7YÑYÓZ×cÑcÓeˆ÷ 
äô	
á1©DÔ1ó	
ð
 	ó€Eð �}ØØˆy�>àØ˜E '™NÐ+Ð+>¸uÀX¹×?]ÐR]Ð>^Ð^_Ð`Ø$ T™{°U¸7±^ÈuÐU]ÉÑ_ñ
ð 	
ô
 ˜˜d™ U¨9Ñ%5Ó6€DÞØÐ-=¸d¸VÀ1Ð+EÑFÐFØ˜d™€CˆÑØ˜yÑ)€CˆÑØ÷g 
Žüò N÷  
�ús#   ˜%I
ÂIÂ-IÆ*I!É

IÉ!
I/c                óˆ  • Sn[         R                  " 5        nUR                  5          UR                  SU R	                  S5      U R	                  S5      U R	                  S5      U R	                  S5      U R	                  S5      U R	                  S5      U R	                  S	5      U S
   45        U R	                  S5      bY  [
        R                  U S   5      (       a  U S   R                  S5        O'UR                  SU S   45        U R	                  S5      nSSS5        UR                  SU S
   45      R                  5       nSSS5        U(       a$  [        U5        [        R                  " SU S   US9  W(       a  US   OSU S'   [        R                  " U 5        g! , (       d  f       N�= f! , (       d  f       Nr= f)z´Stamp the upload's metadata onto the freshly ingested row and, for a
replace, swap the target out in the same transaction. Ingest recreates
the document row, so this runs after it.NzÏUPDATE ade_playground.documents
                   SET title = %s, folder = %s, filing_type = %s, year = %s, period = %s,
                       version = %s, track = %s
                   WHERE doc_key = %sr*   rç   rè   ré   rê   rë   Útrackrã   r  r•  zxreplace target is under a running batch; both documents kept (remove the old one from the library when the run finishes)r1  rÉ  z:SELECT id FROM ade_playground.documents WHERE doc_key = %szreplaced document {id} ({key}))râ   rÇ  r   rh   )r   rY   ÚtransactionrZ   r²   rN   r  rú   r[   r.  r   r5  r   rœ  )r  Úreplaced_keyra   rƒ   s       r3   Ú_finalize_jobrÔ  r  s‚  € ð #'€LÜ	�ŠŒ˜Ø×ÑÕØ�L‰Lð)ð
 —G‘G˜GÓ$Ø—G‘G˜HÓ%Ø—G‘G˜MÓ*Ø—G‘G˜F“OØ—G‘G˜HÓ%Ø—G‘G˜IÓ&Ø—G‘G˜GÓ$Ø˜	‘Nð	ôð  �w‰w�|Ó$Ñ0ô ×$Ñ$ S¨Ñ%6×7Ñ7Ø˜‘J×%Ñ%ðVõð
 —L‘LÐ!UÐX[Ð\hÑXiÐWkÔlØ#&§7¡7¨=Ó#9�L÷9  ð: �l‰lØHÈ3ÈyÉ>ÐJ[ó
ç
‰(‹*ð 	÷= 
öB Ü˜,Ô'Ü�ŠÐ4¸¸\Ñ9JÐP\Ò]Þ!�C˜’F t€Cˆ�MÜ×Ò�SÕ÷I  Õú÷ 
�ús#   ˜F3©C2F"Ä,F3Æ"
F0	Æ,F3Æ3
Gc                óæ  • Uc°  Uc   e[         SU S    S3-  n [        X#5        UR                  5       n[        R
                  " U5      R                  5       U S
'   U S
   SS U S'   [        U 5      nUb!  UR                  SS9  SU S'   US   U S	'   XPS'   gUR                  [         U S    S3-  5        [        [         U S    S3-  5      $ ! [         a  nSU S'   SU SU 3U S	'    SnAgSnAff = f)ae  Resolve the upload to local bytes, hash them, and run admission.
Returns the staged PDF path to run the pipeline on, or None after
marking the job failed. Each upload is admitted once. A body upload was
hashed and admitted in the route, so it passes straight through. An s3
source is fetched, hashed and admitted here, because the route never had
its bytes.Nztmp-rB   r  ÚfailedrA   zcould not fetch r~  r“  r¼  é   rã   Tr{  rÀ  r”  )r"  r   rÅ   r\   rÃ   rÄ   rÆ   rÏ  r)  Úrenameri   )r  Ú	pdf_bytesÚs3_uriÚstagedrb   r5  s         r3   Ú_admit_or_failrÜ  Ÿ  s'  € ð ÑØÑ!Ð!Ð!Ü˜T # h¡- °Ð5Ñ5ˆð	Ü�vÔ&Ø×)Ñ)Ó+ˆIô
 &Ÿnšn¨YÓ7×AÑAÓCˆˆNÑØ˜^Ñ,¨S¨bÐ1ˆˆI‰Ü Ó$ˆØÑØ�M‰M TˆMÑ*Ø#ˆC�‰LØ 	™?ˆC�‰LØ $�ÑØØ�‰”g 3 y¡>Ð"2°$Ð 7Ñ7Ô8ÜŒw˜C 	™NÐ+¨4Ð0Ñ0Ó1Ð1øô ó 	Ø#ˆC�‰LØ-¨f¨X°R¸°uÐ=ˆC�‰LÜûð	ús   šC Ã
C0ÃC+Ã+C0c                ój  • [         U    n [        U5        [        X1U5      nUc,   [        R                  " U5        [
        R                  5         gUS   n[        U S3-  nUR                  SSS9  Un[        R                  " XSS   US   US9n[        R                  " U[        U5      U/5        [        UR                  S	5      =(       d    [           n	[!        U[        U5      U5      u  p«X£S
'   X³S'   X#S'   [        R                  " U5        ["        R$                  " SX
U5        [&        R(                  " 5       (       a  [+        X4U5        O[-        USSSSSUS[/        U5      /5        [-        USSSSSUS[/        Xg S3-  5      S[/        U5      SU	S   SU
/5        [-        USSSSSSSUS US   S![/        Xg S3-  5      S"[/        Xg S#3-  5      S$[/        Xg S%3-  5      S&[/        Xg S'3-  5      S([/        Xg S)3-  5      S*[/        Xg S+3-  5      S,US-U	S.   /5        [1        U5        S/US0'   [        R                  " U5        [
        R                  5         g! [2         a  nS1US0'   [/        U5      US2'    SnANMSnAff = f! [        R                  " U5        [
        R                  5         f = f)3aŒ  The quber workflow: fuse, then read the figures, then ingest.

The figure stage is part of the workflow rather than an option on it. Without
it a document arrives holding only what the text layer gave up: a chart's
plotted values are absent, a table printed as an image stands as the parse's
own reading of it, and the notes that qualify a figure are attached to
nothing. The standalone ADE path is a different pipeline and is unaffected.

The stage runs on every upload, and it spends: each page it scans is paid
for. `_scan_breadth` decides which pages. At nominated breadth only the pages
the parse nominates are scanned, so a document with no nominated page costs
nothing. At all-pages breadth every page is scanned. An artifact file the
run did not write, such as the scanned tables of a document with no table
captured, the ingest reads as "the run produced none of that kind" rather
than as an error.
Nrã   r'  T©ÚparentsÚexist_okrä   r¼  )rä   r¼  Ú
source_urirÑ  Ú
scan_pagesÚscan_pages_reasonrá  z&Upload {}: figure scan breadth {} ({})rl  r°  r‡  r±  r´  r²  rm  Úfigurez--parser  z--modelrq  z--pagesrn  Úpythonz-mzquber.playground.ingest_fusionz	--doc-keyz
--filenamez--unified-jsonz--tables-jsonr  z--fusion-jsonr  z--scanned-tables-jsonr  z--figures-jsonr  z--figure-values-jsonr   z--pdfz
--chunkingrr  ÚdonerA   rÖ  r“  )rC   rº  rÜ  r   rœ  r¸  Úreleaser"  Úmkdirr   Úwrite_source_manifestÚpublishr   ÚTRACKSr²   ÚDEFAULT_TRACKrŽ  r   r5  r   Ú
configuredr¶  r¬  ri   rÔ  r\   )rB   rÙ  rÚ  r  r<  rã   rŠ  ÚbaseÚmanifestrÑ  ÚpagesÚbreadth_reasonrb   s                r3   Ú_run_jobrò  ½  sä  € ô" ˆv‰,€CðV Ü�CÔÜ˜S¨VÓ4ˆØ‰;Øôb 	×Ò˜ÔÜ×ÑÕðc �i‘.ˆÜ˜w˜i zÐ2Ñ2ˆØ�‰˜d¨TˆÑ2Øˆô ×0Ò0Ø *™o¸CÀÑ<OÐ\bñ
ˆô 	�Š˜¤$ s£)¨XÐ!6Ô7Ü�s—w‘w˜wÓ'×8¬=Ñ9ˆô !.¨c´4¸³9¸gÓ FÑˆØ!ˆLÑØ#1ÐÑ Ø"ˆLÑÜ×Ò˜ÔÜ�ŠÐ<¸fÈ^Ô\Ü�>Š>×ÑÜ" 3¨WÕ5ä�s˜L¨4°¸ÀÈÈdÔTWÐX_ÓT`Ð*aÔbÜØØàØØØØØÜ�G  mÐ4Ñ4Ó5ØÜ�G“ØØ�g‘ØØðô	
ô& 	ØØàØØØØ0ØØØØ�J‘Ø Ü�G  mÐ4Ñ4Ó5ØÜ�G  lÐ3Ñ3Ó4ØÜ�G  lÐ3Ñ3Ó4Ø'Ü�G Ð&:Ð;Ñ;Ó<Ø Ü�G  mÐ4Ñ4Ó5Ø&Ü�G Ð&9Ð:Ñ:Ó;ØØØØ�jÑ!ð3ô	
ô> 	�cÔØˆˆG‰ô
 	×Ò˜ÔÜ×ÑÕøô ó  ØˆˆG‰Ü˜3“xˆˆGŽûð ûô 	×Ò˜ÔÜ×ÑÕús0   ‹I ÁG I É
JÉ&I>É9J É>JÊJ Ê,J2z/api/uploadc              ƒ  óP  #   • U
[         ;  a  [        SS[        [         5       35      eSnUbG  UR                  S5      (       a$  UR	                  5       R                  S5      (       d  [        SS5      eO:U R                  5       I Sh  v•N nUR                  S5      (       d  [        SS5      e[        R                  " 5       R                  SS	 n0 S
U_SU_SU_SU_SU_SU_SU_SU_SU	_SU
_SS_SS_SS_SS_SS_SS_S[        S   _SS/ S.EnUb  [        R                  " U5      R                  5       US'   US   SS US'   [        U5      nUb  [        SU5      e[        R!                  S S S!9  [        US    S3-  R#                  U5        O[        R!                  S S S!9  [$           U[&        U'   SSS5        [(        R*                  " U5        [,        R.                  " [0        XËU4S S"9R3                  5         [5        S$0 0 UES#/ 0ED6$  GNŠ! , (       d  f       Nb= f7f)%ue  Run the fusion pipeline on a PDF sent as the raw request body, or on an
s3:// object named by `s3_uri` (body then stays empty).

`track` selects the model line the document is processed on â€” see TRACKS.

`filename` is the original name, kept for presentation and never for
identity. The metadata fields are how the library presents and keys the
document; this route and the PATCH are the only places they are ever
set. `replace` carries the modal's confirmed intent to swap out the
document whose filing tuple this upload matches. Every admitted upload
waits in the `waiting` stage until a pipeline slot frees.
r  ztrack must be one of Nzs3://r  z*s3_uri must be an s3:// URI ending in .pdfs   %PDFzrequest body is not a PDFé   rB   rä   r*   rç   rè   ré   rê   rë   rÇ   rÑ  r  rÉ  rã   r¼  rh   rA   rj  r’  rÜ   )r“  r”  r•  r×  r0  TrÞ  )r6   Úargsr7   r•  r1   )rë  r   ÚsortedÚ
startswithÚlowerÚendswithÚbodyÚuuidÚuuid4Úhexr–  rÃ   rÄ   rÆ   rÏ  r"  rè  Úwrite_bytesÚ
_JOBS_LOCKrC   r   rœ  r;   r<   rò  r=   r�  )Úrequesträ   rÚ  r*   rç   rè   ré   rê   rë   rÇ   rÑ  rú  rB   r  r5  s                  r3   Úuploadr  (  s—  é € ð4 ”FÓÜ˜CÐ#8¼Ä»Ð8HÐ!IÓJÐJØ €DØÑØ× Ñ  ×)Ñ)°·±³×1HÑ1HÈ×1PÑ1PÜ Ð%QÓRÐRð 2Qð —\‘\“^×#ˆØ�‰˜w×'Ñ'Ü Ð%@ÓAÐAÜ�ZŠZ‹\×Ñ˜c˜rÐ"€FðØ�&ðà�Hðð 	�ðð 	�&ð	ð
 	�{ðð 	�ðð 	�&ðð 	�7ðð 	�7ðð 	�ðð 	�dðð 	�tðð 	�4ðð 	˜ðð 	�$ðð  	�ð!ð" 	”/ (Ñ+ð#ð$ ØØò)€Cð, Ñô &Ÿnšn¨TÓ2×<Ñ<Ó>ˆˆNÑØ˜^Ñ,¨S¨bÐ1ˆˆI‰Ü Ó$ˆØÑÜ  TÓ*Ð*Ü�‰˜d¨TˆÑ2Ü	�c˜)‘nÐ% TÐ*Ñ	*×7Ñ7¸Õ=ä�‰˜d¨TˆÑ2Þ	ØŒˆV‰÷ 
ä×Ò�SÔÜ×ÒœH¨F¸&Ð+AÈ$ÑO×UÑUÔWÜÑ*Ð)˜#Ð)˜u bÑ)Ñ*Ð*òW $÷N 
�üs,   ‚BH&ÂHÂ	D&H&Æ/
HÆ9AH&È
H#ÈH&z/api/jobs/{job_id}c           	     óÂ   • [         R                  U 5      =(       d    [        R                  " U 5      nU(       d  [	        SSU  35      e[        S0 0 UESUS   U* S 0ED6$ )zThe job as this process holds it when it is running it, else as the
database records it, which is how a poll lands on any task.rÒ   zno job r•  Nr1   )rC   r²   r   Úload_uploadr   r�  )rB   Útailr  s      r3   Ú
job_statusr  w  sa   € ô �(‰(�6Ó
×
6œd×.Ò.¨vÓ6€CÞÜ˜C 7¨6¨(Ð!3Ó4Ð4ÜÑ:Ð9˜#Ð9˜u c¨%¡j°$°°Ð&8Ñ9Ñ:Ð:r2   c                óî   • U R                   S:X  a  U R                  $ U R                   S:X  a  U R                  $ U R                   S:X  a  U R                  $ [        R
                  " U R                  SS9SS9$ )z´What to put in `answer` for a consumer that only reads text.

A figure renders as the figure, so the text field of a value-seeking
answer is the value and not a sentence about it.
r˜   ÚunanswerableÚscalarr]   ©ÚmoderÚ   )Úindent)r‰  r�   r‰   rš   r]   r^   Ú
model_dump)Úpayloads    r3   Ú	_readabler  �  se   € ð ‡|�|�wÓØ�|‰|ÐØ‡|�|�~Ó%Ø�~‰~ÐØ‡|�|�xÓØ�}‰}ÐÜ�:Š:�g×(Ñ(¨fÐ(Ð5¸aÑ@Ð@r2   c                ó†   • [        U SS5      nU(       d  [        U5      $ U/U Vs/ s H  o3U:w  d  M
  UPM     sn-   $ s  snf )uÑ  The cited ids with the answer's own source leading.

A value payload names the single cell or chunk it was read from in
`source_id`. The UI activates the first reference that carries a box, so
whatever id leads this list becomes the default highlight â€” and the
value's own cell is the highlight the answer earned, not the wider line
record the model happened to cite first. Payloads without a source
(prose, unanswerable, series) keep the model's citation order.
Ú	source_idN)Úgetattrr÷   )r  rZ  r  r`  s       r3   Úcited_ids_source_firstr  �  sC   € ô ˜ ¨dÓ3€IÞÜ�I‹ÐØˆ;©ÓGª #¸YÑ6FŸ#©ÑGÑGÐGùÒGs   ¦	>³>c                óX  • UR                   n[        [        U5      UR                  UR	                  SS9[        U [        X1R                  5      5      U Vs/ s HD  n[        UR                  UR                  UR                  S-   [        UR                  S5      S9PMF     snS9$ s  snf )Nr]   r	  r­   rÛ   )rŽ   r�   r|   r‘   )r   r™   rš   r–   r—   )r  r“   r  r‰  r  rb  r  rZ  rŒ   rŽ   r�   r|   Úroundr‘   )rh   Úresultr2  r  Úcs        r3   Ú_chat_responser     s¢   € Ø�n‰n€GÜÜ˜Ó!Ø�l‰lØ× Ñ  fÐ Ð-Ü  Ô)?À×IYÑIYÓ)ZÓ[ñ
 ó	
ò �ô ØŸ™°·±À1Ç6Á6ÈAÁ:ÔUZÐ[\×[bÑ[bÐdeÓUfôñ ñ	
ñð ùò

s   ÁAB'z	/api/chatc              ƒ  óª  #   • [        U R                  5      n[        R                  " US   5        [	        US   U R
                  U R                  S9I S h  v•N nU(       d  [        SSU R                   S35      e[        U R
                  X R                  [        R                  " U5      5      I S h  v•N n[        U R                  X25      $  Nx N7f)Nrã   ©rl   rÒ   úNo chunks for document õ    â€” is it ingested?)ró   rh   r%   r0   r$   rj   rl   r   Úagent_answerro   r   Úfrom_rowr  )Úreqrî   r2  r  s       r3   Úchatr  °  s¤   é € ä
�3—:‘:Ó
€CÜ×Ò�S˜‘^Ô$Ü˜C 	™N¨C¯L©L¸C¿E¹EÑB×B€FÞÜ˜CÐ#:¸3¿:¹:¸,ÐFZÐ![Ó\Ð\Ü §¡¨f·h±hÔ@P×@YÒ@YÐZ]Ó@^Ó_×_€FÜ˜#Ÿ*™* fÓ5Ð5ñ	 Cñ `ùs%   ‚ACÁCÁACÂ4CÂ5CÃCz/api/chat/streamc              ƒ  ó`  ^ ^^#   • [        T R                  5      n[        R                  " US   5        [	        US   T R
                  T R                  S9I Sh  v•N mT(       d  [        SST R                   S35      e[        R                  " U5      mUUU 4S jn[        U" 5       SS	9$  NR7f)
z�Same contract as /api/chat, streamed: server-sent events carrying the
answer text as it generates ('delta'), then the full response ('done').rã   r  NrÒ   r  r  c                óÎ  >#   •  [        TR                  TTR                  T5        S h  v•N u  pU S:X  a"  S[        R                  " SUS.5       S37v •  M3  [        TR                  UT5      R                  5       nS[        R                  " SUS.5       S37v •  Mz   Nu
 g ! [         a3  nS[        R                  " S[        U5      S.5       S37v •   S nAg S nAff = f7f)	NÚdeltaúdata: )r_   r   ú

ræ  )r_   r  r“  )r_   Údetail)
Úagent_answer_streamrj   ro   r]   r^   r  rh   r  r\   ri   )r‰  r  rú  rb   r2  Úidentityr  s       €€€r3   ÚeventsÚchat_stream.<locals>.eventsÇ  sÑ   øé € ð	SÜ':¸3¿<¹<ÈÐQT×QYÑQYÐ[cÔ'd÷ W‘m�dØ˜7“?Ø"¤4§:¢:°wÈ'Ñ.RÓ#SÐ"TÐTXÐYÕYä)¨#¯*©*°g¸vÓF×QÑQÓS�DØ"¤4§:¢:°vÈ$Ñ.OÓ#PÐ"QÐQUÐVÕVñWÑ'døô ó 	SØœ4Ÿ:š:¨wÄ#ÀcÃ(Ñ&KÓLÐMÈTÐR×RÐRûð	SüsO   ƒC%…"B% §B#«B!¬B#¯A2B% Â!B#Â#B% Â$C%Â%
C"Â/)CÃC%ÃC"Ã"C%útext/event-streamrW   )ró   rh   r%   r0   r$   rj   rl   r   r   r  r   )r  rî   r(  r2  r'  s   `  @@r3   Úchat_streamr+  »  s’   úé € ô �3—:‘:Ó
€CÜ×Ò�S˜‘^Ô$Ü˜C 	™N¨C¯L©L¸C¿E¹EÑB×B€FÞÜ˜CÐ#:¸3¿:¹:¸,ÐFZÐ![Ó\Ð\ä×(Ò(¨Ó-€H÷	Sô ™V›XÐ2EÑFÐFñ# Cùs   …AB.ÁB,ÁAB.)rK   c                  óF   • \ rS rSr% S\S'   S\S'   SrS\S'   SrS	\S
'   Srg)ÚBatchRequestiÛ  rg   rh   ri   Ú	questionsrk   rl   rm   rn   ro   r1   Nrp   r1   r2   r3   r-  r-  Û  s"   ‡ ØƒKð ƒNØ€A€sƒKà-3€DÐ
*Ö3r2   r-  z
/api/batchc              ƒ  óÚ  ^ ^^#   • [        T R                  5      nU(       d  [        SS5      e[        T R                  5      nUS   m[
        R                  " T5        [        TUS   SS9I Sh  v•N nU(       d  [        SS	T R                   S
35      e[        R                  " U5      mSUU 4S jjnSUU 4S jjn[        R                  T R                  UT R                  XE5      $  Ny7f)z Start a run. The work continues if the page is closed; the page keeps
the returned job id in sessionStorage and re-attaches through the state
and events routes.r  u4   no questions â€” the batch window takes one per linerã   r   r­   r  NrÒ   r  r  c              “  óJ   >#   • [        TU TR                  S9I S h  v•N $  N7f)Nr  )r$   rl   )rj   rã   r  s    €€r3   Úretrieve_oneÚ!batch_start.<locals>.retrieve_oneø  s   øé € Ü˜g x°3·5±5Ñ9×9Ð9Ñ9ùs   ƒ#œ!�#c              “  ó”   >#   • [        XTR                  T5      I S h  v•N n[        TR                  X!5      R	                  SS9$  N'7f)Nr]   r	  )r  ro   r  rh   r  )rj   r2  r  r'  r  s      €€r3   Ú
answer_oneÚbatch_start.<locals>.answer_oneû  s@   øé € Ü# H°c·h±hÀÓI×IˆÜ˜cŸj™j¨&Ó9×DÑDÈ&ÐDÐQÐQñ Jùs   ƒAžAŸ(A)rj   ri   r9   úList[RetrievedChunk])rj   ri   r2  r6  r9   úDict[str, Any])r"   r.  r   ró   rh   r%   r0   r$   r   r  rN   r=   ro   )r  r.  rî   Úprober1  r4  rã   r'  s   `     @@r3   Úbatch_startr9  å  sÌ   úé € ô
   §¡Ó.€IÞÜ˜CÐ!WÓXÐXÜ
�3—:‘:Ó
€CØ�)‰n€Gô ×Ò�WÔÜ˜7 I¨a¡L°AÑ6×6€EÞÜ˜CÐ#:¸3¿:¹:¸,ÐFZÐ![Ó\Ð\ä×(Ò(¨Ó-€H÷:ð :÷Rð Rô �;‰;�s—z‘z 9¨c¯h©h¸ÓQÐQñ 7ùs   …A*C+Á/C)Á0A:C+c                óV   • [         R                  U 5      nUc  [        SSU  S35      eU$ )NrÒ   zno run u     â€” the server no longer has it)rN   r²   r   ©rB   Ústates     r3   Ú
_run_stater=    s3   € Ü�I‰I�fÓ€EØ�}ô ˜C 7¨6¨(Ð2RÐ!SÓTÐTØ€Lr2   z/api/batch/{job_id}c                ó   • [        U 5      $ rŸ   )r=  )rB   s    r3   Úbatch_stater?    s   € ä�fÓÐr2   z/api/batch/{job_id}/eventsc              ƒ  óL   ^ #   • [        T 5        U 4S jn[        U" 5       SS9$ 7f)u¾   One server-sent event per question completing â€” index, total, outcome â€”
then a final `done` event. Watching a run and re-attaching to one are
different needs; this is the watching route.c                óŽ   >#   • [         R                  T5        S h  v•N n S[        R                  " U 5       S37v •  M(   N#
 g 7f)Nr#  r$  )rN   r(  r]   r^   )ÚeventrB   s    €r3   ÚstreamÚbatch_events.<locals>.stream  s:   øé € Ü Ÿ<™<¨Ô/÷ 	3�%Øœ4Ÿ:š: eÓ,Ð-¨TÐ2Õ2ñ	3Ñ/ùs%   ƒA™A�AžA¡ AÁAÁAr*  rW   )r=  r   )rB   rC  s   ` r3   Úbatch_eventsrE    s'   øé € ô
 ˆvÔõ3ô ™V›XÐ2EÑFÐFùs   ƒ!$c                ó�   •  [        U R                  5      n[        R                  " US   US   US   5      $ ! [         a     gf = f)zšThe export filename's document part, down the label precedence. A run
whose document was removed mid-session still exports; it just gets the
generic stem.Údocumentr   r*   rä   )ró   rh   r   r   Úexport_stem)r<  rî   s     r3   Ú_export_stemrI    sL   € ðÜ�u—|‘|Ó$ˆô ×Ò  G¡¨c°'©l¸CÀ
¹OÓLÐLøô ó Ùðús   ‚8 ¸
AÁAz/api/batch/{job_id}/export.xlsxc           
     óz   • [        U 5      n[        [        R                  " U5      SSS[	        U5       SU  S30S9$ )NzAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetúContent-Dispositionúattachment; filename="batch-rM  z.xlsx"©rU   rÏ   )r=  r   r   Ú
write_xlsxrI  r;  s     r3   Úbatch_export_xlsxrO  *  sM   € ä�vÓ€EÜÜ×Ò˜%Ó ØVØ&Ð*FÄ|ÐTYÓGZÐF[Ð[\Ð]cÐ\dÐdjÐ(kÐlñð r2   z/api/batch/{job_id}/export.csvc           
     óz   • [        U 5      n[        [        R                  " U5      SSS[	        U5       SU  S30S9$ )Nztext/csv; charset=utf-8rK  rL  rM  z.csv"rM  )r=  r   r   Ú	write_csvrI  r;  s     r3   Úbatch_export_csvrR  4  sM   € ä�vÓ€EÜÜ×Ò˜ÓØ,Ø&Ð*FÄ|ÐTYÓGZÐF[Ð[\Ð]cÐ\dÐdiÐ(jÐkñð r2   r8   )r9   r   )r9   zfrozenset[str])r9   zDict[str, str])rV   rz   r€   rz   r³   rz   r9   z#tuple[Optional[str], Optional[str]]rŸ   )r{   rz   r½   rg   rV   rz   r9   ri   )r9   ri   )r9   r   )rÉ   ri   r9   r   )rí   z'tuple[Any, ...]'r9   r7  )rh   rg   r9   r7  )r9   z
list[dict])
rÉ   ri   r  ri   r  rg   r  rg   r9   rg   )rh   rg   r  r   r9   r?  )rh   rg   rã   ri   r9   rz   )rã   ri   r9   z
List[Path])rã   ri   r9   rg   )rh   rg   r9   r?  )rh   rg   r9   r   )r  r   r9   r?  )g{®Gázt?)rG  r?  rH  r?  rI  r�   r9   r…   )rh   rg   rZ  r‘  r9   r•   )rh   rg   r9   z
List[Flag])r  r7  r<  r   rŠ  r   r9   ztuple[str, str])r  r?  rA   ri   r©  r‘  r9   r:   )r  r?  r<  ri   rŠ  r   r9   r:   )r  r?  r9   r:   )r  r?  r9   r}   )r  r?  rÙ  úOptional[bytes]rÚ  rz   r9   rz   )rB   ri   rÙ  rS  rÚ  rz   r9   r:   )r   r   rä   ri   rÚ  rz   r*   rz   rç   rz   rè   rz   ré   r‚   rê   rz   rë   r‚   rÇ   r…   rÑ  ri   r9   r�  )rô  )rB   ri   r  rg   r9   r�  )rZ  r‘  r9   r‘  )rh   rg   r9   r“   )r  re   r9   r“   )r  re   r9   r   )r  r-  r9   r!   )rB   ri   r9   r!   )rB   ri   r9   r   )r<  r!   r9   ri   )rB   ri   r9   r   )šr  Ú
__future__r   rÃ   r]   Úrer*  r�  r;   rû  Úpathlibr   Útypingr   r   r   r   r	   r
   Úfastapir   r   r   Úfastapi.responsesr   r   r   r   Úlogurur   Úpydanticr   Úquber.files.cacher   Úquber.playgroundr   r   r   r   r   r   r   r   Ú!quber.playground.answers.documentr   Ú$quber.playground.answers.expectationr   r  r   r&  Úquber.playground.batchr    r!   r"   Úquber.playground.retrievalr#   r$   Úquber.playground.tracingr%   Úquber.settingsr&   Ú__file__Ú	with_namerÁ   Ú
playgroundÚdata_dirr!  r"  rß  rŸ  ÚappÚinstallÚLOGIN_ENABLEDÚEventr/   Úon_eventr>   rQ   r²   rc   re   rw   rŒ   r“   r¨   rª   rø   r±   Úcompiler®   rµ   r¾   rË   rÐ   r×   rØ   rt   rï   ró   rþ   r   r  r  r  r  r%  r.  Údeleter9  r<  rA  rJ  rb  rd  rh  rC   ÚLockrÿ  r¤  Ú	SemaphoreÚupload_concurrencyr¸  r–  rë  rì  r€  rŽ  r�  r¬  r¶  rº  rÏ  rÔ  rÜ  rò  Úpostr  r  r  r  r  r  r+  Ú
BatchStorerP   rN   r-  r9  r=  r?  rE  rI  rO  rR  r1   r2   r3   Ú<module>rt     s¼  ðò1õf #ã Û Û 	Û Û Û Û Ý ß D× Dç 3Ñ 3ß UÓ UÝ Ý å (ß V× VÓ VÝ >Ý GÝ Uß IÑ Iß ?Ý 1Ý 'á	ˆh‹×	!Ñ	! (Ó	+€Ù‹>×$Ñ$×-Ñ-€Ø
�YÑ
€Ù�‹N×"Ñ" 1Ñ%€	áÐ&Ñ'€ð —’˜S¡,£.×";Ñ";Ó<€ð
 —?’?Ó$Ð ð ‡�ˆiÓó7ó ð7ð ‡�ˆiÓóó ðð ‡�ˆ u€Ð-óFó .ðFô*4�)ô 4ô"�	ô "ô0�Iô ô	!�9ô 	!ôIô<ñ Ó €Ù Ó"€ð —:’:Ð1Ó2Ð ð7Øð7Ø!.ð7Ø8Eð7à(ô7ö !ô ð" ‡�ˆ˜\€Ð*óQó +ðQð ‡�Ð	Óóó  ðð1ð ˆmó ôô$ð ‡�Ð	Óóó ðô2"�Iô "ôð ‡�Ð$Ó%ó#ó &ð#ôLôô,
ð ‡�Ð%Ó&ó"eó 'ð"eðJ ‡�Ð	%Ó&ó<ó 'ð<ô=ö
ôYôx!ˆ9ô !ð ‡�Ð	'¸¸T¹
€ÐCóó Dðð< €€oÓ Ø�^Š^Ó€
Ø€	ð ×#Ò#¡L£N×$=Ñ$=×$PÑ$PÓQ€ð ÒSð€ð ¨IÑ6Ø¨FÑ3ñ
€ð €ð  Ð'Ð ô&
ôR�	ô ô.Jô&ô4ô:ôz*ôZ2ô<h ðV ‡‚ˆ-¨	€Ð2ð !ØØ Ø!%ØØ Ø!ØØðK+ØðK+àðK+ð ðK+ð ð	K+ð
 ðK+ð ðK+ð ðK+ð ðK+ð ðK+ð ðK+ð ðK+ð ôK+ó 3ðK+ð\ ‡�Ð	¨i€Ð8õ;ó 9ð;ôAôHô ð  ‡‚ˆ+ l€Ð3ó6ó 4ð6ð ‡‚Ð
ÓóGó ðGð6 �o‹oÓ€Ù˜+Ñ&€ô4�9ô 4ð ‡‚ˆ, x€Ð0óRó 1ðRô8ð ‡�Ð	¨x€Ð8óó 9ðð ‡�Ð	%Ó&ó
Gó 'ð
GôMð ‡�Ð	*Ó+óó ,ðð ‡�Ð	)Ó*óó +ñr2   