ó
    À‘´j='  ã                  ój  • S r SSKJ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  SSKJr  SSKrSSKrSSKJr  SSKJr  SS	KJr  S
rSrSrSrSrSSSS.r\	\/S4   rSS jrSS jr S r!S S jr"S!S jr#S"S jr$S#S jr%S$S jr&S%S jr'                S&S jr(S'S jr)\RT                  4S(S jjr+g))aÐ  The GPU stages of an upload, run on the RunPod worker.

The hosted playground has no GPU. Two stages of an upload need one: the
docling parse of the document, and the embedding of its chunks at
ingestion. Both go to the RunPod serverless worker under the contract the
inbound-drop Lambda already uses with it: the job payload carries presigned
S3 URLs for everything the worker reads and writes, the worker holds no
credentials, and a completion marker written strictly after the outputs says
the outputs are whole. The files land in the document's own prefix in the
extracts bucket, so a parse's artifacts and an embedding's vectors sit
beside everything else the document owns.

``configured`` says whether this host sends work there at all. It needs
RUNPOD_ENDPOINT_ID, RUNPOD_API_KEY and QUBER_PLAYGROUND_ARTIFACTS_URI all set.
Without them, the app runs the docling parse on the local GPU in a
`quber fuse` subprocess, and the chunks embed locally in the ingest process.
é    )ÚannotationsN)ÚPath)ÚAnyÚCallableÚDictÚListÚOptional)Úurlparse)ÚConfig)Úlogger)Úget_settingsi@8  é   é
   i4  ÚdoclingÚ
confidenceÚcells)Údocumentr   r   c                 ó    • [        5       R                  n [        U R                  =(       a    U R                  =(       a    U R
                  5      $ ©N)r   Ú
playgroundÚboolÚrunpod_endpoint_idÚrunpod_api_keyÚartifacts_uri)Úps    Ú2/home/mande/repo/quber/src/quber/playground/gpu.pyÚ
configuredr   2   s4   € Ü‹×!Ñ!€AÜ�×$Ñ$×M¨×)9Ñ)9×M¸a¿o¹oÓNÐNó    c                óÆ   • [        5       R                  R                  =(       d    Sn[        U5      nUR                  UR
                  R                  S5      S-   U -   S-   4$ )NÚ Ú/)r   r   r   r
   ÚnetlocÚpathÚstrip)Údoc_keyÚuriÚparseds      r   Ú_bucket_and_prefixr(   7   sP   € Ü
‹.×
#Ñ
#×
1Ñ
1×
7°R€CÜ�c‹]€FØ�=‰=˜&Ÿ+™+×+Ñ+¨CÓ0°3Ñ6¸Ñ@À3ÑFÐFÐFr   c                 ó†   • [         R                  " S[        5       R                  R                  =(       d    S[        SS9S9$ )NÚs3z	us-east-1Ús3v4)Úsignature_version)Úregion_nameÚconfig)Úboto3Úclientr   Ús3_cacheÚregionr   © r   r   Ú_s3r4   =   s6   € ô �<Š<ØÜ “N×+Ñ+×2Ñ2×A°kÜ¨Ñ/ñð r   c                ó@   • [        5       R                  XUS.[        S9$ )N©ÚBucketÚKey)ÚParamsÚ	ExpiresIn)r4   Úgenerate_presigned_urlÚURL_EXPIRY_SECONDS)ÚmethodÚbucketÚkeys      r   Ú_presignr@   H   s'   € Ü‹5×'Ñ'Ø°Ñ5ÔASð (ð ð r   c                óL  • [        5       R                  n[        R                  " SUR                   S3SU 0SSUR
                   30SS9nUR                  5         UR                  5       n[        R                  " SUR                  S	5      UR                  S
5      5        US	   $ )Núhttps://api.runpod.ai/v2/z/runÚinputÚAuthorizationúBearer é   )ÚjsonÚheadersÚtimeoutzrunpod job {} ({})ÚidÚstatus)r   r   ÚhttpxÚpostr   r   Úraise_for_statusrG   r   ÚinfoÚget)Úpayloadr   ÚresponseÚjobs       r   Ú_submitrT   N   s–   € Ü‹×!Ñ!€AÜ�zŠzØ
# A×$8Ñ$8Ð#9¸Ð>Ø�wÐØ  G¨A×,<Ñ,<Ð+=Ð">Ð?Øñ	€Hð ×ÑÔØ
�-‰-‹/€CÜ
‡K‚KÐ$ c§g¡g¨d£m°S·W±W¸XÓ5FÔGØˆt‰9Ðr   c                óÌ   • [        5       n UR                  XS9nUS   R	                  5       U:  a  gUS   R                  5       $ ! UR                  R                   a     gf = f)a  The object's bytes, or None when it does not exist or predates ``since``.

Nothing under a document's prefix is ever deleted, so a previous run's
marker and log stay where they are; a run tells its own apart by the
object's write time against the moment it submitted the job.r6   NÚLastModifiedÚBody)r4   Ú
get_objectÚ
exceptionsÚ	NoSuchKeyÚ	timestampÚread)r>   r?   Úsincer*   rR   s        r   Ú_read_if_freshr^   \   sm   € ô 
‹€BðØ—=‘=¨�=Ð8ˆð �Ñ×)Ñ)Ó+¨eÓ3ØØ�FÑ× Ñ Ó"Ð"øð	 �=‰=×"Ñ"ó Ùðús   ŒA ÁA#Á"A#c                óP   • [        XU5      nUb  [        R                  " U5      $ S $ r   )r^   rG   Úloads©r>   r?   r]   Úbodys       r   Ú_read_json_if_freshrc   l   s'   € Ü˜& uÓ-€DØ#Ñ/Œ4�:Š:�dÓÐ9°TÐ9r   c                óH   • [        XU5      nUb  UR                  SS5      $ S $ )Núutf-8Úreplace)r^   Údecodera   s       r   Ú_read_text_if_freshrh   q   s)   € Ü˜& uÓ-€DØ.2Ñ.>ˆ4�;‰;�w 	Ó*ÐHÀDÐHr   c                óÔ   • [        5       R                  n[        R                  " SUR                   SU  3SSUR
                   30SS9nUR                  5         UR                  5       $ )NrB   z/status/rD   rE   rF   )rH   rI   )r   r   rL   rP   r   r   rN   rG   )Újob_idr   rR   s      r   Ú_job_statusrk   v   sg   € Ü‹×!Ñ!€AÜ�yŠyØ
# A×$8Ñ$8Ð#9¸À&ÀÐJØ  G¨A×,<Ñ,<Ð+=Ð">Ð?Øñ€Hð
 ×ÑÔØ�=‰=‹?Ðr   c                óî  • [         R                   " 5       nSnSn	Sn
 [        XU5      nUb†  U(       a7  [        XU5      =(       d    SnUR                  5       US  H  nU" U5        M     UR	                  S5      S:w  a1  [        SUR	                  SS	5       S
UR	                  SS5       35      eU$ [         R                   " 5       U-
  nXã:”  a  [        S[        U5       SU S35      e[        U5      nUR	                  S5      nUS;   aR  [        SU SU S
[        R                  " UR	                  S5      =(       d    UR	                  S5      5      SS  35      eUS:X  ax  U
=(       d    [         R                   " 5       n
[         R                   " 5       U
-
  S[        -  :”  a7  [        SU S[        R                  " UR	                  S5      5      SS  35      eU(       aw  [         R                   " 5       U	-
  [        :¼  aW  [         R                   " 5       n	[        XU5      nU(       a/  UR                  5       nUUS  H  nU" U5        M     [        U5      n[         R                  " [        5        GMZ  )a!  Poll until the marker exists, mirroring the worker's log into ``log``
as it grows. The marker is the truth about the outputs; the job's own
status is watched beside it so a job that ends without writing one, or
that RunPod fails or cancels, is reported at once rather than at the
deadline.r   g        Nr    rK   Úcompletezworker failed at ÚstageÚ?z: Úerrorzno error recordedzno completion marker after zs (Ú))ÚFAILEDÚ	CANCELLEDÚ	TIMED_OUTzworker job Ú Úoutputi   Ú	COMPLETEDé   z0 completed without a completion marker; output: )Útimerc   rh   Ú
splitlinesrP   ÚRuntimeErrorÚTimeoutErrorÚintrk   rG   ÚdumpsÚPOLL_SECONDSÚLOG_MIRROR_SECONDSÚlenÚsleep)r>   Ú
marker_keyÚlog_keyÚdeadlineÚlogrj   r]   ÚstartedÚmirroredÚlast_mirrorÚended_atÚmarkerÚtextÚlineÚelapsedrK   ÚstateÚliness                     r   Ú_wait_for_markerr‘   �   s=  € ô �iŠi‹k€GØ€HØ€KØ $€HØ
Ü$ V¸Ó?ˆØÑÞÜ*¨6¸EÓB×HÀb�Ø ŸO™OÓ-¨h¨iÓ8�DÙ˜–Iñ 9à�z‰z˜(Ó# zÓ1Ü"Ø'¨¯
©
°7¸CÓ(@Ð'AÀÀFÇJÁJÈwÐXkÓDlÐCmÐnóð ð ˆMÜ—)’)“+ Ñ'ˆØÓÜÐ!<¼SÀ»\¸NÈ#ÈjÈ\ÐYZÐ[Ó\Ð\Ü˜VÓ$ˆØ—
‘
˜8Ó$ˆØÐ8Ó8ÜØ˜f˜X Q u g¨R´·
²
¸6¿:¹:ÀgÓ;N×;fÐRX×R\ÑR\Ð]eÓRfÓ0gÐhlÐilÐ0mÐ/nÐoóð ð �KÓð  ×.¤4§9¢9£;ˆHÜ�yŠy‹{˜XÑ%¨¬LÑ(8Ó8Ü"Ø! & Ð)YÜ—z’z &§*¡*¨XÓ"6Ó7¸¸Ð=Ð>ð@óð ö ”t—y’y“{ [Ñ0Ô4FÓFÜŸ)š)›+ˆKÜ& v¸Ó>ˆDÞØŸ™Ó)�Ø! ( )Ó,�DÙ˜–Iñ -ä˜u›:�Ü�
Š
”<Ô òM r   c                ó°  • [        U 5      u  p4U U  S3nU U  S3n[        5       n[        R                  " 5       S-
  nU [        SX4 U  S35      [        R                  5        V	V
s0 s H  u  pšU	[        SX4 U  SU
 S35      _M     sn
n	[        SX55      [        SX65      S	.n[        U5      nU" S
U 35        [        X5U[        X,U5      nU" SUR                  S5       SUR                  S5       SUR                  S5       S35        [        R                  5        H.  n
UR                  X4 U  SU
 S3[        X SU
 S3-  5      5        M0     gs  sn
n	f )z¨Parse the document on the worker and bring the three parse artifacts
into ``workdir`` for fusion. The PDF must already be in the document's
prefix as ``<doc_key>.pdf``.z.parse.complete.jsonz
.parse.logé<   rX   z.pdfÚ
put_objectÚ.z.json)ÚbaseÚpdf_urlÚartifact_urlsÚ
marker_urlÚlog_urlz)parse submitted to the GPU worker as job zparse complete: Úpagesz pages, Útablesz	 tables, Útotal_secondsÚsN)r(   r4   ry   r@   ÚPARSE_ARTIFACTSÚitemsrT   r‘   ÚPARSE_DEADLINE_SECONDSrP   ÚvaluesÚdownload_fileÚstr)r%   Úworkdirr†   r>   Úprefixrƒ   r„   r*   r]   ÚnameÚstemrQ   rj   r‹   s                 r   Úparser©   ¶   s‹  € ô (¨Ó0�N€FØ�8˜G˜9Ð$8Ð9€JØ�˜˜	 Ð,€GÜ	‹€Bä�IŠI‹K˜"Ñ€EàÜ˜L¨&°H¸W¸IÀTÐ2JÓKô .×3Ñ3Ô5ô
â5‘
�ð ”(˜<¨°8¸G¸9ÀAÀdÀVÈ5Ð1QÓRÒRÙ5ò
ô ˜|¨VÓ@Ü˜L¨&Ó:ñ	€Gô �WÓ€FÙÐ
3°F°8Ð<Ô=Ü˜f°'Ô;QÐSVÐ`eÓf€FÙØ
˜6Ÿ:™: gÓ.Ð/¨x¸¿
¹
À8Ó8LÐ7MÈYÐW]×WaÑWaÐbqÓWrÐVsÐstÐuôô  ×&Ñ&Ö(ˆØ
×Ñ˜ 8¨G¨9°A°d°V¸5Ð!AÄ3ÀwÐS\Ð\]Ð^bÐ]cÐchÐQiÑGiÓCjÖkò )ùó
s   Á&!E
c           
     ó  • [        U 5      u  p4U U  S3nU U  S3nU U  S3nU U  S3n[        5       n	[        R                  " 5       S-
  n
U	R                  X5[        R
                  " SU05      R                  S5      S9  S	U [        S
X55      [        SX65      [        SX75      [        SX85      S.n[        U5      nU" S[        U5       SU 35        [        X7U[        X,U
5      n[        R                  " U	R                  X6S9S   R                  5       5      S   n[        U5      [        U5      :w  a$  [        S[        U5       S[        U5       S35      eU" S[        U5       SUR!                  S5       S35        U$ )zƒEmbed the chunks on the worker. The texts go up as one file, the
vectors come back as one file, both kept in the document's prefix.z.chunks.jsonz.vectors.jsonz.embed.complete.jsonz
.embed.logr“   Útextsre   )r7   r8   rW   ÚembedrX   r”   )Útaskr–   Ú
chunks_urlÚvectors_urlr™   rš   zembedding of z+ chunks submitted to the GPU worker as job r6   rW   Úvectorszworker returned z vectors for z chunkszembedding complete: z vectors in r�   rž   )r(   r4   ry   r”   rG   r~   Úencoder@   rT   r�   r‘   ÚEMBED_DEADLINE_SECONDSr`   rX   r\   r{   rP   )r%   r«   r†   r>   r¦   Ú
chunks_keyÚvectors_keyrƒ   r„   r*   r]   rQ   rj   r‹   r°   s                  r   r¬   r¬   Ô   s�  € ô (¨Ó0�N€FØ�8˜G˜9 LÐ1€JØ�H˜W˜I ]Ð3€KØ�8˜G˜9Ð$8Ð9€JØ�˜˜	 Ð,€GÜ	‹€BÜ�IŠI‹K˜"Ñ€EØ‡M�M˜´d·j²jÀ'È5ÐAQÓ6R×6YÑ6YÐZaÓ6b€MÑcàØÜ˜|¨VÓ@Ü ¨fÓBÜ˜|¨VÓ@Ü˜L¨&Ó:ñ€Gô �WÓ€FÙˆ-œ˜E›
�|Ð#NÈvÈhÐWÔXÜ˜f°'Ô;QÐSVÐ`eÓf€FÜ�jŠj˜Ÿ™¨f˜ÐFÀvÑN×SÑSÓUÓVÐW`Ña€GÜ
ˆ7ƒ|”s˜5“zÓ!ÜÐ-¬c°'«l¨^¸=ÌÈUËÈÐT[Ð\Ó]Ð]ÙÐ
œs 7›|˜n¨L¸¿¹ÀOÓ9TÐ8UÐUVÐWÔXØ€Nr   )Úreturnr   )r%   r¤   rµ   ztuple[str, str])r=   r¤   r>   r¤   r?   r¤   rµ   r¤   )rQ   úDict[str, Any]rµ   r¤   )r>   r¤   r?   r¤   r]   Úfloatrµ   zOptional[bytes])r>   r¤   r?   r¤   r]   r·   rµ   zOptional[Dict[str, Any]])r>   r¤   r?   r¤   r]   r·   rµ   úOptional[str])rj   r¤   rµ   r¶   )r>   r¤   rƒ   r¤   r„   r¸   r…   r·   r†   ÚLogrj   r¤   r]   r·   rµ   r¶   )r%   r¤   r¥   r   r†   r¹   rµ   ÚNone)r%   r¤   r«   z	List[str]r†   r¹   rµ   zList[List[float]]),Ú__doc__Ú
__future__r   rG   ry   Úpathlibr   Útypingr   r   r   r   r	   Úurllib.parser
   r/   rL   Úbotocore.configr   Úlogurur   Úquber.settingsr   r<   r   r€   r¡   r²   rŸ   r¤   r¹   r   r(   r4   r@   rT   r^   rc   rh   rk   r‘   r©   rO   r¬   r3   r   r   Ú<module>rÃ      s÷   ðñõ$ #ã Û Ý ß 6Õ 6Ý !ã Û Ý "Ý å 'àÐ Ø€ØÐ ð !Ð Ø Ð ð  )¸ÈwÑW€à�ˆu�dˆ{Ñ€ôOô
Gòôôô#ô :ô
Iô
ð2!Øð2!Ø ð2!Ø+8ð2!ØDIð2!ØPSð2!Ø]`ð2!Øinð2!àô2!ôjlð< 6<·[±[÷ r   